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
@Ratish1
Copy link
Contributor

Ratish1 commented Nov 21, 2025

@dpolistwm , I meant the test file already exists for tools here https://github.com/strands-agents/sdk-python/blob/93997f0b947875a80e694b511711acce8f693704/tests/strands/tools/test_tools.py, so your tests would go there. But there shouldnt be a need to be creating new test files, since most of the test files already exist in strands. For the fix your proposing those tests are not needed at all since you only added a attribute to those files. Make sure you run hatch run prepare also.

- Removed test_content_block_start_tool_use.py (~96 lines)
- Removed test_tool_use.py (~107 lines)

These tests only verified basic Python dict behavior without testing
any SDK logic. All meaningful coverage is maintained by integration
tests in test_streaming.py which test actual thoughtSignature handling
through the streaming pipeline.
@dpolistwm
Copy link
Author

Makes sense, @Ratish1

The test_handle_content_block_start_with_thought_signature() function
was redundant because the parameterized test already covers the same
functionality (line 127-130). This standalone test just repeated the
same assertions without testing any additional edge cases or coverage.

Removed ~23 lines of duplicate test code while maintaining full coverage.
@codecov
Copy link

codecov bot commented Nov 24, 2025

Codecov Report

❌ Patch coverage is 85.29412% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/strands/models/gemini.py 80.00% 4 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Added two new tests to cover previously missing code paths:

1. test_stream_request_with_invalid_base64_thought_signature()
   - Covers base64 decode error handling (lines 151-153)
   - Verifies graceful degradation when thoughtSignature contains
     invalid base64 data
   - Tests error logging without crashing the request

2. test_stream_response_tool_use_with_string_thought_signature()
   - Covers string-to-bytes conversion path (lines 308-309)
   - Tests edge case where thought_signature is returned as string
     instead of bytes from Gemini API
   - Verifies proper UTF-8 encoding and base64 conversion

Coverage improvement: 3 previously uncovered code paths now tested.
@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...

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

2 participants