1use 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#[derive(Debug)]
25pub struct ConversationBufferWindowMemory {
26 chat_memory: ChatMessageHistory,
28
29 k: usize,
31
32 input_key: String,
34
35 output_key: String,
37
38 memory_key: String,
40
41 return_messages: bool,
43}
44
45impl ConversationBufferWindowMemory {
46 pub fn new(k: usize) -> Self {
51 Self {
52 chat_memory: ChatMessageHistory::new(),
53 k,
54 input_key: "input".to_string(),
55 output_key: "output".to_string(),
56 memory_key: "history".to_string(),
57 return_messages: false,
58 }
59 }
60
61 pub fn with_input_key(mut self, key: String) -> Self {
63 self.input_key = key;
64 self
65 }
66
67 pub fn with_output_key(mut self, key: String) -> Self {
69 self.output_key = key;
70 self
71 }
72
73 pub fn with_memory_key(mut self, key: String) -> Self {
75 self.memory_key = key;
76 self
77 }
78
79 pub fn with_return_messages(mut self, return_messages: bool) -> Self {
81 self.return_messages = return_messages;
82 self
83 }
84
85 pub fn chat_memory(&self) -> &ChatMessageHistory {
87 &self.chat_memory
88 }
89
90 pub fn k(&self) -> usize {
92 self.k
93 }
94
95 fn get_window_messages(&self) -> Vec<Message> {
99 let messages = self.chat_memory.messages();
100 let total = messages.len();
101
102 let max_messages = self.k * 2;
104
105 if total <= max_messages {
106 messages.to_vec()
107 } else {
108 messages[total - max_messages..].to_vec()
109 }
110 }
111
112 fn buffer_as_string(&self) -> String {
114 self.get_window_messages()
115 .iter()
116 .map(|msg| {
117 let role = match msg.message_type {
118 lc_schema::MessageType::Human => "Human",
119 lc_schema::MessageType::AI => "AI",
120 lc_schema::MessageType::System => "System",
121 lc_schema::MessageType::Tool { .. } => "Tool",
122 };
123 format!("{}: {}", role, msg.content)
124 })
125 .collect::<Vec<_>>()
126 .join("\n")
127 }
128}
129
130impl Default for ConversationBufferWindowMemory {
131 fn default() -> Self {
132 Self::new(5)
133 }
134}
135
136#[async_trait]
137impl BaseMemory for ConversationBufferWindowMemory {
138 fn memory_variables(&self) -> Vec<&str> {
139 vec![&self.memory_key]
140 }
141
142 async fn load_memory_variables(
143 &self,
144 _inputs: &HashMap<String, String>,
145 ) -> Result<HashMap<String, Value>, MemoryError> {
146 let mut result = HashMap::new();
147
148 if self.return_messages {
149 let messages: Vec<Value> = self
150 .get_window_messages()
151 .into_iter()
152 .map(|msg| serde_json::to_value(&msg).unwrap_or(Value::Null))
153 .collect();
154 result.insert(self.memory_key.clone(), Value::Array(messages));
155 } else {
156 result.insert(
157 self.memory_key.clone(),
158 Value::String(self.buffer_as_string()),
159 );
160 }
161
162 Ok(result)
163 }
164
165 async fn save_context(
166 &mut self,
167 inputs: &HashMap<String, String>,
168 outputs: &HashMap<String, String>,
169 ) -> Result<(), MemoryError> {
170 let input = inputs.get(&self.input_key).ok_or_else(|| {
172 MemoryError::SaveError(format!("Missing input key '{}'", self.input_key))
173 })?;
174 self.chat_memory.add_user_message(input);
175
176 let output = outputs.get(&self.output_key).ok_or_else(|| {
177 MemoryError::SaveError(format!("Missing output key '{}'", self.output_key))
178 })?;
179 self.chat_memory.add_ai_message(output);
180
181 Ok(())
182 }
183
184 async fn clear(&mut self) -> Result<(), MemoryError> {
185 self.chat_memory.clear();
186 Ok(())
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[tokio::test]
195 async fn test_window_memory() {
196 let mut memory = ConversationBufferWindowMemory::new(2);
197
198 for i in 1..=3 {
200 let inputs = HashMap::from([("input".to_string(), format!("Question{}", i))]);
201 let outputs = HashMap::from([("output".to_string(), format!("Answer{}", i))]);
202 memory.save_context(&inputs, &outputs).await.unwrap();
203 }
204
205 assert_eq!(memory.chat_memory().len(), 6);
207
208 let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
210 let history = memory_vars.get("history").unwrap().as_str().unwrap();
211
212 assert!(!history.contains("Question1"));
214 assert!(!history.contains("Answer1"));
215 assert!(history.contains("Question2"));
216 assert!(history.contains("Answer3"));
217 }
218
219 #[tokio::test]
220 async fn test_window_memory_smaller_than_k() {
221 let mut memory = ConversationBufferWindowMemory::new(5);
222
223 for i in 1..=2 {
225 let inputs = HashMap::from([("input".to_string(), format!("Question{}", i))]);
226 let outputs = HashMap::from([("output".to_string(), format!("Answer{}", i))]);
227 memory.save_context(&inputs, &outputs).await.unwrap();
228 }
229
230 let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
232 let history = memory_vars.get("history").unwrap().as_str().unwrap();
233
234 assert!(history.contains("Question1"));
235 assert!(history.contains("Question2"));
236 }
237
238 #[tokio::test]
239 async fn test_window_memory_clear() {
240 let mut memory = ConversationBufferWindowMemory::new(2);
241
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 memory.clear().await.unwrap();
249 assert_eq!(memory.chat_memory().len(), 0);
250 }
251}