1use 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#[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: impl Into<String>) -> Self {
63 self.input_key = key.into();
64 self
65 }
66
67 pub fn with_output_key(mut self, key: impl Into<String>) -> Self {
69 self.output_key = key.into();
70 self
71 }
72
73 pub fn with_memory_key(mut self, key: impl Into<String>) -> Self {
75 self.memory_key = key.into();
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
136impl BaseChatMemory for ConversationBufferWindowMemory {
138 fn messages(&self) -> &[Message] {
139 self.chat_memory.messages()
140 }
141
142 fn add_message(&mut self, message: Message) {
143 self.chat_memory.add_message(message);
144 }
145}
146
147#[async_trait]
148impl BaseMemory for ConversationBufferWindowMemory {
149 fn memory_variables(&self) -> Vec<&str> {
150 vec![&self.memory_key]
151 }
152
153 async fn load_memory_variables(
154 &self,
155 _inputs: &HashMap<String, String>,
156 ) -> Result<HashMap<String, Value>, MemoryError> {
157 let mut result = HashMap::new();
158
159 if self.return_messages {
160 let messages: Vec<Value> = self
161 .get_window_messages()
162 .into_iter()
163 .map(|msg| serde_json::to_value(&msg).unwrap_or(Value::Null))
164 .collect();
165 result.insert(self.memory_key.clone(), Value::Array(messages));
166 } else {
167 result.insert(
168 self.memory_key.clone(),
169 Value::String(self.buffer_as_string()),
170 );
171 }
172
173 Ok(result)
174 }
175
176 async fn save_context(
177 &mut self,
178 inputs: &HashMap<String, String>,
179 outputs: &HashMap<String, String>,
180 ) -> Result<(), MemoryError> {
181 let input = inputs.get(&self.input_key).ok_or_else(|| {
183 MemoryError::SaveError(format!("Missing input key '{}'", self.input_key))
184 })?;
185 self.chat_memory.add_user_message(input);
186
187 let output = outputs.get(&self.output_key).ok_or_else(|| {
188 MemoryError::SaveError(format!("Missing output key '{}'", self.output_key))
189 })?;
190 self.chat_memory.add_ai_message(output);
191
192 Ok(())
193 }
194
195 async fn clear(&mut self) -> Result<(), MemoryError> {
196 self.chat_memory.clear();
197 Ok(())
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[tokio::test]
206 async fn test_window_memory() {
207 let mut memory = ConversationBufferWindowMemory::new(2);
208
209 for i in 1..=3 {
211 let inputs = HashMap::from([("input".to_string(), format!("Question{}", i))]);
212 let outputs = HashMap::from([("output".to_string(), format!("Answer{}", i))]);
213 memory.save_context(&inputs, &outputs).await.unwrap();
214 }
215
216 assert_eq!(memory.chat_memory().len(), 6);
218
219 let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
221 let history = memory_vars.get("history").unwrap().as_str().unwrap();
222
223 assert!(!history.contains("Question1"));
225 assert!(!history.contains("Answer1"));
226 assert!(history.contains("Question2"));
227 assert!(history.contains("Answer3"));
228 }
229
230 #[tokio::test]
231 async fn test_window_memory_smaller_than_k() {
232 let mut memory = ConversationBufferWindowMemory::new(5);
233
234 for i in 1..=2 {
236 let inputs = HashMap::from([("input".to_string(), format!("Question{}", i))]);
237 let outputs = HashMap::from([("output".to_string(), format!("Answer{}", i))]);
238 memory.save_context(&inputs, &outputs).await.unwrap();
239 }
240
241 let memory_vars = memory.load_memory_variables(&HashMap::new()).await.unwrap();
243 let history = memory_vars.get("history").unwrap().as_str().unwrap();
244
245 assert!(history.contains("Question1"));
246 assert!(history.contains("Question2"));
247 }
248
249 #[tokio::test]
250 async fn test_window_memory_clear() {
251 let mut memory = ConversationBufferWindowMemory::new(2);
252
253 let inputs = HashMap::from([("input".to_string(), "test".to_string())]);
254 let outputs = HashMap::from([("output".to_string(), "received".to_string())]);
255 memory.save_context(&inputs, &outputs).await.unwrap();
256
257 assert_eq!(memory.chat_memory().len(), 2);
258
259 memory.clear().await.unwrap();
260 assert_eq!(memory.chat_memory().len(), 0);
261 }
262}