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    /// Create a persistence configuration with default values.
98    pub fn new() -> Self {
99        Self::default()
100    }
101
102    /// Set whether memory is auto-saved after each `save_context` call.
103    pub fn with_auto_save(mut self, auto_save: bool) -> Self {
104        self.auto_save = auto_save;
105        self
106    }
107
108    /// Set whether memory is auto-loaded on first access.
109    pub fn with_auto_load(mut self, auto_load: bool) -> Self {
110        self.auto_load = auto_load;
111        self
112    }
113
114    /// Set the token limit for summary buffer memory.
115    pub fn with_token_limit(mut self, token_limit: usize) -> Self {
116        self.token_limit = token_limit;
117        self
118    }
119}
120
121/// Memory data structure for serialization
122#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
123pub struct MemoryData {
124    /// Session ID
125    pub session_id: String,
126
127    /// Chat messages (serialized Message objects)
128    pub messages: Vec<lc_schema::Message>,
129
130    /// Current summary (for summary-based memory)
131    pub summary: Option<String>,
132
133    /// Memory metadata
134    pub metadata: std::collections::HashMap<String, String>,
135
136    /// Created timestamp
137    pub created_at: String,
138
139    /// Last updated timestamp
140    pub updated_at: String,
141
142    /// P2-3: 乐观锁版本号。每次写入 +1,保存时按 `{session_id, version}` 过滤,
143    /// 未命中即并发冲突。旧数据反序列化缺省为 0。
144    #[serde(default)]
145    pub version: u64,
146}
147
148impl MemoryData {
149    /// Create a new empty memory data for the given session.
150    pub fn new(session_id: impl Into<String>) -> Self {
151        let now = chrono::Utc::now().to_rfc3339();
152        Self {
153            session_id: session_id.into(),
154            messages: Vec::new(),
155            summary: None,
156            metadata: std::collections::HashMap::new(),
157            created_at: now.clone(),
158            updated_at: now,
159            version: 0,
160        }
161    }
162
163    /// Set the chat messages and update the timestamp.
164    pub fn with_messages(mut self, messages: Vec<lc_schema::Message>) -> Self {
165        self.messages = messages;
166        self.updated_at = chrono::Utc::now().to_rfc3339();
167        self
168    }
169
170    /// Set the current summary and update the timestamp.
171    pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
172        self.summary = Some(summary.into());
173        self.updated_at = chrono::Utc::now().to_rfc3339();
174        self
175    }
176
177    /// Append a message to the stored history.
178    pub fn add_message(&mut self, message: lc_schema::Message) {
179        self.messages.push(message);
180        self.updated_at = chrono::Utc::now().to_rfc3339();
181    }
182
183    /// Replace the stored summary.
184    pub fn set_summary(&mut self, summary: impl Into<String>) {
185        self.summary = Some(summary.into());
186        self.updated_at = chrono::Utc::now().to_rfc3339();
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_persistence_config_default() {
196        let config = PersistenceConfig::default();
197        assert!(config.auto_save);
198        assert!(config.auto_load);
199        assert_eq!(config.token_limit, 4000);
200    }
201
202    #[test]
203    fn test_persistence_config_custom() {
204        let config = PersistenceConfig::new()
205            .with_auto_save(false)
206            .with_token_limit(2000);
207
208        assert!(!config.auto_save);
209        assert_eq!(config.token_limit, 2000);
210    }
211
212    #[test]
213    fn test_memory_data_new() {
214        let data = MemoryData::new("session_123".to_string());
215        assert_eq!(data.session_id, "session_123");
216        assert!(data.messages.is_empty());
217        assert!(data.summary.is_none());
218    }
219
220    #[test]
221    fn test_memory_data_with_messages() {
222        let messages = vec![
223            lc_schema::Message::human("Hello"),
224            lc_schema::Message::ai("Hi!"),
225        ];
226
227        let data = MemoryData::new("session_123".to_string()).with_messages(messages);
228
229        assert_eq!(data.messages.len(), 2);
230    }
231}