Repository navigation
[AISAGE-745] fix(ai-gateway): split tagged Gemini thoughts from the answer without streaming - #8912
Conversation
… streaming Without streaming, Vertex AI's OpenAI-compatible endpoint returns Gemini's thoughts and answer in one content string flagged as a thought, with only the thoughts wrapped in tags. The Gemini thought transform moved the whole string, answer included, into reasoning. Non-streaming requests now ask for the tag explicitly, and the transform keeps the text after it as the answer.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryThe Gemini thought-tag change is small and self-contained: the request now opts into Assumptions: Files Reviewed (4 files)
Reviewed by deepseek-flash-latest · Input: 74.7K · Output: 25.8K · Cached: 1.3M Review guidance: REVIEW.md from base branch |
Summary
Fixes non-streaming Gemini responses through the
gemini-thoughtreasoning transform, which custom LLMs on Vertex AI's OpenAI-compatible endpoint use today and #8488's direct Vertex BYOK will use.Problem. With thoughts enabled, a non-streaming response puts the thoughts and the answer in one
message.contentstring and flags the whole message withextra_content.google.thought: true. Only the thoughts are wrapped in tags:The transform follows the
thoughtflag, so it moved the entire string, answer included, intoreasoning_details, and the client received an empty answer. Streaming is unaffected: Vertex flags thought chunks separately there and sends no tags.Fix.
google.thought_tag_marker: "think". Vertex's docs say no tags are returned unless this is set, although Vertex currently sends<think>anyway, so this pins the separator. Streaming requests don't get it. With a marker set, Vertex also tags streamed chunks and puts the closing tag in the first answer chunk.<think>and contains</think>, only the tagged part becomes areasoning.textdetail and the rest stays ascontent. Content flagged as a thought without tags keeps the old behavior. Thought signatures, on the message or on tool calls, are still mapped to encrypted details.The underlying inconsistency is being reported to Google separately: the message is flagged as a thought even though its content also contains the answer.
Part of AISAGE-745.
Verification
Against Vertex AI (global, 2026-10-26), sending requests built with
applyReasoningDetailsTransformand passing responses throughrewriteModelResponse_ChatCompletions:gemini-3.1-pro-preview:contentis"1081", with one 2,148-characterreasoning.textdetail and the encrypted signature.gemini-3.5-flashwith a tool:contentis""with thereadtool call, plus a 720-characterreasoning.textdetail and the tool call's encrypted signature.Checked Vertex's behavior first:
thought_tag_markeris honored (a custom marker changes the tags), and the default tag isthink.New tests:
rewriteModelResponse,apply-provider-specific-logic, custom LLM andbuild-direct-providersuites pass (286 tests).tsgotypecheck forweb-sharedandapps/web, andoxlinton the changed files, pass.Visual Changes
N/A
Reviewer Notes
kilo/anthropic/claude-opus-5.5), with no subagent model overrides.