Skip to content

Conversation

@dpolistwm
Copy link

@dpolistwm dpolistwm commented Nov 21, 2025

Description

This PR adds support for preserving Gemini's thoughtSignature field during function calling, which is required by Gemini 3 Pro for multi-turn conversations with tools.

Problem

When using Gemini 3 Pro with function calling, the model returns a thought_signature field that must be passed back in subsequent requests. Currently, Strands drops this field during streaming and message reconstruction, causing 400 errors:
Unable to submit request because function call tool_name in the 2. content block is missing a thought_signature

This affects multi-turn conversations, nested agents, and any workflow requiring multiple function call exchanges with Gemini 3 Pro.

Solution

Framework Changes:

  1. src/strands/types/tools.py: Added thoughtSignature: NotRequired[str] to ToolUse TypedDict (changed to total=False to support optional field)
  2. src/strands/types/content.py: Added thoughtSignature: NotRequired[str] to ContentBlockStartToolUse TypedDict and added NotRequired import
  3. src/strands/event_loop/streaming.py: Preserve thoughtSignature when processing streaming chunks (2 locations: extracting from tool use data and passing through to ToolUse object creation)

Gemini Provider Changes:
4. src/strands/models/gemini.py:

  • Capture thought_signature from Gemini function call responses
  • Base64 encode for storage in message history
  • Decode and pass back to Gemini in subsequent requests
  • Configure thinking_config to disable thinking text while preserving signatures

Technical Details

The thoughtSignature is an encrypted token provided by Gemini that preserves the model's reasoning context. It's:

  • Returned as bytes from the Gemini API
  • Stored as Base64-encoded string in message history (for JSON serialization)
  • Decoded back to bytes when sending subsequent requests
  • Optional field (NotRequired[str]) that doesn't affect other providers

Reference: Gemini Thought Signatures Documentation

Related Issues

None - this is a new feature to support Gemini 3 Pro's function calling requirements.

Documentation PR

N/A - No documentation changes needed (inline code comments added for clarity)

Type of Change

New feature

Testing

How have you tested the change?

Tested with:

  • ✅ Gemini 3 Pro (gemini-3-pro-preview) with function calling
  • ✅ Multi-turn conversations with nested agents
  • ✅ Sequential and parallel function calls
  • ✅ Backward compatibility verified with Bedrock and OpenAI providers

Results:

  • ✅ No more 400 errors about missing thought_signature
  • ✅ Function calling works correctly across multiple turns
  • ✅ Nested agents (agents calling other agents) work properly
  • ✅ Optional field doesn't affect models that don't use it
  • ✅ No breaking changes to existing functionality

Verified in consuming repositories:

  • agents-tools: ✅ No warnings introduced
  • Existing Bedrock/OpenAI workflows: ✅ Continue working unchanged

Note on CI: There is 1 pre-existing mypy error in src/strands/tools/tools.py:47 (unused type: ignore comment) that is unrelated to these changes. All type checking passes for the files modified in this PR.

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Related Issues

Fixes #1199

- Add thoughtSignature field to ToolUse TypedDict as optional field
- Add thoughtSignature to ContentBlockStartToolUse TypedDict
- Preserve thoughtSignature during streaming event processing
- Fixes compatibility with Gemini 3 Pro thinking mode

This change enables proper multi-turn function calling with Gemini 3 Pro,
which requires thought_signature to be passed back in subsequent requests.

Resolves: Gemini 3 Pro 400 error for missing thought_signature
See: https://ai.google.dev/gemini-api/docs/thought-signatures
- Capture thought_signature from Gemini function call responses
- Base64 encode thought_signature for storage in message history
- Decode and pass thought_signature back to Gemini in subsequent requests
- Configure thinking_config to disable thinking text but preserve signatures
- Add NotRequired import to content.py for type safety

This complements the framework changes by implementing Gemini-specific
handling of thought signatures for proper multi-turn function calling
with Gemini 3 Pro.

See: https://ai.google.dev/gemini-api/docs/thought-signatures
- Fix variable name conflict with thought_signature
- Break long lines to comply with 120 character limit
- Use explicit type annotations for thought signature variables
@dpolistwm dpolistwm force-pushed the feat/preserve-thought-signature-gemini branch from ed1837d to a20b7ea Compare November 21, 2025 17:16
@github-actions github-actions bot added size/l and removed size/l labels Nov 21, 2025
@Ratish1
Copy link
Contributor

Ratish1 commented Nov 21, 2025

@dpolistwm I see that you have created two new test files. I dont recommend that, the tools test file already exists here and creating new test files for testing small logic is something the maintainers wouldn't like I think. Make sure you only add tests for the fix you are proposing. The best option I think here would be just waiting for a maintainer to see what approach they would take. Thanks.

- Rename test_tools.py -> test_tool_use.py (tests ToolUse TypedDict)
- Rename test_content.py -> test_content_block_start_tool_use.py (tests ContentBlockStartToolUse TypedDict)

This makes the test file names more descriptive and avoids confusion with tests/strands/tools/test_tools.py
@github-actions github-actions bot added size/l and removed size/l labels Nov 21, 2025
@dpolistwm
Copy link
Author

The above commit enhances coverage of Gemini models, including lines that implement the new thoughtSignature preservation functionality

@dpolistwm
Copy link
Author

Guys, do I need to do something else here? Let me know...

@mkmeral
Copy link
Contributor

mkmeral commented Dec 5, 2025

hey @dpolistwm , thank you for the PR :)

I am hesitant to merge in this PR right now, because thoughtSignature is a specific feature for a specific model provider. I don't love the idea of polluting generic ToolUse event. That said, I think a generic field like this can be helpful in the long term, as other model providers try to adapt similar approaches (using a token to encode internal state, which helps them keep their API stateless). This requires a bit more research and work though, as we need to discover other use cases.

Given the bug that keeps Gemin 3 Pro working with Strands, I will instead merge a change to use skip_thought_signature_validator (PR). This will allow the model to work with Strands, although it might cause some degradation in performance. Then I will create a followup ticket for the feature, referring this PR.

@dpolistwm
Copy link
Author

Hey @mkmeral , I understand your concern, but implementing this workaround and degrading the tought process of such a popular model would make strands agents a "second class citizen" among other frameworks in the market.

Many users may stop using Strands Agents with Gemini 3 Pro (one of the most popular, fast and powerful LLMs in the market) if this is the official solution and probably choose ADK instead. While it does not support AWS Agentcore directly (memory, etc.), it does support Google's equally feature-rich AgentEngine counterpart. We would certainly consider that.

Please take this risk into account when making your decision.

@mkmeral
Copy link
Contributor

mkmeral commented Dec 5, 2025

@dpolistwm actually I think I found a way to get the feature working with your PR, and leave the tool use event type as is.

Gemini 3 Pro only validates the latest thought signature, not all of them. So what we can do instead is keep track of the thought signatures in self.thought_signatures in gemini.py. That way thought signatures would work out of the box, and we would only need to change gemini model provider.

Can you update your PR following this approach?

One other thing to note: we would also still want to apply skip_though_signatures, if no thought signatures exist. That would allow developers to switch the models for the same agent, and still keep them working.

Does this make sense?

@dpolistwm
Copy link
Author

I'll try and make this work ;)

This refactors the thoughtSignature feature to be self-contained within
the GeminiModel class instead of spreading it across the SDK:

Changes:
- Removed thoughtSignature from ContentBlockStartToolUse (content.py)
- Removed thoughtSignature from ToolUse (tools.py)
- Removed thoughtSignature handling from streaming.py
- Added self.last_thought_signature instance attribute to GeminiModel
- GeminiModel now stores thought_signature from responses and uses it
  for subsequent requests automatically

Benefits:
- Cleaner SDK types without Gemini-specific fields
- Simpler implementation without base64 encoding/decoding in messages
- thought_signature is automatically tracked per model instance
- No changes required to SDK-level streaming pipeline

Tests updated accordingly to verify internal storage behavior.
…ini.py

Reverted content.py, tools.py, streaming.py and test files to their
original state. thoughtSignature handling is now self-contained in
GeminiModel using self.last_thought_signature.
@dpolistwm
Copy link
Author

Now the only changed file in this PR is gemini.py with the last thought signature. I tested it locally against a real project and it is working fine 💯

@dpolistwm
Copy link
Author

Working on "0 tools" implementation (getting a 400 error)...

@dpolistwm
Copy link
Author

Looks fine now to me. Please let me know if you need anything else

@Ratish1
Copy link
Contributor

Ratish1 commented Dec 6, 2025

Hello @mkmeral, could my PR also be used in some way, since @dpolistwm mentions gemini 3 doesnt work in my PR, maybe we can figure out a way to fix it. Or if this PR is already correct, I will close my PR then ig.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] INVALID_ARGUMENT using gemini-3-pro-preview

3 participants