mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Merge 89d0301db2 into 77765fd302
This commit is contained in:
commit
a2fff49c0f
3 changed files with 100 additions and 0 deletions
|
|
@ -597,6 +597,8 @@ def _build_vertex_schema(parameters: dict, add_property_ordering: bool = False):
|
|||
# refs recursively and correctly detects/skips circular references.
|
||||
unpack_defs(parameters, defs)
|
||||
|
||||
_normalize_tuple_items(parameters)
|
||||
|
||||
# 5. Nullable fields:
|
||||
# * https://github.com/pydantic/pydantic/issues/1270
|
||||
# * https://stackoverflow.com/a/58841311
|
||||
|
|
@ -770,6 +772,39 @@ def filter_schema_fields(schema_dict: dict[str, Any], valid_fields: set[str], pr
|
|||
return result
|
||||
|
||||
|
||||
def _normalize_tuple_items(schema: dict[str, Any], depth: int = 0) -> None:
|
||||
"""Rewrite tuple-form `items` (a list of sub-schemas) as `anyOf`.
|
||||
|
||||
JSON Schema allows it, Vertex's Schema type does not, and every walker below
|
||||
reads `items` as a single sub-schema, so a list there crashes them.
|
||||
"""
|
||||
if depth > DEFAULT_MAX_RECURSE_DEPTH:
|
||||
raise ValueError(f"Max depth of {DEFAULT_MAX_RECURSE_DEPTH} exceeded while processing schema.")
|
||||
|
||||
tuple_items: Final = schema.get("items", None)
|
||||
if isinstance(tuple_items, list):
|
||||
if tuple_items:
|
||||
schema["items"] = {"anyOf": tuple_items} # mutable-ok: the walkers below rewrite this subtree in place
|
||||
else:
|
||||
schema.pop("items")
|
||||
|
||||
properties: Final = schema.get("properties", None)
|
||||
if properties is not None:
|
||||
for value in properties.values():
|
||||
_normalize_tuple_items(value, depth=depth + 1)
|
||||
|
||||
items: Final = schema.get("items", None)
|
||||
if items is not None:
|
||||
_normalize_tuple_items(items, depth=depth + 1)
|
||||
|
||||
for key in ("anyOf", "oneOf", "allOf"):
|
||||
values = schema.get(key, None)
|
||||
if values is not None and isinstance(values, list):
|
||||
for value in values:
|
||||
if isinstance(value, dict):
|
||||
_normalize_tuple_items(value, depth=depth + 1)
|
||||
|
||||
|
||||
def convert_anyof_null_to_nullable(schema, depth=0):
|
||||
if depth > DEFAULT_MAX_RECURSE_DEPTH:
|
||||
raise ValueError(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ IGNORE_FUNCTIONS = [
|
|||
"clean_message",
|
||||
"unpack_defs",
|
||||
"convert_anyof_null_to_nullable", # has a set max depth
|
||||
"_normalize_tuple_items", # has a set max depth
|
||||
"add_object_type",
|
||||
"strip_field",
|
||||
"_transform_prompt",
|
||||
|
|
|
|||
|
|
@ -372,6 +372,70 @@ def test_build_vertex_schema_array_branch_missing_items_in_anyof():
|
|||
}, f"array branch must have items synthesized; got {branch}"
|
||||
|
||||
|
||||
def test_build_vertex_schema_tuple_form_items_become_anyof():
|
||||
"""
|
||||
Regression: tuple-form `items` (`"items": [{...}, {...}]`, valid JSON Schema)
|
||||
used to crash the transform with `'list' object has no attribute 'get'`,
|
||||
surfacing as litellm.APIConnectionError on every vertex_ai request carrying
|
||||
such a tool schema.
|
||||
"""
|
||||
from litellm.llms.vertex_ai.common_utils import _build_vertex_schema
|
||||
|
||||
parameters = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pair": {
|
||||
"type": "array",
|
||||
"items": [{"type": "integer"}, {"type": "string"}],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
result = _build_vertex_schema(parameters)
|
||||
|
||||
assert result["properties"]["pair"]["items"] == {
|
||||
"anyOf": [{"type": "integer"}, {"type": "string"}]
|
||||
}
|
||||
|
||||
|
||||
def test_build_vertex_schema_tuple_form_items_nested_in_anyof_branch():
|
||||
"""Tuple-form `items` on an anyOf branch is reachable only if the walk
|
||||
descends into anyOf, which is where the original crash was raised."""
|
||||
from litellm.llms.vertex_ai.common_utils import _build_vertex_schema
|
||||
|
||||
parameters = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pair": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": [{"type": "integer"}]},
|
||||
{"type": "null"},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
result = _build_vertex_schema(parameters)
|
||||
|
||||
array_branch = result["properties"]["pair"]["anyOf"][0]
|
||||
assert array_branch["items"] == {"anyOf": [{"type": "integer"}]}
|
||||
|
||||
|
||||
def test_build_vertex_schema_empty_tuple_form_items():
|
||||
"""An empty `items: []` has no branches to fold into anyOf, so it takes the
|
||||
same path as `items: {}` rather than raising on a zero-length anyOf."""
|
||||
from litellm.llms.vertex_ai.common_utils import _build_vertex_schema
|
||||
|
||||
parameters = {
|
||||
"type": "object",
|
||||
"properties": {"pair": {"type": "array", "items": []}},
|
||||
}
|
||||
|
||||
result = _build_vertex_schema(parameters)
|
||||
|
||||
assert result["properties"]["pair"]["items"] == {"type": "object"}
|
||||
|
||||
|
||||
def test_vertex_ai_complex_response_schema():
|
||||
import json
|
||||
from copy import deepcopy
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue