1
1
import json
2
2
import re
3
3
from typing import List , Any , Dict , Union
4
+ from dataclasses import is_dataclass , asdict
5
+ from datetime import date , datetime
4
6
5
7
from langchain_core .messages import BaseMessage , HumanMessage , AIMessage , SystemMessage , ToolMessage
6
8
from ag_ui .core import (
@@ -177,3 +179,60 @@ def resolve_message_content(content: Any) -> str | None:
177
179
178
180
def camel_to_snake (name ):
179
181
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