Skip to main content

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.

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

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_signature must 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-pro and gemini-3-flash are 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)