1use reqwest::{Client, StatusCode};
4use std::time::Duration;
5use tracing::{debug, instrument};
6
7use crate::error::{ClientError, Result};
8use crate::types::*;
9
10const DEFAULT_TIMEOUT_SECS: u64 = 30;
12
13#[derive(Debug, Clone)]
15pub struct DakeraClient {
16 pub(crate) client: Client,
18 pub(crate) base_url: String,
20}
21
22impl DakeraClient {
23 pub fn new(base_url: impl Into<String>) -> Result<Self> {
33 DakeraClientBuilder::new(base_url).build()
34 }
35
36 pub fn builder(base_url: impl Into<String>) -> DakeraClientBuilder {
38 DakeraClientBuilder::new(base_url)
39 }
40
41 #[instrument(skip(self))]
47 pub async fn health(&self) -> Result<HealthResponse> {
48 let url = format!("{}/health", self.base_url);
49 let response = self.client.get(&url).send().await?;
50
51 if response.status().is_success() {
52 Ok(response.json().await?)
53 } else {
54 Ok(HealthResponse {
56 healthy: true,
57 version: None,
58 uptime_seconds: None,
59 })
60 }
61 }
62
63 #[instrument(skip(self))]
65 pub async fn ready(&self) -> Result<ReadinessResponse> {
66 let url = format!("{}/health/ready", self.base_url);
67 let response = self.client.get(&url).send().await?;
68
69 if response.status().is_success() {
70 Ok(response.json().await?)
71 } else {
72 Ok(ReadinessResponse {
73 ready: false,
74 components: None,
75 })
76 }
77 }
78
79 #[instrument(skip(self))]
81 pub async fn live(&self) -> Result<bool> {
82 let url = format!("{}/health/live", self.base_url);
83 let response = self.client.get(&url).send().await?;
84 Ok(response.status().is_success())
85 }
86
87 #[instrument(skip(self))]
93 pub async fn list_namespaces(&self) -> Result<Vec<String>> {
94 let url = format!("{}/v1/namespaces", self.base_url);
95 let response = self.client.get(&url).send().await?;
96 self.handle_response::<ListNamespacesResponse>(response)
97 .await
98 .map(|r| r.namespaces)
99 }
100
101 #[instrument(skip(self))]
103 pub async fn get_namespace(&self, namespace: &str) -> Result<NamespaceInfo> {
104 let url = format!("{}/v1/namespaces/{}", self.base_url, namespace);
105 let response = self.client.get(&url).send().await?;
106 self.handle_response(response).await
107 }
108
109 #[instrument(skip(self, request))]
111 pub async fn create_namespace(
112 &self,
113 namespace: &str,
114 request: CreateNamespaceRequest,
115 ) -> Result<NamespaceInfo> {
116 let url = format!("{}/v1/namespaces/{}", self.base_url, namespace);
117 let response = self.client.post(&url).json(&request).send().await?;
118 self.handle_response(response).await
119 }
120
121 #[instrument(skip(self, request), fields(vector_count = request.vectors.len()))]
127 pub async fn upsert(&self, namespace: &str, request: UpsertRequest) -> Result<UpsertResponse> {
128 let url = format!("{}/v1/namespaces/{}/vectors", self.base_url, namespace);
129 debug!(
130 "Upserting {} vectors to {}",
131 request.vectors.len(),
132 namespace
133 );
134
135 let response = self.client.post(&url).json(&request).send().await?;
136 self.handle_response(response).await
137 }
138
139 #[instrument(skip(self, vector))]
141 pub async fn upsert_one(&self, namespace: &str, vector: Vector) -> Result<UpsertResponse> {
142 self.upsert(namespace, UpsertRequest::single(vector)).await
143 }
144
145 #[instrument(skip(self, request), fields(namespace = %namespace, count = request.ids.len()))]
178 pub async fn upsert_columns(
179 &self,
180 namespace: &str,
181 request: ColumnUpsertRequest,
182 ) -> Result<UpsertResponse> {
183 let url = format!(
184 "{}/v1/namespaces/{}/upsert-columns",
185 self.base_url, namespace
186 );
187 debug!(
188 "Upserting {} vectors in column format to {}",
189 request.ids.len(),
190 namespace
191 );
192
193 let response = self.client.post(&url).json(&request).send().await?;
194 self.handle_response(response).await
195 }
196
197 #[instrument(skip(self, request), fields(top_k = request.top_k))]
199 pub async fn query(&self, namespace: &str, request: QueryRequest) -> Result<QueryResponse> {
200 let url = format!("{}/v1/namespaces/{}/query", self.base_url, namespace);
201 debug!(
202 "Querying namespace {} for top {} results",
203 namespace, request.top_k
204 );
205
206 let response = self.client.post(&url).json(&request).send().await?;
207 self.handle_response(response).await
208 }
209
210 #[instrument(skip(self, vector))]
212 pub async fn query_simple(
213 &self,
214 namespace: &str,
215 vector: Vec<f32>,
216 top_k: u32,
217 ) -> Result<QueryResponse> {
218 self.query(namespace, QueryRequest::new(vector, top_k))
219 .await
220 }
221
222 #[instrument(skip(self, request), fields(namespace = %namespace, query_count = request.queries.len()))]
246 pub async fn batch_query(
247 &self,
248 namespace: &str,
249 request: BatchQueryRequest,
250 ) -> Result<BatchQueryResponse> {
251 let url = format!("{}/v1/namespaces/{}/batch-query", self.base_url, namespace);
252 debug!(
253 "Batch querying namespace {} with {} queries",
254 namespace,
255 request.queries.len()
256 );
257
258 let response = self.client.post(&url).json(&request).send().await?;
259 self.handle_response(response).await
260 }
261
262 #[instrument(skip(self, request), fields(id_count = request.ids.len()))]
264 pub async fn delete(&self, namespace: &str, request: DeleteRequest) -> Result<DeleteResponse> {
265 let url = format!(
266 "{}/v1/namespaces/{}/vectors/delete",
267 self.base_url, namespace
268 );
269 debug!("Deleting {} vectors from {}", request.ids.len(), namespace);
270
271 let response = self.client.post(&url).json(&request).send().await?;
272 self.handle_response(response).await
273 }
274
275 #[instrument(skip(self))]
277 pub async fn delete_one(&self, namespace: &str, id: &str) -> Result<DeleteResponse> {
278 self.delete(namespace, DeleteRequest::single(id)).await
279 }
280
281 #[instrument(skip(self, request), fields(doc_count = request.documents.len()))]
287 pub async fn index_documents(
288 &self,
289 namespace: &str,
290 request: IndexDocumentsRequest,
291 ) -> Result<IndexDocumentsResponse> {
292 let url = format!(
293 "{}/v1/namespaces/{}/fulltext/index",
294 self.base_url, namespace
295 );
296 debug!(
297 "Indexing {} documents in {}",
298 request.documents.len(),
299 namespace
300 );
301
302 let response = self.client.post(&url).json(&request).send().await?;
303 self.handle_response(response).await
304 }
305
306 #[instrument(skip(self, document))]
308 pub async fn index_document(
309 &self,
310 namespace: &str,
311 document: Document,
312 ) -> Result<IndexDocumentsResponse> {
313 self.index_documents(
314 namespace,
315 IndexDocumentsRequest {
316 documents: vec![document],
317 },
318 )
319 .await
320 }
321
322 #[instrument(skip(self, request))]
324 pub async fn fulltext_search(
325 &self,
326 namespace: &str,
327 request: FullTextSearchRequest,
328 ) -> Result<FullTextSearchResponse> {
329 let url = format!(
330 "{}/v1/namespaces/{}/fulltext/search",
331 self.base_url, namespace
332 );
333 debug!("Full-text search in {} for: {}", namespace, request.query);
334
335 let response = self.client.post(&url).json(&request).send().await?;
336 self.handle_response(response).await
337 }
338
339 #[instrument(skip(self))]
341 pub async fn search_text(
342 &self,
343 namespace: &str,
344 query: &str,
345 top_k: u32,
346 ) -> Result<FullTextSearchResponse> {
347 self.fulltext_search(namespace, FullTextSearchRequest::new(query, top_k))
348 .await
349 }
350
351 #[instrument(skip(self))]
353 pub async fn fulltext_stats(&self, namespace: &str) -> Result<FullTextStats> {
354 let url = format!(
355 "{}/v1/namespaces/{}/fulltext/stats",
356 self.base_url, namespace
357 );
358 let response = self.client.get(&url).send().await?;
359 self.handle_response(response).await
360 }
361
362 #[instrument(skip(self, request))]
364 pub async fn fulltext_delete(
365 &self,
366 namespace: &str,
367 request: DeleteRequest,
368 ) -> Result<DeleteResponse> {
369 let url = format!(
370 "{}/v1/namespaces/{}/fulltext/delete",
371 self.base_url, namespace
372 );
373 let response = self.client.post(&url).json(&request).send().await?;
374 self.handle_response(response).await
375 }
376
377 #[instrument(skip(self, request), fields(top_k = request.top_k))]
383 pub async fn hybrid_search(
384 &self,
385 namespace: &str,
386 request: HybridSearchRequest,
387 ) -> Result<HybridSearchResponse> {
388 let url = format!("{}/v1/namespaces/{}/hybrid", self.base_url, namespace);
389 debug!(
390 "Hybrid search in {} with vector_weight={}",
391 namespace, request.vector_weight
392 );
393
394 let response = self.client.post(&url).json(&request).send().await?;
395 self.handle_response(response).await
396 }
397
398 #[instrument(skip(self, request), fields(namespace = %namespace))]
435 pub async fn multi_vector_search(
436 &self,
437 namespace: &str,
438 request: MultiVectorSearchRequest,
439 ) -> Result<MultiVectorSearchResponse> {
440 let url = format!("{}/v1/namespaces/{}/multi-vector", self.base_url, namespace);
441 debug!(
442 "Multi-vector search in {} with {} positive vectors",
443 namespace,
444 request.positive_vectors.len()
445 );
446
447 let response = self.client.post(&url).json(&request).send().await?;
448 self.handle_response(response).await
449 }
450
451 #[instrument(skip(self, request), fields(namespace = %namespace))]
485 pub async fn aggregate(
486 &self,
487 namespace: &str,
488 request: AggregationRequest,
489 ) -> Result<AggregationResponse> {
490 let url = format!("{}/v1/namespaces/{}/aggregate", self.base_url, namespace);
491 debug!(
492 "Aggregating in namespace {} with {} aggregations",
493 namespace,
494 request.aggregate_by.len()
495 );
496
497 let response = self.client.post(&url).json(&request).send().await?;
498 self.handle_response(response).await
499 }
500
501 #[instrument(skip(self, request), fields(namespace = %namespace))]
539 pub async fn unified_query(
540 &self,
541 namespace: &str,
542 request: UnifiedQueryRequest,
543 ) -> Result<UnifiedQueryResponse> {
544 let url = format!(
545 "{}/v1/namespaces/{}/unified-query",
546 self.base_url, namespace
547 );
548 debug!(
549 "Unified query in namespace {} with top_k={}",
550 namespace, request.top_k
551 );
552
553 let response = self.client.post(&url).json(&request).send().await?;
554 self.handle_response(response).await
555 }
556
557 #[instrument(skip(self, vector))]
561 pub async fn unified_vector_search(
562 &self,
563 namespace: &str,
564 vector: Vec<f32>,
565 top_k: usize,
566 ) -> Result<UnifiedQueryResponse> {
567 self.unified_query(namespace, UnifiedQueryRequest::vector_search(vector, top_k))
568 .await
569 }
570
571 #[instrument(skip(self))]
575 pub async fn unified_text_search(
576 &self,
577 namespace: &str,
578 field: &str,
579 query: &str,
580 top_k: usize,
581 ) -> Result<UnifiedQueryResponse> {
582 self.unified_query(
583 namespace,
584 UnifiedQueryRequest::fulltext_search(field, query, top_k),
585 )
586 .await
587 }
588
589 #[instrument(skip(self, request), fields(namespace = %namespace))]
626 pub async fn explain_query(
627 &self,
628 namespace: &str,
629 request: QueryExplainRequest,
630 ) -> Result<QueryExplainResponse> {
631 let url = format!("{}/v1/namespaces/{}/explain", self.base_url, namespace);
632 debug!(
633 "Explaining query in namespace {} (query_type={:?}, top_k={})",
634 namespace, request.query_type, request.top_k
635 );
636
637 let response = self.client.post(&url).json(&request).send().await?;
638 self.handle_response(response).await
639 }
640
641 #[instrument(skip(self, request), fields(namespace = %request.namespace, priority = ?request.priority))]
669 pub async fn warm_cache(&self, request: WarmCacheRequest) -> Result<WarmCacheResponse> {
670 let url = format!(
671 "{}/v1/namespaces/{}/cache/warm",
672 self.base_url, request.namespace
673 );
674 debug!(
675 "Warming cache for namespace {} with priority {:?}",
676 request.namespace, request.priority
677 );
678
679 let response = self.client.post(&url).json(&request).send().await?;
680 self.handle_response(response).await
681 }
682
683 #[instrument(skip(self, vector_ids))]
685 pub async fn warm_vectors(
686 &self,
687 namespace: &str,
688 vector_ids: Vec<String>,
689 ) -> Result<WarmCacheResponse> {
690 self.warm_cache(WarmCacheRequest::new(namespace).with_vector_ids(vector_ids))
691 .await
692 }
693
694 #[instrument(skip(self, request), fields(namespace = %namespace))]
727 pub async fn export(&self, namespace: &str, request: ExportRequest) -> Result<ExportResponse> {
728 let url = format!("{}/v1/namespaces/{}/export", self.base_url, namespace);
729 debug!(
730 "Exporting vectors from namespace {} (top_k={}, cursor={:?})",
731 namespace, request.top_k, request.cursor
732 );
733
734 let response = self.client.post(&url).json(&request).send().await?;
735 self.handle_response(response).await
736 }
737
738 #[instrument(skip(self))]
742 pub async fn export_all(&self, namespace: &str) -> Result<ExportResponse> {
743 self.export(namespace, ExportRequest::new()).await
744 }
745
746 #[instrument(skip(self))]
752 pub async fn diagnostics(&self) -> Result<SystemDiagnostics> {
753 let url = format!("{}/ops/diagnostics", self.base_url);
754 let response = self.client.get(&url).send().await?;
755 self.handle_response(response).await
756 }
757
758 #[instrument(skip(self))]
760 pub async fn list_jobs(&self) -> Result<Vec<JobInfo>> {
761 let url = format!("{}/ops/jobs", self.base_url);
762 let response = self.client.get(&url).send().await?;
763 self.handle_response(response).await
764 }
765
766 #[instrument(skip(self))]
768 pub async fn get_job(&self, job_id: &str) -> Result<Option<JobInfo>> {
769 let url = format!("{}/ops/jobs/{}", self.base_url, job_id);
770 let response = self.client.get(&url).send().await?;
771
772 if response.status() == StatusCode::NOT_FOUND {
773 return Ok(None);
774 }
775
776 self.handle_response(response).await.map(Some)
777 }
778
779 #[instrument(skip(self, request))]
781 pub async fn compact(&self, request: CompactionRequest) -> Result<CompactionResponse> {
782 let url = format!("{}/ops/compact", self.base_url);
783 let response = self.client.post(&url).json(&request).send().await?;
784 self.handle_response(response).await
785 }
786
787 #[instrument(skip(self))]
789 pub async fn shutdown(&self) -> Result<()> {
790 let url = format!("{}/ops/shutdown", self.base_url);
791 let response = self.client.post(&url).send().await?;
792
793 if response.status().is_success() {
794 Ok(())
795 } else {
796 let status = response.status().as_u16();
797 let text = response.text().await.unwrap_or_default();
798 Err(ClientError::Server {
799 status,
800 message: text,
801 })
802 }
803 }
804
805 #[instrument(skip(self, request), fields(id_count = request.ids.len()))]
811 pub async fn fetch(&self, namespace: &str, request: FetchRequest) -> Result<FetchResponse> {
812 let url = format!("{}/v1/namespaces/{}/fetch", self.base_url, namespace);
813 debug!("Fetching {} vectors from {}", request.ids.len(), namespace);
814 let response = self.client.post(&url).json(&request).send().await?;
815 self.handle_response(response).await
816 }
817
818 #[instrument(skip(self))]
820 pub async fn fetch_by_ids(&self, namespace: &str, ids: &[&str]) -> Result<Vec<Vector>> {
821 let request = FetchRequest::new(ids.iter().map(|s| s.to_string()).collect());
822 self.fetch(namespace, request).await.map(|r| r.vectors)
823 }
824
825 #[instrument(skip(self, request), fields(doc_count = request.documents.len()))]
831 pub async fn upsert_text(
832 &self,
833 namespace: &str,
834 request: UpsertTextRequest,
835 ) -> Result<TextUpsertResponse> {
836 let url = format!("{}/v1/namespaces/{}/upsert-text", self.base_url, namespace);
837 debug!(
838 "Upserting {} text documents to {}",
839 request.documents.len(),
840 namespace
841 );
842 let response = self.client.post(&url).json(&request).send().await?;
843 self.handle_response(response).await
844 }
845
846 #[instrument(skip(self, request), fields(top_k = request.top_k))]
848 pub async fn query_text(
849 &self,
850 namespace: &str,
851 request: QueryTextRequest,
852 ) -> Result<TextQueryResponse> {
853 let url = format!("{}/v1/namespaces/{}/query-text", self.base_url, namespace);
854 debug!("Text query in {} for: {}", namespace, request.text);
855 let response = self.client.post(&url).json(&request).send().await?;
856 self.handle_response(response).await
857 }
858
859 #[instrument(skip(self))]
861 pub async fn query_text_simple(
862 &self,
863 namespace: &str,
864 text: &str,
865 top_k: u32,
866 ) -> Result<TextQueryResponse> {
867 self.query_text(namespace, QueryTextRequest::new(text, top_k))
868 .await
869 }
870
871 #[instrument(skip(self, request), fields(query_count = request.queries.len()))]
873 pub async fn batch_query_text(
874 &self,
875 namespace: &str,
876 request: BatchQueryTextRequest,
877 ) -> Result<BatchQueryTextResponse> {
878 let url = format!(
879 "{}/v1/namespaces/{}/batch-query-text",
880 self.base_url, namespace
881 );
882 debug!(
883 "Batch text query in {} with {} queries",
884 namespace,
885 request.queries.len()
886 );
887 let response = self.client.post(&url).json(&request).send().await?;
888 self.handle_response(response).await
889 }
890
891 pub(crate) async fn handle_response<T: serde::de::DeserializeOwned>(
897 &self,
898 response: reqwest::Response,
899 ) -> Result<T> {
900 let status = response.status();
901
902 if status.is_success() {
903 Ok(response.json().await?)
904 } else {
905 let status_code = status.as_u16();
906 let text = response.text().await.unwrap_or_default();
907
908 let message = if let Ok(json) = serde_json::from_str::<serde_json::Value>(&text) {
910 json.get("error")
911 .and_then(|e| e.as_str())
912 .unwrap_or(&text)
913 .to_string()
914 } else {
915 text
916 };
917
918 Err(ClientError::Server {
919 status: status_code,
920 message,
921 })
922 }
923 }
924}
925
926#[derive(Debug)]
928pub struct DakeraClientBuilder {
929 base_url: String,
930 timeout: Duration,
931 user_agent: Option<String>,
932}
933
934impl DakeraClientBuilder {
935 pub fn new(base_url: impl Into<String>) -> Self {
937 Self {
938 base_url: base_url.into(),
939 timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS),
940 user_agent: None,
941 }
942 }
943
944 pub fn timeout(mut self, timeout: Duration) -> Self {
946 self.timeout = timeout;
947 self
948 }
949
950 pub fn timeout_secs(mut self, secs: u64) -> Self {
952 self.timeout = Duration::from_secs(secs);
953 self
954 }
955
956 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
958 self.user_agent = Some(user_agent.into());
959 self
960 }
961
962 pub fn build(self) -> Result<DakeraClient> {
964 let base_url = self.base_url.trim_end_matches('/').to_string();
966
967 if !base_url.starts_with("http://") && !base_url.starts_with("https://") {
969 return Err(ClientError::InvalidUrl(
970 "URL must start with http:// or https://".to_string(),
971 ));
972 }
973
974 let user_agent = self
975 .user_agent
976 .unwrap_or_else(|| format!("dakera-client/{}", env!("CARGO_PKG_VERSION")));
977
978 let client = Client::builder()
979 .timeout(self.timeout)
980 .user_agent(user_agent)
981 .build()
982 .map_err(|e| ClientError::Config(e.to_string()))?;
983
984 Ok(DakeraClient { client, base_url })
985 }
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991
992 #[test]
993 fn test_client_builder() {
994 let client = DakeraClient::new("http://localhost:3000");
995 assert!(client.is_ok());
996 }
997
998 #[test]
999 fn test_client_builder_with_options() {
1000 let client = DakeraClient::builder("http://localhost:3000")
1001 .timeout_secs(60)
1002 .user_agent("test-client/1.0")
1003 .build();
1004 assert!(client.is_ok());
1005 }
1006
1007 #[test]
1008 fn test_client_builder_invalid_url() {
1009 let client = DakeraClient::new("invalid-url");
1010 assert!(client.is_err());
1011 }
1012
1013 #[test]
1014 fn test_client_builder_trailing_slash() {
1015 let client = DakeraClient::new("http://localhost:3000/").unwrap();
1016 assert!(!client.base_url.ends_with('/'));
1017 }
1018
1019 #[test]
1020 fn test_vector_creation() {
1021 let v = Vector::new("test", vec![0.1, 0.2, 0.3]);
1022 assert_eq!(v.id, "test");
1023 assert_eq!(v.values.len(), 3);
1024 assert!(v.metadata.is_none());
1025 }
1026
1027 #[test]
1028 fn test_query_request_builder() {
1029 let req = QueryRequest::new(vec![0.1, 0.2], 10)
1030 .with_filter(serde_json::json!({"category": "test"}))
1031 .include_metadata(false);
1032
1033 assert_eq!(req.top_k, 10);
1034 assert!(req.filter.is_some());
1035 assert!(!req.include_metadata);
1036 }
1037
1038 #[test]
1039 fn test_hybrid_search_request() {
1040 let req = HybridSearchRequest::new(vec![0.1], "test query", 5).with_vector_weight(0.7);
1041
1042 assert_eq!(req.vector_weight, 0.7);
1043 assert_eq!(req.text, "test query");
1044 }
1045
1046 #[test]
1047 fn test_hybrid_search_weight_clamping() {
1048 let req = HybridSearchRequest::new(vec![0.1], "test", 5).with_vector_weight(1.5); assert_eq!(req.vector_weight, 1.0);
1051 }
1052
1053 #[test]
1054 fn test_text_document_builder() {
1055 let doc = TextDocument::new("doc1", "Hello world").with_ttl(3600);
1056
1057 assert_eq!(doc.id, "doc1");
1058 assert_eq!(doc.text, "Hello world");
1059 assert_eq!(doc.ttl_seconds, Some(3600));
1060 assert!(doc.metadata.is_none());
1061 }
1062
1063 #[test]
1064 fn test_upsert_text_request_builder() {
1065 let docs = vec![
1066 TextDocument::new("doc1", "Hello"),
1067 TextDocument::new("doc2", "World"),
1068 ];
1069 let req = UpsertTextRequest::new(docs).with_model(EmbeddingModel::BgeSmall);
1070
1071 assert_eq!(req.documents.len(), 2);
1072 assert_eq!(req.model, Some(EmbeddingModel::BgeSmall));
1073 }
1074
1075 #[test]
1076 fn test_query_text_request_builder() {
1077 let req = QueryTextRequest::new("semantic search query", 5)
1078 .with_filter(serde_json::json!({"category": "docs"}))
1079 .include_vectors(true)
1080 .with_model(EmbeddingModel::E5Small);
1081
1082 assert_eq!(req.text, "semantic search query");
1083 assert_eq!(req.top_k, 5);
1084 assert!(req.filter.is_some());
1085 assert!(req.include_vectors);
1086 assert_eq!(req.model, Some(EmbeddingModel::E5Small));
1087 }
1088
1089 #[test]
1090 fn test_fetch_request_builder() {
1091 let req = FetchRequest::new(vec!["id1".to_string(), "id2".to_string()]);
1092
1093 assert_eq!(req.ids.len(), 2);
1094 assert!(req.include_values);
1095 assert!(req.include_metadata);
1096 }
1097
1098 #[test]
1099 fn test_create_namespace_request_builder() {
1100 let req = CreateNamespaceRequest::new()
1101 .with_dimensions(384)
1102 .with_index_type("hnsw");
1103
1104 assert_eq!(req.dimensions, Some(384));
1105 assert_eq!(req.index_type.as_deref(), Some("hnsw"));
1106 }
1107
1108 #[test]
1109 fn test_batch_query_text_request() {
1110 let req =
1111 BatchQueryTextRequest::new(vec!["query one".to_string(), "query two".to_string()], 10);
1112
1113 assert_eq!(req.queries.len(), 2);
1114 assert_eq!(req.top_k, 10);
1115 assert!(!req.include_vectors);
1116 assert!(req.model.is_none());
1117 }
1118}