mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 16:44:33 +00:00
Onboarding: enforce custom model context minimum
This commit is contained in:
@@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Onboarding/Custom providers: raise default custom-provider model context window to the runtime hard minimum (16k) and auto-heal existing custom model entries below that threshold during reconfiguration, preventing immediate `Model context window too small (4096 tokens)` failures. (#21653) Thanks @r4jiv007.
|
||||||
- Web UI/Assistant text: strip internal `<relevant-memories>...</relevant-memories>` scaffolding from rendered assistant messages (while preserving code-fence literals), preventing memory-context leakage in chat output for models that echo internal blocks. (#29851) Thanks @Valkster70.
|
- Web UI/Assistant text: strip internal `<relevant-memories>...</relevant-memories>` scaffolding from rendered assistant messages (while preserving code-fence literals), preventing memory-context leakage in chat output for models that echo internal blocks. (#29851) Thanks @Valkster70.
|
||||||
- Dashboard/Sessions: allow authenticated Control UI clients to delete and patch sessions while still blocking regular webchat clients from session mutation RPCs, fixing Dashboard session delete failures. (#21264) Thanks @jskoiz.
|
- Dashboard/Sessions: allow authenticated Control UI clients to delete and patch sessions while still blocking regular webchat clients from session mutation RPCs, fixing Dashboard session delete failures. (#21264) Thanks @jskoiz.
|
||||||
- Podman/Quadlet setup: fix `sed` escaping and UID mismatch in Podman Quadlet setup. (#26414) Thanks @KnHack and @vincentkoc.
|
- Podman/Quadlet setup: fix `sed` escaping and UID mismatch in Podman Quadlet setup. (#26414) Thanks @KnHack and @vincentkoc.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { CONTEXT_WINDOW_HARD_MIN_TOKENS } from "../agents/context-window-guard.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
import {
|
import {
|
||||||
applyCustomApiConfig,
|
applyCustomApiConfig,
|
||||||
@@ -326,6 +327,91 @@ describe("promptCustomApiConfig", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("applyCustomApiConfig", () => {
|
describe("applyCustomApiConfig", () => {
|
||||||
|
it("uses hard-min context window for newly added custom models", () => {
|
||||||
|
const result = applyCustomApiConfig({
|
||||||
|
config: {},
|
||||||
|
baseUrl: "https://llm.example.com/v1",
|
||||||
|
modelId: "foo-large",
|
||||||
|
compatibility: "openai",
|
||||||
|
providerId: "custom",
|
||||||
|
});
|
||||||
|
|
||||||
|
const model = result.config.models?.providers?.custom?.models?.find(
|
||||||
|
(entry) => entry.id === "foo-large",
|
||||||
|
);
|
||||||
|
expect(model?.contextWindow).toBe(CONTEXT_WINDOW_HARD_MIN_TOKENS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("upgrades existing custom model context window when below hard minimum", () => {
|
||||||
|
const result = applyCustomApiConfig({
|
||||||
|
config: {
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
custom: {
|
||||||
|
api: "openai-completions",
|
||||||
|
baseUrl: "https://llm.example.com/v1",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "foo-large",
|
||||||
|
name: "foo-large",
|
||||||
|
contextWindow: 4096,
|
||||||
|
maxTokens: 1024,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
reasoning: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
baseUrl: "https://llm.example.com/v1",
|
||||||
|
modelId: "foo-large",
|
||||||
|
compatibility: "openai",
|
||||||
|
providerId: "custom",
|
||||||
|
});
|
||||||
|
|
||||||
|
const model = result.config.models?.providers?.custom?.models?.find(
|
||||||
|
(entry) => entry.id === "foo-large",
|
||||||
|
);
|
||||||
|
expect(model?.contextWindow).toBe(CONTEXT_WINDOW_HARD_MIN_TOKENS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves existing custom model context window when already above minimum", () => {
|
||||||
|
const result = applyCustomApiConfig({
|
||||||
|
config: {
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
custom: {
|
||||||
|
api: "openai-completions",
|
||||||
|
baseUrl: "https://llm.example.com/v1",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "foo-large",
|
||||||
|
name: "foo-large",
|
||||||
|
contextWindow: 131072,
|
||||||
|
maxTokens: 4096,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
reasoning: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
baseUrl: "https://llm.example.com/v1",
|
||||||
|
modelId: "foo-large",
|
||||||
|
compatibility: "openai",
|
||||||
|
providerId: "custom",
|
||||||
|
});
|
||||||
|
|
||||||
|
const model = result.config.models?.providers?.custom?.models?.find(
|
||||||
|
(entry) => entry.id === "foo-large",
|
||||||
|
);
|
||||||
|
expect(model?.contextWindow).toBe(131072);
|
||||||
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
{
|
{
|
||||||
name: "invalid compatibility values at runtime",
|
name: "invalid compatibility values at runtime",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CONTEXT_WINDOW_HARD_MIN_TOKENS } from "../agents/context-window-guard.js";
|
||||||
import { DEFAULT_PROVIDER } from "../agents/defaults.js";
|
import { DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||||
import { buildModelAliasIndex, modelKey } from "../agents/model-selection.js";
|
import { buildModelAliasIndex, modelKey } from "../agents/model-selection.js";
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
@@ -16,10 +17,15 @@ import { normalizeAlias } from "./models/shared.js";
|
|||||||
import type { SecretInputMode } from "./onboard-types.js";
|
import type { SecretInputMode } from "./onboard-types.js";
|
||||||
|
|
||||||
const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434/v1";
|
const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434/v1";
|
||||||
const DEFAULT_CONTEXT_WINDOW = 4096;
|
const DEFAULT_CONTEXT_WINDOW = CONTEXT_WINDOW_HARD_MIN_TOKENS;
|
||||||
const DEFAULT_MAX_TOKENS = 4096;
|
const DEFAULT_MAX_TOKENS = 4096;
|
||||||
const VERIFY_TIMEOUT_MS = 30_000;
|
const VERIFY_TIMEOUT_MS = 30_000;
|
||||||
|
|
||||||
|
function normalizeContextWindowForCustomModel(value: unknown): number {
|
||||||
|
const parsed = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : 0;
|
||||||
|
return parsed >= CONTEXT_WINDOW_HARD_MIN_TOKENS ? parsed : CONTEXT_WINDOW_HARD_MIN_TOKENS;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detects if a URL is from Azure AI Foundry or Azure OpenAI.
|
* Detects if a URL is from Azure AI Foundry or Azure OpenAI.
|
||||||
* Matches both:
|
* Matches both:
|
||||||
@@ -600,7 +606,16 @@ export function applyCustomApiConfig(params: ApplyCustomApiConfigParams): Custom
|
|||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
reasoning: false,
|
reasoning: false,
|
||||||
};
|
};
|
||||||
const mergedModels = hasModel ? existingModels : [...existingModels, nextModel];
|
const mergedModels = hasModel
|
||||||
|
? existingModels.map((model) =>
|
||||||
|
model.id === modelId
|
||||||
|
? {
|
||||||
|
...model,
|
||||||
|
contextWindow: normalizeContextWindowForCustomModel(model.contextWindow),
|
||||||
|
}
|
||||||
|
: model,
|
||||||
|
)
|
||||||
|
: [...existingModels, nextModel];
|
||||||
const { apiKey: existingApiKey, ...existingProviderRest } = existingProvider ?? {};
|
const { apiKey: existingApiKey, ...existingProviderRest } = existingProvider ?? {};
|
||||||
const normalizedApiKey =
|
const normalizedApiKey =
|
||||||
normalizeOptionalProviderApiKey(params.apiKey) ??
|
normalizeOptionalProviderApiKey(params.apiKey) ??
|
||||||
|
|||||||
Reference in New Issue
Block a user