1use std::sync::Arc;
4
5use serde_json;
6
7use crate::error::{Result, VectorizerError};
8use crate::http_transport::HttpTransport;
9use crate::models::hybrid_search::{
10 HybridScoringAlgorithm, HybridSearchRequest, HybridSearchResponse,
11};
12use crate::models::*;
13use crate::transport::{Protocol, Transport};
14#[cfg(feature = "umicp")]
15use crate::umicp_transport::UmicpTransport;
16
17#[derive(Clone)]
19pub struct ClientConfig {
20 pub base_url: Option<String>,
22 pub connection_string: Option<String>,
24 pub protocol: Option<Protocol>,
26 pub api_key: Option<String>,
28 pub timeout_secs: Option<u64>,
30 #[cfg(feature = "umicp")]
32 pub umicp: Option<UmicpConfig>,
33 pub hosts: Option<HostConfig>,
35 pub read_preference: Option<ReadPreference>,
37}
38
39#[cfg(feature = "umicp")]
40#[derive(Clone)]
42pub struct UmicpConfig {
43 pub host: String,
44 pub port: u16,
45}
46
47impl Default for ClientConfig {
48 fn default() -> Self {
49 Self {
50 base_url: Some("http://localhost:15002".to_string()),
51 connection_string: None,
52 protocol: None,
53 api_key: None,
54 timeout_secs: Some(30),
55 #[cfg(feature = "umicp")]
56 umicp: None,
57 hosts: None,
58 read_preference: None,
59 }
60 }
61}
62
63pub struct VectorizerClient {
65 transport: Arc<dyn Transport>,
66 protocol: Protocol,
67 base_url: String,
68 #[allow(dead_code)]
70 master_transport: Option<Arc<dyn Transport>>,
71 #[allow(dead_code)]
73 replica_transports: Vec<Arc<dyn Transport>>,
74 #[allow(dead_code)]
76 replica_index: std::sync::atomic::AtomicUsize,
77 #[allow(dead_code)]
79 read_preference: ReadPreference,
80 #[allow(dead_code)]
82 is_replica_mode: bool,
83 config: ClientConfig,
85}
86
87impl VectorizerClient {
88 pub fn base_url(&self) -> &str {
90 &self.base_url
91 }
92
93 pub fn new(config: ClientConfig) -> Result<Self> {
95 let timeout_secs = config.timeout_secs.unwrap_or(30);
96
97 let (transport, protocol, base_url): (Arc<dyn Transport>, Protocol, String) =
99 if let Some(ref conn_str) = config.connection_string {
100 #[allow(unused_variables)] let (proto, host, port) = crate::transport::parse_connection_string(conn_str)?;
103
104 match proto {
105 Protocol::Http => {
106 let transport =
107 HttpTransport::new(&host, config.api_key.as_deref(), timeout_secs)?;
108 (Arc::new(transport), Protocol::Http, host.clone())
109 }
110 #[cfg(feature = "umicp")]
111 Protocol::Umicp => {
112 let umicp_port = port.unwrap_or(15003);
113 let transport = UmicpTransport::new(
114 &host,
115 umicp_port,
116 config.api_key.as_deref(),
117 timeout_secs,
118 )?;
119 let base_url = format!("umicp://{host}:{umicp_port}");
120 (Arc::new(transport), Protocol::Umicp, base_url)
121 }
122 }
123 } else {
124 let proto = config.protocol.unwrap_or(Protocol::Http);
126
127 match proto {
128 Protocol::Http => {
129 let base_url = config
130 .base_url
131 .clone()
132 .unwrap_or_else(|| "http://localhost:15002".to_string());
133 let transport =
134 HttpTransport::new(&base_url, config.api_key.as_deref(), timeout_secs)?;
135 (Arc::new(transport), Protocol::Http, base_url)
136 }
137 #[cfg(feature = "umicp")]
138 Protocol::Umicp => {
139 #[cfg(feature = "umicp")]
140 {
141 let umicp_config = config.umicp.clone().ok_or_else(|| {
142 VectorizerError::configuration(
143 "UMICP configuration is required when using UMICP protocol",
144 )
145 })?;
146
147 let transport = UmicpTransport::new(
148 &umicp_config.host,
149 umicp_config.port,
150 config.api_key.as_deref(),
151 timeout_secs,
152 )?;
153 let base_url =
154 format!("umicp://{}:{}", umicp_config.host, umicp_config.port);
155 (Arc::new(transport), Protocol::Umicp, base_url)
156 }
157 #[cfg(not(feature = "umicp"))]
158 {
159 return Err(VectorizerError::configuration(
160 "UMICP feature is not enabled. Enable it with --features umicp",
161 ));
162 }
163 }
164 }
165 };
166
167 let (master_transport, replica_transports, is_replica_mode) =
169 if let Some(ref hosts) = config.hosts {
170 let master =
171 HttpTransport::new(&hosts.master, config.api_key.as_deref(), timeout_secs)?;
172 let replicas: Result<Vec<Arc<dyn Transport>>> = hosts
173 .replicas
174 .iter()
175 .map(|url| {
176 let t = HttpTransport::new(url, config.api_key.as_deref(), timeout_secs)?;
177 Ok(Arc::new(t) as Arc<dyn Transport>)
178 })
179 .collect();
180 (
181 Some(Arc::new(master) as Arc<dyn Transport>),
182 replicas?,
183 true,
184 )
185 } else {
186 (None, vec![], false)
187 };
188
189 let read_preference = config.read_preference.unwrap_or(ReadPreference::Replica);
190
191 Ok(Self {
192 transport,
193 protocol,
194 base_url,
195 master_transport,
196 replica_transports,
197 replica_index: std::sync::atomic::AtomicUsize::new(0),
198 read_preference,
199 is_replica_mode,
200 config,
201 })
202 }
203
204 pub fn new_default() -> Result<Self> {
206 Self::new(ClientConfig::default())
207 }
208
209 pub fn new_with_url(base_url: &str) -> Result<Self> {
211 Self::new(ClientConfig {
212 base_url: Some(base_url.to_string()),
213 ..Default::default()
214 })
215 }
216
217 pub fn new_with_api_key(base_url: &str, api_key: &str) -> Result<Self> {
219 Self::new(ClientConfig {
220 base_url: Some(base_url.to_string()),
221 api_key: Some(api_key.to_string()),
222 ..Default::default()
223 })
224 }
225
226 pub fn from_connection_string(connection_string: &str, api_key: Option<&str>) -> Result<Self> {
228 Self::new(ClientConfig {
229 connection_string: Some(connection_string.to_string()),
230 api_key: api_key.map(|s| s.to_string()),
231 ..Default::default()
232 })
233 }
234
235 pub fn protocol(&self) -> Protocol {
237 self.protocol
238 }
239
240 #[allow(dead_code)]
242 fn get_write_transport(&self) -> &Arc<dyn Transport> {
243 if self.is_replica_mode {
244 self.master_transport.as_ref().unwrap_or(&self.transport)
245 } else {
246 &self.transport
247 }
248 }
249
250 #[allow(dead_code)]
252 fn get_read_transport(&self, options: Option<&ReadOptions>) -> &Arc<dyn Transport> {
253 if !self.is_replica_mode {
254 return &self.transport;
255 }
256
257 let preference = options
258 .and_then(|o| o.read_preference)
259 .unwrap_or(self.read_preference);
260
261 match preference {
262 ReadPreference::Master => self.master_transport.as_ref().unwrap_or(&self.transport),
263 ReadPreference::Replica | ReadPreference::Nearest => {
264 if self.replica_transports.is_empty() {
265 return self.master_transport.as_ref().unwrap_or(&self.transport);
266 }
267 let idx = self
269 .replica_index
270 .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
271 % self.replica_transports.len();
272 &self.replica_transports[idx]
273 }
274 }
275 }
276
277 pub async fn with_master<F, Fut, T>(&self, callback: F) -> Result<T>
280 where
281 F: FnOnce(VectorizerClient) -> Fut,
282 Fut: std::future::Future<Output = Result<T>>,
283 {
284 let mut master_config = self.config.clone();
285 master_config.read_preference = Some(ReadPreference::Master);
286 let master_client = VectorizerClient::new(master_config)?;
287 callback(master_client).await
288 }
289
290 pub async fn health_check(&self) -> Result<HealthStatus> {
292 let response = self.make_request("GET", "/health", None).await?;
293 let health: HealthStatus = serde_json::from_str(&response).map_err(|e| {
294 VectorizerError::server(format!("Failed to parse health check response: {e}"))
295 })?;
296 Ok(health)
297 }
298
299 pub async fn list_collections(&self) -> Result<Vec<Collection>> {
301 let response = self.make_request("GET", "/collections", None).await?;
302 let collections: Vec<Collection> = if let Ok(wrapper) =
304 serde_json::from_str::<serde_json::Value>(&response)
305 {
306 if let Some(arr) = wrapper.get("collections").and_then(|v| v.as_array()) {
307 serde_json::from_value(serde_json::Value::Array(arr.clone())).map_err(|e| {
308 VectorizerError::server(format!("Failed to parse collections array: {e}"))
309 })?
310 } else if wrapper.is_array() {
311 serde_json::from_value(wrapper).map_err(|e| {
312 VectorizerError::server(format!("Failed to parse collections response: {e}"))
313 })?
314 } else {
315 return Err(VectorizerError::server(
316 "Unexpected collections response format".to_string(),
317 ));
318 }
319 } else {
320 return Err(VectorizerError::server(
321 "Failed to parse collections response".to_string(),
322 ));
323 };
324 Ok(collections)
325 }
326
327 pub async fn search_vectors(
329 &self,
330 collection: &str,
331 query: &str,
332 limit: Option<usize>,
333 score_threshold: Option<f32>,
334 ) -> Result<SearchResponse> {
335 let mut payload = serde_json::Map::new();
336 payload.insert(
337 "query".to_string(),
338 serde_json::Value::String(query.to_string()),
339 );
340 payload.insert(
341 "limit".to_string(),
342 serde_json::Value::Number(limit.unwrap_or(10).into()),
343 );
344
345 if let Some(threshold) = score_threshold {
346 payload.insert(
347 "score_threshold".to_string(),
348 serde_json::Value::Number(serde_json::Number::from_f64(threshold as f64).unwrap()),
349 );
350 }
351
352 let response = self
353 .make_request(
354 "POST",
355 &format!("/collections/{}/search/text", collection),
356 Some(serde_json::Value::Object(payload)),
357 )
358 .await?;
359 let search_response: SearchResponse = serde_json::from_str(&response).map_err(|e| {
360 VectorizerError::server(format!("Failed to parse search response: {e}"))
361 })?;
362 Ok(search_response)
363 }
364
365 pub async fn intelligent_search(
369 &self,
370 request: IntelligentSearchRequest,
371 ) -> Result<IntelligentSearchResponse> {
372 let response = self
373 .make_request(
374 "POST",
375 "/intelligent_search",
376 Some(serde_json::to_value(request).unwrap()),
377 )
378 .await?;
379 let search_response: IntelligentSearchResponse =
380 serde_json::from_str(&response).map_err(|e| {
381 VectorizerError::server(format!(
382 "Failed to parse intelligent search response: {}",
383 e
384 ))
385 })?;
386 Ok(search_response)
387 }
388
389 pub async fn semantic_search(
391 &self,
392 request: SemanticSearchRequest,
393 ) -> Result<SemanticSearchResponse> {
394 let response = self
395 .make_request(
396 "POST",
397 "/semantic_search",
398 Some(serde_json::to_value(request).unwrap()),
399 )
400 .await?;
401 let search_response: SemanticSearchResponse =
402 serde_json::from_str(&response).map_err(|e| {
403 VectorizerError::server(format!("Failed to parse semantic search response: {e}"))
404 })?;
405 Ok(search_response)
406 }
407
408 pub async fn contextual_search(
410 &self,
411 request: ContextualSearchRequest,
412 ) -> Result<ContextualSearchResponse> {
413 let response = self
414 .make_request(
415 "POST",
416 "/contextual_search",
417 Some(serde_json::to_value(request).unwrap()),
418 )
419 .await?;
420 let search_response: ContextualSearchResponse =
421 serde_json::from_str(&response).map_err(|e| {
422 VectorizerError::server(format!(
423 "Failed to parse contextual search response: {}",
424 e
425 ))
426 })?;
427 Ok(search_response)
428 }
429
430 pub async fn multi_collection_search(
432 &self,
433 request: MultiCollectionSearchRequest,
434 ) -> Result<MultiCollectionSearchResponse> {
435 let response = self
436 .make_request(
437 "POST",
438 "/multi_collection_search",
439 Some(serde_json::to_value(request).unwrap()),
440 )
441 .await?;
442 let search_response: MultiCollectionSearchResponse = serde_json::from_str(&response)
443 .map_err(|e| {
444 VectorizerError::server(format!(
445 "Failed to parse multi-collection search response: {}",
446 e
447 ))
448 })?;
449 Ok(search_response)
450 }
451
452 pub async fn hybrid_search(
454 &self,
455 request: HybridSearchRequest,
456 ) -> Result<HybridSearchResponse> {
457 let url = format!("/collections/{}/hybrid_search", request.collection);
458 let payload = serde_json::json!({
459 "query": request.query,
460 "alpha": request.alpha,
461 "algorithm": match request.algorithm {
462 HybridScoringAlgorithm::ReciprocalRankFusion => "rrf",
463 HybridScoringAlgorithm::WeightedCombination => "weighted",
464 HybridScoringAlgorithm::AlphaBlending => "alpha",
465 },
466 "dense_k": request.dense_k,
467 "sparse_k": request.sparse_k,
468 "final_k": request.final_k,
469 "query_sparse": request.query_sparse.as_ref().map(|sv| serde_json::json!({
470 "indices": sv.indices,
471 "values": sv.values,
472 })),
473 });
474 let response = self.make_request("POST", &url, Some(payload)).await?;
475 let search_response: HybridSearchResponse =
476 serde_json::from_str(&response).map_err(|e| {
477 VectorizerError::server(format!("Failed to parse hybrid search response: {e}"))
478 })?;
479 Ok(search_response)
480 }
481
482 pub async fn qdrant_list_collections(&self) -> Result<serde_json::Value> {
486 let response = self
487 .make_request("GET", "/qdrant/collections", None)
488 .await?;
489 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
490 VectorizerError::server(format!(
491 "Failed to parse Qdrant collections response: {}",
492 e
493 ))
494 })?;
495 Ok(result)
496 }
497
498 pub async fn qdrant_get_collection(&self, name: &str) -> Result<serde_json::Value> {
500 let url = format!("/qdrant/collections/{name}");
501 let response = self.make_request("GET", &url, None).await?;
502 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
503 VectorizerError::server(format!("Failed to parse Qdrant collection response: {e}"))
504 })?;
505 Ok(result)
506 }
507
508 pub async fn qdrant_create_collection(
510 &self,
511 name: &str,
512 config: &serde_json::Value,
513 ) -> Result<serde_json::Value> {
514 let url = format!("/qdrant/collections/{name}");
515 let payload = serde_json::json!({ "config": config });
516 let response = self.make_request("PUT", &url, Some(payload)).await?;
517 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
518 VectorizerError::server(format!(
519 "Failed to parse Qdrant create collection response: {}",
520 e
521 ))
522 })?;
523 Ok(result)
524 }
525
526 pub async fn qdrant_upsert_points(
528 &self,
529 collection: &str,
530 points: &serde_json::Value,
531 wait: bool,
532 ) -> Result<serde_json::Value> {
533 let url = format!("/qdrant/collections/{collection}/points");
534 let payload = serde_json::json!({
535 "points": points,
536 "wait": wait,
537 });
538 let response = self.make_request("PUT", &url, Some(payload)).await?;
539 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
540 VectorizerError::server(format!(
541 "Failed to parse Qdrant upsert points response: {}",
542 e
543 ))
544 })?;
545 Ok(result)
546 }
547
548 pub async fn qdrant_search_points(
550 &self,
551 collection: &str,
552 vector: &[f32],
553 limit: Option<usize>,
554 filter: Option<&serde_json::Value>,
555 with_payload: bool,
556 with_vector: bool,
557 ) -> Result<serde_json::Value> {
558 let url = format!("/qdrant/collections/{collection}/points/search");
559 let mut payload = serde_json::json!({
560 "vector": vector,
561 "limit": limit.unwrap_or(10),
562 "with_payload": with_payload,
563 "with_vector": with_vector,
564 });
565 if let Some(filter) = filter {
566 payload["filter"] = filter.clone();
567 }
568 let response = self.make_request("POST", &url, Some(payload)).await?;
569 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
570 VectorizerError::server(format!("Failed to parse Qdrant search response: {e}"))
571 })?;
572 Ok(result)
573 }
574
575 pub async fn qdrant_delete_points(
577 &self,
578 collection: &str,
579 point_ids: &[serde_json::Value],
580 wait: bool,
581 ) -> Result<serde_json::Value> {
582 let url = format!("/qdrant/collections/{collection}/points/delete");
583 let payload = serde_json::json!({
584 "points": point_ids,
585 "wait": wait,
586 });
587 let response = self.make_request("POST", &url, Some(payload)).await?;
588 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
589 VectorizerError::server(format!(
590 "Failed to parse Qdrant delete points response: {}",
591 e
592 ))
593 })?;
594 Ok(result)
595 }
596
597 pub async fn qdrant_retrieve_points(
599 &self,
600 collection: &str,
601 point_ids: &[serde_json::Value],
602 with_payload: bool,
603 with_vector: bool,
604 ) -> Result<serde_json::Value> {
605 let ids_str = point_ids
606 .iter()
607 .map(|id| match id {
608 serde_json::Value::String(s) => s.clone(),
609 serde_json::Value::Number(n) => n.to_string(),
610 _ => serde_json::to_string(id).unwrap_or_default(),
611 })
612 .collect::<Vec<_>>()
613 .join(",");
614 let url = format!(
615 "/qdrant/collections/{}/points?ids={}&with_payload={}&with_vector={}",
616 collection, ids_str, with_payload, with_vector
617 );
618 let response = self.make_request("GET", &url, None).await?;
619 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
620 VectorizerError::server(format!(
621 "Failed to parse Qdrant retrieve points response: {}",
622 e
623 ))
624 })?;
625 Ok(result)
626 }
627
628 pub async fn qdrant_count_points(
630 &self,
631 collection: &str,
632 filter: Option<&serde_json::Value>,
633 ) -> Result<serde_json::Value> {
634 let url = format!("/qdrant/collections/{collection}/points/count");
635 let payload = if let Some(filter) = filter {
636 serde_json::json!({ "filter": filter })
637 } else {
638 serde_json::json!({})
639 };
640 let response = self.make_request("POST", &url, Some(payload)).await?;
641 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
642 VectorizerError::server(format!(
643 "Failed to parse Qdrant count points response: {}",
644 e
645 ))
646 })?;
647 Ok(result)
648 }
649
650 pub async fn qdrant_list_collection_snapshots(
654 &self,
655 collection: &str,
656 ) -> Result<serde_json::Value> {
657 let url = format!("/qdrant/collections/{collection}/snapshots");
658 let response = self.make_request("GET", &url, None).await?;
659 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
660 VectorizerError::server(format!(
661 "Failed to parse Qdrant list snapshots response: {}",
662 e
663 ))
664 })?;
665 Ok(result)
666 }
667
668 pub async fn qdrant_create_collection_snapshot(
670 &self,
671 collection: &str,
672 ) -> Result<serde_json::Value> {
673 let url = format!("/qdrant/collections/{collection}/snapshots");
674 let response = self.make_request("POST", &url, None).await?;
675 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
676 VectorizerError::server(format!(
677 "Failed to parse Qdrant create snapshot response: {}",
678 e
679 ))
680 })?;
681 Ok(result)
682 }
683
684 pub async fn qdrant_delete_collection_snapshot(
686 &self,
687 collection: &str,
688 snapshot_name: &str,
689 ) -> Result<serde_json::Value> {
690 let url = format!("/qdrant/collections/{collection}/snapshots/{snapshot_name}");
691 let response = self.make_request("DELETE", &url, None).await?;
692 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
693 VectorizerError::server(format!(
694 "Failed to parse Qdrant delete snapshot response: {}",
695 e
696 ))
697 })?;
698 Ok(result)
699 }
700
701 pub async fn qdrant_recover_collection_snapshot(
703 &self,
704 collection: &str,
705 location: &str,
706 ) -> Result<serde_json::Value> {
707 let url = format!("/qdrant/collections/{collection}/snapshots/recover");
708 let payload = serde_json::json!({ "location": location });
709 let response = self.make_request("POST", &url, Some(payload)).await?;
710 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
711 VectorizerError::server(format!(
712 "Failed to parse Qdrant recover snapshot response: {}",
713 e
714 ))
715 })?;
716 Ok(result)
717 }
718
719 pub async fn qdrant_list_all_snapshots(&self) -> Result<serde_json::Value> {
721 let url = "/qdrant/snapshots";
722 let response = self.make_request("GET", url, None).await?;
723 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
724 VectorizerError::server(format!(
725 "Failed to parse Qdrant list all snapshots response: {}",
726 e
727 ))
728 })?;
729 Ok(result)
730 }
731
732 pub async fn qdrant_create_full_snapshot(&self) -> Result<serde_json::Value> {
734 let url = "/qdrant/snapshots";
735 let response = self.make_request("POST", url, None).await?;
736 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
737 VectorizerError::server(format!(
738 "Failed to parse Qdrant create full snapshot response: {}",
739 e
740 ))
741 })?;
742 Ok(result)
743 }
744
745 pub async fn qdrant_list_shard_keys(&self, collection: &str) -> Result<serde_json::Value> {
747 let url = format!("/qdrant/collections/{collection}/shards");
748 let response = self.make_request("GET", &url, None).await?;
749 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
750 VectorizerError::server(format!(
751 "Failed to parse Qdrant list shard keys response: {}",
752 e
753 ))
754 })?;
755 Ok(result)
756 }
757
758 pub async fn qdrant_create_shard_key(
760 &self,
761 collection: &str,
762 shard_key: &serde_json::Value,
763 ) -> Result<serde_json::Value> {
764 let url = format!("/qdrant/collections/{collection}/shards");
765 let payload = serde_json::json!({ "shard_key": shard_key });
766 let response = self.make_request("PUT", &url, Some(payload)).await?;
767 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
768 VectorizerError::server(format!(
769 "Failed to parse Qdrant create shard key response: {}",
770 e
771 ))
772 })?;
773 Ok(result)
774 }
775
776 pub async fn qdrant_delete_shard_key(
778 &self,
779 collection: &str,
780 shard_key: &serde_json::Value,
781 ) -> Result<serde_json::Value> {
782 let url = format!("/qdrant/collections/{collection}/shards/delete");
783 let payload = serde_json::json!({ "shard_key": shard_key });
784 let response = self.make_request("POST", &url, Some(payload)).await?;
785 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
786 VectorizerError::server(format!(
787 "Failed to parse Qdrant delete shard key response: {}",
788 e
789 ))
790 })?;
791 Ok(result)
792 }
793
794 pub async fn qdrant_get_cluster_status(&self) -> Result<serde_json::Value> {
796 let url = "/qdrant/cluster";
797 let response = self.make_request("GET", url, None).await?;
798 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
799 VectorizerError::server(format!(
800 "Failed to parse Qdrant cluster status response: {}",
801 e
802 ))
803 })?;
804 Ok(result)
805 }
806
807 pub async fn qdrant_cluster_recover(&self) -> Result<serde_json::Value> {
809 let url = "/qdrant/cluster/recover";
810 let response = self.make_request("POST", url, None).await?;
811 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
812 VectorizerError::server(format!(
813 "Failed to parse Qdrant cluster recover response: {}",
814 e
815 ))
816 })?;
817 Ok(result)
818 }
819
820 pub async fn qdrant_remove_peer(&self, peer_id: &str) -> Result<serde_json::Value> {
822 let url = format!("/qdrant/cluster/peer/{peer_id}");
823 let response = self.make_request("DELETE", &url, None).await?;
824 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
825 VectorizerError::server(format!(
826 "Failed to parse Qdrant remove peer response: {}",
827 e
828 ))
829 })?;
830 Ok(result)
831 }
832
833 pub async fn qdrant_list_metadata_keys(&self) -> Result<serde_json::Value> {
835 let url = "/qdrant/cluster/metadata/keys";
836 let response = self.make_request("GET", url, None).await?;
837 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
838 VectorizerError::server(format!(
839 "Failed to parse Qdrant list metadata keys response: {}",
840 e
841 ))
842 })?;
843 Ok(result)
844 }
845
846 pub async fn qdrant_get_metadata_key(&self, key: &str) -> Result<serde_json::Value> {
848 let url = format!("/qdrant/cluster/metadata/keys/{key}");
849 let response = self.make_request("GET", &url, None).await?;
850 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
851 VectorizerError::server(format!(
852 "Failed to parse Qdrant get metadata key response: {}",
853 e
854 ))
855 })?;
856 Ok(result)
857 }
858
859 pub async fn qdrant_update_metadata_key(
861 &self,
862 key: &str,
863 value: &serde_json::Value,
864 ) -> Result<serde_json::Value> {
865 let url = format!("/qdrant/cluster/metadata/keys/{key}");
866 let payload = serde_json::json!({ "value": value });
867 let response = self.make_request("PUT", &url, Some(payload)).await?;
868 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
869 VectorizerError::server(format!(
870 "Failed to parse Qdrant update metadata key response: {}",
871 e
872 ))
873 })?;
874 Ok(result)
875 }
876
877 pub async fn qdrant_query_points(
879 &self,
880 collection: &str,
881 request: &serde_json::Value,
882 ) -> Result<serde_json::Value> {
883 let url = format!("/qdrant/collections/{collection}/points/query");
884 let response = self
885 .make_request("POST", &url, Some(request.clone()))
886 .await?;
887 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
888 VectorizerError::server(format!(
889 "Failed to parse Qdrant query points response: {}",
890 e
891 ))
892 })?;
893 Ok(result)
894 }
895
896 pub async fn qdrant_batch_query_points(
898 &self,
899 collection: &str,
900 request: &serde_json::Value,
901 ) -> Result<serde_json::Value> {
902 let url = format!("/qdrant/collections/{collection}/points/query/batch");
903 let response = self
904 .make_request("POST", &url, Some(request.clone()))
905 .await?;
906 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
907 VectorizerError::server(format!(
908 "Failed to parse Qdrant batch query points response: {}",
909 e
910 ))
911 })?;
912 Ok(result)
913 }
914
915 pub async fn qdrant_query_points_groups(
917 &self,
918 collection: &str,
919 request: &serde_json::Value,
920 ) -> Result<serde_json::Value> {
921 let url = format!("/qdrant/collections/{collection}/points/query/groups");
922 let response = self
923 .make_request("POST", &url, Some(request.clone()))
924 .await?;
925 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
926 VectorizerError::server(format!(
927 "Failed to parse Qdrant query points groups response: {}",
928 e
929 ))
930 })?;
931 Ok(result)
932 }
933
934 pub async fn qdrant_search_points_groups(
936 &self,
937 collection: &str,
938 request: &serde_json::Value,
939 ) -> Result<serde_json::Value> {
940 let url = format!("/qdrant/collections/{collection}/points/search/groups");
941 let response = self
942 .make_request("POST", &url, Some(request.clone()))
943 .await?;
944 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
945 VectorizerError::server(format!(
946 "Failed to parse Qdrant search points groups response: {}",
947 e
948 ))
949 })?;
950 Ok(result)
951 }
952
953 pub async fn qdrant_search_matrix_pairs(
955 &self,
956 collection: &str,
957 request: &serde_json::Value,
958 ) -> Result<serde_json::Value> {
959 let url = format!("/qdrant/collections/{collection}/points/search/matrix/pairs");
960 let response = self
961 .make_request("POST", &url, Some(request.clone()))
962 .await?;
963 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
964 VectorizerError::server(format!(
965 "Failed to parse Qdrant search matrix pairs response: {}",
966 e
967 ))
968 })?;
969 Ok(result)
970 }
971
972 pub async fn qdrant_search_matrix_offsets(
974 &self,
975 collection: &str,
976 request: &serde_json::Value,
977 ) -> Result<serde_json::Value> {
978 let url = format!("/qdrant/collections/{collection}/points/search/matrix/offsets");
979 let response = self
980 .make_request("POST", &url, Some(request.clone()))
981 .await?;
982 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
983 VectorizerError::server(format!(
984 "Failed to parse Qdrant search matrix offsets response: {}",
985 e
986 ))
987 })?;
988 Ok(result)
989 }
990
991 pub async fn create_collection(
993 &self,
994 name: &str,
995 dimension: usize,
996 metric: Option<SimilarityMetric>,
997 ) -> Result<CollectionInfo> {
998 let mut payload = serde_json::Map::new();
999 payload.insert(
1000 "name".to_string(),
1001 serde_json::Value::String(name.to_string()),
1002 );
1003 payload.insert(
1004 "dimension".to_string(),
1005 serde_json::Value::Number(dimension.into()),
1006 );
1007 payload.insert(
1008 "metric".to_string(),
1009 serde_json::Value::String(format!("{:?}", metric.unwrap_or_default()).to_lowercase()),
1010 );
1011
1012 let response = self
1013 .make_request(
1014 "POST",
1015 "/collections",
1016 Some(serde_json::Value::Object(payload)),
1017 )
1018 .await?;
1019 let create_response: CreateCollectionResponse =
1020 serde_json::from_str(&response).map_err(|e| {
1021 VectorizerError::server(format!(
1022 "Failed to parse create collection response: {}",
1023 e
1024 ))
1025 })?;
1026
1027 let info = CollectionInfo {
1029 name: create_response.collection,
1030 dimension,
1031 metric: format!("{:?}", metric.unwrap_or_default()).to_lowercase(),
1032 vector_count: 0,
1033 document_count: 0,
1034 created_at: "".to_string(),
1035 updated_at: "".to_string(),
1036 indexing_status: Some(crate::models::IndexingStatus {
1037 status: "created".to_string(),
1038 progress: 0.0,
1039 total_documents: 0,
1040 processed_documents: 0,
1041 vector_count: 0,
1042 estimated_time_remaining: None,
1043 last_updated: "".to_string(),
1044 }),
1045 };
1046 Ok(info)
1047 }
1048
1049 pub async fn insert_texts(
1051 &self,
1052 collection: &str,
1053 texts: Vec<BatchTextRequest>,
1054 ) -> Result<BatchResponse> {
1055 let payload = serde_json::json!({
1056 "texts": texts
1057 });
1058
1059 let response = self
1060 .make_request(
1061 "POST",
1062 &format!("/collections/{collection}/documents"),
1063 Some(serde_json::to_value(payload)?),
1064 )
1065 .await?;
1066 let batch_response: BatchResponse = serde_json::from_str(&response).map_err(|e| {
1067 VectorizerError::server(format!("Failed to parse insert texts response: {e}"))
1068 })?;
1069 Ok(batch_response)
1070 }
1071
1072 pub async fn delete_collection(&self, name: &str) -> Result<()> {
1074 self.make_request("DELETE", &format!("/collections/{}", name), None)
1075 .await?;
1076 Ok(())
1077 }
1078
1079 pub async fn get_vector(&self, collection: &str, vector_id: &str) -> Result<Vector> {
1081 let response = self
1082 .make_request(
1083 "GET",
1084 &format!("/collections/{collection}/vectors/{vector_id}"),
1085 None,
1086 )
1087 .await?;
1088 let vector: Vector = serde_json::from_str(&response).map_err(|e| {
1089 VectorizerError::server(format!("Failed to parse get vector response: {e}"))
1090 })?;
1091 Ok(vector)
1092 }
1093
1094 pub async fn get_collection_info(&self, collection: &str) -> Result<CollectionInfo> {
1096 let response = self
1097 .make_request("GET", &format!("/collections/{}", collection), None)
1098 .await?;
1099 let info: CollectionInfo = serde_json::from_str(&response).map_err(|e| {
1100 VectorizerError::server(format!("Failed to parse collection info: {e}"))
1101 })?;
1102 Ok(info)
1103 }
1104
1105 pub async fn embed_text(&self, text: &str, model: Option<&str>) -> Result<EmbeddingResponse> {
1107 let mut payload = serde_json::Map::new();
1108 payload.insert(
1109 "text".to_string(),
1110 serde_json::Value::String(text.to_string()),
1111 );
1112
1113 if let Some(model) = model {
1114 payload.insert(
1115 "model".to_string(),
1116 serde_json::Value::String(model.to_string()),
1117 );
1118 }
1119
1120 let response = self
1121 .make_request("POST", "/embed", Some(serde_json::Value::Object(payload)))
1122 .await?;
1123 let embedding_response: EmbeddingResponse =
1124 serde_json::from_str(&response).map_err(|e| {
1125 VectorizerError::server(format!("Failed to parse embedding response: {e}"))
1126 })?;
1127 Ok(embedding_response)
1128 }
1129
1130 pub async fn discover(
1136 &self,
1137 query: &str,
1138 include_collections: Option<Vec<String>>,
1139 exclude_collections: Option<Vec<String>>,
1140 max_bullets: Option<usize>,
1141 broad_k: Option<usize>,
1142 focus_k: Option<usize>,
1143 ) -> Result<serde_json::Value> {
1144 if query.trim().is_empty() {
1146 return Err(VectorizerError::validation("Query cannot be empty"));
1147 }
1148
1149 if let Some(max) = max_bullets
1151 && max == 0
1152 {
1153 return Err(VectorizerError::validation(
1154 "max_bullets must be greater than 0",
1155 ));
1156 }
1157
1158 let mut payload = serde_json::Map::new();
1159 payload.insert(
1160 "query".to_string(),
1161 serde_json::Value::String(query.to_string()),
1162 );
1163
1164 if let Some(inc) = include_collections {
1165 payload.insert(
1166 "include_collections".to_string(),
1167 serde_json::to_value(inc).unwrap(),
1168 );
1169 }
1170 if let Some(exc) = exclude_collections {
1171 payload.insert(
1172 "exclude_collections".to_string(),
1173 serde_json::to_value(exc).unwrap(),
1174 );
1175 }
1176 if let Some(max) = max_bullets {
1177 payload.insert(
1178 "max_bullets".to_string(),
1179 serde_json::Value::Number(max.into()),
1180 );
1181 }
1182 if let Some(k) = broad_k {
1183 payload.insert("broad_k".to_string(), serde_json::Value::Number(k.into()));
1184 }
1185 if let Some(k) = focus_k {
1186 payload.insert("focus_k".to_string(), serde_json::Value::Number(k.into()));
1187 }
1188
1189 let response = self
1190 .make_request(
1191 "POST",
1192 "/discover",
1193 Some(serde_json::Value::Object(payload)),
1194 )
1195 .await?;
1196 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1197 VectorizerError::server(format!("Failed to parse discover response: {e}"))
1198 })?;
1199 Ok(result)
1200 }
1201
1202 pub async fn filter_collections(
1204 &self,
1205 query: &str,
1206 include: Option<Vec<String>>,
1207 exclude: Option<Vec<String>>,
1208 ) -> Result<serde_json::Value> {
1209 if query.trim().is_empty() {
1211 return Err(VectorizerError::validation("Query cannot be empty"));
1212 }
1213
1214 let mut payload = serde_json::Map::new();
1215 payload.insert(
1216 "query".to_string(),
1217 serde_json::Value::String(query.to_string()),
1218 );
1219
1220 if let Some(inc) = include {
1221 payload.insert("include".to_string(), serde_json::to_value(inc).unwrap());
1222 }
1223 if let Some(exc) = exclude {
1224 payload.insert("exclude".to_string(), serde_json::to_value(exc).unwrap());
1225 }
1226
1227 let response = self
1228 .make_request(
1229 "POST",
1230 "/discovery/filter_collections",
1231 Some(serde_json::Value::Object(payload)),
1232 )
1233 .await?;
1234 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1235 VectorizerError::server(format!("Failed to parse filter response: {e}"))
1236 })?;
1237 Ok(result)
1238 }
1239
1240 pub async fn score_collections(
1242 &self,
1243 query: &str,
1244 name_match_weight: Option<f32>,
1245 term_boost_weight: Option<f32>,
1246 signal_boost_weight: Option<f32>,
1247 ) -> Result<serde_json::Value> {
1248 if let Some(w) = name_match_weight
1250 && !(0.0..=1.0).contains(&w)
1251 {
1252 return Err(VectorizerError::validation(
1253 "name_match_weight must be between 0.0 and 1.0",
1254 ));
1255 }
1256 if let Some(w) = term_boost_weight
1257 && !(0.0..=1.0).contains(&w)
1258 {
1259 return Err(VectorizerError::validation(
1260 "term_boost_weight must be between 0.0 and 1.0",
1261 ));
1262 }
1263 if let Some(w) = signal_boost_weight
1264 && !(0.0..=1.0).contains(&w)
1265 {
1266 return Err(VectorizerError::validation(
1267 "signal_boost_weight must be between 0.0 and 1.0",
1268 ));
1269 }
1270
1271 let mut payload = serde_json::Map::new();
1272 payload.insert(
1273 "query".to_string(),
1274 serde_json::Value::String(query.to_string()),
1275 );
1276
1277 if let Some(w) = name_match_weight {
1278 payload.insert("name_match_weight".to_string(), serde_json::json!(w));
1279 }
1280 if let Some(w) = term_boost_weight {
1281 payload.insert("term_boost_weight".to_string(), serde_json::json!(w));
1282 }
1283 if let Some(w) = signal_boost_weight {
1284 payload.insert("signal_boost_weight".to_string(), serde_json::json!(w));
1285 }
1286
1287 let response = self
1288 .make_request(
1289 "POST",
1290 "/discovery/score_collections",
1291 Some(serde_json::Value::Object(payload)),
1292 )
1293 .await?;
1294 let result: serde_json::Value = serde_json::from_str(&response)
1295 .map_err(|e| VectorizerError::server(format!("Failed to parse score response: {e}")))?;
1296 Ok(result)
1297 }
1298
1299 pub async fn expand_queries(
1301 &self,
1302 query: &str,
1303 max_expansions: Option<usize>,
1304 include_definition: Option<bool>,
1305 include_features: Option<bool>,
1306 include_architecture: Option<bool>,
1307 ) -> Result<serde_json::Value> {
1308 let mut payload = serde_json::Map::new();
1309 payload.insert(
1310 "query".to_string(),
1311 serde_json::Value::String(query.to_string()),
1312 );
1313
1314 if let Some(max) = max_expansions {
1315 payload.insert(
1316 "max_expansions".to_string(),
1317 serde_json::Value::Number(max.into()),
1318 );
1319 }
1320 if let Some(def) = include_definition {
1321 payload.insert(
1322 "include_definition".to_string(),
1323 serde_json::Value::Bool(def),
1324 );
1325 }
1326 if let Some(feat) = include_features {
1327 payload.insert(
1328 "include_features".to_string(),
1329 serde_json::Value::Bool(feat),
1330 );
1331 }
1332 if let Some(arch) = include_architecture {
1333 payload.insert(
1334 "include_architecture".to_string(),
1335 serde_json::Value::Bool(arch),
1336 );
1337 }
1338
1339 let response = self
1340 .make_request(
1341 "POST",
1342 "/discovery/expand_queries",
1343 Some(serde_json::Value::Object(payload)),
1344 )
1345 .await?;
1346 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1347 VectorizerError::server(format!("Failed to parse expand response: {e}"))
1348 })?;
1349 Ok(result)
1350 }
1351
1352 pub async fn get_file_content(
1358 &self,
1359 collection: &str,
1360 file_path: &str,
1361 max_size_kb: Option<usize>,
1362 ) -> Result<serde_json::Value> {
1363 let mut payload = serde_json::Map::new();
1364 payload.insert(
1365 "collection".to_string(),
1366 serde_json::Value::String(collection.to_string()),
1367 );
1368 payload.insert(
1369 "file_path".to_string(),
1370 serde_json::Value::String(file_path.to_string()),
1371 );
1372
1373 if let Some(max) = max_size_kb {
1374 payload.insert(
1375 "max_size_kb".to_string(),
1376 serde_json::Value::Number(max.into()),
1377 );
1378 }
1379
1380 let response = self
1381 .make_request(
1382 "POST",
1383 "/file/content",
1384 Some(serde_json::Value::Object(payload)),
1385 )
1386 .await?;
1387 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1388 VectorizerError::server(format!("Failed to parse file content response: {e}"))
1389 })?;
1390 Ok(result)
1391 }
1392
1393 pub async fn list_files_in_collection(
1395 &self,
1396 collection: &str,
1397 filter_by_type: Option<Vec<String>>,
1398 min_chunks: Option<usize>,
1399 max_results: Option<usize>,
1400 sort_by: Option<&str>,
1401 ) -> Result<serde_json::Value> {
1402 let mut payload = serde_json::Map::new();
1403 payload.insert(
1404 "collection".to_string(),
1405 serde_json::Value::String(collection.to_string()),
1406 );
1407
1408 if let Some(types) = filter_by_type {
1409 payload.insert(
1410 "filter_by_type".to_string(),
1411 serde_json::to_value(types).unwrap(),
1412 );
1413 }
1414 if let Some(min) = min_chunks {
1415 payload.insert(
1416 "min_chunks".to_string(),
1417 serde_json::Value::Number(min.into()),
1418 );
1419 }
1420 if let Some(max) = max_results {
1421 payload.insert(
1422 "max_results".to_string(),
1423 serde_json::Value::Number(max.into()),
1424 );
1425 }
1426 if let Some(sort) = sort_by {
1427 payload.insert(
1428 "sort_by".to_string(),
1429 serde_json::Value::String(sort.to_string()),
1430 );
1431 }
1432
1433 let response = self
1434 .make_request(
1435 "POST",
1436 "/file/list",
1437 Some(serde_json::Value::Object(payload)),
1438 )
1439 .await?;
1440 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1441 VectorizerError::server(format!("Failed to parse list files response: {e}"))
1442 })?;
1443 Ok(result)
1444 }
1445
1446 pub async fn get_file_summary(
1448 &self,
1449 collection: &str,
1450 file_path: &str,
1451 summary_type: Option<&str>,
1452 max_sentences: Option<usize>,
1453 ) -> Result<serde_json::Value> {
1454 let mut payload = serde_json::Map::new();
1455 payload.insert(
1456 "collection".to_string(),
1457 serde_json::Value::String(collection.to_string()),
1458 );
1459 payload.insert(
1460 "file_path".to_string(),
1461 serde_json::Value::String(file_path.to_string()),
1462 );
1463
1464 if let Some(stype) = summary_type {
1465 payload.insert(
1466 "summary_type".to_string(),
1467 serde_json::Value::String(stype.to_string()),
1468 );
1469 }
1470 if let Some(max) = max_sentences {
1471 payload.insert(
1472 "max_sentences".to_string(),
1473 serde_json::Value::Number(max.into()),
1474 );
1475 }
1476
1477 let response = self
1478 .make_request(
1479 "POST",
1480 "/file/summary",
1481 Some(serde_json::Value::Object(payload)),
1482 )
1483 .await?;
1484 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1485 VectorizerError::server(format!("Failed to parse file summary response: {e}"))
1486 })?;
1487 Ok(result)
1488 }
1489
1490 pub async fn get_file_chunks_ordered(
1492 &self,
1493 collection: &str,
1494 file_path: &str,
1495 start_chunk: Option<usize>,
1496 limit: Option<usize>,
1497 include_context: Option<bool>,
1498 ) -> Result<serde_json::Value> {
1499 let mut payload = serde_json::Map::new();
1500 payload.insert(
1501 "collection".to_string(),
1502 serde_json::Value::String(collection.to_string()),
1503 );
1504 payload.insert(
1505 "file_path".to_string(),
1506 serde_json::Value::String(file_path.to_string()),
1507 );
1508
1509 if let Some(start) = start_chunk {
1510 payload.insert(
1511 "start_chunk".to_string(),
1512 serde_json::Value::Number(start.into()),
1513 );
1514 }
1515 if let Some(lim) = limit {
1516 payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
1517 }
1518 if let Some(ctx) = include_context {
1519 payload.insert("include_context".to_string(), serde_json::Value::Bool(ctx));
1520 }
1521
1522 let response = self
1523 .make_request(
1524 "POST",
1525 "/file/chunks",
1526 Some(serde_json::Value::Object(payload)),
1527 )
1528 .await?;
1529 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1530 VectorizerError::server(format!("Failed to parse chunks response: {e}"))
1531 })?;
1532 Ok(result)
1533 }
1534
1535 pub async fn get_project_outline(
1537 &self,
1538 collection: &str,
1539 max_depth: Option<usize>,
1540 include_summaries: Option<bool>,
1541 highlight_key_files: Option<bool>,
1542 ) -> Result<serde_json::Value> {
1543 let mut payload = serde_json::Map::new();
1544 payload.insert(
1545 "collection".to_string(),
1546 serde_json::Value::String(collection.to_string()),
1547 );
1548
1549 if let Some(depth) = max_depth {
1550 payload.insert(
1551 "max_depth".to_string(),
1552 serde_json::Value::Number(depth.into()),
1553 );
1554 }
1555 if let Some(summ) = include_summaries {
1556 payload.insert(
1557 "include_summaries".to_string(),
1558 serde_json::Value::Bool(summ),
1559 );
1560 }
1561 if let Some(highlight) = highlight_key_files {
1562 payload.insert(
1563 "highlight_key_files".to_string(),
1564 serde_json::Value::Bool(highlight),
1565 );
1566 }
1567
1568 let response = self
1569 .make_request(
1570 "POST",
1571 "/file/outline",
1572 Some(serde_json::Value::Object(payload)),
1573 )
1574 .await?;
1575 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1576 VectorizerError::server(format!("Failed to parse outline response: {e}"))
1577 })?;
1578 Ok(result)
1579 }
1580
1581 pub async fn get_related_files(
1583 &self,
1584 collection: &str,
1585 file_path: &str,
1586 limit: Option<usize>,
1587 similarity_threshold: Option<f32>,
1588 include_reason: Option<bool>,
1589 ) -> Result<serde_json::Value> {
1590 let mut payload = serde_json::Map::new();
1591 payload.insert(
1592 "collection".to_string(),
1593 serde_json::Value::String(collection.to_string()),
1594 );
1595 payload.insert(
1596 "file_path".to_string(),
1597 serde_json::Value::String(file_path.to_string()),
1598 );
1599
1600 if let Some(lim) = limit {
1601 payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
1602 }
1603 if let Some(thresh) = similarity_threshold {
1604 payload.insert(
1605 "similarity_threshold".to_string(),
1606 serde_json::json!(thresh),
1607 );
1608 }
1609 if let Some(reason) = include_reason {
1610 payload.insert(
1611 "include_reason".to_string(),
1612 serde_json::Value::Bool(reason),
1613 );
1614 }
1615
1616 let response = self
1617 .make_request(
1618 "POST",
1619 "/file/related",
1620 Some(serde_json::Value::Object(payload)),
1621 )
1622 .await?;
1623 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1624 VectorizerError::server(format!("Failed to parse related files response: {e}"))
1625 })?;
1626 Ok(result)
1627 }
1628
1629 pub async fn search_by_file_type(
1631 &self,
1632 collection: &str,
1633 query: &str,
1634 file_types: Vec<String>,
1635 limit: Option<usize>,
1636 return_full_files: Option<bool>,
1637 ) -> Result<serde_json::Value> {
1638 if file_types.is_empty() {
1640 return Err(VectorizerError::validation("file_types cannot be empty"));
1641 }
1642
1643 let mut payload = serde_json::Map::new();
1644 payload.insert(
1645 "collection".to_string(),
1646 serde_json::Value::String(collection.to_string()),
1647 );
1648 payload.insert(
1649 "query".to_string(),
1650 serde_json::Value::String(query.to_string()),
1651 );
1652 payload.insert(
1653 "file_types".to_string(),
1654 serde_json::to_value(file_types).unwrap(),
1655 );
1656
1657 if let Some(lim) = limit {
1658 payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
1659 }
1660 if let Some(full) = return_full_files {
1661 payload.insert(
1662 "return_full_files".to_string(),
1663 serde_json::Value::Bool(full),
1664 );
1665 }
1666
1667 let response = self
1668 .make_request(
1669 "POST",
1670 "/file/search_by_type",
1671 Some(serde_json::Value::Object(payload)),
1672 )
1673 .await?;
1674 let result: serde_json::Value = serde_json::from_str(&response).map_err(|e| {
1675 VectorizerError::server(format!("Failed to parse search by type response: {e}"))
1676 })?;
1677 Ok(result)
1678 }
1679
1680 async fn make_request(
1682 &self,
1683 method: &str,
1684 endpoint: &str,
1685 payload: Option<serde_json::Value>,
1686 ) -> Result<String> {
1687 match method {
1688 "GET" => self.transport.get(endpoint).await,
1689 "POST" => self.transport.post(endpoint, payload.as_ref()).await,
1690 "PUT" => self.transport.put(endpoint, payload.as_ref()).await,
1691 "DELETE" => self.transport.delete(endpoint).await,
1692 _ => Err(VectorizerError::configuration(format!(
1693 "Unsupported method: {}",
1694 method
1695 ))),
1696 }
1697 }
1698
1699 pub async fn list_graph_nodes(&self, collection: &str) -> Result<ListNodesResponse> {
1703 let url = format!("/graph/nodes/{}", collection);
1704 let response = self.make_request("GET", &url, None).await?;
1705 let result: ListNodesResponse = serde_json::from_str(&response).map_err(|e| {
1706 VectorizerError::server(format!("Failed to parse list nodes response: {e}"))
1707 })?;
1708 Ok(result)
1709 }
1710
1711 pub async fn get_graph_neighbors(
1713 &self,
1714 collection: &str,
1715 node_id: &str,
1716 ) -> Result<GetNeighborsResponse> {
1717 let url = format!("/graph/nodes/{}/{}/neighbors", collection, node_id);
1718 let response = self.make_request("GET", &url, None).await?;
1719 let result: GetNeighborsResponse = serde_json::from_str(&response).map_err(|e| {
1720 VectorizerError::server(format!("Failed to parse neighbors response: {e}"))
1721 })?;
1722 Ok(result)
1723 }
1724
1725 pub async fn find_related_nodes(
1727 &self,
1728 collection: &str,
1729 node_id: &str,
1730 request: FindRelatedRequest,
1731 ) -> Result<FindRelatedResponse> {
1732 let url = format!("/graph/nodes/{}/{}/related", collection, node_id);
1733 let payload = serde_json::to_value(&request).map_err(|e| {
1734 VectorizerError::validation(format!("Failed to serialize request: {e}"))
1735 })?;
1736 let response = self.make_request("POST", &url, Some(payload)).await?;
1737 let result: FindRelatedResponse = serde_json::from_str(&response).map_err(|e| {
1738 VectorizerError::server(format!("Failed to parse related nodes response: {e}"))
1739 })?;
1740 Ok(result)
1741 }
1742
1743 pub async fn find_graph_path(&self, request: FindPathRequest) -> Result<FindPathResponse> {
1745 let payload = serde_json::to_value(&request).map_err(|e| {
1746 VectorizerError::validation(format!("Failed to serialize request: {e}"))
1747 })?;
1748 let response = self
1749 .make_request("POST", "/graph/path", Some(payload))
1750 .await?;
1751 let result: FindPathResponse = serde_json::from_str(&response)
1752 .map_err(|e| VectorizerError::server(format!("Failed to parse path response: {e}")))?;
1753 Ok(result)
1754 }
1755
1756 pub async fn create_graph_edge(
1758 &self,
1759 request: CreateEdgeRequest,
1760 ) -> Result<CreateEdgeResponse> {
1761 let payload = serde_json::to_value(&request).map_err(|e| {
1762 VectorizerError::validation(format!("Failed to serialize request: {e}"))
1763 })?;
1764 let response = self
1765 .make_request("POST", "/graph/edges", Some(payload))
1766 .await?;
1767 let result: CreateEdgeResponse = serde_json::from_str(&response).map_err(|e| {
1768 VectorizerError::server(format!("Failed to parse create edge response: {e}"))
1769 })?;
1770 Ok(result)
1771 }
1772
1773 pub async fn delete_graph_edge(&self, edge_id: &str) -> Result<()> {
1775 let url = format!("/graph/edges/{}", edge_id);
1776 self.make_request("DELETE", &url, None).await?;
1777 Ok(())
1778 }
1779
1780 pub async fn list_graph_edges(&self, collection: &str) -> Result<ListEdgesResponse> {
1782 let url = format!("/graph/collections/{}/edges", collection);
1783 let response = self.make_request("GET", &url, None).await?;
1784 let result: ListEdgesResponse = serde_json::from_str(&response).map_err(|e| {
1785 VectorizerError::server(format!("Failed to parse list edges response: {}", e))
1786 })?;
1787 Ok(result)
1788 }
1789
1790 pub async fn discover_graph_edges(
1792 &self,
1793 collection: &str,
1794 request: DiscoverEdgesRequest,
1795 ) -> Result<DiscoverEdgesResponse> {
1796 let url = format!("/graph/discover/{}", collection);
1797 let payload = serde_json::to_value(&request).map_err(|e| {
1798 VectorizerError::validation(format!("Failed to serialize request: {e}"))
1799 })?;
1800 let response = self.make_request("POST", &url, Some(payload)).await?;
1801 let result: DiscoverEdgesResponse = serde_json::from_str(&response).map_err(|e| {
1802 VectorizerError::server(format!("Failed to parse discover edges response: {}", e))
1803 })?;
1804 Ok(result)
1805 }
1806
1807 pub async fn discover_graph_edges_for_node(
1809 &self,
1810 collection: &str,
1811 node_id: &str,
1812 request: DiscoverEdgesRequest,
1813 ) -> Result<DiscoverEdgesResponse> {
1814 let url = format!("/graph/discover/{collection}/{node_id}");
1815 let payload = serde_json::to_value(&request).map_err(|e| {
1816 VectorizerError::validation(format!("Failed to serialize request: {e}"))
1817 })?;
1818 let response = self.make_request("POST", &url, Some(payload)).await?;
1819 let result: DiscoverEdgesResponse = serde_json::from_str(&response).map_err(|e| {
1820 VectorizerError::server(format!("Failed to parse discover edges response: {}", e))
1821 })?;
1822 Ok(result)
1823 }
1824
1825 pub async fn get_graph_discovery_status(
1827 &self,
1828 collection: &str,
1829 ) -> Result<DiscoveryStatusResponse> {
1830 let url = format!("/graph/discover/{}/status", collection);
1831 let response = self.make_request("GET", &url, None).await?;
1832 let result: DiscoveryStatusResponse = serde_json::from_str(&response).map_err(|e| {
1833 VectorizerError::server(format!("Failed to parse discovery status response: {}", e))
1834 })?;
1835 Ok(result)
1836 }
1837
1838 pub async fn upload_file(
1873 &self,
1874 file_bytes: Vec<u8>,
1875 filename: &str,
1876 collection_name: &str,
1877 options: UploadFileOptions,
1878 ) -> Result<FileUploadResponse> {
1879 let mut form_fields = std::collections::HashMap::new();
1880 form_fields.insert("collection_name".to_string(), collection_name.to_string());
1881
1882 if let Some(chunk_size) = options.chunk_size {
1883 form_fields.insert("chunk_size".to_string(), chunk_size.to_string());
1884 }
1885
1886 if let Some(chunk_overlap) = options.chunk_overlap {
1887 form_fields.insert("chunk_overlap".to_string(), chunk_overlap.to_string());
1888 }
1889
1890 if let Some(metadata) = options.metadata {
1891 let metadata_json = serde_json::to_string(&metadata).map_err(|e| {
1892 VectorizerError::validation(format!("Failed to serialize metadata: {e}"))
1893 })?;
1894 form_fields.insert("metadata".to_string(), metadata_json);
1895 }
1896
1897 if let Some(public_key) = options.public_key {
1898 form_fields.insert("public_key".to_string(), public_key);
1899 }
1900
1901 let http_transport = crate::http_transport::HttpTransport::new(
1903 &self.base_url,
1904 self.config.api_key.as_deref(),
1905 self.config.timeout_secs.unwrap_or(30),
1906 )?;
1907
1908 let response = http_transport
1909 .post_multipart("/files/upload", file_bytes, filename, form_fields)
1910 .await?;
1911
1912 let result: FileUploadResponse = serde_json::from_str(&response).map_err(|e| {
1913 VectorizerError::server(format!("Failed to parse upload response: {e}"))
1914 })?;
1915
1916 Ok(result)
1917 }
1918
1919 pub async fn upload_file_content(
1953 &self,
1954 content: &str,
1955 filename: &str,
1956 collection_name: &str,
1957 options: UploadFileOptions,
1958 ) -> Result<FileUploadResponse> {
1959 let file_bytes = content.as_bytes().to_vec();
1960 self.upload_file(file_bytes, filename, collection_name, options)
1961 .await
1962 }
1963
1964 pub async fn get_upload_config(&self) -> Result<FileUploadConfig> {
1984 let response = self.make_request("GET", "/files/config", None).await?;
1985 let result: FileUploadConfig = serde_json::from_str(&response)
1986 .map_err(|e| VectorizerError::server(format!("Failed to parse upload config: {e}")))?;
1987 Ok(result)
1988 }
1989}