mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(lint): annotate type-array list construction as mutable-ok
The repo's type_discipline_gate (LIT002) flags the new list comprehension as a mutable-collection construction. A JSON Schema type array must round-trip as a JSON list, so a tuple would be wrong; annotate it with the sanctioned # mutable-ok reason instead. Verified with scripts/check_type_discipline.py (delta 0 vs base) and ruff format.
This commit is contained in:
parent
affd76499e
commit
3db0c3215d
1 changed files with 127 additions and 125 deletions
|
|
@ -1,125 +1,127 @@
|
|||
import json
|
||||
from typing import Any
|
||||
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
|
||||
|
||||
|
||||
def normalize_json_schema_types(
|
||||
schema: dict[str, Any] | list[Any] | Any,
|
||||
depth: int = 0,
|
||||
max_depth: int = DEFAULT_MAX_RECURSE_DEPTH,
|
||||
) -> dict[str, Any] | list[Any] | Any:
|
||||
"""
|
||||
Normalize JSON schema types from uppercase to lowercase format.
|
||||
|
||||
Some providers (like certain Google services) use uppercase types like 'BOOLEAN', 'STRING', 'ARRAY', 'OBJECT'
|
||||
but standard JSON Schema requires lowercase: 'boolean', 'string', 'array', 'object'
|
||||
|
||||
This function recursively normalizes all type fields in a schema to lowercase.
|
||||
|
||||
Args:
|
||||
schema: The schema to normalize (dict, list, or other)
|
||||
depth: Current recursion depth
|
||||
max_depth: Maximum recursion depth to prevent infinite loops
|
||||
|
||||
Returns:
|
||||
The normalized schema with lowercase types
|
||||
"""
|
||||
# Prevent infinite recursion
|
||||
if depth >= max_depth:
|
||||
return schema
|
||||
|
||||
if not isinstance(schema, (dict, list)):
|
||||
return schema
|
||||
|
||||
# Type mapping from uppercase to lowercase
|
||||
type_mapping = {
|
||||
"BOOLEAN": "boolean",
|
||||
"STRING": "string",
|
||||
"ARRAY": "array",
|
||||
"OBJECT": "object",
|
||||
"NUMBER": "number",
|
||||
"INTEGER": "integer",
|
||||
"NULL": "null",
|
||||
}
|
||||
|
||||
if isinstance(schema, list):
|
||||
return [normalize_json_schema_types(item, depth + 1, max_depth) for item in schema]
|
||||
|
||||
if isinstance(schema, dict):
|
||||
normalized_schema: dict[str, Any] = {}
|
||||
|
||||
for key, value in schema.items():
|
||||
if key == "type" and isinstance(value, str) and value in type_mapping:
|
||||
normalized_schema[key] = type_mapping[value]
|
||||
elif key == "type" and isinstance(value, list):
|
||||
# JSON Schema also allows a list of types, which is the usual way
|
||||
# to mark a field nullable (e.g. ["STRING", "NULL"]). Without this
|
||||
# branch those entries fall through to the generic list recursion,
|
||||
# which leaves the bare strings uppercase.
|
||||
normalized_schema[key] = [type_mapping.get(entry, entry) for entry in value]
|
||||
elif key == "properties" and isinstance(value, dict):
|
||||
# Recursively normalize properties
|
||||
normalized_schema[key] = {
|
||||
prop_key: normalize_json_schema_types(prop_value, depth + 1, max_depth)
|
||||
for prop_key, prop_value in value.items()
|
||||
}
|
||||
elif key == "items" and isinstance(value, (dict, list)):
|
||||
# Recursively normalize array items
|
||||
normalized_schema[key] = normalize_json_schema_types(value, depth + 1, max_depth)
|
||||
elif isinstance(value, (dict, list)):
|
||||
# Recursively normalize any nested dict or list
|
||||
normalized_schema[key] = normalize_json_schema_types(value, depth + 1, max_depth)
|
||||
else:
|
||||
normalized_schema[key] = value
|
||||
|
||||
return normalized_schema
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def normalize_tool_schema(tool: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Normalize a tool's parameter schema to use standard JSON Schema lowercase types.
|
||||
|
||||
Args:
|
||||
tool: The tool definition containing function parameters
|
||||
|
||||
Returns:
|
||||
The tool with normalized schema types
|
||||
"""
|
||||
if not isinstance(tool, dict):
|
||||
return tool
|
||||
|
||||
normalized_tool = tool.copy()
|
||||
|
||||
# Normalize function parameters if present
|
||||
if "function" in tool and isinstance(tool["function"], dict):
|
||||
normalized_tool["function"] = tool["function"].copy()
|
||||
if "parameters" in tool["function"]:
|
||||
normalized_tool["function"]["parameters"] = normalize_json_schema_types(tool["function"]["parameters"])
|
||||
|
||||
return normalized_tool
|
||||
|
||||
|
||||
def validate_schema(schema: dict, response: str):
|
||||
"""
|
||||
Validate if the returned json response follows the schema.
|
||||
|
||||
Params:
|
||||
- schema - dict: JSON schema
|
||||
- response - str: Received json response as string.
|
||||
"""
|
||||
from jsonschema import ValidationError, validate
|
||||
|
||||
from litellm import JSONSchemaValidationError
|
||||
|
||||
try:
|
||||
response_dict = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
raise JSONSchemaValidationError(model="", llm_provider="", raw_response=response, schema=json.dumps(schema))
|
||||
|
||||
try:
|
||||
validate(response_dict, schema=schema)
|
||||
except ValidationError:
|
||||
raise JSONSchemaValidationError(model="", llm_provider="", raw_response=response, schema=json.dumps(schema))
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
|
||||
|
||||
|
||||
def normalize_json_schema_types(
|
||||
schema: dict[str, Any] | list[Any] | Any,
|
||||
depth: int = 0,
|
||||
max_depth: int = DEFAULT_MAX_RECURSE_DEPTH,
|
||||
) -> dict[str, Any] | list[Any] | Any:
|
||||
"""
|
||||
Normalize JSON schema types from uppercase to lowercase format.
|
||||
|
||||
Some providers (like certain Google services) use uppercase types like 'BOOLEAN', 'STRING', 'ARRAY', 'OBJECT'
|
||||
but standard JSON Schema requires lowercase: 'boolean', 'string', 'array', 'object'
|
||||
|
||||
This function recursively normalizes all type fields in a schema to lowercase.
|
||||
|
||||
Args:
|
||||
schema: The schema to normalize (dict, list, or other)
|
||||
depth: Current recursion depth
|
||||
max_depth: Maximum recursion depth to prevent infinite loops
|
||||
|
||||
Returns:
|
||||
The normalized schema with lowercase types
|
||||
"""
|
||||
# Prevent infinite recursion
|
||||
if depth >= max_depth:
|
||||
return schema
|
||||
|
||||
if not isinstance(schema, (dict, list)):
|
||||
return schema
|
||||
|
||||
# Type mapping from uppercase to lowercase
|
||||
type_mapping = {
|
||||
"BOOLEAN": "boolean",
|
||||
"STRING": "string",
|
||||
"ARRAY": "array",
|
||||
"OBJECT": "object",
|
||||
"NUMBER": "number",
|
||||
"INTEGER": "integer",
|
||||
"NULL": "null",
|
||||
}
|
||||
|
||||
if isinstance(schema, list):
|
||||
return [normalize_json_schema_types(item, depth + 1, max_depth) for item in schema]
|
||||
|
||||
if isinstance(schema, dict):
|
||||
normalized_schema: dict[str, Any] = {}
|
||||
|
||||
for key, value in schema.items():
|
||||
if key == "type" and isinstance(value, str) and value in type_mapping:
|
||||
normalized_schema[key] = type_mapping[value]
|
||||
elif key == "type" and isinstance(value, list):
|
||||
# JSON Schema also allows a list of types, which is the usual way
|
||||
# to mark a field nullable (e.g. ["STRING", "NULL"]). Without this
|
||||
# branch those entries fall through to the generic list recursion,
|
||||
# which leaves the bare strings uppercase.
|
||||
normalized_schema[key] = [ # mutable-ok: a JSON Schema type array must round-trip as a JSON list
|
||||
type_mapping.get(entry, entry) for entry in value
|
||||
]
|
||||
elif key == "properties" and isinstance(value, dict):
|
||||
# Recursively normalize properties
|
||||
normalized_schema[key] = {
|
||||
prop_key: normalize_json_schema_types(prop_value, depth + 1, max_depth)
|
||||
for prop_key, prop_value in value.items()
|
||||
}
|
||||
elif key == "items" and isinstance(value, (dict, list)):
|
||||
# Recursively normalize array items
|
||||
normalized_schema[key] = normalize_json_schema_types(value, depth + 1, max_depth)
|
||||
elif isinstance(value, (dict, list)):
|
||||
# Recursively normalize any nested dict or list
|
||||
normalized_schema[key] = normalize_json_schema_types(value, depth + 1, max_depth)
|
||||
else:
|
||||
normalized_schema[key] = value
|
||||
|
||||
return normalized_schema
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def normalize_tool_schema(tool: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Normalize a tool's parameter schema to use standard JSON Schema lowercase types.
|
||||
|
||||
Args:
|
||||
tool: The tool definition containing function parameters
|
||||
|
||||
Returns:
|
||||
The tool with normalized schema types
|
||||
"""
|
||||
if not isinstance(tool, dict):
|
||||
return tool
|
||||
|
||||
normalized_tool = tool.copy()
|
||||
|
||||
# Normalize function parameters if present
|
||||
if "function" in tool and isinstance(tool["function"], dict):
|
||||
normalized_tool["function"] = tool["function"].copy()
|
||||
if "parameters" in tool["function"]:
|
||||
normalized_tool["function"]["parameters"] = normalize_json_schema_types(tool["function"]["parameters"])
|
||||
|
||||
return normalized_tool
|
||||
|
||||
|
||||
def validate_schema(schema: dict, response: str):
|
||||
"""
|
||||
Validate if the returned json response follows the schema.
|
||||
|
||||
Params:
|
||||
- schema - dict: JSON schema
|
||||
- response - str: Received json response as string.
|
||||
"""
|
||||
from jsonschema import ValidationError, validate
|
||||
|
||||
from litellm import JSONSchemaValidationError
|
||||
|
||||
try:
|
||||
response_dict = json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
raise JSONSchemaValidationError(model="", llm_provider="", raw_response=response, schema=json.dumps(schema))
|
||||
|
||||
try:
|
||||
validate(response_dict, schema=schema)
|
||||
except ValidationError:
|
||||
raise JSONSchemaValidationError(model="", llm_provider="", raw_response=response, schema=json.dumps(schema))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue