1use async_trait::async_trait;
5use lc_shared::splitter::{RecursiveCharacterSplitter, TextSplitter};
6
7use crate::document_store::{ChunkDocument, ChunkedDocumentStoreTrait, DocumentStore};
8use crate::{Document, VectorStoreError};
9
10#[derive(Debug, Clone)]
12pub struct RedisStoreConfig {
13 pub url: String,
15 pub key_prefix: String,
17}
18
19impl Default for RedisStoreConfig {
20 fn default() -> Self {
21 Self {
22 url: "redis://127.0.0.1:6379".to_string(),
23 key_prefix: "langchainrust".to_string(),
24 }
25 }
26}
27
28impl RedisStoreConfig {
29 pub fn new(url: impl Into<String>) -> Self {
31 Self {
32 url: url.into(),
33 ..Default::default()
34 }
35 }
36 pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
38 self.key_prefix = prefix.into();
39 self
40 }
41}
42
43pub struct RedisDocumentStore {
45 config: RedisStoreConfig,
46}
47
48impl RedisDocumentStore {
49 pub async fn new(config: RedisStoreConfig) -> Result<Self, VectorStoreError> {
51 let client = redis::Client::open(config.url.as_str())
52 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
53 let _ = client
55 .get_connection()
56 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
57 Ok(Self { config })
58 }
59
60 fn doc_key(&self, id: &str) -> String {
61 format!("{}:doc:{}", self.config.key_prefix, id)
62 }
63 fn chunk_key(&self, id: &str) -> String {
64 format!("{}:chunk:{}", self.config.key_prefix, id)
65 }
66 fn parent_chunks_key(&self, pid: &str) -> String {
67 format!("{}:pchunks:{}", self.config.key_prefix, pid)
68 }
69 fn doc_ids_key(&self) -> String {
70 format!("{}:doc_ids", self.config.key_prefix)
71 }
72 fn parent_ids_key(&self) -> String {
73 format!("{}:parent_ids", self.config.key_prefix)
74 }
75 fn all_chunks_key(&self) -> String {
76 format!("{}:all_chunks", self.config.key_prefix)
77 }
78}
79
80#[async_trait]
81impl DocumentStore for RedisDocumentStore {
82 async fn add_document(&self, document: Document) -> Result<String, VectorStoreError> {
83 let id = document
84 .id
85 .clone()
86 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
87 let json = serde_json::to_string(&document)
88 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
89 let config = self.config.clone();
90 let id2 = id.clone();
91
92 tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
93 let mut conn = redis::Client::open(config.url.as_str())
94 .and_then(|c| c.get_connection())
95 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
96 redis::cmd("SET")
97 .arg(format!("{}:doc:{}", config.key_prefix, id2))
98 .arg(&json)
99 .query::<()>(&mut conn)
100 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
101 redis::cmd("SADD")
102 .arg(format!("{}:doc_ids", config.key_prefix))
103 .arg(&id2)
104 .query::<()>(&mut conn)
105 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
106 Ok(())
107 })
108 .await
109 .map_err(|e| VectorStoreError::StorageError(e.to_string()))??;
110
111 Ok(id)
112 }
113
114 async fn add_documents(
115 &self,
116 documents: Vec<Document>,
117 ) -> Result<Vec<String>, VectorStoreError> {
118 let mut ids = Vec::new();
119 for doc in documents {
120 ids.push(self.add_document(doc).await?);
121 }
122 Ok(ids)
123 }
124
125 async fn get_document(&self, id: &str) -> Result<Option<Document>, VectorStoreError> {
126 let result = self.get_str(&self.doc_key(id)).await?;
127 match result {
128 Some(json) => match serde_json::from_str(&json) {
129 Ok(doc) => Ok(Some(doc)),
130 Err(e) => {
131 log::error!(
133 "failed to parse stored payload for document `{}` (corrupted or written by an older schema): {}",
134 id,
135 e
136 );
137 Ok(None)
138 }
139 },
140 None => Ok(None),
141 }
142 }
143
144 async fn delete_document(&self, id: &str) -> Result<(), VectorStoreError> {
145 self.del(&self.doc_key(id)).await?;
146 self.srem(&self.doc_ids_key(), id).await
147 }
148
149 async fn count(&self) -> usize {
150 self.scard(&self.doc_ids_key()).await.unwrap_or(0)
151 }
152
153 async fn clear(&self) -> Result<(), VectorStoreError> {
154 self.clear_with_prefix().await
156 }
157}
158
159impl RedisDocumentStore {
160 async fn get_str(&self, key: &str) -> Result<Option<String>, VectorStoreError> {
161 let config = self.config.clone();
162 let key = key.to_string();
163 tokio::task::spawn_blocking(move || -> Result<Option<String>, VectorStoreError> {
164 let mut conn = redis::Client::open(config.url.as_str())
165 .and_then(|c| c.get_connection())
166 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
167 redis::cmd("GET")
168 .arg(&key)
169 .query(&mut conn)
170 .map_err(|e| VectorStoreError::StorageError(e.to_string()))
171 })
172 .await
173 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
174 }
175
176 async fn del(&self, key: &str) -> Result<(), VectorStoreError> {
177 let config = self.config.clone();
178 let key = key.to_string();
179 tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
180 let mut conn = redis::Client::open(config.url.as_str())
181 .and_then(|c| c.get_connection())
182 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
183 redis::cmd("DEL")
184 .arg(&key)
185 .query::<()>(&mut conn)
186 .map_err(|e| VectorStoreError::StorageError(e.to_string()))
187 })
188 .await
189 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
190 }
191
192 async fn srem(&self, key: &str, member: &str) -> Result<(), VectorStoreError> {
193 let config = self.config.clone();
194 let (k, m) = (key.to_string(), member.to_string());
195 tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
196 let mut conn = redis::Client::open(config.url.as_str())
197 .and_then(|c| c.get_connection())
198 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
199 redis::cmd("SREM")
200 .arg(&k)
201 .arg(&m)
202 .query::<()>(&mut conn)
203 .map_err(|e| VectorStoreError::StorageError(e.to_string()))
204 })
205 .await
206 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
207 }
208
209 async fn smembers(&self, key: &str) -> Result<Vec<String>, VectorStoreError> {
210 let config = self.config.clone();
211 let key = key.to_string();
212 tokio::task::spawn_blocking(move || -> Result<Vec<String>, VectorStoreError> {
213 let mut conn = redis::Client::open(config.url.as_str())
214 .and_then(|c| c.get_connection())
215 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
216 redis::cmd("SMEMBERS")
217 .arg(&key)
218 .query(&mut conn)
219 .map_err(|e| VectorStoreError::StorageError(e.to_string()))
220 })
221 .await
222 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
223 }
224
225 async fn scard(&self, key: &str) -> Result<usize, VectorStoreError> {
226 let config = self.config.clone();
227 let key = key.to_string();
228 tokio::task::spawn_blocking(move || -> Result<usize, VectorStoreError> {
229 let mut conn = redis::Client::open(config.url.as_str())
230 .and_then(|c| c.get_connection())
231 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
232 redis::cmd("SCARD")
233 .arg(&key)
234 .query(&mut conn)
235 .map_err(|e| VectorStoreError::StorageError(e.to_string()))
236 })
237 .await
238 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
239 }
240
241 async fn clear_with_prefix(&self) -> Result<(), VectorStoreError> {
243 let config = self.config.clone();
244 tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
245 let mut conn = redis::Client::open(config.url.as_str())
246 .and_then(|c| c.get_connection())
247 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
248
249 let pattern = format!("{}:*", config.key_prefix);
250 let mut cursor: u64 = 0;
251
252 loop {
253 let (next_cursor, keys): (u64, Vec<String>) = redis::cmd("SCAN")
254 .arg(cursor)
255 .arg("MATCH")
256 .arg(&pattern)
257 .arg("COUNT")
258 .arg(100)
259 .query(&mut conn)
260 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
261
262 if !keys.is_empty() {
263 redis::cmd("DEL")
264 .arg(&keys)
265 .query::<()>(&mut conn)
266 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
267 }
268
269 cursor = next_cursor;
270 if cursor == 0 {
271 break;
272 }
273 }
274
275 Ok(())
276 })
277 .await
278 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
279 }
280
281 pub async fn save_to_disk(&self) -> Result<(), VectorStoreError> {
286 let config = self.config.clone();
287 tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
288 let mut conn = redis::Client::open(config.url.as_str())
289 .and_then(|c| c.get_connection())
290 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
291 redis::cmd("SAVE")
292 .query::<()>(&mut conn)
293 .map_err(|e| VectorStoreError::StorageError(e.to_string()))
294 })
295 .await
296 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
297 }
298}
299
300#[async_trait]
301impl ChunkedDocumentStoreTrait for RedisDocumentStore {
302 async fn add_parent_document(
303 &self,
304 document: Document,
305 chunk_size: usize,
306 ) -> Result<(String, Vec<String>), VectorStoreError> {
307 let splitter = RecursiveCharacterSplitter::new(chunk_size, chunk_size / 10);
308 let chunks_text = splitter.split_text(&document.content);
309 let parent_id = document
310 .id
311 .clone()
312 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
313 let doc_json = serde_json::to_string(&Document {
314 id: Some(parent_id.clone()),
315 ..document
316 })
317 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
318
319 let config = self.config.clone();
321 let pid = parent_id.clone();
322 let chunks = chunks_text.clone();
323
324 tokio::task::spawn_blocking(move || -> Result<(String, Vec<String>), VectorStoreError> {
325 let mut conn = redis::Client::open(config.url.as_str())
326 .and_then(|c| c.get_connection())
327 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
328
329 redis::cmd("SET")
330 .arg(format!("{}:doc:{}", config.key_prefix, pid))
331 .arg(&doc_json)
332 .query::<()>(&mut conn)
333 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
334
335 let mut chunk_ids = Vec::new();
336 for (i, text) in chunks.iter().enumerate() {
337 let cid = format!("{}:chunk:{}", pid, i);
338 let chunk = ChunkDocument::new(cid.clone(), pid.clone(), text.clone(), i);
339 let cjson = serde_json::to_string(&chunk)
340 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
341 redis::cmd("SET")
342 .arg(format!("{}:chunk:{}", config.key_prefix, cid))
343 .arg(&cjson)
344 .query::<()>(&mut conn)
345 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
346 redis::cmd("SADD")
347 .arg(format!("{}:pchunks:{}", config.key_prefix, pid))
348 .arg(&cid)
349 .query::<()>(&mut conn)
350 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
351 redis::cmd("SADD")
352 .arg(format!("{}:all_chunks", config.key_prefix))
353 .arg(&cid)
354 .query::<()>(&mut conn)
355 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
356 chunk_ids.push(cid);
357 }
358 redis::cmd("SADD")
359 .arg(format!("{}:parent_ids", config.key_prefix))
360 .arg(&pid)
361 .query::<()>(&mut conn)
362 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
363 redis::cmd("SADD")
364 .arg(format!("{}:doc_ids", config.key_prefix))
365 .arg(&pid)
366 .query::<()>(&mut conn)
367 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
368
369 Ok((pid, chunk_ids))
370 })
371 .await
372 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
373 }
374
375 async fn add_parent_documents(
376 &self,
377 documents: Vec<Document>,
378 chunk_size: usize,
379 ) -> Result<Vec<(String, Vec<String>)>, VectorStoreError> {
380 let mut results = Vec::new();
381 for doc in documents {
382 results.push(self.add_parent_document(doc, chunk_size).await?);
383 }
384 Ok(results)
385 }
386
387 async fn get_parent_document(
388 &self,
389 parent_id: &str,
390 ) -> Result<Option<Document>, VectorStoreError> {
391 self.get_document(parent_id).await
392 }
393
394 async fn get_chunk(&self, chunk_id: &str) -> Result<Option<ChunkDocument>, VectorStoreError> {
395 match self.get_str(&self.chunk_key(chunk_id)).await? {
396 Some(json) => match serde_json::from_str(&json) {
397 Ok(chunk) => Ok(Some(chunk)),
398 Err(e) => {
399 log::error!(
400 "failed to parse stored payload for chunk `{}` (corrupted or written by an older schema): {}",
401 chunk_id,
402 e
403 );
404 Ok(None)
405 }
406 },
407 None => Ok(None),
408 }
409 }
410
411 async fn get_chunk_document(
412 &self,
413 chunk_id: &str,
414 ) -> Result<Option<Document>, VectorStoreError> {
415 Ok(self.get_chunk(chunk_id).await?.map(|c| c.to_document()))
416 }
417
418 async fn get_chunks_for_parent(
419 &self,
420 parent_id: &str,
421 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
422 let ids = self.smembers(&self.parent_chunks_key(parent_id)).await?;
423 let mut chunks = Vec::new();
424 for id in ids {
425 if let Some(c) = self.get_chunk(&id).await? {
426 chunks.push(c);
427 }
428 }
429 chunks.sort_by_key(|c| c.segment);
430 Ok(chunks)
431 }
432
433 async fn get_chunk_documents_for_parent(
434 &self,
435 parent_id: &str,
436 ) -> Result<Vec<Document>, VectorStoreError> {
437 Ok(self
438 .get_chunks_for_parent(parent_id)
439 .await?
440 .into_iter()
441 .map(|c| c.to_document())
442 .collect())
443 }
444
445 async fn delete_parent_document(&self, parent_id: &str) -> Result<(), VectorStoreError> {
446 let chunks = self.get_chunks_for_parent(parent_id).await?;
447 for chunk in &chunks {
448 self.del(&self.chunk_key(&chunk.chunk_id)).await?;
449 }
450 self.del(&self.doc_key(parent_id)).await?;
451 self.del(&self.parent_chunks_key(parent_id)).await?;
452 self.srem(&self.doc_ids_key(), parent_id).await?;
453 self.srem(&self.parent_ids_key(), parent_id).await
454 }
455
456 async fn parent_count(&self) -> usize {
457 self.scard(&self.parent_ids_key()).await.unwrap_or(0)
458 }
459
460 async fn chunk_count(&self) -> usize {
461 self.scard(&self.all_chunks_key()).await.unwrap_or(0)
462 }
463
464 async fn get_all_chunks(&self) -> Result<Vec<ChunkDocument>, VectorStoreError> {
465 let ids = self.smembers(&self.all_chunks_key()).await?;
466 let mut chunks = Vec::new();
467 for id in ids {
468 if let Some(c) = self.get_chunk(&id).await? {
469 chunks.push(c);
470 }
471 }
472 Ok(chunks)
473 }
474
475 async fn clear(&self) -> Result<(), VectorStoreError> {
476 self.clear_with_prefix().await
478 }
479
480 fn add_parent_document_blocking(
481 &self,
482 _document: Document,
483 _chunk_size: usize,
484 ) -> Result<(String, Vec<String>), VectorStoreError> {
485 Err(VectorStoreError::StorageError(
486 "blocking not supported, use async API".to_string(),
487 ))
488 }
489
490 fn get_parent_document_blocking(
491 &self,
492 _parent_id: &str,
493 ) -> Result<Option<Document>, VectorStoreError> {
494 Err(VectorStoreError::StorageError(
495 "blocking not supported, use async API".to_string(),
496 ))
497 }
498
499 fn get_chunk_blocking(
500 &self,
501 _chunk_id: &str,
502 ) -> Result<Option<ChunkDocument>, VectorStoreError> {
503 Err(VectorStoreError::StorageError(
504 "blocking not supported, use async API".to_string(),
505 ))
506 }
507
508 fn blocking_get_chunks_for_parent(
509 &self,
510 _parent_id: &str,
511 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
512 Err(VectorStoreError::StorageError(
513 "blocking not supported, use async API".to_string(),
514 ))
515 }
516}