#![cfg(feature = "qdrant")]
use embedd::{EmbedMode, TextEmbedder};
use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, VectorParamsBuilder};
use qdrant_client::Qdrant;
struct FixedDimEmbedder {
dim: usize,
}
impl TextEmbedder for FixedDimEmbedder {
fn embed_texts(&self, texts: &[String], _mode: EmbedMode) -> anyhow::Result<Vec<Vec<f32>>> {
Ok(texts
.iter()
.map(|t| {
let mut vec = vec![0.0f32; self.dim];
for (i, c) in t.chars().enumerate() {
vec[i % self.dim] += (c as u32) as f32 / 1000.0;
}
let norm: f32 = vec.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 1e-9 {
for x in &mut vec {
*x /= norm;
}
}
vec
})
.collect())
}
fn dimension(&self) -> Option<usize> {
Some(self.dim)
}
}
async fn qdrant_client() -> Option<Qdrant> {
let client = Qdrant::from_url("http://localhost:6334").build().ok()?;
client.health_check().await.ok()?;
Some(client)
}
#[tokio::test]
async fn embed_upsert_and_search_roundtrip() {
let Some(client) = qdrant_client().await else {
eprintln!("skipping: Qdrant not available on localhost:6334");
return;
};
let collection = "embedd_test_e2e";
let dim = 32;
let embedder = FixedDimEmbedder { dim };
let _ = client.delete_collection(collection).await;
client
.create_collection(
CreateCollectionBuilder::new(collection)
.vectors_config(VectorParamsBuilder::new(dim as u64, Distance::Cosine)),
)
.await
.expect("failed to create collection");
let docs = vec![
"the cat sat on the mat".to_string(),
"a dog played in the park".to_string(),
"the sun shone brightly".to_string(),
];
embedd::qdrant::embed_and_upsert(
&client,
collection,
&embedder,
&docs,
0,
EmbedMode::Document,
)
.await
.expect("upsert failed");
let results = embedd::qdrant::embed_and_search(&client, collection, &embedder, "cat mat", 3)
.await
.expect("search failed");
assert_eq!(results.result.len(), 3, "expected 3 results");
let top = &results.result[0];
let top_text = top
.payload
.get("text")
.and_then(|v| v.kind.as_ref())
.map(|k| match k {
qdrant_client::qdrant::value::Kind::StringValue(s) => s.as_str(),
_ => "",
})
.unwrap_or("");
assert_eq!(
top_text, "the cat sat on the mat",
"expected cat sentence as top result, got: {top_text}"
);
let scores: Vec<f32> = results.result.iter().map(|r| r.score).collect();
for w in scores.windows(2) {
assert!(w[0] >= w[1], "scores not sorted descending: {:?}", scores);
}
client
.delete_collection(collection)
.await
.expect("cleanup failed");
}
#[tokio::test]
async fn embed_and_upsert_empty_is_noop() {
let Some(client) = qdrant_client().await else {
eprintln!("skipping: Qdrant not available on localhost:6334");
return;
};
let embedder = FixedDimEmbedder { dim: 32 };
embedd::qdrant::embed_and_upsert(
&client,
"nonexistent_collection",
&embedder,
&[],
0,
EmbedMode::Document,
)
.await
.expect("empty upsert should be a no-op");
}