diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index d0f0e5c1e24..ef35fc09610 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -748,13 +748,14 @@ def _transform_request_body( # noqa: PLR0915 ] data = RequestBody(contents=content) - if system_instructions is not None: + # Vertex rejects system_instruction/tools/toolConfig alongside cachedContent. + if system_instructions is not None and cached_content is None: data["system_instruction"] = system_instructions - if tools is not None: + if tools is not None and cached_content is None: data["tools"] = tools - if tool_choice is not None: + if tool_choice is not None and cached_content is None: data["toolConfig"] = tool_choice - if include_server_side_tool_invocations: + if include_server_side_tool_invocations and cached_content is None: if "toolConfig" not in data: data["toolConfig"] = {} data["toolConfig"]["includeServerSideToolInvocations"] = True diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py index 6937b4c3ba1..6952be7db3c 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py @@ -86,6 +86,53 @@ def test_check_if_part_exists_in_parts_camel_case_snake_case(): assert check_if_part_exists_in_parts(parts_mixed, part_mixed_casing) +def test_cached_content_omits_system_instruction_tools_toolconfig(): + """Regression: #26014 / #17304 — cachedContent must not ship with tools/system/toolConfig.""" + cache_name = "projects/p/locations/us-central1/cachedContents/abc123" + messages = [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "hi"}, + ] + optional_params = { + "tools": [ + { + "functionDeclarations": [ + {"name": "get_weather", "description": "Get weather"}, + ] + } + ], + "tool_choice": {"functionCallingConfig": {"mode": "AUTO"}}, + } + + result = _transform_request_body( + messages=list(messages), + model="gemini-2.5-pro", + optional_params=dict(optional_params), + custom_llm_provider="vertex_ai", + litellm_params={}, + cached_content=cache_name, + ) + + assert result.get("cachedContent") == cache_name + assert "system_instruction" not in result + assert "tools" not in result + assert "toolConfig" not in result + assert "contents" in result + + # Without cache, conflicting fields are included as before + result_no_cache = _transform_request_body( + messages=list(messages), + model="gemini-2.5-pro", + optional_params=dict(optional_params), + custom_llm_provider="vertex_ai", + litellm_params={}, + cached_content=None, + ) + assert "system_instruction" in result_no_cache + assert "tools" in result_no_cache + assert "toolConfig" in result_no_cache + + # Tests for issue #14556: Labels field provider-aware filtering def test_google_genai_excludes_labels(): """Test that Google GenAI/AI Studio endpoints exclude labels when custom_llm_provider='gemini'"""