Skip to main content

lc_memory/
persistent.rs

1// lc-memory/src/persistent.rs
2//! Memory Persistence Trait
3//!
4//! Defines the interface for persistent memory storage.
5//! Allows memory to be saved/loaded from external storage (MongoDB, Redis, etc.)
6
7use super::base::{BaseMemory, MemoryError};
8use async_trait::async_trait;
9
10/// Persistent Memory Trait
11///
12/// Extends BaseMemory with persistence capabilities.
13/// Implementations can save/load memory state to external storage.
14///
15/// # Design
16/// - Framework provides trait and algorithms
17/// - Business layer provides storage implementation
18///
19/// # Example
20/// ```ignore
21/// use lc_memory::{MongoPersistentMemory, PersistentMemory};
22///
23/// let mut memory = MongoPersistentMemory::new(config);
24/// memory.load_from_store("session_123").await?;
25/// memory.save_context(&inputs, &outputs).await?;
26/// memory.save_to_store("session_123").await?;
27/// ```
28#[async_trait]
29pub trait PersistentMemory: BaseMemory {
30    /// Load memory state from persistent storage
31    ///
32    /// # Arguments
33    /// * `session_id` - Unique identifier for the conversation session
34    ///
35    /// # Returns
36    /// Ok(()) if successful, MemoryError if failed
37    async fn load_from_store(&mut self, session_id: &str) -> Result<(), MemoryError>;
38
39    /// Save current memory state to persistent storage
40    ///
41    /// Called after each conversation turn to persist the updated state.
42    ///
43    /// # Arguments
44    /// * `session_id` - Unique identifier for the conversation session
45    async fn save_to_store(&mut self, session_id: &str) -> Result<(), MemoryError>;
46
47    /// Delete a session's memory from storage
48    ///
49    /// # Arguments
50    /// * `session_id` - Session to delete
51    async fn delete_session(&self, session_id: &str) -> Result<(), MemoryError>;
52
53    /// Check if a session exists in storage
54    ///
55    /// # Arguments
56    /// * `session_id` - Session to check
57    async fn session_exists(&self, session_id: &str) -> Result<bool, MemoryError>;
58
59    /// Get current session ID
60    ///
61    /// P0-2: 从内部字段读取真实会话 ID(而非恒返回 None 的伪实现)。
62    /// 返回 `Option<String>` 避免暴露内部锁的借用生命周期。
63    fn current_session_id(&self) -> Option<String>;
64
65    /// Set session ID
66    fn set_session_id(&mut self, session_id: String);
67}
68
69/// Memory persistence configuration
70///
71/// P1-3: 删除 `max_messages` 死字段(全库无压缩逻辑引用它,`with_max_messages(50)`
72/// 是"API 承诺多于实现");`token_limit` 作为 token 预算的单一来源,构造参数与
73/// `with_config` 都落到它。
74#[derive(Debug, Clone)]
75pub struct PersistenceConfig {
76    /// Auto-save after each save_context call
77    pub auto_save: bool,
78
79    /// Auto-load on first access
80    pub auto_load: bool,
81
82    /// Token limit for summary buffer memory
83    pub token_limit: usize,
84}
85
86impl Default for PersistenceConfig {
87    fn default() -> Self {
88        Self {
89            auto_save: true,
90            auto_load: true,
91            token_limit: 4000,
92        }
93    }
94}
95
96impl PersistenceConfig {
97    pub fn new() -> Self {
98        Self::default()
99    }
100
101    pub fn with_auto_save(mut self, auto_save: bool) -> Self {
102        self.auto_save = auto_save;
103        self
104    }
105
106    pub fn with_auto_load(mut self, auto_load: bool) -> Self {
107        self.auto_load = auto_load;
108        self
109    }
110
111    pub fn with_token_limit(mut self, token_limit: usize) -> Self {
112        self.token_limit = token_limit;
113        self
114    }
115}
116
117/// Memory data structure for serialization
118#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
119pub struct MemoryData {
120    /// Session ID
121    pub session_id: String,
122
123    /// Chat messages (serialized Message objects)
124    pub messages: Vec<lc_schema::Message>,
125
126    /// Current summary (for summary-based memory)
127    pub summary: Option<String>,
128
129    /// Memory metadata
130    pub metadata: std::collections::HashMap<String, String>,
131
132    /// Created timestamp
133    pub created_at: String,
134
135    /// Last updated timestamp
136    pub updated_at: String,
137
138    /// P2-3: 乐观锁版本号。每次写入 +1,保存时按 `{session_id, version}` 过滤,
139    /// 未命中即并发冲突。旧数据反序列化缺省为 0。
140    #[serde(default)]
141    pub version: u64,
142}
143
144impl MemoryData {
145    pub fn new(session_id: String) -> Self {
146        let now = chrono::Utc::now().to_rfc3339();
147        Self {
148            session_id,
149            messages: Vec::new(),
150            summary: None,
151            metadata: std::collections::HashMap::new(),
152            created_at: now.clone(),
153            updated_at: now,
154            version: 0,
155        }
156    }
157
158    pub fn with_messages(mut self, messages: Vec<lc_schema::Message>) -> Self {
159        self.messages = messages;
160        self.updated_at = chrono::Utc::now().to_rfc3339();
161        self
162    }
163
164    pub fn with_summary(mut self, summary: String) -> Self {
165        self.summary = Some(summary);
166        self.updated_at = chrono::Utc::now().to_rfc3339();
167        self
168    }
169
170    pub fn add_message(&mut self, message: lc_schema::Message) {
171        self.messages.push(message);
172        self.updated_at = chrono::Utc::now().to_rfc3339();
173    }
174
175    pub fn set_summary(&mut self, summary: String) {
176        self.summary = Some(summary);
177        self.updated_at = chrono::Utc::now().to_rfc3339();
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_persistence_config_default() {
187        let config = PersistenceConfig::default();
188        assert!(config.auto_save);
189        assert!(config.auto_load);
190        assert_eq!(config.token_limit, 4000);
191    }
192
193    #[test]
194    fn test_persistence_config_custom() {
195        let config = PersistenceConfig::new()
196            .with_auto_save(false)
197            .with_token_limit(2000);
198
199        assert!(!config.auto_save);
200        assert_eq!(config.token_limit, 2000);
201    }
202
203    #[test]
204    fn test_memory_data_new() {
205        let data = MemoryData::new("session_123".to_string());
206        assert_eq!(data.session_id, "session_123");
207        assert!(data.messages.is_empty());
208        assert!(data.summary.is_none());
209    }
210
211    #[test]
212    fn test_memory_data_with_messages() {
213        let messages = vec![
214            lc_schema::Message::human("Hello"),
215            lc_schema::Message::ai("Hi!"),
216        ];
217
218        let data = MemoryData::new("session_123".to_string()).with_messages(messages);
219
220        assert_eq!(data.messages.len(), 2);
221    }
222}