From 9e523dff450ad4e282f175547f5ad6053838e6a3 Mon Sep 17 00:00:00 2001 From: Matt Trefilek Date: Thu, 27 Aug 2026 11:18:46 -0500 Subject: [PATCH] fix(gemini): send googleMaps retrievalConfig in the request body VertexGeminiConfig._map_function extracts latitude/longitude from a googleMaps tool into optional_params["toolConfig"]["retrievalConfig"], but _transform_request_body only built toolConfig from tool_choice, so the location never reached Google and Maps grounding ran without it Pop toolConfig from optional_params and merge it with tool_choice. Type retrievalConfig on ToolConfig --- .../llms/vertex_ai/gemini/transformation.py | 12 ++++- litellm/types/llms/vertex_ai.py | 12 +++++ .../vertex_ai/gemini/test_transformation.py | 53 +++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index 11c026010ee..95c6b42962a 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -1191,6 +1191,7 @@ def _transform_request_body( ) tools: Final[Tools | None] = optional_params.pop("tools", None) tool_choice: Final[ToolConfig | None] = optional_params.pop("tool_choice", None) + tool_config: Final[ToolConfig | None] = optional_params.pop("toolConfig", None) include_server_side_tool_invocations: bool = optional_params.pop("include_server_side_tool_invocations", False) safety_settings: list[SafetSettingsConfig] | None = optional_params.pop("safety_settings", None) # Drop output_config as it's not supported by Vertex AI @@ -1223,8 +1224,15 @@ def _transform_request_body( data["system_instruction"] = system_instructions if tools is not None: data["tools"] = tools - if tool_choice is not None: - data["toolConfig"] = tool_choice + merged_tool_config: Final[ToolConfig | None] = ( + {**tool_config, **tool_choice} + if tool_config is not None and tool_choice is not None + else tool_config + if tool_config is not None + else tool_choice + ) + if merged_tool_config is not None: + data["toolConfig"] = merged_tool_config if include_server_side_tool_invocations: if "toolConfig" not in data: data["toolConfig"] = {} diff --git a/litellm/types/llms/vertex_ai.py b/litellm/types/llms/vertex_ai.py index 3b95b786631..fdff781ffae 100644 --- a/litellm/types/llms/vertex_ai.py +++ b/litellm/types/llms/vertex_ai.py @@ -2,6 +2,7 @@ from enum import Enum from typing import Any, Final, Literal, Protocol from typing_extensions import ( + ReadOnly, Required, TypedDict, ) @@ -278,9 +279,20 @@ class Tools(TypedDict, total=False): retrieval: Retrieval +class LatLng(TypedDict): + latitude: ReadOnly[float] + longitude: ReadOnly[float] + + +class RetrievalConfig(TypedDict, total=False): + latLng: ReadOnly[LatLng] + languageCode: ReadOnly[str] + + class ToolConfig(TypedDict, total=False): functionCallingConfig: FunctionCallingConfig includeServerSideToolInvocations: bool + retrievalConfig: ReadOnly[RetrievalConfig] class TTL(TypedDict, total=False): diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_transformation.py b/tests/test_litellm/llms/vertex_ai/gemini/test_transformation.py index fad310fc5c0..2e50fa9a19a 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_transformation.py @@ -338,3 +338,56 @@ def test_map_function_enterprise_web_search_snake_case(): assert len(result) == 1 assert "enterpriseWebSearch" in result[0] + + +def test__transform_request_body_google_maps_retrieval_config_is_sent(): + """ + googleMaps latitude/longitude are extracted into optional_params["toolConfig"]["retrievalConfig"] + by VertexGeminiConfig._map_function. That toolConfig must reach the request body, otherwise + Google grounds without a location. + """ + optional_params = {} + tools = VertexGeminiConfig()._map_function( + value=[{"googleMaps": {"latitude": 37.7749, "longitude": -122.4194}}], + optional_params=optional_params, + ) + optional_params["tools"] = tools + + rb: RequestBody = transformation._transform_request_body( + messages=[{"role": "user", "content": "coffee near me"}], + model="gemini-3-flash-preview", + optional_params=optional_params, + custom_llm_provider="gemini", + litellm_params={}, + cached_content=None, + ) + + assert rb["tools"] == [{"googleMaps": {}}] + assert rb["toolConfig"] == {"retrievalConfig": {"latLng": {"latitude": 37.7749, "longitude": -122.4194}}} + + +def test__transform_request_body_tool_config_merges_with_tool_choice(): + """ + A googleMaps retrievalConfig and a function-calling tool_choice both live under toolConfig; + neither should clobber the other. + """ + optional_params = { + "toolConfig": {"retrievalConfig": {"latLng": {"latitude": 1.0, "longitude": 2.0}}}, + "tool_choice": {"functionCallingConfig": {"mode": "AUTO"}}, + "include_server_side_tool_invocations": True, + } + + rb: RequestBody = transformation._transform_request_body( + messages=[{"role": "user", "content": "hi"}], + model="gemini-3-flash-preview", + optional_params=optional_params, + custom_llm_provider="gemini", + litellm_params={}, + cached_content=None, + ) + + assert rb["toolConfig"] == { + "retrievalConfig": {"latLng": {"latitude": 1.0, "longitude": 2.0}}, + "functionCallingConfig": {"mode": "AUTO"}, + "includeServerSideToolInvocations": True, + }