mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
[Bug Fix] Gemini Tool Calling - fix gemini empty enum property (#14155)
* fix: _convert_schema_types * fix recursive detector * test_convert_schema_types_type_array_conversion * fix: DEFAULT_NUM_WORKERS_LITELLM_PROXY * add _fix_enum_empty_strings * test_tool_call_with_empty_enum_property * test_fix_enum_empty_strings * fix _fix_enum_empty_strings
This commit is contained in:
parent
5802dfb93d
commit
6e90f12e64
4 changed files with 139 additions and 1 deletions
|
|
@ -187,6 +187,25 @@ def _check_text_in_content(parts: List[PartType]) -> bool:
|
|||
return has_text_param
|
||||
|
||||
|
||||
def _fix_enum_empty_strings(schema, depth=0):
|
||||
"""Fix empty strings in enum values by replacing them with None. Gemini doesn't accept empty strings in enums."""
|
||||
if depth > DEFAULT_MAX_RECURSE_DEPTH:
|
||||
raise ValueError(f"Max depth of {DEFAULT_MAX_RECURSE_DEPTH} exceeded while processing schema.")
|
||||
|
||||
if "enum" in schema and isinstance(schema["enum"], list):
|
||||
schema["enum"] = [None if value == "" else value for value in schema["enum"]]
|
||||
|
||||
# Reuse existing recursion pattern from convert_anyof_null_to_nullable
|
||||
properties = schema.get("properties", None)
|
||||
if properties is not None:
|
||||
for _, value in properties.items():
|
||||
_fix_enum_empty_strings(value, depth=depth + 1)
|
||||
|
||||
items = schema.get("items", None)
|
||||
if items is not None:
|
||||
_fix_enum_empty_strings(items, depth=depth + 1)
|
||||
|
||||
|
||||
def _build_vertex_schema(parameters: dict, add_property_ordering: bool = False):
|
||||
"""
|
||||
This is a modified version of https://github.com/google-gemini/generative-ai-python/blob/8f77cc6ac99937cd3a81299ecf79608b91b06bbb/google/generativeai/types/content_types.py#L419
|
||||
|
|
@ -217,6 +236,9 @@ def _build_vertex_schema(parameters: dict, add_property_ordering: bool = False):
|
|||
|
||||
_convert_schema_types(parameters)
|
||||
|
||||
# Handle empty strings in enum values - Gemini doesn't accept empty strings in enums
|
||||
_fix_enum_empty_strings(parameters)
|
||||
|
||||
# Handle empty items objects
|
||||
process_items(parameters)
|
||||
add_object_type(parameters)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ IGNORE_FUNCTIONS = [
|
|||
"_extract_fields_recursive", # max depth set.
|
||||
"_remove_json_schema_refs", # max depth set.,
|
||||
"_convert_schema_types", # max depth set.,
|
||||
"_fix_enum_empty_strings", # max depth set.,
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -186,6 +186,62 @@ class BaseLLMChatTest(ABC):
|
|||
print(response)
|
||||
print(json.dumps(response, indent=4, default=str))
|
||||
|
||||
def test_tool_call_with_empty_enum_property(self):
|
||||
litellm._turn_on_debug()
|
||||
from litellm.utils import supports_function_calling
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
base_completion_call_args = self.get_base_completion_call_args()
|
||||
if not supports_function_calling(base_completion_call_args["model"], None):
|
||||
print("Model does not support function calling")
|
||||
pytest.skip("Model does not support function calling")
|
||||
base_completion_call_args = self.get_base_completion_call_args()
|
||||
response = self.completion_function(
|
||||
**base_completion_call_args,
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Search for the latest iPhone models and tell me which storage options are available."
|
||||
}
|
||||
],
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "litellm_product_search",
|
||||
"description": "Search for product information and specifications.\n\nSupports filtering by category, brand, price range, and availability.\nCan retrieve detailed product specifications, pricing, and stock information.\nSupports different search modes and result formatting options.\n",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"search_mode": {
|
||||
"default": "",
|
||||
"description": "The search strategy to use for finding products.",
|
||||
"enum": [
|
||||
"",
|
||||
"product_search",
|
||||
"product_search_with_filters",
|
||||
"product_search_with_sorting",
|
||||
"product_search_with_pagination",
|
||||
"product_search_with_aggregation",
|
||||
],
|
||||
"title": "Search Mode",
|
||||
"type": "string"
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"search_mode"
|
||||
],
|
||||
"title": "product_search_arguments",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
print(response)
|
||||
print(json.dumps(response, indent=4, default=str))
|
||||
|
||||
|
||||
|
||||
def test_streaming(self):
|
||||
"""Check if litellm handles streaming correctly"""
|
||||
|
|
|
|||
|
|
@ -741,4 +741,63 @@ def test_convert_schema_types_type_array_conversion():
|
|||
|
||||
# 4. Other properties preserved
|
||||
assert input_schema["properties"]["studio"]["description"] == "The studio ID or name"
|
||||
assert input_schema["required"] == ["studio"]
|
||||
assert input_schema["required"] == ["studio"]
|
||||
|
||||
|
||||
def test_fix_enum_empty_strings():
|
||||
"""
|
||||
Test _fix_enum_empty_strings function replaces empty strings with None in enum arrays.
|
||||
|
||||
This test verifies the fix for the issue where Gemini rejects tool definitions
|
||||
with empty strings in enum values, causing API failures.
|
||||
|
||||
Relevant issue: Gemini does not accept empty strings in enum values
|
||||
"""
|
||||
from litellm.llms.vertex_ai.common_utils import _fix_enum_empty_strings
|
||||
|
||||
# Input: Schema with empty string in enum (the problematic case)
|
||||
input_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_agent_type": {
|
||||
"enum": ["", "desktop", "mobile", "tablet"],
|
||||
"type": "string",
|
||||
"description": "Device type for user agent"
|
||||
}
|
||||
},
|
||||
"required": ["user_agent_type"]
|
||||
}
|
||||
|
||||
# Expected output: Empty strings replaced with None
|
||||
expected_output = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_agent_type": {
|
||||
"enum": [None, "desktop", "mobile", "tablet"],
|
||||
"type": "string",
|
||||
"description": "Device type for user agent"
|
||||
}
|
||||
},
|
||||
"required": ["user_agent_type"]
|
||||
}
|
||||
|
||||
# Apply the transformation
|
||||
_fix_enum_empty_strings(input_schema)
|
||||
|
||||
# Verify the transformation
|
||||
assert input_schema == expected_output
|
||||
|
||||
# Verify specific transformations:
|
||||
# 1. Empty string replaced with None
|
||||
enum_values = input_schema["properties"]["user_agent_type"]["enum"]
|
||||
assert "" not in enum_values
|
||||
assert None in enum_values
|
||||
|
||||
# 2. Other enum values preserved
|
||||
assert "desktop" in enum_values
|
||||
assert "mobile" in enum_values
|
||||
assert "tablet" in enum_values
|
||||
|
||||
# 3. Other properties preserved
|
||||
assert input_schema["properties"]["user_agent_type"]["type"] == "string"
|
||||
assert input_schema["properties"]["user_agent_type"]["description"] == "Device type for user agent"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue