lc_memory/context_window/
manager.rs1use std::sync::Arc;
5
6use lc_core::language_models::BaseChatModel;
7use lc_core::token_counter::{TiktokenCounter, TokenCounter};
8use lc_schema::Message;
9
10use crate::base::MemoryError;
11
12use super::trimmer::Strategy;
13
14pub struct ContextWindow<M: BaseChatModel> {
19 max_tokens: usize,
21 counter: Arc<dyn TokenCounter>,
23 strategy: Strategy<M>,
25}
26
27impl<M: BaseChatModel> ContextWindow<M> {
28 pub fn new(max_tokens: usize) -> Self
30 where
31 TiktokenCounter: TokenCounter,
32 {
33 Self {
34 max_tokens,
35 counter: Arc::new(TiktokenCounter::new().expect("tiktoken cl100k_base load failed")),
36 strategy: Strategy::Truncate,
37 }
38 }
39
40 pub fn with_strategy(max_tokens: usize, strategy: Strategy<M>) -> Self
42 where
43 TiktokenCounter: TokenCounter,
44 {
45 Self {
46 max_tokens,
47 counter: Arc::new(TiktokenCounter::new().expect("tiktoken cl100k_base load failed")),
48 strategy,
49 }
50 }
51
52 pub fn with_max_tokens(max_tokens: usize) -> Self
54 where
55 TiktokenCounter: TokenCounter,
56 {
57 Self::new(max_tokens)
58 }
59
60 pub fn with_counter(mut self, counter: Arc<dyn TokenCounter>) -> Self {
62 self.counter = counter;
63 self
64 }
65
66 pub fn max_tokens(&self) -> usize {
68 self.max_tokens
69 }
70
71 pub async fn fit(&self, messages: Vec<Message>) -> Result<Vec<Message>, MemoryError> {
82 let total_tokens = self.counter.count_messages(&messages) as usize;
83
84 if total_tokens <= self.max_tokens {
85 return Ok(messages);
86 }
87
88 match &self.strategy {
89 Strategy::Truncate => self.truncate(messages),
90 Strategy::Summarize {
91 llm,
92 summary_prompt,
93 } => self.summarize(messages, llm, summary_prompt).await,
94 }
95 }
96
97 fn truncate(&self, messages: Vec<Message>) -> Result<Vec<Message>, MemoryError> {
106 let mut system_messages: Vec<Message> = Vec::new();
108 let mut other_messages: Vec<Message> = Vec::new();
109
110 for msg in messages {
111 if matches!(msg.message_type, lc_schema::MessageType::System) {
112 system_messages.push(msg);
113 } else {
114 other_messages.push(msg);
115 }
116 }
117
118 let base_tokens = self.counter.count_messages(&system_messages) as usize;
121
122 let msg_incremental_costs: Vec<usize> = other_messages
127 .iter()
128 .map(|m| {
129 let single_count = self.counter.count_messages(std::slice::from_ref(m)) as usize;
130 single_count.saturating_sub(2)
132 })
133 .collect();
134
135 let mut kept: Vec<Message> = Vec::new();
137 let mut running_tokens = base_tokens;
138
139 for (msg, cost) in other_messages
140 .into_iter()
141 .rev()
142 .zip(msg_incremental_costs.into_iter().rev())
143 {
144 if running_tokens + cost <= self.max_tokens {
145 running_tokens += cost;
146 kept.push(msg);
147 } else {
148 break;
150 }
151 }
152
153 kept.reverse();
154
155 let mut result = system_messages;
156 result.extend(kept);
157 Ok(result)
158 }
159
160 async fn summarize(
166 &self,
167 messages: Vec<Message>,
168 llm: &Arc<M>,
169 summary_prompt: &str,
170 ) -> Result<Vec<Message>, MemoryError> {
171 let mut system_messages: Vec<Message> = Vec::new();
173 let mut other_messages: Vec<Message> = Vec::new();
174
175 for msg in messages {
176 if matches!(msg.message_type, lc_schema::MessageType::System) {
177 system_messages.push(msg);
178 } else {
179 other_messages.push(msg);
180 }
181 }
182
183 if other_messages.is_empty() {
184 return Ok(system_messages);
186 }
187
188 let mut keep_from_idx = other_messages.len(); for i in 0..other_messages.len() {
196 let recent = &other_messages[i..];
197 let mut candidate = system_messages.clone();
198 candidate.push(Message::system("summary placeholder"));
200 candidate.extend(recent.iter().cloned());
201
202 let tokens = self.counter.count_messages(&candidate) as usize;
203 if tokens <= self.max_tokens {
204 keep_from_idx = i;
205 break;
206 }
207 }
208
209 if keep_from_idx >= other_messages.len() {
212 return self.truncate(system_messages);
214 }
215
216 let to_summarize = &other_messages[..keep_from_idx];
217 let to_keep = &other_messages[keep_from_idx..];
218
219 if to_summarize.is_empty() {
220 let mut result = system_messages;
222 result.extend(to_keep.to_vec());
223 return Ok(result);
224 }
225
226 let conversation_text = to_summarize
228 .iter()
229 .map(|msg| {
230 let role = match msg.message_type {
231 lc_schema::MessageType::Human => "Human",
232 lc_schema::MessageType::AI => "AI",
233 lc_schema::MessageType::System => "System",
234 lc_schema::MessageType::Tool { .. } => "Tool",
235 };
236 format!("{}: {}", role, msg.content)
237 })
238 .collect::<Vec<_>>()
239 .join("\n");
240
241 let prompt = summary_prompt.replace("{conversation}", &conversation_text);
242
243 let summary_messages = vec![Message::human(&prompt)];
244
245 let result = llm
246 .invoke(summary_messages, None)
247 .await
248 .map_err(|e| MemoryError::SaveError(format!("LLM summarization failed: {}", e)))?;
249
250 let summary_message = Message::system(format!("[Conversation Summary] {}", result.content));
251
252 let mut final_messages = system_messages;
254 final_messages.push(summary_message);
255 final_messages.extend(to_keep.to_vec());
256
257 let final_tokens = self.counter.count_messages(&final_messages) as usize;
259 if final_tokens > self.max_tokens {
260 return self.truncate(final_messages);
261 }
262
263 Ok(final_messages)
264 }
265}