Repository navigation
Conversation
normalize_messages removed reasoning_content from every assistant message, so when a model reasoned and then called a tool, its reasoning was gone by the time it read the tool's result. The Gemma 4 docs say the reasoning must not be removed between the tool calls of one model turn, and the Gemma 4 and Qwen3 templates render it for the assistant messages after the last user message. normalize_messages now takes the first string among reasoning_content, reasoning and thinking, removes all three, and puts it back as reasoning_content for the assistant messages after the last user message, or for all of them when there is none. The docs tell clients to send the reasoning back, and test_tool_calls.reasoning_kept_within_turn checks it with prompt_tokens.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
normalize_messagesremovesreasoning_contentfrom every assistant message. When a model reasons, calls a tool and goes on from the result, its reasoning is gone from the prompt by the time it reads the result. The Gemma 4 docs say:Before, for a request that sends the reasoning back with the tool call:
After:
Reasoning is now kept for the assistant messages after the last user message and removed from earlier ones, as before. The Gemma 4 and Qwen3 templates render it only for those messages. Clients that send it as
reasoningorthinkinginstead ofreasoning_contenthave it moved toreasoning_content, which the templates read.The prompt is longer now, and the request after a tool call prefills the reasoning again. The engine already holds the reasoning it generated, but the prompt cache reuses only saved prompt prefixes, and the template also writes a newline before
<channel|>that the model did not generate. In a two-step tool chain with thinking on (gemma4-it:e4b, about 240 tokens of reasoning):Each tool step pays once for its reasoning. The next request can then reuse the longer prompt, because it is now large enough to be saved as a prefix.
Once a new user message follows, the template drops the earlier turn's reasoning again, so the new prompt differs from the cached one from the first tool call on. With prefix slots (the default on NPU) the follow-up in the test above reused the same 147 tokens as before. Where prefix slots are off, on OGA backends and for Gemma 4 conversations with images or audio, the cache is cleared on any difference, so that request prefills the whole conversation again; this is not measured.
The docs now tell clients to send the reasoning back, and
test_tool_calls.reasoning_kept_within_turnchecks it withprompt_tokens.How to reproduce
Send a tool call with its result, once without and once with
reasoning_contenton the tool call, and compareprompt_tokens:On
mainboth requests report the same count, because the reasoning is removed. On this branch the second one is larger by the reasoning's tokens.I found this while comparing the prompts flm renders with Google's template, using a small debug branch that logs every prompt to a file (
log-prompt,FLM_LOG_PROMPT=<file>). It also shows the rendered prompt directly, if you want to see the reasoning in it.