Skip to content

fix(gemini): preserve toolConfig on native generate_content - #23493

Merged
1 commit merged into
BerriAI:mainfrom
emerzon:fix/gemini-native-preserve-toolconfig
Mar 13, 2026
Merged

fix(gemini): preserve toolConfig on native generate_content#23493
1 commit merged into
BerriAI:mainfrom
emerzon:fix/gemini-native-preserve-toolconfig

Conversation

@emerzon

@emerzon emerzon commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Fixes #23491

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🐛 Bug Fix
✅ Test

Changes

This fixes the native Google/Vertex generate_content path dropping top-level toolConfig.

Before this change, LiteLLM preserved contents but rebuilt the downstream native request body without toolConfig. For affected Gemini tool-calling requests, that changed provider behavior from a normal follow-up tool call into a single empty STOP event.

This PR:

  • threads toolConfig / tool_config through the native generate_content setup path
  • passes toolConfig through BaseLLMHTTPHandler.generate_content_handler() and async_generate_content_handler()
  • includes toolConfig in both Google-native and Vertex-native transform_generate_content_request() output
  • adds regression coverage to verify native request transforms preserve toolConfig
  • updates the proxy request test to assert the outbound native request body includes toolConfig

Copilot AI review requested due to automatic review settings March 12, 2026 22:46
@vercel

vercel Bot commented Mar 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 12, 2026 11:07pm

Request Review

@greptile-apps

greptile-apps Bot commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression in the native Google/Vertex generate_content path where toolConfig was being silently dropped, causing tool-calling requests to receive an empty STOP response. It threads the tool_config field through every entrypoint (generate_content, agenerate_content_stream, generate_content_stream, setup_generate_content_call) down to both concrete transform_generate_content_request implementations (Gemini and Vertex AI). As a companion fix, system_instruction — which had the same omission in the sync generate_content_stream path — is also extracted and forwarded there.

Key changes:

  • New _get_tool_config_from_kwargs helper uses explicit key presence checks ("toolConfig" in kwargs) instead of falsy evaluation, correctly preserving intentionally empty dict values
  • tool_config parameter added to BaseLLMHTTPHandler.generate_content_handler, async_generate_content_handler, and both transform_generate_content_request overrides
  • VertexAIGoogleGenAIConfig also aligns the system_instruction guard from a falsy check to is not None, removing a pre-existing inconsistency
  • Regression coverage added: parameterized transformation test across both provider configs, a sync-stream forwarding test, and an updated proxy request test that asserts toolConfig in the outbound body

Confidence Score: 4/5

  • This PR is safe to merge; it is a focused, additive parameter-threading fix with no backwards-incompatible changes and good mock-based test coverage.
  • The fix is mechanically straightforward — every call site that was missing tool_config has been updated and backed by a test. The helper function avoids the falsy-dict footgun. The only deduction is that the agenerate_content_stream path still has no test asserting tool_config forwarding (the existing test only asserts stream=True), and the redundant transform_generate_content_request call inside setup_generate_content_call remains a maintenance footgun for future parameter additions — both noted in prior review threads.
  • No files require special attention; all changed files have been reviewed and the logic is consistent.

Important Files Changed

Filename Overview
litellm/google_genai/main.py Adds _get_tool_config_from_kwargs helper, extracts and forwards tool_config through all four call paths (generate_content, agenerate_content_stream, generate_content_stream, setup_generate_content_call). Also backfills the missing system_instruction extraction in generate_content_stream, addressing the issue flagged in the previous review thread.
litellm/llms/custom_httpx/llm_http_handler.py Adds tool_config parameter to both generate_content_handler and async_generate_content_handler, and correctly threads it into transform_generate_content_request in both the sync path and the _is_async delegation path.
litellm/llms/gemini/google_genai/transformation.py Adds tool_config parameter to transform_generate_content_request and emits toolConfig into the request dict when the value is not None. Consistent with the system_instruction guard pattern.
litellm/llms/vertex_ai/google_genai/transformation.py Adds tool_config to transform_generate_content_request, correctly guarded with if tool_config is not None. Also aligns system_instruction guard from falsy check to is not None — a pre-existing inconsistency addressed as a bonus improvement.
litellm/llms/base_llm/google_genai/transformation.py Updates the abstract base class signature to include tool_config as an optional parameter, keeping it consistent with all concrete implementations.
tests/test_litellm/google_genai/test_google_genai_main.py Adds test_generate_content_stream_forwards_system_instruction which asserts both tool_config and system_instruction are forwarded in the sync stream path. Fixes an existing test assertion that was using == as a statement instead of assert. No real network calls — all mocked.
tests/test_litellm/google_genai/test_google_genai_transformation.py Adds parameterized test_transform_generate_content_request_preserves_tool_config covering both GoogleGenAIConfig and VertexAIGoogleGenAIConfig. Updates existing tests to pass tool_config=None explicitly after the new required param was added to the signature.
tests/proxy_unit_tests/test_google_gemini_proxy_request.py Injects toolConfig into the sample payload and asserts it is present in the outbound HTTP request body, providing end-to-end regression coverage for the proxy path.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant generate_content / agenerate_content_stream / generate_content_stream
    participant setup_generate_content_call
    participant BaseLLMHTTPHandler
    participant async_generate_content_handler
    participant transform_generate_content_request

    Caller->>generate_content / agenerate_content_stream / generate_content_stream: call(kwargs incl. toolConfig)
    generate_content / agenerate_content_stream / generate_content_stream->>setup_generate_content_call: setup(..., **kwargs)
    setup_generate_content_call->>transform_generate_content_request: transform(..., tool_config)
    Note over setup_generate_content_call: request_body stored in SetupResult (unused by callers)
    setup_generate_content_call-->>generate_content / agenerate_content_stream / generate_content_stream: SetupResult

    generate_content / agenerate_content_stream / generate_content_stream->>generate_content / agenerate_content_stream / generate_content_stream: tool_config = _get_tool_config_from_kwargs(kwargs)
    generate_content / agenerate_content_stream / generate_content_stream->>BaseLLMHTTPHandler: generate_content_handler(..., tool_config)

    alt _is_async = True
        BaseLLMHTTPHandler->>async_generate_content_handler: async_generate_content_handler(..., tool_config)
        async_generate_content_handler->>transform_generate_content_request: transform(..., tool_config)
        transform_generate_content_request-->>async_generate_content_handler: {contents, tools, toolConfig, ...}
    else sync
        BaseLLMHTTPHandler->>transform_generate_content_request: transform(..., tool_config)
        transform_generate_content_request-->>BaseLLMHTTPHandler: {contents, tools, toolConfig, ...}
    end

    BaseLLMHTTPHandler->>BaseLLMHTTPHandler: HTTP POST with toolConfig in body
Loading

Last reviewed commit: 296ea95

Comment thread litellm/google_genai/main.py Outdated

Copilot AI left a comment

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.

Pull request overview

This PR adds support for passing toolConfig through LiteLLM’s Google GenAI generate_content request flow (including Vertex AI’s Google GenAI-compatible path), and updates unit tests to validate the field is preserved end-to-end.

Changes:

  • Thread tool_config from litellm.google_genai.main into the HTTP handler and provider transform_generate_content_request(...).
  • Add toolConfig mapping in both GoogleGenAIConfig and VertexAIGoogleGenAIConfig request transformations.
  • Extend tests to assert toolConfig is preserved in transformed requests and outgoing HTTP JSON bodies.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
litellm/google_genai/main.py Extracts toolConfig/tool_config from kwargs and forwards it into request construction + handler calls.
litellm/llms/custom_httpx/llm_http_handler.py Adds tool_config plumbing into sync/async generate_content handler signatures and transform calls.
litellm/llms/base_llm/google_genai/transformation.py Updates the base interface signature/docs to include tool_config.
litellm/llms/gemini/google_genai/transformation.py Adds toolConfig into the Google GenAI request payload.
litellm/llms/vertex_ai/google_genai/transformation.py Adds toolConfig into the Vertex AI Google GenAI-format request payload.
tests/test_litellm/google_genai/test_google_genai_transformation.py Adds/updates tests to ensure toolConfig is preserved for both Google and Vertex configs.
tests/proxy_unit_tests/test_google_gemini_proxy_request.py Verifies the outbound HTTP request JSON includes the provided toolConfig.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread litellm/llms/vertex_ai/google_genai/transformation.py Outdated
Comment thread litellm/llms/vertex_ai/google_genai/transformation.py Outdated
Comment thread litellm/llms/gemini/google_genai/transformation.py Outdated
Comment thread litellm/llms/custom_httpx/llm_http_handler.py Outdated
Comment thread litellm/llms/custom_httpx/llm_http_handler.py Outdated
Comment thread litellm/llms/base_llm/google_genai/transformation.py Outdated
Comment thread litellm/google_genai/main.py Outdated
Comment thread litellm/google_genai/main.py Outdated
Comment thread litellm/google_genai/main.py Outdated
Comment thread litellm/google_genai/main.py Outdated
Comment thread tests/test_litellm/google_genai/test_google_genai_main.py
Comment thread litellm/llms/vertex_ai/google_genai/transformation.py Outdated
Comment thread litellm/google_genai/main.py
@emerzon
emerzon force-pushed the fix/gemini-native-preserve-toolconfig branch from f3108f0 to 296ea95 Compare March 12, 2026 23:05
@ghost
ghost merged commit 92d39c3 into BerriAI:main Mar 13, 2026
15 of 37 checks passed
RheagalFire pushed a commit that referenced this pull request Mar 13, 2026
* bump: version 1.82.1 → 1.82.2

* fix(gemini): preserve toolConfig on native generate_content (#23493)

* chore: regenerate poetry.lock to match pyproject.toml (#23514)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(docs): correct Docker image tag in v1.82.0 release notes

Add missing 'v' prefix to Docker image tag: main-1.82.0-stable → main-v1.82.0-stable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com>
Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
RheagalFire pushed a commit that referenced this pull request Mar 13, 2026
…#23505)

* fix(gemini): preserve toolConfig on native generate_content (#23493)

* fix(ui): use sanitizeNumeric for team_member_budget in team edit form

The team edit form (TeamInfo.tsx) used Number() to convert the
team_member_budget field value, which silently converts null/undefined/""
to 0. When an admin edits a team for any reason without touching the
budget field, this sends team_member_budget=0 to the backend, creating a
shared budget row with max_budget=0.0 that blocks all team members.

Use sanitizeNumeric (already used for tpm_limit, rpm_limit, soft_budget
in the same form) which correctly returns null for empty/null/undefined
values, preventing accidental zero-budget creation.

---------

Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
RheagalFire pushed a commit that referenced this pull request Mar 13, 2026
…#23568)

* bump: version 1.82.1 → 1.82.2

* fix(gemini): preserve toolConfig on native generate_content (#23493)

* chore: regenerate poetry.lock to match pyproject.toml (#23514)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix claude.md

* ui logo (#23556)

* fix(proxy): prevent OOM/Prisma connection loss from unbounded managed-object poll (#23472)

* fix(proxy): cap managed-object poll size + expire stale rows + kill-switch flag to prevent OOM/Prisma connection loss

* fix(constants): simplify PROXY_BATCH_POLLING_ENABLED readability

* docs+test: document new polling env vars, add pagination+stale-cleanup tests

* fix: exclude stale_expired from batch poll queries; fix update_many assertions in tests

* fix: scope stale cleanup to file_purpose, fix file_object mocks, add CheckBatchCost tests

* fix: avoid duplicate cost logging in fallback path; guard integer constants against zero/negative values

* fix: cache _has_batch_processed_column; guard cleanup from aborting poll; narrow fallback except

* fix: add complete/completed to primary query not_in; fix vacuous test assertion

- Primary find_many was missing "complete" and "completed" in its not_in
  filter, creating asymmetry with the fallback query. A job whose status
  was set to "complete" but whose batch_processed flag update failed would
  be silently re-fetched and re-processed every cycle, emitting duplicate
  cost logs.

- test_fallback_completion_update_omits_batch_processed patched
  _is_base64_encoded_unified_file_id to return None, causing an immediate
  continue — so update() was never called and the assertion looped over an
  empty list (vacuously true). Rewrote the test to mock the full
  completion pipeline, verify update() is called exactly once, and assert
  batch_processed is absent from the update data.

- Added symmetric test (primary path) proving batch_processed IS included
  when the column exists.

Made-with: Cursor

* fix(huggingface): forward extra_headers to embedding handler (#23502)

The huggingface branch in litellm.embedding() did not pass the headers
kwarg to huggingface_embed.embedding(), silently dropping user-provided
extra_headers like X-HF-Bill-To.

Fixes #23502

Made-with: Cursor

---------

Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com>
Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <ishaanjaffer0324@gmail.com>
@emerzon
emerzon deleted the fix/gemini-native-preserve-toolconfig branch April 29, 2026 01:23
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
This pull request was closed.
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]: Native Gemini/Vertex generate_content drops toolConfig, causing empty STOP on valid tool-calling requests

2 participants