From 814ba8fd5d38c41e3f07222df17ad09dc7d9d7a5 Mon Sep 17 00:00:00 2001 From: Fabio Natanael Kepler Date: Wed, 12 Mar 2025 18:37:22 +0000 Subject: [PATCH 1/2] 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](https://github.com/python/cpython/issues/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. --- libs/agno/agno/utils/json_schema.py | 40 ++++++++++++----------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/libs/agno/agno/utils/json_schema.py b/libs/agno/agno/utils/json_schema.py index 0e20ac27cf..661fc3095c 100644 --- a/libs/agno/agno/utils/json_schema.py +++ b/libs/agno/agno/utils/json_schema.py @@ -3,6 +3,16 @@ from agno.utils.log import logger +def is_origin_union_type(origin: Any) -> bool: + import sys + + if sys.version_info.minor >= 10: + from types import UnionType + + return origin in [Union, UnionType] + + return origin is Union + def get_json_type_for_py_type(arg: str) -> str: """ 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]]: key_schema = get_json_schema_for_arg(type_args[0]) if type_args else {"type": "string"} value_schema = get_json_schema_for_arg(type_args[1]) if len(type_args) > 1 else {"type": "string"} return {"type": "object", "propertyNames": key_schema, "additionalProperties": value_schema} - elif type_origin is Union: + elif is_origin_union_type(type_origin): types = [] for arg in type_args: - if arg is not type(None): - try: - schema = get_json_schema_for_arg(arg) - if schema: - types.append(schema) - except Exception: - continue + try: + schema = get_json_schema_for_arg(arg) + types.append(schema) + except Exception: + continue return {"anyOf": types} if types else None return {"type": get_json_type_for_py_type(t.__name__)} @@ -74,15 +82,6 @@ def get_json_schema( continue try: - # Check if type is Optional (Union with NoneType) - type_origin = get_origin(v) - type_args = get_args(v) - is_optional = type_origin is Union and len(type_args) == 2 and any(arg is type(None) for arg in type_args) - - # Get the actual type if it's Optional - if is_optional: - v = next(arg for arg in type_args if arg is not type(None)) - # Handle cases with no type hint if v: arg_json_schema = get_json_schema_for_arg(v) @@ -90,13 +89,6 @@ def get_json_schema( arg_json_schema = {} if arg_json_schema is not None: - if is_optional: - # Handle null type for optional fields - if isinstance(arg_json_schema["type"], list): - arg_json_schema["type"].append("null") - else: - arg_json_schema["type"] = [arg_json_schema["type"], "null"] - # Add description if param_descriptions and k in param_descriptions and param_descriptions[k]: arg_json_schema["description"] = param_descriptions[k] From e93d0242c66bc501b48d1c8e9ed8e886fbae49ca Mon Sep 17 00:00:00 2001 From: Fabio Kepler Date: Thu, 13 Mar 2025 09:49:14 +0000 Subject: [PATCH 2/2] Fix formatting --- libs/agno/agno/utils/json_schema.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/agno/agno/utils/json_schema.py b/libs/agno/agno/utils/json_schema.py index 661fc3095c..fcb2301b16 100644 --- a/libs/agno/agno/utils/json_schema.py +++ b/libs/agno/agno/utils/json_schema.py @@ -10,9 +10,10 @@ def is_origin_union_type(origin: Any) -> bool: from types import UnionType return origin in [Union, UnionType] - + return origin is Union + def get_json_type_for_py_type(arg: str) -> str: """ Get the JSON schema type for a given type.