lc_langgraph/compiled/
visualize.rs1use super::graph::CompiledGraph;
6use crate::edge::GraphEdge;
7use crate::persistence::{EdgeDefinition, GraphDefinition, NodeDefinition, NodeType};
8use crate::state::StateSchema;
9
10impl<S: StateSchema> CompiledGraph<S> {
11 pub fn visualize_ascii(&self) -> String {
13 let mut output = String::new();
14 output.push_str("┌─────────────────────────────────────┐\n");
15 output.push_str("│ LangGraph Structure │\n");
16 output.push_str("└─────────────────────────────────────┘\n\n");
17
18 output.push_str(&format!("Entry Point: {}\n\n", self.entry_point));
19
20 output.push_str("Nodes:\n");
21 for name in self.nodes.keys() {
22 output.push_str(&format!(" • {}\n", name));
23 }
24
25 output.push_str("\nEdges:\n");
26 for edge in &self.edges {
27 match edge {
28 GraphEdge::Fixed { source, target } => {
29 output.push_str(&format!(" {} → {}\n", source, target));
30 }
31 GraphEdge::Conditional {
32 source,
33 router_name,
34 targets,
35 ..
36 } => {
37 output.push_str(&format!(" {} → [{}]\n", source, router_name));
38 for (route, target) in targets {
39 output.push_str(&format!(" {} → {}\n", route, target));
40 }
41 }
42 GraphEdge::FanOut { source, targets } => {
43 output.push_str(&format!(" {} → [FanOut]\n", source));
44 for target in targets {
45 output.push_str(&format!(" → {}\n", target));
46 }
47 }
48 GraphEdge::FanIn { sources, target } => {
49 output.push_str(&format!(" [FanIn] → {}\n", target));
50 for source in sources {
51 output.push_str(&format!(" {} →\n", source));
52 }
53 }
54 }
55 }
56
57 if !self.conditional_routers.is_empty() {
58 output.push_str("\nRouters:\n");
59 for name in self.conditional_routers.keys() {
60 output.push_str(&format!(" • {}\n", name));
61 }
62 }
63
64 output.push_str(&format!("\nRecursion Limit: {}\n", self.recursion_limit));
65
66 output
67 }
68
69 pub fn visualize_mermaid(&self) -> String {
71 let mut output = String::new();
72 output.push_str("```mermaid\n");
73 output.push_str("graph TD\n");
74
75 output.push_str(" START[\"START\"]\n");
76 output.push_str(" END[\"END\"]\n");
77
78 for name in self.nodes.keys() {
79 output.push_str(&format!(" {}[\"{}\"]\n", name, name));
80 }
81
82 for edge in &self.edges {
83 match edge {
84 GraphEdge::Fixed { source, target } => {
85 output.push_str(&format!(" {} --> {}\n", source, target));
86 }
87 GraphEdge::Conditional {
88 source,
89 router_name,
90 targets,
91 ..
92 } => {
93 for (route, target) in targets {
94 output.push_str(&format!(" {} -->|{}| {}\n", source, route, target));
95 }
96 let _ = router_name; }
98 GraphEdge::FanOut { source, targets } => {
99 output.push_str(&format!(" {} --> {{\n", source));
100 for target in targets {
101 output.push_str(&format!(" --> {}\n", target));
102 }
103 output.push_str(" }\n");
104 }
105 GraphEdge::FanIn { sources, target } => {
106 for source in sources {
107 output.push_str(&format!(" {} --> {}\n", source, target));
108 }
109 }
110 }
111 }
112
113 output.push_str("```\n");
114 output
115 }
116
117 pub fn visualize_json(&self) -> serde_json::Value {
119 let nodes: Vec<String> = self.nodes.keys().cloned().collect();
120
121 let edges: Vec<serde_json::Value> = self
122 .edges
123 .iter()
124 .map(|edge| match edge {
125 GraphEdge::Fixed { source, target } => {
126 serde_json::json!({
127 "type": "fixed",
128 "source": source,
129 "target": target
130 })
131 }
132 GraphEdge::Conditional {
133 source,
134 router_name,
135 targets,
136 default_target,
137 } => {
138 serde_json::json!({
139 "type": "conditional",
140 "source": source,
141 "router": router_name,
142 "targets": targets,
143 "default": default_target
144 })
145 }
146 GraphEdge::FanOut { source, targets } => {
147 serde_json::json!({
148 "type": "fanout",
149 "source": source,
150 "targets": targets
151 })
152 }
153 GraphEdge::FanIn { sources, target } => {
154 serde_json::json!({
155 "type": "fanin",
156 "sources": sources,
157 "target": target
158 })
159 }
160 })
161 .collect();
162
163 let routers: Vec<String> = self.conditional_routers.keys().cloned().collect();
164
165 serde_json::json!({
166 "entry_point": self.entry_point,
167 "nodes": nodes,
168 "edges": edges,
169 "routers": routers,
170 "recursion_limit": self.recursion_limit
171 })
172 }
173
174 pub fn to_definition(&self) -> GraphDefinition {
175 let mut definition = GraphDefinition::new(self.entry_point.clone())
176 .with_recursion_limit(self.recursion_limit);
177
178 for node_name in self.nodes.keys() {
179 definition.add_node(NodeDefinition {
180 name: node_name.clone(),
181 node_type: NodeType::Sync,
182 config: serde_json::json!({}),
183 });
184 }
185
186 for edge in &self.edges {
187 let edge_def = match edge {
188 GraphEdge::Fixed { source, target } => {
189 EdgeDefinition::fixed(source.clone(), target.clone())
190 }
191 GraphEdge::Conditional {
192 source,
193 router_name,
194 targets,
195 default_target,
196 } => EdgeDefinition::conditional(
197 source.clone(),
198 router_name.clone(),
199 targets.clone(),
200 default_target.clone(),
201 ),
202 GraphEdge::FanOut { source, targets } => {
203 EdgeDefinition::fan_out(source.clone(), targets.clone())
204 }
205 GraphEdge::FanIn { sources, target } => {
206 EdgeDefinition::fan_in(sources.clone(), target.clone())
207 }
208 };
209 definition.add_edge(edge_def);
210 }
211
212 definition
213 }
214}