sqz_engine/
kv_cache_optimizer.rs1use crate::types::ModelFamily;
28
29const DEFAULT_SINK_TOKENS: usize = 4;
31
32const CHARS_PER_TOKEN: usize = 4;
34
35const OPENAI_CACHE_TOKENS: usize = 1024;
37
38const ANTHROPIC_CACHE_SENTINEL: &str = "[cache_break]";
40
41pub fn compress_with_sinks(content: &str, model_family: &ModelFamily) -> String {
51 if content.is_empty() {
52 return String::new();
53 }
54
55 let sink_chars = DEFAULT_SINK_TOKENS * CHARS_PER_TOKEN;
56
57 let protected_prefix = match model_family {
59 ModelFamily::AnthropicClaude => {
60 let cache_end = find_anthropic_cache_end(content);
62 sink_chars.max(cache_end)
63 }
64 ModelFamily::OpenAiGpt => {
65 let openai_boundary = OPENAI_CACHE_TOKENS * CHARS_PER_TOKEN;
67 sink_chars.max(openai_boundary)
68 }
69 ModelFamily::GoogleGemini | ModelFamily::Local(_) => {
70 sink_chars
72 }
73 };
74
75 let protected_prefix =
80 crate::text_boundary::floor_char_boundary(content, protected_prefix);
81
82 let protected = &content[..protected_prefix];
84 let compressible = &content[protected_prefix..];
85
86 if compressible.is_empty() {
87 return content.to_string();
88 }
89
90 let compressed_tail = compress_region(compressible);
93
94 format!("{protected}{compressed_tail}")
95}
96
97pub fn compress_with_custom_sinks(
102 content: &str,
103 model_family: &ModelFamily,
104 sink_tokens: usize,
105) -> String {
106 if content.is_empty() {
107 return String::new();
108 }
109
110 let sink_chars = sink_tokens * CHARS_PER_TOKEN;
111
112 let protected_prefix = match model_family {
113 ModelFamily::AnthropicClaude => {
114 let cache_end = find_anthropic_cache_end(content);
115 sink_chars.max(cache_end)
116 }
117 ModelFamily::OpenAiGpt => {
118 let openai_boundary = OPENAI_CACHE_TOKENS * CHARS_PER_TOKEN;
119 sink_chars.max(openai_boundary)
120 }
121 ModelFamily::GoogleGemini | ModelFamily::Local(_) => sink_chars,
122 };
123
124 let protected_prefix =
125 crate::text_boundary::floor_char_boundary(content, protected_prefix);
126 let protected = &content[..protected_prefix];
127 let compressible = &content[protected_prefix..];
128
129 if compressible.is_empty() {
130 return content.to_string();
131 }
132
133 let compressed_tail = compress_region(compressible);
134 format!("{protected}{compressed_tail}")
135}
136
137fn find_anthropic_cache_end(content: &str) -> usize {
142 if let Some(pos) = content.rfind(ANTHROPIC_CACHE_SENTINEL) {
143 pos + ANTHROPIC_CACHE_SENTINEL.len()
144 } else {
145 0
146 }
147}
148
149fn compress_region(text: &str) -> String {
152 let mut result = Vec::new();
153 let mut prev_line: Option<&str> = None;
154 let mut blank_count = 0;
155
156 for line in text.lines() {
157 let trimmed = line.trim_end();
158
159 if trimmed.is_empty() {
160 blank_count += 1;
161 if blank_count <= 1 {
162 result.push(String::new());
163 }
164 prev_line = None;
165 continue;
166 }
167
168 blank_count = 0;
169
170 if prev_line == Some(trimmed) {
172 continue;
173 }
174
175 result.push(trimmed.to_string());
176 prev_line = Some(trimmed);
177 }
178
179 let mut out = result.join("\n");
181 if text.ends_with('\n') && !out.ends_with('\n') {
182 out.push('\n');
183 }
184 out
185}
186
187#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_empty_content() {
195 let result = compress_with_sinks("", &ModelFamily::AnthropicClaude);
196 assert!(result.is_empty());
197 }
198
199 #[test]
200 fn test_short_content_preserved() {
201 let content = "hello";
203 let result = compress_with_sinks(content, &ModelFamily::AnthropicClaude);
204 assert_eq!(result, content);
205 }
206
207 #[test]
208 fn test_anthropic_cache_sentinel_preserved() {
209 let mut content = String::new();
210 content.push_str("System prompt line 1\n");
211 content.push_str("System prompt line 2\n");
212 content.push_str(ANTHROPIC_CACHE_SENTINEL);
213 content.push('\n');
214 for _ in 0..50 {
216 content.push_str("repeated line\n");
217 }
218
219 let result = compress_with_sinks(&content, &ModelFamily::AnthropicClaude);
220
221 assert!(
223 result.contains(ANTHROPIC_CACHE_SENTINEL),
224 "cache sentinel should be preserved"
225 );
226 assert!(
228 result.len() < content.len(),
229 "compressed ({}) should be shorter than original ({})",
230 result.len(),
231 content.len()
232 );
233 }
234
235 #[test]
236 fn test_openai_preserves_first_1024_tokens() {
237 let prefix = "x".repeat(4096);
239 let suffix = "repeated line\n".repeat(100);
240 let content = format!("{prefix}{suffix}");
241
242 let result = compress_with_sinks(&content, &ModelFamily::OpenAiGpt);
243
244 assert!(
246 result.starts_with(&prefix),
247 "first 4096 chars should be preserved for OpenAI"
248 );
249 assert!(result.len() < content.len());
251 }
252
253 #[test]
254 fn test_local_model_only_preserves_sinks() {
255 let content = "important\n".repeat(100);
256 let result = compress_with_sinks(&content, &ModelFamily::Local("llama".into()));
257
258 let sink_chars = DEFAULT_SINK_TOKENS * CHARS_PER_TOKEN;
260 let expected_prefix = &content[..sink_chars.min(content.len())];
261 assert!(
262 result.starts_with(expected_prefix),
263 "sink tokens should be preserved"
264 );
265 }
266
267 #[test]
268 fn test_custom_sink_count() {
269 let content = "a".repeat(200);
270 let result = compress_with_custom_sinks(
271 &content,
272 &ModelFamily::GoogleGemini,
273 10, );
275 assert!(result.starts_with(&"a".repeat(40)));
277 }
278
279 #[test]
280 fn test_compress_region_deduplicates() {
281 let text = "line a\nline a\nline a\nline b\nline b\n";
282 let compressed = compress_region(text);
283 assert_eq!(compressed, "line a\nline b\n");
284 }
285
286 #[test]
287 fn test_compress_region_collapses_blanks() {
288 let text = "line a\n\n\n\n\nline b\n";
289 let compressed = compress_region(text);
290 assert_eq!(compressed, "line a\n\nline b\n");
291 }
292
293 use proptest::prelude::*;
296
297 fn arb_model_family() -> impl Strategy<Value = ModelFamily> {
298 prop_oneof![
299 Just(ModelFamily::AnthropicClaude),
300 Just(ModelFamily::OpenAiGpt),
301 Just(ModelFamily::GoogleGemini),
302 Just(ModelFamily::Local("test".into())),
303 ]
304 }
305
306 proptest! {
307 #[test]
309 fn prop_compressed_not_longer(
310 content in "[a-z \n]{10,500}",
311 model in arb_model_family(),
312 ) {
313 let result = compress_with_sinks(&content, &model);
314 prop_assert!(
315 result.len() <= content.len(),
316 "compressed ({}) should be <= original ({})",
317 result.len(),
318 content.len()
319 );
320 }
321
322 #[test]
324 fn prop_sink_prefix_preserved(
325 content in "[a-z]{50,200}",
326 model in arb_model_family(),
327 ) {
328 let result = compress_with_sinks(&content, &model);
329 let sink_chars = (DEFAULT_SINK_TOKENS * CHARS_PER_TOKEN).min(content.len());
330 let expected_prefix = &content[..sink_chars];
331 prop_assert!(
332 result.starts_with(expected_prefix),
333 "sink prefix should be preserved"
334 );
335 }
336
337 #[test]
339 fn prop_empty_in_empty_out(model in arb_model_family()) {
340 let result = compress_with_sinks("", &model);
341 prop_assert!(result.is_empty());
342 }
343 }
344}