daimon_core/
archival_memory.rs1use std::collections::HashMap;
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::Arc;
16
17use serde_json::Value;
18
19use crate::error::Result;
20
21#[derive(Debug, Clone)]
23pub struct ArchivalRecord {
24 pub id: String,
26 pub text: String,
28 pub metadata: HashMap<String, Value>,
30 pub score: Option<f64>,
33}
34
35pub trait ArchivalMemory: Send + Sync {
42 fn insert(
44 &self,
45 text: &str,
46 metadata: HashMap<String, Value>,
47 ) -> impl Future<Output = Result<String>> + Send;
48
49 fn search(
52 &self,
53 query: &str,
54 top_k: usize,
55 ) -> impl Future<Output = Result<Vec<ArchivalRecord>>> + Send;
56
57 fn delete(&self, id: &str) -> impl Future<Output = Result<bool>> + Send;
59
60 fn count(&self) -> impl Future<Output = Result<usize>> + Send;
62}
63
64pub trait ErasedArchivalMemory: Send + Sync {
67 fn insert_erased<'a>(
68 &'a self,
69 text: &'a str,
70 metadata: HashMap<String, Value>,
71 ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>>;
72
73 fn search_erased<'a>(
74 &'a self,
75 query: &'a str,
76 top_k: usize,
77 ) -> Pin<Box<dyn Future<Output = Result<Vec<ArchivalRecord>>> + Send + 'a>>;
78
79 fn delete_erased<'a>(
80 &'a self,
81 id: &'a str,
82 ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'a>>;
83
84 fn count_erased(&self) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + '_>>;
85}
86
87impl<T: ArchivalMemory> ErasedArchivalMemory for T {
88 fn insert_erased<'a>(
89 &'a self,
90 text: &'a str,
91 metadata: HashMap<String, Value>,
92 ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
93 Box::pin(self.insert(text, metadata))
94 }
95
96 fn search_erased<'a>(
97 &'a self,
98 query: &'a str,
99 top_k: usize,
100 ) -> Pin<Box<dyn Future<Output = Result<Vec<ArchivalRecord>>> + Send + 'a>> {
101 Box::pin(self.search(query, top_k))
102 }
103
104 fn delete_erased<'a>(
105 &'a self,
106 id: &'a str,
107 ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'a>> {
108 Box::pin(self.delete(id))
109 }
110
111 fn count_erased(&self) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + '_>> {
112 Box::pin(self.count())
113 }
114}
115
116pub type SharedArchivalMemory = Arc<dyn ErasedArchivalMemory>;
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use std::sync::Mutex;
123
124 struct VecArchivalMemory(Mutex<Vec<ArchivalRecord>>);
125
126 impl ArchivalMemory for VecArchivalMemory {
127 async fn insert(&self, text: &str, metadata: HashMap<String, Value>) -> Result<String> {
128 let mut records = self.0.lock().unwrap();
129 let id = format!("rec-{}", records.len());
130 records.push(ArchivalRecord {
131 id: id.clone(),
132 text: text.to_string(),
133 metadata,
134 score: None,
135 });
136 Ok(id)
137 }
138
139 async fn search(&self, query: &str, top_k: usize) -> Result<Vec<ArchivalRecord>> {
140 let records = self.0.lock().unwrap();
141 Ok(records
142 .iter()
143 .filter(|r| r.text.contains(query))
144 .take(top_k)
145 .cloned()
146 .collect())
147 }
148
149 async fn delete(&self, id: &str) -> Result<bool> {
150 let mut records = self.0.lock().unwrap();
151 let before = records.len();
152 records.retain(|r| r.id != id);
153 Ok(records.len() != before)
154 }
155
156 async fn count(&self) -> Result<usize> {
157 Ok(self.0.lock().unwrap().len())
158 }
159 }
160
161 #[tokio::test]
162 async fn archival_memory_is_implementable_from_core_alone() {
163 let mem = VecArchivalMemory(Mutex::new(Vec::new()));
164 let id = mem.insert("the sky is blue", HashMap::new()).await.unwrap();
165 mem.insert("water is wet", HashMap::new()).await.unwrap();
166
167 assert_eq!(mem.count().await.unwrap(), 2);
168 let results = mem.search("sky", 5).await.unwrap();
169 assert_eq!(results.len(), 1);
170 assert_eq!(results[0].id, id);
171
172 assert!(mem.delete(&id).await.unwrap());
173 assert_eq!(mem.count().await.unwrap(), 1);
174
175 let shared: SharedArchivalMemory = Arc::new(VecArchivalMemory(Mutex::new(Vec::new())));
176 shared
177 .insert_erased("erased fact", HashMap::new())
178 .await
179 .unwrap();
180 assert_eq!(shared.count_erased().await.unwrap(), 1);
181 }
182}