1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
12pub struct RetryConfig {
13 pub max_retries: u32,
15 pub base_delay: std::time::Duration,
17 pub max_delay: std::time::Duration,
19 pub jitter: bool,
21}
22
23impl Default for RetryConfig {
24 fn default() -> Self {
25 Self {
26 max_retries: 3,
27 base_delay: std::time::Duration::from_millis(100),
28 max_delay: std::time::Duration::from_secs(60),
29 jitter: true,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Default)]
43pub struct RateLimitHeaders {
44 pub limit: Option<u64>,
46 pub remaining: Option<u64>,
48 pub reset: Option<u64>,
50 pub quota_used: Option<u64>,
52 pub quota_limit: Option<u64>,
54}
55
56impl RateLimitHeaders {
57 pub fn from_response(response: &reqwest::Response) -> Self {
59 let headers = response.headers();
60 fn parse(h: &reqwest::header::HeaderMap, name: &str) -> Option<u64> {
61 h.get(name)
62 .and_then(|v| v.to_str().ok())
63 .and_then(|s| s.parse().ok())
64 }
65 Self {
66 limit: parse(headers, "X-RateLimit-Limit"),
67 remaining: parse(headers, "X-RateLimit-Remaining"),
68 reset: parse(headers, "X-RateLimit-Reset"),
69 quota_used: parse(headers, "X-Quota-Used"),
70 quota_limit: parse(headers, "X-Quota-Limit"),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct HealthResponse {
82 pub healthy: bool,
84 pub version: Option<String>,
86 pub uptime_seconds: Option<u64>,
88 pub build_sha: Option<String>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ReadinessResponse {
95 pub ready: bool,
97 pub components: Option<HashMap<String, bool>>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct NamespaceInfo {
108 #[serde(alias = "namespace")]
110 pub name: String,
111 #[serde(default)]
113 pub vector_count: u64,
114 #[serde(alias = "dimension")]
116 pub dimensions: Option<u32>,
117 pub index_type: Option<String>,
119 #[serde(default)]
121 pub created: Option<bool>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct ListNamespacesResponse {
127 pub namespaces: Vec<String>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Vector {
138 pub id: String,
140 pub values: Vec<f32>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub metadata: Option<HashMap<String, serde_json::Value>>,
145}
146
147impl Vector {
148 pub fn new(id: impl Into<String>, values: Vec<f32>) -> Self {
150 Self {
151 id: id.into(),
152 values,
153 metadata: None,
154 }
155 }
156
157 pub fn with_metadata(
159 id: impl Into<String>,
160 values: Vec<f32>,
161 metadata: HashMap<String, serde_json::Value>,
162 ) -> Self {
163 Self {
164 id: id.into(),
165 values,
166 metadata: Some(metadata),
167 }
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct UpsertRequest {
174 pub vectors: Vec<Vector>,
176}
177
178impl UpsertRequest {
179 pub fn single(vector: Vector) -> Self {
181 Self {
182 vectors: vec![vector],
183 }
184 }
185
186 pub fn batch(vectors: Vec<Vector>) -> Self {
188 Self { vectors }
189 }
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct UpsertResponse {
195 pub upserted_count: u64,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct ColumnUpsertRequest {
217 pub ids: Vec<String>,
219 pub vectors: Vec<Vec<f32>>,
221 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
224 pub attributes: HashMap<String, Vec<serde_json::Value>>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub ttl_seconds: Option<u64>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub dimension: Option<usize>,
231}
232
233impl ColumnUpsertRequest {
234 pub fn new(ids: Vec<String>, vectors: Vec<Vec<f32>>) -> Self {
236 Self {
237 ids,
238 vectors,
239 attributes: HashMap::new(),
240 ttl_seconds: None,
241 dimension: None,
242 }
243 }
244
245 pub fn with_attribute(
247 mut self,
248 name: impl Into<String>,
249 values: Vec<serde_json::Value>,
250 ) -> Self {
251 self.attributes.insert(name.into(), values);
252 self
253 }
254
255 pub fn with_ttl(mut self, seconds: u64) -> Self {
257 self.ttl_seconds = Some(seconds);
258 self
259 }
260
261 pub fn with_dimension(mut self, dim: usize) -> Self {
263 self.dimension = Some(dim);
264 self
265 }
266
267 pub fn len(&self) -> usize {
269 self.ids.len()
270 }
271
272 pub fn is_empty(&self) -> bool {
274 self.ids.is_empty()
275 }
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct DeleteRequest {
281 pub ids: Vec<String>,
283}
284
285impl DeleteRequest {
286 pub fn single(id: impl Into<String>) -> Self {
288 Self {
289 ids: vec![id.into()],
290 }
291 }
292
293 pub fn batch(ids: Vec<String>) -> Self {
295 Self { ids }
296 }
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct DeleteResponse {
302 pub deleted_count: u64,
304}
305
306#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
312#[serde(rename_all = "snake_case")]
313pub enum ReadConsistency {
314 Strong,
316 #[default]
318 Eventual,
319 #[serde(rename = "bounded_staleness")]
321 BoundedStaleness,
322}
323
324#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
326pub struct StalenessConfig {
327 #[serde(default = "default_max_staleness_ms")]
329 pub max_staleness_ms: u64,
330}
331
332fn default_max_staleness_ms() -> u64 {
333 5000 }
335
336impl StalenessConfig {
337 pub fn new(max_staleness_ms: u64) -> Self {
339 Self { max_staleness_ms }
340 }
341}
342
343#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
345#[serde(rename_all = "snake_case")]
346pub enum DistanceMetric {
347 #[default]
349 Cosine,
350 Euclidean,
352 DotProduct,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct QueryRequest {
359 pub vector: Vec<f32>,
361 pub top_k: u32,
363 #[serde(default)]
365 pub distance_metric: DistanceMetric,
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub filter: Option<serde_json::Value>,
369 #[serde(default = "default_true")]
371 pub include_metadata: bool,
372 #[serde(default)]
374 pub include_vectors: bool,
375 #[serde(default)]
377 pub consistency: ReadConsistency,
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub staleness_config: Option<StalenessConfig>,
381}
382
383fn default_true() -> bool {
384 true
385}
386
387impl QueryRequest {
388 pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
390 Self {
391 vector,
392 top_k,
393 distance_metric: DistanceMetric::default(),
394 filter: None,
395 include_metadata: true,
396 include_vectors: false,
397 consistency: ReadConsistency::default(),
398 staleness_config: None,
399 }
400 }
401
402 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
404 self.filter = Some(filter);
405 self
406 }
407
408 pub fn include_metadata(mut self, include: bool) -> Self {
410 self.include_metadata = include;
411 self
412 }
413
414 pub fn include_vectors(mut self, include: bool) -> Self {
416 self.include_vectors = include;
417 self
418 }
419
420 pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
422 self.distance_metric = metric;
423 self
424 }
425
426 pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
428 self.consistency = consistency;
429 self
430 }
431
432 pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
434 self.consistency = ReadConsistency::BoundedStaleness;
435 self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
436 self
437 }
438
439 pub fn with_strong_consistency(mut self) -> Self {
441 self.consistency = ReadConsistency::Strong;
442 self
443 }
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct Match {
449 pub id: String,
451 pub score: f32,
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub metadata: Option<HashMap<String, serde_json::Value>>,
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct QueryResponse {
461 #[serde(alias = "matches")]
463 pub results: Vec<Match>,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
472pub struct Document {
473 pub id: String,
475 pub text: String,
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub metadata: Option<HashMap<String, serde_json::Value>>,
480}
481
482impl Document {
483 pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
485 Self {
486 id: id.into(),
487 text: text.into(),
488 metadata: None,
489 }
490 }
491
492 pub fn with_metadata(
494 id: impl Into<String>,
495 text: impl Into<String>,
496 metadata: HashMap<String, serde_json::Value>,
497 ) -> Self {
498 Self {
499 id: id.into(),
500 text: text.into(),
501 metadata: Some(metadata),
502 }
503 }
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct IndexDocumentsRequest {
509 pub documents: Vec<Document>,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct IndexDocumentsResponse {
516 pub indexed_count: u64,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct FullTextSearchRequest {
523 pub query: String,
525 pub top_k: u32,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub filter: Option<serde_json::Value>,
530}
531
532impl FullTextSearchRequest {
533 pub fn new(query: impl Into<String>, top_k: u32) -> Self {
535 Self {
536 query: query.into(),
537 top_k,
538 filter: None,
539 }
540 }
541
542 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
544 self.filter = Some(filter);
545 self
546 }
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
551pub struct FullTextMatch {
552 pub id: String,
554 pub score: f32,
556 #[serde(skip_serializing_if = "Option::is_none")]
558 pub text: Option<String>,
559 #[serde(skip_serializing_if = "Option::is_none")]
561 pub metadata: Option<HashMap<String, serde_json::Value>>,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
566pub struct FullTextSearchResponse {
567 #[serde(alias = "matches")]
569 pub results: Vec<FullTextMatch>,
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize)]
574pub struct FullTextStats {
575 pub document_count: u64,
577 pub term_count: u64,
579}
580
581#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct HybridSearchRequest {
591 #[serde(skip_serializing_if = "Option::is_none")]
593 pub vector: Option<Vec<f32>>,
594 pub text: String,
596 pub top_k: u32,
598 #[serde(default = "default_vector_weight")]
600 pub vector_weight: f32,
601 #[serde(skip_serializing_if = "Option::is_none")]
603 pub filter: Option<serde_json::Value>,
604}
605
606fn default_vector_weight() -> f32 {
607 0.5
608}
609
610impl HybridSearchRequest {
611 pub fn new(vector: Vec<f32>, text: impl Into<String>, top_k: u32) -> Self {
613 Self {
614 vector: Some(vector),
615 text: text.into(),
616 top_k,
617 vector_weight: 0.5,
618 filter: None,
619 }
620 }
621
622 pub fn text_only(text: impl Into<String>, top_k: u32) -> Self {
624 Self {
625 vector: None,
626 text: text.into(),
627 top_k,
628 vector_weight: 0.5,
629 filter: None,
630 }
631 }
632
633 pub fn with_vector_weight(mut self, weight: f32) -> Self {
635 self.vector_weight = weight.clamp(0.0, 1.0);
636 self
637 }
638
639 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
641 self.filter = Some(filter);
642 self
643 }
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
648pub struct HybridSearchResponse {
649 #[serde(alias = "matches")]
651 pub results: Vec<Match>,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize)]
660pub struct SystemDiagnostics {
661 pub system: SystemInfo,
663 pub resources: ResourceUsage,
665 pub components: ComponentHealth,
667 pub active_jobs: u64,
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize)]
673pub struct SystemInfo {
674 pub version: String,
676 pub rust_version: String,
678 pub uptime_seconds: u64,
680 pub pid: u32,
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
686pub struct ResourceUsage {
687 pub memory_bytes: u64,
689 pub thread_count: u64,
691 pub open_fds: u64,
693 pub cpu_percent: Option<f64>,
695}
696
697#[derive(Debug, Clone, Serialize, Deserialize)]
699pub struct ComponentHealth {
700 pub storage: HealthStatus,
702 pub search_engine: HealthStatus,
704 pub cache: HealthStatus,
706 pub grpc: HealthStatus,
708}
709
710#[derive(Debug, Clone, Serialize, Deserialize)]
712pub struct HealthStatus {
713 pub healthy: bool,
715 pub message: String,
717 pub last_check: u64,
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize)]
723pub struct JobInfo {
724 pub id: String,
726 pub job_type: String,
728 pub status: String,
730 pub created_at: u64,
732 #[serde(skip_serializing_if = "Option::is_none")]
734 pub started_at: Option<u64>,
735 #[serde(skip_serializing_if = "Option::is_none")]
737 pub completed_at: Option<u64>,
738 pub progress: u8,
740 #[serde(skip_serializing_if = "Option::is_none")]
742 pub message: Option<String>,
743 #[serde(default)]
745 pub metadata: std::collections::HashMap<String, String>,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize)]
750pub struct CompactionRequest {
751 #[serde(skip_serializing_if = "Option::is_none")]
753 pub namespace: Option<String>,
754 #[serde(default)]
756 pub force: bool,
757}
758
759#[derive(Debug, Clone, Serialize, Deserialize)]
761pub struct CompactionResponse {
762 pub job_id: String,
764 pub message: String,
766}
767
768#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
774#[serde(rename_all = "snake_case")]
775pub enum WarmingPriority {
776 Critical,
778 High,
780 #[default]
782 Normal,
783 Low,
785 Background,
787}
788
789#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
791#[serde(rename_all = "snake_case")]
792pub enum WarmingTargetTier {
793 L1,
795 #[default]
797 L2,
798 Both,
800}
801
802#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
804#[serde(rename_all = "snake_case")]
805pub enum AccessPatternHint {
806 #[default]
808 Random,
809 Sequential,
811 Temporal,
813 Spatial,
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
819pub struct WarmCacheRequest {
820 pub namespace: String,
822 #[serde(skip_serializing_if = "Option::is_none")]
824 pub vector_ids: Option<Vec<String>>,
825 #[serde(default)]
827 pub priority: WarmingPriority,
828 #[serde(default)]
830 pub target_tier: WarmingTargetTier,
831 #[serde(default)]
833 pub background: bool,
834 #[serde(skip_serializing_if = "Option::is_none")]
836 pub ttl_hint_seconds: Option<u64>,
837 #[serde(default)]
839 pub access_pattern: AccessPatternHint,
840 #[serde(skip_serializing_if = "Option::is_none")]
842 pub max_vectors: Option<usize>,
843}
844
845impl WarmCacheRequest {
846 pub fn new(namespace: impl Into<String>) -> Self {
848 Self {
849 namespace: namespace.into(),
850 vector_ids: None,
851 priority: WarmingPriority::default(),
852 target_tier: WarmingTargetTier::default(),
853 background: false,
854 ttl_hint_seconds: None,
855 access_pattern: AccessPatternHint::default(),
856 max_vectors: None,
857 }
858 }
859
860 pub fn with_vector_ids(mut self, ids: Vec<String>) -> Self {
862 self.vector_ids = Some(ids);
863 self
864 }
865
866 pub fn with_priority(mut self, priority: WarmingPriority) -> Self {
868 self.priority = priority;
869 self
870 }
871
872 pub fn with_target_tier(mut self, tier: WarmingTargetTier) -> Self {
874 self.target_tier = tier;
875 self
876 }
877
878 pub fn in_background(mut self) -> Self {
880 self.background = true;
881 self
882 }
883
884 pub fn with_ttl(mut self, seconds: u64) -> Self {
886 self.ttl_hint_seconds = Some(seconds);
887 self
888 }
889
890 pub fn with_access_pattern(mut self, pattern: AccessPatternHint) -> Self {
892 self.access_pattern = pattern;
893 self
894 }
895
896 pub fn with_max_vectors(mut self, max: usize) -> Self {
898 self.max_vectors = Some(max);
899 self
900 }
901}
902
903#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct WarmCacheResponse {
906 pub success: bool,
908 pub entries_warmed: u64,
910 pub entries_skipped: u64,
912 #[serde(skip_serializing_if = "Option::is_none")]
914 pub job_id: Option<String>,
915 pub message: String,
917 #[serde(skip_serializing_if = "Option::is_none")]
919 pub estimated_completion: Option<String>,
920 pub target_tier: WarmingTargetTier,
922 pub priority: WarmingPriority,
924 #[serde(skip_serializing_if = "Option::is_none")]
926 pub bytes_warmed: Option<u64>,
927}
928
929#[derive(Debug, Clone, Serialize, Deserialize)]
935pub struct ExportRequest {
936 #[serde(default = "default_export_top_k")]
938 pub top_k: usize,
939 #[serde(skip_serializing_if = "Option::is_none")]
941 pub cursor: Option<String>,
942 #[serde(default = "default_true")]
944 pub include_vectors: bool,
945 #[serde(default = "default_true")]
947 pub include_metadata: bool,
948}
949
950fn default_export_top_k() -> usize {
951 1000
952}
953
954impl Default for ExportRequest {
955 fn default() -> Self {
956 Self {
957 top_k: 1000,
958 cursor: None,
959 include_vectors: true,
960 include_metadata: true,
961 }
962 }
963}
964
965impl ExportRequest {
966 pub fn new() -> Self {
968 Self::default()
969 }
970
971 pub fn with_top_k(mut self, top_k: usize) -> Self {
973 self.top_k = top_k;
974 self
975 }
976
977 pub fn with_cursor(mut self, cursor: impl Into<String>) -> Self {
979 self.cursor = Some(cursor.into());
980 self
981 }
982
983 pub fn include_vectors(mut self, include: bool) -> Self {
985 self.include_vectors = include;
986 self
987 }
988
989 pub fn include_metadata(mut self, include: bool) -> Self {
991 self.include_metadata = include;
992 self
993 }
994}
995
996#[derive(Debug, Clone, Serialize, Deserialize)]
998pub struct ExportedVector {
999 pub id: String,
1001 #[serde(skip_serializing_if = "Option::is_none")]
1003 pub values: Option<Vec<f32>>,
1004 #[serde(skip_serializing_if = "Option::is_none")]
1006 pub metadata: Option<serde_json::Value>,
1007 #[serde(skip_serializing_if = "Option::is_none")]
1009 pub ttl_seconds: Option<u64>,
1010}
1011
1012#[derive(Debug, Clone, Serialize, Deserialize)]
1014pub struct ExportResponse {
1015 pub vectors: Vec<ExportedVector>,
1017 #[serde(skip_serializing_if = "Option::is_none")]
1019 pub next_cursor: Option<String>,
1020 pub total_count: usize,
1022 pub returned_count: usize,
1024}
1025
1026#[derive(Debug, Clone, Serialize, Deserialize)]
1032pub struct BatchQueryItem {
1033 #[serde(skip_serializing_if = "Option::is_none")]
1035 pub id: Option<String>,
1036 pub vector: Vec<f32>,
1038 #[serde(default = "default_batch_top_k")]
1040 pub top_k: u32,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1043 pub filter: Option<serde_json::Value>,
1044 #[serde(default)]
1046 pub include_metadata: bool,
1047 #[serde(default)]
1049 pub consistency: ReadConsistency,
1050 #[serde(skip_serializing_if = "Option::is_none")]
1052 pub staleness_config: Option<StalenessConfig>,
1053}
1054
1055fn default_batch_top_k() -> u32 {
1056 10
1057}
1058
1059impl BatchQueryItem {
1060 pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
1062 Self {
1063 id: None,
1064 vector,
1065 top_k,
1066 filter: None,
1067 include_metadata: true,
1068 consistency: ReadConsistency::default(),
1069 staleness_config: None,
1070 }
1071 }
1072
1073 pub fn with_id(mut self, id: impl Into<String>) -> Self {
1075 self.id = Some(id.into());
1076 self
1077 }
1078
1079 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1081 self.filter = Some(filter);
1082 self
1083 }
1084
1085 pub fn include_metadata(mut self, include: bool) -> Self {
1087 self.include_metadata = include;
1088 self
1089 }
1090
1091 pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
1093 self.consistency = consistency;
1094 self
1095 }
1096
1097 pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
1099 self.consistency = ReadConsistency::BoundedStaleness;
1100 self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
1101 self
1102 }
1103}
1104
1105#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub struct BatchQueryRequest {
1108 pub queries: Vec<BatchQueryItem>,
1110}
1111
1112impl BatchQueryRequest {
1113 pub fn new(queries: Vec<BatchQueryItem>) -> Self {
1115 Self { queries }
1116 }
1117
1118 pub fn single(query: BatchQueryItem) -> Self {
1120 Self {
1121 queries: vec![query],
1122 }
1123 }
1124}
1125
1126#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct BatchQueryResult {
1129 #[serde(skip_serializing_if = "Option::is_none")]
1131 pub id: Option<String>,
1132 pub results: Vec<Match>,
1134 pub latency_ms: f64,
1136 #[serde(skip_serializing_if = "Option::is_none")]
1138 pub error: Option<String>,
1139}
1140
1141#[derive(Debug, Clone, Serialize, Deserialize)]
1143pub struct BatchQueryResponse {
1144 pub results: Vec<BatchQueryResult>,
1146 pub total_latency_ms: f64,
1148 pub query_count: usize,
1150}
1151
1152#[derive(Debug, Clone, Serialize, Deserialize)]
1158pub struct MultiVectorSearchRequest {
1159 pub positive_vectors: Vec<Vec<f32>>,
1161 #[serde(skip_serializing_if = "Option::is_none")]
1163 pub positive_weights: Option<Vec<f32>>,
1164 #[serde(skip_serializing_if = "Option::is_none")]
1166 pub negative_vectors: Option<Vec<Vec<f32>>>,
1167 #[serde(skip_serializing_if = "Option::is_none")]
1169 pub negative_weights: Option<Vec<f32>>,
1170 #[serde(default = "default_multi_vector_top_k")]
1172 pub top_k: u32,
1173 #[serde(default)]
1175 pub distance_metric: DistanceMetric,
1176 #[serde(skip_serializing_if = "Option::is_none")]
1178 pub score_threshold: Option<f32>,
1179 #[serde(default)]
1181 pub enable_mmr: bool,
1182 #[serde(default = "default_mmr_lambda")]
1184 pub mmr_lambda: f32,
1185 #[serde(default = "default_true")]
1187 pub include_metadata: bool,
1188 #[serde(default)]
1190 pub include_vectors: bool,
1191 #[serde(skip_serializing_if = "Option::is_none")]
1193 pub filter: Option<serde_json::Value>,
1194 #[serde(default)]
1196 pub consistency: ReadConsistency,
1197 #[serde(skip_serializing_if = "Option::is_none")]
1199 pub staleness_config: Option<StalenessConfig>,
1200}
1201
1202fn default_multi_vector_top_k() -> u32 {
1203 10
1204}
1205
1206fn default_mmr_lambda() -> f32 {
1207 0.5
1208}
1209
1210impl MultiVectorSearchRequest {
1211 pub fn new(positive_vectors: Vec<Vec<f32>>) -> Self {
1213 Self {
1214 positive_vectors,
1215 positive_weights: None,
1216 negative_vectors: None,
1217 negative_weights: None,
1218 top_k: 10,
1219 distance_metric: DistanceMetric::default(),
1220 score_threshold: None,
1221 enable_mmr: false,
1222 mmr_lambda: 0.5,
1223 include_metadata: true,
1224 include_vectors: false,
1225 filter: None,
1226 consistency: ReadConsistency::default(),
1227 staleness_config: None,
1228 }
1229 }
1230
1231 pub fn with_top_k(mut self, top_k: u32) -> Self {
1233 self.top_k = top_k;
1234 self
1235 }
1236
1237 pub fn with_positive_weights(mut self, weights: Vec<f32>) -> Self {
1239 self.positive_weights = Some(weights);
1240 self
1241 }
1242
1243 pub fn with_negative_vectors(mut self, vectors: Vec<Vec<f32>>) -> Self {
1245 self.negative_vectors = Some(vectors);
1246 self
1247 }
1248
1249 pub fn with_negative_weights(mut self, weights: Vec<f32>) -> Self {
1251 self.negative_weights = Some(weights);
1252 self
1253 }
1254
1255 pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1257 self.distance_metric = metric;
1258 self
1259 }
1260
1261 pub fn with_score_threshold(mut self, threshold: f32) -> Self {
1263 self.score_threshold = Some(threshold);
1264 self
1265 }
1266
1267 pub fn with_mmr(mut self, lambda: f32) -> Self {
1269 self.enable_mmr = true;
1270 self.mmr_lambda = lambda.clamp(0.0, 1.0);
1271 self
1272 }
1273
1274 pub fn include_metadata(mut self, include: bool) -> Self {
1276 self.include_metadata = include;
1277 self
1278 }
1279
1280 pub fn include_vectors(mut self, include: bool) -> Self {
1282 self.include_vectors = include;
1283 self
1284 }
1285
1286 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1288 self.filter = Some(filter);
1289 self
1290 }
1291
1292 pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
1294 self.consistency = consistency;
1295 self
1296 }
1297}
1298
1299#[derive(Debug, Clone, Serialize, Deserialize)]
1301pub struct MultiVectorSearchResult {
1302 pub id: String,
1304 pub score: f32,
1306 #[serde(skip_serializing_if = "Option::is_none")]
1308 pub mmr_score: Option<f32>,
1309 #[serde(skip_serializing_if = "Option::is_none")]
1311 pub original_rank: Option<usize>,
1312 #[serde(skip_serializing_if = "Option::is_none")]
1314 pub metadata: Option<HashMap<String, serde_json::Value>>,
1315 #[serde(skip_serializing_if = "Option::is_none")]
1317 pub vector: Option<Vec<f32>>,
1318}
1319
1320#[derive(Debug, Clone, Serialize, Deserialize)]
1322pub struct MultiVectorSearchResponse {
1323 pub results: Vec<MultiVectorSearchResult>,
1325 #[serde(skip_serializing_if = "Option::is_none")]
1327 pub computed_query_vector: Option<Vec<f32>>,
1328}
1329
1330#[derive(Debug, Clone, Serialize, Deserialize)]
1336#[serde(untagged)]
1337pub enum AggregateFunction {
1338 Count,
1340 Sum { field: String },
1342 Avg { field: String },
1344 Min { field: String },
1346 Max { field: String },
1348}
1349
1350#[derive(Debug, Clone, Serialize, Deserialize)]
1352pub struct AggregationRequest {
1353 pub aggregate_by: HashMap<String, serde_json::Value>,
1356 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1359 pub group_by: Vec<String>,
1360 #[serde(skip_serializing_if = "Option::is_none")]
1362 pub filter: Option<serde_json::Value>,
1363 #[serde(default = "default_agg_limit")]
1365 pub limit: usize,
1366}
1367
1368fn default_agg_limit() -> usize {
1369 100
1370}
1371
1372impl AggregationRequest {
1373 pub fn new() -> Self {
1375 Self {
1376 aggregate_by: HashMap::new(),
1377 group_by: Vec::new(),
1378 filter: None,
1379 limit: 100,
1380 }
1381 }
1382
1383 pub fn with_count(mut self, name: impl Into<String>) -> Self {
1385 self.aggregate_by
1386 .insert(name.into(), serde_json::json!(["Count"]));
1387 self
1388 }
1389
1390 pub fn with_sum(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1392 self.aggregate_by
1393 .insert(name.into(), serde_json::json!(["Sum", field.into()]));
1394 self
1395 }
1396
1397 pub fn with_avg(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1399 self.aggregate_by
1400 .insert(name.into(), serde_json::json!(["Avg", field.into()]));
1401 self
1402 }
1403
1404 pub fn with_min(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1406 self.aggregate_by
1407 .insert(name.into(), serde_json::json!(["Min", field.into()]));
1408 self
1409 }
1410
1411 pub fn with_max(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1413 self.aggregate_by
1414 .insert(name.into(), serde_json::json!(["Max", field.into()]));
1415 self
1416 }
1417
1418 pub fn group_by(mut self, fields: Vec<String>) -> Self {
1420 self.group_by = fields;
1421 self
1422 }
1423
1424 pub fn with_group_by(mut self, field: impl Into<String>) -> Self {
1426 self.group_by.push(field.into());
1427 self
1428 }
1429
1430 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1432 self.filter = Some(filter);
1433 self
1434 }
1435
1436 pub fn with_limit(mut self, limit: usize) -> Self {
1438 self.limit = limit;
1439 self
1440 }
1441}
1442
1443impl Default for AggregationRequest {
1444 fn default() -> Self {
1445 Self::new()
1446 }
1447}
1448
1449#[derive(Debug, Clone, Serialize, Deserialize)]
1451pub struct AggregationResponse {
1452 #[serde(skip_serializing_if = "Option::is_none")]
1454 pub aggregations: Option<HashMap<String, serde_json::Value>>,
1455 #[serde(skip_serializing_if = "Option::is_none")]
1457 pub aggregation_groups: Option<Vec<AggregationGroup>>,
1458}
1459
1460#[derive(Debug, Clone, Serialize, Deserialize)]
1462pub struct AggregationGroup {
1463 #[serde(flatten)]
1465 pub group_key: HashMap<String, serde_json::Value>,
1466 #[serde(flatten)]
1468 pub aggregations: HashMap<String, serde_json::Value>,
1469}
1470
1471#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1477pub enum VectorSearchMethod {
1478 #[default]
1480 ANN,
1481 #[serde(rename = "kNN")]
1483 KNN,
1484}
1485
1486#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1488#[serde(rename_all = "lowercase")]
1489pub enum SortDirection {
1490 Asc,
1492 #[default]
1494 Desc,
1495}
1496
1497#[derive(Debug, Clone, Serialize, Deserialize)]
1500#[serde(untagged)]
1501pub enum RankBy {
1502 VectorSearch {
1504 field: String,
1505 method: VectorSearchMethod,
1506 query_vector: Vec<f32>,
1507 },
1508 FullTextSearch {
1510 field: String,
1511 method: String, query: String,
1513 },
1514 AttributeOrder {
1516 field: String,
1517 direction: SortDirection,
1518 },
1519 Sum(Vec<RankBy>),
1521 Max(Vec<RankBy>),
1523 Product { weight: f32, ranking: Box<RankBy> },
1525}
1526
1527impl RankBy {
1528 pub fn vector_ann(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1530 RankBy::VectorSearch {
1531 field: field.into(),
1532 method: VectorSearchMethod::ANN,
1533 query_vector,
1534 }
1535 }
1536
1537 pub fn ann(query_vector: Vec<f32>) -> Self {
1539 Self::vector_ann("vector", query_vector)
1540 }
1541
1542 pub fn vector_knn(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1544 RankBy::VectorSearch {
1545 field: field.into(),
1546 method: VectorSearchMethod::KNN,
1547 query_vector,
1548 }
1549 }
1550
1551 pub fn knn(query_vector: Vec<f32>) -> Self {
1553 Self::vector_knn("vector", query_vector)
1554 }
1555
1556 pub fn bm25(field: impl Into<String>, query: impl Into<String>) -> Self {
1558 RankBy::FullTextSearch {
1559 field: field.into(),
1560 method: "BM25".to_string(),
1561 query: query.into(),
1562 }
1563 }
1564
1565 pub fn asc(field: impl Into<String>) -> Self {
1567 RankBy::AttributeOrder {
1568 field: field.into(),
1569 direction: SortDirection::Asc,
1570 }
1571 }
1572
1573 pub fn desc(field: impl Into<String>) -> Self {
1575 RankBy::AttributeOrder {
1576 field: field.into(),
1577 direction: SortDirection::Desc,
1578 }
1579 }
1580
1581 pub fn sum(rankings: Vec<RankBy>) -> Self {
1583 RankBy::Sum(rankings)
1584 }
1585
1586 pub fn max(rankings: Vec<RankBy>) -> Self {
1588 RankBy::Max(rankings)
1589 }
1590
1591 pub fn product(weight: f32, ranking: RankBy) -> Self {
1593 RankBy::Product {
1594 weight,
1595 ranking: Box::new(ranking),
1596 }
1597 }
1598}
1599
1600#[derive(Debug, Clone, Serialize, Deserialize)]
1618pub struct UnifiedQueryRequest {
1619 pub rank_by: serde_json::Value,
1621 #[serde(default = "default_unified_top_k")]
1623 pub top_k: usize,
1624 #[serde(skip_serializing_if = "Option::is_none")]
1626 pub filter: Option<serde_json::Value>,
1627 #[serde(default = "default_true")]
1629 pub include_metadata: bool,
1630 #[serde(default)]
1632 pub include_vectors: bool,
1633 #[serde(default)]
1635 pub distance_metric: DistanceMetric,
1636}
1637
1638fn default_unified_top_k() -> usize {
1639 10
1640}
1641
1642impl UnifiedQueryRequest {
1643 pub fn vector_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1645 Self {
1646 rank_by: serde_json::json!(["ANN", query_vector]),
1647 top_k,
1648 filter: None,
1649 include_metadata: true,
1650 include_vectors: false,
1651 distance_metric: DistanceMetric::default(),
1652 }
1653 }
1654
1655 pub fn vector_knn_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1657 Self {
1658 rank_by: serde_json::json!(["kNN", query_vector]),
1659 top_k,
1660 filter: None,
1661 include_metadata: true,
1662 include_vectors: false,
1663 distance_metric: DistanceMetric::default(),
1664 }
1665 }
1666
1667 pub fn fulltext_search(
1669 field: impl Into<String>,
1670 query: impl Into<String>,
1671 top_k: usize,
1672 ) -> Self {
1673 Self {
1674 rank_by: serde_json::json!([field.into(), "BM25", query.into()]),
1675 top_k,
1676 filter: None,
1677 include_metadata: true,
1678 include_vectors: false,
1679 distance_metric: DistanceMetric::default(),
1680 }
1681 }
1682
1683 pub fn attribute_order(
1685 field: impl Into<String>,
1686 direction: SortDirection,
1687 top_k: usize,
1688 ) -> Self {
1689 let dir = match direction {
1690 SortDirection::Asc => "asc",
1691 SortDirection::Desc => "desc",
1692 };
1693 Self {
1694 rank_by: serde_json::json!([field.into(), dir]),
1695 top_k,
1696 filter: None,
1697 include_metadata: true,
1698 include_vectors: false,
1699 distance_metric: DistanceMetric::default(),
1700 }
1701 }
1702
1703 pub fn with_rank_by(rank_by: serde_json::Value, top_k: usize) -> Self {
1705 Self {
1706 rank_by,
1707 top_k,
1708 filter: None,
1709 include_metadata: true,
1710 include_vectors: false,
1711 distance_metric: DistanceMetric::default(),
1712 }
1713 }
1714
1715 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1717 self.filter = Some(filter);
1718 self
1719 }
1720
1721 pub fn include_metadata(mut self, include: bool) -> Self {
1723 self.include_metadata = include;
1724 self
1725 }
1726
1727 pub fn include_vectors(mut self, include: bool) -> Self {
1729 self.include_vectors = include;
1730 self
1731 }
1732
1733 pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1735 self.distance_metric = metric;
1736 self
1737 }
1738
1739 pub fn with_top_k(mut self, top_k: usize) -> Self {
1741 self.top_k = top_k;
1742 self
1743 }
1744}
1745
1746#[derive(Debug, Clone, Serialize, Deserialize)]
1748pub struct UnifiedSearchResult {
1749 pub id: String,
1751 #[serde(rename = "$dist", skip_serializing_if = "Option::is_none")]
1754 pub dist: Option<f32>,
1755 #[serde(skip_serializing_if = "Option::is_none")]
1757 pub metadata: Option<serde_json::Value>,
1758 #[serde(skip_serializing_if = "Option::is_none")]
1760 pub vector: Option<Vec<f32>>,
1761}
1762
1763#[derive(Debug, Clone, Serialize, Deserialize)]
1765pub struct UnifiedQueryResponse {
1766 pub results: Vec<UnifiedSearchResult>,
1768 #[serde(skip_serializing_if = "Option::is_none")]
1770 pub next_cursor: Option<String>,
1771}
1772
1773fn default_explain_top_k() -> usize {
1778 10
1779}
1780
1781#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1783#[serde(rename_all = "snake_case")]
1784#[derive(Default)]
1785pub enum ExplainQueryType {
1786 #[default]
1788 VectorSearch,
1789 FullTextSearch,
1791 HybridSearch,
1793 MultiVector,
1795 BatchQuery,
1797}
1798
1799#[derive(Debug, Clone, Serialize, Deserialize)]
1801pub struct QueryExplainRequest {
1802 #[serde(default)]
1804 pub query_type: ExplainQueryType,
1805 #[serde(skip_serializing_if = "Option::is_none")]
1807 pub vector: Option<Vec<f32>>,
1808 #[serde(default = "default_explain_top_k")]
1810 pub top_k: usize,
1811 #[serde(skip_serializing_if = "Option::is_none")]
1813 pub filter: Option<serde_json::Value>,
1814 #[serde(skip_serializing_if = "Option::is_none")]
1816 pub text_query: Option<String>,
1817 #[serde(default = "default_distance_metric")]
1819 pub distance_metric: String,
1820 #[serde(default)]
1822 pub execute: bool,
1823 #[serde(default)]
1825 pub verbose: bool,
1826}
1827
1828fn default_distance_metric() -> String {
1829 "cosine".to_string()
1830}
1831
1832impl QueryExplainRequest {
1833 pub fn vector_search(vector: Vec<f32>, top_k: usize) -> Self {
1835 Self {
1836 query_type: ExplainQueryType::VectorSearch,
1837 vector: Some(vector),
1838 top_k,
1839 filter: None,
1840 text_query: None,
1841 distance_metric: "cosine".to_string(),
1842 execute: false,
1843 verbose: false,
1844 }
1845 }
1846
1847 pub fn fulltext_search(text_query: impl Into<String>, top_k: usize) -> Self {
1849 Self {
1850 query_type: ExplainQueryType::FullTextSearch,
1851 vector: None,
1852 top_k,
1853 filter: None,
1854 text_query: Some(text_query.into()),
1855 distance_metric: "bm25".to_string(),
1856 execute: false,
1857 verbose: false,
1858 }
1859 }
1860
1861 pub fn hybrid_search(vector: Vec<f32>, text_query: impl Into<String>, top_k: usize) -> Self {
1863 Self {
1864 query_type: ExplainQueryType::HybridSearch,
1865 vector: Some(vector),
1866 top_k,
1867 filter: None,
1868 text_query: Some(text_query.into()),
1869 distance_metric: "hybrid".to_string(),
1870 execute: false,
1871 verbose: false,
1872 }
1873 }
1874
1875 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1877 self.filter = Some(filter);
1878 self
1879 }
1880
1881 pub fn with_distance_metric(mut self, metric: impl Into<String>) -> Self {
1883 self.distance_metric = metric.into();
1884 self
1885 }
1886
1887 pub fn with_execution(mut self) -> Self {
1889 self.execute = true;
1890 self
1891 }
1892
1893 pub fn with_verbose(mut self) -> Self {
1895 self.verbose = true;
1896 self
1897 }
1898}
1899
1900#[derive(Debug, Clone, Serialize, Deserialize)]
1902pub struct ExecutionStage {
1903 pub name: String,
1905 pub description: String,
1907 pub order: u32,
1909 pub estimated_input: u64,
1911 pub estimated_output: u64,
1913 pub estimated_cost: f64,
1915 #[serde(default)]
1917 pub details: HashMap<String, serde_json::Value>,
1918}
1919
1920#[derive(Debug, Clone, Serialize, Deserialize)]
1922pub struct CostEstimate {
1923 pub total_cost: f64,
1925 pub estimated_time_ms: u64,
1927 pub estimated_memory_bytes: u64,
1929 pub estimated_io_ops: u64,
1931 #[serde(default)]
1933 pub cost_breakdown: HashMap<String, f64>,
1934 pub confidence: f64,
1936}
1937
1938#[derive(Debug, Clone, Serialize, Deserialize)]
1940pub struct ActualStats {
1941 pub execution_time_ms: u64,
1943 pub results_returned: usize,
1945 pub vectors_scanned: u64,
1947 pub vectors_after_filter: u64,
1949 pub index_lookups: u64,
1951 pub cache_hits: u64,
1953 pub cache_misses: u64,
1955 pub memory_used_bytes: u64,
1957}
1958
1959#[derive(Debug, Clone, Serialize, Deserialize)]
1961pub struct Recommendation {
1962 pub recommendation_type: String,
1964 pub priority: String,
1966 pub description: String,
1968 pub expected_improvement: String,
1970 pub implementation: String,
1972}
1973
1974#[derive(Debug, Clone, Serialize, Deserialize)]
1976pub struct IndexSelection {
1977 pub index_type: String,
1979 pub selection_reason: String,
1981 #[serde(default)]
1983 pub alternatives_considered: Vec<IndexAlternative>,
1984 #[serde(default)]
1986 pub index_config: HashMap<String, serde_json::Value>,
1987 pub index_stats: IndexStatistics,
1989}
1990
1991#[derive(Debug, Clone, Serialize, Deserialize)]
1993pub struct IndexAlternative {
1994 pub index_type: String,
1996 pub rejection_reason: String,
1998 pub estimated_cost: f64,
2000}
2001
2002#[derive(Debug, Clone, Serialize, Deserialize)]
2004pub struct IndexStatistics {
2005 pub vector_count: u64,
2007 pub dimension: usize,
2009 pub memory_bytes: u64,
2011 #[serde(skip_serializing_if = "Option::is_none")]
2013 pub build_time_ms: Option<u64>,
2014 #[serde(skip_serializing_if = "Option::is_none")]
2016 pub last_updated: Option<u64>,
2017}
2018
2019#[derive(Debug, Clone, Serialize, Deserialize)]
2021pub struct QueryParams {
2022 pub top_k: usize,
2024 pub has_filter: bool,
2026 pub filter_complexity: String,
2028 #[serde(skip_serializing_if = "Option::is_none")]
2030 pub vector_dimension: Option<usize>,
2031 pub distance_metric: String,
2033 #[serde(skip_serializing_if = "Option::is_none")]
2035 pub text_query_length: Option<usize>,
2036}
2037
2038#[derive(Debug, Clone, Serialize, Deserialize)]
2040pub struct QueryExplainResponse {
2041 pub query_type: ExplainQueryType,
2043 pub namespace: String,
2045 pub index_selection: IndexSelection,
2047 pub stages: Vec<ExecutionStage>,
2049 pub cost_estimate: CostEstimate,
2051 #[serde(skip_serializing_if = "Option::is_none")]
2053 pub actual_stats: Option<ActualStats>,
2054 #[serde(default)]
2056 pub recommendations: Vec<Recommendation>,
2057 pub summary: String,
2059 pub query_params: QueryParams,
2061}
2062
2063#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
2069#[serde(rename_all = "kebab-case")]
2070pub enum EmbeddingModel {
2071 #[default]
2073 BgeLarge,
2074 Minilm,
2076 BgeSmall,
2078 E5Small,
2080 #[serde(rename = "modernbert-embed-base")]
2082 ModernBertEmbedBase,
2083 #[serde(rename = "gte-modernbert-base")]
2085 GteModernBertBase,
2086}
2087
2088#[derive(Debug, Clone, Serialize, Deserialize)]
2090pub struct TextDocument {
2091 pub id: String,
2093 pub text: String,
2095 #[serde(skip_serializing_if = "Option::is_none")]
2097 pub metadata: Option<HashMap<String, serde_json::Value>>,
2098 #[serde(skip_serializing_if = "Option::is_none")]
2100 pub ttl_seconds: Option<u64>,
2101}
2102
2103impl TextDocument {
2104 pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
2106 Self {
2107 id: id.into(),
2108 text: text.into(),
2109 metadata: None,
2110 ttl_seconds: None,
2111 }
2112 }
2113
2114 pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
2116 self.metadata = Some(metadata);
2117 self
2118 }
2119
2120 pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
2122 self.ttl_seconds = Some(ttl_seconds);
2123 self
2124 }
2125}
2126
2127#[derive(Debug, Clone, Serialize, Deserialize)]
2129pub struct UpsertTextRequest {
2130 pub documents: Vec<TextDocument>,
2132 #[serde(skip_serializing_if = "Option::is_none")]
2134 pub model: Option<EmbeddingModel>,
2135}
2136
2137impl UpsertTextRequest {
2138 pub fn new(documents: Vec<TextDocument>) -> Self {
2140 Self {
2141 documents,
2142 model: None,
2143 }
2144 }
2145
2146 pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2148 self.model = Some(model);
2149 self
2150 }
2151}
2152
2153#[derive(Debug, Clone, Serialize, Deserialize)]
2155pub struct TextUpsertResponse {
2156 pub upserted_count: u64,
2158 pub tokens_processed: u64,
2160 pub model: EmbeddingModel,
2162 pub embedding_time_ms: u64,
2164}
2165
2166#[derive(Debug, Clone, Serialize, Deserialize)]
2168pub struct TextSearchResult {
2169 pub id: String,
2171 pub score: f32,
2173 #[serde(skip_serializing_if = "Option::is_none")]
2175 pub text: Option<String>,
2176 #[serde(skip_serializing_if = "Option::is_none")]
2178 pub metadata: Option<HashMap<String, serde_json::Value>>,
2179 #[serde(skip_serializing_if = "Option::is_none")]
2181 pub vector: Option<Vec<f32>>,
2182}
2183
2184#[derive(Debug, Clone, Serialize, Deserialize)]
2186pub struct QueryTextRequest {
2187 pub text: String,
2189 pub top_k: u32,
2191 #[serde(skip_serializing_if = "Option::is_none")]
2193 pub filter: Option<serde_json::Value>,
2194 pub include_text: bool,
2196 pub include_vectors: bool,
2198 #[serde(skip_serializing_if = "Option::is_none")]
2200 pub model: Option<EmbeddingModel>,
2201}
2202
2203impl QueryTextRequest {
2204 pub fn new(text: impl Into<String>, top_k: u32) -> Self {
2206 Self {
2207 text: text.into(),
2208 top_k,
2209 filter: None,
2210 include_text: true,
2211 include_vectors: false,
2212 model: None,
2213 }
2214 }
2215
2216 pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
2218 self.filter = Some(filter);
2219 self
2220 }
2221
2222 pub fn include_text(mut self, include: bool) -> Self {
2224 self.include_text = include;
2225 self
2226 }
2227
2228 pub fn include_vectors(mut self, include: bool) -> Self {
2230 self.include_vectors = include;
2231 self
2232 }
2233
2234 pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2236 self.model = Some(model);
2237 self
2238 }
2239}
2240
2241#[derive(Debug, Clone, Serialize, Deserialize)]
2243pub struct TextQueryResponse {
2244 pub results: Vec<TextSearchResult>,
2246 pub model: EmbeddingModel,
2248 pub embedding_time_ms: u64,
2250 pub search_time_ms: u64,
2252}
2253
2254#[derive(Debug, Clone, Serialize, Deserialize)]
2256pub struct BatchQueryTextRequest {
2257 pub queries: Vec<String>,
2259 pub top_k: u32,
2261 #[serde(skip_serializing_if = "Option::is_none")]
2263 pub filter: Option<serde_json::Value>,
2264 pub include_vectors: bool,
2266 #[serde(skip_serializing_if = "Option::is_none")]
2268 pub model: Option<EmbeddingModel>,
2269}
2270
2271impl BatchQueryTextRequest {
2272 pub fn new(queries: Vec<String>, top_k: u32) -> Self {
2274 Self {
2275 queries,
2276 top_k,
2277 filter: None,
2278 include_vectors: false,
2279 model: None,
2280 }
2281 }
2282}
2283
2284#[derive(Debug, Clone, Serialize, Deserialize)]
2286pub struct BatchQueryTextResponse {
2287 pub results: Vec<Vec<TextSearchResult>>,
2289 pub model: EmbeddingModel,
2291 pub embedding_time_ms: u64,
2293 pub search_time_ms: u64,
2295}
2296
2297#[derive(Debug, Clone, Serialize, Deserialize)]
2303pub struct FetchRequest {
2304 pub ids: Vec<String>,
2306 pub include_values: bool,
2308 pub include_metadata: bool,
2310}
2311
2312impl FetchRequest {
2313 pub fn new(ids: Vec<String>) -> Self {
2315 Self {
2316 ids,
2317 include_values: true,
2318 include_metadata: true,
2319 }
2320 }
2321}
2322
2323#[derive(Debug, Clone, Serialize, Deserialize)]
2325pub struct FetchResponse {
2326 pub vectors: Vec<Vector>,
2328}
2329
2330#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2336pub struct CreateNamespaceRequest {
2337 #[serde(rename = "dimension", skip_serializing_if = "Option::is_none")]
2339 pub dimensions: Option<u32>,
2340 #[serde(skip_serializing_if = "Option::is_none")]
2342 pub index_type: Option<String>,
2343 #[serde(skip_serializing_if = "Option::is_none")]
2345 pub metadata: Option<HashMap<String, serde_json::Value>>,
2346}
2347
2348impl CreateNamespaceRequest {
2349 pub fn new() -> Self {
2351 Self::default()
2352 }
2353
2354 pub fn with_dimensions(mut self, dimensions: u32) -> Self {
2356 self.dimensions = Some(dimensions);
2357 self
2358 }
2359
2360 pub fn with_index_type(mut self, index_type: impl Into<String>) -> Self {
2362 self.index_type = Some(index_type.into());
2363 self
2364 }
2365}
2366
2367#[derive(Debug, Clone, Serialize, Deserialize)]
2372pub struct ConfigureNamespaceRequest {
2373 pub dimension: usize,
2375 #[serde(skip_serializing_if = "Option::is_none")]
2377 pub distance: Option<DistanceMetric>,
2378}
2379
2380impl ConfigureNamespaceRequest {
2381 pub fn new(dimension: usize) -> Self {
2383 Self {
2384 dimension,
2385 distance: None,
2386 }
2387 }
2388
2389 pub fn with_distance(mut self, distance: DistanceMetric) -> Self {
2391 self.distance = Some(distance);
2392 self
2393 }
2394}
2395
2396#[derive(Debug, Clone, Serialize, Deserialize)]
2398pub struct ConfigureNamespaceResponse {
2399 pub namespace: String,
2401 pub dimension: usize,
2403 pub distance: DistanceMetric,
2405 pub created: bool,
2407}
2408
2409#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
2415#[serde(rename_all = "snake_case")]
2416pub enum EdgeType {
2417 RelatedTo,
2419 SharesEntity,
2421 Precedes,
2423 #[default]
2425 LinkedBy,
2426}
2427
2428#[derive(Debug, Clone, Serialize, Deserialize)]
2430pub struct GraphEdge {
2431 pub id: String,
2433 pub source_id: String,
2435 pub target_id: String,
2437 pub edge_type: EdgeType,
2439 pub weight: f64,
2441 pub created_at: i64,
2443}
2444
2445#[derive(Debug, Clone, Serialize, Deserialize)]
2447pub struct GraphNode {
2448 pub memory_id: String,
2450 pub content_preview: String,
2452 pub importance: f64,
2454 pub depth: u32,
2456}
2457
2458#[derive(Debug, Clone, Serialize, Deserialize)]
2460pub struct MemoryGraph {
2461 pub root_id: String,
2463 pub depth: u32,
2465 pub nodes: Vec<GraphNode>,
2467 pub edges: Vec<GraphEdge>,
2469}
2470
2471#[derive(Debug, Clone, Serialize, Deserialize)]
2473pub struct GraphPath {
2474 pub source_id: String,
2476 pub target_id: String,
2478 pub path: Vec<String>,
2480 pub hops: i32,
2482 pub edges: Vec<GraphEdge>,
2484}
2485
2486#[derive(Debug, Clone, Serialize, Deserialize)]
2488pub struct GraphLinkRequest {
2489 pub target_id: String,
2491 pub edge_type: EdgeType,
2493}
2494
2495#[derive(Debug, Clone, Serialize, Deserialize)]
2497pub struct GraphLinkResponse {
2498 pub edge: GraphEdge,
2500}
2501
2502#[derive(Debug, Clone, Serialize, Deserialize)]
2504pub struct GraphExport {
2505 pub agent_id: String,
2507 pub format: String,
2509 pub data: String,
2511 pub node_count: u64,
2513 pub edge_count: u64,
2515}
2516
2517#[derive(Debug, Clone, Default)]
2519pub struct GraphOptions {
2520 pub depth: Option<u32>,
2522 pub types: Option<Vec<EdgeType>>,
2524}
2525
2526impl GraphOptions {
2527 pub fn new() -> Self {
2529 Self::default()
2530 }
2531
2532 pub fn depth(mut self, depth: u32) -> Self {
2534 self.depth = Some(depth);
2535 self
2536 }
2537
2538 pub fn types(mut self, types: Vec<EdgeType>) -> Self {
2540 self.types = Some(types);
2541 self
2542 }
2543}
2544
2545#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2551pub struct NamespaceNerConfig {
2552 pub extract_entities: bool,
2553 #[serde(skip_serializing_if = "Option::is_none")]
2554 pub entity_types: Option<Vec<String>>,
2555}
2556
2557#[derive(Debug, Clone, Serialize, Deserialize)]
2559pub struct ExtractedEntity {
2560 pub entity_type: String,
2561 pub value: String,
2562 pub score: f64,
2563}
2564
2565#[derive(Debug, Clone, Serialize, Deserialize)]
2567pub struct EntityExtractionResponse {
2568 pub entities: Vec<ExtractedEntity>,
2569}
2570
2571#[derive(Debug, Clone, Serialize, Deserialize)]
2573pub struct MemoryEntitiesResponse {
2574 pub memory_id: String,
2575 pub entities: Vec<ExtractedEntity>,
2576}
2577
2578#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2590#[serde(rename_all = "lowercase")]
2591pub enum FeedbackSignal {
2592 Upvote,
2593 Downvote,
2594 Flag,
2595 Positive,
2596 Negative,
2597}
2598
2599#[derive(Debug, Clone, Serialize, Deserialize)]
2601pub struct FeedbackHistoryEntry {
2602 pub signal: FeedbackSignal,
2603 pub timestamp: u64,
2605 pub old_importance: f32,
2606 pub new_importance: f32,
2607}
2608
2609#[derive(Debug, Clone, Serialize, Deserialize)]
2611pub struct MemoryFeedbackBody {
2612 pub agent_id: String,
2613 pub signal: FeedbackSignal,
2614}
2615
2616#[derive(Debug, Clone, Serialize, Deserialize)]
2618pub struct MemoryImportancePatch {
2619 pub agent_id: String,
2620 pub importance: f32,
2621}
2622
2623#[derive(Debug, Clone, Serialize, Deserialize)]
2625pub struct FeedbackResponse {
2626 pub memory_id: String,
2627 pub new_importance: f32,
2629 pub signal: FeedbackSignal,
2630}
2631
2632#[derive(Debug, Clone, Serialize, Deserialize)]
2634pub struct FeedbackHistoryResponse {
2635 pub memory_id: String,
2636 pub entries: Vec<FeedbackHistoryEntry>,
2638}
2639
2640#[derive(Debug, Clone, Serialize, Deserialize)]
2642pub struct AgentFeedbackSummary {
2643 pub agent_id: String,
2644 pub upvotes: u64,
2645 pub downvotes: u64,
2646 pub flags: u64,
2647 pub total_feedback: u64,
2648 pub health_score: f32,
2650}
2651
2652#[derive(Debug, Clone, Serialize, Deserialize)]
2654pub struct FeedbackHealthResponse {
2655 pub agent_id: String,
2656 pub health_score: f32,
2658 pub memory_count: usize,
2659 pub avg_importance: f32,
2660}
2661
2662#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2668#[serde(rename_all = "snake_case")]
2669pub enum TifClassification {
2670 SurfaceContradiction,
2672 AskClarification,
2674 ConfidentReuse,
2676 VerifyBeforeUse,
2678}
2679
2680impl TifClassification {
2681 pub fn as_str(&self) -> &'static str {
2683 match self {
2684 Self::SurfaceContradiction => "surface_contradiction",
2685 Self::AskClarification => "ask_clarification",
2686 Self::ConfidentReuse => "confident_reuse",
2687 Self::VerifyBeforeUse => "verify_before_use",
2688 }
2689 }
2690}
2691
2692#[derive(Debug, Clone, Serialize, Deserialize)]
2697pub struct TifScore {
2698 pub truth: f64,
2700 pub indeterminacy: f64,
2702 pub falsity: f64,
2704 pub feedback_count: u64,
2706 pub classification: TifClassification,
2708}
2709
2710fn classify_tif(truth: f64, indeterminacy: f64, falsity: f64) -> TifClassification {
2711 if falsity >= 0.5 {
2712 TifClassification::SurfaceContradiction
2713 } else if indeterminacy >= 0.5 {
2714 TifClassification::AskClarification
2715 } else if truth >= 0.7 {
2716 TifClassification::ConfidentReuse
2717 } else {
2718 TifClassification::VerifyBeforeUse
2719 }
2720}
2721
2722impl TifScore {
2723 pub fn from_feedback_history(history: &FeedbackHistoryResponse) -> Self {
2732 let mut upvotes: u64 = 0;
2733 let mut downvotes: u64 = 0;
2734 let mut flags: u64 = 0;
2735 for entry in &history.entries {
2736 match entry.signal {
2737 FeedbackSignal::Upvote | FeedbackSignal::Positive => upvotes += 1,
2738 FeedbackSignal::Downvote | FeedbackSignal::Negative => downvotes += 1,
2739 FeedbackSignal::Flag => flags += 1,
2740 }
2741 }
2742 let total = upvotes + downvotes + flags;
2743 if total == 0 {
2744 return Self {
2745 truth: 0.0,
2746 indeterminacy: 1.0,
2747 falsity: 0.0,
2748 feedback_count: 0,
2749 classification: TifClassification::AskClarification,
2750 };
2751 }
2752 let total_f = total as f64;
2753 let base_indeterminacy = if total < 3 {
2754 (3 - total) as f64 * 0.25
2755 } else {
2756 0.0
2757 };
2758 let mut truth = upvotes as f64 / total_f;
2759 let mut falsity = downvotes as f64 / total_f;
2760 let mut indeterminacy = flags as f64 / total_f + base_indeterminacy;
2761 let sum = truth + falsity + indeterminacy;
2762 truth /= sum;
2763 falsity /= sum;
2764 indeterminacy /= sum;
2765 Self {
2766 truth,
2767 indeterminacy,
2768 falsity,
2769 feedback_count: total,
2770 classification: classify_tif(truth, indeterminacy, falsity),
2771 }
2772 }
2773
2774 pub fn from_metadata(data: &serde_json::Value) -> Option<Self> {
2778 let truth = data["truth"].as_f64()?;
2779 let indeterminacy = data["indeterminacy"].as_f64()?;
2780 let falsity = data["falsity"].as_f64()?;
2781 let feedback_count = data["feedback_count"].as_u64().unwrap_or(0);
2782 Some(Self {
2783 truth,
2784 indeterminacy,
2785 falsity,
2786 feedback_count,
2787 classification: classify_tif(truth, indeterminacy, falsity),
2788 })
2789 }
2790}
2791
2792#[cfg(test)]
2793mod tif_tests {
2794 use super::*;
2795
2796 fn make_history(signals: &[&str]) -> FeedbackHistoryResponse {
2797 FeedbackHistoryResponse {
2798 memory_id: "test-mem".to_string(),
2799 entries: signals
2800 .iter()
2801 .map(|s| {
2802 let signal = match *s {
2803 "upvote" => FeedbackSignal::Upvote,
2804 "downvote" => FeedbackSignal::Downvote,
2805 "flag" => FeedbackSignal::Flag,
2806 "positive" => FeedbackSignal::Positive,
2807 "negative" => FeedbackSignal::Negative,
2808 other => panic!("unknown signal: {other}"),
2809 };
2810 FeedbackHistoryEntry {
2811 signal,
2812 timestamp: 0,
2813 old_importance: 0.5,
2814 new_importance: 0.5,
2815 }
2816 })
2817 .collect(),
2818 }
2819 }
2820
2821 #[test]
2822 fn no_feedback_max_indeterminacy() {
2823 let score = TifScore::from_feedback_history(&make_history(&[]));
2824 assert_eq!(score.truth, 0.0);
2825 assert_eq!(score.indeterminacy, 1.0);
2826 assert_eq!(score.falsity, 0.0);
2827 assert_eq!(score.feedback_count, 0);
2828 assert_eq!(score.classification, TifClassification::AskClarification);
2829 }
2830
2831 #[test]
2832 fn all_upvotes() {
2833 let score = TifScore::from_feedback_history(&make_history(&["upvote", "upvote", "upvote"]));
2834 assert!((score.truth - 1.0).abs() < 1e-9);
2835 assert_eq!(score.feedback_count, 3);
2836 assert_eq!(score.classification, TifClassification::ConfidentReuse);
2837 }
2838
2839 #[test]
2840 fn all_downvotes() {
2841 let score = TifScore::from_feedback_history(&make_history(&["downvote", "downvote"]));
2842 assert!((score.falsity - 0.8).abs() < 1e-9);
2843 assert!((score.indeterminacy - 0.2).abs() < 1e-9);
2844 assert_eq!(
2845 score.classification,
2846 TifClassification::SurfaceContradiction
2847 );
2848 }
2849
2850 #[test]
2851 fn all_flags() {
2852 let score = TifScore::from_feedback_history(&make_history(&["flag", "flag"]));
2853 assert!((score.indeterminacy - 1.0).abs() < 1e-9);
2854 assert_eq!(score.classification, TifClassification::AskClarification);
2855 }
2856
2857 #[test]
2858 fn mixed_signals() {
2859 let score = TifScore::from_feedback_history(&make_history(&[
2860 "upvote", "upvote", "upvote", "upvote", "downvote", "downvote", "flag", "flag", "flag",
2861 "flag",
2862 ]));
2863 assert!((score.truth - 0.4).abs() < 1e-9);
2864 assert!((score.falsity - 0.2).abs() < 1e-9);
2865 assert!((score.indeterminacy - 0.4).abs() < 1e-9);
2866 assert_eq!(score.feedback_count, 10);
2867 }
2868
2869 #[test]
2870 fn positive_alias() {
2871 let score =
2872 TifScore::from_feedback_history(&make_history(&["positive", "positive", "downvote"]));
2873 assert!((score.truth - 2.0 / 3.0).abs() < 1e-9);
2874 assert!((score.falsity - 1.0 / 3.0).abs() < 1e-9);
2875 }
2876
2877 #[test]
2878 fn negative_alias() {
2879 let score =
2880 TifScore::from_feedback_history(&make_history(&["upvote", "negative", "negative"]));
2881 assert!((score.falsity - 2.0 / 3.0).abs() < 1e-9);
2882 }
2883
2884 #[test]
2885 fn proportions_sum_to_one() {
2886 let score = TifScore::from_feedback_history(&make_history(&["upvote", "downvote", "flag"]));
2887 assert!((score.truth + score.indeterminacy + score.falsity - 1.0).abs() < 1e-9);
2888 }
2889
2890 #[test]
2891 fn classification_surface_contradiction() {
2892 let score = TifScore::from_feedback_history(&make_history(&[
2893 "downvote", "downvote", "downvote", "upvote", "upvote",
2894 ]));
2895 assert_eq!(
2896 score.classification,
2897 TifClassification::SurfaceContradiction
2898 );
2899 }
2900
2901 #[test]
2902 fn classification_verify_before_use() {
2903 let score = TifScore::from_feedback_history(&make_history(&[
2905 "upvote", "upvote", "downvote", "downvote", "flag", "flag", "flag",
2906 ]));
2907 assert_eq!(score.classification, TifClassification::VerifyBeforeUse);
2908 }
2909
2910 #[test]
2911 fn falsity_priority_over_indeterminacy() {
2912 let score = TifScore::from_feedback_history(&make_history(&[
2914 "downvote", "downvote", "downvote", "flag", "flag", "flag",
2915 ]));
2916 assert_eq!(
2917 score.classification,
2918 TifClassification::SurfaceContradiction
2919 );
2920 }
2921
2922 #[test]
2923 fn from_metadata_round_trip() {
2924 use serde_json::json;
2925 let data =
2926 json!({ "truth": 0.75, "indeterminacy": 0.15, "falsity": 0.10, "feedback_count": 20 });
2927 let score = TifScore::from_metadata(&data).unwrap();
2928 assert!((score.truth - 0.75).abs() < 1e-9);
2929 assert_eq!(score.feedback_count, 20);
2930 assert_eq!(score.classification, TifClassification::ConfidentReuse);
2931 }
2932
2933 #[test]
2934 fn from_metadata_missing_feedback_count() {
2935 use serde_json::json;
2936 let data = json!({ "truth": 0.8, "indeterminacy": 0.1, "falsity": 0.1 });
2937 let score = TifScore::from_metadata(&data).unwrap();
2938 assert_eq!(score.feedback_count, 0);
2939 }
2940
2941 #[test]
2942 fn classification_as_str() {
2943 assert_eq!(
2944 TifClassification::SurfaceContradiction.as_str(),
2945 "surface_contradiction"
2946 );
2947 assert_eq!(
2948 TifClassification::AskClarification.as_str(),
2949 "ask_clarification"
2950 );
2951 assert_eq!(
2952 TifClassification::ConfidentReuse.as_str(),
2953 "confident_reuse"
2954 );
2955 assert_eq!(
2956 TifClassification::VerifyBeforeUse.as_str(),
2957 "verify_before_use"
2958 );
2959 }
2960
2961 #[test]
2964 fn thin_evidence_single_upvote_not_confident() {
2965 let score = TifScore::from_feedback_history(&make_history(&["upvote"]));
2966 assert!((score.truth + score.indeterminacy + score.falsity - 1.0).abs() < 1e-9);
2967 assert!(score.indeterminacy > 0.0);
2968 assert!(score.truth < 0.70);
2969 assert_eq!(score.classification, TifClassification::VerifyBeforeUse);
2970 }
2971
2972 #[test]
2973 fn thin_evidence_two_upvotes_confident() {
2974 let score = TifScore::from_feedback_history(&make_history(&["upvote", "upvote"]));
2975 assert!((score.truth - 0.8).abs() < 1e-9);
2976 assert!((score.indeterminacy - 0.2).abs() < 1e-9);
2977 assert_eq!(score.classification, TifClassification::ConfidentReuse);
2978 }
2979
2980 #[test]
2981 fn thin_evidence_three_upvotes_no_base() {
2982 let score = TifScore::from_feedback_history(&make_history(&["upvote", "upvote", "upvote"]));
2983 assert!((score.truth - 1.0).abs() < 1e-9);
2984 assert!((score.indeterminacy - 0.0).abs() < 1e-9);
2985 assert_eq!(score.classification, TifClassification::ConfidentReuse);
2986 }
2987
2988 #[test]
2991 fn golden_no_feedback() {
2992 let s = TifScore::from_feedback_history(&make_history(&[]));
2993 assert!((s.truth - 0.0).abs() < 1e-9);
2994 assert!((s.indeterminacy - 1.0).abs() < 1e-9);
2995 assert!((s.falsity - 0.0).abs() < 1e-9);
2996 assert_eq!(s.classification, TifClassification::AskClarification);
2997 }
2998
2999 #[test]
3000 fn golden_one_upvote() {
3001 let s = TifScore::from_feedback_history(&make_history(&["upvote"]));
3002 assert!((s.truth - 2.0 / 3.0).abs() < 1e-4);
3003 assert!((s.indeterminacy - 1.0 / 3.0).abs() < 1e-4);
3004 assert!((s.falsity - 0.0).abs() < 1e-9);
3005 assert_eq!(s.classification, TifClassification::VerifyBeforeUse);
3006 }
3007
3008 #[test]
3009 fn golden_two_upvotes() {
3010 let s = TifScore::from_feedback_history(&make_history(&["upvote", "upvote"]));
3011 assert!((s.truth - 0.8).abs() < 1e-9);
3012 assert!((s.indeterminacy - 0.2).abs() < 1e-9);
3013 assert!((s.falsity - 0.0).abs() < 1e-9);
3014 assert_eq!(s.classification, TifClassification::ConfidentReuse);
3015 }
3016
3017 #[test]
3018 fn golden_three_upvotes() {
3019 let s = TifScore::from_feedback_history(&make_history(&["upvote", "upvote", "upvote"]));
3020 assert!((s.truth - 1.0).abs() < 1e-9);
3021 assert!((s.indeterminacy - 0.0).abs() < 1e-9);
3022 assert!((s.falsity - 0.0).abs() < 1e-9);
3023 assert_eq!(s.classification, TifClassification::ConfidentReuse);
3024 }
3025
3026 #[test]
3027 fn golden_two_downvotes() {
3028 let s = TifScore::from_feedback_history(&make_history(&["downvote", "downvote"]));
3029 assert!((s.truth - 0.0).abs() < 1e-9);
3030 assert!((s.indeterminacy - 0.2).abs() < 1e-9);
3031 assert!((s.falsity - 0.8).abs() < 1e-9);
3032 assert_eq!(s.classification, TifClassification::SurfaceContradiction);
3033 }
3034
3035 #[test]
3036 fn golden_two_flags() {
3037 let s = TifScore::from_feedback_history(&make_history(&["flag", "flag"]));
3038 assert!((s.truth - 0.0).abs() < 1e-9);
3039 assert!((s.indeterminacy - 1.0).abs() < 1e-9);
3040 assert!((s.falsity - 0.0).abs() < 1e-9);
3041 assert_eq!(s.classification, TifClassification::AskClarification);
3042 }
3043
3044 #[test]
3045 fn golden_8up_1down_1flag() {
3046 let s = TifScore::from_feedback_history(&make_history(&[
3047 "upvote", "upvote", "upvote", "upvote", "upvote", "upvote", "upvote", "upvote",
3048 "downvote", "flag",
3049 ]));
3050 assert!((s.truth - 0.8).abs() < 1e-9);
3051 assert!((s.indeterminacy - 0.1).abs() < 1e-9);
3052 assert!((s.falsity - 0.1).abs() < 1e-9);
3053 assert_eq!(s.classification, TifClassification::ConfidentReuse);
3054 }
3055
3056 #[test]
3057 fn golden_3down_3flag() {
3058 let s = TifScore::from_feedback_history(&make_history(&[
3059 "downvote", "downvote", "downvote", "flag", "flag", "flag",
3060 ]));
3061 assert!((s.truth - 0.0).abs() < 1e-9);
3062 assert!((s.indeterminacy - 0.5).abs() < 1e-9);
3063 assert!((s.falsity - 0.5).abs() < 1e-9);
3064 assert_eq!(s.classification, TifClassification::SurfaceContradiction);
3065 }
3066}
3067
3068#[derive(Debug, Clone, Serialize, Deserialize)]
3074pub struct OdeEntity {
3075 pub text: String,
3077 pub label: String,
3079 pub start: usize,
3081 pub end: usize,
3083 pub score: f32,
3085}
3086
3087#[derive(Debug, Clone, Serialize, Deserialize)]
3089pub struct ExtractEntitiesRequest {
3090 pub content: String,
3092 pub agent_id: String,
3094 #[serde(skip_serializing_if = "Option::is_none")]
3096 pub memory_id: Option<String>,
3097 #[serde(skip_serializing_if = "Option::is_none")]
3100 pub entity_types: Option<Vec<String>>,
3101}
3102
3103#[derive(Debug, Clone, Serialize, Deserialize)]
3105pub struct ExtractEntitiesResponse {
3106 pub entities: Vec<OdeEntity>,
3108 pub model: String,
3110 pub processing_time_ms: u64,
3112}
3113
3114#[derive(Debug, Clone, Serialize, Deserialize)]
3120pub struct KgQueryResponse {
3121 pub agent_id: String,
3123 pub node_count: usize,
3125 pub edge_count: usize,
3127 pub edges: Vec<GraphEdge>,
3129}
3130
3131#[derive(Debug, Clone, Serialize, Deserialize)]
3133pub struct KgPathResponse {
3134 pub agent_id: String,
3136 pub from_id: String,
3138 pub to_id: String,
3140 pub hop_count: usize,
3142 pub path: Vec<String>,
3144}
3145
3146#[derive(Debug, Clone, Serialize, Deserialize)]
3148pub struct KgExportResponse {
3149 pub agent_id: String,
3151 pub format: String,
3153 pub node_count: usize,
3155 pub edge_count: usize,
3157 pub edges: Vec<GraphEdge>,
3159}
3160
3161#[derive(Debug, Clone, Serialize, Deserialize)]
3173pub struct MemoryPolicy {
3174 #[serde(skip_serializing_if = "Option::is_none")]
3177 pub working_ttl_seconds: Option<u64>,
3178 #[serde(skip_serializing_if = "Option::is_none")]
3180 pub episodic_ttl_seconds: Option<u64>,
3181 #[serde(skip_serializing_if = "Option::is_none")]
3183 pub semantic_ttl_seconds: Option<u64>,
3184 #[serde(skip_serializing_if = "Option::is_none")]
3186 pub procedural_ttl_seconds: Option<u64>,
3187
3188 #[serde(skip_serializing_if = "Option::is_none")]
3191 pub working_decay: Option<String>,
3192 #[serde(skip_serializing_if = "Option::is_none")]
3194 pub episodic_decay: Option<String>,
3195 #[serde(skip_serializing_if = "Option::is_none")]
3197 pub semantic_decay: Option<String>,
3198 #[serde(skip_serializing_if = "Option::is_none")]
3200 pub procedural_decay: Option<String>,
3201
3202 #[serde(skip_serializing_if = "Option::is_none")]
3206 pub spaced_repetition_factor: Option<f64>,
3207 #[serde(skip_serializing_if = "Option::is_none")]
3209 pub spaced_repetition_base_interval_seconds: Option<u64>,
3210
3211 #[serde(skip_serializing_if = "Option::is_none")]
3216 pub consolidation_enabled: Option<bool>,
3217 #[serde(skip_serializing_if = "Option::is_none")]
3220 pub consolidation_threshold: Option<f32>,
3221 #[serde(skip_serializing_if = "Option::is_none")]
3223 pub consolidation_interval_hours: Option<u32>,
3224 #[serde(skip_serializing_if = "Option::is_none")]
3229 pub consolidated_count: Option<u64>,
3230
3231 #[serde(skip_serializing_if = "Option::is_none")]
3234 pub rate_limit_enabled: Option<bool>,
3235 #[serde(skip_serializing_if = "Option::is_none")]
3237 pub rate_limit_stores_per_minute: Option<u32>,
3238 #[serde(skip_serializing_if = "Option::is_none")]
3240 pub rate_limit_recalls_per_minute: Option<u32>,
3241
3242 #[serde(skip_serializing_if = "Option::is_none")]
3249 pub dedup_on_store: Option<bool>,
3250 #[serde(skip_serializing_if = "Option::is_none")]
3255 pub dedup_threshold: Option<f32>,
3256}
3257
3258impl Default for MemoryPolicy {
3259 fn default() -> Self {
3260 Self {
3261 working_ttl_seconds: Some(14_400),
3262 episodic_ttl_seconds: Some(2_592_000),
3263 semantic_ttl_seconds: Some(31_536_000),
3264 procedural_ttl_seconds: Some(63_072_000),
3265 working_decay: Some("exponential".to_string()),
3266 episodic_decay: Some("power_law".to_string()),
3267 semantic_decay: Some("logarithmic".to_string()),
3268 procedural_decay: Some("flat".to_string()),
3269 spaced_repetition_factor: Some(1.0),
3270 spaced_repetition_base_interval_seconds: Some(86_400),
3271 consolidation_enabled: Some(false),
3272 consolidation_threshold: Some(0.92),
3273 consolidation_interval_hours: Some(24),
3274 consolidated_count: Some(0),
3275 rate_limit_enabled: Some(false),
3276 rate_limit_stores_per_minute: None,
3277 rate_limit_recalls_per_minute: None,
3278 dedup_on_store: Some(false),
3279 dedup_threshold: Some(0.92),
3280 }
3281 }
3282}
3283
3284#[derive(Debug, Clone, Serialize, Deserialize)]
3290pub struct BulkUpdateRequest {
3291 pub filter: serde_json::Value,
3292 pub update: serde_json::Value,
3293}
3294
3295#[derive(Debug, Clone, Serialize, Deserialize)]
3297pub struct BulkUpdateResponse {
3298 pub updated: u64,
3299 pub failed: u64,
3300 pub errors: Vec<String>,
3301}
3302
3303#[derive(Debug, Clone, Serialize, Deserialize)]
3305pub struct BulkDeleteRequest {
3306 pub filter: serde_json::Value,
3307}
3308
3309#[derive(Debug, Clone, Serialize, Deserialize)]
3311pub struct BulkDeleteResponse {
3312 pub deleted: u64,
3313 pub failed: u64,
3314 pub errors: Vec<String>,
3315}
3316
3317#[derive(Debug, Clone, Serialize, Deserialize)]
3319pub struct CountVectorsRequest {
3320 #[serde(skip_serializing_if = "Option::is_none")]
3321 pub filter: Option<serde_json::Value>,
3322}
3323
3324#[derive(Debug, Clone, Serialize, Deserialize)]
3326pub struct CountVectorsResponse {
3327 pub count: u64,
3328 pub namespace: String,
3329}
3330
3331#[derive(Debug, Clone, Serialize, Deserialize)]
3333pub struct AgentConsolidateResponse {
3334 pub agent_id: String,
3335 pub memories_scanned: u64,
3336 pub clusters_found: u64,
3337 pub memories_deprecated: u64,
3338 pub anchor_ids: Vec<String>,
3339 pub deprecated_ids: Vec<String>,
3340 #[serde(skip_serializing_if = "Option::is_none")]
3341 pub skipped: Option<bool>,
3342 #[serde(skip_serializing_if = "Option::is_none")]
3343 pub reason: Option<String>,
3344}
3345
3346#[derive(Debug, Clone, Serialize, Deserialize)]
3348pub struct AgentConsolidationLogEntry {
3349 pub timestamp: u64,
3350 pub clusters_found: u64,
3351 pub memories_deprecated: u64,
3352 pub anchor_ids: Vec<String>,
3353 pub deprecated_ids: Vec<String>,
3354}
3355
3356#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3358pub struct ConsolidationConfigPatch {
3359 #[serde(skip_serializing_if = "Option::is_none")]
3360 pub enabled: Option<bool>,
3361 #[serde(skip_serializing_if = "Option::is_none")]
3362 pub epsilon: Option<f64>,
3363 #[serde(skip_serializing_if = "Option::is_none")]
3364 pub min_samples: Option<u32>,
3365 #[serde(skip_serializing_if = "Option::is_none")]
3366 pub soft_deprecation_days: Option<u32>,
3367}
3368
3369#[derive(Debug, Clone, Serialize, Deserialize)]
3371pub struct AgentConsolidationConfig {
3372 pub enabled: bool,
3373 pub epsilon: f64,
3374 pub min_samples: u32,
3375 pub soft_deprecation_days: u32,
3376}
3377
3378#[derive(Debug, Clone, Serialize, Deserialize)]
3380pub struct NamespaceEntityConfig {
3381 pub namespace: String,
3382 pub extract_entities: bool,
3383 pub entity_types: Vec<String>,
3384}
3385
3386#[derive(Debug, Clone, Serialize, Deserialize)]
3388pub struct NamespaceExtractorConfig {
3389 pub provider: String,
3390 #[serde(skip_serializing_if = "Option::is_none")]
3391 pub model: Option<String>,
3392 #[serde(skip_serializing_if = "Option::is_none")]
3393 pub base_url: Option<String>,
3394}
3395
3396#[derive(Debug, Clone, Serialize, Deserialize)]
3402pub struct NodeReplicationLag {
3403 pub node_id: String,
3404 pub lag_ms: u64,
3405 pub status: String,
3406}
3407
3408#[derive(Debug, Clone, Serialize, Deserialize)]
3410pub struct ReplicationStatus {
3411 pub replication_factor: u32,
3412 pub healthy_replicas: u32,
3413 pub total_nodes: u32,
3414 #[serde(default)]
3415 pub replication_lag: Vec<NodeReplicationLag>,
3416}
3417
3418#[derive(Debug, Clone, Serialize, Deserialize)]
3420pub struct ShardInfo {
3421 pub shard_id: String,
3422 pub namespace: String,
3423 pub primary_node: String,
3424 #[serde(default)]
3425 pub replica_nodes: Vec<String>,
3426 pub state: String,
3427 pub vector_count: u64,
3428 pub size_bytes: u64,
3429}
3430
3431#[derive(Debug, Clone, Serialize, Deserialize)]
3433pub struct ShardListResponse {
3434 pub shards: Vec<ShardInfo>,
3435 pub total: u32,
3436}
3437
3438#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3440pub struct ShardRebalanceRequest {
3441 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3442 pub shard_ids: Vec<String>,
3443 #[serde(default)]
3444 pub dry_run: bool,
3445}
3446
3447#[derive(Debug, Clone, Serialize, Deserialize)]
3449pub struct ShardMove {
3450 pub shard_id: String,
3451 pub from_node: String,
3452 pub to_node: String,
3453}
3454
3455#[derive(Debug, Clone, Serialize, Deserialize)]
3457pub struct ShardRebalanceResponse {
3458 pub initiated: bool,
3459 pub operation_id: String,
3460 pub shards_affected: u32,
3461 #[serde(skip_serializing_if = "Option::is_none")]
3462 pub estimated_seconds: Option<u64>,
3463 #[serde(default)]
3464 pub planned_moves: Vec<ShardMove>,
3465}
3466
3467#[derive(Debug, Clone, Serialize, Deserialize)]
3469pub struct MaintenanceStatus {
3470 pub enabled: bool,
3471 #[serde(skip_serializing_if = "Option::is_none")]
3472 pub reason: Option<String>,
3473 #[serde(skip_serializing_if = "Option::is_none")]
3474 pub enabled_at: Option<u64>,
3475 #[serde(skip_serializing_if = "Option::is_none")]
3476 pub scheduled_end: Option<u64>,
3477 #[serde(default)]
3478 pub nodes_in_maintenance: Vec<String>,
3479 pub rejecting_requests: bool,
3480}
3481
3482#[derive(Debug, Clone, Serialize, Deserialize)]
3484pub struct EnableMaintenanceRequest {
3485 pub reason: String,
3486 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3487 pub node_ids: Vec<String>,
3488 #[serde(default)]
3489 pub reject_requests: bool,
3490 #[serde(skip_serializing_if = "Option::is_none")]
3491 pub duration_minutes: Option<u32>,
3492}
3493
3494#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3496pub struct DisableMaintenanceRequest {
3497 #[serde(skip_serializing_if = "Option::is_none")]
3498 pub force: Option<bool>,
3499}
3500
3501#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3503pub struct QuotaConfig {
3504 #[serde(skip_serializing_if = "Option::is_none")]
3505 pub max_vectors: Option<u64>,
3506 #[serde(skip_serializing_if = "Option::is_none")]
3507 pub max_storage_bytes: Option<u64>,
3508 #[serde(skip_serializing_if = "Option::is_none")]
3509 pub max_dimensions: Option<usize>,
3510 #[serde(skip_serializing_if = "Option::is_none")]
3511 pub max_metadata_bytes: Option<usize>,
3512 #[serde(default)]
3513 pub enforcement: String,
3514}
3515
3516#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3518pub struct QuotaUsage {
3519 pub vector_count: u64,
3520 pub storage_bytes: u64,
3521 #[serde(skip_serializing_if = "Option::is_none")]
3522 pub avg_dimensions: Option<usize>,
3523 #[serde(skip_serializing_if = "Option::is_none")]
3524 pub avg_metadata_bytes: Option<usize>,
3525 pub last_updated: u64,
3526}
3527
3528#[derive(Debug, Clone, Serialize, Deserialize)]
3530pub struct QuotaStatus {
3531 pub namespace: String,
3532 pub config: QuotaConfig,
3533 pub usage: QuotaUsage,
3534 #[serde(skip_serializing_if = "Option::is_none")]
3535 pub vector_usage_percent: Option<f32>,
3536 #[serde(skip_serializing_if = "Option::is_none")]
3537 pub storage_usage_percent: Option<f32>,
3538 pub is_exceeded: bool,
3539 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3540 pub exceeded_quotas: Vec<String>,
3541}
3542
3543#[derive(Debug, Clone, Serialize, Deserialize)]
3545pub struct QuotaListResponse {
3546 pub quotas: Vec<QuotaStatus>,
3547 pub total: u64,
3548 #[serde(skip_serializing_if = "Option::is_none")]
3549 pub default_config: Option<QuotaConfig>,
3550}
3551
3552#[derive(Debug, Clone, Serialize, Deserialize)]
3554pub struct DefaultQuotaResponse {
3555 pub config: Option<QuotaConfig>,
3556}
3557
3558#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3560pub struct SetDefaultQuotaRequest {
3561 pub config: Option<QuotaConfig>,
3562}
3563
3564#[derive(Debug, Clone, Serialize, Deserialize)]
3566pub struct SetQuotaRequest {
3567 pub config: QuotaConfig,
3568}
3569
3570#[derive(Debug, Clone, Serialize, Deserialize)]
3572pub struct SetQuotaResponse {
3573 pub success: bool,
3574 pub namespace: String,
3575 pub config: QuotaConfig,
3576 pub message: String,
3577}
3578
3579#[derive(Debug, Clone, Serialize, Deserialize)]
3581pub struct QuotaCheckRequest {
3582 pub vector_ids: Vec<String>,
3583 #[serde(skip_serializing_if = "Option::is_none")]
3584 pub dimensions: Option<usize>,
3585 #[serde(skip_serializing_if = "Option::is_none")]
3586 pub metadata_bytes: Option<usize>,
3587}
3588
3589#[derive(Debug, Clone, Serialize, Deserialize)]
3591pub struct QuotaCheckResult {
3592 pub allowed: bool,
3593 #[serde(skip_serializing_if = "Option::is_none")]
3594 pub reason: Option<String>,
3595 pub usage: QuotaUsage,
3596 #[serde(skip_serializing_if = "Option::is_none")]
3597 pub exceeded_quota: Option<String>,
3598}
3599
3600#[derive(Debug, Clone, Serialize, Deserialize)]
3602pub struct AdminBackupInfo {
3603 pub backup_id: String,
3604 pub name: String,
3605 pub backup_type: String,
3606 pub status: String,
3607 #[serde(default)]
3608 pub namespaces: Vec<String>,
3609 pub vector_count: u64,
3610 pub size_bytes: u64,
3611 pub created_at: u64,
3612 #[serde(skip_serializing_if = "Option::is_none")]
3613 pub completed_at: Option<u64>,
3614 #[serde(skip_serializing_if = "Option::is_none")]
3615 pub duration_seconds: Option<u64>,
3616 #[serde(skip_serializing_if = "Option::is_none")]
3617 pub storage_path: Option<String>,
3618 #[serde(skip_serializing_if = "Option::is_none")]
3619 pub error: Option<String>,
3620 pub encrypted: bool,
3621 #[serde(skip_serializing_if = "Option::is_none")]
3622 pub compression: Option<String>,
3623}
3624
3625#[derive(Debug, Clone, Serialize, Deserialize)]
3627pub struct BackupListResponse {
3628 pub backups: Vec<AdminBackupInfo>,
3629 pub total: u64,
3630}
3631
3632#[derive(Debug, Clone, Serialize, Deserialize)]
3634pub struct CreateBackupRequest {
3635 pub name: String,
3636 #[serde(skip_serializing_if = "Option::is_none")]
3637 pub backup_type: Option<String>,
3638 #[serde(skip_serializing_if = "Option::is_none")]
3639 pub namespaces: Option<Vec<String>>,
3640 #[serde(skip_serializing_if = "Option::is_none")]
3641 pub encrypt: Option<bool>,
3642 #[serde(skip_serializing_if = "Option::is_none")]
3643 pub compression: Option<String>,
3644}
3645
3646#[derive(Debug, Clone, Serialize, Deserialize)]
3648pub struct CreateBackupResponse {
3649 pub backup: AdminBackupInfo,
3650 #[serde(skip_serializing_if = "Option::is_none")]
3651 pub estimated_completion: Option<u64>,
3652}
3653
3654#[derive(Debug, Clone, Serialize, Deserialize)]
3656pub struct RestoreBackupRequest {
3657 pub backup_id: String,
3658 #[serde(skip_serializing_if = "Option::is_none")]
3659 pub target_namespaces: Option<Vec<String>>,
3660 #[serde(skip_serializing_if = "Option::is_none")]
3661 pub overwrite: Option<bool>,
3662 #[serde(skip_serializing_if = "Option::is_none")]
3663 pub point_in_time: Option<u64>,
3664}
3665
3666#[derive(Debug, Clone, Serialize, Deserialize)]
3668pub struct RestoreBackupResponse {
3669 pub restore_id: String,
3670 pub status: String,
3671 pub backup_id: String,
3672 #[serde(default)]
3673 pub namespaces: Vec<String>,
3674 pub started_at: u64,
3675 #[serde(skip_serializing_if = "Option::is_none")]
3676 pub estimated_completion: Option<u64>,
3677 #[serde(skip_serializing_if = "Option::is_none")]
3678 pub progress_percent: Option<u8>,
3679 #[serde(skip_serializing_if = "Option::is_none")]
3680 pub vectors_restored: Option<u64>,
3681 #[serde(skip_serializing_if = "Option::is_none")]
3682 pub completed_at: Option<u64>,
3683 #[serde(skip_serializing_if = "Option::is_none")]
3684 pub duration_seconds: Option<u64>,
3685 #[serde(skip_serializing_if = "Option::is_none")]
3686 pub error: Option<String>,
3687}
3688
3689#[derive(Debug, Clone, Serialize, Deserialize)]
3691pub struct BackupSchedule {
3692 pub enabled: bool,
3693 #[serde(skip_serializing_if = "Option::is_none")]
3694 pub cron: Option<String>,
3695 pub backup_type: String,
3696 pub retention_days: u32,
3697 pub max_backups: u32,
3698 #[serde(default)]
3699 pub namespaces: Vec<String>,
3700 pub encrypt: bool,
3701 #[serde(skip_serializing_if = "Option::is_none")]
3702 pub compression: Option<String>,
3703 #[serde(skip_serializing_if = "Option::is_none")]
3704 pub last_backup_at: Option<u64>,
3705 #[serde(skip_serializing_if = "Option::is_none")]
3706 pub next_backup_at: Option<u64>,
3707}
3708
3709#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3711pub struct UpdateBackupScheduleRequest {
3712 #[serde(skip_serializing_if = "Option::is_none")]
3713 pub enabled: Option<bool>,
3714 #[serde(skip_serializing_if = "Option::is_none")]
3715 pub cron: Option<String>,
3716 #[serde(skip_serializing_if = "Option::is_none")]
3717 pub backup_type: Option<String>,
3718 #[serde(skip_serializing_if = "Option::is_none")]
3719 pub retention_days: Option<u32>,
3720 #[serde(skip_serializing_if = "Option::is_none")]
3721 pub max_backups: Option<u32>,
3722 #[serde(skip_serializing_if = "Option::is_none")]
3723 pub namespaces: Option<Vec<String>>,
3724 #[serde(skip_serializing_if = "Option::is_none")]
3725 pub encrypt: Option<bool>,
3726 #[serde(skip_serializing_if = "Option::is_none")]
3727 pub compression: Option<String>,
3728}
3729
3730fn default_route_top_k() -> usize {
3735 3
3736}
3737
3738fn default_route_min_similarity() -> f32 {
3739 0.3
3740}
3741
3742#[derive(Debug, Clone, Serialize, Deserialize)]
3744pub struct RouteRequest {
3745 pub query: String,
3747 #[serde(default = "default_route_top_k")]
3749 pub top_k: usize,
3750 #[serde(default = "default_route_min_similarity")]
3752 pub min_similarity: f32,
3753 #[serde(skip_serializing_if = "Option::is_none")]
3755 pub model: Option<String>,
3756}
3757
3758#[derive(Debug, Clone, Serialize, Deserialize)]
3760pub struct RouteMatch {
3761 pub namespace: String,
3763 pub similarity: f64,
3765 #[serde(skip_serializing_if = "Option::is_none")]
3767 pub description: Option<String>,
3768}
3769
3770#[derive(Debug, Clone, Serialize, Deserialize)]
3772pub struct RouteResponse {
3773 pub routes: Vec<RouteMatch>,
3775 pub model: String,
3777 pub embedding_time_ms: u64,
3779}
3780
3781#[derive(Debug, Clone, Serialize, Deserialize)]
3787pub struct ImportJobStatus {
3788 pub job_id: String,
3790 pub status: String,
3792 pub format: String,
3794 pub total: usize,
3796 pub imported: usize,
3798 pub skipped: usize,
3800 #[serde(default)]
3802 pub errors: Vec<String>,
3803 pub started_at: u64,
3805 #[serde(skip_serializing_if = "Option::is_none")]
3807 pub finished_at: Option<u64>,
3808}
3809
3810#[derive(Debug, Clone, Serialize, Deserialize)]
3816pub struct TierInfo {
3817 pub name: String,
3819 pub tier_type: String,
3821 pub technology: String,
3823 pub description: String,
3825 pub target_latency: String,
3827 #[serde(skip_serializing_if = "Option::is_none")]
3829 pub capacity: Option<String>,
3830 pub status: String,
3832 pub current_count: u64,
3834 pub hit_count: u64,
3836 pub hit_rate: f64,
3838}
3839
3840#[derive(Debug, Clone, Serialize, Deserialize)]
3842pub struct TierConfig {
3843 pub hot_tier_capacity: usize,
3845 pub hot_to_warm_threshold_secs: u64,
3847 pub warm_to_cold_threshold_secs: u64,
3849 pub auto_tier_enabled: bool,
3851 pub tier_check_interval_secs: u64,
3853}
3854
3855#[derive(Debug, Clone, Serialize, Deserialize)]
3857pub struct TierActivity {
3858 pub promotions: u64,
3860 pub demotions: u64,
3862 pub cache_hit_rate: f64,
3864 pub storage_backend: String,
3866 pub promotions_to_hot: u64,
3868 pub demotions_to_warm: u64,
3870 pub demotions_to_cold: u64,
3872}
3873
3874#[derive(Debug, Clone, Serialize, Deserialize)]
3876pub struct StorageTierOverview {
3877 pub tiers_enabled: bool,
3879 pub architecture: Vec<TierInfo>,
3881 pub config: TierConfig,
3883 pub activity: TierActivity,
3885}
3886
3887#[derive(Debug, Clone, Serialize, Deserialize)]
3893pub struct MemoryTypeStatsResponse {
3894 pub total: u64,
3896 pub working: u64,
3898 pub episodic: u64,
3900 pub semantic: u64,
3902 pub procedural: u64,
3904 pub agent_namespaces: u64,
3906}
3907
3908fn default_target_dimension() -> usize {
3913 1024
3914}
3915
3916#[derive(Debug, Clone, Serialize, Deserialize)]
3918pub struct MigrateNamespaceDimensionsRequest {
3919 #[serde(default)]
3921 pub namespaces: Vec<String>,
3922 #[serde(default = "default_target_dimension")]
3924 pub target_dimension: usize,
3925}
3926
3927#[derive(Debug, Clone, Serialize, Deserialize)]
3929pub struct NamespaceMigrationResult {
3930 pub namespace: String,
3932 pub original_dimension: usize,
3934 pub vectors_migrated: usize,
3936 pub vectors_skipped: usize,
3938 pub status: String,
3940 #[serde(skip_serializing_if = "Option::is_none")]
3942 pub error: Option<String>,
3943}
3944
3945#[derive(Debug, Clone, Serialize, Deserialize)]
3947pub struct MigrateDimensionsResponse {
3948 pub migrated: usize,
3950 pub failed: usize,
3952 pub already_current: usize,
3954 pub results: Vec<NamespaceMigrationResult>,
3956}
3957
3958#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3960pub struct DrainReembedRequest {
3961 #[serde(skip_serializing_if = "Option::is_none")]
3963 pub timeout_secs: Option<u64>,
3964 #[serde(skip_serializing_if = "Option::is_none")]
3966 pub batch_size: Option<usize>,
3967 #[serde(skip_serializing_if = "Option::is_none")]
3969 pub min_importance: Option<f32>,
3970}
3971
3972#[derive(Debug, Clone, Serialize, Deserialize)]
3977pub struct DrainReembedResponse {
3978 pub processed: usize,
3980 pub remaining: usize,
3982 pub elapsed_ms: u128,
3984 pub cycles: usize,
3986 pub timed_out: bool,
3988}
3989
3990#[derive(Debug, Clone, Serialize, Deserialize)]
3996pub struct StaticCountResponse {
3997 pub static_count: usize,
3999}
4000
4001#[cfg(test)]
4002mod embedding_model_tests {
4003 use super::*;
4004
4005 #[test]
4006 fn embedding_model_wire_values() {
4007 assert_eq!(
4010 serde_json::to_string(&EmbeddingModel::ModernBertEmbedBase).unwrap(),
4011 r#""modernbert-embed-base""#
4012 );
4013 assert_eq!(
4014 serde_json::to_string(&EmbeddingModel::GteModernBertBase).unwrap(),
4015 r#""gte-modernbert-base""#
4016 );
4017 assert_eq!(
4019 serde_json::to_string(&EmbeddingModel::BgeLarge).unwrap(),
4020 r#""bge-large""#
4021 );
4022 assert_eq!(
4023 serde_json::to_string(&EmbeddingModel::E5Small).unwrap(),
4024 r#""e5-small""#
4025 );
4026 }
4027
4028 #[test]
4029 fn embedding_model_roundtrip() {
4030 for (variant, wire) in [
4031 (EmbeddingModel::ModernBertEmbedBase, "modernbert-embed-base"),
4032 (EmbeddingModel::GteModernBertBase, "gte-modernbert-base"),
4033 ] {
4034 let json = format!(r#""{wire}""#);
4035 let decoded: EmbeddingModel = serde_json::from_str(&json).unwrap();
4036 assert_eq!(decoded, variant);
4037 }
4038 }
4039}