Skip to content

Commit 6932ba0

Browse files
authored
Fix multiple issues with converting type hints to JSON Schema
The main issue: newish syntax for unions with `|` supported by Python 3.10 are of [type](https://docs.python.org/3.10/library/types.html#types.UnionType) `types.UnionType`, and didn't match `type(Union)`. There's a very recently [completed issue on CPython](python/cpython#105499) that will unify both (apparently for 3.14). See also this [MyPy warning](https://mypy.readthedocs.io/en/stable/runtime_troubles.html#future-annotations-import-pep-563) about evaluating type hints. Other issues: - Only unions of two values were properly handled for optional values; - Unions with more than two values and an optional one would only parse one of the values, dropping the others; - Some code lines were never true (like `isinstance(arg_json_schema["type"], list)`); The code is now simplified while handling new type annotation syntax and null/optional values.
1 parent b7f1f49 commit 6932ba0

File tree

1 file changed

+16
-24
lines changed

1 file changed

+16
-24
lines changed

libs/agno/agno/utils/json_schema.py

+16-24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
from agno.utils.log import logger
44

55

6+
def is_origin_union_type(origin: Any) -> bool:
7+
import sys
8+
9+
if sys.version_info.minor >= 10:
10+
from types import UnionType
11+
12+
return origin in [Union, UnionType]
13+
14+
return origin is Union
15+
616
def get_json_type_for_py_type(arg: str) -> str:
717
"""
818
Get the JSON schema type for a given type.
@@ -43,16 +53,14 @@ def get_json_schema_for_arg(t: Any) -> Optional[Dict[str, Any]]:
4353
key_schema = get_json_schema_for_arg(type_args[0]) if type_args else {"type": "string"}
4454
value_schema = get_json_schema_for_arg(type_args[1]) if len(type_args) > 1 else {"type": "string"}
4555
return {"type": "object", "propertyNames": key_schema, "additionalProperties": value_schema}
46-
elif type_origin is Union:
56+
elif is_origin_union_type(type_origin):
4757
types = []
4858
for arg in type_args:
49-
if arg is not type(None):
50-
try:
51-
schema = get_json_schema_for_arg(arg)
52-
if schema:
53-
types.append(schema)
54-
except Exception:
55-
continue
59+
try:
60+
schema = get_json_schema_for_arg(arg)
61+
types.append(schema)
62+
except Exception:
63+
continue
5664
return {"anyOf": types} if types else None
5765

5866
return {"type": get_json_type_for_py_type(t.__name__)}
@@ -74,29 +82,13 @@ def get_json_schema(
7482
continue
7583

7684
try:
77-
# Check if type is Optional (Union with NoneType)
78-
type_origin = get_origin(v)
79-
type_args = get_args(v)
80-
is_optional = type_origin is Union and len(type_args) == 2 and any(arg is type(None) for arg in type_args)
81-
82-
# Get the actual type if it's Optional
83-
if is_optional:
84-
v = next(arg for arg in type_args if arg is not type(None))
85-
8685
# Handle cases with no type hint
8786
if v:
8887
arg_json_schema = get_json_schema_for_arg(v)
8988
else:
9089
arg_json_schema = {}
9190

9291
if arg_json_schema is not None:
93-
if is_optional:
94-
# Handle null type for optional fields
95-
if isinstance(arg_json_schema["type"], list):
96-
arg_json_schema["type"].append("null")
97-
else:
98-
arg_json_schema["type"] = [arg_json_schema["type"], "null"]
99-
10092
# Add description
10193
if param_descriptions and k in param_descriptions and param_descriptions[k]:
10294
arg_json_schema["description"] = param_descriptions[k]

0 commit comments

Comments
 (0)