Skip to main content

rust_langgraph/channels/
ephemeral.rs

1//! Ephemeral value channel implementation.
2//!
3//! A channel that clears its value after each superstep.
4
5use super::BaseChannel;
6use crate::errors::Result;
7use serde::{Deserialize, Serialize};
8use std::fmt::Debug;
9use std::marker::PhantomData;
10
11/// A channel that is cleared after each superstep.
12///
13/// Ephemeral channels are useful for temporary data that should only
14/// be visible to nodes within a single superstep. After the step completes,
15/// the value is automatically cleared.
16///
17/// # Example
18///
19/// ```rust
20/// use rust_langgraph::channels::{BaseChannel, EphemeralValue};
21///
22/// let mut channel = EphemeralValue::<String>::new();
23/// channel.update(vec![serde_json::json!("temporary")]).unwrap();
24/// assert!(channel.get().unwrap().is_some());
25///
26/// // After superstep (simulated by calling clear)
27/// channel.clear();
28/// assert!(channel.get().unwrap().is_none());
29/// ```
30#[derive(Debug, Clone)]
31pub struct EphemeralValue<T> {
32    value: Option<T>,
33    _phantom: PhantomData<T>,
34}
35
36impl<T> EphemeralValue<T> {
37    /// Create a new empty EphemeralValue channel
38    pub fn new() -> Self {
39        Self {
40            value: None,
41            _phantom: PhantomData,
42        }
43    }
44
45    /// Clear the ephemeral value
46    pub fn clear(&mut self) {
47        self.value = None;
48    }
49}
50
51impl<T> Default for EphemeralValue<T> {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl<T> BaseChannel for EphemeralValue<T>
58where
59    T: Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + Debug + 'static,
60{
61    fn get(&self) -> Result<Option<serde_json::Value>> {
62        match &self.value {
63            Some(v) => Ok(Some(serde_json::to_value(v)?)),
64            None => Ok(None),
65        }
66    }
67
68    fn update(&mut self, values: Vec<serde_json::Value>) -> Result<()> {
69        // Like LastValue, keep only the last value
70        if let Some(last) = values.last() {
71            self.value = Some(serde_json::from_value(last.clone())?);
72        }
73        Ok(())
74    }
75
76    fn checkpoint(&self) -> Result<serde_json::Value> {
77        // Ephemeral values are not persisted in checkpoints
78        Ok(serde_json::Value::Null)
79    }
80
81    fn from_checkpoint(_data: serde_json::Value) -> Result<Box<dyn BaseChannel>> {
82        // Always start empty
83        Ok(Box::new(Self::new()))
84    }
85
86    fn type_name(&self) -> &'static str {
87        "EphemeralValue"
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_ephemeral_basic() {
97        let mut channel = EphemeralValue::<i32>::new();
98        assert!(channel.get().unwrap().is_none());
99
100        channel.update(vec![serde_json::json!(42)]).unwrap();
101        assert_eq!(channel.get().unwrap(), Some(serde_json::json!(42)));
102    }
103
104    #[test]
105    fn test_ephemeral_clear() {
106        let mut channel = EphemeralValue::<String>::new();
107        channel.update(vec![serde_json::json!("temporary")]).unwrap();
108        assert!(channel.get().unwrap().is_some());
109
110        channel.clear();
111        assert!(channel.get().unwrap().is_none());
112    }
113
114    #[test]
115    fn test_ephemeral_checkpoint() {
116        let mut channel = EphemeralValue::<i32>::new();
117        channel.update(vec![serde_json::json!(100)]).unwrap();
118
119        // Ephemeral values should not be saved in checkpoints
120        let checkpoint = channel.checkpoint().unwrap();
121        assert_eq!(checkpoint, serde_json::Value::Null);
122
123        // Restoring from checkpoint should give empty channel
124        let restored = EphemeralValue::<i32>::from_checkpoint(checkpoint).unwrap();
125        assert!(restored.get().unwrap().is_none());
126    }
127
128    #[test]
129    fn test_ephemeral_last_write_wins() {
130        let mut channel = EphemeralValue::<i32>::new();
131        channel
132            .update(vec![
133                serde_json::json!(1),
134                serde_json::json!(2),
135                serde_json::json!(3),
136            ])
137            .unwrap();
138
139        assert_eq!(channel.get().unwrap(), Some(serde_json::json!(3)));
140    }
141}