mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 12:18:35 +00:00
fix: validate lmstudio discovered context lengths
This commit is contained in:
@@ -3,6 +3,7 @@ import { readProviderJsonArrayFieldResponse } from "openclaw/plugin-sdk/provider
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { SELF_HOSTED_DEFAULT_COST } from "openclaw/plugin-sdk/provider-setup";
|
||||
import { fetchWithSsrFGuard, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { asPositiveSafeInteger } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH } from "./defaults.js";
|
||||
import {
|
||||
buildLmstudioModelName,
|
||||
@@ -214,18 +215,8 @@ export async function ensureLmstudioModelLoaded(params: {
|
||||
}
|
||||
const matchingModel = preflight.models.find((entry) => entry.key?.trim() === modelKey);
|
||||
const loadedContextWindow = matchingModel ? resolveLoadedContextWindow(matchingModel) : null;
|
||||
const advertisedContextLimit =
|
||||
matchingModel?.max_context_length !== undefined &&
|
||||
Number.isFinite(matchingModel.max_context_length) &&
|
||||
matchingModel.max_context_length > 0
|
||||
? Math.floor(matchingModel.max_context_length)
|
||||
: null;
|
||||
const requestedContextLength =
|
||||
params.requestedContextLength !== undefined &&
|
||||
Number.isFinite(params.requestedContextLength) &&
|
||||
params.requestedContextLength > 0
|
||||
? Math.floor(params.requestedContextLength)
|
||||
: null;
|
||||
const advertisedContextLimit = asPositiveSafeInteger(matchingModel?.max_context_length) ?? null;
|
||||
const requestedContextLength = asPositiveSafeInteger(params.requestedContextLength) ?? null;
|
||||
const contextLengthForLoad =
|
||||
advertisedContextLimit === null
|
||||
? (requestedContextLength ?? LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH)
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
fetchLmstudioModels,
|
||||
} from "./models.fetch.js";
|
||||
import {
|
||||
mapLmstudioWireEntry,
|
||||
normalizeLmstudioConfiguredCatalogEntry,
|
||||
normalizeLmstudioProviderConfig,
|
||||
resolveLmstudioInferenceBase,
|
||||
@@ -160,6 +161,23 @@ describe("lmstudio-models", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("drops malformed discovered context metadata", () => {
|
||||
const model = mapLmstudioWireEntry({
|
||||
type: "llm",
|
||||
key: "bad-context",
|
||||
max_context_length: 32768.5,
|
||||
loaded_instances: [{ id: "loaded", config: { context_length: Number.POSITIVE_INFINITY } }],
|
||||
});
|
||||
|
||||
expect(model).toMatchObject({
|
||||
id: "bad-context",
|
||||
contextWindow: SELF_HOSTED_DEFAULT_CONTEXT_WINDOW,
|
||||
contextTokens: LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH,
|
||||
maxTokens: SELF_HOSTED_DEFAULT_MAX_TOKENS,
|
||||
loaded: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves reasoning capability for supported and unsupported options", () => {
|
||||
expect(resolveLmstudioReasoningCapability({ capabilities: undefined })).toBe(false);
|
||||
expect(
|
||||
@@ -499,6 +517,24 @@ describe("lmstudio-models", () => {
|
||||
expectLoadContextLength(fetchMock, 8192);
|
||||
});
|
||||
|
||||
it("omits malformed context lengths before loading models", async () => {
|
||||
const fetchMock = createModelLoadFetchMock({
|
||||
loadedContextLength: 4096.5,
|
||||
maxContextLength: 32768.5,
|
||||
});
|
||||
vi.stubGlobal("fetch", asFetch(fetchMock));
|
||||
|
||||
await expect(
|
||||
ensureLmstudioModelLoaded({
|
||||
baseUrl: "http://localhost:1234/v1",
|
||||
modelKey: "qwen3-8b-instruct",
|
||||
requestedContextLength: 8192.5,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expectLoadContextLength(fetchMock, LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH);
|
||||
});
|
||||
|
||||
it("throws when model discovery fails", async () => {
|
||||
const fetchMock = vi.fn(async () => ({
|
||||
ok: false,
|
||||
|
||||
@@ -209,11 +209,10 @@ export function resolveLoadedContextWindow(
|
||||
let contextWindow: number | null = null;
|
||||
for (const instance of loadedInstances) {
|
||||
// Discovery payload is external JSON, so tolerate malformed entries.
|
||||
const length = instance?.config?.context_length;
|
||||
if (length === undefined || !Number.isFinite(length) || length <= 0) {
|
||||
const normalized = asPositiveSafeInteger(instance?.config?.context_length);
|
||||
if (normalized === undefined) {
|
||||
continue;
|
||||
}
|
||||
const normalized = Math.floor(length);
|
||||
contextWindow = contextWindow === null ? normalized : Math.max(contextWindow, normalized);
|
||||
}
|
||||
return contextWindow;
|
||||
@@ -463,12 +462,7 @@ export function mapLmstudioWireEntry(entry: LmstudioModelWire): LmstudioModelBas
|
||||
return null;
|
||||
}
|
||||
const loadedContextWindow = resolveLoadedContextWindow(entry);
|
||||
const advertisedContextWindow =
|
||||
entry.max_context_length !== undefined &&
|
||||
Number.isFinite(entry.max_context_length) &&
|
||||
entry.max_context_length > 0
|
||||
? Math.floor(entry.max_context_length)
|
||||
: null;
|
||||
const advertisedContextWindow = asPositiveSafeInteger(entry.max_context_length) ?? null;
|
||||
const contextWindow = advertisedContextWindow ?? SELF_HOSTED_DEFAULT_CONTEXT_WINDOW;
|
||||
// Keep native/advertised context window metadata in catalog, but use a practical
|
||||
// default target for model loading unless callers explicitly override it.
|
||||
|
||||
Reference in New Issue
Block a user