Skip to content

fix(google_genai): read functionDeclarations.parameters in the generateContent bridge - #37568

Closed
vovanphuc wants to merge 4 commits into
BerriAI:litellm_internal_stagingfrom
vovanphuc:fix/genai-function-declaration-parameters
Closed

vovanphuc wants to merge 4 commits into
BerriAI:litellm_internal_stagingfrom
vovanphuc:fix/genai-function-declaration-parameters

Conversation

@vovanphuc

@vovanphuc vovanphuc commented Aug 20, 2026 •

Copy link
Copy Markdown

TLDR

Problem this solves:

  • Gemini tool schemas vanish when the model is not Gemini
  • Models get a function with no arguments, invent their own
  • Fails silently: 200 OK, wrong args, nothing logged

How it solves it:

  • Read functionDeclarations[].parameters when parametersJsonSchema is absent
  • parametersJsonSchema still wins when both are present

User Flow

Before: a developer whose app uses the Google GenAI SDK against the proxy gets function calls back with empty arguments as soon as the model name is served by a non-Gemini deployment

  1. They send POST https://litellm-domain/v1beta/models/my-model:generateContent, where my-model is served by an OpenAI-compatible deployment, declaring one tool get_weather that takes a required string city
  2. They get 200 back with {"functionCall":{"name":"get_weather","args":{}}}, the right function with no arguments
  3. Their app calls get_weather() with no city and raises a KeyError
  4. On repeated runs the model sometimes answers {"args":{"location":"Hanoi"}}, a parameter name that appears nowhere in the tool they declared
  5. They point the same app at my-gemini-model, send the identical request, and get {"functionCall":{"name":"get_weather","args":{"city":"Hanoi"}}}, so the same client code works on one model and silently misbehaves on another

After: the same request returns the arguments the tool declared, whichever provider serves the model

  1. They send POST https://litellm-domain/v1beta/models/my-model:generateContent, where my-model is served by an OpenAI-compatible deployment, declaring one tool get_weather that takes a required string city
  2. They get 200 back with {"functionCall":{"name":"get_weather","args":{"city":"Hanoi"}}}
  3. Their app calls get_weather(city="Hanoi") and returns the right weather
  4. Repeated runs return the same city argument every time
  5. Pointing the same app at my-gemini-model returns the identical result, so behaviour no longer depends on which provider backs the model name

Relevant issues

Fixes #37567

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v
  • My PR passes all required CI/CD checks (CI re-running after the merge from the base branch)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 (4/5)

Screenshots / Proof of Fix

Shared setup for both runs: ghcr.io/berriai/litellm:v1.100.1 with the source file under test mounted in, same config and prompt

model_list:
  - model_name: my-model
    litellm_params:
      model: openai/gpt-4o
      api_base: https://openrouter.ai/api/v1
      api_key: os.environ/OPENAI_COMPAT_KEY

general_settings:
  master_key: sk-1234

The request, sent 3 times against a live proxy hitting the real provider

for i in 1 2 3; do
curl -s -X POST "http://127.0.0.1:4000/v1beta/models/my-model:generateContent" \
 -H "x-goog-api-key: sk-1234" -H 'Content-Type: application/json' -d '{
  "contents":[{"role":"user","parts":[{"text":"What is the weather in Hanoi? Use the tool."}]}],
  "tools":[{"functionDeclarations":[{"name":"get_weather","description":"Get the weather for a city",
    "parameters":{"type":"OBJECT","properties":{"city":{"type":"STRING","description":"City name"}},"required":["city"]}}]}]}'
echo; done

Before (c2c2a62)

  1. Ran the request above. Every run returns the function with no arguments
{"candidates":[{"content":{"parts":[{"functionCall":{"name":"get_weather","args":{}}}],"role":"model"},"finishReason":"STOP","index":0,"safetyRatings":[]}],"usageMetadata":{"promptTokenCount":49,"candidatesTokenCount":10,"totalTokenCount":59}}
{"candidates":[{"content":{"parts":[{"functionCall":{"name":"get_weather","args":{}}}],"role":"model"},"finishReason":"STOP","index":0,"safetyRatings":[]}],"usageMetadata":{"promptTokenCount":49,"candidatesTokenCount":10,"totalTokenCount":59}}
{"candidates":[{"content":{"parts":[{"functionCall":{"name":"get_weather","args":{}}}],"role":"model"},"finishReason":"STOP","index":0,"safetyRatings":[]}],"usageMetadata":{"promptTokenCount":49,"candidatesTokenCount":10,"totalTokenCount":59}}
  1. Sent the byte-identical schema under parametersJsonSchema instead, same proxy and model and prompt, and the arguments arrive intact, so the upstream model and the provider are fine
{"functionCall": {"name": "get_weather", "args": {"city": "Hanoi"}}}
{"functionCall": {"name": "get_weather", "args": {"city": "Hanoi"}}}
{"functionCall": {"name": "get_weather", "args": {"city": "Hanoi"}}}

After (9b0cbff)

  1. Ran the same request. Every run returns the declared argument
{"candidates":[{"content":{"parts":[{"functionCall":{"name":"get_weather","args":{"city":"Hanoi"}}}],"role":"model"},"finishReason":"STOP","index":0,"safetyRatings":[]}],"usageMetadata":{"promptTokenCount":59,"candidatesTokenCount":15,"totalTokenCount":74}}
{"candidates":[{"content":{"parts":[{"functionCall":{"name":"get_weather","args":{"city":"Hanoi"}}}],"role":"model"},"finishReason":"STOP","index":0,"safetyRatings":[]}],"usageMetadata":{"promptTokenCount":59,"candidatesTokenCount":15,"totalTokenCount":74}}
{"candidates":[{"content":{"parts":[{"functionCall":{"name":"get_weather","args":{"city":"Hanoi"}}}],"role":"model"},"finishReason":"STOP","index":0,"safetyRatings":[]}],"usageMetadata":{"promptTokenCount":59,"candidatesTokenCount":15,"totalTokenCount":74}}

The prompt token count rises from 49 to 59, which is the schema now actually leaving the proxy

Type

🐛 Bug Fix

Caveats (if any)

  • parametersJsonSchema keeps priority, so gemini-cli is untouched
  • New field typed ReadOnly[object], matching parametersJsonSchema on the base

Final 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

@CLAassistant

CLAassistant commented Aug 20, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR preserves Google GenAI function declarations using the standard parameters field while retaining parametersJsonSchema precedence

  • Adds parameters to the function-declaration type and maps it into OpenAI-compatible tools
  • Adds regression coverage for standard parameters conversion and dual-field precedence

Confidence Score: 4/5

The PR appears safe to merge, with only non-blocking repository-organization and test-comment cleanup requested

The fallback preserves the intended schema and existing precedence without an established runtime regression; remaining findings concern repository conventions and maintainability

Files Needing Attention: litellm/google_genai/adapters/transformation.py, tests/test_litellm/google_genai/test_google_genai_adapter.py

Important Files Changed

Filename Overview
litellm/google_genai/adapters/transformation.py Correctly adds the schema fallback and normalization path, but extends provider-specific logic outside the required provider hierarchy
tests/test_litellm/google_genai/test_google_genai_adapter.py Adds focused regression and precedence coverage, with unnecessary explanatory docstrings under the repository comment policy

Reviews (1): Last reviewed commit: "fix(google_genai): read functionDeclarat..." | Re-trigger Greptile

Comment on lines +363 to +364
elif "parameters" in func_decl:
function_chunk["parameters"] = func_decl["parameters"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Provider logic outside standard hierarchy

This Google GenAI-specific branch extends provider behavior outside litellm/llms, increasing fragmentation and making future provider maintenance harder.

Rule Used: What: Avoid writing provider-specific code outside... (source)

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!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sits beside the existing parametersJsonSchema read in the same adapter. Relocating the bridge into litellm/llms is a separate refactor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point — the new elif is a one-liner that mirrors the existing parametersJsonSchema branch immediately above it, so it doesn't add new fragmentation. The adapter already lives outside litellm/llms, and relocating the whole bridge is a separate concern. The fix is correctly scoped.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment thread tests/test_litellm/google_genai/test_google_genai_adapter.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88cb413656

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/test_litellm/google_genai/test_google_genai_adapter.py Outdated
@codecov

codecov Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…teContent bridge

The generate_content -> chat/completions bridge only read
functionDeclarations[].parametersJsonSchema, the field gemini-cli sends.
The google-genai SDKs serialize tool schemas to `parameters`, so those
requests reached the upstream provider with a function that had a name
and description but no arguments, and the model invented its own.

Fall back to `parameters` when `parametersJsonSchema` is absent, and
declare it on _GenAIFunctionDeclaration.
…only Mapping

The new TypedDict field tripped the LIT001 type-discipline budget by
adding one more mutable collection annotation. Declare it as
Mapping[str, object] instead, which is the read-only view the rule asks
for, leaving the budget at its base total.
@vovanphuc
vovanphuc force-pushed the fix/genai-function-declaration-parameters branch from 012bef4 to 24e8619 Compare August 20, 2026 02:30
The names and assertions already say what each case covers, so the
prose only duplicated them.
@codspeed

codspeed Bot commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing vovanphuc:fix/genai-function-declaration-parameters (9b0cbff) with litellm_internal_staging (c2c2a62)

Open in CodSpeed

@yuneng-berri
yuneng-berri deleted the branch BerriAI:litellm_internal_staging September 13, 2026 04:43
@yuneng-berri yuneng-berri reopened this Sep 13, 2026
…ration-parameters

Conflicts in litellm/google_genai/adapters/transformation.py: keep the
base's imports, and annotate the new `parameters` field as
ReadOnly[object] to match `parametersJsonSchema`, which the base now
declares that way.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@mateo-berri

Copy link
Copy Markdown
Contributor

Closing in favor of #42067, which landed the tool parameters fix together with response_schema forwarding. Thanks vovanphuc for the groundwork here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: generateContent bridge drops functionDeclarations.parameters, so tool schemas are silently lost for non-Gemini providers

4 participants