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}