lc_langgraph/compiled/types.rs
1// crates/lc-langgraph/src/compiled/types.rs
2//! Public types for graph execution results and streaming events
3
4use crate::edge::GraphEdge;
5use crate::node::GraphNode;
6use crate::state::{StateSchema, StateUpdate};
7use async_trait::async_trait;
8use serde_json::Value as JsonValue;
9use std::collections::HashMap;
10use std::sync::Arc;
11
12/// GraphInvocation - Result of graph execution
13#[derive(Debug)]
14pub struct GraphInvocation<S: StateSchema> {
15 /// The final state after execution.
16 pub final_state: S,
17 /// Execution steps recorded during the run.
18 pub steps: Vec<ExecutionStep>,
19 /// Number of recursive steps consumed during execution.
20 pub recursion_count: usize,
21}
22
23impl<S: StateSchema> GraphInvocation<S> {
24 /// Return the final state.
25 pub fn state(&self) -> &S {
26 &self.final_state
27 }
28
29 /// Return the recorded execution steps.
30 pub fn steps(&self) -> &[ExecutionStep] {
31 &self.steps
32 }
33}
34
35/// ExecutionStep - Single step in execution history
36#[derive(Debug, Clone)]
37pub enum ExecutionStep {
38 /// A regular node execution step.
39 Node {
40 /// Name of the executed node.
41 name: String,
42 /// Metadata returned by the node execution.
43 metadata: HashMap<String, JsonValue>,
44 },
45 /// A checkpoint was saved during execution.
46 Checkpoint {
47 /// The saved checkpoint id.
48 id: String,
49 /// The next node scheduled to run after the checkpoint.
50 next_node: String,
51 },
52 /// A parallel branch execution step.
53 ParallelNode {
54 /// Name of the parallel branch.
55 branch: String,
56 /// Metadata returned by the branch execution.
57 metadata: HashMap<String, JsonValue>,
58 },
59}
60
61impl ExecutionStep {
62 /// Construct a `Node` execution step.
63 pub fn node(name: impl Into<String>, metadata: HashMap<String, JsonValue>) -> Self {
64 Self::Node {
65 name: name.into(),
66 metadata,
67 }
68 }
69
70 /// Construct a `Checkpoint` execution step.
71 pub fn checkpoint(id: impl Into<String>, next_node: impl Into<String>) -> Self {
72 Self::Checkpoint {
73 id: id.into(),
74 next_node: next_node.into(),
75 }
76 }
77
78 /// Construct a `ParallelNode` execution step.
79 pub fn parallel_node(branch: impl Into<String>, metadata: HashMap<String, JsonValue>) -> Self {
80 Self::ParallelNode {
81 branch: branch.into(),
82 metadata,
83 }
84 }
85}
86
87/// ParallelBranch - Result of a parallel execution branch
88#[derive(Debug, Clone)]
89pub struct ParallelBranch<S: StateSchema> {
90 /// Name of the parallel branch.
91 pub name: String,
92 /// Final state produced by the branch.
93 pub final_state: S,
94 /// Execution steps recorded in the branch.
95 pub steps: Vec<ExecutionStep>,
96}
97
98/// ParallelInvocation - Result of graph execution with parallel branches
99#[derive(Debug)]
100pub struct ParallelInvocation<S: StateSchema> {
101 /// The final merged state after execution.
102 pub final_state: S,
103 /// Execution steps recorded during the run.
104 pub steps: Vec<ExecutionStep>,
105 /// Number of recursive steps consumed during execution.
106 pub recursion_count: usize,
107 /// Captured results of each parallel branch.
108 pub parallel_branches: Vec<ParallelBranch<S>>,
109}
110
111impl<S: StateSchema> ParallelInvocation<S> {
112 /// Return the final merged state.
113 pub fn state(&self) -> &S {
114 &self.final_state
115 }
116
117 /// Return the captured parallel branch results.
118 pub fn branches(&self) -> &[ParallelBranch<S>] {
119 &self.parallel_branches
120 }
121}
122
123/// StreamEvent - Event for streaming execution
124#[derive(Debug, Clone)]
125pub enum StreamEvent<S: StateSchema> {
126 /// Execution started with the initial state.
127 Start(S),
128 /// A node was entered with the current state.
129 EnterNode(String, S),
130 /// A node completed with its state update.
131 NodeComplete(String, StateUpdate<S>),
132 /// The state was updated after applying a reducer.
133 StateUpdate(S),
134 /// Execution finished with the final state.
135 End(S),
136 /// A node requested a runtime interrupt (suspension) with a payload.
137 NodeInterrupt(String, JsonValue),
138 /// Execution resumed after a runtime interrupt with the given decision.
139 Resumed(String, JsonValue),
140}
141
142impl<S: StateSchema> StreamEvent<S> {
143 /// Construct a `Start` stream event.
144 pub fn start(state: S) -> Self {
145 Self::Start(state)
146 }
147
148 /// Construct an `EnterNode` stream event.
149 pub fn enter_node(name: impl Into<String>, state: S) -> Self {
150 Self::EnterNode(name.into(), state)
151 }
152
153 /// Construct a `NodeComplete` stream event.
154 pub fn node_complete(name: impl Into<String>, update: StateUpdate<S>) -> Self {
155 Self::NodeComplete(name.into(), update)
156 }
157
158 /// Construct a `StateUpdate` stream event.
159 pub fn state_update(state: S) -> Self {
160 Self::StateUpdate(state)
161 }
162
163 /// Construct an `End` stream event.
164 pub fn end(state: S) -> Self {
165 Self::End(state)
166 }
167
168 /// Construct a `NodeInterrupt` stream event.
169 pub fn node_interrupt(name: impl Into<String>, payload: JsonValue) -> Self {
170 Self::NodeInterrupt(name.into(), payload)
171 }
172
173 /// Construct a `Resumed` stream event.
174 pub fn resumed(name: impl Into<String>, decision: JsonValue) -> Self {
175 Self::Resumed(name.into(), decision)
176 }
177}
178
179/// A suspended runtime interrupt: the payload a node published when it
180/// requested human-in-the-loop input, carried through [`GraphExecution`] so a
181/// resume can re-enter the interrupted node with the human's decision.
182#[derive(Debug, Clone)]
183pub struct PendingInterrupt {
184 /// Unique id of this interrupt.
185 pub id: String,
186 /// The decision/value collected from the human, injected back into the node
187 /// on resume.
188 pub value: JsonValue,
189}
190
191/// GraphExecution - State for interrupted execution that can be resumed
192#[derive(Debug, Clone)]
193pub struct GraphExecution<S: StateSchema> {
194 /// The state at the point of interruption.
195 pub state: S,
196 /// The node where execution was interrupted.
197 pub current_node: String,
198 /// Execution steps recorded so far.
199 pub steps: Vec<ExecutionStep>,
200 /// Recursion budget consumed up to the interruption.
201 pub recursion_count: usize,
202 /// Where execution was interrupted ("node" or "after_node").
203 pub interrupted_at: String,
204 /// A node-requested (runtime) interrupt awaiting a human decision. When
205 /// `Some`, resuming re-runs `current_node` and injects this value into it.
206 pub pending_interrupt: Option<PendingInterrupt>,
207}
208
209impl<S: StateSchema> GraphExecution<S> {
210 /// Create a new execution context for resuming from an interruption.
211 pub fn new(
212 state: S,
213 current_node: impl Into<String>,
214 interrupted_at: impl Into<String>,
215 ) -> Self {
216 Self {
217 state,
218 current_node: current_node.into(),
219 steps: Vec::new(),
220 recursion_count: 0,
221 interrupted_at: interrupted_at.into(),
222 pending_interrupt: None,
223 }
224 }
225
226 /// Return the current state.
227 pub fn state(&self) -> &S {
228 &self.state
229 }
230
231 /// Return the interruption point.
232 pub fn interrupted_at(&self) -> &str {
233 &self.interrupted_at
234 }
235}
236
237// ── Dynamic extension types ──
238
239/// A task submitted externally for dynamic planning mid-execution.
240#[derive(Debug, Clone)]
241pub struct DynamicTask {
242 /// Unique id of the task.
243 pub id: String,
244 /// Description of the task to plan for.
245 pub description: String,
246}
247
248/// Result of dynamic planning: nodes and edges to inject into a running graph.
249pub struct DynamicInjection<S: StateSchema> {
250 /// Nodes to inject into the runtime, keyed by name.
251 pub nodes: Vec<(String, Arc<dyn GraphNode<S>>)>,
252 /// Edges to inject into the runtime.
253 pub edges: Vec<GraphEdge>,
254}
255
256/// Planner trait for converting external tasks into graph nodes/edges at runtime.
257#[async_trait]
258pub trait DynamicPlanner<S: StateSchema>: Send + Sync {
259 /// Given pending tasks and current state, produce nodes/edges to inject.
260 async fn plan(
261 &self,
262 tasks: &[DynamicTask],
263 current_state: &S,
264 ) -> Result<DynamicInjection<S>, String>;
265}