ds_api/conversation/
summarizer.rs1use crate::raw::request::message::{Message, Role};
7
8pub trait Summarizer: Send + Sync {
12 fn should_summarize(&self, history: &[Message]) -> bool;
14
15 fn summarize(&self, history: &mut Vec<Message>) -> Option<Message>;
19}
20
21#[derive(Clone, Debug)]
27pub struct TokenBasedSummarizer {
28 pub threshold: usize,
29 pub retain_last: usize,
30 pub max_summary_chars: usize,
31}
32
33impl Default for TokenBasedSummarizer {
34 fn default() -> Self {
35 Self {
36 threshold: 100_000,
37 retain_last: 10,
38 max_summary_chars: 2_000, }
40 }
41}
42
43impl TokenBasedSummarizer {
44 fn estimate_tokens(history: &[Message]) -> usize {
47 history
49 .iter()
50 .filter(|m| !matches!(m.role, Role::System))
51 .filter_map(|m| m.content.as_ref())
52 .map(|s| s.len())
53 .sum::<usize>()
54 / 4
55 }
56}
57
58impl Summarizer for TokenBasedSummarizer {
59 fn should_summarize(&self, history: &[Message]) -> bool {
60 let est = Self::estimate_tokens(history);
61 est >= self.threshold
62 }
63
64 fn summarize(&self, history: &mut Vec<Message>) -> Option<Message> {
65 if history.len() <= self.retain_last {
66 return None;
67 }
68
69 let split = history.len().saturating_sub(self.retain_last);
71 if split == 0 {
72 return None;
73 }
74
75 let older: Vec<String> = history.drain(0..split).filter_map(|m| m.content).collect();
77
78 if older.is_empty() {
79 return None;
81 }
82
83 let joined = older.join("\n");
85 let summary_text = if joined.len() > self.max_summary_chars {
86 let mut s = joined;
87 s.truncate(self.max_summary_chars);
88 format!("Short summary of earlier conversation:\n{}\n(Truncated)", s)
89 } else {
90 format!("Short summary of earlier conversation:\n{}", joined)
91 };
92
93 let mut summary_msg = Message::new(Role::System, summary_text.as_str());
95 summary_msg.name = Some("[auto-summary]".to_string());
97
98 history.insert(0, summary_msg.clone());
99 Some(summary_msg)
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::raw::request::message::{Message, Role};
107
108 #[test]
109 fn summarizer_should_trigger_and_replace() {
110 let mut hist = vec![
111 Message::new(Role::User, "User message 1"),
112 Message::new(Role::Assistant, "Assistant reply 1"),
113 Message::new(Role::User, "User message 2"),
114 Message::new(Role::Assistant, "Assistant reply 2"),
115 ];
116
117 let summ = TokenBasedSummarizer {
119 threshold: 0, retain_last: 1,
121 max_summary_chars: 100,
122 };
123
124 assert!(summ.should_summarize(&hist));
125 let maybe_summary = summ.summarize(&mut hist);
126 assert!(maybe_summary.is_some());
127 assert!(hist.len() <= (1 + 1));
129 assert!(matches!(hist[0].role, Role::System));
131 }
132}