fix(vertex_ai): only fall back to a placeholder thought signature on the first parallel function call - #37541
Merged
mateo-berri merged 6 commits intoAug 21, 2026
Conversation
…the first parallel function call Gemini returns a thoughtSignature on the first function call of a parallel batch and leaves the siblings bare. When replaying that assistant turn, litellm gave every unsigned call the skip_thought_signature_validator placeholder, so a three-call turn went back with three signatures where Gemini had produced one. Keep the placeholder for the first call only and forward the siblings with whatever signature they actually carry, which is usually none.
Contributor
Greptile SummaryThe PR narrows Gemini placeholder thought signatures to the first parallel function call while preserving real signatures on any call
Confidence Score: 5/5The PR appears safe to merge No blocking failure remains
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/prompt_templates/factory.py | Extracts only real signatures and limits Gemini-3 placeholder fallback to the first emitted function-call part |
| litellm/llms/vertex_ai/gemini/transformation.py | Updates signature-collection documentation to reflect that synthesized placeholders are no longer returned by the extraction helper |
| tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py | Adds focused coverage for parallel-call signature preservation and placeholder scoping across Gemini model variants |
Reviews (2): Last reviewed commit: "test(vertex_ai): cover gemini-3.5-flash ..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…alls Link the Gemini Enterprise Agent Platform docs at both places the behavior is decided. The docs state that only the first functionCall part of a parallel batch carries a thought_signature, and that setting skip_thought_signature_validator "should be a last resort as it will negatively impact model performance".
…cstrings Add gemini-3.5-flash to the placeholder-scoping matrix and a regression test that a natively signed parallel turn replays with no skip_thought_signature_validator anywhere in the payload, the shape that was producing empty text responses on 3.5. Hoist the repeated placeholder expression into one constant and rewrite the docstrings that restated their own assertions to say why the case matters instead.
Contributor
Contributor
|
bugbot run |
Contributor
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a5ad22b. Configure here.
mateo-berri
approved these changes
Aug 21, 2026
mateo-berri
left a comment
Contributor
There was a problem hiding this comment.
LGTM. Thanks for the contribution!
mateo-berri
merged commit Aug 21, 2026
6eacdbf
into
BerriAI:litellm_internal_staging
73 of 75 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
Problem this solves:
Google documents both halves of this. On the expected shape: "In cases of parallel function calls in a single response, only the first functionCall part will contain the thought_signature." On the placeholder: "You can set thought_signature to skip_thought_signature_validator, but, this should be a last resort as it will negatively impact model performance." Fabricating one per sibling call is the opposite of a last resort, and it costs quality on every parallel tool-calling turn
That cost is measurable, not theoretical. Internal testing at Google found that setting
skip_thought_signature_validatoron parallel function calls causes gemini-3.5 to return empty text responses. Following the documented shape, one real signature on the first call and nothing on the siblings, reduced the error rateHow it solves it:
User Flow
Before: a developer using parallel tool calling against a Gemini 3 model gets their conversation replayed with signatures the model never produced
get_weathertool and ask for Paris, London and Tokyo in parallel__thought__suffix, the other two are bareskip_thought_signature_validatorplaceholder attached to London and Tokyo, so two of the three calls claim a signature the model never returnedAfter: the same replay carries exactly the signatures the model produced
__thought__suffix, the other two are bareRelevant issues
Linear ticket
Resolves LIT-5888
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
E2e against real Vertex AI (
gemini-3.7-flash,vertex_location: global). The observable that matters here is the exact payload Vertex receives on the replay turn, so the deployment'sapi_basepoints at a local recording relay that logs every request body and forwards it unchanged to the realhttps://aiplatform.googleapis.com. Proxy config:Both legs run the same two turns against their own proxy, booted from the commit under test:
Before (da7a10e, the merge base)
Turn 1 returns three parallel tool calls, a signature only on the first id:
Turn 2 answers normally (200,
finish_reason: stop): "The current weather in Paris, London, and Tokyo is: Paris 20°C, London 20°C, Tokyo 20°C". The model parts of that request as the relay recorded them on their way to Vertex:c2tpcF90aG91Z2h0X3NpZ25hdHVyZV92YWxpZGF0b3I=is base64 forskip_thought_signature_validator: London and Tokyo were sent claiming a signature the model never producedAfter (a5ad22b, the PR tip)
Turn 1 returns three parallel tool calls, same shape as before, a signature only on the first id:
Turn 2 answers normally (200,
finish_reason: stop):The model parts of that request as the relay recorded them on their way to Vertex:
Paris keeps its real signature and the two unsigned siblings are replayed bare, matching what Gemini returned. No placeholder anywhere in the request
Notes from the runs, both pre-existing behavior this PR leaves alone:
function_call); Vertex accepts themType
🐛 Bug Fix
Caveats (if any)
provider_specific_fields.thought_signatureis the carrier that works there. Pre-existing and unchanged by this PR: the first call still gets a placeholder and the siblings go bare, which Vertex acceptsFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
a5ad22b passes /live-pr-risk