1use serde::{Deserialize, Serialize};
2
3pub type VectorId = String;
5
6pub type NamespaceId = String;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Vector {
12 pub id: VectorId,
13 pub values: Vec<f32>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub metadata: Option<serde_json::Value>,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub ttl_seconds: Option<u64>,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub expires_at: Option<u64>,
22}
23
24impl Vector {
25 pub fn is_expired(&self) -> bool {
27 if let Some(expires_at) = self.expires_at {
28 let now = std::time::SystemTime::now()
29 .duration_since(std::time::UNIX_EPOCH)
30 .unwrap_or_default()
31 .as_secs();
32 now >= expires_at
33 } else {
34 false
35 }
36 }
37
38 #[inline]
41 pub fn is_expired_at(&self, now_secs: u64) -> bool {
42 self.expires_at.is_some_and(|exp| now_secs >= exp)
43 }
44
45 pub fn apply_ttl(&mut self) {
47 if let Some(ttl) = self.ttl_seconds {
48 let now = std::time::SystemTime::now()
49 .duration_since(std::time::UNIX_EPOCH)
50 .unwrap_or_default()
51 .as_secs();
52 self.expires_at = Some(now + ttl);
53 }
54 }
55
56 pub fn remaining_ttl(&self) -> Option<u64> {
58 self.expires_at.and_then(|expires_at| {
59 let now = std::time::SystemTime::now()
60 .duration_since(std::time::UNIX_EPOCH)
61 .unwrap_or_default()
62 .as_secs();
63 if now < expires_at {
64 Some(expires_at - now)
65 } else {
66 None
67 }
68 })
69 }
70}
71
72#[derive(Debug, Deserialize)]
74pub struct UpsertRequest {
75 pub vectors: Vec<Vector>,
76}
77
78#[derive(Debug, Serialize, Deserialize)]
80pub struct UpsertResponse {
81 pub upserted_count: usize,
82}
83
84#[derive(Debug, Deserialize)]
87pub struct ColumnUpsertRequest {
88 pub ids: Vec<VectorId>,
90 pub vectors: Vec<Vec<f32>>,
92 #[serde(default)]
95 pub attributes: std::collections::HashMap<String, Vec<serde_json::Value>>,
96 #[serde(default)]
98 pub ttl_seconds: Option<u64>,
99 #[serde(default)]
101 pub dimension: Option<usize>,
102}
103
104impl ColumnUpsertRequest {
105 pub fn to_vectors(&self) -> Result<Vec<Vector>, String> {
107 let count = self.ids.len();
108
109 if self.vectors.len() != count {
111 return Err(format!(
112 "vectors array length ({}) doesn't match ids array length ({})",
113 self.vectors.len(),
114 count
115 ));
116 }
117
118 for (attr_name, attr_values) in &self.attributes {
119 if attr_values.len() != count {
120 return Err(format!(
121 "attribute '{}' array length ({}) doesn't match ids array length ({})",
122 attr_name,
123 attr_values.len(),
124 count
125 ));
126 }
127 }
128
129 let expected_dim = if let Some(dim) = self.dimension {
132 Some(dim)
133 } else {
134 self.vectors.first().map(|v| v.len())
135 };
136
137 if let Some(expected) = expected_dim {
138 for (i, vec) in self.vectors.iter().enumerate() {
139 if vec.len() != expected {
140 return Err(format!(
141 "vectors[{}] has dimension {} but expected {}",
142 i,
143 vec.len(),
144 expected
145 ));
146 }
147 }
148 }
149
150 let mut vectors = Vec::with_capacity(count);
152 for i in 0..count {
153 let metadata = if self.attributes.is_empty() {
155 None
156 } else {
157 let mut meta = serde_json::Map::new();
158 for (attr_name, attr_values) in &self.attributes {
159 let value = &attr_values[i];
160 if !value.is_null() {
161 meta.insert(attr_name.clone(), value.clone());
162 }
163 }
164 if meta.is_empty() {
165 None
166 } else {
167 Some(serde_json::Value::Object(meta))
168 }
169 };
170
171 let mut vector = Vector {
172 id: self.ids[i].clone(),
173 values: self.vectors[i].clone(),
174 metadata,
175 ttl_seconds: self.ttl_seconds,
176 expires_at: None,
177 };
178 vector.apply_ttl();
179 vectors.push(vector);
180 }
181
182 Ok(vectors)
183 }
184}
185
186#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
188#[serde(rename_all = "snake_case")]
189pub enum DistanceMetric {
190 #[default]
191 Cosine,
192 Euclidean,
193 DotProduct,
194}
195
196#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
203#[serde(rename_all = "snake_case")]
204pub enum ReadConsistency {
205 Strong,
207 #[default]
209 Eventual,
210 #[serde(rename = "bounded_staleness")]
212 BoundedStaleness,
213}
214
215#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
217pub struct StalenessConfig {
218 #[serde(default = "default_max_staleness_ms")]
220 pub max_staleness_ms: u64,
221}
222
223fn default_max_staleness_ms() -> u64 {
224 5000 }
226
227#[derive(Debug, Deserialize)]
229pub struct QueryRequest {
230 pub vector: Vec<f32>,
231 #[serde(default = "default_top_k")]
232 pub top_k: usize,
233 #[serde(default)]
234 pub distance_metric: DistanceMetric,
235 #[serde(default = "default_true")]
236 pub include_metadata: bool,
237 #[serde(default)]
238 pub include_vectors: bool,
239 #[serde(default)]
241 pub filter: Option<FilterExpression>,
242 #[serde(default)]
244 pub cursor: Option<String>,
245 #[serde(default)]
248 pub consistency: ReadConsistency,
249 #[serde(default)]
251 pub staleness_config: Option<StalenessConfig>,
252}
253
254fn default_top_k() -> usize {
255 10
256}
257
258fn default_true() -> bool {
259 true
260}
261
262#[derive(Debug, Serialize, Deserialize)]
264pub struct SearchResult {
265 pub id: VectorId,
266 pub score: f32,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub metadata: Option<serde_json::Value>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub vector: Option<Vec<f32>>,
271}
272
273#[derive(Debug, Serialize, Deserialize)]
275pub struct QueryResponse {
276 pub results: Vec<SearchResult>,
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub next_cursor: Option<String>,
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub has_more: Option<bool>,
283 #[serde(default)]
285 pub search_time_ms: u64,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct PaginationCursor {
295 pub last_score: f32,
297 pub last_id: String,
299}
300
301impl PaginationCursor {
302 pub fn new(last_score: f32, last_id: String) -> Self {
304 Self {
305 last_score,
306 last_id,
307 }
308 }
309
310 pub fn encode(&self) -> String {
312 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
313 let json = serde_json::to_string(self).unwrap_or_default();
314 URL_SAFE_NO_PAD.encode(json.as_bytes())
315 }
316
317 pub fn decode(cursor: &str) -> Option<Self> {
319 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
320 let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
321 let json = String::from_utf8(bytes).ok()?;
322 serde_json::from_str(&json).ok()
323 }
324}
325
326#[derive(Debug, Deserialize)]
328pub struct DeleteRequest {
329 pub ids: Vec<VectorId>,
330}
331
332#[derive(Debug, Serialize)]
334pub struct DeleteResponse {
335 pub deleted_count: usize,
336}
337
338#[derive(Debug, Clone, Deserialize)]
344pub struct BatchQueryItem {
345 #[serde(default)]
347 pub id: Option<String>,
348 pub vector: Vec<f32>,
350 #[serde(default = "default_batch_top_k")]
352 pub top_k: u32,
353 #[serde(default)]
355 pub filter: Option<FilterExpression>,
356 #[serde(default)]
358 pub include_metadata: bool,
359 #[serde(default)]
361 pub consistency: ReadConsistency,
362 #[serde(default)]
364 pub staleness_config: Option<StalenessConfig>,
365}
366
367fn default_batch_top_k() -> u32 {
368 10
369}
370
371#[derive(Debug, Deserialize)]
373pub struct BatchQueryRequest {
374 pub queries: Vec<BatchQueryItem>,
376}
377
378#[derive(Debug, Serialize)]
380pub struct BatchQueryResult {
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub id: Option<String>,
384 pub results: Vec<SearchResult>,
386 pub latency_ms: f64,
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub error: Option<String>,
391}
392
393#[derive(Debug, Serialize)]
395pub struct BatchQueryResponse {
396 pub results: Vec<BatchQueryResult>,
398 pub total_latency_ms: f64,
400 pub query_count: usize,
402}
403
404#[derive(Debug, Deserialize)]
410pub struct MultiVectorSearchRequest {
411 pub positive_vectors: Vec<Vec<f32>>,
413 #[serde(default)]
415 pub positive_weights: Option<Vec<f32>>,
416 #[serde(default)]
418 pub negative_vectors: Option<Vec<Vec<f32>>>,
419 #[serde(default)]
421 pub negative_weights: Option<Vec<f32>>,
422 #[serde(default = "default_top_k")]
424 pub top_k: usize,
425 #[serde(default)]
427 pub distance_metric: DistanceMetric,
428 #[serde(default)]
430 pub score_threshold: Option<f32>,
431 #[serde(default)]
433 pub enable_mmr: bool,
434 #[serde(default = "default_mmr_lambda")]
436 pub mmr_lambda: f32,
437 #[serde(default = "default_true")]
439 pub include_metadata: bool,
440 #[serde(default)]
442 pub include_vectors: bool,
443 #[serde(default)]
445 pub filter: Option<FilterExpression>,
446 #[serde(default)]
448 pub consistency: ReadConsistency,
449 #[serde(default)]
451 pub staleness_config: Option<StalenessConfig>,
452}
453
454fn default_mmr_lambda() -> f32 {
455 0.5
456}
457
458#[derive(Debug, Serialize, Deserialize)]
460pub struct MultiVectorSearchResult {
461 pub id: VectorId,
462 pub score: f32,
464 #[serde(skip_serializing_if = "Option::is_none")]
466 pub mmr_score: Option<f32>,
467 #[serde(skip_serializing_if = "Option::is_none")]
469 pub original_rank: Option<usize>,
470 #[serde(skip_serializing_if = "Option::is_none")]
471 pub metadata: Option<serde_json::Value>,
472 #[serde(skip_serializing_if = "Option::is_none")]
473 pub vector: Option<Vec<f32>>,
474}
475
476#[derive(Debug, Serialize, Deserialize)]
478pub struct MultiVectorSearchResponse {
479 pub results: Vec<MultiVectorSearchResult>,
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub computed_query_vector: Option<Vec<f32>>,
483}
484
485#[derive(Debug, Serialize, Deserialize)]
491pub struct IndexDocumentRequest {
492 pub id: String,
493 pub text: String,
494 #[serde(skip_serializing_if = "Option::is_none")]
495 pub metadata: Option<serde_json::Value>,
496}
497
498#[derive(Debug, Deserialize)]
500pub struct IndexDocumentsRequest {
501 pub documents: Vec<IndexDocumentRequest>,
502}
503
504#[derive(Debug, Serialize, Deserialize)]
506pub struct IndexDocumentsResponse {
507 pub indexed_count: usize,
508}
509
510#[derive(Debug, Deserialize)]
512pub struct FullTextSearchRequest {
513 pub query: String,
514 #[serde(default = "default_top_k")]
515 pub top_k: usize,
516 #[serde(default)]
518 pub filter: Option<FilterExpression>,
519}
520
521#[derive(Debug, Serialize, Deserialize)]
523pub struct FullTextSearchResult {
524 pub id: String,
525 pub score: f32,
526 #[serde(skip_serializing_if = "Option::is_none")]
527 pub metadata: Option<serde_json::Value>,
528}
529
530#[derive(Debug, Serialize, Deserialize)]
532pub struct FullTextSearchResponse {
533 pub results: Vec<FullTextSearchResult>,
534 #[serde(default)]
536 pub search_time_ms: u64,
537}
538
539#[derive(Debug, Deserialize)]
541pub struct DeleteDocumentsRequest {
542 pub ids: Vec<String>,
543}
544
545#[derive(Debug, Serialize)]
547pub struct DeleteDocumentsResponse {
548 pub deleted_count: usize,
549}
550
551#[derive(Debug, Serialize)]
553pub struct FullTextIndexStats {
554 pub document_count: u32,
555 pub unique_terms: usize,
556 pub avg_doc_length: f32,
557}
558
559#[derive(Debug, Deserialize)]
565pub struct HybridSearchRequest {
566 #[serde(default)]
569 pub vector: Option<Vec<f32>>,
570 pub text: String,
572 #[serde(default = "default_top_k")]
574 pub top_k: usize,
575 #[serde(default = "default_vector_weight")]
578 pub vector_weight: f32,
579 #[serde(default)]
581 pub distance_metric: DistanceMetric,
582 #[serde(default = "default_true")]
584 pub include_metadata: bool,
585 #[serde(default)]
587 pub include_vectors: bool,
588 #[serde(default)]
590 pub filter: Option<FilterExpression>,
591}
592
593fn default_vector_weight() -> f32 {
594 0.5 }
596
597#[derive(Debug, Serialize, Deserialize)]
599pub struct HybridSearchResult {
600 pub id: String,
601 pub score: f32,
603 pub vector_score: f32,
605 pub text_score: f32,
607 #[serde(skip_serializing_if = "Option::is_none")]
608 pub metadata: Option<serde_json::Value>,
609 #[serde(skip_serializing_if = "Option::is_none")]
610 pub vector: Option<Vec<f32>>,
611}
612
613#[derive(Debug, Serialize, Deserialize)]
615pub struct HybridSearchResponse {
616 pub results: Vec<HybridSearchResult>,
617 #[serde(default)]
619 pub search_time_ms: u64,
620}
621
622#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
628#[serde(untagged)]
629pub enum FilterValue {
630 String(String),
631 Number(f64),
632 Integer(i64),
633 Boolean(bool),
634 StringArray(Vec<String>),
635 NumberArray(Vec<f64>),
636}
637
638impl FilterValue {
639 pub fn as_f64(&self) -> Option<f64> {
641 match self {
642 FilterValue::Number(n) => Some(*n),
643 FilterValue::Integer(i) => Some(*i as f64),
644 _ => None,
645 }
646 }
647
648 pub fn as_str(&self) -> Option<&str> {
650 match self {
651 FilterValue::String(s) => Some(s.as_str()),
652 _ => None,
653 }
654 }
655
656 pub fn as_bool(&self) -> Option<bool> {
658 match self {
659 FilterValue::Boolean(b) => Some(*b),
660 _ => None,
661 }
662 }
663}
664
665#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
667#[serde(rename_all = "snake_case")]
668pub enum FilterCondition {
669 #[serde(rename = "$eq")]
671 Eq(FilterValue),
672 #[serde(rename = "$ne")]
674 Ne(FilterValue),
675 #[serde(rename = "$gt")]
677 Gt(FilterValue),
678 #[serde(rename = "$gte")]
680 Gte(FilterValue),
681 #[serde(rename = "$lt")]
683 Lt(FilterValue),
684 #[serde(rename = "$lte")]
686 Lte(FilterValue),
687 #[serde(rename = "$in")]
689 In(Vec<FilterValue>),
690 #[serde(rename = "$nin")]
692 NotIn(Vec<FilterValue>),
693 #[serde(rename = "$exists")]
695 Exists(bool),
696 #[serde(rename = "$contains")]
701 Contains(String),
702 #[serde(rename = "$icontains")]
704 IContains(String),
705 #[serde(rename = "$startsWith")]
707 StartsWith(String),
708 #[serde(rename = "$endsWith")]
710 EndsWith(String),
711 #[serde(rename = "$glob")]
713 Glob(String),
714 #[serde(rename = "$regex")]
716 Regex(String),
717 #[serde(rename = "$arrayContains")]
722 ArrayContains(FilterValue),
723 #[serde(rename = "$arrayContainsAll")]
725 ArrayContainsAll(Vec<FilterValue>),
726 #[serde(rename = "$arrayContainsAny")]
728 ArrayContainsAny(Vec<FilterValue>),
729}
730
731#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
733#[serde(untagged)]
734pub enum FilterExpression {
735 And {
737 #[serde(rename = "$and")]
738 conditions: Vec<FilterExpression>,
739 },
740 Or {
742 #[serde(rename = "$or")]
743 conditions: Vec<FilterExpression>,
744 },
745 Field {
747 #[serde(flatten)]
748 field: std::collections::HashMap<String, FilterCondition>,
749 },
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize, Default)]
758pub struct QuotaConfig {
759 #[serde(skip_serializing_if = "Option::is_none")]
761 pub max_vectors: Option<u64>,
762 #[serde(skip_serializing_if = "Option::is_none")]
764 pub max_storage_bytes: Option<u64>,
765 #[serde(skip_serializing_if = "Option::is_none")]
767 pub max_dimensions: Option<usize>,
768 #[serde(skip_serializing_if = "Option::is_none")]
770 pub max_metadata_bytes: Option<usize>,
771 #[serde(default)]
773 pub enforcement: QuotaEnforcement,
774}
775
776#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
778#[serde(rename_all = "snake_case")]
779pub enum QuotaEnforcement {
780 None,
782 Soft,
784 #[default]
786 Hard,
787}
788
789#[derive(Debug, Clone, Serialize, Deserialize, Default)]
791pub struct QuotaUsage {
792 pub vector_count: u64,
794 pub storage_bytes: u64,
796 pub avg_dimensions: Option<usize>,
798 pub avg_metadata_bytes: Option<usize>,
800 pub last_updated: u64,
802}
803
804impl QuotaUsage {
805 pub fn new(vector_count: u64, storage_bytes: u64) -> Self {
807 let now = std::time::SystemTime::now()
808 .duration_since(std::time::UNIX_EPOCH)
809 .unwrap_or_default()
810 .as_secs();
811 Self {
812 vector_count,
813 storage_bytes,
814 avg_dimensions: None,
815 avg_metadata_bytes: None,
816 last_updated: now,
817 }
818 }
819
820 pub fn touch(&mut self) {
822 self.last_updated = std::time::SystemTime::now()
823 .duration_since(std::time::UNIX_EPOCH)
824 .unwrap_or_default()
825 .as_secs();
826 }
827}
828
829#[derive(Debug, Clone, Serialize, Deserialize)]
831pub struct QuotaStatus {
832 pub namespace: String,
834 pub config: QuotaConfig,
836 pub usage: QuotaUsage,
838 #[serde(skip_serializing_if = "Option::is_none")]
840 pub vector_usage_percent: Option<f32>,
841 #[serde(skip_serializing_if = "Option::is_none")]
843 pub storage_usage_percent: Option<f32>,
844 pub is_exceeded: bool,
846 #[serde(skip_serializing_if = "Vec::is_empty")]
848 pub exceeded_quotas: Vec<String>,
849}
850
851impl QuotaStatus {
852 pub fn new(namespace: String, config: QuotaConfig, usage: QuotaUsage) -> Self {
854 let vector_usage_percent = config
855 .max_vectors
856 .map(|max| (usage.vector_count as f32 / max as f32) * 100.0);
857
858 let storage_usage_percent = config
859 .max_storage_bytes
860 .map(|max| (usage.storage_bytes as f32 / max as f32) * 100.0);
861
862 let mut exceeded_quotas = Vec::new();
863
864 if let Some(max) = config.max_vectors {
865 if usage.vector_count > max {
866 exceeded_quotas.push("max_vectors".to_string());
867 }
868 }
869
870 if let Some(max) = config.max_storage_bytes {
871 if usage.storage_bytes > max {
872 exceeded_quotas.push("max_storage_bytes".to_string());
873 }
874 }
875
876 let is_exceeded = !exceeded_quotas.is_empty();
877
878 Self {
879 namespace,
880 config,
881 usage,
882 vector_usage_percent,
883 storage_usage_percent,
884 is_exceeded,
885 exceeded_quotas,
886 }
887 }
888}
889
890#[derive(Debug, Deserialize)]
892pub struct SetQuotaRequest {
893 pub config: QuotaConfig,
895}
896
897#[derive(Debug, Serialize)]
899pub struct SetQuotaResponse {
900 pub success: bool,
902 pub namespace: String,
904 pub config: QuotaConfig,
906 pub message: String,
908}
909
910#[derive(Debug, Clone, Serialize)]
912pub struct QuotaCheckResult {
913 pub allowed: bool,
915 #[serde(skip_serializing_if = "Option::is_none")]
917 pub reason: Option<String>,
918 pub usage: QuotaUsage,
920 #[serde(skip_serializing_if = "Option::is_none")]
922 pub exceeded_quota: Option<String>,
923}
924
925#[derive(Debug, Serialize)]
927pub struct QuotaListResponse {
928 pub quotas: Vec<QuotaStatus>,
930 pub total: u64,
932 #[serde(skip_serializing_if = "Option::is_none")]
934 pub default_config: Option<QuotaConfig>,
935}
936
937#[derive(Debug, Serialize)]
939pub struct DefaultQuotaResponse {
940 pub config: Option<QuotaConfig>,
942}
943
944#[derive(Debug, Deserialize)]
946pub struct SetDefaultQuotaRequest {
947 pub config: Option<QuotaConfig>,
949}
950
951#[derive(Debug, Deserialize)]
953pub struct QuotaCheckRequest {
954 pub vector_ids: Vec<String>,
956 #[serde(default)]
958 pub dimensions: Option<usize>,
959 #[serde(default)]
961 pub metadata_bytes: Option<usize>,
962}
963
964#[derive(Debug, Deserialize)]
970pub struct ExportRequest {
971 #[serde(default = "default_export_top_k")]
973 pub top_k: usize,
974 #[serde(skip_serializing_if = "Option::is_none")]
976 pub cursor: Option<String>,
977 #[serde(default = "default_true")]
979 pub include_vectors: bool,
980 #[serde(default = "default_true")]
982 pub include_metadata: bool,
983}
984
985fn default_export_top_k() -> usize {
986 1000
987}
988
989impl Default for ExportRequest {
990 fn default() -> Self {
991 Self {
992 top_k: 1000,
993 cursor: None,
994 include_vectors: true,
995 include_metadata: true,
996 }
997 }
998}
999
1000#[derive(Debug, Clone, Serialize, Deserialize)]
1002pub struct ExportedVector {
1003 pub id: String,
1005 #[serde(skip_serializing_if = "Option::is_none")]
1007 pub values: Option<Vec<f32>>,
1008 #[serde(skip_serializing_if = "Option::is_none")]
1010 pub metadata: Option<serde_json::Value>,
1011 #[serde(skip_serializing_if = "Option::is_none")]
1013 pub ttl_seconds: Option<u64>,
1014}
1015
1016impl From<&Vector> for ExportedVector {
1017 fn from(v: &Vector) -> Self {
1018 Self {
1019 id: v.id.clone(),
1020 values: Some(v.values.clone()),
1021 metadata: v.metadata.clone(),
1022 ttl_seconds: v.ttl_seconds,
1023 }
1024 }
1025}
1026
1027#[derive(Debug, Serialize)]
1029pub struct ExportResponse {
1030 pub vectors: Vec<ExportedVector>,
1032 #[serde(skip_serializing_if = "Option::is_none")]
1034 pub next_cursor: Option<String>,
1035 pub total_count: usize,
1037 pub returned_count: usize,
1039}
1040
1041#[derive(Debug, Clone, Serialize, Deserialize)]
1048#[serde(untagged)]
1049pub enum RankBy {
1050 VectorSearch {
1053 field: String,
1054 method: VectorSearchMethod,
1055 query_vector: Vec<f32>,
1056 },
1057 FullTextSearch {
1059 field: String,
1060 method: String, query: String,
1062 },
1063 AttributeOrder {
1065 field: String,
1066 direction: SortDirection,
1067 },
1068 Sum(Vec<RankBy>),
1070 Max(Vec<RankBy>),
1072 Product { weight: f32, ranking: Box<RankBy> },
1074}
1075
1076#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1078pub enum VectorSearchMethod {
1079 #[default]
1081 ANN,
1082 #[serde(rename = "kNN")]
1084 KNN,
1085}
1086
1087#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1089#[serde(rename_all = "lowercase")]
1090#[derive(Default)]
1091pub enum SortDirection {
1092 Asc,
1093 #[default]
1094 Desc,
1095}
1096
1097#[derive(Debug, Deserialize)]
1099pub struct UnifiedQueryRequest {
1100 pub rank_by: RankByInput,
1102 #[serde(default = "default_top_k")]
1104 pub top_k: usize,
1105 #[serde(default)]
1107 pub filter: Option<FilterExpression>,
1108 #[serde(default = "default_true")]
1110 pub include_metadata: bool,
1111 #[serde(default)]
1113 pub include_vectors: bool,
1114 #[serde(default)]
1116 pub distance_metric: DistanceMetric,
1117}
1118
1119#[derive(Debug, Clone, Serialize, Deserialize)]
1127#[serde(from = "serde_json::Value")]
1128pub struct RankByInput(pub RankBy);
1129
1130impl From<serde_json::Value> for RankByInput {
1131 fn from(value: serde_json::Value) -> Self {
1132 RankByInput(parse_rank_by(&value).unwrap_or_else(|| {
1133 RankBy::AttributeOrder {
1135 field: "id".to_string(),
1136 direction: SortDirection::Asc,
1137 }
1138 }))
1139 }
1140}
1141
1142fn parse_rank_by(value: &serde_json::Value) -> Option<RankBy> {
1144 let arr = value.as_array()?;
1145 if arr.is_empty() {
1146 return None;
1147 }
1148
1149 let first = arr.first()?.as_str()?;
1150
1151 match first {
1152 "Sum" => {
1154 let rankings = arr.get(1)?.as_array()?;
1155 let parsed: Option<Vec<RankBy>> = rankings.iter().map(parse_rank_by).collect();
1156 Some(RankBy::Sum(parsed?))
1157 }
1158 "Max" => {
1159 let rankings = arr.get(1)?.as_array()?;
1160 let parsed: Option<Vec<RankBy>> = rankings.iter().map(parse_rank_by).collect();
1161 Some(RankBy::Max(parsed?))
1162 }
1163 "Product" => {
1164 let weight = arr.get(1)?.as_f64()? as f32;
1165 let ranking = parse_rank_by(arr.get(2)?)?;
1166 Some(RankBy::Product {
1167 weight,
1168 ranking: Box::new(ranking),
1169 })
1170 }
1171 "ANN" => {
1173 let query_vector = parse_vector_array(arr.get(1)?)?;
1174 Some(RankBy::VectorSearch {
1175 field: "vector".to_string(),
1176 method: VectorSearchMethod::ANN,
1177 query_vector,
1178 })
1179 }
1180 "kNN" => {
1181 let query_vector = parse_vector_array(arr.get(1)?)?;
1182 Some(RankBy::VectorSearch {
1183 field: "vector".to_string(),
1184 method: VectorSearchMethod::KNN,
1185 query_vector,
1186 })
1187 }
1188 field => {
1190 let second = arr.get(1)?;
1191
1192 if let Some(method_str) = second.as_str() {
1194 match method_str {
1195 "ANN" => {
1196 let query_vector = parse_vector_array(arr.get(2)?)?;
1197 Some(RankBy::VectorSearch {
1198 field: field.to_string(),
1199 method: VectorSearchMethod::ANN,
1200 query_vector,
1201 })
1202 }
1203 "kNN" => {
1204 let query_vector = parse_vector_array(arr.get(2)?)?;
1205 Some(RankBy::VectorSearch {
1206 field: field.to_string(),
1207 method: VectorSearchMethod::KNN,
1208 query_vector,
1209 })
1210 }
1211 "BM25" => {
1212 let query = arr.get(2)?.as_str()?;
1213 Some(RankBy::FullTextSearch {
1214 field: field.to_string(),
1215 method: "BM25".to_string(),
1216 query: query.to_string(),
1217 })
1218 }
1219 "asc" => Some(RankBy::AttributeOrder {
1220 field: field.to_string(),
1221 direction: SortDirection::Asc,
1222 }),
1223 "desc" => Some(RankBy::AttributeOrder {
1224 field: field.to_string(),
1225 direction: SortDirection::Desc,
1226 }),
1227 _ => None,
1228 }
1229 } else {
1230 None
1231 }
1232 }
1233 }
1234}
1235
1236fn parse_vector_array(value: &serde_json::Value) -> Option<Vec<f32>> {
1238 let arr = value.as_array()?;
1239 arr.iter().map(|v| v.as_f64().map(|n| n as f32)).collect()
1240}
1241
1242#[derive(Debug, Serialize, Deserialize)]
1244pub struct UnifiedQueryResponse {
1245 pub results: Vec<UnifiedSearchResult>,
1247 #[serde(skip_serializing_if = "Option::is_none")]
1249 pub next_cursor: Option<String>,
1250}
1251
1252#[derive(Debug, Serialize, Deserialize)]
1254pub struct UnifiedSearchResult {
1255 pub id: String,
1257 #[serde(rename = "$dist", skip_serializing_if = "Option::is_none")]
1260 pub dist: Option<f32>,
1261 #[serde(skip_serializing_if = "Option::is_none")]
1263 pub metadata: Option<serde_json::Value>,
1264 #[serde(skip_serializing_if = "Option::is_none")]
1266 pub vector: Option<Vec<f32>>,
1267}
1268
1269#[derive(Debug, Clone, Serialize, Deserialize)]
1275pub enum AggregateFunction {
1276 Count,
1278 Sum { field: String },
1280 Avg { field: String },
1282 Min { field: String },
1284 Max { field: String },
1286}
1287
1288#[derive(Debug, Clone, Serialize, Deserialize)]
1290#[serde(from = "serde_json::Value")]
1291pub struct AggregateFunctionInput(pub AggregateFunction);
1292
1293impl From<serde_json::Value> for AggregateFunctionInput {
1294 fn from(value: serde_json::Value) -> Self {
1295 parse_aggregate_function(&value)
1296 .map(AggregateFunctionInput)
1297 .unwrap_or_else(|| {
1298 AggregateFunctionInput(AggregateFunction::Count)
1300 })
1301 }
1302}
1303
1304fn parse_aggregate_function(value: &serde_json::Value) -> Option<AggregateFunction> {
1306 let arr = value.as_array()?;
1307 if arr.is_empty() {
1308 return None;
1309 }
1310
1311 let func_name = arr.first()?.as_str()?;
1312
1313 match func_name {
1314 "Count" => Some(AggregateFunction::Count),
1315 "Sum" => {
1316 let field = arr.get(1)?.as_str()?;
1317 Some(AggregateFunction::Sum {
1318 field: field.to_string(),
1319 })
1320 }
1321 "Avg" => {
1322 let field = arr.get(1)?.as_str()?;
1323 Some(AggregateFunction::Avg {
1324 field: field.to_string(),
1325 })
1326 }
1327 "Min" => {
1328 let field = arr.get(1)?.as_str()?;
1329 Some(AggregateFunction::Min {
1330 field: field.to_string(),
1331 })
1332 }
1333 "Max" => {
1334 let field = arr.get(1)?.as_str()?;
1335 Some(AggregateFunction::Max {
1336 field: field.to_string(),
1337 })
1338 }
1339 _ => None,
1340 }
1341}
1342
1343#[derive(Debug, Deserialize)]
1345pub struct AggregationRequest {
1346 pub aggregate_by: std::collections::HashMap<String, AggregateFunctionInput>,
1349 #[serde(default)]
1352 pub group_by: Vec<String>,
1353 #[serde(default)]
1355 pub filter: Option<FilterExpression>,
1356 #[serde(default = "default_agg_limit")]
1358 pub limit: usize,
1359}
1360
1361fn default_agg_limit() -> usize {
1362 100
1363}
1364
1365#[derive(Debug, Serialize, Deserialize)]
1367pub struct AggregationResponse {
1368 #[serde(skip_serializing_if = "Option::is_none")]
1370 pub aggregations: Option<std::collections::HashMap<String, serde_json::Value>>,
1371 #[serde(skip_serializing_if = "Option::is_none")]
1373 pub aggregation_groups: Option<Vec<AggregationGroup>>,
1374}
1375
1376#[derive(Debug, Serialize, Deserialize)]
1378pub struct AggregationGroup {
1379 #[serde(flatten)]
1381 pub group_key: std::collections::HashMap<String, serde_json::Value>,
1382 #[serde(flatten)]
1384 pub aggregations: std::collections::HashMap<String, serde_json::Value>,
1385}
1386
1387#[derive(Debug, Clone, Serialize, Deserialize)]
1393pub struct TextDocument {
1394 pub id: VectorId,
1396 pub text: String,
1398 #[serde(skip_serializing_if = "Option::is_none")]
1400 pub metadata: Option<serde_json::Value>,
1401 #[serde(skip_serializing_if = "Option::is_none")]
1403 pub ttl_seconds: Option<u64>,
1404}
1405
1406#[derive(Debug, Deserialize)]
1408pub struct TextUpsertRequest {
1409 pub documents: Vec<TextDocument>,
1411 #[serde(default)]
1413 pub model: Option<EmbeddingModelType>,
1414}
1415
1416#[derive(Debug, Serialize, Deserialize)]
1418pub struct TextUpsertResponse {
1419 pub upserted_count: usize,
1421 pub tokens_processed: usize,
1423 pub model: EmbeddingModelType,
1425 pub embedding_time_ms: u64,
1427}
1428
1429#[derive(Debug, Deserialize)]
1431pub struct TextQueryRequest {
1432 pub text: String,
1434 #[serde(default = "default_top_k")]
1436 pub top_k: usize,
1437 #[serde(default)]
1439 pub filter: Option<FilterExpression>,
1440 #[serde(default)]
1442 pub include_vectors: bool,
1443 #[serde(default = "default_true")]
1445 pub include_text: bool,
1446 #[serde(default)]
1448 pub model: Option<EmbeddingModelType>,
1449}
1450
1451#[derive(Debug, Serialize, Deserialize)]
1453pub struct TextQueryResponse {
1454 pub results: Vec<TextSearchResult>,
1456 pub model: EmbeddingModelType,
1458 pub embedding_time_ms: u64,
1460 pub search_time_ms: u64,
1462}
1463
1464#[derive(Debug, Serialize, Deserialize)]
1466pub struct TextSearchResult {
1467 pub id: VectorId,
1469 pub score: f32,
1471 #[serde(skip_serializing_if = "Option::is_none")]
1473 pub text: Option<String>,
1474 #[serde(skip_serializing_if = "Option::is_none")]
1476 pub metadata: Option<serde_json::Value>,
1477 #[serde(skip_serializing_if = "Option::is_none")]
1479 pub vector: Option<Vec<f32>>,
1480}
1481
1482#[derive(Debug, Deserialize)]
1484pub struct BatchTextQueryRequest {
1485 pub queries: Vec<String>,
1487 #[serde(default = "default_top_k")]
1489 pub top_k: usize,
1490 #[serde(default)]
1492 pub filter: Option<FilterExpression>,
1493 #[serde(default)]
1495 pub include_vectors: bool,
1496 #[serde(default)]
1498 pub model: Option<EmbeddingModelType>,
1499}
1500
1501#[derive(Debug, Serialize, Deserialize)]
1503pub struct BatchTextQueryResponse {
1504 pub results: Vec<Vec<TextSearchResult>>,
1506 pub model: EmbeddingModelType,
1508 pub embedding_time_ms: u64,
1510 pub search_time_ms: u64,
1512}
1513
1514#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1522pub enum EmbeddingModelType {
1523 #[default]
1525 #[serde(rename = "bge-large")]
1526 BgeLarge,
1527 #[serde(rename = "minilm")]
1529 MiniLM,
1530 #[serde(rename = "bge-small")]
1532 BgeSmall,
1533 #[serde(rename = "e5-small")]
1535 E5Small,
1536 #[serde(rename = "modernbert-embed-base")]
1538 ModernBertEmbedBase,
1539 #[serde(rename = "gte-modernbert-base")]
1541 GteModernBertBase,
1542}
1543
1544impl EmbeddingModelType {
1545 pub fn dimension(&self) -> usize {
1547 match self {
1548 EmbeddingModelType::BgeLarge => 1024,
1549 EmbeddingModelType::MiniLM => 384,
1550 EmbeddingModelType::BgeSmall => 384,
1551 EmbeddingModelType::E5Small => 384,
1552 EmbeddingModelType::ModernBertEmbedBase => 768,
1553 EmbeddingModelType::GteModernBertBase => 768,
1554 }
1555 }
1556}
1557
1558impl std::fmt::Display for EmbeddingModelType {
1559 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1560 match self {
1561 EmbeddingModelType::BgeLarge => write!(f, "bge-large"),
1562 EmbeddingModelType::MiniLM => write!(f, "minilm"),
1563 EmbeddingModelType::BgeSmall => write!(f, "bge-small"),
1564 EmbeddingModelType::E5Small => write!(f, "e5-small"),
1565 EmbeddingModelType::ModernBertEmbedBase => write!(f, "modernbert-embed-base"),
1566 EmbeddingModelType::GteModernBertBase => write!(f, "gte-modernbert-base"),
1567 }
1568 }
1569}
1570
1571#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1577#[serde(rename_all = "snake_case")]
1578#[derive(Default)]
1579pub enum MemoryType {
1580 #[default]
1582 Episodic,
1583 Semantic,
1585 Procedural,
1587 Working,
1589}
1590
1591impl std::fmt::Display for MemoryType {
1592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1593 match self {
1594 MemoryType::Episodic => write!(f, "episodic"),
1595 MemoryType::Semantic => write!(f, "semantic"),
1596 MemoryType::Procedural => write!(f, "procedural"),
1597 MemoryType::Working => write!(f, "working"),
1598 }
1599 }
1600}
1601
1602#[derive(Debug, Clone, Serialize, Deserialize)]
1604pub struct Memory {
1605 pub id: String,
1606 #[serde(default)]
1607 pub memory_type: MemoryType,
1608 pub content: String,
1609 pub agent_id: String,
1610 #[serde(skip_serializing_if = "Option::is_none")]
1611 pub session_id: Option<String>,
1612 #[serde(default = "default_importance")]
1613 pub importance: f32,
1614 #[serde(default)]
1615 pub tags: Vec<String>,
1616 #[serde(skip_serializing_if = "Option::is_none")]
1617 pub metadata: Option<serde_json::Value>,
1618 pub created_at: u64,
1619 pub last_accessed_at: u64,
1620 #[serde(default)]
1621 pub access_count: u32,
1622 #[serde(skip_serializing_if = "Option::is_none")]
1623 pub ttl_seconds: Option<u64>,
1624 #[serde(skip_serializing_if = "Option::is_none")]
1625 pub expires_at: Option<u64>,
1626}
1627
1628fn default_importance() -> f32 {
1629 0.5
1630}
1631
1632impl Memory {
1633 pub fn new(id: String, content: String, agent_id: String, memory_type: MemoryType) -> Self {
1635 let now = std::time::SystemTime::now()
1636 .duration_since(std::time::UNIX_EPOCH)
1637 .unwrap_or_default()
1638 .as_secs();
1639 Self {
1640 id,
1641 memory_type,
1642 content,
1643 agent_id,
1644 session_id: None,
1645 importance: 0.5,
1646 tags: Vec::new(),
1647 metadata: None,
1648 created_at: now,
1649 last_accessed_at: now,
1650 access_count: 0,
1651 ttl_seconds: None,
1652 expires_at: None,
1653 }
1654 }
1655
1656 pub fn is_expired(&self) -> bool {
1658 if let Some(expires_at) = self.expires_at {
1659 let now = std::time::SystemTime::now()
1660 .duration_since(std::time::UNIX_EPOCH)
1661 .unwrap_or_default()
1662 .as_secs();
1663 now >= expires_at
1664 } else {
1665 false
1666 }
1667 }
1668
1669 pub fn to_vector_metadata(&self) -> serde_json::Value {
1671 let mut meta = serde_json::Map::new();
1672 meta.insert("_dakera_type".to_string(), serde_json::json!("memory"));
1673 meta.insert(
1674 "memory_type".to_string(),
1675 serde_json::json!(self.memory_type),
1676 );
1677 meta.insert("content".to_string(), serde_json::json!(self.content));
1678 meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1679 if let Some(ref sid) = self.session_id {
1680 meta.insert("session_id".to_string(), serde_json::json!(sid));
1681 }
1682 meta.insert("importance".to_string(), serde_json::json!(self.importance));
1683 meta.insert("tags".to_string(), serde_json::json!(self.tags));
1684 meta.insert("created_at".to_string(), serde_json::json!(self.created_at));
1685 meta.insert(
1686 "last_accessed_at".to_string(),
1687 serde_json::json!(self.last_accessed_at),
1688 );
1689 meta.insert(
1690 "access_count".to_string(),
1691 serde_json::json!(self.access_count),
1692 );
1693 if let Some(ref ttl) = self.ttl_seconds {
1694 meta.insert("ttl_seconds".to_string(), serde_json::json!(ttl));
1695 }
1696 if let Some(ref expires) = self.expires_at {
1697 meta.insert("expires_at".to_string(), serde_json::json!(expires));
1698 }
1699 if let Some(ref user_meta) = self.metadata {
1700 meta.insert("user_metadata".to_string(), user_meta.clone());
1701 }
1702 serde_json::Value::Object(meta)
1703 }
1704
1705 pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1707 let mut v = Vector {
1708 id: self.id.clone(),
1709 values: embedding,
1710 metadata: Some(self.to_vector_metadata()),
1711 ttl_seconds: self.ttl_seconds,
1712 expires_at: self.expires_at,
1713 };
1714 v.apply_ttl();
1715 v
1716 }
1717
1718 pub fn from_vector(vector: &Vector) -> Option<Self> {
1720 let meta = vector.metadata.as_ref()?.as_object()?;
1721 let entry_type = meta.get("_dakera_type")?.as_str()?;
1722 if entry_type != "memory" {
1723 return None;
1724 }
1725
1726 Some(Memory {
1727 id: vector.id.clone(),
1728 memory_type: serde_json::from_value(meta.get("memory_type")?.clone())
1729 .unwrap_or_default(),
1730 content: meta.get("content")?.as_str()?.to_string(),
1731 agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1732 session_id: meta
1733 .get("session_id")
1734 .and_then(|v| v.as_str())
1735 .map(String::from),
1736 importance: meta
1737 .get("importance")
1738 .and_then(|v| v.as_f64())
1739 .unwrap_or(0.5) as f32,
1740 tags: meta
1741 .get("tags")
1742 .and_then(|v| serde_json::from_value(v.clone()).ok())
1743 .unwrap_or_default(),
1744 metadata: meta.get("user_metadata").cloned(),
1745 created_at: meta.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0),
1746 last_accessed_at: meta
1747 .get("last_accessed_at")
1748 .and_then(|v| v.as_u64())
1749 .unwrap_or(0),
1750 access_count: meta
1751 .get("access_count")
1752 .and_then(|v| v.as_u64())
1753 .unwrap_or(0) as u32,
1754 ttl_seconds: vector.ttl_seconds,
1755 expires_at: vector.expires_at,
1756 })
1757 }
1758}
1759
1760#[derive(Debug, Clone, Serialize, Deserialize)]
1762pub struct Session {
1763 pub id: String,
1764 pub agent_id: String,
1765 pub started_at: u64,
1766 #[serde(skip_serializing_if = "Option::is_none")]
1767 pub ended_at: Option<u64>,
1768 #[serde(skip_serializing_if = "Option::is_none")]
1769 pub summary: Option<String>,
1770 #[serde(skip_serializing_if = "Option::is_none")]
1771 pub metadata: Option<serde_json::Value>,
1772 #[serde(default)]
1774 pub memory_count: usize,
1775}
1776
1777impl Session {
1778 pub fn new(id: String, agent_id: String) -> Self {
1779 let now = std::time::SystemTime::now()
1780 .duration_since(std::time::UNIX_EPOCH)
1781 .unwrap_or_default()
1782 .as_secs();
1783 Self {
1784 id,
1785 agent_id,
1786 started_at: now,
1787 ended_at: None,
1788 summary: None,
1789 metadata: None,
1790 memory_count: 0,
1791 }
1792 }
1793
1794 pub fn to_vector_metadata(&self) -> serde_json::Value {
1796 let mut meta = serde_json::Map::new();
1797 meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
1798 meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1799 meta.insert("started_at".to_string(), serde_json::json!(self.started_at));
1800 if let Some(ref ended) = self.ended_at {
1801 meta.insert("ended_at".to_string(), serde_json::json!(ended));
1802 }
1803 if let Some(ref summary) = self.summary {
1804 meta.insert("summary".to_string(), serde_json::json!(summary));
1805 }
1806 if let Some(ref user_meta) = self.metadata {
1807 meta.insert("user_metadata".to_string(), user_meta.clone());
1808 }
1809 meta.insert(
1810 "memory_count".to_string(),
1811 serde_json::json!(self.memory_count),
1812 );
1813 serde_json::Value::Object(meta)
1814 }
1815
1816 pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1818 Vector {
1819 id: self.id.clone(),
1820 values: embedding,
1821 metadata: Some(self.to_vector_metadata()),
1822 ttl_seconds: None,
1823 expires_at: None,
1824 }
1825 }
1826
1827 pub fn from_vector(vector: &Vector) -> Option<Self> {
1829 let meta = vector.metadata.as_ref()?.as_object()?;
1830 let entry_type = meta.get("_dakera_type")?.as_str()?;
1831 if entry_type != "session" {
1832 return None;
1833 }
1834
1835 Some(Session {
1836 id: vector.id.clone(),
1837 agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1838 started_at: meta.get("started_at").and_then(|v| v.as_u64()).unwrap_or(0),
1839 ended_at: meta.get("ended_at").and_then(|v| v.as_u64()),
1840 summary: meta
1841 .get("summary")
1842 .and_then(|v| v.as_str())
1843 .map(String::from),
1844 metadata: meta.get("user_metadata").cloned(),
1845 memory_count: meta
1846 .get("memory_count")
1847 .and_then(|v| v.as_u64())
1848 .unwrap_or(0) as usize,
1849 })
1850 }
1851}
1852
1853#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1855#[serde(rename_all = "snake_case")]
1856#[derive(Default)]
1857pub enum DecayStrategy {
1858 #[default]
1859 Exponential,
1860 Linear,
1861 StepFunction,
1862 PowerLaw,
1864 Logarithmic,
1866 Flat,
1868}
1869
1870#[derive(Debug, Clone, Serialize, Deserialize)]
1872pub struct DecayConfig {
1873 #[serde(default)]
1874 pub strategy: DecayStrategy,
1875 #[serde(default = "default_half_life_hours")]
1876 pub half_life_hours: f64,
1877 #[serde(default = "default_min_importance")]
1878 pub min_importance: f32,
1879}
1880
1881fn default_half_life_hours() -> f64 {
1882 168.0 }
1884
1885fn default_min_importance() -> f32 {
1886 0.01
1887}
1888
1889impl Default for DecayConfig {
1890 fn default() -> Self {
1891 Self {
1892 strategy: DecayStrategy::default(),
1893 half_life_hours: default_half_life_hours(),
1894 min_importance: default_min_importance(),
1895 }
1896 }
1897}
1898
1899#[derive(Debug, Deserialize)]
1905pub struct StoreMemoryRequest {
1906 pub content: String,
1907 pub agent_id: String,
1908 #[serde(default)]
1909 pub memory_type: MemoryType,
1910 #[serde(skip_serializing_if = "Option::is_none")]
1911 pub session_id: Option<String>,
1912 #[serde(default = "default_importance")]
1913 pub importance: f32,
1914 #[serde(default)]
1915 pub tags: Vec<String>,
1916 #[serde(skip_serializing_if = "Option::is_none")]
1917 pub metadata: Option<serde_json::Value>,
1918 #[serde(skip_serializing_if = "Option::is_none")]
1919 pub ttl_seconds: Option<u64>,
1920 #[serde(skip_serializing_if = "Option::is_none")]
1925 pub expires_at: Option<u64>,
1926 #[serde(skip_serializing_if = "Option::is_none")]
1928 pub id: Option<String>,
1929}
1930
1931#[derive(Debug, Serialize)]
1933pub struct StoreMemoryResponse {
1934 pub memory: Memory,
1935 pub embedding_time_ms: u64,
1936}
1937
1938#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1942#[serde(rename_all = "lowercase")]
1943pub enum RoutingMode {
1944 #[default]
1946 Auto,
1947 Vector,
1949 Bm25,
1951 Hybrid,
1953}
1954
1955#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1960#[serde(rename_all = "lowercase")]
1961pub enum FusionStrategy {
1962 Rrf,
1967 #[default]
1970 MinMax,
1971}
1972
1973#[derive(Debug, Clone, Deserialize)]
1975pub struct RecallRequest {
1976 pub query: String,
1977 pub agent_id: String,
1978 #[serde(default = "default_top_k")]
1979 pub top_k: usize,
1980 #[serde(default)]
1981 pub memory_type: Option<MemoryType>,
1982 #[serde(default)]
1983 pub session_id: Option<String>,
1984 #[serde(default)]
1985 pub tags: Option<Vec<String>>,
1986 #[serde(default)]
1987 pub min_importance: Option<f32>,
1988 #[serde(default = "default_true")]
1990 pub importance_weighted: bool,
1991 #[serde(default)]
1993 pub include_associated: bool,
1994 #[serde(default)]
1996 pub associated_memories_cap: Option<usize>,
1997 #[serde(default)]
1999 pub since: Option<String>,
2000 #[serde(default)]
2002 pub until: Option<String>,
2003 #[serde(default)]
2006 pub associated_memories_depth: Option<u8>,
2007 #[serde(default)]
2010 pub associated_memories_min_weight: Option<f32>,
2011 #[serde(default)]
2015 pub routing: RoutingMode,
2016 #[serde(default = "default_true")]
2020 pub rerank: bool,
2021 #[serde(default)]
2024 pub fusion: FusionStrategy,
2025 #[serde(default)]
2029 pub vector_weight: Option<f32>,
2030 #[serde(default)]
2037 pub iterations: Option<u8>,
2038 #[serde(default = "default_true")]
2042 pub neighborhood: bool,
2043}
2044
2045#[derive(Debug, Serialize, Deserialize)]
2047pub struct RecallResult {
2048 pub memory: Memory,
2049 pub score: f32,
2050 #[serde(skip_serializing_if = "Option::is_none")]
2052 pub weighted_score: Option<f32>,
2053 #[serde(skip_serializing_if = "Option::is_none")]
2055 pub smart_score: Option<f32>,
2056 #[serde(skip_serializing_if = "Option::is_none")]
2059 pub depth: Option<u8>,
2060}
2061
2062#[derive(Debug, Serialize)]
2064pub struct RecallResponse {
2065 pub memories: Vec<RecallResult>,
2066 pub query_embedding_time_ms: u64,
2067 pub search_time_ms: u64,
2068 #[serde(skip_serializing_if = "Option::is_none")]
2071 pub associated_memories: Option<Vec<RecallResult>>,
2072}
2073
2074#[derive(Debug, Deserialize)]
2076pub struct ForgetRequest {
2077 pub agent_id: String,
2078 #[serde(default)]
2079 pub memory_ids: Option<Vec<String>>,
2080 #[serde(default)]
2081 pub memory_type: Option<MemoryType>,
2082 #[serde(default)]
2083 pub session_id: Option<String>,
2084 #[serde(default)]
2085 pub tags: Option<Vec<String>>,
2086 #[serde(default)]
2088 pub below_importance: Option<f32>,
2089}
2090
2091#[derive(Debug, Serialize)]
2093pub struct ForgetResponse {
2094 pub deleted_count: usize,
2095}
2096
2097#[derive(Debug, Deserialize)]
2099pub struct UpdateMemoryRequest {
2100 #[serde(default)]
2101 pub content: Option<String>,
2102 #[serde(default)]
2103 pub importance: Option<f32>,
2104 #[serde(default)]
2105 pub tags: Option<Vec<String>>,
2106 #[serde(default)]
2107 pub metadata: Option<serde_json::Value>,
2108 #[serde(default)]
2109 pub memory_type: Option<MemoryType>,
2110}
2111
2112#[derive(Debug, Deserialize)]
2114pub struct UpdateImportanceRequest {
2115 pub memory_id: String,
2116 pub importance: f32,
2117 pub agent_id: String,
2118}
2119
2120#[derive(Debug, Deserialize)]
2122pub struct ConsolidateRequest {
2123 pub agent_id: String,
2124 #[serde(default)]
2126 pub memory_ids: Option<Vec<String>>,
2127 #[serde(default = "default_consolidation_threshold")]
2129 pub threshold: f32,
2130 #[serde(default)]
2132 pub target_type: Option<MemoryType>,
2133}
2134
2135fn default_consolidation_threshold() -> f32 {
2136 0.85
2137}
2138
2139#[derive(Debug, Serialize)]
2141pub struct ConsolidateResponse {
2142 pub consolidated_memory: Memory,
2143 pub source_memory_ids: Vec<String>,
2144 pub memories_removed: usize,
2145}
2146
2147#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2149#[serde(rename_all = "lowercase")]
2150pub enum FeedbackSignal {
2151 Upvote,
2153 Downvote,
2155 Flag,
2157 Positive,
2159 Negative,
2161}
2162
2163#[derive(Debug, Clone, Serialize, Deserialize)]
2165pub struct FeedbackHistoryEntry {
2166 pub signal: FeedbackSignal,
2167 pub timestamp: u64,
2168 pub old_importance: f32,
2169 pub new_importance: f32,
2170}
2171
2172#[derive(Debug, Deserialize)]
2174pub struct FeedbackRequest {
2175 pub agent_id: String,
2176 pub memory_id: String,
2177 pub signal: FeedbackSignal,
2178}
2179
2180#[derive(Debug, Deserialize)]
2182pub struct MemoryFeedbackRequest {
2183 pub agent_id: String,
2184 pub signal: FeedbackSignal,
2185}
2186
2187#[derive(Debug, Serialize)]
2189pub struct FeedbackResponse {
2190 pub memory_id: String,
2191 pub new_importance: f32,
2192 pub signal: FeedbackSignal,
2193}
2194
2195#[derive(Debug, Serialize)]
2197pub struct FeedbackHistoryResponse {
2198 pub memory_id: String,
2199 pub entries: Vec<FeedbackHistoryEntry>,
2200}
2201
2202#[derive(Debug, Serialize)]
2204pub struct AgentFeedbackSummary {
2205 pub agent_id: String,
2206 pub upvotes: u64,
2207 pub downvotes: u64,
2208 pub flags: u64,
2209 pub total_feedback: u64,
2210 pub health_score: f32,
2212}
2213
2214#[derive(Debug, Deserialize)]
2216pub struct MemoryImportancePatchRequest {
2217 pub agent_id: String,
2218 pub importance: f32,
2219}
2220
2221#[derive(Debug, Deserialize)]
2223pub struct FeedbackHealthQuery {
2224 pub agent_id: String,
2225}
2226
2227#[derive(Debug, Serialize)]
2229pub struct FeedbackHealthResponse {
2230 pub agent_id: String,
2231 pub health_score: f32,
2233 pub memory_count: usize,
2234 pub avg_importance: f32,
2235}
2236
2237#[derive(Debug, Deserialize)]
2239pub struct SearchMemoriesRequest {
2240 pub agent_id: String,
2241 #[serde(default)]
2242 pub query: Option<String>,
2243 #[serde(default)]
2244 pub memory_type: Option<MemoryType>,
2245 #[serde(default)]
2246 pub session_id: Option<String>,
2247 #[serde(default)]
2248 pub tags: Option<Vec<String>>,
2249 #[serde(default)]
2250 pub min_importance: Option<f32>,
2251 #[serde(default)]
2252 pub max_importance: Option<f32>,
2253 #[serde(default)]
2254 pub created_after: Option<u64>,
2255 #[serde(default)]
2256 pub created_before: Option<u64>,
2257 #[serde(default = "default_top_k")]
2258 pub top_k: usize,
2259 #[serde(default)]
2260 pub sort_by: Option<MemorySortField>,
2261 #[serde(default)]
2263 pub routing: RoutingMode,
2264 #[serde(default)]
2267 pub rerank: bool,
2268}
2269
2270#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
2272#[serde(rename_all = "snake_case")]
2273pub enum MemorySortField {
2274 CreatedAt,
2275 LastAccessedAt,
2276 Importance,
2277 AccessCount,
2278}
2279
2280#[derive(Debug, Serialize)]
2282pub struct SearchMemoriesResponse {
2283 pub memories: Vec<RecallResult>,
2284 pub total_count: usize,
2285}
2286
2287#[derive(Debug, Deserialize)]
2293pub struct SessionStartRequest {
2294 pub agent_id: String,
2295 #[serde(skip_serializing_if = "Option::is_none")]
2296 pub metadata: Option<serde_json::Value>,
2297 #[serde(skip_serializing_if = "Option::is_none")]
2299 pub id: Option<String>,
2300}
2301
2302#[derive(Debug, Serialize)]
2304pub struct SessionStartResponse {
2305 pub session: Session,
2306}
2307
2308#[derive(Debug, Deserialize)]
2310pub struct SessionEndRequest {
2311 #[serde(default)]
2312 pub summary: Option<String>,
2313 #[serde(default)]
2315 pub auto_summarize: bool,
2316}
2317
2318#[derive(Debug, Serialize)]
2320pub struct SessionEndResponse {
2321 pub session: Session,
2322 pub memory_count: usize,
2323}
2324
2325#[derive(Debug, Serialize)]
2327pub struct ListSessionsResponse {
2328 pub sessions: Vec<Session>,
2329 pub total: usize,
2330}
2331
2332#[derive(Debug, Serialize)]
2334pub struct SessionMemoriesResponse {
2335 pub session: Session,
2336 pub memories: Vec<Memory>,
2337 #[serde(skip_serializing_if = "Option::is_none")]
2339 pub total: Option<usize>,
2340}
2341
2342#[derive(Debug, Serialize, Deserialize, Clone)]
2348pub struct AgentSummary {
2349 pub agent_id: String,
2350 pub memory_count: usize,
2351 pub session_count: usize,
2352 pub active_sessions: usize,
2353}
2354
2355#[derive(Debug, Serialize)]
2357pub struct AgentStats {
2358 pub agent_id: String,
2359 pub total_memories: usize,
2360 pub memories_by_type: std::collections::HashMap<String, usize>,
2361 pub total_sessions: usize,
2362 pub active_sessions: usize,
2363 pub avg_importance: f32,
2364 pub oldest_memory_at: Option<u64>,
2365 pub newest_memory_at: Option<u64>,
2366}
2367
2368#[derive(Debug, Serialize)]
2374pub struct WakeUpResponse {
2375 pub agent_id: String,
2376 pub memories: Vec<Memory>,
2378 pub total_available: usize,
2380}
2381
2382#[derive(Debug, Deserialize)]
2384pub struct KnowledgeGraphRequest {
2385 pub agent_id: String,
2386 pub memory_id: String,
2387 #[serde(default = "default_graph_depth")]
2388 pub depth: usize,
2389 #[serde(default = "default_graph_min_similarity")]
2390 pub min_similarity: f32,
2391}
2392
2393fn default_graph_depth() -> usize {
2394 2
2395}
2396
2397fn default_graph_min_similarity() -> f32 {
2398 0.7
2399}
2400
2401#[derive(Debug, Serialize)]
2403pub struct KnowledgeGraphNode {
2404 pub memory: Memory,
2405 pub similarity: f32,
2406 pub related: Vec<KnowledgeGraphEdge>,
2407}
2408
2409#[derive(Debug, Serialize)]
2411pub struct KnowledgeGraphEdge {
2412 pub memory_id: String,
2413 pub similarity: f32,
2414 pub shared_tags: Vec<String>,
2415}
2416
2417#[derive(Debug, Serialize)]
2419pub struct KnowledgeGraphResponse {
2420 pub root: KnowledgeGraphNode,
2421 pub total_nodes: usize,
2422}
2423
2424fn default_full_graph_max_nodes() -> usize {
2429 200
2430}
2431
2432fn default_full_graph_min_similarity() -> f32 {
2433 0.50
2434}
2435
2436fn default_full_graph_cluster_threshold() -> f32 {
2437 0.60
2438}
2439
2440fn default_full_graph_max_edges_per_node() -> usize {
2441 8
2442}
2443
2444#[derive(Debug, Deserialize)]
2446pub struct FullKnowledgeGraphRequest {
2447 pub agent_id: String,
2448 #[serde(default = "default_full_graph_max_nodes")]
2449 pub max_nodes: usize,
2450 #[serde(default = "default_full_graph_min_similarity")]
2451 pub min_similarity: f32,
2452 #[serde(default = "default_full_graph_cluster_threshold")]
2453 pub cluster_threshold: f32,
2454 #[serde(default = "default_full_graph_max_edges_per_node")]
2455 pub max_edges_per_node: usize,
2456}
2457
2458#[derive(Debug, Serialize)]
2460pub struct FullGraphNode {
2461 pub id: String,
2462 pub content: String,
2463 pub memory_type: String,
2464 pub importance: f32,
2465 pub tags: Vec<String>,
2466 pub created_at: Option<String>,
2467 pub cluster_id: usize,
2468 pub centrality: f32,
2469}
2470
2471#[derive(Debug, Serialize)]
2473pub struct FullGraphEdge {
2474 pub source: String,
2475 pub target: String,
2476 pub similarity: f32,
2477 pub shared_tags: Vec<String>,
2478}
2479
2480#[derive(Debug, Serialize)]
2482pub struct GraphCluster {
2483 pub id: usize,
2484 pub node_count: usize,
2485 pub top_tags: Vec<String>,
2486 pub avg_importance: f32,
2487}
2488
2489#[derive(Debug, Serialize)]
2491pub struct GraphStats {
2492 pub total_memories: usize,
2493 pub included_memories: usize,
2494 pub total_edges: usize,
2495 pub cluster_count: usize,
2496 pub density: f32,
2497 pub hub_memory_id: Option<String>,
2498}
2499
2500#[derive(Debug, Serialize)]
2502pub struct FullKnowledgeGraphResponse {
2503 pub nodes: Vec<FullGraphNode>,
2504 pub edges: Vec<FullGraphEdge>,
2505 pub clusters: Vec<GraphCluster>,
2506 pub stats: GraphStats,
2507}
2508
2509#[derive(Debug, Deserialize)]
2511pub struct SummarizeRequest {
2512 pub agent_id: String,
2513 pub memory_ids: Vec<String>,
2514 #[serde(default)]
2515 pub target_type: Option<MemoryType>,
2516}
2517
2518#[derive(Debug, Serialize)]
2520pub struct SummarizeResponse {
2521 pub summary_memory: Memory,
2522 pub source_count: usize,
2523}
2524
2525#[derive(Debug, Deserialize)]
2527pub struct DeduplicateRequest {
2528 pub agent_id: String,
2529 #[serde(default = "default_dedup_threshold")]
2530 pub threshold: f32,
2531 #[serde(default)]
2532 pub memory_type: Option<MemoryType>,
2533 #[serde(default)]
2535 pub dry_run: bool,
2536}
2537
2538fn default_dedup_threshold() -> f32 {
2539 0.92
2540}
2541
2542#[derive(Debug, Serialize)]
2544pub struct DuplicateGroup {
2545 pub canonical_id: String,
2546 pub duplicate_ids: Vec<String>,
2547 pub avg_similarity: f32,
2548}
2549
2550#[derive(Debug, Serialize)]
2552pub struct DeduplicateResponse {
2553 pub groups: Vec<DuplicateGroup>,
2554 pub duplicates_found: usize,
2555 pub duplicates_merged: usize,
2556}
2557
2558fn default_cross_agent_min_similarity() -> f32 {
2563 0.3
2564}
2565
2566fn default_cross_agent_max_nodes_per_agent() -> usize {
2567 50
2568}
2569
2570fn default_cross_agent_max_cross_edges() -> usize {
2571 200
2572}
2573
2574#[derive(Debug, Deserialize)]
2576pub struct CrossAgentNetworkRequest {
2577 #[serde(default)]
2579 pub agent_ids: Option<Vec<String>>,
2580 #[serde(default = "default_cross_agent_min_similarity")]
2582 pub min_similarity: f32,
2583 #[serde(default = "default_cross_agent_max_nodes_per_agent")]
2585 pub max_nodes_per_agent: usize,
2586 #[serde(default)]
2588 pub min_importance: f32,
2589 #[serde(default = "default_cross_agent_max_cross_edges")]
2591 pub max_cross_edges: usize,
2592}
2593
2594#[derive(Debug, Serialize)]
2596pub struct AgentNetworkInfo {
2597 pub agent_id: String,
2598 pub memory_count: usize,
2599 pub avg_importance: f32,
2600}
2601
2602#[derive(Debug, Serialize)]
2604pub struct AgentNetworkNode {
2605 pub id: String,
2606 pub agent_id: String,
2607 pub content: String,
2608 pub importance: f32,
2609 pub tags: Vec<String>,
2610 pub memory_type: String,
2611 pub created_at: u64,
2612}
2613
2614#[derive(Debug, Serialize)]
2616pub struct AgentNetworkEdge {
2617 pub source: String,
2618 pub target: String,
2619 pub source_agent: String,
2620 pub target_agent: String,
2621 pub similarity: f32,
2622}
2623
2624#[derive(Debug, Serialize)]
2626pub struct AgentNetworkStats {
2627 pub total_agents: usize,
2628 pub total_nodes: usize,
2629 pub total_cross_edges: usize,
2630 pub density: f32,
2631}
2632
2633#[derive(Debug, Serialize)]
2635pub struct CrossAgentNetworkResponse {
2636 pub node_count: usize,
2637 pub agents: Vec<AgentNetworkInfo>,
2638 pub nodes: Vec<AgentNetworkNode>,
2639 pub edges: Vec<AgentNetworkEdge>,
2640 pub stats: AgentNetworkStats,
2641}
2642
2643#[derive(Debug, Deserialize, Default)]
2651pub struct BatchMemoryFilter {
2652 #[serde(default)]
2654 pub tags: Option<Vec<String>>,
2655 #[serde(default)]
2657 pub min_importance: Option<f32>,
2658 #[serde(default)]
2660 pub max_importance: Option<f32>,
2661 #[serde(default)]
2663 pub created_after: Option<u64>,
2664 #[serde(default)]
2666 pub created_before: Option<u64>,
2667 #[serde(default)]
2669 pub memory_type: Option<MemoryType>,
2670 #[serde(default)]
2672 pub session_id: Option<String>,
2673}
2674
2675impl BatchMemoryFilter {
2676 pub fn has_any(&self) -> bool {
2678 self.tags.is_some()
2679 || self.min_importance.is_some()
2680 || self.max_importance.is_some()
2681 || self.created_after.is_some()
2682 || self.created_before.is_some()
2683 || self.memory_type.is_some()
2684 || self.session_id.is_some()
2685 }
2686
2687 pub fn matches(&self, memory: &Memory) -> bool {
2689 if let Some(ref tags) = self.tags {
2690 if !tags.is_empty() && !tags.iter().all(|t| memory.tags.contains(t)) {
2691 return false;
2692 }
2693 }
2694 if let Some(min) = self.min_importance {
2695 if memory.importance < min {
2696 return false;
2697 }
2698 }
2699 if let Some(max) = self.max_importance {
2700 if memory.importance > max {
2701 return false;
2702 }
2703 }
2704 if let Some(after) = self.created_after {
2705 if memory.created_at < after {
2706 return false;
2707 }
2708 }
2709 if let Some(before) = self.created_before {
2710 if memory.created_at > before {
2711 return false;
2712 }
2713 }
2714 if let Some(ref mt) = self.memory_type {
2715 if memory.memory_type != *mt {
2716 return false;
2717 }
2718 }
2719 if let Some(ref sid) = self.session_id {
2720 if memory.session_id.as_ref() != Some(sid) {
2721 return false;
2722 }
2723 }
2724 true
2725 }
2726}
2727
2728#[derive(Debug, Deserialize)]
2730pub struct BatchRecallRequest {
2731 pub agent_id: String,
2733 #[serde(default)]
2735 pub filter: BatchMemoryFilter,
2736 #[serde(default = "default_batch_limit")]
2738 pub limit: usize,
2739}
2740
2741fn default_batch_limit() -> usize {
2742 100
2743}
2744
2745#[derive(Debug, Serialize)]
2747pub struct BatchRecallResponse {
2748 pub memories: Vec<Memory>,
2749 pub total: usize,
2750 pub filtered: usize,
2751}
2752
2753#[derive(Debug, Deserialize)]
2755pub struct BatchForgetRequest {
2756 pub agent_id: String,
2758 pub filter: BatchMemoryFilter,
2760}
2761
2762#[derive(Debug, Serialize)]
2764pub struct BatchForgetResponse {
2765 pub deleted_count: usize,
2766}
2767
2768#[derive(Debug, Deserialize)]
2776pub struct BatchStoreMemoryItem {
2777 pub content: String,
2778 #[serde(default)]
2779 pub memory_type: MemoryType,
2780 #[serde(skip_serializing_if = "Option::is_none")]
2781 pub session_id: Option<String>,
2782 #[serde(default = "default_importance")]
2783 pub importance: f32,
2784 #[serde(default)]
2785 pub tags: Vec<String>,
2786 #[serde(skip_serializing_if = "Option::is_none")]
2787 pub metadata: Option<serde_json::Value>,
2788 #[serde(skip_serializing_if = "Option::is_none")]
2789 pub ttl_seconds: Option<u64>,
2790 #[serde(skip_serializing_if = "Option::is_none")]
2791 pub expires_at: Option<u64>,
2792 #[serde(skip_serializing_if = "Option::is_none")]
2794 pub id: Option<String>,
2795}
2796
2797#[derive(Debug, Deserialize)]
2804pub struct BatchStoreMemoryRequest {
2805 pub agent_id: String,
2806 pub memories: Vec<BatchStoreMemoryItem>,
2807}
2808
2809#[derive(Debug, Serialize)]
2811pub struct BatchStoreMemoryResponse {
2812 pub stored: Vec<Memory>,
2813 pub stored_count: usize,
2814 pub total_embedding_time_ms: u64,
2815}
2816
2817#[derive(Debug, Deserialize)]
2824pub struct NamespaceEntityConfigRequest {
2825 pub extract_entities: bool,
2827 #[serde(default)]
2830 pub entity_types: Vec<String>,
2831}
2832
2833#[derive(Debug, Serialize, Deserialize)]
2835pub struct NamespaceEntityConfigResponse {
2836 pub namespace: String,
2837 pub extract_entities: bool,
2838 pub entity_types: Vec<String>,
2839}
2840
2841#[derive(Debug, Deserialize)]
2844pub struct ExtractEntitiesRequest {
2845 pub content: String,
2847 #[serde(default)]
2850 pub entity_types: Vec<String>,
2851}
2852
2853#[derive(Debug, Clone, Serialize, Deserialize)]
2855pub struct EntityResult {
2856 pub entity_type: String,
2857 pub value: String,
2858 pub score: f32,
2859 pub start: usize,
2860 pub end: usize,
2861 pub tag: String,
2863}
2864
2865#[derive(Debug, Serialize)]
2867pub struct ExtractEntitiesResponse {
2868 pub entities: Vec<EntityResult>,
2869 pub count: usize,
2870}
2871
2872#[derive(Debug, Deserialize)]
2878pub struct GraphTraverseQuery {
2879 #[serde(default = "default_ce5_graph_depth")]
2881 pub depth: u32,
2882}
2883
2884fn default_ce5_graph_depth() -> u32 {
2885 3
2886}
2887
2888#[derive(Debug, Deserialize)]
2890pub struct GraphPathQuery {
2891 pub to: String,
2893}
2894
2895#[derive(Debug, Deserialize)]
2897pub struct MemoryLinkRequest {
2898 pub target_id: String,
2900 #[serde(skip_serializing_if = "Option::is_none")]
2902 pub label: Option<String>,
2903 pub agent_id: String,
2905}
2906
2907#[derive(Debug, Serialize)]
2909pub struct GraphTraverseResponse {
2910 pub root_id: String,
2911 pub depth: u32,
2912 pub node_count: usize,
2913 pub nodes: Vec<GraphNodeResponse>,
2914}
2915
2916#[derive(Debug, Serialize)]
2918pub struct GraphNodeResponse {
2919 pub memory_id: String,
2920 pub depth: u32,
2921 pub edges: Vec<GraphEdgeResponse>,
2922}
2923
2924#[derive(Debug, Serialize)]
2926pub struct GraphEdgeResponse {
2927 pub from_id: String,
2928 pub to_id: String,
2929 pub edge_type: String,
2930 pub weight: f32,
2931 pub created_at: u64,
2932}
2933
2934#[derive(Debug, Serialize)]
2936pub struct GraphPathResponse {
2937 pub from_id: String,
2938 pub to_id: String,
2939 pub path: Vec<String>,
2941 pub hop_count: usize,
2942}
2943
2944#[derive(Debug, Serialize)]
2946pub struct MemoryLinkResponse {
2947 pub from_id: String,
2948 pub to_id: String,
2949 pub edge_type: String,
2950}
2951
2952#[derive(Debug, Serialize)]
2954pub struct GraphExportResponse {
2955 pub agent_id: String,
2956 pub namespace: String,
2957 pub node_count: usize,
2958 pub edge_count: usize,
2959 pub edges: Vec<GraphEdgeResponse>,
2960}
2961
2962#[derive(Debug, Deserialize)]
2968pub struct KgQueryParams {
2969 pub agent_id: String,
2971 #[serde(default)]
2973 pub root_id: Option<String>,
2974 #[serde(default)]
2976 pub edge_type: Option<String>,
2977 #[serde(default)]
2979 pub min_weight: Option<f32>,
2980 #[serde(default = "default_kg_depth")]
2982 pub max_depth: u32,
2983 #[serde(default = "default_kg_limit")]
2985 pub limit: usize,
2986}
2987
2988fn default_kg_depth() -> u32 {
2989 3
2990}
2991
2992fn default_kg_limit() -> usize {
2993 100
2994}
2995
2996#[derive(Debug, Serialize)]
2998pub struct KgQueryResponse {
2999 pub agent_id: String,
3000 pub node_count: usize,
3001 pub edge_count: usize,
3002 pub edges: Vec<GraphEdgeResponse>,
3003}
3004
3005#[derive(Debug, Deserialize)]
3007pub struct KgPathParams {
3008 pub agent_id: String,
3010 pub from: String,
3012 pub to: String,
3014}
3015
3016#[derive(Debug, Serialize)]
3018pub struct KgPathResponse {
3019 pub agent_id: String,
3020 pub from_id: String,
3021 pub to_id: String,
3022 pub hop_count: usize,
3023 pub path: Vec<String>,
3024}
3025
3026#[derive(Debug, Deserialize)]
3028pub struct KgExportParams {
3029 pub agent_id: String,
3031 #[serde(default = "default_kg_format")]
3033 pub format: String,
3034}
3035
3036fn default_kg_format() -> String {
3037 "json".to_string()
3038}
3039
3040#[derive(Debug, Serialize)]
3042pub struct KgExportJsonResponse {
3043 pub agent_id: String,
3044 pub format: String,
3045 pub node_count: usize,
3046 pub edge_count: usize,
3047 pub edges: Vec<GraphEdgeResponse>,
3048}
3049
3050fn default_working_ttl() -> Option<u64> {
3055 Some(14_400) }
3057fn default_episodic_ttl() -> Option<u64> {
3058 Some(2_592_000) }
3060fn default_semantic_ttl() -> Option<u64> {
3061 Some(31_536_000) }
3063fn default_procedural_ttl() -> Option<u64> {
3064 Some(63_072_000) }
3066fn default_working_decay() -> DecayStrategy {
3067 DecayStrategy::Exponential
3068}
3069fn default_episodic_decay() -> DecayStrategy {
3070 DecayStrategy::PowerLaw
3071}
3072fn default_semantic_decay() -> DecayStrategy {
3073 DecayStrategy::Logarithmic
3074}
3075fn default_procedural_decay() -> DecayStrategy {
3076 DecayStrategy::Flat
3077}
3078fn default_sr_factor() -> f64 {
3079 1.0
3080}
3081fn default_sr_base_interval() -> u64 {
3082 86_400 }
3084fn default_consolidation_enabled() -> bool {
3085 false
3086}
3087fn default_policy_consolidation_threshold() -> f32 {
3088 0.92
3089}
3090fn default_consolidation_interval_hours() -> u32 {
3091 24
3092}
3093fn default_store_dedup_threshold() -> f32 {
3094 0.95
3095}
3096
3097#[derive(Debug, Clone, Serialize, Deserialize)]
3102pub struct MemoryPolicy {
3103 #[serde(
3106 default = "default_working_ttl",
3107 skip_serializing_if = "Option::is_none"
3108 )]
3109 pub working_ttl_seconds: Option<u64>,
3110 #[serde(
3112 default = "default_episodic_ttl",
3113 skip_serializing_if = "Option::is_none"
3114 )]
3115 pub episodic_ttl_seconds: Option<u64>,
3116 #[serde(
3118 default = "default_semantic_ttl",
3119 skip_serializing_if = "Option::is_none"
3120 )]
3121 pub semantic_ttl_seconds: Option<u64>,
3122 #[serde(
3124 default = "default_procedural_ttl",
3125 skip_serializing_if = "Option::is_none"
3126 )]
3127 pub procedural_ttl_seconds: Option<u64>,
3128
3129 #[serde(default = "default_working_decay")]
3132 pub working_decay: DecayStrategy,
3133 #[serde(default = "default_episodic_decay")]
3135 pub episodic_decay: DecayStrategy,
3136 #[serde(default = "default_semantic_decay")]
3138 pub semantic_decay: DecayStrategy,
3139 #[serde(default = "default_procedural_decay")]
3141 pub procedural_decay: DecayStrategy,
3142
3143 #[serde(default = "default_sr_factor")]
3148 pub spaced_repetition_factor: f64,
3149 #[serde(default = "default_sr_base_interval")]
3151 pub spaced_repetition_base_interval_seconds: u64,
3152
3153 #[serde(default = "default_consolidation_enabled")]
3156 pub consolidation_enabled: bool,
3157 #[serde(default = "default_policy_consolidation_threshold")]
3159 pub consolidation_threshold: f32,
3160 #[serde(default = "default_consolidation_interval_hours")]
3162 pub consolidation_interval_hours: u32,
3163 #[serde(default)]
3165 pub consolidated_count: u64,
3166
3167 #[serde(default)]
3171 pub rate_limit_enabled: bool,
3172 #[serde(default, skip_serializing_if = "Option::is_none")]
3175 pub rate_limit_stores_per_minute: Option<u32>,
3176 #[serde(default, skip_serializing_if = "Option::is_none")]
3179 pub rate_limit_recalls_per_minute: Option<u32>,
3180
3181 #[serde(default)]
3189 pub dedup_on_store: bool,
3190 #[serde(default = "default_store_dedup_threshold")]
3192 pub dedup_threshold: f32,
3193}
3194
3195impl Default for MemoryPolicy {
3196 fn default() -> Self {
3197 Self {
3198 working_ttl_seconds: default_working_ttl(),
3199 episodic_ttl_seconds: default_episodic_ttl(),
3200 semantic_ttl_seconds: default_semantic_ttl(),
3201 procedural_ttl_seconds: default_procedural_ttl(),
3202 working_decay: default_working_decay(),
3203 episodic_decay: default_episodic_decay(),
3204 semantic_decay: default_semantic_decay(),
3205 procedural_decay: default_procedural_decay(),
3206 spaced_repetition_factor: default_sr_factor(),
3207 spaced_repetition_base_interval_seconds: default_sr_base_interval(),
3208 consolidation_enabled: default_consolidation_enabled(),
3209 consolidation_threshold: default_policy_consolidation_threshold(),
3210 consolidation_interval_hours: default_consolidation_interval_hours(),
3211 consolidated_count: 0,
3212 rate_limit_enabled: false,
3213 rate_limit_stores_per_minute: None,
3214 rate_limit_recalls_per_minute: None,
3215 dedup_on_store: false,
3216 dedup_threshold: default_store_dedup_threshold(),
3217 }
3218 }
3219}
3220
3221impl MemoryPolicy {
3222 pub fn ttl_for_type(&self, memory_type: &MemoryType) -> Option<u64> {
3224 match memory_type {
3225 MemoryType::Working => self.working_ttl_seconds,
3226 MemoryType::Episodic => self.episodic_ttl_seconds,
3227 MemoryType::Semantic => self.semantic_ttl_seconds,
3228 MemoryType::Procedural => self.procedural_ttl_seconds,
3229 }
3230 }
3231
3232 pub fn decay_for_type(&self, memory_type: &MemoryType) -> DecayStrategy {
3234 match memory_type {
3235 MemoryType::Working => self.working_decay,
3236 MemoryType::Episodic => self.episodic_decay,
3237 MemoryType::Semantic => self.semantic_decay,
3238 MemoryType::Procedural => self.procedural_decay,
3239 }
3240 }
3241
3242 pub fn spaced_repetition_extension(&self, access_count: u32) -> u64 {
3246 if self.spaced_repetition_factor <= 0.0 {
3247 return 0;
3248 }
3249 let ext = access_count as f64
3250 * self.spaced_repetition_factor
3251 * self.spaced_repetition_base_interval_seconds as f64;
3252 ext.round() as u64
3253 }
3254}
3255
3256#[cfg(test)]
3257mod tests {
3258 use super::*;
3259
3260 #[test]
3263 fn test_memory_to_vector_from_vector_roundtrip() {
3264 let memory = Memory {
3265 id: "mem_abc123".to_string(),
3266 memory_type: MemoryType::Episodic,
3267 content: "Test content".to_string(),
3268 agent_id: "test-agent".to_string(),
3269 session_id: Some("sess_001".to_string()),
3270 importance: 0.8,
3271 tags: vec!["tag1".to_string(), "tag2".to_string()],
3272 metadata: Some(serde_json::json!({"key": "value"})),
3273 created_at: 1_700_000_000,
3274 last_accessed_at: 1_700_001_000,
3275 access_count: 5,
3276 ttl_seconds: None,
3277 expires_at: None,
3278 };
3279
3280 let embedding = vec![0.1f32, 0.2, 0.3];
3281 let vector = memory.to_vector(embedding.clone());
3282 assert_eq!(vector.id, memory.id);
3283 assert_eq!(vector.values, embedding);
3284
3285 let recovered =
3286 Memory::from_vector(&vector).expect("should reconstruct memory from vector");
3287 assert_eq!(recovered.id, memory.id);
3288 assert_eq!(recovered.content, memory.content);
3289 assert_eq!(recovered.agent_id, memory.agent_id);
3290 assert_eq!(recovered.session_id, memory.session_id);
3291 assert_eq!(recovered.tags, memory.tags);
3292 assert_eq!(recovered.access_count, memory.access_count);
3293 assert_eq!(recovered.created_at, memory.created_at);
3294 assert_eq!(recovered.last_accessed_at, memory.last_accessed_at);
3295 assert!(
3297 (recovered.importance - memory.importance).abs() < 1e-5,
3298 "importance mismatch: {} vs {}",
3299 recovered.importance,
3300 memory.importance
3301 );
3302 }
3303
3304 #[test]
3305 fn test_memory_from_vector_rejects_session_type() {
3306 let mut meta = serde_json::Map::new();
3307 meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
3308 let vector = Vector {
3309 id: "v1".to_string(),
3310 values: vec![],
3311 metadata: Some(serde_json::Value::Object(meta)),
3312 ttl_seconds: None,
3313 expires_at: None,
3314 };
3315 assert!(
3316 Memory::from_vector(&vector).is_none(),
3317 "from_vector should return None for wrong _dakera_type"
3318 );
3319 }
3320
3321 #[test]
3322 fn test_memory_from_vector_rejects_missing_metadata() {
3323 let vector = Vector {
3324 id: "v1".to_string(),
3325 values: vec![],
3326 metadata: None,
3327 ttl_seconds: None,
3328 expires_at: None,
3329 };
3330 assert!(Memory::from_vector(&vector).is_none());
3331 }
3332
3333 #[test]
3336 fn test_session_to_vector_from_vector_roundtrip() {
3337 let mut session = Session::new("sess_xyz".to_string(), "agent-007".to_string());
3338 session.ended_at = Some(1_700_005_000);
3339 session.summary = Some("Session ended cleanly".to_string());
3340 session.memory_count = 42;
3341
3342 let embedding = vec![0.5f32, 0.5, 0.5];
3343 let vector = session.to_vector(embedding.clone());
3344 assert_eq!(vector.id, session.id);
3345 assert_eq!(vector.values, embedding);
3346
3347 let recovered = Session::from_vector(&vector).expect("should reconstruct session");
3348 assert_eq!(recovered.id, session.id);
3349 assert_eq!(recovered.agent_id, session.agent_id);
3350 assert_eq!(recovered.ended_at, session.ended_at);
3351 assert_eq!(recovered.summary, session.summary);
3352 assert_eq!(recovered.memory_count, session.memory_count);
3353 }
3354
3355 #[test]
3356 fn test_session_from_vector_rejects_memory_type() {
3357 let mut meta = serde_json::Map::new();
3358 meta.insert("_dakera_type".to_string(), serde_json::json!("memory"));
3359 let vector = Vector {
3360 id: "sess_1".to_string(),
3361 values: vec![],
3362 metadata: Some(serde_json::Value::Object(meta)),
3363 ttl_seconds: None,
3364 expires_at: None,
3365 };
3366 assert!(
3367 Session::from_vector(&vector).is_none(),
3368 "from_vector should return None for wrong _dakera_type"
3369 );
3370 }
3371
3372 #[test]
3373 fn test_session_active_has_no_ended_at() {
3374 let session = Session::new("sess_active".to_string(), "agent-1".to_string());
3375 let vector = session.to_vector(vec![0.1]);
3376 let recovered = Session::from_vector(&vector).unwrap();
3377 assert!(recovered.ended_at.is_none());
3378 assert_eq!(recovered.memory_count, 0);
3379 }
3380
3381 #[test]
3384 fn test_pagination_cursor_encode_decode_roundtrip() {
3385 let cursor = PaginationCursor::new(0.75, "mem_abc".to_string());
3386 let encoded = cursor.encode();
3387 let decoded = PaginationCursor::decode(&encoded).expect("should decode valid cursor");
3388 assert!(
3389 (decoded.last_score - cursor.last_score).abs() < 1e-6,
3390 "score mismatch after decode"
3391 );
3392 assert_eq!(decoded.last_id, cursor.last_id);
3393 }
3394
3395 #[test]
3396 fn test_pagination_cursor_decode_invalid_returns_none() {
3397 assert!(PaginationCursor::decode("not-valid-base64!!!").is_none());
3398 assert!(PaginationCursor::decode("").is_none());
3399 assert!(PaginationCursor::decode("aGVsbG8=").is_none()); }
3402
3403 #[test]
3406 fn test_distance_metric_serde_round_trip() {
3407 let cases = [
3408 (DistanceMetric::Cosine, "\"cosine\""),
3409 (DistanceMetric::Euclidean, "\"euclidean\""),
3410 (DistanceMetric::DotProduct, "\"dot_product\""),
3411 ];
3412 for (metric, expected_json) in &cases {
3413 let serialized = serde_json::to_string(metric).unwrap();
3414 assert_eq!(
3415 &serialized, expected_json,
3416 "serialized form mismatch for {:?}",
3417 metric
3418 );
3419 let deserialized: DistanceMetric = serde_json::from_str(&serialized).unwrap();
3420 assert_eq!(*metric, deserialized);
3421 }
3422 }
3423
3424 #[test]
3427 fn test_vector_is_expired_at_not_yet_expired() {
3428 let vector = Vector {
3429 id: "v1".to_string(),
3430 values: vec![1.0],
3431 metadata: None,
3432 ttl_seconds: None,
3433 expires_at: Some(u64::MAX),
3434 };
3435 assert!(!vector.is_expired_at(0));
3436 assert!(!vector.is_expired_at(1_000_000));
3437 }
3438
3439 #[test]
3440 fn test_vector_is_expired_at_past_expiry() {
3441 let vector = Vector {
3442 id: "v1".to_string(),
3443 values: vec![1.0],
3444 metadata: None,
3445 ttl_seconds: None,
3446 expires_at: Some(100),
3447 };
3448 assert!(vector.is_expired_at(100), "at boundary should be expired");
3449 assert!(vector.is_expired_at(200));
3450 assert!(!vector.is_expired_at(99));
3451 }
3452
3453 #[test]
3454 fn test_vector_no_expiry_never_expires() {
3455 let vector = Vector {
3456 id: "v1".to_string(),
3457 values: vec![1.0],
3458 metadata: None,
3459 ttl_seconds: None,
3460 expires_at: None,
3461 };
3462 assert!(!vector.is_expired_at(u64::MAX));
3463 }
3464
3465 #[test]
3468 fn test_column_upsert_mismatched_vectors_length_errors() {
3469 let req = ColumnUpsertRequest {
3470 ids: vec!["a".to_string(), "b".to_string()],
3471 vectors: vec![vec![1.0, 2.0]], attributes: Default::default(),
3473 ttl_seconds: None,
3474 dimension: None,
3475 };
3476 assert!(
3477 req.to_vectors().is_err(),
3478 "should error when vectors.len() != ids.len()"
3479 );
3480 }
3481
3482 #[test]
3483 fn test_column_upsert_mismatched_attribute_length_errors() {
3484 let mut attrs = std::collections::HashMap::new();
3485 attrs.insert(
3486 "score".to_string(),
3487 vec![serde_json::json!(1.0)], );
3489 let req = ColumnUpsertRequest {
3490 ids: vec!["a".to_string(), "b".to_string()],
3491 vectors: vec![vec![1.0], vec![2.0]],
3492 attributes: attrs,
3493 ttl_seconds: None,
3494 dimension: None,
3495 };
3496 assert!(
3497 req.to_vectors().is_err(),
3498 "should error when attribute array length != ids.len()"
3499 );
3500 }
3501
3502 #[test]
3503 fn test_column_upsert_mismatched_dimension_errors() {
3504 let req = ColumnUpsertRequest {
3505 ids: vec!["a".to_string(), "b".to_string()],
3506 vectors: vec![vec![1.0, 2.0], vec![1.0]], attributes: Default::default(),
3508 ttl_seconds: None,
3509 dimension: None,
3510 };
3511 assert!(
3512 req.to_vectors().is_err(),
3513 "should error on dimension mismatch between vectors"
3514 );
3515 }
3516
3517 #[test]
3518 fn test_column_upsert_success() {
3519 let req = ColumnUpsertRequest {
3520 ids: vec!["a".to_string(), "b".to_string()],
3521 vectors: vec![vec![1.0, 0.0], vec![0.0, 1.0]],
3522 attributes: Default::default(),
3523 ttl_seconds: None,
3524 dimension: Some(2),
3525 };
3526 let result = req.to_vectors().expect("valid request should succeed");
3527 assert_eq!(result.len(), 2);
3528 assert_eq!(result[0].id, "a");
3529 assert_eq!(result[1].id, "b");
3530 assert_eq!(result[0].values, vec![1.0, 0.0]);
3531 assert_eq!(result[1].values, vec![0.0, 1.0]);
3532 }
3533}