Skip to main content

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}