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