diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 4320f756454..89a708077f3 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -45,6 +45,7 @@ infer_content_type_from_url_and_content, is_non_content_values_set, parse_tool_call_arguments, + unpack_defs, ) from .image_handling import convert_url_to_base64 @@ -1462,6 +1463,56 @@ def convert_to_gemini_tool_call_invoke( ) +def _clean_refs_for_gemini(obj: Any) -> None: + """ + Recursively clean $defs, $ref, and definitions from a dict for Gemini compatibility. + + Gemini rejects: + - $defs sections (even after $ref has been inlined) + - Any remaining $ref (circular refs, external URLs) + + This function: + 1. Removes all $defs/definitions keys + 2. Replaces any remaining $ref with a placeholder object + """ + if isinstance(obj, dict): + # Remove $defs and definitions at this level + obj.pop("$defs", None) + obj.pop("definitions", None) + + # Check for and handle remaining $ref (circular or external) + if "$ref" in obj: + ref_value = obj.pop("$ref") + # Replace with a generic object type as placeholder + obj["type"] = "object" + obj["description"] = f"(schema reference: {ref_value})" + + # Recurse into values + for value in obj.values(): + _clean_refs_for_gemini(value) + elif isinstance(obj, list): + for item in obj: + _clean_refs_for_gemini(item) + + +def _prepare_response_for_gemini(response_data: dict) -> dict: + """ + Prepare a tool response dict for Gemini by inlining $ref and removing $defs. + + Gemini rejects JSON schemas with $defs/$ref in function_response content. + This function applies unpack_defs to inline references, then cleans up + any remaining $defs sections and unresolved $refs (circular or external). + + Returns a new dict (does not mutate the input). + """ + import copy + + result = copy.deepcopy(response_data) + unpack_defs(result, {}) + _clean_refs_for_gemini(result) + return result + + def convert_to_gemini_tool_call_result( message: Union[ChatCompletionToolMessage, ChatCompletionFunctionMessage], last_message_with_tool_calls: Optional[dict], @@ -1570,6 +1621,11 @@ def convert_to_gemini_tool_call_result( # Not valid JSON, wrap in content field response_data = {"content": content_str} + # Gemini rejects JSON schemas with $defs/$ref in function_response content. + # Inline $refs and clean up for Gemini compatibility. + if isinstance(response_data, dict): + response_data = _prepare_response_for_gemini(response_data) + # We can't determine from openai message format whether it's a successful or # error call result so default to the successful result template _function_response = VertexFunctionResponse(