Skip to main content

gobby_code/search/
semantic.rs

1//! Compatibility wrapper for Qdrant vector search.
2//!
3//! Reusable vector projection behavior lives in `crate::vector::code_symbols`.
4
5pub use crate::vector::code_symbols::{embed_query, vector_search};
6
7use crate::config::{CODE_SYMBOL_COLLECTION_PREFIX, Context};
8use gobby_core::qdrant::{CollectionScope, SearchRequest};
9
10pub fn semantic_search(ctx: &Context, query: &str, limit: usize) -> Vec<(String, f64)> {
11    let Some(embedding_config) = ctx.embedding.as_ref() else {
12        return vec![];
13    };
14    let Some(query_vector) = embed_query(embedding_config, query) else {
15        return vec![];
16    };
17
18    let collection = gobby_core::qdrant::collection_name(
19        "gcode",
20        CollectionScope::Custom(&format!(
21            "{CODE_SYMBOL_COLLECTION_PREFIX}{}",
22            ctx.project_id
23        )),
24    );
25    let request = SearchRequest {
26        vector: query_vector,
27        limit,
28        filter: None,
29    };
30
31    let Ok((hits, _state)) =
32        gobby_core::qdrant::with_qdrant(ctx.qdrant.as_ref(), Vec::new(), |config| {
33            gobby_core::qdrant::search(config, &collection, request)
34        })
35    else {
36        return vec![];
37    };
38
39    hits.into_iter()
40        .map(|hit| (hit.id, f64::from(hit.score)))
41        .collect()
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::config::QdrantConfig;
48    use std::path::PathBuf;
49
50    fn make_ctx_no_qdrant() -> Context {
51        Context {
52            database_url: "postgresql://localhost/nonexistent".to_string(),
53            project_root: PathBuf::from("/nonexistent"),
54            project_id: "test".to_string(),
55            quiet: true,
56            falkordb: None,
57            qdrant: None,
58            embedding: None,
59            code_vectors: crate::config::CodeVectorSettings::default(),
60            daemon_url: None,
61        }
62    }
63
64    #[test]
65    fn test_semantic_search_no_qdrant() {
66        let ctx = make_ctx_no_qdrant();
67        let result = semantic_search(&ctx, "test query", 10);
68        assert!(result.is_empty());
69    }
70
71    #[test]
72    fn test_semantic_search_no_embedding_config() {
73        let ctx = Context {
74            qdrant: Some(QdrantConfig {
75                url: Some("http://localhost:6333".to_string()),
76                api_key: None,
77            }),
78            ..make_ctx_no_qdrant()
79        };
80        let result = semantic_search(&ctx, "test query", 10);
81        assert!(result.is_empty());
82    }
83}