11from typing import Any , Dict , Optional , Union , get_args , get_origin
22
3+ from pydantic import BaseModel
4+
35from agno .utils .log import logger
46
57
@@ -38,13 +40,86 @@ def get_json_type_for_py_type(arg: str) -> str:
3840 return "object"
3941
4042
41- def get_json_schema_for_arg (t : Any ) -> Optional [Dict [str , Any ]]:
43+ def inline_pydantic_schema (schema : Dict [str , Any ]) -> Dict [str , Any ]:
44+ """
45+ Recursively inline Pydantic model schemas by replacing $ref with actual schema.
46+ """
47+ if not isinstance (schema , dict ):
48+ return schema
49+
50+ def resolve_ref (ref : str , defs : Dict [str , Any ]) -> Dict [str , Any ]:
51+ """Resolve a $ref to its actual schema."""
52+ if not ref .startswith ("#/$defs/" ):
53+ return {"type" : "object" } # Fallback for external refs
54+
55+ def_name = ref .split ("/" )[- 1 ]
56+ if def_name in defs :
57+ return defs [def_name ]
58+ return {"type" : "object" } # Fallback if definition not found
59+
60+ def process_schema (s : Dict [str , Any ], defs : Dict [str , Any ]) -> Dict [str , Any ]:
61+ """Process a schema dictionary, resolving all references."""
62+ if not isinstance (s , dict ):
63+ return s
64+
65+ # Handle $ref
66+ if "$ref" in s :
67+ return resolve_ref (s ["$ref" ], defs )
68+
69+ # Create a new dict to avoid modifying the input
70+ result = s .copy ()
71+
72+ # Handle arrays
73+ if "items" in result :
74+ result ["items" ] = process_schema (result ["items" ], defs )
75+
76+ # Handle object properties
77+ if "properties" in result :
78+ for prop_name , prop_schema in result ["properties" ].items ():
79+ result ["properties" ][prop_name ] = process_schema (prop_schema , defs )
80+
81+ # Handle anyOf (for Union types)
82+ if "anyOf" in result :
83+ result ["anyOf" ] = [process_schema (sub_schema , defs ) for sub_schema in result ["anyOf" ]]
84+
85+ # Handle allOf (for inheritance)
86+ if "allOf" in result :
87+ result ["allOf" ] = [process_schema (sub_schema , defs ) for sub_schema in result ["allOf" ]]
88+
89+ # Handle additionalProperties
90+ if "additionalProperties" in result :
91+ result ["additionalProperties" ] = process_schema (result ["additionalProperties" ], defs )
92+
93+ # Handle propertyNames
94+ if "propertyNames" in result :
95+ result ["propertyNames" ] = process_schema (result ["propertyNames" ], defs )
96+
97+ return result
98+
99+ # Store definitions for later use
100+ definitions = schema .pop ("$defs" , {})
101+
102+ # First, resolve any nested references in definitions
103+ resolved_definitions = {}
104+ for def_name , def_schema in definitions .items ():
105+ resolved_definitions [def_name ] = process_schema (def_schema , definitions )
106+
107+ # Process the main schema with resolved definitions
108+ result = process_schema (schema , resolved_definitions )
109+
110+ # Remove any remaining definitions
111+ if "$defs" in result :
112+ del result ["$defs" ]
113+
114+ return result
115+
116+
117+ def get_json_schema_for_arg (type_hint : Any ) -> Optional [Dict [str , Any ]]:
42118 # log_info(f"Getting JSON schema for arg: {t}")
43- type_args = get_args (t )
119+ type_args = get_args (type_hint )
44120 # log_info(f"Type args: {type_args}")
45- type_origin = get_origin (t )
121+ type_origin = get_origin (type_hint )
46122 # log_info(f"Type origin: {type_origin}")
47-
48123 if type_origin is not None :
49124 if type_origin in (list , tuple , set , frozenset ):
50125 json_schema_for_items = get_json_schema_for_arg (type_args [0 ]) if type_args else {"type" : "string" }
@@ -65,7 +140,39 @@ def get_json_schema_for_arg(t: Any) -> Optional[Dict[str, Any]]:
65140 continue
66141 return {"anyOf" : types } if types else None
67142
68- json_schema : Dict [str , Any ] = {"type" : get_json_type_for_py_type (t .__name__ )}
143+ elif issubclass (type_hint , BaseModel ):
144+ # Get the schema and inline it
145+ schema = type_hint .model_json_schema ()
146+ return inline_pydantic_schema (schema )
147+ elif hasattr (type_hint , "__dataclass_fields__" ):
148+ # Convert dataclass to JSON schema
149+ properties = {}
150+ required = []
151+
152+ for field_name , field in type_hint .__dataclass_fields__ .items ():
153+ field_type = field .type
154+ field_schema = get_json_schema_for_arg (field_type )
155+
156+ if "anyOf" in field_schema and any (schema ["type" ] == "null" for schema in field_schema ["anyOf" ]):
157+ field_schema ["type" ] = next (schema ["type" ] for schema in field_schema ["anyOf" ] if schema ["type" ] != "null" )
158+ field_schema .pop ("anyOf" )
159+ else :
160+ required .append (field_name )
161+
162+ if field_schema :
163+ properties [field_name ] = field_schema
164+
165+ arg_json_schema = {
166+ "type" : "object" ,
167+ "properties" : properties ,
168+ "additionalProperties" : False
169+ }
170+
171+ if required :
172+ arg_json_schema ["required" ] = required
173+ return arg_json_schema
174+
175+ json_schema : Dict [str , Any ] = {"type" : get_json_type_for_py_type (type_hint .__name__ )}
69176 if json_schema ["type" ] == "object" :
70177 json_schema ["properties" ] = {}
71178 json_schema ["additionalProperties" ] = False
@@ -83,38 +190,37 @@ def get_json_schema(
83190 json_schema ["additionalProperties" ] = False
84191
85192 # We only include the fields in the type_hints dict
86- for k , v in type_hints .items ():
193+ for parameter_name , type_hint in type_hints .items ():
87194 # log_info(f"Parsing arg: {k} | {v}")
88- if k == "return" :
195+ if parameter_name == "return" :
89196 continue
90197
91198 try :
92199 # Check if type is Optional (Union with NoneType)
93- type_origin = get_origin (v )
94- type_args = get_args (v )
200+ type_origin = get_origin (type_hint )
201+ type_args = get_args (type_hint )
95202 is_optional = type_origin is Union and len (type_args ) == 2 and any (arg is type (None ) for arg in type_args )
96203
97204 # Get the actual type if it's Optional
98205 if is_optional :
99- v = next (arg for arg in type_args if arg is not type (None ))
206+ type_hint = next (arg for arg in type_args if arg is not type (None ))
100207
101- # Handle cases with no type hint
102- if v :
103- arg_json_schema = get_json_schema_for_arg (v )
208+ if type_hint :
209+ arg_json_schema = get_json_schema_for_arg (type_hint )
104210 else :
105211 arg_json_schema = {}
106212
107213 if arg_json_schema is not None :
108214 # Add description
109- if param_descriptions and k in param_descriptions and param_descriptions [k ]:
110- arg_json_schema ["description" ] = param_descriptions [k ]
215+ if param_descriptions and parameter_name in param_descriptions and param_descriptions [parameter_name ]:
216+ arg_json_schema ["description" ] = param_descriptions [parameter_name ]
111217
112- json_schema ["properties" ][k ] = arg_json_schema
218+ json_schema ["properties" ][parameter_name ] = arg_json_schema
113219
114220 else :
115- logger .warning (f"Could not parse argument { k } of type { v } " )
221+ logger .warning (f"Could not parse argument { parameter_name } of type { type_hint } " )
116222 except Exception as e :
117- logger .error (f"Error processing argument { k } : { str (e )} " )
223+ logger .error (f"Error processing argument { parameter_name } : { str (e )} " )
118224 continue
119225
120226 return json_schema
0 commit comments