Skip to main content

paladin_memory/config/
rag.rs

1//! RAG and memory-extraction configuration types for the paladin-memory crate.
2//!
3//! This module provides:
4//! - [`RetrievalTrigger`] — when to trigger RAG retrieval
5//! - [`RagConfig`] — unified RAG configuration (merged from `application_settings` and `rag_retrieval_service`)
6//! - [`MemoryExtractionStrategy`] — when to extract memories from conversations
7//! - [`MemoryExtractionConfig`] — configuration for the memory-extraction pipeline
8
9use serde::{Deserialize, Serialize};
10
11// ── RetrievalTrigger ─────────────────────────────────────────────────────────
12
13/// When to trigger memory retrieval during Paladin execution.
14#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
15pub enum RetrievalTrigger {
16    /// Always retrieve memories for every query.
17    Always,
18    /// Retrieve only when specific keywords are detected.
19    KeywordBased,
20    /// Retrieve when semantic similarity exceeds a threshold.
21    SemanticThreshold,
22}
23
24// ── RagConfig ────────────────────────────────────────────────────────────────
25
26/// Unified configuration for RAG (Retrieval-Augmented Generation).
27///
28/// Combines the application-level settings (formerly in `application_settings.rs`)
29/// and the service-level configuration (formerly in `rag_retrieval_service.rs`).
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct RagConfig {
32    /// Number of top results to retrieve from Sanctum.
33    pub top_k: usize,
34    /// Minimum similarity score threshold (0.0–1.0).
35    pub min_similarity: f32,
36    /// Maximum tokens to include in the RAG context.
37    pub max_tokens: usize,
38    /// Timeout for RAG retrieval in seconds.
39    pub timeout_seconds: u64,
40    /// When to trigger memory retrieval.
41    pub retrieval_trigger: RetrievalTrigger,
42}
43
44impl Default for RagConfig {
45    fn default() -> Self {
46        Self {
47            top_k: 5,
48            min_similarity: 0.7,
49            max_tokens: 2000,
50            timeout_seconds: 5,
51            retrieval_trigger: RetrievalTrigger::Always,
52        }
53    }
54}
55
56impl RagConfig {
57    /// Validates RAG configuration.
58    ///
59    /// Returns `Err(String)` describing the first validation failure found.
60    pub fn validate(&self) -> Result<(), String> {
61        if self.top_k == 0 {
62            return Err("RAG top_k must be greater than 0".to_string());
63        }
64
65        if self.top_k > 100 {
66            return Err(format!(
67                "RAG top_k {} seems unusually large (max 100)",
68                self.top_k
69            ));
70        }
71
72        if !(0.0..=1.0).contains(&self.min_similarity) {
73            return Err(format!(
74                "RAG min_similarity {} must be between 0.0 and 1.0",
75                self.min_similarity
76            ));
77        }
78
79        if self.max_tokens == 0 {
80            return Err("RAG max_tokens must be greater than 0".to_string());
81        }
82
83        if self.timeout_seconds == 0 {
84            return Err("RAG timeout_seconds must be greater than 0".to_string());
85        }
86
87        Ok(())
88    }
89}
90
91// ── MemoryExtractionStrategy ─────────────────────────────────────────────────
92
93/// Strategy for when to extract memories from conversations.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
95#[serde(rename_all = "snake_case")]
96pub enum MemoryExtractionStrategy {
97    /// Extract after every conversation turn.
98    EveryTurn,
99    /// Extract only when the conversation completes (recommended).
100    #[default]
101    OnCompletion,
102    /// Manual extraction only (user-triggered).
103    Manual,
104    /// Extract when importance threshold is exceeded.
105    Threshold { importance: u8 },
106}
107
108// ── MemoryExtractionConfig ────────────────────────────────────────────────────
109
110/// Configuration for the memory-extraction pipeline.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct MemoryExtractionConfig {
113    /// Memory extraction strategy.
114    pub strategy: MemoryExtractionStrategy,
115    /// Enable automatic extraction.
116    pub enabled: bool,
117}
118
119impl Default for MemoryExtractionConfig {
120    fn default() -> Self {
121        Self {
122            strategy: MemoryExtractionStrategy::OnCompletion,
123            enabled: true,
124        }
125    }
126}
127
128impl MemoryExtractionConfig {
129    /// Validates memory extraction configuration.
130    ///
131    /// Returns `Err(String)` if the configuration is invalid.
132    pub fn validate(&self) -> Result<(), String> {
133        if let MemoryExtractionStrategy::Threshold { importance } = self.strategy
134            && importance == 0
135        {
136            return Err(
137                "Memory extraction threshold importance must be greater than 0".to_string(),
138            );
139        }
140        Ok(())
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_rag_config_defaults() {
150        let cfg = RagConfig::default();
151        assert_eq!(cfg.top_k, 5);
152        assert!((cfg.min_similarity - 0.7).abs() < f32::EPSILON);
153        assert_eq!(cfg.max_tokens, 2000);
154        assert_eq!(cfg.timeout_seconds, 5);
155        assert_eq!(cfg.retrieval_trigger, RetrievalTrigger::Always);
156    }
157
158    #[test]
159    fn test_rag_config_validate_ok() {
160        assert!(RagConfig::default().validate().is_ok());
161    }
162
163    #[test]
164    fn test_rag_config_validate_zero_top_k() {
165        let cfg = RagConfig {
166            top_k: 0,
167            ..Default::default()
168        };
169        assert!(cfg.validate().is_err());
170    }
171
172    #[test]
173    fn test_rag_config_validate_bad_similarity() {
174        let cfg = RagConfig {
175            min_similarity: 1.5,
176            ..Default::default()
177        };
178        assert!(cfg.validate().is_err());
179    }
180
181    #[test]
182    fn test_rag_config_validate_zero_timeout() {
183        let cfg = RagConfig {
184            timeout_seconds: 0,
185            ..Default::default()
186        };
187        assert!(cfg.validate().is_err());
188    }
189
190    #[test]
191    fn test_memory_extraction_config_defaults() {
192        let cfg = MemoryExtractionConfig::default();
193        assert!(cfg.enabled);
194        assert_eq!(cfg.strategy, MemoryExtractionStrategy::OnCompletion);
195    }
196
197    #[test]
198    fn test_memory_extraction_threshold_zero_invalid() {
199        let cfg = MemoryExtractionConfig {
200            strategy: MemoryExtractionStrategy::Threshold { importance: 0 },
201            enabled: true,
202        };
203        assert!(cfg.validate().is_err());
204    }
205
206    #[test]
207    fn test_memory_extraction_threshold_nonzero_valid() {
208        let cfg = MemoryExtractionConfig {
209            strategy: MemoryExtractionStrategy::Threshold { importance: 5 },
210            enabled: true,
211        };
212        assert!(cfg.validate().is_ok());
213    }
214}