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, 0).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 = self.merge_parallel_states(¶llel_branches)?;
74 current_node = END.to_string();
75 }
76 } else {
77 let node = self.get_node(¤t_node).await?;
78
79 let config = NodeConfig {
80 recursion_limit: self.recursion_limit,
81 debug: false,
82 metadata: HashMap::new(),
83 };
84
85 let update = node.execute(&state, Some(config)).await?;
86
87 if let Some(new_state) = update.update {
88 state = self.default_reducer.reduce(&state, &new_state);
89 }
90
91 steps.push(ExecutionStep::node(
92 current_node.clone(),
93 update.metadata.clone(),
94 ));
95
96 if self.interrupt_after.contains(¤t_node) {
97 return Err(GraphError::ExecutionInterrupted(format!(
98 "after_{}",
99 current_node
100 )));
101 }
102
103 current_node = self.find_next_node(¤t_node, &state).await?;
104 }
105 }
106
107 Ok(ParallelInvocation {
108 final_state: state,
109 steps,
110 recursion_count,
111 parallel_branches,
112 })
113 }
114
115 pub async fn invoke_dynamic(
121 &self,
122 input: S,
123 planner: &dyn DynamicPlanner<S>,
124 ) -> GraphResult<GraphInvocation<S>> {
125 let mut state = input;
126 let mut current_node = self.entry_point.clone();
127 let mut steps: Vec<ExecutionStep> = Vec::new();
128 let mut recursion_count = 0;
129
130 if let Some(ref checkpointer) = self.checkpointer {
131 let checkpoint_id = checkpointer.lock().await.save(&state, 0).await?;
132 steps.push(ExecutionStep::checkpoint(
133 checkpoint_id,
134 current_node.clone(),
135 ));
136 }
137
138 loop {
139 if current_node == END {
140 break;
141 }
142
143 if recursion_count >= self.recursion_limit {
145 return Err(GraphError::RecursionLimitReached(self.recursion_limit));
146 }
147
148 let pending = {
150 let mut inbox = self.task_inbox.lock().await;
151 inbox.drain(..).collect::<Vec<_>>()
152 };
153 if !pending.is_empty() {
154 let injection: DynamicInjection<S> = planner
155 .plan(&pending, &state)
156 .await
157 .map_err(|e| GraphError::RuntimeError(e.to_string()))?;
158
159 {
160 let mut rn = self.runtime_nodes.write().await;
161 for (name, node) in injection.nodes {
162 rn.insert(name, node);
163 }
164 }
165 {
166 let mut re = self.runtime_edges.write().await;
167 re.extend(injection.edges);
168 }
169 }
170
171 if self.interrupt_before.contains(¤t_node) {
172 return Err(GraphError::ExecutionInterrupted(current_node.clone()));
173 }
174
175 recursion_count += 1;
176
177 let node = self.get_node(¤t_node).await?;
178 let config = NodeConfig {
179 recursion_limit: self.recursion_limit,
180 debug: false,
181 metadata: HashMap::new(),
182 };
183 let update = node.execute(&state, Some(config)).await?;
184
185 if let Some(new_state) = update.update {
186 state = self.default_reducer.reduce(&state, &new_state);
187 }
188 steps.push(ExecutionStep::node(
189 current_node.clone(),
190 update.metadata.clone(),
191 ));
192
193 if self.interrupt_after.contains(¤t_node) {
194 return Err(GraphError::ExecutionInterrupted(format!(
195 "after_{}",
196 current_node
197 )));
198 }
199
200 let next_node = self.find_next_node(¤t_node, &state).await?;
201
202 if let Some(ref checkpointer) = self.checkpointer {
203 let checkpoint_id =
204 checkpointer.lock().await.save(&state, recursion_count).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}