Gemini Thought Signatures
A reasoning state field unique to the Gemini 3+ series, required for multi-turn conversations and function calling. No equivalent concept exists for other providers.
When using function calling or multi-turn conversations with Gemini 3+, the thought_signature returned in each response must be passed back unchanged in subsequent turns to preserve the model's reasoning state. Omitting it will result in a 400 error.
- Official docs: Vertex AI Thought Signatures
- Applicable models: Gemini 3 / 3.1 / 3.5 / 3.6 series listed in the Model Catalog → Gemini.
- Not applicable to:
turing/gemini-2.5-*series (this mechanism does not apply).
Field Locations
- Function calling response:
choices[].message.tool_calls[0].provider_specific_fields.thought_signature - Multi-turn conversation response:
choices[].message.provider_specific_fields.thought_signature
Automatic Handling via SDK (Recommended)
When the OpenAI Python SDK passes back response.choices[0].message as a whole, it automatically preserves thought_signature. Do not destructure the message object.
from openai import OpenAI
client = OpenAI(api_key=TURING_API_KEY, base_url=TURING_BASE_URL)
# Turn 1: Initial call to obtain a tool call
response_1 = client.chat.completions.create(
model="turing/gemini-3-pro-latest",
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}],
)
# Turn 2: Provide tool result (key: reuse response_1.choices[0].message as-is)
messages = [
{"role": "user", "content": "What's the weather in Tokyo?"},
response_1.choices[0].message, # automatically includes thought_signature
{
"role": "tool",
"content": '{"temperature": 30, "condition": "sunny"}',
"tool_call_id": response_1.choices[0].message.tool_calls[0].id,
},
]
response_2 = client.chat.completions.create(
model="turing/gemini-3-pro-latest",
messages=messages,
tools=[...],
)
Manually Constructing the Message (cURL / Custom Workflows)
When using cURL or a custom workflow, you must manually place thought_signature inside provider_specific_fields:
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"Tokyo\"}"
},
"provider_specific_fields": {
"thought_signature": "eyJ0aGlua2luZ19zdGF0ZSI6Li4ufQ=="
}
}]
}
Multi-Turn Conversations (Without Tools)
Even without function calling, thought_signature must be preserved across turns in multi-turn conversations:
# Turn 1
response_1 = client.chat.completions.create(
model="turing/gemini-3-pro-latest",
messages=[{"role": "user", "content": "Analyze the development trends in quantum computing."}],
)
# Turn 2: likewise reuse the message object directly
messages = [
{"role": "user", "content": "Analyze the development trends in quantum computing."},
response_1.choices[0].message, # automatically carries thought_signature
{"role": "user", "content": "Based on your analysis, what specific investment recommendations would you make?"},
]
client.chat.completions.create(
model="turing/gemini-3-pro-latest",
messages=messages,
)
Field location: message.provider_specific_fields.thought_signature.
Key Constraints
- Must not be modified:
thought_signaturemust be passed back exactly as received. Any alteration will cause a 400 error. - Must not be omitted: Failing to include it from the second turn onward will result in a 400 error.
- Not reusable across models: Signatures from
gemini-3-proandgemini-3-flashare mutually incompatible; switching models will invalidate the signature.
Common Errors
# ❌ Wrong: thought_signature omitted when constructing manually
messages.append({
"role": "assistant",
"content": "...", # missing provider_specific_fields
})
# ✅ Correct: reuse response.choices[0].message as a whole
messages.append(response.choices[0].message)