1use 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 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 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 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 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 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 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}