agent_io/agent/
compaction.rs1use derive_builder::Builder;
4use serde::{Deserialize, Serialize};
5
6use crate::llm::Message;
7
8pub const DEFAULT_SUMMARY_PROMPT: &str = r#"Summarize the conversation so far, preserving:
101. Key decisions and their reasons
112. Important facts learned
123. Current task state and next steps
134. Any user preferences or constraints
14
15Format the summary as a structured markdown document."#;
16
17#[derive(Debug, Clone, Builder)]
19#[builder(pattern = "owned")]
20pub struct CompactionConfig {
21 #[builder(default = "true")]
23 pub enabled: bool,
24
25 #[builder(default = "0.80")]
27 pub threshold_ratio: f32,
28
29 #[builder(setter(into, strip_option), default = "None")]
31 pub model: Option<String>,
32
33 #[builder(default = "DEFAULT_SUMMARY_PROMPT.to_string()")]
35 pub summary_prompt: String,
36}
37
38impl Default for CompactionConfig {
39 fn default() -> Self {
40 Self {
41 enabled: true,
42 threshold_ratio: 0.80,
43 model: None,
44 summary_prompt: DEFAULT_SUMMARY_PROMPT.to_string(),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize)]
51pub struct TokenUsage {
52 pub input_tokens: u64,
53 pub output_tokens: u64,
54 pub cache_creation_tokens: u64,
55 pub cache_read_tokens: u64,
56}
57
58impl TokenUsage {
59 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn total(&self) -> u64 {
64 self.input_tokens + self.output_tokens
65 }
66
67 pub fn add_input(&mut self, tokens: u64, cached: bool) {
68 if cached {
69 self.cache_read_tokens += tokens;
70 } else {
71 self.input_tokens += tokens;
72 }
73 }
74
75 pub fn add_output(&mut self, tokens: u64) {
76 self.output_tokens += tokens;
77 }
78
79 pub fn add_cache_creation(&mut self, tokens: u64) {
80 self.cache_creation_tokens += tokens;
81 }
82}
83
84pub struct CompactionService {
86 config: CompactionConfig,
87 usage: TokenUsage,
88}
89
90impl CompactionService {
91 pub fn new(config: CompactionConfig) -> Self {
92 Self {
93 config,
94 usage: TokenUsage::new(),
95 }
96 }
97
98 pub fn should_compact(&self, current_tokens: u64, context_window: u64) -> bool {
100 if !self.config.enabled || context_window == 0 {
101 return false;
102 }
103
104 let threshold = (context_window as f32 * self.config.threshold_ratio) as u64;
105 current_tokens >= threshold
106 }
107
108 pub fn summary_prompt(&self) -> &str {
110 &self.config.summary_prompt
111 }
112
113 pub fn update_usage(&mut self, usage: &TokenUsage) {
115 self.usage.input_tokens += usage.input_tokens;
116 self.usage.output_tokens += usage.output_tokens;
117 self.usage.cache_creation_tokens += usage.cache_creation_tokens;
118 self.usage.cache_read_tokens += usage.cache_read_tokens;
119 }
120
121 pub fn get_usage(&self) -> &TokenUsage {
123 &self.usage
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct CompactionResult {
130 pub summary: Message,
132 pub messages_removed: usize,
134 pub tokens_saved: u64,
136}
137
138impl CompactionResult {
139 pub fn new(summary: impl Into<String>, messages_removed: usize, tokens_saved: u64) -> Self {
140 Self {
141 summary: Message::system(summary.into()),
142 messages_removed,
143 tokens_saved,
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_should_compact() {
154 let config = CompactionConfig::default();
155 let service = CompactionService::new(config);
156
157 assert!(!service.should_compact(50, 100));
159
160 assert!(service.should_compact(80, 100));
162
163 assert!(service.should_compact(90, 100));
165 }
166
167 #[test]
168 fn test_disabled_compaction() {
169 let config = CompactionConfig {
170 enabled: false,
171 ..Default::default()
172 };
173 let service = CompactionService::new(config);
174
175 assert!(!service.should_compact(99, 100));
176 }
177
178 #[test]
179 fn test_token_usage() {
180 let mut usage = TokenUsage::new();
181 usage.add_input(100, false);
182 usage.add_input(50, true);
183 usage.add_output(75);
184
185 assert_eq!(usage.input_tokens, 100);
186 assert_eq!(usage.cache_read_tokens, 50);
187 assert_eq!(usage.output_tokens, 75);
188 assert_eq!(usage.total(), 175);
189 }
190}