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};
6
7use crate::document_store::{ChunkDocument, ChunkedDocumentStoreTrait, DocumentStore};
8use crate::{Document, VectorStoreError};
9
10/// Redis 文档存储配置
11#[derive(Debug, Clone)]
12pub struct RedisStoreConfig {
13    /// Redis 连接地址
14    pub url: String,
15    /// 键前缀
16    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    /// 使用连接地址创建配置,键前缀取默认值。
30    pub fn new(url: impl Into<String>) -> Self {
31        Self {
32            url: url.into(),
33            ..Default::default()
34        }
35    }
36    /// 设置键前缀。
37    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
38        self.key_prefix = prefix.into();
39        self
40    }
41}
42
43/// Redis 文档存储实现
44pub struct RedisDocumentStore {
45    config: RedisStoreConfig,
46}
47
48impl RedisDocumentStore {
49    /// 根据配置连接 Redis 并验证连接可用,创建存储实例。
50    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        // 验证连接(客户端不保留,后续操作按需重连)
54        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                    // 键存在但载荷损坏/由旧 schema 写入,区别于真实 miss
132                    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        // M29: use SCAN + DEL with prefix instead of FLUSHDB to avoid wiping other keys
155        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    /// M29: Clear only keys with the configured prefix using SCAN + DEL
242    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    /// 将 Redis 数据集持久化到磁盘 (RDB 快照,`SAVE` 命令)。
282    ///
283    /// C3: 原 `ChunkedDocumentStoreTrait::save` 为假默认方法,已从 trait 删除;
284    /// Redis 的持久化语义是整库 RDB 快照,与文件路径无关,故改为无参固有方法。
285    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        // 使用 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) => 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        // M29: use SCAN + DEL with prefix instead of FLUSHDB to avoid wiping other keys
477        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}