Skip to main content

lc_langgraph/compiled/
parallel.rs

1// crates/lc-langgraph/src/compiled/parallel.rs
2//! CompiledGraph invoke_parallel, invoke_dynamic, and merge_parallel_states methods
3
4use 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            // Q3: check the limit before executing a step (same guard as `invoke`).
37            if recursion_count >= self.recursion_limit {
38                return Err(GraphError::RecursionLimitReached(self.recursion_limit));
39            }
40
41            if self.interrupt_before.contains(&current_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(&current_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(&parallel_branches)?;
69                    current_node = merge_node;
70                } else {
71                    // H6: 与 invoke.rs 的主路径一致——所有分支结果经 reducer 合并,
72                    // 而不是只保留 `parallel_branches.last()` 一个分支、丢弃其余分支。
73                    state = self.merge_parallel_states(&parallel_branches)?;
74                    current_node = END.to_string();
75                }
76            } else {
77                let node = self.get_node(&current_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(&current_node) {
97                    return Err(GraphError::ExecutionInterrupted(format!(
98                        "after_{}",
99                        current_node
100                    )));
101                }
102
103                current_node = self.find_next_node(&current_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    /// Execute the graph with dynamic task injection support.
116    ///
117    /// After each node completes, checks the task_inbox for newly submitted tasks.
118    /// If tasks are found, uses the provided `planner` to convert them into new
119    /// nodes/edges and injects them into the runtime registries before continuing.
120    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            // Q3: check the limit before executing a step (same guard as `invoke`).
144            if recursion_count >= self.recursion_limit {
145                return Err(GraphError::RecursionLimitReached(self.recursion_limit));
146            }
147
148            // Check for pending dynamically-submitted tasks
149            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(&current_node) {
172                return Err(GraphError::ExecutionInterrupted(current_node.clone()));
173            }
174
175            recursion_count += 1;
176
177            let node = self.get_node(&current_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(&current_node) {
194                return Err(GraphError::ExecutionInterrupted(format!(
195                    "after_{}",
196                    current_node
197                )));
198            }
199
200            let next_node = self.find_next_node(&current_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}