langchain_rust/memory/
token_buffer.rs1use std::collections::VecDeque;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use tiktoken_rs::{bpe_for_model, cl100k_base_singleton};
6use tokio::sync::Mutex;
7
8use crate::schemas::{
9 memory::{BaseMemory, MemoryError},
10 messages::{Message, MessageType},
11};
12
13type TokenCounter = Arc<dyn Fn(&str) -> usize + Send + Sync>;
14
15pub struct TokenBufferMemory {
29 max_tokens: usize,
30 counter: TokenCounter,
31 messages: VecDeque<(Message, usize)>,
32 total_tokens: usize,
33}
34
35impl TokenBufferMemory {
36 pub fn new(max_tokens: usize) -> Self {
37 Self {
38 max_tokens,
39 counter: Arc::new(|text: &str| cl100k_base_singleton().encode_ordinary(text).len()),
40 messages: VecDeque::new(),
41 total_tokens: 0,
42 }
43 }
44
45 pub fn with_model(mut self, model: &str) -> Result<Self, MemoryError> {
47 let bpe = bpe_for_model(model).map_err(|e| MemoryError::Other(e.to_string()))?;
48 self.counter = Arc::new(move |text: &str| bpe.encode_ordinary(text).len());
49 Ok(self)
50 }
51
52 pub fn with_token_counter<F>(mut self, counter: F) -> Self
54 where
55 F: Fn(&str) -> usize + Send + Sync + 'static,
56 {
57 self.counter = Arc::new(counter);
58 self
59 }
60
61 pub fn total_tokens(&self) -> usize {
63 self.total_tokens
64 }
65
66 fn count(&self, message: &Message) -> usize {
67 let mut tokens = (self.counter)(&message.content);
68 if let Some(tool_calls) = &message.tool_calls {
69 tokens += (self.counter)(&tool_calls.to_string());
70 }
71 tokens
72 }
73
74 fn pop_front(&mut self) {
75 if let Some((_, tokens)) = self.messages.pop_front() {
76 self.total_tokens -= tokens;
77 }
78 }
79
80 fn prune(&mut self) {
81 while self.total_tokens > self.max_tokens && !self.messages.is_empty() {
82 self.pop_front();
83 }
84 while matches!(
85 self.messages.front(),
86 Some((m, _)) if m.message_type == MessageType::ToolMessage
87 ) {
88 self.pop_front();
89 }
90 }
91}
92
93impl From<TokenBufferMemory> for Arc<dyn BaseMemory> {
94 fn from(memory: TokenBufferMemory) -> Self {
95 Arc::new(memory)
96 }
97}
98
99impl From<TokenBufferMemory> for Arc<Mutex<dyn BaseMemory>> {
100 fn from(memory: TokenBufferMemory) -> Self {
101 Arc::new(Mutex::new(memory))
102 }
103}
104
105#[async_trait]
106impl BaseMemory for TokenBufferMemory {
107 async fn messages(&self) -> Result<Vec<Message>, MemoryError> {
108 Ok(self.messages.iter().map(|(m, _)| m.clone()).collect())
109 }
110
111 async fn add_message(&mut self, message: Message) -> Result<(), MemoryError> {
112 let tokens = self.count(&message);
113 self.total_tokens += tokens;
114 self.messages.push_back((message, tokens));
115 self.prune();
116 Ok(())
117 }
118
119 async fn clear(&mut self) -> Result<(), MemoryError> {
120 self.messages.clear();
121 self.total_tokens = 0;
122 Ok(())
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use serde_json::json;
130
131 fn words(max_tokens: usize) -> TokenBufferMemory {
133 TokenBufferMemory::new(max_tokens).with_token_counter(|t| t.split_whitespace().count())
134 }
135
136 async fn contents(memory: &TokenBufferMemory) -> Vec<String> {
137 memory
138 .messages()
139 .await
140 .unwrap()
141 .into_iter()
142 .map(|m| m.content)
143 .collect()
144 }
145
146 #[tokio::test]
147 async fn keeps_everything_under_the_limit() {
148 let mut memory = words(10);
149 memory.add_user_message(&"one two three").await.unwrap();
150 memory.add_ai_message(&"four five").await.unwrap();
151 assert_eq!(memory.total_tokens(), 5);
152 assert_eq!(contents(&memory).await, vec!["one two three", "four five"]);
153 }
154
155 #[tokio::test]
156 async fn drops_oldest_messages_when_over_the_limit() {
157 let mut memory = words(5);
158 memory.add_user_message(&"a b").await.unwrap();
159 memory.add_ai_message(&"c d").await.unwrap();
160 memory.add_user_message(&"e f").await.unwrap(); assert_eq!(contents(&memory).await, vec!["c d", "e f"]);
162 assert_eq!(memory.total_tokens(), 4);
163
164 memory.add_ai_message(&"g h i j").await.unwrap(); assert_eq!(contents(&memory).await, vec!["g h i j"]);
166 assert_eq!(memory.total_tokens(), 4);
167 }
168
169 #[tokio::test]
170 async fn message_bigger_than_limit_is_not_kept() {
171 let mut memory = words(3);
172 memory.add_user_message(&"short").await.unwrap();
173 memory
174 .add_ai_message(&"this one is way too long")
175 .await
176 .unwrap();
177 assert!(contents(&memory).await.is_empty());
178 assert_eq!(memory.total_tokens(), 0);
179 }
180
181 #[tokio::test]
182 async fn tool_calls_count_and_orphan_tool_results_are_dropped() {
183 let mut memory = words(6);
184 memory
185 .add_message(
186 Message::new_ai_message("").with_tool_calls(json!([{"id": "call_1", "a": 1}])),
187 )
188 .await
189 .unwrap();
190 assert!(memory.total_tokens() > 0, "tool calls must be counted");
191 memory
192 .add_message(Message::new_tool_message("r1 r2", "call_1"))
193 .await
194 .unwrap();
195 memory.add_ai_message(&"x y z w").await.unwrap();
196
197 let messages = memory.messages().await.unwrap();
199 assert_eq!(messages.len(), 1);
200 assert_eq!(messages[0].message_type, MessageType::AIMessage);
201 assert_eq!(messages[0].content, "x y z w");
202 assert_eq!(memory.total_tokens(), 4);
203 }
204
205 #[tokio::test]
206 async fn clear_resets_tokens() {
207 let mut memory = words(10);
208 memory.add_user_message(&"a b c").await.unwrap();
209 memory.clear().await.unwrap();
210 assert_eq!(memory.total_tokens(), 0);
211 assert!(contents(&memory).await.is_empty());
212 }
213
214 #[tokio::test]
215 async fn default_and_model_tokenizers_count_real_tokens() {
216 let mut memory = TokenBufferMemory::new(1000);
217 memory.add_user_message(&"hello world").await.unwrap();
218 assert_eq!(memory.total_tokens(), 2);
219
220 let mut memory = TokenBufferMemory::new(1000).with_model("gpt-4o").unwrap();
221 memory.add_user_message(&"hello world").await.unwrap();
222 assert_eq!(memory.total_tokens(), 2);
223
224 assert!(TokenBufferMemory::new(10)
225 .with_model("not-a-model")
226 .is_err());
227 }
228
229 #[tokio::test]
230 async fn works_behind_the_shared_mutex() {
231 let memory: Arc<Mutex<dyn BaseMemory>> = words(2).into();
232 memory.lock().await.add_user_message(&"a b").await.unwrap();
233 memory.lock().await.add_ai_message(&"c").await.unwrap();
234 assert_eq!(memory.lock().await.to_string().await.unwrap(), "ai: c");
235 }
236}