1+ from dataclasses import dataclass
12from typing import Any , Dict
23
34from langgraph .graph .state import CompiledStateGraph
45
56
6- def resolve_refs (schema , root = None ):
7- """Recursively resolves $ref references in a JSON schema."""
7+ @dataclass
8+ class SchemaDetails :
9+ schema : dict [str , Any ]
10+ has_input_circular_dependency : bool
11+ has_output_circular_dependency : bool
12+
13+
14+ def resolve_refs (schema , root = None , visited = None ):
15+ """Recursively resolves $ref references in a JSON schema, handling circular references.
16+
17+ Returns:
18+ tuple: (resolved_schema, has_circular_dependency)
19+ """
820 if root is None :
9- root = schema # Store the root schema to resolve $refs
21+ root = schema
22+
23+ if visited is None :
24+ visited = set ()
25+
26+ has_circular = False
1027
1128 if isinstance (schema , dict ):
1229 if "$ref" in schema :
13- ref_path = schema ["$ref" ].lstrip ("#/" ).split ("/" )
30+ ref_path = schema ["$ref" ]
31+
32+ if ref_path in visited :
33+ # Circular dependency detected
34+ return {
35+ "type" : "object" ,
36+ "description" : f"Circular reference to { ref_path } " ,
37+ }, True
38+
39+ visited .add (ref_path )
40+
41+ # Resolve the reference
42+ ref_parts = ref_path .lstrip ("#/" ).split ("/" )
1443 ref_schema = root
15- for part in ref_path :
44+ for part in ref_parts :
1645 ref_schema = ref_schema .get (part , {})
17- return resolve_refs (ref_schema , root )
1846
19- return {k : resolve_refs (v , root ) for k , v in schema .items ()}
47+ result , circular = resolve_refs (ref_schema , root , visited )
48+ has_circular = has_circular or circular
49+
50+ # Remove from visited after resolution (allows the same ref in different branches)
51+ visited .discard (ref_path )
52+
53+ return result , has_circular
54+
55+ resolved_dict = {}
56+ for k , v in schema .items ():
57+ resolved_value , circular = resolve_refs (v , root , visited )
58+ resolved_dict [k ] = resolved_value
59+ has_circular = has_circular or circular
60+ return resolved_dict , has_circular
2061
2162 elif isinstance (schema , list ):
22- return [resolve_refs (item , root ) for item in schema ]
63+ resolved_list = []
64+ for item in schema :
65+ resolved_item , circular = resolve_refs (item , root , visited )
66+ resolved_list .append (resolved_item )
67+ has_circular = has_circular or circular
68+ return resolved_list , has_circular
2369
24- return schema
70+ return schema , False
2571
2672
2773def process_nullable_types (
@@ -45,8 +91,10 @@ def process_nullable_types(
4591
4692def generate_schema_from_graph (
4793 graph : CompiledStateGraph [Any , Any , Any ],
48- ) -> Dict [ str , Any ] :
94+ ) -> SchemaDetails :
4995 """Extract input/output schema from a LangGraph graph"""
96+ input_circular_dependency = False
97+ output_circular_dependency = False
5098 schema = {
5199 "input" : {"type" : "object" , "properties" : {}, "required" : []},
52100 "output" : {"type" : "object" , "properties" : {}, "required" : []},
@@ -55,7 +103,9 @@ def generate_schema_from_graph(
55103 if hasattr (graph , "input_schema" ):
56104 if hasattr (graph .input_schema , "model_json_schema" ):
57105 input_schema = graph .input_schema .model_json_schema ()
58- unpacked_ref_def_properties = resolve_refs (input_schema )
106+ unpacked_ref_def_properties , input_circular_dependency = resolve_refs (
107+ input_schema
108+ )
59109
60110 # Process the schema to handle nullable types
61111 processed_properties = process_nullable_types (
@@ -70,7 +120,9 @@ def generate_schema_from_graph(
70120 if hasattr (graph , "output_schema" ):
71121 if hasattr (graph .output_schema , "model_json_schema" ):
72122 output_schema = graph .output_schema .model_json_schema ()
73- unpacked_ref_def_properties = resolve_refs (output_schema )
123+ unpacked_ref_def_properties , output_circular_dependency = resolve_refs (
124+ output_schema
125+ )
74126
75127 # Process the schema to handle nullable types
76128 processed_properties = process_nullable_types (
@@ -82,4 +134,4 @@ def generate_schema_from_graph(
82134 "required" , []
83135 )
84136
85- return schema
137+ return SchemaDetails ( schema , input_circular_dependency , output_circular_dependency )
0 commit comments