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