Skip to main content

lc_memory/
buffer.rs

1// lc-memory/src/buffer.rs
2//! Conversation Buffer Memory
3//!
4//! Simple conversation buffer memory that saves all conversation history.
5
6use async_trait::async_trait;
7use serde_json::Value;
8use std::collections::HashMap;
9
10use super::base::{BaseMemory, ChatMessageHistory, MemoryError};
11use lc_schema::Message;
12
13/// Conversation Buffer Memory
14///
15/// Saves all conversation history in memory.
16///
17/// # Example
18/// ```ignore
19/// use lc_memory::ConversationBufferMemory;
20///
21/// let mut memory = ConversationBufferMemory::new();
22///
23/// // Save conversation
24/// let inputs = HashMap::from([("input".to_string(), "Hello".to_string())]);
25/// let outputs = HashMap::from([("output".to_string(), "Hi!".to_string())]);
26/// memory.save_context(&inputs, &outputs).await?;
27///
28/// // Load memory
29/// let memory_vars = memory.load_memory_variables(&HashMap::new()).await?;
30/// println!("{:?}", memory_vars.get("history"));
31/// ```
32#[derive(Debug)]
33pub struct ConversationBufferMemory {
34    /// Chat history
35    chat_memory: ChatMessageHistory,
36
37    /// Input key name (default: "input")
38    input_key: String,
39
40    /// Output key name (default: "output")
41    output_key: String,
42
43    /// Memory variable name (default: "history")
44    memory_key: String,
45
46    /// Whether to return message objects
47    return_messages: bool,
48}
49
50impl ConversationBufferMemory {
51    /// Create new conversation buffer memory
52    pub fn new() -> Self {
53        Self {
54            chat_memory: ChatMessageHistory::new(),
55            input_key: "input".to_string(),
56            output_key: "output".to_string(),
57            memory_key: "history".to_string(),
58            return_messages: false,
59        }
60    }
61
62    /// Set input key name
63    pub fn with_input_key(mut self, key: String) -> Self {
64        self.input_key = key;
65        self
66    }
67
68    /// Set output key name
69    pub fn with_output_key(mut self, key: String) -> Self {
70        self.output_key = key;
71        self
72    }
73
74    /// Set memory variable name
75    pub fn with_memory_key(mut self, key: String) -> Self {
76        self.memory_key = key;
77        self
78    }
79
80    /// Set whether to return message objects
81    pub fn with_return_messages(mut self, return_messages: bool) -> Self {
82        self.return_messages = return_messages;
83        self
84    }
85
86    /// Create from existing history
87    pub fn from_chat_memory(chat_memory: ChatMessageHistory) -> Self {
88        Self {
89            chat_memory,
90            ..Self::new()
91        }
92    }
93
94    /// Get chat history
95    pub fn chat_memory(&self) -> &ChatMessageHistory {
96        &self.chat_memory
97    }
98
99    /// Get mutable chat history
100    pub fn chat_memory_mut(&mut self) -> &mut ChatMessageHistory {
101        &mut self.chat_memory
102    }
103
104    /// Convert history to string
105    fn buffer_as_string(&self) -> String {
106        self.chat_memory.to_string()
107    }
108
109    /// Convert history to message list
110    fn buffer_as_messages(&self) -> Vec<Message> {
111        self.chat_memory.messages().to_vec()
112    }
113}
114
115impl Default for ConversationBufferMemory {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121#[async_trait]
122impl BaseMemory for ConversationBufferMemory {
123    fn memory_variables(&self) -> Vec<&str> {
124        vec![&self.memory_key]
125    }
126
127    async fn load_memory_variables(
128        &self,
129        _inputs: &HashMap<String, String>,
130    ) -> Result<HashMap<String, Value>, MemoryError> {
131        let mut result = HashMap::new();
132
133        if self.return_messages {
134            // Return message list
135            let messages: Vec<Value> = self
136                .buffer_as_messages()
137                .into_iter()
138                .map(|msg| serde_json::to_value(&msg).unwrap_or(Value::Null))
139                .collect();
140            result.insert(self.memory_key.clone(), Value::Array(messages));
141        } else {
142            // Return string
143            result.insert(
144                self.memory_key.clone(),
145                Value::String(self.buffer_as_string()),
146            );
147        }
148
149        Ok(result)
150    }
151
152    async fn save_context(
153        &mut self,
154        inputs: &HashMap<String, String>,
155        outputs: &HashMap<String, String>,
156    ) -> Result<(), MemoryError> {
157        // M76: Return error when required keys are missing instead of silently skipping
158        let input = inputs.get(&self.input_key).ok_or_else(|| {
159            MemoryError::SaveError(format!("Missing input key '{}'", self.input_key))
160        })?;
161        self.chat_memory.add_user_message(input);
162
163        let output = outputs.get(&self.output_key).ok_or_else(|| {
164            MemoryError::SaveError(format!("Missing output key '{}'", self.output_key))
165        })?;
166        self.chat_memory.add_ai_message(output);
167
168        Ok(())
169    }
170
171    async fn clear(&mut self) -> Result<(), MemoryError> {
172        self.chat_memory.clear();
173        Ok(())
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[tokio::test]
182    async fn test_conversation_buffer_memory() {
183        let mut memory = ConversationBufferMemory::new();
184
185        // Save conversation
186        let inputs = HashMap::from([("input".to_string(), "Hello".to_string())]);
187        let outputs = HashMap::from([(
188            "output".to_string(),
189            "Hello! How can I help you?".to_string(),
190        )]);
191
192        memory.save_context(&inputs, &outputs).await.unwrap();
193
194        // Load memory
195        let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
196
197        assert!(memory_vars.contains_key("history"));
198        let history = memory_vars.get("history").unwrap();
199        assert!(history.as_str().unwrap().contains("Human: Hello"));
200        assert!(history.as_str().unwrap().contains("AI: Hello"));
201    }
202
203    #[tokio::test]
204    async fn test_conversation_buffer_memory_multiple() {
205        let mut memory = ConversationBufferMemory::new();
206
207        // First round
208        let inputs1 = HashMap::from([("input".to_string(), "My name is Zhang San".to_string())]);
209        let outputs1 = HashMap::from([("output".to_string(), "Hello Zhang San!".to_string())]);
210        memory.save_context(&inputs1, &outputs1).await.unwrap();
211
212        // Second round
213        let inputs2 = HashMap::from([("input".to_string(), "What is my name?".to_string())]);
214        let outputs2 =
215            HashMap::from([("output".to_string(), "Your name is Zhang San".to_string())]);
216        memory.save_context(&inputs2, &outputs2).await.unwrap();
217
218        // Check history
219        let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
220        let history = memory_vars.get("history").unwrap().as_str().unwrap();
221
222        assert!(history.contains("Zhang San"));
223        assert!(memory.chat_memory().len() == 4); // 2 rounds * 2 messages
224    }
225
226    #[tokio::test]
227    async fn test_conversation_buffer_memory_clear() {
228        let mut memory = ConversationBufferMemory::new();
229
230        // Save conversation
231        let inputs = HashMap::from([("input".to_string(), "test".to_string())]);
232        let outputs = HashMap::from([("output".to_string(), "received".to_string())]);
233        memory.save_context(&inputs, &outputs).await.unwrap();
234
235        assert_eq!(memory.chat_memory().len(), 2);
236
237        // Clear
238        memory.clear().await.unwrap();
239        assert_eq!(memory.chat_memory().len(), 0);
240    }
241
242    #[tokio::test]
243    async fn test_conversation_buffer_memory_return_messages() {
244        let mut memory = ConversationBufferMemory::new().with_return_messages(true);
245
246        let inputs = HashMap::from([("input".to_string(), "Hello".to_string())]);
247        let outputs = HashMap::from([("output".to_string(), "Hello!".to_string())]);
248        memory.save_context(&inputs, &outputs).await.unwrap();
249
250        let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
251        let history = memory_vars.get("history").unwrap();
252
253        // Should return message array
254        assert!(history.is_array());
255        let messages = history.as_array().unwrap();
256        assert_eq!(messages.len(), 2);
257    }
258}