Skip to main content

langgraph_core_rs/runnable/
seq.rs

1use std::sync::Arc;
2use async_trait::async_trait;
3use serde_json::Value as JsonValue;
4use langgraph_checkpoint::config::RunnableConfig;
5use super::base::{Runnable, RunnableError};
6
7/// Chains multiple `Runnable`s sequentially — output of step N becomes input of step N+1.
8///
9/// This is the Rust equivalent of Python's `RunnableSeq` / `RunnableSequence`.
10/// Requires at least 2 steps (first = node logic, rest = writers/post-processors).
11///
12/// In LangGraph's Pregel engine, every node is assembled as:
13/// `RunnableSeq(node_func, channel_write)`
14pub struct RunnableSeq {
15    name: String,
16    steps: Vec<Arc<dyn Runnable>>,
17}
18
19impl RunnableSeq {
20    /// Create a new sequence. Panics if fewer than 2 steps.
21    pub fn new(name: impl Into<String>, steps: Vec<Arc<dyn Runnable>>) -> Self {
22        assert!(steps.len() >= 2, "RunnableSeq requires at least 2 steps");
23        Self {
24            name: name.into(),
25            steps,
26        }
27    }
28
29    /// Create with any number of steps (1 is allowed for degenerate cases).
30    pub fn new_relaxed(name: impl Into<String>, steps: Vec<Arc<dyn Runnable>>) -> Self {
31        Self {
32            name: name.into(),
33            steps,
34        }
35    }
36
37    /// Number of steps in the sequence.
38    pub fn len(&self) -> usize {
39        self.steps.len()
40    }
41
42    /// Whether the sequence is empty.
43    pub fn is_empty(&self) -> bool {
44        self.steps.is_empty()
45    }
46}
47
48#[async_trait]
49impl Runnable for RunnableSeq {
50    fn invoke(&self, input: &JsonValue, config: &RunnableConfig) -> Result<JsonValue, RunnableError> {
51        let mut current = input.clone();
52        for step in &self.steps {
53            current = step.invoke(&current, config)?;
54        }
55        Ok(current)
56    }
57
58    async fn ainvoke(&self, input: &JsonValue, config: &RunnableConfig) -> Result<JsonValue, RunnableError> {
59        let mut current = input.clone();
60        for step in &self.steps {
61            current = step.ainvoke(&current, config).await?;
62        }
63        Ok(current)
64    }
65
66    fn name(&self) -> &str {
67        &self.name
68    }
69}
70
71/// Pipe operator support: `a | b` creates `RunnableSeq("a|b", [a, b])`.
72///
73/// Since Rust doesn't support operator overloading on trait objects,
74/// use the `pipe` free function instead:
75/// ```ignore
76/// let seq = pipe(a, b); // equivalent to a | b in Python
77/// ```
78pub fn pipe(first: Arc<dyn Runnable>, second: Arc<dyn Runnable>) -> RunnableSeq {
79    let name = format!("{}|{}", first.name(), second.name());
80    RunnableSeq::new_relaxed(name, vec![first, second])
81}