fix: Preserved nullable object fields by carrying schema properties
This commit is contained in:
parent
dca42047b9
commit
99ef233a69
@ -1,4 +1,5 @@
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union, get_type_hints
|
||||
|
||||
@ -617,7 +618,7 @@ def convert_anyof_null_to_nullable(schema, depth=0):
|
||||
if anyof is not None:
|
||||
contains_null = False
|
||||
for atype in anyof:
|
||||
if atype == {"type": "null"}:
|
||||
if isinstance(atype, dict) and atype.get("type") == "null":
|
||||
# remove null type
|
||||
anyof.remove(atype)
|
||||
contains_null = True
|
||||
@ -735,7 +736,20 @@ def _convert_schema_types(schema, depth=0):
|
||||
type_val = schema["type"]
|
||||
if isinstance(type_val, list) and len(type_val) > 1:
|
||||
# Convert ["string", "number"] -> {"anyOf": [{"type": "STRING"}, {"type": "NUMBER"}]}
|
||||
schema["anyOf"] = [{"type": t} for t in type_val if isinstance(t, str)]
|
||||
# Preserve other schema fields by copying them into each non-null anyOf item.
|
||||
base_schema = {k: v for k, v in schema.items() if k not in {"type", "anyOf"}}
|
||||
any_of: List[Dict[str, Any]] = []
|
||||
for t in type_val:
|
||||
if not isinstance(t, str):
|
||||
continue
|
||||
if t == "null":
|
||||
# Keep null entry minimal so we can strip it later.
|
||||
any_of.append({"type": "null"})
|
||||
continue
|
||||
item_schema = deepcopy(base_schema)
|
||||
item_schema["type"] = t
|
||||
any_of.append(item_schema)
|
||||
schema["anyOf"] = any_of
|
||||
schema.pop("type")
|
||||
elif isinstance(type_val, list) and len(type_val) == 1:
|
||||
schema["type"] = type_val[0]
|
||||
|
||||
@ -3598,6 +3598,60 @@ def test_vertex_schema_test():
|
||||
print(response)
|
||||
|
||||
|
||||
def test_gemini_nullable_object_tool_schema_httpx():
|
||||
"""
|
||||
Ensure nullable object tool params preserve nested properties in Vertex schema conversion.
|
||||
"""
|
||||
load_vertex_ai_credentials()
|
||||
litellm._turn_on_debug()
|
||||
|
||||
|
||||
tools = [{
|
||||
"type": "function",
|
||||
"strict": True,
|
||||
"function": {
|
||||
"name": "create_support_ticket",
|
||||
"description": "Create a paid user support ticket",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["ticket_id", "customer_context"],
|
||||
"properties": {
|
||||
"ticket_id": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for the support ticket"
|
||||
},
|
||||
"customer_context": {
|
||||
"type": ["object", "null"],
|
||||
"description": "Context about the paid customer, if available",
|
||||
"additionalProperties": False,
|
||||
"required": ["user_id", "plan"],
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"description": "Internal user identifier"
|
||||
},
|
||||
"plan": {
|
||||
"type": "string",
|
||||
"description": "Subscription plan name (e.g. pro, enterprise)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
response = litellm.completion(
|
||||
model="vertex_ai/gemini-2.5-flash",
|
||||
messages=[{"role": "user", "content": "call the tool"}],
|
||||
tools=tools,
|
||||
tool_choice="required",
|
||||
)
|
||||
|
||||
print(response)
|
||||
|
||||
|
||||
def test_vertex_ai_response_id():
|
||||
"""Test that litellm preserves the response ID from Vertex AI's API for non-streaming responses"""
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
|
||||
Loading…
Reference in New Issue
Block a user