Skip to main content

dk_engine/graph/
vector.rs

1//! Vector similarity search abstraction.
2//!
3//! Provides a trait for embedding-based semantic search that can be
4//! backed by Qdrant or any other vector database. Gracefully degrades
5//! to no-op when Qdrant is not configured.
6
7use async_trait::async_trait;
8use uuid::Uuid;
9
10/// A search result from vector similarity.
11#[derive(Debug, Clone)]
12pub struct VectorSearchResult {
13    pub symbol_id: Uuid,
14    pub score: f32,
15}
16
17/// Trait for vector search backends.
18#[async_trait]
19pub trait VectorSearch: Send + Sync + 'static {
20    async fn index_embedding(
21        &self,
22        symbol_id: Uuid,
23        repo_id: Uuid,
24        embedding: Vec<f32>,
25    ) -> anyhow::Result<()>;
26
27    async fn search_similar(
28        &self,
29        repo_id: Uuid,
30        query_embedding: Vec<f32>,
31        limit: usize,
32    ) -> anyhow::Result<Vec<VectorSearchResult>>;
33
34    async fn delete_embedding(&self, symbol_id: Uuid) -> anyhow::Result<()>;
35}
36
37/// No-op vector search (used when Qdrant is not configured).
38pub struct NoOpVectorSearch;
39
40#[async_trait]
41impl VectorSearch for NoOpVectorSearch {
42    async fn index_embedding(
43        &self,
44        _symbol_id: Uuid,
45        _repo_id: Uuid,
46        _embedding: Vec<f32>,
47    ) -> anyhow::Result<()> {
48        Ok(())
49    }
50
51    async fn search_similar(
52        &self,
53        _repo_id: Uuid,
54        _query_embedding: Vec<f32>,
55        _limit: usize,
56    ) -> anyhow::Result<Vec<VectorSearchResult>> {
57        Ok(vec![])
58    }
59
60    async fn delete_embedding(&self, _symbol_id: Uuid) -> anyhow::Result<()> {
61        Ok(())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[tokio::test]
70    async fn noop_search_returns_empty() {
71        let search = NoOpVectorSearch;
72        let results = search
73            .search_similar(Uuid::new_v4(), vec![0.1, 0.2, 0.3], 10)
74            .await
75            .unwrap();
76        assert!(results.is_empty());
77    }
78
79    #[tokio::test]
80    async fn noop_index_succeeds() {
81        let search = NoOpVectorSearch;
82        search
83            .index_embedding(Uuid::new_v4(), Uuid::new_v4(), vec![0.1, 0.2])
84            .await
85            .unwrap();
86    }
87
88    #[tokio::test]
89    async fn noop_delete_succeeds() {
90        let search = NoOpVectorSearch;
91        search.delete_embedding(Uuid::new_v4()).await.unwrap();
92    }
93}