use super::{Document, SearchResult, VectorStore, VectorStoreError};
use async_trait::async_trait;
use std::sync::Arc;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct QdrantConfig {
pub url: String,
pub collection_name: String,
pub vector_size: usize,
}
impl QdrantConfig {
pub fn new(url: impl Into<String>, collection: impl Into<String>, vector_size: usize) -> Self {
Self {
url: url.into(),
collection_name: collection.into(),
vector_size,
}
}
}
pub struct QdrantVectorStore {
config: QdrantConfig,
_qdrant_client_placeholder: String, }
impl QdrantVectorStore {
pub async fn new(config: QdrantConfig) -> Result<Self, VectorStoreError> {
println!("注意: 尝试连接到 Qdrant 在: {}", config.url);
println!("请确保已在 Linux 上启动 Qdrant 并提供正确的 URL");
if !config.url.contains("://") {
return Err(VectorStoreError::ConnectionError(
"Qdrant URL 格式无效,应为 http://host:port 形式".to_string()
));
}
Ok(Self {
config,
_qdrant_client_placeholder: "placeholder".to_string(),
})
}
pub async fn from_env(vector_size: usize) -> Result<Self, VectorStoreError> {
let url = std::env::var("QDRANT_URL")
.unwrap_or_else(|_| "http://localhost:6334".to_string());
let collection = std::env::var("QDRANT_COLLECTION")
.unwrap_or_else(|_| "langchainrust".to_string());
Self::new(QdrantConfig::new(url, collection, vector_size)).await
}
}
#[async_trait]
impl VectorStore for QdrantVectorStore {
async fn add_documents(
&self,
documents: Vec<Document>,
embeddings: Vec<Vec<f32>>,
) -> Result<Vec<String>, VectorStoreError> {
eprintln!("警告: 使用 Qdrant 占位实现。在获得实际服务前回退到内存操作。");
if documents.len() != embeddings.len() {
return Err(VectorStoreError::StorageError(
"文档数量和嵌入向量数量不匹配".to_string()
));
}
for embedding in &embeddings {
if embedding.len() != self.config.vector_size {
return Err(VectorStoreError::StorageError(format!(
"向量维度不匹配: 期望 {}, 实际 {}",
self.config.vector_size,
embedding.len()
)));
}
}
use uuid::Uuid;
let ids: Vec<String> = documents.iter()
.map(|_| Uuid::new_v4().to_string())
.collect();
Ok(ids)
}
async fn similarity_search(
&self,
query_embedding: &[f32],
k: usize,
) -> Result<Vec<SearchResult>, VectorStoreError> {
eprintln!("警告: 使用 Qdrant 占位实现。在获得实际服务前无法返回真实结果。");
if query_embedding.len() != self.config.vector_size {
return Err(VectorStoreError::StorageError(format!(
"查询向量维度不匹配: 期望 {}, 实际 {}",
self.config.vector_size,
query_embedding.len()
)));
}
Ok(vec![])
}
async fn get_document(&self, id: &str) -> Result<Option<Document>, VectorStoreError> {
eprintln!("警告: Qdrant 服务未连接,无法获取文档");
Ok(None) }
async fn delete_document(&self, id: &str) -> Result<(), VectorStoreError> {
eprintln!("警告: Qdrant 服务未连接,无法删除文档");
Ok(()) }
async fn count(&self) -> usize {
eprintln!("警告: Qdrant 服务未连接,无法获取计数");
0 }
async fn clear(&self) -> Result<(), VectorStoreError> {
eprintln!("警告: Qdrant 服务未连接,无法清空");
Ok(()) }
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore = "需要实际的 Qdrant 服务地址"]
async fn test_qdrant_integration() {
let config = QdrantConfig::new("http://your-actual-url:6334", "test_collection", 1536);
let store = QdrantVectorStore::new(config).await;
assert!(store.is_ok());
}
#[tokio::test]
async fn test_config_creation() {
let config = QdrantConfig::new("http://localhost:6334", "my_collection", 128);
assert_eq!(config.url, "http://localhost:6334");
assert_eq!(config.collection_name, "my_collection");
assert_eq!(config.vector_size, 128);
}
#[test]
fn test_invalid_url_format() {
let result = tokio_test::block_on(async {
QdrantVectorStore::new(QdrantConfig::new("invalid-url", "test", 128)).await
});
assert!(result.is_err());
match result.unwrap_err() {
VectorStoreError::ConnectionError(_) => {},
_ => panic!("Expected ConnectionError"),
}
}
}