Skip to content

Thought Signatures With Google Gemini Models #2293

@TariqAHassan

Description

@TariqAHassan

Question

Overview

I am wondering if thought_signatures returned from Google-GLA are conserved in Pydantic AI.

In the GenAI SDK for Python, if I do the following:

from google import genai
from google.genai import types as genai_types

client = genai.Client(
    api_key="API_KEY_HERE",  #  I believe this will _not_ use vertex
)


tool_def = {
    "name": "is_prime",
    "description": "Check if a number is prime.",
    "parameters": {
        "properties": {
            "value": {
                "description": "If the number is prime",
                "type": "integer",
            }
        },
        "required": ["value"],
        "type": "object",
    },
}


stream = client.aio.models.generate_content_stream(
    model="gemini-2.5-pro",
    contents=[
        "Compute the sum of all prime numbers on [0, 10]. "
        "Think very hard and use tools."
    ],
    config=genai_types.GenerateContentConfig(
        thinking_config=genai_types.ThinkingConfig(
            include_thoughts=True,
            thinking_budget=-1,
        ),
        system_instruction="You are a helpful assistant",
        tools=[{"function_declarations": [tool_def]}],
    ),
)


async def main() -> None:
    async for chunk in await stream:
        for part in chunk.candidates[0].content.parts:
            print(part)
            print("Has thought signature:", bool(part.thought_signature))
            print("-" * 100)

I get the payload in Appendix 1 (below). As can be seen, it includes a thought_signature (attached to the part with a function call).

However, if I do:

from google.genai.types import ThinkingConfigDict
from pydantic_ai import Agent
from pydantic_ai.messages import (
    FinalResultEvent,
    ToolCallPartDelta,
    TextPartDelta,
    PartDeltaEvent,
    PartStartEvent,
    FunctionToolCallEvent,
    FunctionToolResultEvent,
)
from pydantic_ai.models.google import GoogleModel, GoogleModelSettings
from pydantic_ai.providers.google import GoogleProvider
from pydantic_ai.toolsets import FunctionToolset


def multiply(a: float, b: float) -> float:
    return a * b


agent_toolset = FunctionToolset(
    tools=[
        multiply,
    ]
)

agent = Agent(
    GoogleModel(
        model_name="gemini-2.5-pro",
        provider=GoogleProvider(
            api_key="API_KEY_HERE",
        ),
    ),
    system_prompt="You are a helpful assistant.",
    model_settings=GoogleModelSettings(
        google_thinking_config=ThinkingConfigDict(
            include_thoughts=True,
            thinking_budget=256,
        )
    ),
    toolsets=[agent_toolset],
)

async def main() -> None:
    prompt = ("How many quarters would fit between Earth and the Moon? Assume a quarter is 0.07 inches in width and the distance between the Earth and Moon is 238,855 miles."
              "Reason carefully, guess when you need to and use tools along the way to help you with the math.")

    all_nodes = []
    async with agent.iter(prompt) as run:
        async for node in run:
            all_nodes.append(node)

then look for the signature

all_nodes[2]
# CallToolsNode(model_response=ModelResponse(parts=[ThinkingPart(content='....

type(all_nodes[2].model_response.parts[0])
# pydantic_ai.messages.ThinkingPart

assert all_nodes[2].model_response.parts[0].signature is None

type(all_nodes[2].model_response.parts[1])
# pydantic_ai.messages.TextPart


type(all_nodes[2].model_response.parts[2])
# pydantic_ai.messages.ToolCallPart

all_nodes[2].model_response.parts[2]
# ToolCallPart(tool_name='divide', args={'a': 15133852800, 'b': 0.25}, tool_call_id='...')

I can't find it in any of the parts.

Some Additional Notes

  • I can see that there is a thought_signature field for models.gemini._GeminiThoughtPart(), but not for Google.
  • The vertex endpoint has never returned thinking_signature for me (even when using Google's Python SDK). I've only encountered it when using GLA.
  • Google states here that this signature is necessary for thinking to work as expected.

Please let me know if I'm missing something. I'm still new to the library (which is great by the way!)

Appendix

Appendix 1

video_metadata=None thought=True inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=None function_response=None text="**Analyzing Prime Summation**\n\nI'm currently focused on the problem of calculating the sum of primes within the range [0, 10]. I've pinpointed the `is_prime` tool and its parameter, `value`, as the core components for this task. I'm exploring how to iterate through the range and effectively use this tool to determine the primes.\n\n\n"
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=True inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=None function_response=None text="**Devising the Prime Summation**\n\nI'm now building the step-by-step logic, focusing on using the `is_prime` tool within a loop for the [0, 10] range. I'm initializing an `is_prime_numbers` list to collect prime numbers, intending to sum them later. I've mapped out the tool calls for each number and its expected `True` or `False` outcome to test the program.\n\n\n"
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=True inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=None function_response=None text="**Formulating a Concise Solution**\n\nI've outlined a step-by-step approach: initialize `total_sum` to zero, loop through the range [0, 10], and for each number, call `is_prime`. If `is_prime` returns True, I'll add the number to `total_sum`. I'll then present the final sum to provide a clear and organized solution for the user. I plan to use print statements to show the prime identification as I work through the process.\n\n\n"
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=True inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=None function_response=None text="**Developing the Prime Algorithm**\n\nI'm now integrating the steps. I'll initialize `total_sum` to zero and iterate through [0, 10], calling `is_prime` as planned. If `True`, I'll update `total_sum`, printing each step for clarity. Afterward, the final `total_sum` of 17 will be presented with the primes (2, 3, 5, 7) for a complete and understandable solution.\n\n\n"
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=b'<BYTES_HERE>' code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 0
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: True
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 1
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 2
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 3
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 4
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 5
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 6
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 7
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 8
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 9
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(
  args={
    'value': 10
  },
  name='is_prime'
) function_response=None text=None
Has thought signature: False
----------------------------------------------------------------------------------------------------

Additional Context

No response

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions