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