Skip to main content

leviath_core/
lifecycle.rs

1//! LLM compaction configuration for regions that summarize on overflow.
2
3use serde::{Deserialize, Serialize};
4
5/// Configuration for LLM-based compaction.
6///
7/// When a Compacting region hits its threshold, it sends content to an LLM
8/// for summarization. This config controls which LLM is used and how
9/// the summarization prompt is constructed.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct CompactionConfig {
12    /// Provider to use for compaction (e.g., "anthropic", "openai")
13    pub provider: String,
14
15    /// Model to use (e.g., "claude-sonnet-4-6", "gpt-5.4-mini")
16    pub model: String,
17
18    /// Custom system prompt for compaction (None = use default)
19    pub system_prompt: Option<String>,
20
21    /// Custom user prompt template. Use {content} as placeholder for region
22    /// content, {region_name} for the region name.
23    pub user_prompt_template: Option<String>,
24
25    /// Max tokens for the summary response
26    pub max_summary_tokens: usize,
27
28    /// Temperature (lower = more deterministic)
29    pub temperature: f32,
30}
31
32impl Default for CompactionConfig {
33    fn default() -> Self {
34        Self {
35            provider: "anthropic".to_string(),
36            model: "claude-sonnet-4-6".to_string(),
37            system_prompt: None,
38            user_prompt_template: None,
39            max_summary_tokens: 2000,
40            temperature: 0.2,
41        }
42    }
43}
44
45impl CompactionConfig {
46    /// Get the system prompt, using the default if none is configured.
47    pub fn system_prompt(&self) -> &str {
48        self.system_prompt.as_deref().unwrap_or(
49            "You are a context compaction assistant. Your job is to summarize content \
50             concisely while preserving all key information, decisions, and actionable items. \
51             Never lose critical details.",
52        )
53    }
54
55    /// Get the user prompt, substituting {content} and {region_name} placeholders.
56    pub fn user_prompt(&self, content: &str, region_name: &str) -> String {
57        if let Some(template) = &self.user_prompt_template {
58            template
59                .replace("{content}", content)
60                .replace("{region_name}", region_name)
61        } else {
62            format!(
63                "Summarize the following content from the \"{}\" context region. \
64                 Preserve key facts, decisions, code snippets, and actionable items. \
65                 Be concise but thorough.\n\n{}",
66                region_name, content
67            )
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_compaction_config_defaults() {
78        let config = CompactionConfig::default();
79        assert_eq!(config.max_summary_tokens, 2000);
80        assert_eq!(config.temperature, 0.2);
81        assert!(config.system_prompt().contains("compaction assistant"));
82    }
83
84    #[test]
85    fn test_compaction_config_user_prompt() {
86        let config = CompactionConfig::default();
87        let prompt = config.user_prompt("some content here", "conversation");
88        assert!(prompt.contains("conversation"));
89        assert!(prompt.contains("some content here"));
90    }
91
92    #[test]
93    fn test_compaction_config_custom_template() {
94        let config = CompactionConfig {
95            user_prompt_template: Some("Region {region_name}: {content}".to_string()),
96            ..Default::default()
97        };
98        let prompt = config.user_prompt("hello", "test");
99        assert_eq!(prompt, "Region test: hello");
100    }
101}