From 11d2f3e5f8751cbdca35139cd7310ce0cdb7bad2 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 26 Jun 2026 13:55:04 -0400 Subject: [PATCH] fix(llm): end reasoning before responses --- packages/llm/src/protocols/gemini.ts | 32 +++++++++++++------ packages/llm/src/protocols/openai-chat.ts | 7 +++- packages/llm/test/provider/gemini.test.ts | 5 ++- .../llm/test/provider/openai-chat.test.ts | 2 +- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/llm/src/protocols/gemini.ts b/packages/llm/src/protocols/gemini.ts index 3a2311c8fd..c732460820 100644 --- a/packages/llm/src/protocols/gemini.ts +++ b/packages/llm/src/protocols/gemini.ts @@ -407,21 +407,35 @@ const step = (state: ParserState, event: GeminiEvent) => { if ("thoughtSignature" in part && part.thoughtSignature && "thought" in part && part.thought) reasoningSignature = part.thoughtSignature if ("text" in part && part.text.length > 0) { - lifecycle = part.thought - ? Lifecycle.reasoningDelta( - lifecycle, - events, - "reasoning-0", - part.text, - part.thoughtSignature ? googleMetadata({ thoughtSignature: part.thoughtSignature }) : undefined, - ) - : Lifecycle.textDelta(lifecycle, events, "text-0", part.text) + if (part.thought) { + lifecycle = Lifecycle.reasoningDelta( + lifecycle, + events, + "reasoning-0", + part.text, + part.thoughtSignature ? googleMetadata({ thoughtSignature: part.thoughtSignature }) : undefined, + ) + continue + } + lifecycle = Lifecycle.reasoningEnd( + lifecycle, + events, + "reasoning-0", + reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined, + ) + lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text) continue } if ("functionCall" in part) { const input = part.functionCall.args const id = `tool_${nextToolCallId++}` + lifecycle = Lifecycle.reasoningEnd( + lifecycle, + events, + "reasoning-0", + reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined, + ) lifecycle = Lifecycle.stepStart(lifecycle, events) events.push( LLMEvent.toolCall({ diff --git a/packages/llm/src/protocols/openai-chat.ts b/packages/llm/src/protocols/openai-chat.ts index e37eec95ec..4e41a34ce4 100644 --- a/packages/llm/src/protocols/openai-chat.ts +++ b/packages/llm/src/protocols/openai-chat.ts @@ -411,7 +411,12 @@ const step = (state: ParserState, event: OpenAIChatEvent) => if (delta?.reasoning_content) lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", delta.reasoning_content) - if (delta?.content) lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content) + if (delta?.content) { + lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0") + lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content) + } + + if (toolDeltas.length) lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0") for (const tool of toolDeltas) { const result = ToolStream.appendOrStart( diff --git a/packages/llm/test/provider/gemini.test.ts b/packages/llm/test/provider/gemini.test.ts index d30742c47d..1dc253c0ea 100644 --- a/packages/llm/test/provider/gemini.test.ts +++ b/packages/llm/test/provider/gemini.test.ts @@ -347,10 +347,10 @@ describe("Gemini route", () => { { type: "step-start", index: 0 }, { type: "reasoning-start", id: "reasoning-0" }, { type: "reasoning-delta", id: "reasoning-0", text: "thinking" }, + { type: "reasoning-end", id: "reasoning-0" }, { type: "text-start", id: "text-0" }, { type: "text-delta", id: "text-0", text: "Hello" }, { type: "text-delta", id: "text-0", text: "!" }, - { type: "reasoning-end", id: "reasoning-0" }, { type: "text-end", id: "text-0" }, { type: "step-finish", index: 0, reason: "stop", usage, providerMetadata: undefined }, { @@ -399,6 +399,9 @@ describe("Gemini route", () => { providerMetadata: { google: { thoughtSignature: "thought_sig" } }, }) expect(toolCall).toMatchObject({ providerMetadata: { google: { thoughtSignature: "tool_sig" } } }) + expect(response.events.findIndex((event) => event.type === "reasoning-end")).toBeLessThan( + response.events.findIndex((event) => event.type === "tool-call"), + ) const prepared = yield* LLMClient.prepare( LLM.request({ diff --git a/packages/llm/test/provider/openai-chat.test.ts b/packages/llm/test/provider/openai-chat.test.ts index 5dbc89f1ae..02995c00ce 100644 --- a/packages/llm/test/provider/openai-chat.test.ts +++ b/packages/llm/test/provider/openai-chat.test.ts @@ -542,9 +542,9 @@ describe("OpenAI Chat route", () => { { type: "step-start", index: 0 }, { type: "reasoning-start", id: "reasoning-0" }, { type: "reasoning-delta", id: "reasoning-0", text: "thinking" }, + { type: "reasoning-end", id: "reasoning-0" }, { type: "text-start", id: "text-0" }, { type: "text-delta", id: "text-0", text: "Hello" }, - { type: "reasoning-end", id: "reasoning-0" }, { type: "text-end", id: "text-0" }, { type: "step-finish", index: 0, reason: "stop" }, { type: "finish", reason: "stop" },