fix: preserve thought_signature in functionCall roundtrip to fix multi-turn tool use - #27
Open
Ken Chau (kenotron-ms) wants to merge 2 commits into
Open
fix: preserve thought_signature in functionCall roundtrip to fix multi-turn tool use#27Ken Chau (kenotron-ms) wants to merge 2 commits into
Ken Chau (kenotron-ms) wants to merge 2 commits into
Conversation
Root cause: Gemini API now returns thought_signature on functionCall parts in responses. The provider was discarding it during parsing and not re-attaching it during serialization of conversation history. Fixes: - In _convert_to_chat_response() (~line 964): preserve thought_signature as an extra field on ToolCallBlock and ToolCall (both support extra fields) - In _convert_messages() (~line 1121): re-attach thought_signature to each functionCall dict if the field is present during conversation history serialization Backward compatible: responses without thought_signature pass through unchanged. Testing: - New test file: tests/test_thought_signature.py with 8 comprehensive tests covering parse, serialize, full roundtrip, multiple tool calls, and mixed presence scenarios - 104 tests pass; 2 pre-existing unrelated failures in test_pricing_and_vision.py Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> Fixes microsoft-amplifier/amplifier-shared#41
…m ToolCallBlock
- Changed → at both the
parse site (tc_extra guard) and serialize site (_convert_messages) to preserve
empty-string signatures that were previously silently dropped
- Removed thought_signature/block_extra storage from ToolCallBlock entirely — it was
dead code since _convert_messages only reads from msg["tool_calls"] (ToolCall),
never from content_blocks (ToolCallBlock)
- Replaced test_parse_preserves_thought_signature_in_tool_call_block with
test_toolcallblock_does_not_store_thought_signature documenting the intentional
design and why ToolCallBlock never had the field
- Added two new edge-case tests:
- test_empty_string_thought_signature_is_preserved: validates the `is not None`
check prevents empty-string drop at parse time
- test_empty_string_thought_signature_serialized: validates empty-string round-trip
through _convert_messages back into Gemini API request
Fixes microsoft-amplifier/amplifier-shared#41
Generated with Amplifier
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
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.
Summary
Closes / related to: microsoft-amplifier/amplifier-shared#41
Gemini's API now returns a
thought_signaturefield onfunctionCallparts in responses. When those parts are included as conversation history in subsequent request turns, thethought_signaturemust be present — otherwise the API returns400 INVALID_ARGUMENT. The provider was discarding this field during parsing and never re-attaching it during serialization, breaking all multi-turn tool use for any Gemini-routed agent (browser-tester, any vision-role agent) after the first tool call.Root Cause
Two locations in
amplifier_module_provider_gemini/__init__.py:Parse (drop site) —
_convert_to_chat_response()(~line 964):ToolCallBlockandToolCallwere built without preservingfc.thought_signature.Serialize (re-submit site) —
_convert_messages()(~line 1121):function_calldicts were built with onlynameandargs, never re-attaching any signature field.Changes
amplifier_module_provider_gemini/__init__.py_convert_to_chat_response(): When a functionCall part containsthought_signature, store it as an extra field on bothToolCallBlockandToolCall. Both already carrymodel_config = ConfigDict(extra="allow"), so no model changes needed._convert_messages(): When building afunction_calldict for a subsequent request turn, re-attachthought_signatureif the storedToolCallhas the field.thought_signaturepass through unchanged (backward-compat).New:
tests/test_thought_signature.py— 8 teststest_parse_preserves_thought_signature_in_tool_calltest_parse_preserves_thought_signature_in_tool_call_blocktest_serialize_reattaches_thought_signaturetest_serialize_without_thought_signature_still_workstest_thought_signature_full_roundtriptest_multiple_tool_calls_each_thought_signature_preservedtest_mixed_tool_calls_only_signed_ones_get_signaturetest_second_tool_call_turn_does_not_raise_with_thought_signatureTest evidence
test_pricing_and_vision.pyrequire liveGOOGLE_API_KEY— failing onmainbefore this branch, unrelated)issue-41-gemini):thought_signaturesurvives parse → serialize:{"function_call": {"name": "search", "args": {"q": "amplifier"}, "thought_signature": "abc123-secret-signature-XYZ"}}Affected agents (all fixed by this PR)
browser-tester:browser-operator(confirmed reproducing on chore: add CI workflow #41)browser-tester:browser-researcher(same routing)browser-tester:visual-documenter(same routing)model_role=visionor direct Gemini config with tool useReference