From d680feb5dc9b824e353e9dff8df252030a1ddbb6 Mon Sep 17 00:00:00 2001 From: Caspar <9448365+casparhsws@users.noreply.github.com> Date: Thu, 8 May 2025 07:13:37 +0100 Subject: [PATCH] Fix cache miss for gemini models with response_format (#10635) * Fix unwanted mutation of kwargs in apply_response_schema_transformation * add assertion the original dict is not mutated --- .../vertex_and_google_ai_studio_gemini.py | 19 +++---- ...test_vertex_and_google_ai_studio_gemini.py | 53 ++++++++++--------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 82d0653896..ff4b44d21b 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -337,21 +337,22 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): return old_schema def apply_response_schema_transformation(self, value: dict, optional_params: dict): + new_value = deepcopy(value) # remove 'additionalProperties' from json schema - value = _remove_additional_properties(value) + new_value = _remove_additional_properties(new_value) # remove 'strict' from json schema - value = _remove_strict_from_schema(value) - if value["type"] == "json_object": + new_value = _remove_strict_from_schema(new_value) + if new_value["type"] == "json_object": optional_params["response_mime_type"] = "application/json" - elif value["type"] == "text": + elif new_value["type"] == "text": optional_params["response_mime_type"] = "text/plain" - if "response_schema" in value: + if "response_schema" in new_value: optional_params["response_mime_type"] = "application/json" - optional_params["response_schema"] = value["response_schema"] - elif value["type"] == "json_schema": # type: ignore - if "json_schema" in value and "schema" in value["json_schema"]: # type: ignore + optional_params["response_schema"] = new_value["response_schema"] + elif new_value["type"] == "json_schema": # type: ignore + if "json_schema" in new_value and "schema" in new_value["json_schema"]: # type: ignore optional_params["response_mime_type"] = "application/json" - optional_params["response_schema"] = value["json_schema"]["schema"] # type: ignore + optional_params["response_schema"] = new_value["json_schema"]["schema"] # type: ignore if "response_schema" in optional_params and isinstance( optional_params["response_schema"], dict diff --git a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py index 4b1c085bb4..9abcd57b29 100644 --- a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py +++ b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py @@ -1,5 +1,6 @@ import asyncio from typing import List, cast +from copy import deepcopy from unittest.mock import MagicMock import pytest @@ -69,37 +70,39 @@ def test_get_model_name_from_gemini_spec_model(): def test_vertex_ai_response_schema_dict(): v = VertexGeminiConfig() - transformed_request = v.map_openai_params( - non_default_params={ - "messages": [{"role": "user", "content": "Hello, world!"}], - "response_format": { - "type": "json_schema", - "json_schema": { - "name": "math_reasoning", - "schema": { - "type": "object", - "properties": { - "steps": { - "type": "array", - "items": { - "type": "object", - "properties": { - "thought": {"type": "string"}, - "output": {"type": "string"}, - }, - "required": ["thought", "output"], - "additionalProperties": False, + non_default_params = { + "messages": [{"role": "user", "content": "Hello, world!"}], + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "math_reasoning", + "schema": { + "type": "object", + "properties": { + "steps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "thought": {"type": "string"}, + "output": {"type": "string"}, }, + "required": ["thought", "output"], + "additionalProperties": False, }, - "final_answer": {"type": "string"}, }, - "required": ["steps", "final_answer"], - "additionalProperties": False, + "final_answer": {"type": "string"}, }, - "strict": False, + "required": ["steps", "final_answer"], + "additionalProperties": False, }, + "strict": False, }, }, + } + original_non_default_params = deepcopy(non_default_params) + transformed_request = v.map_openai_params( + non_default_params=non_default_params, optional_params={}, model="gemini-2.0-flash-lite", drop_params=False, @@ -137,6 +140,8 @@ def test_vertex_ai_response_schema_dict(): "required": ["steps", "final_answer"], "propertyOrdering": ["steps", "final_answer"], } + # should not mutate the original non_default_params + assert non_default_params == original_non_default_params class MathReasoning(BaseModel):