Skip to main content

lc_langgraph/
state.rs

1// crates/lc-langgraph/src/state.rs
2//! State management for LangGraph
3//!
4//! This module provides the state abstraction for graph execution.
5//! States are data structures that flow through nodes in the graph.
6
7use crate::errors::{GraphError, GraphResult};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::fmt::Debug;
11
12/// State Schema trait
13pub trait StateSchema:
14    Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de> + Debug
15{
16    /// Create initial state from input
17    fn from_input(input: Self) -> Self {
18        input
19    }
20
21    /// Get state as JSON for debugging/checkpointing.
22    ///
23    /// Returns a `Result` instead of silently producing `Value::Null` (Q2):
24    /// a serialization failure is data corruption in the state machine and
25    /// must surface at the call site, not be swallowed.
26    fn to_json(&self) -> GraphResult<serde_json::Value> {
27        serde_json::to_value(self).map_err(|e| {
28            GraphError::StateError(format!("Failed to serialize state to JSON: {}", e))
29        })
30    }
31}
32
33/// State update representation
34///
35/// Nodes return StateUpdate which contains partial updates to the state.
36/// The reducer pattern determines how updates are merged into the full state.
37#[derive(Debug, Clone, Serialize)]
38pub struct StateUpdate<S: StateSchema> {
39    /// Full or partial state update
40    pub update: Option<S>,
41
42    /// Additional metadata (for debugging/tracing)
43    pub metadata: HashMap<String, serde_json::Value>,
44}
45
46impl<S: StateSchema> StateUpdate<S> {
47    /// Create a full state update
48    pub fn full(state: S) -> Self {
49        Self {
50            update: Some(state),
51            metadata: HashMap::new(),
52        }
53    }
54
55    /// Create update with metadata
56    pub fn with_metadata(state: S, metadata: HashMap<String, serde_json::Value>) -> Self {
57        Self {
58            update: Some(state),
59            metadata,
60        }
61    }
62
63    /// Create a no-change update (for nodes that don't modify state)
64    pub fn unchanged() -> Self {
65        Self {
66            update: None,
67            metadata: HashMap::new(),
68        }
69    }
70
71    /// Add metadata entry
72    pub fn add_metadata(&mut self, key: impl Into<String>, value: serde_json::Value) {
73        self.metadata.insert(key.into(), value);
74    }
75}
76
77/// Reducer trait for merging state updates
78///
79/// Reducers define how state updates are merged into the current state.
80/// This enables patterns like `add_messages` which appends rather than replaces.
81pub trait Reducer<S: StateSchema>: Send + Sync {
82    /// Reduce current state with an update
83    fn reduce(&self, current: &S, update: &S) -> S;
84}
85
86/// Default reducer that replaces state entirely
87pub struct ReplaceReducer;
88
89impl<S: StateSchema> Reducer<S> for ReplaceReducer {
90    fn reduce(&self, _current: &S, update: &S) -> S {
91        update.clone()
92    }
93}
94
95/// Append reducer for vector fields (like add_messages pattern)
96///
97/// This reducer appends new items to vector fields in the state.
98/// Useful for message history, steps history, etc.
99pub struct AppendReducer<S: StateSchema, T: Clone + Send + Sync> {
100    /// Function that reads the vector field from a state.
101    pub field_accessor: fn(&S) -> &[T],
102    /// Function that writes the merged vector back into a state.
103    pub field_mutator: fn(&mut S, Vec<T>),
104}
105
106impl<S: StateSchema, T: Clone + Send + Sync> Reducer<S> for AppendReducer<S, T> {
107    fn reduce(&self, current: &S, update: &S) -> S {
108        let current_items = (self.field_accessor)(current);
109        let update_items = (self.field_accessor)(update);
110
111        let mut merged: Vec<T> = current_items.to_vec();
112        merged.extend(update_items.iter().cloned());
113
114        let mut result = current.clone();
115        (self.field_mutator)(&mut result, merged);
116        result
117    }
118}
119
120/// AgentState Messages Reducer - Appends messages instead of replacing
121pub struct AppendMessagesReducer;
122
123impl Reducer<AgentState> for AppendMessagesReducer {
124    fn reduce(&self, current: &AgentState, update: &AgentState) -> AgentState {
125        let mut result = update.clone();
126        result.messages = current.messages.clone();
127        result.messages.extend(update.messages.iter().cloned());
128        result
129    }
130}
131
132/// AgentState Steps Reducer - Appends steps instead of replacing
133pub struct AppendStepsReducer;
134
135impl Reducer<AgentState> for AppendStepsReducer {
136    fn reduce(&self, current: &AgentState, update: &AgentState) -> AgentState {
137        let mut result = update.clone();
138        result.steps = current.steps.clone();
139        result.steps.extend(update.steps.iter().cloned());
140        result
141    }
142}
143
144/// Common state with messages (agent-style)
145///
146/// This provides a pre-built state schema for agent-style graphs
147/// that track messages through the execution.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct AgentState {
150    /// Input query
151    pub input: String,
152
153    /// Chat messages history
154    pub messages: Vec<MessageEntry>,
155
156    /// Intermediate steps
157    pub steps: Vec<StepEntry>,
158
159    /// Output result
160    pub output: Option<String>,
161}
162
163impl StateSchema for AgentState {}
164
165impl AgentState {
166    /// Create new agent state with input
167    pub fn new(input: impl Into<String>) -> Self {
168        let input = input.into();
169        let msg = MessageEntry::human(input.clone());
170        Self {
171            input,
172            messages: vec![msg],
173            steps: vec![],
174            output: None,
175        }
176    }
177
178    /// Add a message to history
179    pub fn add_message(&mut self, message: MessageEntry) {
180        self.messages.push(message);
181    }
182
183    /// Add a step to history
184    pub fn add_step(&mut self, step: StepEntry) {
185        self.steps.push(step);
186    }
187
188    /// Set output
189    pub fn set_output(&mut self, output: impl Into<String>) {
190        self.output = Some(output.into());
191    }
192}
193
194/// Message entry for agent state
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct MessageEntry {
197    /// Role of the message sender.
198    pub role: MessageRole,
199    /// Text content of the message.
200    pub content: String,
201}
202
203impl MessageEntry {
204    /// Create a human message entry.
205    pub fn human(content: impl Into<String>) -> Self {
206        Self {
207            role: MessageRole::Human,
208            content: content.into(),
209        }
210    }
211
212    /// Create an AI message entry.
213    pub fn ai(content: impl Into<String>) -> Self {
214        Self {
215            role: MessageRole::AI,
216            content: content.into(),
217        }
218    }
219
220    /// Create a system message entry.
221    pub fn system(content: impl Into<String>) -> Self {
222        Self {
223            role: MessageRole::System,
224            content: content.into(),
225        }
226    }
227
228    /// Create a tool message entry.
229    pub fn tool(content: impl Into<String>) -> Self {
230        Self {
231            role: MessageRole::Tool,
232            content: content.into(),
233        }
234    }
235}
236
237/// Message role types
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
239pub enum MessageRole {
240    /// System message.
241    System,
242    /// Human message.
243    Human,
244    /// AI assistant message.
245    AI,
246    /// Tool result message.
247    Tool,
248}
249
250/// Step entry for intermediate execution steps
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct StepEntry {
253    /// Action that was taken.
254    pub action: String,
255    /// Observation or result of the action.
256    pub observation: String,
257}
258
259impl StepEntry {
260    /// Create a new step entry.
261    pub fn new(action: impl Into<String>, observation: impl Into<String>) -> Self {
262        Self {
263            action: action.into(),
264            observation: observation.into(),
265        }
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_append_messages_reducer() {
275        let mut current = AgentState::new("Hello".to_string());
276        current.add_message(MessageEntry::ai("Response 1".to_string()));
277
278        let mut update = AgentState::new("Hello".to_string());
279        update.add_message(MessageEntry::ai("Response 2".to_string()));
280        update.set_output("Done".to_string());
281
282        let reducer = AppendMessagesReducer;
283        let result = reducer.reduce(&current, &update);
284
285        assert_eq!(result.messages.len(), 4);
286        assert_eq!(result.output, Some("Done".to_string()));
287    }
288
289    #[test]
290    fn test_append_steps_reducer() {
291        let mut current = AgentState::new("Test".to_string());
292        current.add_step(StepEntry::new(
293            "Action 1".to_string(),
294            "Result 1".to_string(),
295        ));
296
297        let mut update = AgentState::new("Test".to_string());
298        update.add_step(StepEntry::new(
299            "Action 2".to_string(),
300            "Result 2".to_string(),
301        ));
302
303        let reducer = AppendStepsReducer;
304        let result = reducer.reduce(&current, &update);
305
306        assert_eq!(result.steps.len(), 2);
307    }
308}