lc_langgraph/compiled/
parallel.rs1use super::graph::CompiledGraph;
5use super::types::{
6 DynamicInjection, DynamicPlanner, ExecutionStep, GraphInvocation, ParallelBranch,
7 ParallelInvocation,
8};
9use crate::errors::{GraphError, GraphResult};
10use crate::node::NodeConfig;
11use crate::state::StateSchema;
12use crate::END;
13use std::collections::HashMap;
14
15impl<S: StateSchema> CompiledGraph<S> {
16 pub async fn invoke_parallel(&self, input: S) -> GraphResult<ParallelInvocation<S>> {
18 let mut state = input;
19 let mut current_node = self.entry_point.clone();
20 let mut steps: Vec<ExecutionStep> = Vec::new();
21 let mut recursion_count = 0;
22 let mut parallel_branches: Vec<ParallelBranch<S>> = Vec::new();
23
24 if let Some(ref checkpointer) = self.checkpointer {
25 let checkpoint_id = checkpointer.lock().await.save(&state, 0).await?;
26 steps.push(ExecutionStep::checkpoint(
27 checkpoint_id,
28 current_node.clone(),
29 ));
30 }
31
32 loop {
33 if current_node == END {
34 break;
35 }
36
37 if recursion_count >= self.recursion_limit {
39 return Err(GraphError::RecursionLimitReached(self.recursion_limit));
40 }
41
42 if self.interrupt_before.contains(¤t_node) {
43 return Err(GraphError::ExecutionInterrupted(current_node.clone()));
44 }
45
46 if current_node != crate::START {
50 recursion_count += 1;
51
52 let node = self.get_node(¤t_node).await?;
53
54 let config = NodeConfig {
55 recursion_limit: self.recursion_limit,
56 debug: false,
57 metadata: HashMap::new(),
58 };
59
60 let update = node.execute(&state, Some(config)).await?;
61
62 if let Some(new_state) = update.update {
63 state = self.default_reducer.reduce(&state, &new_state);
64 }
65
66 steps.push(ExecutionStep::node(
67 current_node.clone(),
68 update.metadata.clone(),
69 ));
70
71 if self.interrupt_after.contains(¤t_node) {
72 return Err(GraphError::ExecutionInterrupted(format!(
73 "after_{}",
74 current_node
75 )));
76 }
77 }
78
79 let fan_out_targets = self.find_fan_out_targets(¤t_node).await;
81
82 if let Some(targets) = fan_out_targets {
83 recursion_count += 1;
84 let branch_results = self
85 .execute_parallel_branches(&targets, &state, recursion_count)
86 .await?;
87
88 for (name, inv) in branch_results {
89 parallel_branches.push(ParallelBranch {
90 name: name.clone(),
91 final_state: inv.final_state.clone(),
92 steps: inv.steps.clone(),
93 });
94 steps.push(ExecutionStep::ParallelNode {
95 branch: name,
96 metadata: HashMap::new(),
97 });
98 }
99
100 let merge_target = self.find_fan_in_target(&targets).await;
101 let base = state.clone();
103 if let Some(merge_node) = merge_target {
104 state = self.merge_parallel_states(¶llel_branches, &base)?;
105 current_node = merge_node;
106 } else {
107 state = self.merge_parallel_states(¶llel_branches, &base)?;
108 current_node = END.to_string();
109 }
110 } else {
111 current_node = self.find_next_node(¤t_node, &state).await?;
112 }
113 }
114
115 Ok(ParallelInvocation {
116 final_state: state,
117 steps,
118 recursion_count,
119 parallel_branches,
120 })
121 }
122
123 pub async fn invoke_dynamic(
129 &self,
130 input: S,
131 planner: &dyn DynamicPlanner<S>,
132 ) -> GraphResult<GraphInvocation<S>> {
133 let mut state = input;
134 let mut current_node = self.entry_point.clone();
135 let mut steps: Vec<ExecutionStep> = Vec::new();
136 let mut recursion_count = 0;
137
138 if let Some(ref checkpointer) = self.checkpointer {
139 let checkpoint_id = checkpointer.lock().await.save(&state, 0).await?;
140 steps.push(ExecutionStep::checkpoint(
141 checkpoint_id,
142 current_node.clone(),
143 ));
144 }
145
146 loop {
147 if current_node == END {
148 break;
149 }
150
151 if recursion_count >= self.recursion_limit {
153 return Err(GraphError::RecursionLimitReached(self.recursion_limit));
154 }
155
156 let pending = {
158 let mut inbox = self.task_inbox.lock().await;
159 inbox.drain(..).collect::<Vec<_>>()
160 };
161 if !pending.is_empty() {
162 let injection: DynamicInjection<S> = planner
163 .plan(&pending, &state)
164 .await
165 .map_err(|e| GraphError::RuntimeError(e.to_string()))?;
166
167 {
168 let mut rn = self.runtime_nodes.write().await;
169 for (name, node) in injection.nodes {
170 rn.insert(name, node);
171 }
172 }
173 {
174 let mut re = self.runtime_edges.write().await;
175 re.extend(injection.edges);
176 }
177 }
178
179 if self.interrupt_before.contains(¤t_node) {
180 return Err(GraphError::ExecutionInterrupted(current_node.clone()));
181 }
182
183 recursion_count += 1;
184
185 let node = self.get_node(¤t_node).await?;
186 let config = NodeConfig {
187 recursion_limit: self.recursion_limit,
188 debug: false,
189 metadata: HashMap::new(),
190 };
191 let update = node.execute(&state, Some(config)).await?;
192
193 if let Some(new_state) = update.update {
194 state = self.default_reducer.reduce(&state, &new_state);
195 }
196 steps.push(ExecutionStep::node(
197 current_node.clone(),
198 update.metadata.clone(),
199 ));
200
201 if self.interrupt_after.contains(¤t_node) {
202 return Err(GraphError::ExecutionInterrupted(format!(
203 "after_{}",
204 current_node
205 )));
206 }
207
208 let fan_out_targets = self.find_fan_out_targets(¤t_node).await;
213 if let Some(targets) = fan_out_targets {
214 recursion_count += 1;
215 let mut parallel_branches: Vec<ParallelBranch<S>> = Vec::new();
216
217 let branch_results = self
218 .execute_parallel_branches(&targets, &state, recursion_count)
219 .await?;
220 for (name, inv) in branch_results {
221 parallel_branches.push(ParallelBranch {
222 name: name.clone(),
223 final_state: inv.final_state.clone(),
224 steps: inv.steps.clone(),
225 });
226 steps.push(ExecutionStep::ParallelNode {
227 branch: name,
228 metadata: HashMap::new(),
229 });
230 }
231
232 let merge_target = self.find_fan_in_target(&targets).await;
233 let base = state.clone();
234 state = self.merge_parallel_states(¶llel_branches, &base)?;
235 current_node = merge_target.unwrap_or_else(|| END.to_string());
236
237 if let Some(ref checkpointer) = self.checkpointer {
238 let checkpoint_id = checkpointer
239 .lock()
240 .await
241 .save(&state, recursion_count)
242 .await?;
243 steps.push(ExecutionStep::checkpoint(
244 checkpoint_id,
245 current_node.clone(),
246 ));
247 }
248 continue;
249 }
250
251 let next_node = self.find_next_node(¤t_node, &state).await?;
252
253 if let Some(ref checkpointer) = self.checkpointer {
254 let checkpoint_id = checkpointer
255 .lock()
256 .await
257 .save(&state, recursion_count)
258 .await?;
259 steps.push(ExecutionStep::checkpoint(checkpoint_id, next_node.clone()));
260 }
261
262 current_node = next_node;
263 }
264
265 Ok(GraphInvocation {
266 final_state: state,
267 steps,
268 recursion_count,
269 })
270 }
271
272 pub(super) fn merge_parallel_states(
279 &self,
280 branches: &[ParallelBranch<S>],
281 base: &S,
282 ) -> GraphResult<S> {
283 if branches.is_empty() {
284 return Err(GraphError::StateError(
285 "Cannot merge states: no parallel branches completed".to_string(),
286 ));
287 }
288
289 let mut merged = base.clone();
290 for branch in branches {
291 merged = self.default_reducer.reduce(&merged, &branch.final_state);
292 }
293 Ok(merged)
294 }
295}