diff --git a/docs/my-website/docs/completion/json_mode.md b/docs/my-website/docs/completion/json_mode.md index 14477f9915..0122e20261 100644 --- a/docs/my-website/docs/completion/json_mode.md +++ b/docs/my-website/docs/completion/json_mode.md @@ -341,90 +341,4 @@ curl http://0.0.0.0:4000/v1/chat/completions \ ``` - - -## Gemini - Native JSON Schema Format (Gemini 2.0+) - -Gemini 2.0+ models automatically use the native `responseJsonSchema` parameter, which provides better compatibility with standard JSON Schema format. - -### Benefits (Gemini 2.0+): -- Standard JSON Schema format (lowercase types like `string`, `object`) -- Supports `additionalProperties: false` for stricter validation -- Better compatibility with Pydantic's `model_json_schema()` -- No `propertyOrdering` required - -### Usage - - - - -```python -from litellm import completion -from pydantic import BaseModel - -class UserInfo(BaseModel): - name: str - age: int - -response = completion( - model="gemini/gemini-2.0-flash", - messages=[{"role": "user", "content": "Extract: John is 25 years old"}], - response_format={ - "type": "json_schema", - "json_schema": { - "name": "user_info", - "schema": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"} - }, - "required": ["name", "age"], - "additionalProperties": False # Supported on Gemini 2.0+ - } - } - } -) -``` - - - - -```bash -curl http://0.0.0.0:4000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $LITELLM_API_KEY" \ - -d '{ - "model": "gemini-2.0-flash", - "messages": [ - {"role": "user", "content": "Extract: John is 25 years old"} - ], - "response_format": { - "type": "json_schema", - "json_schema": { - "name": "user_info", - "schema": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"} - }, - "required": ["name", "age"], - "additionalProperties": false - } - } - } - }' -``` - - - - -### Model Behavior - -| Model | Format Used | `additionalProperties` Support | -|-------|-------------|-------------------------------| -| Gemini 2.0+ | `responseJsonSchema` (JSON Schema) | ✅ Yes | -| Gemini 1.5 | `responseSchema` (OpenAPI) | ❌ No | - -LiteLLM automatically selects the appropriate format based on the model version. \ No newline at end of file + \ No newline at end of file diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index d8ddc2579d..2aa6a00c72 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -150,34 +150,6 @@ def get_supports_response_schema( return _supports_response_schema -def supports_response_json_schema(model: str) -> bool: - """ - Check if the model supports responseJsonSchema (JSON Schema format). - - responseJsonSchema is supported by Gemini 2.0+ models and uses standard - JSON Schema format with lowercase types (string, object, etc.) instead of - the OpenAPI-style responseSchema with uppercase types (STRING, OBJECT, etc.). - - Benefits of responseJsonSchema: - - Supports additionalProperties for stricter schema validation - - Uses standard JSON Schema format (no type conversion needed) - - Better compatibility with Pydantic's model_json_schema() - - Args: - model: The model name (e.g., "gemini-2.0-flash", "gemini-2.5-pro") - - Returns: - True if the model supports responseJsonSchema, False otherwise - """ - model_lower = model.lower() - - # Gemini 2.0+ and 2.5+ models support responseJsonSchema - # Pattern matches: gemini-2.0-*, gemini-2.5-*, gemini-3-*, etc. - gemini_2_plus_pattern = re.compile(r"gemini-([2-9]|[1-9]\d+)\.") - - return bool(gemini_2_plus_pattern.search(model_lower)) - - from typing import Literal, Optional all_gemini_url_modes = Literal[ @@ -514,44 +486,6 @@ def _build_vertex_schema(parameters: dict, add_property_ordering: bool = False): return parameters -def _build_json_schema(parameters: dict) -> dict: - """ - Build a JSON Schema for use with Gemini's responseJsonSchema parameter. - - Unlike _build_vertex_schema (used for responseSchema), this function: - - Does NOT convert types to uppercase (keeps standard JSON Schema format) - - Does NOT add propertyOrdering - - Does NOT filter fields (allows additionalProperties) - - Still unpacks $defs/$ref (Gemini doesn't support JSON Schema references) - - Parameters: - parameters: dict - the JSON schema to process - - Returns: - dict - the processed schema in standard JSON Schema format - """ - # Unpack $defs references (Gemini doesn't support $ref) - defs = parameters.pop("$defs", {}) - for name, value in defs.items(): - unpack_defs(value, defs) - unpack_defs(parameters, defs) - - # Convert anyOf with null to nullable - convert_anyof_null_to_nullable(parameters) - - # Handle empty strings in enum values - Gemini doesn't accept empty strings in enums - _fix_enum_empty_strings(parameters) - - # Remove enums for non-string typed fields (Gemini requires enum only on strings) - _fix_enum_types(parameters) - - # Handle empty items objects - process_items(parameters) - add_object_type(parameters) - - return parameters - - def _filter_anyof_fields(schema_dict: Dict[str, Any]) -> Dict[str, Any]: """ When anyof is present, only keep the anyof field and its contents - otherwise VertexAI will throw an error - https://github.com/BerriAI/litellm/issues/11164 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 692bd5f199..7a37812bcf 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 @@ -92,12 +92,7 @@ from litellm.utils import ( ) from ....utils import _remove_additional_properties, _remove_strict_from_schema -from ..common_utils import ( - VertexAIError, - _build_json_schema, - _build_vertex_schema, - supports_response_json_schema, -) +from ..common_utils import VertexAIError, _build_vertex_schema from ..vertex_llm_base import VertexBase from .transformation import ( _gemini_convert_messages_with_history, @@ -629,55 +624,30 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): ) return old_schema - def apply_response_schema_transformation( - self, value: dict, optional_params: dict, model: str - ): + def apply_response_schema_transformation(self, value: dict, optional_params: dict): new_value = deepcopy(value) - # remove 'strict' from json schema (not supported by Gemini) + # remove 'additionalProperties' from json schema + new_value = _remove_additional_properties(new_value) + # remove 'strict' from json schema new_value = _remove_strict_from_schema(new_value) - - # Automatically use responseJsonSchema for Gemini 2.0+ models - # responseJsonSchema uses standard JSON Schema format and supports additionalProperties - # For older models (Gemini 1.5), fall back to responseSchema (OpenAPI format) - use_json_schema = supports_response_json_schema(model) - - if not use_json_schema: - # For responseSchema, remove 'additionalProperties' (not supported) - new_value = _remove_additional_properties(new_value) - - # Handle response type - if new_value.get("type") == "json_object": + if new_value["type"] == "json_object": optional_params["response_mime_type"] = "application/json" - elif new_value.get("type") == "text": + elif new_value["type"] == "text": optional_params["response_mime_type"] = "text/plain" - - # Extract schema from response_format - schema = None if "response_schema" in new_value: optional_params["response_mime_type"] = "application/json" - schema = new_value["response_schema"] - elif new_value.get("type") == "json_schema": - if "json_schema" in new_value and "schema" in new_value["json_schema"]: + 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" - schema = new_value["json_schema"]["schema"] + optional_params["response_schema"] = new_value["json_schema"]["schema"] # type: ignore - if schema and isinstance(schema, dict): - if use_json_schema: - # Use responseJsonSchema (Gemini 2.0+ only, opt-in) - # - Standard JSON Schema format (lowercase types) - # - Supports additionalProperties - # - No propertyOrdering needed - optional_params["response_json_schema"] = _build_json_schema( - deepcopy(schema) - ) - else: - # Use responseSchema (default, backwards compatible) - # - OpenAPI-style format (uppercase types) - # - No additionalProperties support - # - Requires propertyOrdering - optional_params["response_schema"] = self._map_response_schema( - value=schema - ) + if "response_schema" in optional_params and isinstance( + optional_params["response_schema"], dict + ): + optional_params["response_schema"] = self._map_response_schema( + value=optional_params["response_schema"] + ) @staticmethod def _map_reasoning_effort_to_thinking_budget( @@ -977,7 +947,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): optional_params["max_output_tokens"] = value elif param == "response_format" and isinstance(value, dict): # type: ignore self.apply_response_schema_transformation( - value=value, optional_params=optional_params, model=model + value=value, optional_params=optional_params ) elif param == "frequency_penalty": if self._supports_penalty_parameters(model): diff --git a/litellm/types/llms/vertex_ai.py b/litellm/types/llms/vertex_ai.py index 0a4a2d0f14..74d6339887 100644 --- a/litellm/types/llms/vertex_ai.py +++ b/litellm/types/llms/vertex_ai.py @@ -207,7 +207,6 @@ class GenerationConfig(TypedDict, total=False): frequency_penalty: float response_mime_type: Literal["text/plain", "application/json"] response_schema: dict - response_json_schema: dict seed: int responseLogprobs: bool logprobs: int diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py index 3c77c5cd50..c0b80ae771 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py @@ -74,10 +74,6 @@ def test_get_model_name_from_gemini_spec_model(): def test_vertex_ai_response_schema_dict(): - """ - Test that older Gemini models (1.5) use responseSchema (OpenAPI format). - responseSchema requires propertyOrdering and doesn't support additionalProperties. - """ v = VertexGeminiConfig() non_default_params = { "messages": [{"role": "user", "content": "Hello, world!"}], @@ -113,7 +109,7 @@ def test_vertex_ai_response_schema_dict(): transformed_request = v.map_openai_params( non_default_params=non_default_params, optional_params={}, - model="gemini-1.5-flash", # Old model uses responseSchema (OpenAPI format) + model="gemini-2.0-flash-lite", drop_params=False, ) @@ -164,9 +160,6 @@ class Step(BaseModel): def test_vertex_ai_response_schema_defs(): - """ - Test that $defs are unpacked for older Gemini models using responseSchema. - """ v = VertexGeminiConfig() schema = cast(dict, v.get_json_schema_from_pydantic_object(MathReasoning)) @@ -180,7 +173,7 @@ def test_vertex_ai_response_schema_defs(): "response_format": schema, }, optional_params={}, - model="gemini-1.5-flash", # Old model uses responseSchema (OpenAPI format) + model="gemini-2.0-flash-lite", drop_params=False, ) @@ -210,93 +203,7 @@ def test_vertex_ai_response_schema_defs(): } -def test_vertex_ai_response_json_schema_for_gemini_2(): - """ - Test that Gemini 2.0+ models automatically use responseJsonSchema. - - responseJsonSchema uses standard JSON Schema format: - - lowercase types (string, object, etc.) - - no propertyOrdering required - - supports additionalProperties - """ - 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": "test_schema", - "schema": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"}, - }, - "required": ["name"], - "additionalProperties": False, - }, - }, - }, - }, - optional_params={}, - model="gemini-2.0-flash", # Gemini 2.0+ automatically uses responseJsonSchema - drop_params=False, - ) - - # Should use response_json_schema, not response_schema - assert "response_json_schema" in transformed_request - assert "response_schema" not in transformed_request - - # Types should be lowercase (standard JSON Schema format) - assert transformed_request["response_json_schema"]["type"] == "object" - assert transformed_request["response_json_schema"]["properties"]["name"]["type"] == "string" - assert transformed_request["response_json_schema"]["properties"]["age"]["type"] == "integer" - - # Should NOT have propertyOrdering (not needed for responseJsonSchema) - assert "propertyOrdering" not in transformed_request["response_json_schema"] - - # additionalProperties should be preserved (supported by responseJsonSchema) - assert transformed_request["response_json_schema"].get("additionalProperties") == False - - -def test_vertex_ai_response_schema_for_old_models(): - """ - Test that older models (Gemini 1.5) automatically use responseSchema. - """ - 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": "test_schema", - "schema": { - "type": "object", - "properties": { - "name": {"type": "string"}, - }, - }, - }, - }, - }, - optional_params={}, - model="gemini-1.5-flash", # Old model automatically uses responseSchema - drop_params=False, - ) - - # Should use response_schema for older models - assert "response_schema" in transformed_request - assert "response_json_schema" not in transformed_request - - def test_vertex_ai_retain_property_ordering(): - """ - Test that existing propertyOrdering is preserved for older models using responseSchema. - """ v = VertexGeminiConfig() transformed_request = v.map_openai_params( non_default_params={ @@ -317,7 +224,7 @@ def test_vertex_ai_retain_property_ordering(): }, }, optional_params={}, - model="gemini-1.5-flash", # Old model uses responseSchema which needs propertyOrdering + model="gemini-2.0-flash-lite", drop_params=False, )