gobby_code/vector/code_symbols/
search.rs1use crate::config::{CODE_SYMBOL_COLLECTION_PREFIX, Context};
2
3use super::embedding::{embed_query_with_source, embedding_source_from_context};
4use super::qdrant::{collection_name, vector_search};
5use super::types::{CodeSymbolVectorSearchHit, CodeSymbolVectorSearchRequest};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum SearchError {
9 MissingQdrantConfig,
10 MissingEmbeddingConfig,
11 QueryEmbeddingFailed,
12 InvalidCollectionName(gobby_core::qdrant::CollectionNameError),
13 VectorSearch(String),
14}
15
16impl std::fmt::Display for SearchError {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 match self {
19 Self::MissingQdrantConfig => write!(f, "Qdrant config is missing"),
20 Self::MissingEmbeddingConfig => write!(f, "embedding config is missing"),
21 Self::QueryEmbeddingFailed => write!(f, "query embedding failed"),
22 Self::InvalidCollectionName(error) => write!(f, "{error}"),
23 Self::VectorSearch(error) => write!(f, "semantic vector search failed: {error}"),
24 }
25 }
26}
27
28impl std::error::Error for SearchError {}
29
30pub fn search_code_symbols(
31 ctx: &Context,
32 request: &CodeSymbolVectorSearchRequest,
33) -> Result<Vec<CodeSymbolVectorSearchHit>, SearchError> {
34 let qdrant_config = match &ctx.qdrant {
35 Some(config) => config,
36 None => return Err(SearchError::MissingQdrantConfig),
37 };
38
39 let embedding_source = match embedding_source_from_context(ctx) {
40 Some(source) => source,
41 None => return Err(SearchError::MissingEmbeddingConfig),
42 };
43
44 let embedding = match embed_query_with_source(&embedding_source, &request.query) {
45 Some(embedding) => embedding,
46 None => return Err(SearchError::QueryEmbeddingFailed),
47 };
48
49 let collection = collection_name(&request.collection_prefix, &request.project_id)
50 .map_err(SearchError::InvalidCollectionName)?;
51 match vector_search(qdrant_config, &collection, &embedding, request.limit) {
52 Ok(hits) => Ok(hits
53 .into_iter()
54 .map(|(symbol_id, score)| CodeSymbolVectorSearchHit { symbol_id, score })
55 .collect()),
56 Err(error) => Err(SearchError::VectorSearch(error.to_string())),
57 }
58}
59
60pub fn semantic_search(ctx: &Context, query: &str, limit: usize) -> Vec<(String, f64)> {
64 let request = CodeSymbolVectorSearchRequest {
65 project_id: ctx.project_id.clone(),
66 query: query.to_string(),
67 limit,
68 collection_prefix: CODE_SYMBOL_COLLECTION_PREFIX.to_string(),
69 };
70
71 match search_code_symbols(ctx, &request) {
72 Ok(hits) => hits
73 .into_iter()
74 .map(|hit| (hit.symbol_id, hit.score))
75 .collect(),
76 Err(error) => {
77 log::warn!("semantic vector search skipped: {error}");
78 Vec::new()
79 }
80 }
81}