11import json
22import re
33from typing import List , Any , Dict , Union
4+ from dataclasses import is_dataclass , asdict
5+ from datetime import date , datetime
46
57from langchain_core .messages import BaseMessage , HumanMessage , AIMessage , SystemMessage , ToolMessage
68from ag_ui .core import (
@@ -177,3 +179,60 @@ def resolve_message_content(content: Any) -> str | None:
177179
178180def camel_to_snake (name ):
179181 return re .sub (r'(?<!^)(?=[A-Z])' , '_' , name ).lower ()
182+
183+ def json_safe_stringify (o ):
184+ if is_dataclass (o ): # dataclasses like Flight(...)
185+ return asdict (o )
186+ if hasattr (o , "model_dump" ): # pydantic v2
187+ return o .model_dump ()
188+ if hasattr (o , "dict" ): # pydantic v1
189+ return o .dict ()
190+ if hasattr (o , "__dict__" ): # plain objects
191+ return vars (o )
192+ if isinstance (o , (datetime , date )):
193+ return o .isoformat ()
194+ return str (o ) # last resort
195+
196+ def make_json_safe (value : Any ) -> Any :
197+ """
198+ Recursively convert a value into a JSON-serializable structure.
199+
200+ - Handles Pydantic models via `model_dump`.
201+ - Handles LangChain messages via `to_dict`.
202+ - Recursively walks dicts, lists, and tuples.
203+ - For arbitrary objects, falls back to `__dict__` if available, else `repr()`.
204+ """
205+ # Pydantic models
206+ if hasattr (value , "model_dump" ):
207+ try :
208+ return make_json_safe (value .model_dump (by_alias = True , exclude_none = True ))
209+ except Exception :
210+ pass
211+
212+ # LangChain-style objects
213+ if hasattr (value , "to_dict" ):
214+ try :
215+ return make_json_safe (value .to_dict ())
216+ except Exception :
217+ pass
218+
219+ # Dict
220+ if isinstance (value , dict ):
221+ return {key : make_json_safe (sub_value ) for key , sub_value in value .items ()}
222+
223+ # List / tuple
224+ if isinstance (value , (list , tuple )):
225+ return [make_json_safe (sub_value ) for sub_value in value ]
226+
227+ # Already JSON safe
228+ if isinstance (value , (str , int , float , bool )) or value is None :
229+ return value
230+
231+ # Arbitrary object: try __dict__ first, fallback to repr
232+ if hasattr (value , "__dict__" ):
233+ return {
234+ "__type__" : type (value ).__name__ ,
235+ ** make_json_safe (value .__dict__ ),
236+ }
237+
238+ return repr (value )
0 commit comments