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>> {
17 let mut state = input;
18 let mut current_node = self.entry_point.clone();
19 let mut steps: Vec<ExecutionStep> = Vec::new();
20 let mut recursion_count = 0;
21 let mut parallel_branches: Vec<ParallelBranch<S>> = Vec::new();
22
23 if let Some(ref checkpointer) = self.checkpointer {
24 let checkpoint_id = checkpointer.lock().await.save(&state).await?;
25 steps.push(ExecutionStep::checkpoint(
26 checkpoint_id,
27 current_node.clone(),
28 ));
29 }
30
31 loop {
32 if current_node == END {
33 break;
34 }
35
36 if recursion_count >= self.recursion_limit {
38 return Err(GraphError::RecursionLimitReached(self.recursion_limit));
39 }
40
41 if self.interrupt_before.contains(¤t_node) {
42 return Err(GraphError::ExecutionInterrupted(current_node.clone()));
43 }
44
45 recursion_count += 1;
46
47 let fan_out_targets = self.find_fan_out_targets(¤t_node).await;
48
49 if let Some(targets) = fan_out_targets {
50 let branch_results = self
51 .execute_parallel_branches(&targets, &state, recursion_count)
52 .await?;
53
54 for (name, inv) in branch_results {
55 parallel_branches.push(ParallelBranch {
56 name: name.clone(),
57 final_state: inv.final_state.clone(),
58 steps: inv.steps.clone(),
59 });
60 steps.push(ExecutionStep::ParallelNode {
61 branch: name,
62 metadata: HashMap::new(),
63 });
64 }
65
66 let merge_target = self.find_fan_in_target(&targets).await;
67 if let Some(merge_node) = merge_target {
68 state = self.merge_parallel_states(¶llel_branches)?;
69 current_node = merge_node;
70 } else {
71 state = parallel_branches
72 .last()
73 .map(|b| b.final_state.clone())
74 .unwrap_or(state);
75 current_node = END.to_string();
76 }
77 } else {
78 let node = self.get_node(¤t_node).await?;
79
80 let config = NodeConfig {
81 recursion_limit: self.recursion_limit,
82 debug: false,
83 metadata: HashMap::new(),
84 };
85
86 let update = node.execute(&state, Some(config)).await?;
87
88 if let Some(new_state) = update.update {
89 state = self.default_reducer.reduce(&state, &new_state);
90 }
91
92 steps.push(ExecutionStep::node(
93 current_node.clone(),
94 update.metadata.clone(),
95 ));
96
97 if self.interrupt_after.contains(¤t_node) {
98 return Err(GraphError::ExecutionInterrupted(format!(
99 "after_{}",
100 current_node
101 )));
102 }
103
104 current_node = self.find_next_node(¤t_node, &state).await?;
105 }
106 }
107
108 Ok(ParallelInvocation {
109 final_state: state,
110 steps,
111 recursion_count,
112 parallel_branches,
113 })
114 }
115
116 pub async fn invoke_dynamic(
122 &self,
123 input: S,
124 planner: &dyn DynamicPlanner<S>,
125 ) -> GraphResult<GraphInvocation<S>> {
126 let mut state = input;
127 let mut current_node = self.entry_point.clone();
128 let mut steps: Vec<ExecutionStep> = Vec::new();
129 let mut recursion_count = 0;
130
131 if let Some(ref checkpointer) = self.checkpointer {
132 let checkpoint_id = checkpointer.lock().await.save(&state).await?;
133 steps.push(ExecutionStep::checkpoint(
134 checkpoint_id,
135 current_node.clone(),
136 ));
137 }
138
139 loop {
140 if current_node == END {
141 break;
142 }
143
144 if recursion_count >= self.recursion_limit {
146 return Err(GraphError::RecursionLimitReached(self.recursion_limit));
147 }
148
149 let pending = {
151 let mut inbox = self.task_inbox.lock().await;
152 inbox.drain(..).collect::<Vec<_>>()
153 };
154 if !pending.is_empty() {
155 let injection: DynamicInjection<S> = planner
156 .plan(&pending, &state)
157 .await
158 .map_err(|e| GraphError::RuntimeError(e.to_string()))?;
159
160 {
161 let mut rn = self.runtime_nodes.write().await;
162 for (name, node) in injection.nodes {
163 rn.insert(name, node);
164 }
165 }
166 {
167 let mut re = self.runtime_edges.write().await;
168 re.extend(injection.edges);
169 }
170 }
171
172 if self.interrupt_before.contains(¤t_node) {
173 return Err(GraphError::ExecutionInterrupted(current_node.clone()));
174 }
175
176 recursion_count += 1;
177
178 let node = self.get_node(¤t_node).await?;
179 let config = NodeConfig {
180 recursion_limit: self.recursion_limit,
181 debug: false,
182 metadata: HashMap::new(),
183 };
184 let update = node.execute(&state, Some(config)).await?;
185
186 if let Some(new_state) = update.update {
187 state = self.default_reducer.reduce(&state, &new_state);
188 }
189 steps.push(ExecutionStep::node(
190 current_node.clone(),
191 update.metadata.clone(),
192 ));
193
194 if self.interrupt_after.contains(¤t_node) {
195 return Err(GraphError::ExecutionInterrupted(format!(
196 "after_{}",
197 current_node
198 )));
199 }
200
201 let next_node = self.find_next_node(¤t_node, &state).await?;
202
203 if let Some(ref checkpointer) = self.checkpointer {
204 let checkpoint_id = checkpointer.lock().await.save(&state).await?;
205 steps.push(ExecutionStep::checkpoint(checkpoint_id, next_node.clone()));
206 }
207
208 current_node = next_node;
209 }
210
211 Ok(GraphInvocation {
212 final_state: state,
213 steps,
214 recursion_count,
215 })
216 }
217
218 pub(super) fn merge_parallel_states(&self, branches: &[ParallelBranch<S>]) -> GraphResult<S> {
219 if branches.is_empty() {
220 return Err(GraphError::StateError(
221 "Cannot merge states: no parallel branches completed".to_string(),
222 ));
223 }
224
225 let mut merged = branches[0].final_state.clone();
226 for branch in branches.iter().skip(1) {
227 merged = self.default_reducer.reduce(&merged, &branch.final_state);
228 }
229 Ok(merged)
230 }
231}