1use crate::{Document, FilterOp, MetadataFilter, SearchResult, VectorStore, VectorStoreError};
5use async_trait::async_trait;
6use qdrant_client::{
7 qdrant::{
8 Condition, CreateCollectionBuilder, DeletePointsBuilder, Distance, Filter, PointId,
9 PointStruct, QueryPointsBuilder, Range, UpsertPointsBuilder, VectorParamsBuilder,
10 },
11 Payload, Qdrant,
12};
13use serde_json::Value;
14use std::collections::HashMap;
15use std::sync::Arc;
16use uuid::Uuid;
17
18#[derive(Debug, Clone)]
20pub struct QdrantConfig {
21 pub url: String,
23 pub collection_name: String,
25 pub vector_size: usize,
27 pub distance: QdrantDistance,
29}
30
31#[derive(Debug, Clone, Copy)]
33pub enum QdrantDistance {
34 Cosine,
36 Euclid,
38 Dot,
40}
41
42impl From<QdrantDistance> for Distance {
43 fn from(dist: QdrantDistance) -> Self {
44 match dist {
45 QdrantDistance::Cosine => Distance::Cosine,
46 QdrantDistance::Euclid => Distance::Euclid,
47 QdrantDistance::Dot => Distance::Dot,
48 }
49 }
50}
51
52impl Default for QdrantConfig {
53 fn default() -> Self {
54 Self {
55 url: "http://localhost:6334".to_string(),
56 collection_name: "langchainrust".to_string(),
57 vector_size: 1536,
58 distance: QdrantDistance::Cosine,
59 }
60 }
61}
62
63impl QdrantConfig {
64 pub fn new(url: impl Into<String>, collection_name: impl Into<String>) -> Self {
66 Self {
67 url: url.into(),
68 collection_name: collection_name.into(),
69 ..Default::default()
70 }
71 }
72
73 pub fn with_vector_size(mut self, size: usize) -> Self {
75 self.vector_size = size;
76 self
77 }
78
79 pub fn with_distance(mut self, distance: QdrantDistance) -> Self {
81 self.distance = distance;
82 self
83 }
84}
85
86pub struct QdrantVectorStore {
88 client: Arc<Qdrant>,
89 config: QdrantConfig,
90}
91
92impl QdrantVectorStore {
93 pub async fn new(config: QdrantConfig) -> Result<Self, VectorStoreError> {
95 let client = Qdrant::from_url(&config.url).build().map_err(|e| {
96 VectorStoreError::ConnectionError(format!("failed to connect to Qdrant: {}", e))
97 })?;
98
99 let client = Arc::new(client);
100
101 let exists = client
102 .collection_exists(&config.collection_name)
103 .await
104 .map_err(|e| {
105 VectorStoreError::StorageError(format!("failed to check collection: {}", e))
106 })?;
107
108 if !exists {
109 client
110 .create_collection(
111 CreateCollectionBuilder::new(&config.collection_name).vectors_config(
112 VectorParamsBuilder::new(
113 config.vector_size as u64,
114 Distance::from(config.distance),
115 ),
116 ),
117 )
118 .await
119 .map_err(|e| {
120 VectorStoreError::StorageError(format!("failed to create collection: {}", e))
121 })?;
122 }
123
124 Ok(Self { client, config })
125 }
126
127 pub async fn from_env() -> Result<Self, VectorStoreError> {
129 let url =
130 std::env::var("QDRANT_URL").unwrap_or_else(|_| "http://localhost:6334".to_string());
131 let collection_name =
132 std::env::var("QDRANT_COLLECTION").unwrap_or_else(|_| "langchainrust".to_string());
133
134 Self::new(QdrantConfig::new(url, collection_name)).await
135 }
136
137 pub async fn delete_by_metadata(
139 &self,
140 key: &str,
141 value: &str,
142 ) -> Result<usize, VectorStoreError> {
143 let filter = Filter::must([Condition::matches(key, value.to_string())]);
144
145 let total = self.count().await as u64;
148 let matched = self
149 .client
150 .query(
151 QueryPointsBuilder::new(&self.config.collection_name)
152 .query(vec![0.0; self.config.vector_size])
153 .filter(filter.clone())
154 .limit(total.max(1))
155 .with_payload(false),
156 )
157 .await
158 .map_err(|e| {
159 VectorStoreError::StorageError(format!(
160 "failed to count matching points by metadata: {}",
161 e
162 ))
163 })?;
164
165 let deleted = matched.result.len();
166
167 if deleted > 0 {
168 self.client
169 .delete_points(
170 DeletePointsBuilder::new(&self.config.collection_name).points(filter),
171 )
172 .await
173 .map_err(|e| {
174 VectorStoreError::StorageError(format!(
175 "failed to delete points by metadata: {}",
176 e
177 ))
178 })?;
179 }
180
181 Ok(deleted)
182 }
183
184 fn build_query_builder(
189 &self,
190 query_embedding: &[f32],
191 k: usize,
192 filter: Option<&MetadataFilter>,
193 ) -> Result<QueryPointsBuilder, VectorStoreError> {
194 if query_embedding.len() != self.config.vector_size {
195 return Err(VectorStoreError::StorageError(format!(
196 "query vector dimension mismatch: expected {}, got {}",
197 self.config.vector_size,
198 query_embedding.len()
199 )));
200 }
201
202 let mut builder = QueryPointsBuilder::new(&self.config.collection_name)
203 .query(query_embedding.to_vec())
204 .limit(k as u64)
205 .with_payload(true);
206
207 if let Some(f) = filter {
208 builder = builder.filter(filter_to_qdrant(f)?);
209 }
210
211 Ok(builder)
212 }
213
214 async fn search_impl(
216 &self,
217 builder: QueryPointsBuilder,
218 ) -> Result<Vec<SearchResult>, VectorStoreError> {
219 let search_result = self
220 .client
221 .query(builder)
222 .await
223 .map_err(|e| VectorStoreError::StorageError(format!("search failed: {}", e)))?;
224
225 let results: Vec<SearchResult> = search_result
226 .result
227 .into_iter()
228 .map(|scored_point| {
229 let payload = scored_point.payload;
230
231 let content = payload
232 .get("content")
233 .and_then(|v| v.as_str())
234 .map(|s| s.as_str())
235 .unwrap_or("")
236 .to_string();
237
238 let id = payload
239 .get("doc_id")
240 .and_then(|v| v.as_str())
241 .map(|s| s.to_string());
242
243 let mut metadata = HashMap::new();
244 for (key, value) in &payload {
245 if key != "content" && key != "doc_id" {
246 if let Some(s) = value.as_str() {
247 metadata.insert(key.clone(), s.clone().into());
248 }
249 }
250 }
251
252 SearchResult {
253 document: Document {
254 content,
255 metadata,
256 id,
257 },
258 score: scored_point.score,
259 }
260 })
261 .collect();
262
263 Ok(results)
264 }
265}
266
267fn match_condition(key: &str, value: &Value) -> Result<Condition, VectorStoreError> {
278 match value {
279 Value::String(s) => Ok(Condition::matches(key, s.clone())),
280 Value::Bool(b) => Ok(Condition::matches(key, *b)),
281 Value::Number(n) => {
282 let int = n
283 .as_i64()
284 .or_else(|| n.as_f64().filter(|f| f.fract() == 0.0).map(|f| f as i64));
285 match int {
286 Some(i) => Ok(Condition::matches(key, i)),
287 None => Err(VectorStoreError::UnsupportedFilter(format!(
288 "Qdrant match condition requires an integer, string, or boolean value, got {n}"
289 ))),
290 }
291 }
292 other => Err(VectorStoreError::UnsupportedFilter(format!(
293 "Qdrant match condition requires a scalar value, got {other}"
294 ))),
295 }
296}
297
298fn field_to_condition(
304 key: &str,
305 op: FilterOp,
306 value: &Value,
307) -> Result<Condition, VectorStoreError> {
308 match op {
309 FilterOp::Eq => match_condition(key, value),
310 FilterOp::Ne => Ok(Condition::from(Filter::must_not([match_condition(
311 key, value,
312 )?]))),
313 FilterOp::Gt | FilterOp::Gte | FilterOp::Lt | FilterOp::Lte => {
314 let num = value.as_f64().ok_or_else(|| {
315 VectorStoreError::UnsupportedFilter(format!(
316 "Qdrant range condition requires a numeric value, got {value}"
317 ))
318 })?;
319 let mut range = Range::default();
320 match op {
321 FilterOp::Gt => range.gt = Some(num),
322 FilterOp::Gte => range.gte = Some(num),
323 FilterOp::Lt => range.lt = Some(num),
324 FilterOp::Lte => range.lte = Some(num),
325 _ => unreachable!(),
326 }
327 Ok(Condition::range(key, range))
328 }
329 FilterOp::In | FilterOp::Nin => {
330 let set = value.as_array().ok_or_else(|| {
331 VectorStoreError::UnsupportedFilter(format!(
332 "Qdrant {:?} condition requires an array value, got {value}",
333 op
334 ))
335 })?;
336 if set.is_empty() && op == FilterOp::In {
338 return Err(VectorStoreError::UnsupportedFilter(
339 "Qdrant In condition with an empty array cannot be expressed".to_string(),
340 ));
341 }
342 let conds: Result<Vec<Condition>, _> =
343 set.iter().map(|v| match_condition(key, v)).collect();
344 match op {
345 FilterOp::In => Ok(Condition::from(Filter::should(conds?))),
346 FilterOp::Nin => Ok(Condition::from(Filter::must_not(conds?))),
347 _ => unreachable!(),
348 }
349 }
350 }
351}
352
353fn to_condition(filter: &MetadataFilter) -> Result<Condition, VectorStoreError> {
360 match filter {
361 MetadataFilter::Field { key, op, value } => field_to_condition(key, *op, value),
362 MetadataFilter::And(items) => {
363 let conds: Result<Vec<Condition>, _> = items.iter().map(to_condition).collect();
364 Ok(Condition::from(Filter::must(conds?)))
365 }
366 MetadataFilter::Or(items) => {
367 let conds: Result<Vec<Condition>, _> = items.iter().map(to_condition).collect();
368 Ok(Condition::from(Filter::should(conds?)))
369 }
370 }
371}
372
373pub fn filter_to_qdrant(filter: &MetadataFilter) -> Result<Filter, VectorStoreError> {
378 Ok(Filter::must([to_condition(filter)?]))
379}
380
381#[async_trait]
382impl VectorStore for QdrantVectorStore {
383 async fn add_documents(
384 &self,
385 documents: Vec<Document>,
386 embeddings: Vec<Vec<f32>>,
387 ) -> Result<Vec<String>, VectorStoreError> {
388 if documents.len() != embeddings.len() {
389 return Err(VectorStoreError::StorageError(
390 "document count and embedding count mismatch".to_string(),
391 ));
392 }
393
394 if documents.is_empty() {
395 return Ok(Vec::new());
396 }
397
398 for embedding in &embeddings {
399 if embedding.len() != self.config.vector_size {
400 return Err(VectorStoreError::StorageError(format!(
401 "vector dimension mismatch: expected {}, got {}",
402 self.config.vector_size,
403 embedding.len()
404 )));
405 }
406 }
407
408 let mut ids = Vec::new();
409 let mut points = Vec::new();
410
411 for (doc, embedding) in documents.into_iter().zip(embeddings) {
412 let user_id = doc.id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
413
414 let internal_uuid = Uuid::new_v4();
416 let point_id = PointId::from(internal_uuid.to_string());
417
418 let mut payload = Payload::new();
419 payload.insert("content", doc.content.clone());
420 payload.insert("doc_id", user_id.clone()); for (key, value) in &doc.metadata {
423 payload.insert(key.clone(), value.clone());
424 }
425
426 let point = PointStruct::new(point_id, embedding, payload);
427 points.push(point);
428 ids.push(user_id);
429 }
430
431 self.client
432 .upsert_points(UpsertPointsBuilder::new(
433 &self.config.collection_name,
434 points,
435 ))
436 .await
437 .map_err(|e| {
438 VectorStoreError::StorageError(format!("failed to insert documents: {}", e))
439 })?;
440
441 Ok(ids)
442 }
443
444 async fn similarity_search(
445 &self,
446 query_embedding: &[f32],
447 k: usize,
448 ) -> Result<Vec<SearchResult>, VectorStoreError> {
449 let builder = self.build_query_builder(query_embedding, k, None)?;
450 self.search_impl(builder).await
451 }
452
453 async fn similarity_search_with_filter(
455 &self,
456 query_embedding: &[f32],
457 k: usize,
458 filter: Option<&MetadataFilter>,
459 ) -> Result<Vec<SearchResult>, VectorStoreError> {
460 let builder = self.build_query_builder(query_embedding, k, filter)?;
461 self.search_impl(builder).await
462 }
463
464 async fn get_document(&self, id: &str) -> Result<Option<Document>, VectorStoreError> {
465 let filter = Filter::must([Condition::matches("doc_id", id.to_string())]);
466
467 let results = self
468 .client
469 .query(
470 QueryPointsBuilder::new(&self.config.collection_name)
471 .query(vec![0.0; self.config.vector_size])
472 .filter(filter)
473 .limit(1)
474 .with_payload(true),
475 )
476 .await
477 .map_err(|e| {
478 VectorStoreError::StorageError(format!("failed to get document: {}", e))
479 })?;
480
481 if let Some(point) = results.result.first() {
482 let payload_map = point.payload.clone();
483
484 let content = payload_map
485 .get("content")
486 .and_then(|v| v.as_str())
487 .map(|s| s.as_str())
488 .unwrap_or("")
489 .to_string();
490
491 let doc_id = payload_map
492 .get("doc_id")
493 .and_then(|v| v.as_str())
494 .map(|s| s.to_string());
495
496 let mut metadata = HashMap::new();
497 for (key, value) in &payload_map {
498 if key != "content" && key != "doc_id" {
499 if let Some(s) = value.as_str() {
500 metadata.insert(key.clone(), s.clone().into());
501 }
502 }
503 }
504
505 Ok(Some(Document {
506 content,
507 metadata,
508 id: doc_id,
509 }))
510 } else {
511 Ok(None)
512 }
513 }
514
515 async fn get_embedding(&self, id: &str) -> Result<Option<Vec<f32>>, VectorStoreError> {
516 let filter = Filter::must([Condition::matches("doc_id", id.to_string())]);
517
518 let results = self
519 .client
520 .query(
521 QueryPointsBuilder::new(&self.config.collection_name)
522 .query(vec![0.0; self.config.vector_size])
523 .filter(filter)
524 .limit(1)
525 .with_payload(true),
526 )
527 .await
528 .map_err(|e| VectorStoreError::StorageError(format!("failed to get vector: {}", e)))?;
529
530 if let Some(point) = results.result.first() {
531 if let Some(vectors) = &point.vectors {
532 if let Some(qdrant_client::qdrant::vector_output::Vector::Dense(dense)) =
533 vectors.get_vector()
534 {
535 return Ok(Some(dense.data.clone()));
536 }
537 }
538 }
539 Ok(None)
540 }
541
542 async fn delete_document(&self, id: &str) -> Result<(), VectorStoreError> {
543 let filter = Filter::must([Condition::matches("doc_id", id.to_string())]);
544
545 self.client
546 .delete_points(DeletePointsBuilder::new(&self.config.collection_name).points(filter))
547 .await
548 .map_err(|e| {
549 VectorStoreError::StorageError(format!("failed to delete document: {}", e))
550 })?;
551
552 Ok(())
553 }
554
555 async fn count(&self) -> usize {
556 let info = self
557 .client
558 .collection_info(&self.config.collection_name)
559 .await;
560
561 info.map(|i| i.result.and_then(|r| r.points_count).unwrap_or(0) as usize)
562 .unwrap_or(0)
563 }
564
565 async fn clear(&self) -> Result<(), VectorStoreError> {
566 let collection_name = self.config.collection_name.clone();
567
568 self.client
569 .delete_collection(&collection_name)
570 .await
571 .map_err(|e| {
572 VectorStoreError::StorageError(format!("failed to delete collection: {}", e))
573 })?;
574
575 self.client
576 .create_collection(
577 CreateCollectionBuilder::new(&collection_name).vectors_config(
578 VectorParamsBuilder::new(
579 self.config.vector_size as u64,
580 Distance::from(self.config.distance),
581 ),
582 ),
583 )
584 .await
585 .map_err(|e| {
586 VectorStoreError::StorageError(format!("failed to recreate collection: {}", e))
587 })?;
588
589 Ok(())
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
598 fn test_config_default() {
599 let config = QdrantConfig::default();
600 assert_eq!(config.url, "http://localhost:6334");
601 assert_eq!(config.collection_name, "langchainrust");
602 assert_eq!(config.vector_size, 1536);
603 }
604
605 #[test]
606 fn test_config_builder() {
607 let config = QdrantConfig::new("http://custom:6334", "test_collection")
608 .with_vector_size(3072)
609 .with_distance(QdrantDistance::Euclid);
610
611 assert_eq!(config.url, "http://custom:6334");
612 assert_eq!(config.collection_name, "test_collection");
613 assert_eq!(config.vector_size, 3072);
614 assert!(matches!(config.distance, QdrantDistance::Euclid));
615 }
616
617 #[test]
619 fn test_filter_to_qdrant_eq() {
620 let f = filter_to_qdrant(&MetadataFilter::field("lang", FilterOp::Eq, "rust")).unwrap();
621 let expected = Filter::must([Condition::matches("lang", "rust".to_string())]);
622 assert_eq!(f, expected);
623 }
624
625 #[test]
627 fn test_filter_to_qdrant_ne_number() {
628 let f = filter_to_qdrant(&MetadataFilter::field("year", FilterOp::Ne, 2020.0)).unwrap();
629 let expected = Filter::must([Condition::from(Filter::must_not([Condition::matches(
630 "year", 2020_i64,
631 )]))]);
632 assert_eq!(f, expected);
633 }
634
635 #[test]
637 fn test_filter_to_qdrant_range() {
638 let f = filter_to_qdrant(&MetadataFilter::field("year", FilterOp::Gte, 2020)).unwrap();
639 let expected = Filter::must([Condition::range(
640 "year",
641 Range {
642 gte: Some(2020.0),
643 ..Default::default()
644 },
645 )]);
646 assert_eq!(f, expected);
647 }
648
649 #[test]
651 fn test_filter_to_qdrant_in_nin() {
652 let f =
653 filter_to_qdrant(&MetadataFilter::field("tag", FilterOp::In, vec!["a", "b"])).unwrap();
654 let expected = Filter::must([Condition::from(Filter::should([
655 Condition::matches("tag", "a".to_string()),
656 Condition::matches("tag", "b".to_string()),
657 ]))]);
658 assert_eq!(f, expected);
659
660 let f = filter_to_qdrant(&MetadataFilter::field("tag", FilterOp::Nin, vec!["a"])).unwrap();
661 let expected = Filter::must([Condition::from(Filter::must_not([Condition::matches(
662 "tag",
663 "a".to_string(),
664 )]))]);
665 assert_eq!(f, expected);
666 }
667
668 #[test]
670 fn test_filter_to_qdrant_and_or() {
671 let f = MetadataFilter::and(vec![
672 MetadataFilter::field("lang", FilterOp::Eq, "rust"),
673 MetadataFilter::or(vec![
674 MetadataFilter::field("year", FilterOp::Gte, 2020),
675 MetadataFilter::field("tag", FilterOp::In, vec!["ml"]),
676 ]),
677 ]);
678 let expected = Filter::must([Condition::from(Filter::must([
679 Condition::matches("lang", "rust".to_string()),
680 Condition::from(Filter::should([
681 Condition::range(
682 "year",
683 Range {
684 gte: Some(2020.0),
685 ..Default::default()
686 },
687 ),
688 Condition::from(Filter::should([Condition::matches(
689 "tag",
690 "ml".to_string(),
691 )])),
692 ])),
693 ]))]);
694 assert_eq!(filter_to_qdrant(&f).unwrap(), expected);
695 }
696
697 #[test]
699 fn test_filter_to_qdrant_unsupported() {
700 let float_eq = filter_to_qdrant(&MetadataFilter::field("score", FilterOp::Eq, 0.5));
702 assert!(matches!(
703 float_eq,
704 Err(VectorStoreError::UnsupportedFilter(_))
705 ));
706
707 let range_on_str = filter_to_qdrant(&MetadataFilter::field("year", FilterOp::Gt, "abc"));
709 assert!(matches!(
710 range_on_str,
711 Err(VectorStoreError::UnsupportedFilter(_))
712 ));
713
714 let empty_in = filter_to_qdrant(&MetadataFilter::field(
716 "tag",
717 FilterOp::In,
718 Vec::<String>::new(),
719 ));
720 assert!(matches!(
721 empty_in,
722 Err(VectorStoreError::UnsupportedFilter(_))
723 ));
724 }
725}