Skip to main content

ceres_core/
search.rs

1//! Search service for semantic dataset queries.
2//!
3//! This module provides a high-level service for performing semantic searches
4//! across the dataset index using vector embeddings.
5
6use pgvector::Vector;
7
8use crate::traits::{DatasetStore, EmbeddingProvider};
9use crate::{AppError, SearchResult};
10
11/// Service for semantic search operations.
12///
13/// This service encapsulates the search business logic, coordinating between
14/// the embedding provider and the dataset store.
15///
16/// # Type Parameters
17///
18/// * `S` - Dataset store implementation (e.g., `DatasetRepository`)
19/// * `E` - Embedding provider implementation (e.g., `GeminiClient`)
20///
21/// # Example
22///
23/// ```ignore
24/// use ceres_core::search::SearchService;
25///
26/// let search_service = SearchService::new(repo, gemini);
27/// let results = search_service.search("climate data", 10).await?;
28///
29/// for result in results {
30///     println!("{}: {:.2}", result.dataset.title, result.similarity_score);
31/// }
32/// ```
33pub struct SearchService<S, E>
34where
35    S: DatasetStore,
36    E: EmbeddingProvider,
37{
38    store: S,
39    embedding: E,
40}
41
42impl<S, E> Clone for SearchService<S, E>
43where
44    S: DatasetStore + Clone,
45    E: EmbeddingProvider + Clone,
46{
47    fn clone(&self) -> Self {
48        Self {
49            store: self.store.clone(),
50            embedding: self.embedding.clone(),
51        }
52    }
53}
54
55impl<S, E> SearchService<S, E>
56where
57    S: DatasetStore,
58    E: EmbeddingProvider,
59{
60    /// Creates a new search service.
61    ///
62    /// # Arguments
63    ///
64    /// * `store` - Dataset store for vector search queries
65    /// * `embedding` - Embedding provider for generating query embeddings
66    pub fn new(store: S, embedding: E) -> Self {
67        Self { store, embedding }
68    }
69
70    /// Performs semantic search and returns ranked results.
71    ///
72    /// This method:
73    /// 1. Generates an embedding vector from the query text
74    /// 2. Searches the database using cosine similarity
75    /// 3. Returns results ordered by similarity (highest first)
76    ///
77    /// # Arguments
78    ///
79    /// * `query` - The search query text
80    /// * `limit` - Maximum number of results to return
81    ///
82    /// # Returns
83    ///
84    /// A vector of [`SearchResult`], ordered by similarity score (highest first).
85    ///
86    /// # Errors
87    ///
88    /// Returns an error if:
89    /// - The embedding generation fails (API error, network error, etc.)
90    /// - The database query fails
91    pub async fn search(&self, query: &str, limit: usize) -> Result<Vec<SearchResult>, AppError> {
92        let embedding = self.embedding.generate(query).await?;
93        let query_vector = Vector::from(embedding);
94        self.store.search(query_vector, limit).await
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    // Tests require concrete implementations - see integration tests
101}