aster/agents/error_handling/
overflow_handler.rs1use crate::context_mgmt::compact_messages;
30use crate::conversation::Conversation;
31use crate::providers::base::{Provider, ProviderUsage};
32use crate::providers::errors::ProviderError;
33use crate::session::Session;
34use anyhow::Result;
35use tracing::{debug, info, warn};
36
37pub struct OverflowHandler {
42 compaction_attempted: bool,
44
45 compaction_attempts: u32,
47
48 max_retries: u32,
50}
51
52impl Default for OverflowHandler {
53 fn default() -> Self {
54 Self::new(2)
55 }
56}
57
58impl OverflowHandler {
59 pub fn new(max_retries: u32) -> Self {
65 Self {
66 compaction_attempted: false,
67 compaction_attempts: 0,
68 max_retries,
69 }
70 }
71
72 pub fn is_context_overflow(error: &ProviderError) -> bool {
82 matches!(error, ProviderError::ContextLengthExceeded(_))
83 }
84
85 pub fn compaction_attempted(&self) -> bool {
87 self.compaction_attempted
88 }
89
90 pub fn compaction_attempts(&self) -> u32 {
92 self.compaction_attempts
93 }
94
95 pub fn can_retry(&self) -> bool {
97 self.compaction_attempts < self.max_retries
98 }
99
100 pub fn reset(&mut self) {
102 self.compaction_attempted = false;
103 self.compaction_attempts = 0;
104 }
105
106 pub async fn handle_overflow(
131 &mut self,
132 provider: &dyn Provider,
133 conversation: &Conversation,
134 _session: &Session,
135 ) -> Result<(Conversation, ProviderUsage, bool)> {
136 self.compaction_attempts += 1;
137 self.compaction_attempted = true;
138
139 info!(
140 "Handling context overflow (attempt {}/{})",
141 self.compaction_attempts, self.max_retries
142 );
143
144 if self.compaction_attempts > self.max_retries {
145 warn!("Maximum compaction retries ({}) exceeded", self.max_retries);
146 return Err(anyhow::anyhow!(
147 "Context limit exceeded after {} compaction attempts. \
148 Try using a shorter message, a model with a larger context window, \
149 or start a new session.",
150 self.max_retries
151 ));
152 }
153
154 debug!("Attempting conversation compaction");
155
156 match compact_messages(provider, conversation, false).await {
157 Ok((compacted_conversation, usage)) => {
158 info!(
159 "Compaction successful, conversation reduced from {} to {} messages",
160 conversation.len(),
161 compacted_conversation.len()
162 );
163 Ok((compacted_conversation, usage, true))
164 }
165 Err(e) => {
166 warn!("Compaction failed: {}", e);
167 Err(anyhow::anyhow!("Failed to compact conversation: {}", e))
168 }
169 }
170 }
171
172 pub async fn handle_overflow_with_pruning(
188 &mut self,
189 provider: &dyn Provider,
190 conversation: &Conversation,
191 session: &Session,
192 pruning_config: &crate::context::types::PruningConfig,
193 ) -> Result<(Conversation, ProviderUsage, bool)> {
194 use crate::context::pruner::ProgressivePruner;
195 use crate::providers::base::Usage;
196
197 let pruned_messages = ProgressivePruner::prune_messages(
199 conversation.messages(),
200 pruning_config.hard_clear_ratio + 0.1, pruning_config,
202 );
203
204 let pruned_conversation = Conversation::new_unvalidated(pruned_messages);
205
206 let original_len: usize = conversation
208 .messages()
209 .iter()
210 .map(|m| m.as_concat_text().len())
211 .sum();
212 let pruned_len: usize = pruned_conversation
213 .messages()
214 .iter()
215 .map(|m| m.as_concat_text().len())
216 .sum();
217
218 if pruned_len < original_len * 8 / 10 {
219 info!(
221 "Progressive pruning reduced context from {} to {} chars",
222 original_len, pruned_len
223 );
224 return Ok((
225 pruned_conversation,
226 ProviderUsage::new("pruning".to_string(), Usage::default()),
227 true,
228 ));
229 }
230
231 debug!("Progressive pruning insufficient, falling back to compaction");
233 self.handle_overflow(provider, conversation, session).await
234 }
235}
236
237#[derive(Debug)]
239pub struct OverflowResult {
240 pub conversation: Conversation,
242 pub usage: ProviderUsage,
244 pub should_retry: bool,
246 pub attempts: u32,
248}
249
250#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_is_context_overflow() {
260 let overflow_error = ProviderError::ContextLengthExceeded("Context too long".to_string());
261 let other_error = ProviderError::ServerError("Server error".to_string());
262
263 assert!(OverflowHandler::is_context_overflow(&overflow_error));
264 assert!(!OverflowHandler::is_context_overflow(&other_error));
265 }
266
267 #[test]
268 fn test_overflow_handler_new() {
269 let handler = OverflowHandler::new(3);
270 assert_eq!(handler.max_retries, 3);
271 assert!(!handler.compaction_attempted);
272 assert_eq!(handler.compaction_attempts, 0);
273 }
274
275 #[test]
276 fn test_overflow_handler_default() {
277 let handler = OverflowHandler::default();
278 assert_eq!(handler.max_retries, 2);
279 }
280
281 #[test]
282 fn test_can_retry() {
283 let mut handler = OverflowHandler::new(2);
284
285 assert!(handler.can_retry());
286
287 handler.compaction_attempts = 1;
288 assert!(handler.can_retry());
289
290 handler.compaction_attempts = 2;
291 assert!(!handler.can_retry());
292 }
293
294 #[test]
295 fn test_reset() {
296 let mut handler = OverflowHandler::new(2);
297 handler.compaction_attempted = true;
298 handler.compaction_attempts = 2;
299
300 handler.reset();
301
302 assert!(!handler.compaction_attempted);
303 assert_eq!(handler.compaction_attempts, 0);
304 }
305
306 #[test]
307 fn test_compaction_attempted() {
308 let mut handler = OverflowHandler::new(2);
309 assert!(!handler.compaction_attempted());
310
311 handler.compaction_attempted = true;
312 assert!(handler.compaction_attempted());
313 }
314}