Skip to main content

lean_ctx_proxy/
prose_patterns.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Deterministic, loss-conscious compression for repetitive prose.
3//!
4//! This pass runs before section ranking. It never enters fenced code blocks,
5//! never rewrites a surviving sentence, and only removes later duplicates or
6//! explanatory detail that is already represented by a preserved error.
7
8use std::collections::HashSet;
9
10/// Result of the pre-ranking prose pattern pass.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct PatternCompression {
13    pub compressed: String,
14    pub units_removed: usize,
15}
16
17/// Compress repeated prose while preserving code fences verbatim.
18pub fn compress_prose_patterns(content: &str) -> PatternCompression {
19    if content.trim().is_empty() {
20        return PatternCompression {
21            compressed: content.to_owned(),
22            units_removed: 0,
23        };
24    }
25
26    let mut compressed = String::with_capacity(content.len());
27    let mut prose = Vec::new();
28    let mut in_fence = false;
29    let mut units_removed = 0usize;
30
31    for line in content.split_inclusive('\n') {
32        if is_fence(line) {
33            let result = compress_prose_segment(&prose.join(""));
34            compressed.push_str(&result.compressed);
35            units_removed += result.units_removed;
36            prose.clear();
37            compressed.push_str(line);
38            in_fence = !in_fence;
39        } else if in_fence {
40            compressed.push_str(line);
41        } else {
42            prose.push(line);
43        }
44    }
45
46    let result = compress_prose_segment(&prose.join(""));
47    compressed.push_str(&result.compressed);
48    units_removed += result.units_removed;
49
50    PatternCompression {
51        compressed,
52        units_removed,
53    }
54}
55
56/// Remove instruction lines already emitted by an earlier conversation turn.
57///
58/// The caller owns `seen`, so the scope is an individual request pipeline and
59/// cannot leak state between requests. Only imperative boilerplate is removed;
60/// ordinary repeated prose remains available to the normal local pass.
61pub fn remove_seen_instruction_lines(content: &mut String, seen: &mut HashSet<String>) -> usize {
62    let mut retained = String::with_capacity(content.len());
63    let mut units_removed = 0;
64
65    for line in content.split_inclusive('\n') {
66        if is_instruction_line(line) {
67            let key = canonical(line);
68            if !key.is_empty() && !seen.insert(key) {
69                units_removed += 1;
70                continue;
71            }
72        }
73        retained.push_str(line);
74    }
75
76    if units_removed > 0 {
77        *content = retained;
78    }
79    units_removed
80}
81
82fn compress_prose_segment(segment: &str) -> PatternCompression {
83    if segment.trim().is_empty() {
84        return PatternCompression {
85            compressed: segment.to_owned(),
86            units_removed: 0,
87        };
88    }
89
90    let mut seen_headings = HashSet::new();
91    let mut seen_paragraphs = HashSet::new();
92    let mut seen_instruction_lines = HashSet::new();
93    let mut retained = Vec::new();
94    let mut paragraph = Vec::new();
95    let mut units_removed = 0usize;
96
97    let flush_paragraph = |paragraph: &mut Vec<&str>,
98                           retained: &mut Vec<String>,
99                           seen_paragraphs: &mut HashSet<String>,
100                           units_removed: &mut usize| {
101        if paragraph.is_empty() {
102            return;
103        }
104        let original = paragraph.join("");
105        paragraph.clear();
106        let result = compress_paragraph(&original);
107        let key = canonical(&result.compressed);
108        if !key.is_empty() && !seen_paragraphs.insert(key) {
109            *units_removed += 1;
110            return;
111        }
112        *units_removed += result.units_removed;
113        retained.push(result.compressed);
114    };
115
116    for line in segment.split_inclusive('\n') {
117        if line.trim().is_empty() {
118            flush_paragraph(
119                &mut paragraph,
120                &mut retained,
121                &mut seen_paragraphs,
122                &mut units_removed,
123            );
124            retained.push(line.to_owned());
125            continue;
126        }
127
128        if is_heading(line) {
129            flush_paragraph(
130                &mut paragraph,
131                &mut retained,
132                &mut seen_paragraphs,
133                &mut units_removed,
134            );
135            let key = canonical(line.trim_start_matches('#').trim());
136            if key.is_empty() || seen_headings.insert(key) {
137                retained.push(line.to_owned());
138            } else {
139                units_removed += 1;
140            }
141            continue;
142        }
143
144        if is_instruction_line(line) {
145            flush_paragraph(
146                &mut paragraph,
147                &mut retained,
148                &mut seen_paragraphs,
149                &mut units_removed,
150            );
151            let key = canonical(line);
152            if key.is_empty() || seen_instruction_lines.insert(key) {
153                retained.push(line.to_owned());
154            } else {
155                units_removed += 1;
156            }
157            continue;
158        }
159
160        paragraph.push(line);
161    }
162
163    flush_paragraph(
164        &mut paragraph,
165        &mut retained,
166        &mut seen_paragraphs,
167        &mut units_removed,
168    );
169
170    PatternCompression {
171        compressed: retained.concat(),
172        units_removed,
173    }
174}
175
176fn compress_paragraph(paragraph: &str) -> PatternCompression {
177    let sentences = sentences(paragraph);
178    if sentences.len() < 2 {
179        return PatternCompression {
180            compressed: paragraph.to_owned(),
181            units_removed: 0,
182        };
183    }
184
185    let error = paragraph_has_error(paragraph);
186    let mut retained = Vec::new();
187    let mut previous = Vec::<HashSet<String>>::new();
188    let mut units_removed = 0usize;
189
190    for (index, sentence) in sentences.iter().enumerate() {
191        let words = word_set(sentence);
192        let duplicate = words.len() >= 6
193            && previous
194                .iter()
195                .any(|candidate| overlap_ratio(&words, candidate) >= 0.82);
196        let verbose_error_detail = error
197            && index > 0
198            && index + 1 < sentences.len()
199            && words.len() >= 12
200            && !contains_remediation(sentence);
201
202        if duplicate || verbose_error_detail {
203            units_removed += 1;
204            continue;
205        }
206
207        previous.push(words);
208        retained.push(*sentence);
209    }
210
211    if retained.is_empty() {
212        return PatternCompression {
213            compressed: sentences[0].to_owned(),
214            units_removed: units_removed.saturating_sub(1),
215        };
216    }
217
218    PatternCompression {
219        compressed: retained.join(" "),
220        units_removed,
221    }
222}
223
224fn sentences(paragraph: &str) -> Vec<&str> {
225    let mut result = Vec::new();
226    let mut start = 0;
227    for (index, character) in paragraph.char_indices() {
228        if matches!(character, '.' | '!' | '?') {
229            let end = index + character.len_utf8();
230            if let Some(sentence) = paragraph.get(start..end) {
231                if !sentence.trim().is_empty() {
232                    result.push(sentence.trim());
233                }
234            }
235            start = end;
236        }
237    }
238    if let Some(tail) = paragraph.get(start..) {
239        if !tail.trim().is_empty() {
240            result.push(tail.trim());
241        }
242    }
243    result
244}
245
246fn canonical(text: &str) -> String {
247    text.chars()
248        .flat_map(char::to_lowercase)
249        .map(|character| {
250            if character.is_alphanumeric() {
251                character
252            } else {
253                ' '
254            }
255        })
256        .collect::<String>()
257        .split_whitespace()
258        .collect::<Vec<_>>()
259        .join(" ")
260}
261
262fn word_set(text: &str) -> HashSet<String> {
263    canonical(text)
264        .split_whitespace()
265        .filter(|word| word.len() > 2)
266        .map(ToOwned::to_owned)
267        .collect()
268}
269
270fn overlap_ratio(left: &HashSet<String>, right: &HashSet<String>) -> f32 {
271    let union = left.union(right).count();
272    if union == 0 {
273        return 0.0;
274    }
275    left.intersection(right).count() as f32 / union as f32
276}
277
278fn is_fence(line: &str) -> bool {
279    let trimmed = line.trim_start();
280    trimmed.starts_with("```") || trimmed.starts_with("~~~")
281}
282
283fn is_heading(line: &str) -> bool {
284    let trimmed = line.trim_start();
285    trimmed.starts_with('#') && trimmed.trim_start_matches('#').starts_with(' ')
286}
287
288fn is_instruction_line(line: &str) -> bool {
289    let words = canonical(line);
290    let instruction_prefixes = [
291        "please ",
292        "always ",
293        "never ",
294        "must ",
295        "do not ",
296        "remember to ",
297        "make sure ",
298        "follow ",
299    ];
300    instruction_prefixes
301        .iter()
302        .any(|prefix| words.starts_with(prefix))
303}
304
305fn paragraph_has_error(paragraph: &str) -> bool {
306    sentence_has_error(paragraph)
307}
308
309fn sentence_has_error(sentence: &str) -> bool {
310    let lower = canonical(sentence);
311    [
312        "error",
313        "exception",
314        "failed",
315        "failure",
316        "panic",
317        "traceback",
318    ]
319    .iter()
320    .any(|marker| lower.split_whitespace().any(|word| word == *marker))
321}
322
323fn contains_remediation(sentence: &str) -> bool {
324    let lower = canonical(sentence);
325    [
326        "fix ", "retry ", "resolve ", "run ", "use ", "change ", "set ", "remove ",
327    ]
328    .iter()
329    .any(|prefix| lower.starts_with(prefix))
330}
331
332#[cfg(test)]
333mod tests {
334    use super::compress_prose_patterns;
335
336    #[test]
337    fn removes_repeated_explanations_but_keeps_the_first_one() {
338        let source = "The cache stores normalized keys before lookup. The cache stores normalized keys before lookup. Keep the key format stable.";
339        let result = compress_prose_patterns(source);
340        assert_eq!(
341            result.compressed,
342            "The cache stores normalized keys before lookup. Keep the key format stable."
343        );
344        assert_eq!(result.units_removed, 1);
345    }
346
347    #[test]
348    fn collapses_repeated_markdown_headings_and_instruction_lines() {
349        let source = "# Rules\nAlways run tests before merging.\n# Rules\nAlways run tests before merging.\n";
350        let result = compress_prose_patterns(source);
351        assert_eq!(
352            result.compressed,
353            "# Rules\nAlways run tests before merging.\n"
354        );
355        assert_eq!(result.units_removed, 2);
356    }
357
358    #[test]
359    fn keeps_the_error_and_remediation_while_dropping_verbose_detail() {
360        let source = "Error: cache write failed. The worker attempted every available fallback and recorded extensive retry diagnostics for each failed filesystem operation. Retry after freeing disk space.";
361        let result = compress_prose_patterns(source);
362        assert_eq!(
363            result.compressed,
364            "Error: cache write failed. Retry after freeing disk space."
365        );
366        assert_eq!(result.units_removed, 1);
367    }
368
369    #[test]
370    fn code_fences_are_byte_preserved() {
371        let source = "Repeat this explanation. Repeat this explanation.\n```rust\nlet value = \"Repeat this explanation.\";\n```\n";
372        let result = compress_prose_patterns(source);
373        assert!(
374            result
375                .compressed
376                .contains("let value = \"Repeat this explanation.\";")
377        );
378    }
379
380    #[test]
381    fn removes_boilerplate_instruction_lines_seen_in_earlier_turns() {
382        let mut seen = std::collections::HashSet::new();
383        let mut first = "Always run tests before merging.\nKeep the failure output.\n".to_owned();
384        let mut second = "Always run tests before merging.\nKeep the failure output.\n".to_owned();
385        assert_eq!(
386            super::remove_seen_instruction_lines(&mut first, &mut seen),
387            0
388        );
389        assert_eq!(
390            super::remove_seen_instruction_lines(&mut second, &mut seen),
391            1
392        );
393        assert_eq!(second, "Keep the failure output.\n");
394    }
395}