Repository navigation
fix(models): keep the streamed thought signature on its own part - #7588
Open
AtulJoshi1206 wants to merge 1 commit into
Open
AtulJoshi1206 wants to merge 1 commit into
AtulJoshi1206 wants to merge 1 commit into
Conversation
With thought summaries enabled, the Interactions streaming converter wrote each thought signature onto the last thought part it had collected, which is the summary's text part. The request encoder turns any part that carries text into a text step, so the request after a tool call sent the private summary back as model output and dropped the signature that Gemini 3 needs. When a turn has more than one thought step, a later signature also overwrote the earlier one. Append every signature as a text-free thought part, which is what the non-streaming converter already produces.
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.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
No existing issue; described below following the bug-report structure.
Describe the Bug:
With
Gemini(use_interactions_api=True), streaming (StreamingMode.SSE) and thought summaries on,_handle_thought_signaturewrites eachthought_signaturedelta onto the last thought part collected so far. That part is the summary's text part,Part(text=summary, thought=True, thought_signature=sig).On the request that carries the tool result:
_get_latest_user_contentscarries that part forward._convert_part_to_interaction_contentcheckspart.textfirst and turns it into amodel_outputtext step.Separately, when one streamed turn has more than one thought step, a later signature overwrites the earlier step's signature.
The non-streaming converter already avoids both. Commit 51f21e7 notes that "a part that also carries text is re-encoded as a text step, which drops the signature".
Impact:
Gemini 3 rejects the follow-up request unless the signature comes back verbatim. So with summaries on, every streamed tool-calling turn breaks on the request that carries the tool result, and the model sees its own private summary as prior output.
ManagedAgentstreams through the same converter and persists the same shape.Observed Behavior:
After a tool call, the second
interactions.create(...)(previous_interaction_id='int_1') sends:[{"type": "model_output", "content": [{"type": "text", "text": "I should call the tool."}]}, {"type": "function_result", "name": "get_weather", "call_id": "call_1", "result": {"weather": "sunny"}, "is_error": false}]Expected Behavior:
The same steps the non-streaming path sends for that turn:
[{"type": "thought", "signature": "<signature>"}, {"type": "function_result", "name": "get_weather", "call_id": "call_1", "result": {"weather": "sunny"}, "is_error": false}]Solution:
_handle_thought_signaturenow always appends the signature as its own text-freePart(thought=True, thought_signature=...), the same as the non-streaming converter.I considered fixing this in the encoder instead, by emitting a thought step for text + thought + signature parts. I didn't, for two reasons: it can't recover a signature the stream handler has already overwritten, and it would change the encoding contract for parts from other producers. The producer-side fix matches the design in 51f21e7.
Testing Plan
Unit Tests:
All in
tests/unittests/models/test_interactions_utils.py:test_thought_signature_delta_after_a_summary_gets_its_own_partreplacestest_thought_signature_delta_attaches_to_last_thought, which asserted the old behavior.test_thought_signature_deltas_of_two_thought_steps_are_both_kept.TestThoughtSignatureRoundTrip::test_signature_is_sent_back_with_the_tool_result[streamed|unary]feeds the same turn, streamed and unary, through_get_latest_user_contentsand_convert_contents_to_steps. The unary case passes before and after, so it pins parity.TestThoughtSignatureRoundTrip::test_runner_sends_the_streamed_signature_backrunsInMemoryRunner+LlmAgent+Gemini(use_interactions_api=True)over SSE against a scripted Interactions stream, and asserts on the secondinteractions.createcall.Checklist
Additional context
This is independent of #7508, which merges streamed text deltas per step. The two touch different functions; with this change, the summary parts that #7508 merges no longer carry a signature.
🤖 Generated with Claude Code