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