fix(vertex_ai): strip LiteLLM-internal keys from extra_body before merging to Gemini request - #23131
Conversation
…rging to Gemini request PR #20950 added extra_body forwarding to Vertex AI Gemini. LiteLLM-internal keys (cache, tags) were being merged into the request body, causing Vertex AI to reject with 400: 'Unknown name "cache": Cannot find field.' - Add _LITELLM_INTERNAL_EXTRA_BODY_KEYS frozenset (cache, tags) - Skip these keys in _pop_and_merge_extra_body before merging - Add regression tests for cache and tags stripping Fixes regression from 1.79.3 → 1.81.12 when using proxy cache with extra_body={"cache": {"use-cache": True, "ttl": 86400}} Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes a regression introduced in #20950 where LiteLLM-internal Key changes:
Issues found:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| litellm/llms/vertex_ai/gemini/transformation.py | Adds _LITELLM_INTERNAL_EXTRA_BODY_KEYS frozenset and filters cache/tags from extra_body before merging into the Vertex AI request body. The comment on line 532 is truncated, the frozenset type annotation lacks a subscript, and the fix is scoped only to this provider — other providers merging extra_body are unprotected. |
| tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py | Adds two unit tests verifying that cache and tags are stripped from extra_body before forwarding to Vertex AI, while legitimate keys pass through. Tests are mock-only (no real network calls) and are self-contained. |
| litellm/model_prices_and_context_window_backup.json | Adds pricing and capability metadata for gemini/gemini-3.1-flash-image-preview. Unrelated to the stated fix but the entry looks well-formed and consistent with neighboring model entries. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Client request with extra_body\n{cache: {...}, tags: [...], custom_param: ...}"] --> B["_transform_request_body()"]
B --> C["_pop_and_merge_extra_body(data, optional_params)"]
C --> D{"key in _LITELLM_INTERNAL_EXTRA_BODY_KEYS?"}
D -- "yes (cache, tags)" --> E["skip — key is LiteLLM-internal"]
D -- "no (custom_param, ...)" --> F{"key already in data\nand both are dicts?"}
F -- "yes" --> G["deep-merge: data[k].update(v)"]
F -- "no" --> H["shallow-set: data[k] = v"]
G --> I["Final Vertex AI RequestBody\n(no cache, no tags)"]
H --> I
E --> I
I --> J["POST /generateContent → Vertex AI"]
Last reviewed commit: 4dc277e
| raise e | ||
|
|
||
|
|
||
| # Keys that LiteLLM consumes internally and must never be forwarded to the |
There was a problem hiding this comment.
Truncated comment
The comment on line 532 is cut off mid-sentence — it currently reads # Keys that LiteLLM consumes internally and must never be forwarded to the with no completion. This should be finished to explain what the keys must not be forwarded to.
| # Keys that LiteLLM consumes internally and must never be forwarded to the | |
| # Keys that LiteLLM consumes internally and must never be forwarded to the Vertex AI request body. |
|
|
||
|
|
||
| # Keys that LiteLLM consumes internally and must never be forwarded to the | ||
| _LITELLM_INTERNAL_EXTRA_BODY_KEYS: frozenset = frozenset({"cache", "tags"}) |
There was a problem hiding this comment.
Hardcoded internal-key list is Vertex AI-only and incomplete
_LITELLM_INTERNAL_EXTRA_BODY_KEYS is defined and consumed only inside the Vertex AI/Gemini transformation. Any other provider that merges extra_body (e.g. future providers added via _pop_and_merge_extra_body or similar helpers) will not benefit from this filter and will silently forward cache/tags to the upstream API, causing the same 400 errors this PR is trying to fix.
Additionally, LiteLLM's proxy layer reads other internal keys from extra_body (e.g. metadata, litellm_metadata) that are not currently included in this frozenset. If those keys are ever added to a user's extra_body, they would still be forwarded to Vertex AI.
A more robust approach would be to centralise this filtering at the point where extra_body is first processed (e.g. in litellm_pre_call_utils.py or in the base provider transformation), so all providers are protected. At the very least, consider whether metadata and litellm_metadata should also be included here.
|
|
||
|
|
||
| # Keys that LiteLLM consumes internally and must never be forwarded to the | ||
| _LITELLM_INTERNAL_EXTRA_BODY_KEYS: frozenset = frozenset({"cache", "tags"}) |
There was a problem hiding this comment.
Type annotation on frozenset is redundant
The type annotation frozenset on the right-hand side literal is redundant since Python infers the type automatically from the frozenset(...) constructor call. More importantly, using the bare frozenset without a subscript loses the element type. Either omit the annotation or make it explicit:
| _LITELLM_INTERNAL_EXTRA_BODY_KEYS: frozenset = frozenset({"cache", "tags"}) | |
| _LITELLM_INTERNAL_EXTRA_BODY_KEYS: frozenset[str] = frozenset({"cache", "tags"}) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Restore independent fixes from main that were collaterally removed when PR #23276 (staging_03_10 → main) carried a revert commit: - bedrock: restore output_config pop (PR #23240) - redact_messages: restore dict handling for ModelResponse (PR #23235) - model_checks: restore list() copies to avoid cache mutation (PR #23236) - openapi_to_mcp_generator: restore relative URL handling (PR #23238) - vertex_ai/gemini: restore _LITELLM_INTERNAL_EXTRA_BODY_KEYS check (PR #23131) - openai types: restore extra finish reasons (PR #22138) - completion_extras: restore usage transformation logic Accept main for: model_prices JSONs, credential_endpoints, team_endpoints, object_permission_utils, responses transformation.
…_body_cache_fix fix(vertex_ai): strip LiteLLM-internal keys from extra_body before merging to Gemini request
Restore independent fixes from main that were collaterally removed when PR BerriAI#23276 (staging_03_10 → main) carried a revert commit: - bedrock: restore output_config pop (PR BerriAI#23240) - redact_messages: restore dict handling for ModelResponse (PR BerriAI#23235) - model_checks: restore list() copies to avoid cache mutation (PR BerriAI#23236) - openapi_to_mcp_generator: restore relative URL handling (PR BerriAI#23238) - vertex_ai/gemini: restore _LITELLM_INTERNAL_EXTRA_BODY_KEYS check (PR BerriAI#23131) - openai types: restore extra finish reasons (PR BerriAI#22138) - completion_extras: restore usage transformation logic Accept main for: model_prices JSONs, credential_endpoints, team_endpoints, object_permission_utils, responses transformation.
Problem
After upgrading from litellm proxy 1.79.3 to 1.81.12, Vertex AI Gemini models fail when using proxy cache with
extra_body={"cache": {"use-cache": True, "ttl": 86400}}:Fixes #22970
Root Cause
PR #20950 added
extra_bodyforwarding to the Vertex AI Gemini completion transformation. Before that,extra_bodywas silently dropped—socachenever reached Vertex AI. After the change, all keys fromextra_bodyare merged into the request body. Vertex AI enforces a strict JSON schema and rejects unknown fields likecacheandtags.Solution
_LITELLM_INTERNAL_EXTRA_BODY_KEYSfrozenset (cache,tags)_pop_and_merge_extra_bodybefore merging into the Vertex AI requestcacheis consumed by LiteLLM's proxy response caching layer (caching.md)tagsis consumed by LiteLLM's logging/tracking (litellm_proxy.md)Testing
test_extra_body_cache_not_forwarded_to_vertex_ai— verifiescacheis strippedtest_extra_body_tags_not_forwarded_to_vertex_ai— verifiestagsis strippedextra_bodykeys still pass throughMade with Cursor