Skip to main content

synaptic_condenser/
llm_summarizing.rs

1use std::sync::Arc;
2
3use crate::Condenser;
4use async_trait::async_trait;
5use synaptic_core::{ChatModel, ChatRequest, Message, SynapticError};
6
7/// Summarizes older messages using an LLM, keeping recent messages intact.
8pub struct LlmSummarizingCondenser {
9    model: Arc<dyn ChatModel>,
10    max_tokens: usize,
11    keep_recent: usize,
12}
13
14impl LlmSummarizingCondenser {
15    pub fn new(model: Arc<dyn ChatModel>, max_tokens: usize, keep_recent: usize) -> Self {
16        Self {
17            model,
18            max_tokens,
19            keep_recent,
20        }
21    }
22}
23
24#[async_trait]
25impl Condenser for LlmSummarizingCondenser {
26    async fn condense(&self, messages: Vec<Message>) -> Result<Vec<Message>, SynapticError> {
27        // If messages are within budget, return as-is
28        let estimated_tokens: usize = messages.iter().map(|m| m.content().len() / 4 + 4).sum();
29        if estimated_tokens <= self.max_tokens {
30            return Ok(messages);
31        }
32
33        // Split into system (if any), old messages to summarize, and recent to keep
34        let (system_msg, rest) = if !messages.is_empty() && messages[0].is_system() {
35            (Some(messages[0].clone()), &messages[1..])
36        } else {
37            (None, messages.as_slice())
38        };
39
40        if rest.len() <= self.keep_recent {
41            return Ok(messages);
42        }
43
44        let split = rest.len() - self.keep_recent;
45        let to_summarize = &rest[..split];
46        let to_keep = &rest[split..];
47
48        // Build summarization prompt
49        let mut summary_text = String::new();
50        for msg in to_summarize {
51            summary_text.push_str(&format!("{}: {}\n", msg.role(), msg.content()));
52        }
53
54        let prompt = format!(
55            "Summarize the following conversation concisely, preserving key information:\n\n{}",
56            summary_text
57        );
58
59        let request = ChatRequest::new(vec![Message::human(prompt)]);
60        let response = self.model.chat(request).await?;
61        let summary = response.message.content().to_string();
62
63        // Reassemble: system + summary + recent
64        let mut result = Vec::new();
65        if let Some(sys) = system_msg {
66            result.push(sys);
67        }
68        result.push(Message::system(format!(
69            "[Conversation Summary]\n{}",
70            summary
71        )));
72        result.extend_from_slice(to_keep);
73
74        Ok(result)
75    }
76}