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::{BaseChatMemory, 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: impl Into<String>) -> Self {
64        self.input_key = key.into();
65        self
66    }
67
68    /// Set output key name
69    pub fn with_output_key(mut self, key: impl Into<String>) -> Self {
70        self.output_key = key.into();
71        self
72    }
73
74    /// Set memory variable name
75    pub fn with_memory_key(mut self, key: impl Into<String>) -> Self {
76        self.memory_key = key.into();
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/// P0-1: `ConversationBufferMemory` 实现 `BaseChatMemory`,可当聊天缓冲用。
122impl BaseChatMemory for ConversationBufferMemory {
123    fn messages(&self) -> &[Message] {
124        self.chat_memory.messages()
125    }
126
127    fn add_message(&mut self, message: Message) {
128        self.chat_memory.add_message(message);
129    }
130}
131
132#[async_trait]
133impl BaseMemory for ConversationBufferMemory {
134    fn memory_variables(&self) -> Vec<&str> {
135        vec![&self.memory_key]
136    }
137
138    async fn load_memory_variables(
139        &self,
140        _inputs: &HashMap<String, String>,
141    ) -> Result<HashMap<String, Value>, MemoryError> {
142        let mut result = HashMap::new();
143
144        if self.return_messages {
145            // Return message list
146            let messages: Vec<Value> = self
147                .buffer_as_messages()
148                .into_iter()
149                .map(|msg| serde_json::to_value(&msg).unwrap_or(Value::Null))
150                .collect();
151            result.insert(self.memory_key.clone(), Value::Array(messages));
152        } else {
153            // Return string
154            result.insert(
155                self.memory_key.clone(),
156                Value::String(self.buffer_as_string()),
157            );
158        }
159
160        Ok(result)
161    }
162
163    async fn save_context(
164        &mut self,
165        inputs: &HashMap<String, String>,
166        outputs: &HashMap<String, String>,
167    ) -> Result<(), MemoryError> {
168        // M76: Return error when required keys are missing instead of silently skipping
169        let input = inputs.get(&self.input_key).ok_or_else(|| {
170            MemoryError::SaveError(format!("Missing input key '{}'", self.input_key))
171        })?;
172        self.chat_memory.add_user_message(input);
173
174        let output = outputs.get(&self.output_key).ok_or_else(|| {
175            MemoryError::SaveError(format!("Missing output key '{}'", self.output_key))
176        })?;
177        self.chat_memory.add_ai_message(output);
178
179        Ok(())
180    }
181
182    async fn clear(&mut self) -> Result<(), MemoryError> {
183        self.chat_memory.clear();
184        Ok(())
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[tokio::test]
193    async fn test_conversation_buffer_memory() {
194        let mut memory = ConversationBufferMemory::new();
195
196        // Save conversation
197        let inputs = HashMap::from([("input".to_string(), "Hello".to_string())]);
198        let outputs = HashMap::from([(
199            "output".to_string(),
200            "Hello! How can I help you?".to_string(),
201        )]);
202
203        memory.save_context(&inputs, &outputs).await.unwrap();
204
205        // Load memory
206        let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
207
208        assert!(memory_vars.contains_key("history"));
209        let history = memory_vars.get("history").unwrap();
210        assert!(history.as_str().unwrap().contains("Human: Hello"));
211        assert!(history.as_str().unwrap().contains("AI: Hello"));
212    }
213
214    #[tokio::test]
215    async fn test_conversation_buffer_memory_multiple() {
216        let mut memory = ConversationBufferMemory::new();
217
218        // First round
219        let inputs1 = HashMap::from([("input".to_string(), "My name is Zhang San".to_string())]);
220        let outputs1 = HashMap::from([("output".to_string(), "Hello Zhang San!".to_string())]);
221        memory.save_context(&inputs1, &outputs1).await.unwrap();
222
223        // Second round
224        let inputs2 = HashMap::from([("input".to_string(), "What is my name?".to_string())]);
225        let outputs2 =
226            HashMap::from([("output".to_string(), "Your name is Zhang San".to_string())]);
227        memory.save_context(&inputs2, &outputs2).await.unwrap();
228
229        // Check history
230        let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
231        let history = memory_vars.get("history").unwrap().as_str().unwrap();
232
233        assert!(history.contains("Zhang San"));
234        assert!(memory.chat_memory().len() == 4); // 2 rounds * 2 messages
235    }
236
237    #[tokio::test]
238    async fn test_conversation_buffer_memory_clear() {
239        let mut memory = ConversationBufferMemory::new();
240
241        // Save conversation
242        let inputs = HashMap::from([("input".to_string(), "test".to_string())]);
243        let outputs = HashMap::from([("output".to_string(), "received".to_string())]);
244        memory.save_context(&inputs, &outputs).await.unwrap();
245
246        assert_eq!(memory.chat_memory().len(), 2);
247
248        // Clear
249        memory.clear().await.unwrap();
250        assert_eq!(memory.chat_memory().len(), 0);
251    }
252
253    /// P0-1: 验证四种 Memory 可实现 `BaseChatMemory`,从而支持
254    /// 泛型记忆代码 `fn f<T: BaseChatMemory>(m: &T)` 与 trait 对象。
255    #[tokio::test]
256    async fn test_base_chat_memory_generic_function() {
257        use crate::window::ConversationBufferWindowMemory;
258
259        // 泛型函数:对任意 BaseChatMemory 实现读取消息数
260        fn count_messages<T: BaseChatMemory>(memory: &T) -> usize {
261            memory.messages().len()
262        }
263
264        // Buffer 实现 BaseChatMemory
265        let mut buffer = ConversationBufferMemory::new();
266        buffer.add_user_message("Hello");
267        buffer.add_ai_message("Hi!");
268        assert_eq!(count_messages(&buffer), 2);
269
270        // Window 实现 BaseChatMemory
271        let mut window = ConversationBufferWindowMemory::new(2);
272        window.add_user_message("Q1");
273        window.add_ai_message("A1");
274        assert_eq!(count_messages(&window), 2);
275
276        // trait 对象(多态分发)
277        let mut dyn_mem: Box<dyn BaseChatMemory> = Box::new(ConversationBufferWindowMemory::new(2));
278        dyn_mem.add_user_message("q");
279        dyn_mem.add_ai_message("a");
280        assert_eq!(dyn_mem.messages().len(), 2);
281    }
282
283    #[tokio::test]
284    async fn test_conversation_buffer_memory_return_messages() {
285        let mut memory = ConversationBufferMemory::new().with_return_messages(true);
286
287        let inputs = HashMap::from([("input".to_string(), "Hello".to_string())]);
288        let outputs = HashMap::from([("output".to_string(), "Hello!".to_string())]);
289        memory.save_context(&inputs, &outputs).await.unwrap();
290
291        let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
292        let history = memory_vars.get("history").unwrap();
293
294        // Should return message array
295        assert!(history.is_array());
296        let messages = history.as_array().unwrap();
297        assert_eq!(messages.len(), 2);
298    }
299}