Skip to content
Merged
36 changes: 21 additions & 15 deletions litellm/litellm_core_utils/prompt_templates/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,13 +1200,14 @@ def _encode_tool_call_id_with_signature(tool_call_id: str, thought_signature: st
return tool_call_id


def _get_thought_signature_from_tool(tool: dict, model: str | None = None) -> str | None:
def _get_thought_signature_from_tool(tool: dict) -> str | None:
"""Extract thought signature from tool call's provider_specific_fields.

If not provided try to extract thought signature from tool call id

Checks both tool.provider_specific_fields and tool.function.provider_specific_fields.
If no signature is found and model is gemini-3, returns a dummy signature.
Returns None when the tool call carries no signature; callers decide whether a
placeholder signature is needed.
"""
# First check tool's provider_specific_fields
provider_fields: Final = tool.get("provider_specific_fields") or {}
Expand Down Expand Up @@ -1236,13 +1237,6 @@ def _get_thought_signature_from_tool(tool: dict, model: str | None = None) -> st
if len(parts) == 2:
_, signature = parts
return signature
# If no signature found and model is gemini-3, return dummy signature
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
VertexGeminiConfig,
)

if model and VertexGeminiConfig._is_gemini_3_or_newer(model):
return _get_dummy_thought_signature()
return None


Expand All @@ -1251,10 +1245,14 @@ def _get_dummy_thought_signature() -> str:

This is used when transferring conversation history from older models
(like gemini-2.5-flash) to gemini-3, which requires thought_signature
for strict validation.
for strict validation. Google documents it as a last resort that "will
negatively impact model performance", so callers must only fall back to it
when no real signature is available.

See:
https://ai.google.dev/gemini-api/docs/thought-signatures#faqs
https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/thinking/thought-signatures
"""
# Return a base64-encoded dummy signature string
# Below dummy signature is recommended by google - https://ai.google.dev/gemini-api/docs/thought-signatures#faqs
dummy_data: Final = b"skip_thought_signature_validator"
return base64.b64encode(dummy_data).decode("utf-8")

Expand Down Expand Up @@ -1312,16 +1310,24 @@ def convert_to_gemini_tool_call_invoke(
VertexGeminiConfig,
)

needs_dummy_signature: Final = model is not None and VertexGeminiConfig._is_gemini_3_or_newer(model)
Comment thread
greptile-apps[bot] marked this conversation as resolved.

if tool_calls is not None:
for idx, tool in enumerate(tool_calls):
for tool in tool_calls:
if "function" in tool:
gemini_function_call: VertexFunctionCall | None = _gemini_tool_call_invoke_helper(
function_call_params=tool["function"],
tool_call_id=(tool.get("id") if forward_function_call_id else None),
)
if gemini_function_call is not None:
part_dict: VertexPartType = {"function_call": gemini_function_call}
thought_signature = _get_thought_signature_from_tool(dict(tool), model=model)
thought_signature = _get_thought_signature_from_tool(dict(tool))
# Gemini signs only the first functionCall part of a parallel batch, so scope the
# placeholder fallback to that part instead of fabricating one per sibling call:
# https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/thinking/thought-signatures#parallel_function_calling_example
is_first_function_call = len(_parts_list) == 0
if not thought_signature and is_first_function_call and needs_dummy_signature:
thought_signature = _get_dummy_thought_signature()
if thought_signature:
part_dict["thoughtSignature"] = thought_signature

Expand All @@ -1344,7 +1350,7 @@ def convert_to_gemini_tool_call_invoke(
thought_signature = provider_fields.get("thought_signature")

# If no signature found and model is gemini-3, use dummy signature
if not thought_signature and model and VertexGeminiConfig._is_gemini_3_or_newer(model):
if not thought_signature and needs_dummy_signature:
thought_signature = _get_dummy_thought_signature()

if thought_signature:
Expand Down
7 changes: 3 additions & 4 deletions litellm/llms/vertex_ai/gemini/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,10 +645,9 @@ def _collect_tool_call_thought_signatures(
the text part as well would send two copies and double-bill the previous
turn's reasoning tokens on gemini-3 and newer models.

Detection deliberately calls _get_thought_signature_from_tool without the
model argument: with a gemini-3 model that helper synthesizes a dummy
signature for unsigned tool calls, which must not suppress a real
text-part signature (e.g. replaying gemini-2.5 history to a newer model).
Only real signatures count here; a synthesized placeholder must not
suppress a genuine text-part signature (e.g. replaying gemini-2.5 history
to a newer model).
"""
signatures: tuple[str, ...] = ()

Expand Down
Loading
Loading