From 684f41692d4b78faf2d621aaae4678f77dafedcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=89=BA=E9=9F=AC=28yangyitao=29?= Date: Sun, 15 Mar 2026 02:31:00 +0000 Subject: [PATCH 1/4] fix(ollama): send think param for thinking models Ollama thinking models (deepseek-r1, qwq, etc.) require an explicit `think` boolean in the /api/chat request body to control reasoning. Previously this parameter was never sent, so thinking models always used their default behavior regardless of the user's thinking-level config. Now the Ollama stream function forwards the reasoning level as `think: true` when reasoning is enabled and `think: false` when reasoning is disabled. Closes #46680 --- src/agents/ollama-stream.test.ts | 54 ++++++++++++++++++++++++++++++++ src/agents/ollama-stream.ts | 13 ++++++++ 2 files changed, 67 insertions(+) diff --git a/src/agents/ollama-stream.test.ts b/src/agents/ollama-stream.test.ts index ded8064ea19..5382aaddb45 100644 --- a/src/agents/ollama-stream.test.ts +++ b/src/agents/ollama-stream.test.ts @@ -544,6 +544,60 @@ describe("createOllamaStreamFn", () => { [{ type: "text", text: "final answer" }], ); }); + + it("sends think:true when reasoning level is set", async () => { + await withMockNdjsonFetch( + [ + '{"model":"m","created_at":"t","message":{"role":"assistant","content":"ok"},"done":false}', + '{"model":"m","created_at":"t","message":{"role":"assistant","content":""},"done":true,"prompt_eval_count":1,"eval_count":1}', + ], + async (fetchMock) => { + const streamFn = createOllamaStreamFn("http://ollama-host:11434"); + const stream = streamFn( + { + id: "deepseek-r1:32b", + api: "ollama", + provider: "ollama", + contextWindow: 131072, + } as never, + { messages: [{ role: "user", content: "hello" }] } as never, + { reasoning: "medium" } as never, + ); + await collectStreamEvents(stream); + + const [, reqInit] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; + const body = JSON.parse(reqInit.body as string) as { think?: boolean }; + expect(body.think).toBe(true); + }, + ); + }); + + it("sends think:false when reasoning is not set but options are present", async () => { + await withMockNdjsonFetch( + [ + '{"model":"m","created_at":"t","message":{"role":"assistant","content":"ok"},"done":false}', + '{"model":"m","created_at":"t","message":{"role":"assistant","content":""},"done":true,"prompt_eval_count":1,"eval_count":1}', + ], + async (fetchMock) => { + const streamFn = createOllamaStreamFn("http://ollama-host:11434"); + const stream = streamFn( + { + id: "deepseek-r1:32b", + api: "ollama", + provider: "ollama", + contextWindow: 131072, + } as never, + { messages: [{ role: "user", content: "hello" }] } as never, + {} as never, + ); + await collectStreamEvents(stream); + + const [, reqInit] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; + const body = JSON.parse(reqInit.body as string) as { think?: boolean }; + expect(body.think).toBe(false); + }, + ); + }); }); describe("resolveOllamaBaseUrlForRun", () => { diff --git a/src/agents/ollama-stream.ts b/src/agents/ollama-stream.ts index f332ad1fd83..e92b0897144 100644 --- a/src/agents/ollama-stream.ts +++ b/src/agents/ollama-stream.ts @@ -42,6 +42,7 @@ interface OllamaChatRequest { model: string; messages: OllamaChatMessage[]; stream: boolean; + think?: boolean; tools?: OllamaTool[]; options?: Record; } @@ -459,10 +460,22 @@ export function createOllamaStreamFn( ollamaOptions.num_predict = options.maxTokens; } + // Ollama thinking models (e.g. deepseek-r1, qwq) respect a top-level + // `think` boolean. Forward the reasoning level so `think: false` is + // sent explicitly when thinking is disabled (#46680). + const thinkParam: { think?: boolean } = {}; + if (options?.reasoning) { + thinkParam.think = true; + } else if (options && !options.reasoning) { + // Thinking explicitly disabled – tell Ollama not to think. + thinkParam.think = false; + } + const body: OllamaChatRequest = { model: model.id, messages: ollamaMessages, stream: true, + ...thinkParam, ...(ollamaTools.length > 0 ? { tools: ollamaTools } : {}), options: ollamaOptions, }; From aac1c913d3a2af85a4f1a79e9eead2d1baa00420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=89=BA=E9=9F=AC=28yangyitao=29?= Date: Sun, 15 Mar 2026 06:14:13 +0000 Subject: [PATCH 2/4] fix(ollama): await streamFn result in think-param tests to satisfy AsyncIterable type --- src/agents/ollama-stream.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agents/ollama-stream.test.ts b/src/agents/ollama-stream.test.ts index 5382aaddb45..3e465ec8368 100644 --- a/src/agents/ollama-stream.test.ts +++ b/src/agents/ollama-stream.test.ts @@ -553,7 +553,7 @@ describe("createOllamaStreamFn", () => { ], async (fetchMock) => { const streamFn = createOllamaStreamFn("http://ollama-host:11434"); - const stream = streamFn( + const stream = await streamFn( { id: "deepseek-r1:32b", api: "ollama", @@ -580,7 +580,7 @@ describe("createOllamaStreamFn", () => { ], async (fetchMock) => { const streamFn = createOllamaStreamFn("http://ollama-host:11434"); - const stream = streamFn( + const stream = await streamFn( { id: "deepseek-r1:32b", api: "ollama", From 2092d1a73b1f248dc75eacb58541f7451c2742b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=89=BA=E9=9F=AC=28yangyitao=29?= Date: Sat, 21 Mar 2026 02:27:10 +0000 Subject: [PATCH 3/4] fix(ollama): guard against reasoning="off" truthy string, add test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Check `options.reasoning !== "off"` before setting think: true - Simplify else-if to just `else if (options)` - Add test: reasoning="off" → think: false Addresses review feedback from greptile-apps, codex-connector, and @Kaspre --- src/agents/ollama-stream.test.ts | 27 +++++++++++++++++++++++++++ src/agents/ollama-stream.ts | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/agents/ollama-stream.test.ts b/src/agents/ollama-stream.test.ts index 3e465ec8368..03d35834db7 100644 --- a/src/agents/ollama-stream.test.ts +++ b/src/agents/ollama-stream.test.ts @@ -598,6 +598,33 @@ describe("createOllamaStreamFn", () => { }, ); }); + + it("sends think:false when reasoning is 'off'", async () => { + await withMockNdjsonFetch( + [ + '{"model":"m","created_at":"t","message":{"role":"assistant","content":"ok"},"done":false}', + '{"model":"m","created_at":"t","message":{"role":"assistant","content":""},"done":true,"prompt_eval_count":1,"eval_count":1}', + ], + async (fetchMock) => { + const streamFn = createOllamaStreamFn("http://ollama-host:11434"); + const stream = await streamFn( + { + id: "deepseek-r1:32b", + api: "ollama", + provider: "ollama", + contextWindow: 131072, + } as never, + { messages: [{ role: "user", content: "hello" }] } as never, + { reasoning: "off" } as never, + ); + await collectStreamEvents(stream); + + const [, reqInit] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; + const body = JSON.parse(reqInit.body as string) as { think?: boolean }; + expect(body.think).toBe(false); + }, + ); + }); }); describe("resolveOllamaBaseUrlForRun", () => { diff --git a/src/agents/ollama-stream.ts b/src/agents/ollama-stream.ts index e92b0897144..1c3048b67a1 100644 --- a/src/agents/ollama-stream.ts +++ b/src/agents/ollama-stream.ts @@ -464,9 +464,9 @@ export function createOllamaStreamFn( // `think` boolean. Forward the reasoning level so `think: false` is // sent explicitly when thinking is disabled (#46680). const thinkParam: { think?: boolean } = {}; - if (options?.reasoning) { + if (options?.reasoning && options.reasoning !== "off") { thinkParam.think = true; - } else if (options && !options.reasoning) { + } else if (options) { // Thinking explicitly disabled – tell Ollama not to think. thinkParam.think = false; } From d6396ac86f5d5f0eecd8dbda89c6bcd7314e6b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=89=BA=E9=9F=AC=28yangyitao=29?= Date: Sat, 21 Mar 2026 02:55:17 +0000 Subject: [PATCH 4/4] =?UTF-8?q?fix(ollama):=20revert=20"off"=20guard=20?= =?UTF-8?q?=E2=80=94=20ThinkingLevel=20excludes=20"off"=20at=20type=20leve?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi-ai ThinkingLevel = "minimal"|"low"|"medium"|"high"|"xhigh" (no "off"). The !== "off" check caused TS2367. Remove guard and test since the type system already prevents reasoning="off" from reaching this code path. --- src/agents/ollama-stream.test.ts | 27 --------------------------- src/agents/ollama-stream.ts | 2 +- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/agents/ollama-stream.test.ts b/src/agents/ollama-stream.test.ts index 03d35834db7..3e465ec8368 100644 --- a/src/agents/ollama-stream.test.ts +++ b/src/agents/ollama-stream.test.ts @@ -598,33 +598,6 @@ describe("createOllamaStreamFn", () => { }, ); }); - - it("sends think:false when reasoning is 'off'", async () => { - await withMockNdjsonFetch( - [ - '{"model":"m","created_at":"t","message":{"role":"assistant","content":"ok"},"done":false}', - '{"model":"m","created_at":"t","message":{"role":"assistant","content":""},"done":true,"prompt_eval_count":1,"eval_count":1}', - ], - async (fetchMock) => { - const streamFn = createOllamaStreamFn("http://ollama-host:11434"); - const stream = await streamFn( - { - id: "deepseek-r1:32b", - api: "ollama", - provider: "ollama", - contextWindow: 131072, - } as never, - { messages: [{ role: "user", content: "hello" }] } as never, - { reasoning: "off" } as never, - ); - await collectStreamEvents(stream); - - const [, reqInit] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; - const body = JSON.parse(reqInit.body as string) as { think?: boolean }; - expect(body.think).toBe(false); - }, - ); - }); }); describe("resolveOllamaBaseUrlForRun", () => { diff --git a/src/agents/ollama-stream.ts b/src/agents/ollama-stream.ts index 1c3048b67a1..6a8fd608914 100644 --- a/src/agents/ollama-stream.ts +++ b/src/agents/ollama-stream.ts @@ -464,7 +464,7 @@ export function createOllamaStreamFn( // `think` boolean. Forward the reasoning level so `think: false` is // sent explicitly when thinking is disabled (#46680). const thinkParam: { think?: boolean } = {}; - if (options?.reasoning && options.reasoning !== "off") { + if (options?.reasoning) { thinkParam.think = true; } else if (options) { // Thinking explicitly disabled – tell Ollama not to think.