fix(gemini): dereference $defs/$ref in tool response content (#19062)
Gemini API rejects JSON schemas with $defs/$ref references anywhere in the conversation, including in function_response content. This causes errors when tools return JSON containing schemas (e.g., toolbelt_inspect_tool returning tool definitions). The fix: 1. Apply unpack_defs() to all JSON tool responses before sending to Gemini 2. Recursively remove $defs sections after inlining references 3. Replace any remaining $ref (circular refs, external URLs) with placeholders Edge cases handled: - Circular $ref (self-referential types like TreeNode.left -> TreeNode) - External $ref (URL-based like https://...) - Deeply nested $ref in anyOf/oneOf/allOf This ensures function_response content is clean before being sent to the Gemini API. Related issues: - https://github.com/google-gemini/gemini-cli/issues/13326 - FastMCP #1372, #1426
This commit is contained in:
parent
cd2d381dd4
commit
84dad952a2
@ -45,6 +45,7 @@ from .common_utils import (
|
||||
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(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user