diff --git a/server/collectors/LlmProbe.js b/server/collectors/LlmProbe.js index bdc6735..8458561 100644 --- a/server/collectors/LlmProbe.js +++ b/server/collectors/LlmProbe.js @@ -444,6 +444,7 @@ export class LlmProbe { // Model info from /v1/models — 401/403 means protected; other failure = down let modelsOk = false; let owned = null; + let servedModelId = null; try { const modelsRes = await this._fetch(`${this.baseUrl}/v1/models`); const auth = this._noteAuthStatus(modelsRes.status); @@ -454,7 +455,8 @@ export class LlmProbe { modelsOk = true; const modelsData = await modelsRes.json(); const model = modelsData?.data?.[0]; - this.modelId = normalizeModelId(model?.id || null); + servedModelId = normalizeModelId(model?.id || null); + this.modelId = servedModelId; // Drop HF hub cache paths from modelPath if /v1/models id was a cache dir if (isHfHubCachePath(model?.id)) this.modelPath = null; // ds4-server uses context_length; vLLM uses max_model_len @@ -527,6 +529,9 @@ export class LlmProbe { /* metrics optional */ } await this._enrichSglangModelInfo(); + // Native SGLang endpoints expose the storage path (often just `/model`). + // Keep that as modelPath, but show/use the client-facing served model ID. + if (servedModelId) this.modelId = servedModelId; return this._getSnapshot(); } diff --git a/server/collectors/__tests__/LlmProbe.sglang.test.js b/server/collectors/__tests__/LlmProbe.sglang.test.js index a08749f..18b7b4e 100644 --- a/server/collectors/__tests__/LlmProbe.sglang.test.js +++ b/server/collectors/__tests__/LlmProbe.sglang.test.js @@ -89,7 +89,7 @@ test("_probeIsSglang: prefers /server_info and skips deprecated /get_server_info assert.deepEqual(hits, ["/server_info"]); }); -test("probe: prefers /server_info and /model_info over deprecated aliases", async () => { +test("probe: prefers current SGLang endpoints while retaining the served model ID", async () => { const probe = new LlmProbe({ lanIp: "10.0.0.1" }, 30000); probe.serverIsOpenAI = true; probe.backendType = "sglang"; @@ -135,7 +135,8 @@ test("probe: prefers /server_info and /model_info over deprecated aliases", asyn }; const snap = await probe.probe(); assert.equal(snap.backend, "sglang"); - assert.equal(snap.modelId, "org/ShortName"); + assert.equal(snap.modelId, "org/model"); + assert.equal(snap.modelPath, "org/ShortName"); assert.equal(hits.includes("/get_server_info"), false); assert.equal(hits.includes("/get_model_info"), false); assert.equal(hits.includes("/server_info"), true); @@ -548,3 +549,52 @@ test("_applySglangPrefillSplit does not clobber server_info tok/s", () => { assert.equal(probe.lastTokenCounts.output, 150); assert.equal(probe.cachedPrefillTps, 0); // first split sample seeds }); + +test("probe: SGLang keeps the served model ID when native info uses a local path", async () => { + const probe = new LlmProbe({ lanIp: "10.0.0.1" }, 8888); + probe.serverIsOpenAI = true; + probe.backendType = "sglang"; + probe.authOpen = true; + probe._lastDetectAt = Date.now(); + probe._fetch = async (url) => { + const u = String(url); + if (u.endsWith("/v1/models")) { + return { + ok: true, + status: 200, + json: async () => ({ + data: [ + { + id: "qwen3.8-27b-sglang", + owned_by: "sglang", + max_model_len: 262144, + }, + ], + }), + }; + } + if (u.endsWith("/get_server_info")) { + return { + ok: true, + status: 200, + json: async () => ({ model_path: "/model", context_length: 262144 }), + }; + } + if (u.endsWith("/get_model_info")) { + return { + ok: true, + status: 200, + json: async () => ({ model_path: "/model" }), + }; + } + if (u.endsWith("/metrics") || u.endsWith("/model_info")) { + return { ok: false, status: 404, json: async () => ({}), text: async () => "" }; + } + return { ok: false, status: 404, json: async () => ({}), text: async () => "" }; + }; + + const snap = await probe.probe(); + assert.equal(snap.modelId, "qwen3.8-27b-sglang"); + assert.equal(snap.modelPath, "/model"); + assert.equal(snap.contextLength, 262144); +});