Skip to main content

lc_vector_stores/
redis_store.rs

1// lc-vector-stores/src/redis_store.rs
2//! Redis 文档存储实现
3
4use async_trait::async_trait;
5use lc_shared::splitter::{RecursiveCharacterSplitter, TextSplitter};
6use std::path::Path;
7
8use crate::document_store::{ChunkDocument, ChunkedDocumentStoreTrait, DocumentStore};
9use crate::{Document, VectorStoreError};
10
11#[derive(Debug, Clone)]
12pub struct RedisStoreConfig {
13    pub url: String,
14    pub key_prefix: String,
15}
16
17impl Default for RedisStoreConfig {
18    fn default() -> Self {
19        Self {
20            url: "redis://127.0.0.1:6379".to_string(),
21            key_prefix: "langchainrust".to_string(),
22        }
23    }
24}
25
26impl RedisStoreConfig {
27    pub fn new(url: impl Into<String>) -> Self {
28        Self {
29            url: url.into(),
30            ..Default::default()
31        }
32    }
33    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
34        self.key_prefix = prefix.into();
35        self
36    }
37}
38
39pub struct RedisDocumentStore {
40    config: RedisStoreConfig,
41    #[allow(dead_code)]
42    client: redis::Client,
43}
44
45impl RedisDocumentStore {
46    pub async fn new(config: RedisStoreConfig) -> Result<Self, VectorStoreError> {
47        let client = redis::Client::open(config.url.as_str())
48            .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
49        // 验证连接
50        let _ = client
51            .get_connection()
52            .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
53        Ok(Self { config, client })
54    }
55
56    fn doc_key(&self, id: &str) -> String {
57        format!("{}:doc:{}", self.config.key_prefix, id)
58    }
59    fn chunk_key(&self, id: &str) -> String {
60        format!("{}:chunk:{}", self.config.key_prefix, id)
61    }
62    fn parent_chunks_key(&self, pid: &str) -> String {
63        format!("{}:pchunks:{}", self.config.key_prefix, pid)
64    }
65    fn doc_ids_key(&self) -> String {
66        format!("{}:doc_ids", self.config.key_prefix)
67    }
68    fn parent_ids_key(&self) -> String {
69        format!("{}:parent_ids", self.config.key_prefix)
70    }
71    fn all_chunks_key(&self) -> String {
72        format!("{}:all_chunks", self.config.key_prefix)
73    }
74}
75
76#[async_trait]
77impl DocumentStore for RedisDocumentStore {
78    async fn add_document(&self, document: Document) -> Result<String, VectorStoreError> {
79        let id = document
80            .id
81            .clone()
82            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
83        let json = serde_json::to_string(&document)
84            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
85        let config = self.config.clone();
86        let id2 = id.clone();
87
88        tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
89            let mut conn = redis::Client::open(config.url.as_str())
90                .and_then(|c| c.get_connection())
91                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
92            redis::cmd("SET")
93                .arg(format!("{}:doc:{}", config.key_prefix, id2))
94                .arg(&json)
95                .query::<()>(&mut conn)
96                .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
97            redis::cmd("SADD")
98                .arg(format!("{}:doc_ids", config.key_prefix))
99                .arg(&id2)
100                .query::<()>(&mut conn)
101                .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
102            Ok(())
103        })
104        .await
105        .map_err(|e| VectorStoreError::StorageError(e.to_string()))??;
106
107        Ok(id)
108    }
109
110    async fn add_documents(
111        &self,
112        documents: Vec<Document>,
113    ) -> Result<Vec<String>, VectorStoreError> {
114        let mut ids = Vec::new();
115        for doc in documents {
116            ids.push(self.add_document(doc).await?);
117        }
118        Ok(ids)
119    }
120
121    async fn get_document(&self, id: &str) -> Result<Option<Document>, VectorStoreError> {
122        let result = self.get_str(&self.doc_key(id)).await?;
123        match result {
124            Some(json) => Ok(serde_json::from_str(&json).ok()),
125            None => Ok(None),
126        }
127    }
128
129    async fn delete_document(&self, id: &str) -> Result<(), VectorStoreError> {
130        self.del(&self.doc_key(id)).await?;
131        self.srem(&self.doc_ids_key(), id).await
132    }
133
134    async fn count(&self) -> usize {
135        self.scard(&self.doc_ids_key()).await.unwrap_or(0)
136    }
137
138    async fn clear(&self) -> Result<(), VectorStoreError> {
139        // M29: use SCAN + DEL with prefix instead of FLUSHDB to avoid wiping other keys
140        self.clear_with_prefix().await
141    }
142}
143
144impl RedisDocumentStore {
145    async fn get_str(&self, key: &str) -> Result<Option<String>, VectorStoreError> {
146        let config = self.config.clone();
147        let key = key.to_string();
148        tokio::task::spawn_blocking(move || -> Result<Option<String>, VectorStoreError> {
149            let mut conn = redis::Client::open(config.url.as_str())
150                .and_then(|c| c.get_connection())
151                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
152            redis::cmd("GET")
153                .arg(&key)
154                .query(&mut conn)
155                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
156        })
157        .await
158        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
159    }
160
161    async fn del(&self, key: &str) -> Result<(), VectorStoreError> {
162        let config = self.config.clone();
163        let key = key.to_string();
164        tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
165            let mut conn = redis::Client::open(config.url.as_str())
166                .and_then(|c| c.get_connection())
167                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
168            redis::cmd("DEL")
169                .arg(&key)
170                .query::<()>(&mut conn)
171                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
172        })
173        .await
174        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
175    }
176
177    #[allow(dead_code)]
178    async fn sadd(&self, key: &str, member: &str) -> Result<(), VectorStoreError> {
179        let config = self.config.clone();
180        let (k, m) = (key.to_string(), member.to_string());
181        tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
182            let mut conn = redis::Client::open(config.url.as_str())
183                .and_then(|c| c.get_connection())
184                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
185            redis::cmd("SADD")
186                .arg(&k)
187                .arg(&m)
188                .query::<()>(&mut conn)
189                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
190        })
191        .await
192        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
193    }
194
195    async fn srem(&self, key: &str, member: &str) -> Result<(), VectorStoreError> {
196        let config = self.config.clone();
197        let (k, m) = (key.to_string(), member.to_string());
198        tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
199            let mut conn = redis::Client::open(config.url.as_str())
200                .and_then(|c| c.get_connection())
201                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
202            redis::cmd("SREM")
203                .arg(&k)
204                .arg(&m)
205                .query::<()>(&mut conn)
206                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
207        })
208        .await
209        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
210    }
211
212    async fn smembers(&self, key: &str) -> Result<Vec<String>, VectorStoreError> {
213        let config = self.config.clone();
214        let key = key.to_string();
215        tokio::task::spawn_blocking(move || -> Result<Vec<String>, VectorStoreError> {
216            let mut conn = redis::Client::open(config.url.as_str())
217                .and_then(|c| c.get_connection())
218                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
219            redis::cmd("SMEMBERS")
220                .arg(&key)
221                .query(&mut conn)
222                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
223        })
224        .await
225        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
226    }
227
228    async fn scard(&self, key: &str) -> Result<usize, VectorStoreError> {
229        let config = self.config.clone();
230        let key = key.to_string();
231        tokio::task::spawn_blocking(move || -> Result<usize, VectorStoreError> {
232            let mut conn = redis::Client::open(config.url.as_str())
233                .and_then(|c| c.get_connection())
234                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
235            redis::cmd("SCARD")
236                .arg(&key)
237                .query(&mut conn)
238                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
239        })
240        .await
241        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
242    }
243
244    #[allow(dead_code)]
245    async fn flushdb(&self) -> Result<(), VectorStoreError> {
246        let config = self.config.clone();
247        tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
248            let mut conn = redis::Client::open(config.url.as_str())
249                .and_then(|c| c.get_connection())
250                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
251            redis::cmd("FLUSHDB")
252                .query::<()>(&mut conn)
253                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
254        })
255        .await
256        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
257    }
258
259    /// M29: Clear only keys with the configured prefix using SCAN + DEL
260    async fn clear_with_prefix(&self) -> Result<(), VectorStoreError> {
261        let config = self.config.clone();
262        tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
263            let mut conn = redis::Client::open(config.url.as_str())
264                .and_then(|c| c.get_connection())
265                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
266
267            let pattern = format!("{}:*", config.key_prefix);
268            let mut cursor: u64 = 0;
269
270            loop {
271                let (next_cursor, keys): (u64, Vec<String>) = redis::cmd("SCAN")
272                    .arg(cursor)
273                    .arg("MATCH")
274                    .arg(&pattern)
275                    .arg("COUNT")
276                    .arg(100)
277                    .query(&mut conn)
278                    .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
279
280                if !keys.is_empty() {
281                    redis::cmd("DEL")
282                        .arg(&keys)
283                        .query::<()>(&mut conn)
284                        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
285                }
286
287                cursor = next_cursor;
288                if cursor == 0 {
289                    break;
290                }
291            }
292
293            Ok(())
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        // 使用 spawn_blocking 执行所有 Redis 操作
320        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) => Ok(serde_json::from_str(&json).ok()),
397            None => Ok(None),
398        }
399    }
400
401    async fn get_chunk_document(
402        &self,
403        chunk_id: &str,
404    ) -> Result<Option<Document>, VectorStoreError> {
405        Ok(self.get_chunk(chunk_id).await?.map(|c| c.to_document()))
406    }
407
408    async fn get_chunks_for_parent(
409        &self,
410        parent_id: &str,
411    ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
412        let ids = self.smembers(&self.parent_chunks_key(parent_id)).await?;
413        let mut chunks = Vec::new();
414        for id in ids {
415            if let Some(c) = self.get_chunk(&id).await? {
416                chunks.push(c);
417            }
418        }
419        chunks.sort_by_key(|c| c.segment);
420        Ok(chunks)
421    }
422
423    async fn get_chunk_documents_for_parent(
424        &self,
425        parent_id: &str,
426    ) -> Result<Vec<Document>, VectorStoreError> {
427        Ok(self
428            .get_chunks_for_parent(parent_id)
429            .await?
430            .into_iter()
431            .map(|c| c.to_document())
432            .collect())
433    }
434
435    async fn delete_parent_document(&self, parent_id: &str) -> Result<(), VectorStoreError> {
436        let chunks = self.get_chunks_for_parent(parent_id).await?;
437        for chunk in &chunks {
438            self.del(&self.chunk_key(&chunk.chunk_id)).await?;
439        }
440        self.del(&self.doc_key(parent_id)).await?;
441        self.del(&self.parent_chunks_key(parent_id)).await?;
442        self.srem(&self.doc_ids_key(), parent_id).await?;
443        self.srem(&self.parent_ids_key(), parent_id).await
444    }
445
446    async fn parent_count(&self) -> usize {
447        self.scard(&self.parent_ids_key()).await.unwrap_or(0)
448    }
449
450    async fn chunk_count(&self) -> usize {
451        self.scard(&self.all_chunks_key()).await.unwrap_or(0)
452    }
453
454    async fn get_all_chunks(&self) -> Result<Vec<ChunkDocument>, VectorStoreError> {
455        let ids = self.smembers(&self.all_chunks_key()).await?;
456        let mut chunks = Vec::new();
457        for id in ids {
458            if let Some(c) = self.get_chunk(&id).await? {
459                chunks.push(c);
460            }
461        }
462        Ok(chunks)
463    }
464
465    async fn clear(&self) -> Result<(), VectorStoreError> {
466        // M29: use SCAN + DEL with prefix instead of FLUSHDB to avoid wiping other keys
467        self.clear_with_prefix().await
468    }
469
470    async fn save(&self, _path: impl AsRef<Path> + Send) -> Result<(), VectorStoreError> {
471        let config = self.config.clone();
472        tokio::task::spawn_blocking(move || -> Result<(), VectorStoreError> {
473            let mut conn = redis::Client::open(config.url.as_str())
474                .and_then(|c| c.get_connection())
475                .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
476            redis::cmd("SAVE")
477                .query::<()>(&mut conn)
478                .map_err(|e| VectorStoreError::StorageError(e.to_string()))
479        })
480        .await
481        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
482    }
483
484    fn add_parent_document_blocking(
485        &self,
486        _document: Document,
487        _chunk_size: usize,
488    ) -> Result<(String, Vec<String>), VectorStoreError> {
489        Err(VectorStoreError::StorageError(
490            "blocking not supported, use async API".to_string(),
491        ))
492    }
493
494    fn get_parent_document_blocking(
495        &self,
496        _parent_id: &str,
497    ) -> Result<Option<Document>, VectorStoreError> {
498        Err(VectorStoreError::StorageError(
499            "blocking not supported, use async API".to_string(),
500        ))
501    }
502
503    fn get_chunk_blocking(
504        &self,
505        _chunk_id: &str,
506    ) -> Result<Option<ChunkDocument>, VectorStoreError> {
507        Err(VectorStoreError::StorageError(
508            "blocking not supported, use async API".to_string(),
509        ))
510    }
511
512    fn blocking_get_chunks_for_parent(
513        &self,
514        _parent_id: &str,
515    ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
516        Err(VectorStoreError::StorageError(
517            "blocking not supported, use async API".to_string(),
518        ))
519    }
520}