Skip to content

Commit a8c745a

Browse files
committed
fix: stop forcing native ollama num_ctx
1 parent 80047b1 commit a8c745a

3 files changed

Lines changed: 14 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
2020

2121
### Fixes
2222

23+
- Ollama: stop native `/api/chat` requests from copying catalog `contextWindow` or `maxTokens` into `options.num_ctx` unless `params.num_ctx` is explicitly configured, avoiding pathological prompt-ingestion latency on local large-context models. Fixes #62267. Thanks @BenSHPD.
2324
- Ollama: keep the model idle watchdog enabled for `*:cloud` models routed through a local Ollama host, so cloud-backed tool-loop stalls fail over visibly instead of inheriting local-model no-idle behavior. Fixes #79350. Thanks @geek111.
2425
- Voice/Ollama: honor routed voice agent `tools.allow` for classic embedded voice responses, including empty allowlists, so no-tool Ollama agents do not receive tool schemas. Fixes #79506. Thanks @donkeykong91.
2526
- Gateway: reread config from disk after the first in-process restart loop startup, preventing SIGUSR1 restarts from reusing a stale startup snapshot and dropping config written after boot. Fixes #79947. Thanks @TheLevti.

extensions/ollama/src/stream-runtime.test.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ describe("createConfiguredOllamaCompatStreamWrapper", () => {
208208
};
209209
expect(requestBody.think).toBe(false);
210210
expect(requestBody.options?.think).toBeUndefined();
211-
expect(requestBody.options?.num_ctx).toBe(131072);
211+
expect(requestBody.options?.num_ctx).toBeUndefined();
212212
},
213213
);
214214
});
@@ -310,7 +310,7 @@ describe("createConfiguredOllamaCompatStreamWrapper", () => {
310310
};
311311
expect(requestBody.think).toBe("low");
312312
expect(requestBody.options?.think).toBeUndefined();
313-
expect(requestBody.options?.num_ctx).toBe(131072);
313+
expect(requestBody.options?.num_ctx).toBeUndefined();
314314
},
315315
);
316316
});
@@ -405,7 +405,7 @@ describe("createConfiguredOllamaCompatStreamWrapper", () => {
405405
};
406406
expect(requestBody.think).toBe("high");
407407
expect(requestBody.options?.think).toBeUndefined();
408-
expect(requestBody.options?.num_ctx).toBe(131072);
408+
expect(requestBody.options?.num_ctx).toBeUndefined();
409409
},
410410
);
411411
});
@@ -1609,9 +1609,7 @@ describe("createOllamaStreamFn", () => {
16091609
if (!requestBody.options) {
16101610
throw new Error("Expected Ollama request options");
16111611
}
1612-
// Catalog `contextWindow` flows through as `num_ctx` so the request
1613-
// does not silently truncate to Ollama's small Modelfile default.
1614-
expect(requestBody.options?.num_ctx).toBe(131072);
1612+
expect(requestBody.options?.num_ctx).toBeUndefined();
16151613
expect(requestBody.options.num_predict).toBe(123);
16161614
},
16171615
);
@@ -1694,7 +1692,7 @@ describe("createOllamaStreamFn", () => {
16941692
);
16951693
});
16961694

1697-
it("falls back to catalog contextWindow as num_ctx when params.num_ctx is unset", async () => {
1695+
it("does not fall back to catalog contextWindow as native Ollama num_ctx", async () => {
16981696
await withMockNdjsonFetch(
16991697
[
17001698
'{"model":"m","created_at":"t","message":{"role":"assistant","content":"ok"},"done":false}',
@@ -1715,12 +1713,12 @@ describe("createOllamaStreamFn", () => {
17151713
const requestBody = JSON.parse(requestInit.body) as {
17161714
options?: { num_ctx?: number };
17171715
};
1718-
expect(requestBody.options?.num_ctx).toBe(32768);
1716+
expect(requestBody.options?.num_ctx).toBeUndefined();
17191717
},
17201718
);
17211719
});
17221720

1723-
it("falls back to catalog maxTokens as num_ctx when contextWindow is absent", async () => {
1721+
it("does not fall back to catalog maxTokens as native Ollama num_ctx", async () => {
17241722
await withMockNdjsonFetch(
17251723
[
17261724
'{"model":"m","created_at":"t","message":{"role":"assistant","content":"ok"},"done":false}',
@@ -1744,7 +1742,7 @@ describe("createOllamaStreamFn", () => {
17441742
const requestBody = JSON.parse(requestInit.body) as {
17451743
options?: { num_ctx?: number };
17461744
};
1747-
expect(requestBody.options?.num_ctx).toBe(65536);
1745+
expect(requestBody.options?.num_ctx).toBeUndefined();
17481746
},
17491747
);
17501748
});

extensions/ollama/src/stream.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -293,29 +293,16 @@ function resolveOllamaNumCtx(model: ProviderRuntimeModel): number {
293293
/**
294294
* Resolves num_ctx for native /api/chat requests:
295295
* 1. explicit `params.num_ctx` set on the model wins,
296-
* 2. otherwise the catalog `contextWindow` / `maxTokens` is forwarded so
297-
* OpenClaw's known model windows survive the trip and `/api/chat` does
298-
* not silently truncate to Ollama's small Modelfile default (typically
299-
* 2048 tokens) — which is too small for a system prompt plus tool
300-
* definitions and produces "model picks wrong tools / says nonsense"
301-
* symptoms on agent turns,
302-
* 3. when neither is known, return undefined so the Modelfile decides.
296+
* 2. otherwise return undefined so Ollama's model, OLLAMA_CONTEXT_LENGTH,
297+
* VRAM, or Modelfile policy decides.
303298
*
304299
* This intentionally differs from `resolveOllamaNumCtx` by not falling back
305300
* to `DEFAULT_CONTEXT_TOKENS`: that constant is a sane wrapper-side guess for
306-
* the OpenAI-compat path, but on the native path we prefer to leave num_ctx
307-
* absent rather than guess a window for an unknown model.
301+
* the OpenAI-compat path, but native `/api/chat` should not force the full
302+
* advertised catalog context for local models unless the operator opted in.
308303
*/
309304
function resolveOllamaNativeNumCtx(model: ProviderRuntimeModel): number | undefined {
310-
const configured = resolveOllamaConfiguredNumCtx(model);
311-
if (configured !== undefined) {
312-
return configured;
313-
}
314-
const catalog = model.contextWindow ?? model.maxTokens;
315-
if (typeof catalog === "number" && Number.isFinite(catalog) && catalog > 0) {
316-
return Math.floor(catalog);
317-
}
318-
return undefined;
305+
return resolveOllamaConfiguredNumCtx(model);
319306
}
320307

321308
function resolveOllamaModelOptions(model: ProviderRuntimeModel): Record<string, unknown> {

0 commit comments

Comments
 (0)