Skip to main content

vectorizer_sdk/client/
search.rs

1//! Search surface: text/vector search, intelligent search, semantic
2//! search, contextual search, multi-collection search, hybrid
3//! (dense + sparse) search.
4//!
5//! Six methods covering every search variant the v3 server exposes.
6//! Discovery (multi-stage filter + score + expand) lives in
7//! [`super::discovery`]; per-file search variants in [`super::files`].
8
9use super::VectorizerClient;
10use crate::error::{Result, VectorizerError};
11use crate::models::hybrid_search::{
12    HybridScoringAlgorithm, HybridSearchRequest, HybridSearchResponse,
13};
14use crate::models::*;
15
16impl VectorizerClient {
17    /// Text search against one collection. The server embeds the
18    /// query with the collection's provider, runs ANN search, and
19    /// returns up to `limit` (default 10) hits scored above the
20    /// optional `score_threshold`.
21    pub async fn search_vectors(
22        &self,
23        collection: &str,
24        query: &str,
25        limit: Option<usize>,
26        score_threshold: Option<f32>,
27    ) -> Result<SearchResponse> {
28        let mut payload = serde_json::Map::new();
29        payload.insert(
30            "query".to_string(),
31            serde_json::Value::String(query.to_string()),
32        );
33        payload.insert(
34            "limit".to_string(),
35            serde_json::Value::Number(limit.unwrap_or(10).into()),
36        );
37        if let Some(threshold) = score_threshold {
38            payload.insert(
39                "score_threshold".to_string(),
40                serde_json::Value::Number(serde_json::Number::from_f64(threshold as f64).unwrap()),
41            );
42        }
43        let response = self
44            .make_request(
45                "POST",
46                &format!("/collections/{collection}/search/text"),
47                Some(serde_json::Value::Object(payload)),
48            )
49            .await?;
50        let search_response: SearchResponse = serde_json::from_str(&response).map_err(|e| {
51            VectorizerError::server(format!("Failed to parse search response: {e}"))
52        })?;
53        Ok(search_response)
54    }
55
56    /// Intelligent search — multi-query expansion + MMR
57    /// diversification + domain term boosting.
58    pub async fn intelligent_search(
59        &self,
60        request: IntelligentSearchRequest,
61    ) -> Result<IntelligentSearchResponse> {
62        let response = self
63            .make_request(
64                "POST",
65                "/intelligent_search",
66                Some(serde_json::to_value(request).unwrap()),
67            )
68            .await?;
69        serde_json::from_str(&response).map_err(|e| {
70            VectorizerError::server(format!("Failed to parse intelligent search response: {e}"))
71        })
72    }
73
74    /// Semantic search — advanced reranking + similarity-threshold
75    /// filtering on top of the base text search.
76    pub async fn semantic_search(
77        &self,
78        request: SemanticSearchRequest,
79    ) -> Result<SemanticSearchResponse> {
80        let response = self
81            .make_request(
82                "POST",
83                "/semantic_search",
84                Some(serde_json::to_value(request).unwrap()),
85            )
86            .await?;
87        serde_json::from_str(&response).map_err(|e| {
88            VectorizerError::server(format!("Failed to parse semantic search response: {e}"))
89        })
90    }
91
92    /// Context-aware search with metadata filtering and contextual
93    /// reranking.
94    pub async fn contextual_search(
95        &self,
96        request: ContextualSearchRequest,
97    ) -> Result<ContextualSearchResponse> {
98        let response = self
99            .make_request(
100                "POST",
101                "/contextual_search",
102                Some(serde_json::to_value(request).unwrap()),
103            )
104            .await?;
105        serde_json::from_str(&response).map_err(|e| {
106            VectorizerError::server(format!("Failed to parse contextual search response: {e}"))
107        })
108    }
109
110    /// Multi-collection search with cross-collection reranking and
111    /// aggregation.
112    pub async fn multi_collection_search(
113        &self,
114        request: MultiCollectionSearchRequest,
115    ) -> Result<MultiCollectionSearchResponse> {
116        let response = self
117            .make_request(
118                "POST",
119                "/multi_collection_search",
120                Some(serde_json::to_value(request).unwrap()),
121            )
122            .await?;
123        serde_json::from_str(&response).map_err(|e| {
124            VectorizerError::server(format!(
125                "Failed to parse multi-collection search response: {e}"
126            ))
127        })
128    }
129
130    /// Hybrid search combining dense and sparse vectors with one of
131    /// three scoring algorithms (RRF, weighted, alpha-blending).
132    pub async fn hybrid_search(
133        &self,
134        request: HybridSearchRequest,
135    ) -> Result<HybridSearchResponse> {
136        let url = format!("/collections/{}/hybrid_search", request.collection);
137        let payload = serde_json::json!({
138            "query": request.query,
139            "alpha": request.alpha,
140            "algorithm": match request.algorithm {
141                HybridScoringAlgorithm::ReciprocalRankFusion => "rrf",
142                HybridScoringAlgorithm::WeightedCombination => "weighted",
143                HybridScoringAlgorithm::AlphaBlending => "alpha",
144            },
145            "dense_k": request.dense_k,
146            "sparse_k": request.sparse_k,
147            "final_k": request.final_k,
148            "query_sparse": request.query_sparse.as_ref().map(|sv| serde_json::json!({
149                "indices": sv.indices,
150                "values": sv.values,
151            })),
152        });
153        let response = self.make_request("POST", &url, Some(payload)).await?;
154        serde_json::from_str(&response).map_err(|e| {
155            VectorizerError::server(format!("Failed to parse hybrid search response: {e}"))
156        })
157    }
158}