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