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 if let Some(cd) = user_meta.get("_dakera_content_date") {
1701 meta.insert("_dakera_content_date".to_string(), cd.clone());
1702 }
1703 meta.insert("user_metadata".to_string(), user_meta.clone());
1704 }
1705 serde_json::Value::Object(meta)
1706 }
1707
1708 pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1710 let mut v = Vector {
1711 id: self.id.clone(),
1712 values: embedding,
1713 metadata: Some(self.to_vector_metadata()),
1714 ttl_seconds: self.ttl_seconds,
1715 expires_at: self.expires_at,
1716 };
1717 v.apply_ttl();
1718 v
1719 }
1720
1721 pub fn from_vector(vector: &Vector) -> Option<Self> {
1723 let meta = vector.metadata.as_ref()?.as_object()?;
1724 let entry_type = meta.get("_dakera_type")?.as_str()?;
1725 if entry_type != "memory" {
1726 return None;
1727 }
1728
1729 Some(Memory {
1730 id: vector.id.clone(),
1731 memory_type: serde_json::from_value(meta.get("memory_type")?.clone())
1732 .unwrap_or_default(),
1733 content: meta.get("content")?.as_str()?.to_string(),
1734 agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1735 session_id: meta
1736 .get("session_id")
1737 .and_then(|v| v.as_str())
1738 .map(String::from),
1739 importance: meta
1740 .get("importance")
1741 .and_then(|v| v.as_f64())
1742 .unwrap_or(0.5) as f32,
1743 tags: meta
1744 .get("tags")
1745 .and_then(|v| serde_json::from_value(v.clone()).ok())
1746 .unwrap_or_default(),
1747 metadata: meta.get("user_metadata").cloned(),
1748 created_at: meta.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0),
1749 last_accessed_at: meta
1750 .get("last_accessed_at")
1751 .and_then(|v| v.as_u64())
1752 .unwrap_or(0),
1753 access_count: meta
1754 .get("access_count")
1755 .and_then(|v| v.as_u64())
1756 .unwrap_or(0) as u32,
1757 ttl_seconds: vector.ttl_seconds,
1758 expires_at: vector.expires_at,
1759 })
1760 }
1761}
1762
1763#[derive(Debug, Clone, Serialize, Deserialize)]
1765pub struct Session {
1766 pub id: String,
1767 pub agent_id: String,
1768 pub started_at: u64,
1769 #[serde(skip_serializing_if = "Option::is_none")]
1770 pub ended_at: Option<u64>,
1771 #[serde(skip_serializing_if = "Option::is_none")]
1772 pub summary: Option<String>,
1773 #[serde(skip_serializing_if = "Option::is_none")]
1774 pub metadata: Option<serde_json::Value>,
1775 #[serde(default)]
1777 pub memory_count: usize,
1778}
1779
1780impl Session {
1781 pub fn new(id: String, agent_id: String) -> Self {
1782 let now = std::time::SystemTime::now()
1783 .duration_since(std::time::UNIX_EPOCH)
1784 .unwrap_or_default()
1785 .as_secs();
1786 Self {
1787 id,
1788 agent_id,
1789 started_at: now,
1790 ended_at: None,
1791 summary: None,
1792 metadata: None,
1793 memory_count: 0,
1794 }
1795 }
1796
1797 pub fn to_vector_metadata(&self) -> serde_json::Value {
1799 let mut meta = serde_json::Map::new();
1800 meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
1801 meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1802 meta.insert("started_at".to_string(), serde_json::json!(self.started_at));
1803 if let Some(ref ended) = self.ended_at {
1804 meta.insert("ended_at".to_string(), serde_json::json!(ended));
1805 }
1806 if let Some(ref summary) = self.summary {
1807 meta.insert("summary".to_string(), serde_json::json!(summary));
1808 }
1809 if let Some(ref user_meta) = self.metadata {
1810 meta.insert("user_metadata".to_string(), user_meta.clone());
1811 }
1812 meta.insert(
1813 "memory_count".to_string(),
1814 serde_json::json!(self.memory_count),
1815 );
1816 serde_json::Value::Object(meta)
1817 }
1818
1819 pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1821 Vector {
1822 id: self.id.clone(),
1823 values: embedding,
1824 metadata: Some(self.to_vector_metadata()),
1825 ttl_seconds: None,
1826 expires_at: None,
1827 }
1828 }
1829
1830 pub fn from_vector(vector: &Vector) -> Option<Self> {
1832 let meta = vector.metadata.as_ref()?.as_object()?;
1833 let entry_type = meta.get("_dakera_type")?.as_str()?;
1834 if entry_type != "session" {
1835 return None;
1836 }
1837
1838 Some(Session {
1839 id: vector.id.clone(),
1840 agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1841 started_at: meta.get("started_at").and_then(|v| v.as_u64()).unwrap_or(0),
1842 ended_at: meta.get("ended_at").and_then(|v| v.as_u64()),
1843 summary: meta
1844 .get("summary")
1845 .and_then(|v| v.as_str())
1846 .map(String::from),
1847 metadata: meta.get("user_metadata").cloned(),
1848 memory_count: meta
1849 .get("memory_count")
1850 .and_then(|v| v.as_u64())
1851 .unwrap_or(0) as usize,
1852 })
1853 }
1854}
1855
1856#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1858#[serde(rename_all = "snake_case")]
1859#[derive(Default)]
1860pub enum DecayStrategy {
1861 #[default]
1862 Exponential,
1863 Linear,
1864 StepFunction,
1865 PowerLaw,
1867 Logarithmic,
1869 Flat,
1871}
1872
1873#[derive(Debug, Clone, Serialize, Deserialize)]
1875pub struct DecayConfig {
1876 #[serde(default)]
1877 pub strategy: DecayStrategy,
1878 #[serde(default = "default_half_life_hours")]
1879 pub half_life_hours: f64,
1880 #[serde(default = "default_min_importance")]
1881 pub min_importance: f32,
1882}
1883
1884fn default_half_life_hours() -> f64 {
1885 168.0 }
1887
1888fn default_min_importance() -> f32 {
1889 0.01
1890}
1891
1892impl Default for DecayConfig {
1893 fn default() -> Self {
1894 Self {
1895 strategy: DecayStrategy::default(),
1896 half_life_hours: default_half_life_hours(),
1897 min_importance: default_min_importance(),
1898 }
1899 }
1900}
1901
1902#[derive(Debug, Deserialize)]
1908pub struct StoreMemoryRequest {
1909 pub content: String,
1910 pub agent_id: String,
1911 #[serde(default)]
1912 pub memory_type: MemoryType,
1913 #[serde(skip_serializing_if = "Option::is_none")]
1914 pub session_id: Option<String>,
1915 #[serde(default = "default_importance")]
1916 pub importance: f32,
1917 #[serde(default)]
1918 pub tags: Vec<String>,
1919 #[serde(skip_serializing_if = "Option::is_none")]
1920 pub metadata: Option<serde_json::Value>,
1921 #[serde(skip_serializing_if = "Option::is_none")]
1922 pub ttl_seconds: Option<u64>,
1923 #[serde(skip_serializing_if = "Option::is_none")]
1928 pub expires_at: Option<u64>,
1929 #[serde(skip_serializing_if = "Option::is_none")]
1931 pub id: Option<String>,
1932}
1933
1934#[derive(Debug, Serialize)]
1936pub struct StoreMemoryResponse {
1937 pub memory: Memory,
1938 pub embedding_time_ms: u64,
1939}
1940
1941#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1945#[serde(rename_all = "lowercase")]
1946pub enum RoutingMode {
1947 #[default]
1949 Auto,
1950 Vector,
1952 Bm25,
1954 Hybrid,
1956}
1957
1958#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1963#[serde(rename_all = "lowercase")]
1964pub enum FusionStrategy {
1965 Rrf,
1970 #[default]
1973 MinMax,
1974}
1975
1976#[derive(Debug, Clone, Deserialize)]
1978pub struct RecallRequest {
1979 pub query: String,
1980 pub agent_id: String,
1981 #[serde(default = "default_top_k")]
1982 pub top_k: usize,
1983 #[serde(default)]
1984 pub memory_type: Option<MemoryType>,
1985 #[serde(default)]
1986 pub session_id: Option<String>,
1987 #[serde(default)]
1988 pub tags: Option<Vec<String>>,
1989 #[serde(default)]
1990 pub min_importance: Option<f32>,
1991 #[serde(default = "default_true")]
1993 pub importance_weighted: bool,
1994 #[serde(default)]
1996 pub include_associated: bool,
1997 #[serde(default)]
1999 pub associated_memories_cap: Option<usize>,
2000 #[serde(default)]
2002 pub since: Option<String>,
2003 #[serde(default)]
2005 pub until: Option<String>,
2006 #[serde(default)]
2009 pub associated_memories_depth: Option<u8>,
2010 #[serde(default)]
2013 pub associated_memories_min_weight: Option<f32>,
2014 #[serde(default)]
2018 pub routing: RoutingMode,
2019 #[serde(default = "default_true")]
2023 pub rerank: bool,
2024 #[serde(default)]
2027 pub fusion: FusionStrategy,
2028 #[serde(default)]
2032 pub vector_weight: Option<f32>,
2033 #[serde(default)]
2040 pub iterations: Option<u8>,
2041 #[serde(default = "default_true")]
2045 pub neighborhood: bool,
2046}
2047
2048#[derive(Debug, Serialize, Deserialize)]
2050pub struct RecallResult {
2051 pub memory: Memory,
2052 pub score: f32,
2053 #[serde(skip_serializing_if = "Option::is_none")]
2055 pub weighted_score: Option<f32>,
2056 #[serde(skip_serializing_if = "Option::is_none")]
2058 pub smart_score: Option<f32>,
2059 #[serde(skip_serializing_if = "Option::is_none")]
2062 pub depth: Option<u8>,
2063}
2064
2065#[derive(Debug, Serialize)]
2067pub struct RecallResponse {
2068 pub memories: Vec<RecallResult>,
2069 pub query_embedding_time_ms: u64,
2070 pub search_time_ms: u64,
2071 #[serde(skip_serializing_if = "Option::is_none")]
2074 pub associated_memories: Option<Vec<RecallResult>>,
2075}
2076
2077#[derive(Debug, Deserialize)]
2079pub struct ForgetRequest {
2080 pub agent_id: String,
2081 #[serde(default)]
2082 pub memory_ids: Option<Vec<String>>,
2083 #[serde(default)]
2084 pub memory_type: Option<MemoryType>,
2085 #[serde(default)]
2086 pub session_id: Option<String>,
2087 #[serde(default)]
2088 pub tags: Option<Vec<String>>,
2089 #[serde(default)]
2091 pub below_importance: Option<f32>,
2092}
2093
2094#[derive(Debug, Serialize)]
2096pub struct ForgetResponse {
2097 pub deleted_count: usize,
2098}
2099
2100#[derive(Debug, Deserialize)]
2102pub struct UpdateMemoryRequest {
2103 #[serde(default)]
2104 pub content: Option<String>,
2105 #[serde(default)]
2106 pub importance: Option<f32>,
2107 #[serde(default)]
2108 pub tags: Option<Vec<String>>,
2109 #[serde(default)]
2110 pub metadata: Option<serde_json::Value>,
2111 #[serde(default)]
2112 pub memory_type: Option<MemoryType>,
2113}
2114
2115#[derive(Debug, Deserialize)]
2117pub struct UpdateImportanceRequest {
2118 pub memory_id: String,
2119 pub importance: f32,
2120 pub agent_id: String,
2121}
2122
2123#[derive(Debug, Deserialize)]
2125pub struct ConsolidateRequest {
2126 pub agent_id: String,
2127 #[serde(default)]
2129 pub memory_ids: Option<Vec<String>>,
2130 #[serde(default = "default_consolidation_threshold")]
2132 pub threshold: f32,
2133 #[serde(default)]
2135 pub target_type: Option<MemoryType>,
2136}
2137
2138fn default_consolidation_threshold() -> f32 {
2139 0.85
2140}
2141
2142#[derive(Debug, Serialize)]
2144pub struct ConsolidateResponse {
2145 pub consolidated_memory: Memory,
2146 pub source_memory_ids: Vec<String>,
2147 pub memories_removed: usize,
2148}
2149
2150#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2152#[serde(rename_all = "lowercase")]
2153pub enum FeedbackSignal {
2154 Upvote,
2156 Downvote,
2158 Flag,
2160 Positive,
2162 Negative,
2164}
2165
2166#[derive(Debug, Clone, Serialize, Deserialize)]
2168pub struct FeedbackHistoryEntry {
2169 pub signal: FeedbackSignal,
2170 pub timestamp: u64,
2171 pub old_importance: f32,
2172 pub new_importance: f32,
2173}
2174
2175#[derive(Debug, Deserialize)]
2177pub struct FeedbackRequest {
2178 pub agent_id: String,
2179 pub memory_id: String,
2180 pub signal: FeedbackSignal,
2181}
2182
2183#[derive(Debug, Deserialize)]
2185pub struct MemoryFeedbackRequest {
2186 pub agent_id: String,
2187 pub signal: FeedbackSignal,
2188}
2189
2190#[derive(Debug, Serialize)]
2192pub struct FeedbackResponse {
2193 pub memory_id: String,
2194 pub new_importance: f32,
2195 pub signal: FeedbackSignal,
2196}
2197
2198#[derive(Debug, Serialize)]
2200pub struct FeedbackHistoryResponse {
2201 pub memory_id: String,
2202 pub entries: Vec<FeedbackHistoryEntry>,
2203}
2204
2205#[derive(Debug, Serialize)]
2207pub struct AgentFeedbackSummary {
2208 pub agent_id: String,
2209 pub upvotes: u64,
2210 pub downvotes: u64,
2211 pub flags: u64,
2212 pub total_feedback: u64,
2213 pub health_score: f32,
2215}
2216
2217#[derive(Debug, Deserialize)]
2219pub struct MemoryImportancePatchRequest {
2220 pub agent_id: String,
2221 pub importance: f32,
2222}
2223
2224#[derive(Debug, Deserialize)]
2226pub struct FeedbackHealthQuery {
2227 pub agent_id: String,
2228}
2229
2230#[derive(Debug, Serialize)]
2232pub struct FeedbackHealthResponse {
2233 pub agent_id: String,
2234 pub health_score: f32,
2236 pub memory_count: usize,
2237 pub avg_importance: f32,
2238}
2239
2240#[derive(Debug, Deserialize)]
2242pub struct SearchMemoriesRequest {
2243 pub agent_id: String,
2244 #[serde(default)]
2245 pub query: Option<String>,
2246 #[serde(default)]
2247 pub memory_type: Option<MemoryType>,
2248 #[serde(default)]
2249 pub session_id: Option<String>,
2250 #[serde(default)]
2251 pub tags: Option<Vec<String>>,
2252 #[serde(default)]
2253 pub min_importance: Option<f32>,
2254 #[serde(default)]
2255 pub max_importance: Option<f32>,
2256 #[serde(default)]
2257 pub created_after: Option<u64>,
2258 #[serde(default)]
2259 pub created_before: Option<u64>,
2260 #[serde(default = "default_top_k")]
2261 pub top_k: usize,
2262 #[serde(default)]
2263 pub sort_by: Option<MemorySortField>,
2264 #[serde(default)]
2266 pub routing: RoutingMode,
2267 #[serde(default)]
2270 pub rerank: bool,
2271}
2272
2273#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
2275#[serde(rename_all = "snake_case")]
2276pub enum MemorySortField {
2277 CreatedAt,
2278 LastAccessedAt,
2279 Importance,
2280 AccessCount,
2281}
2282
2283#[derive(Debug, Serialize)]
2285pub struct SearchMemoriesResponse {
2286 pub memories: Vec<RecallResult>,
2287 pub total_count: usize,
2288}
2289
2290#[derive(Debug, Deserialize)]
2296pub struct SessionStartRequest {
2297 pub agent_id: String,
2298 #[serde(skip_serializing_if = "Option::is_none")]
2299 pub metadata: Option<serde_json::Value>,
2300 #[serde(skip_serializing_if = "Option::is_none")]
2302 pub id: Option<String>,
2303}
2304
2305#[derive(Debug, Serialize)]
2307pub struct SessionStartResponse {
2308 pub session: Session,
2309}
2310
2311#[derive(Debug, Deserialize)]
2313pub struct SessionEndRequest {
2314 #[serde(default)]
2315 pub summary: Option<String>,
2316 #[serde(default)]
2318 pub auto_summarize: bool,
2319}
2320
2321#[derive(Debug, Serialize)]
2323pub struct SessionEndResponse {
2324 pub session: Session,
2325 pub memory_count: usize,
2326}
2327
2328#[derive(Debug, Serialize)]
2330pub struct ListSessionsResponse {
2331 pub sessions: Vec<Session>,
2332 pub total: usize,
2333}
2334
2335#[derive(Debug, Serialize)]
2337pub struct SessionMemoriesResponse {
2338 pub session: Session,
2339 pub memories: Vec<Memory>,
2340 #[serde(skip_serializing_if = "Option::is_none")]
2342 pub total: Option<usize>,
2343}
2344
2345#[derive(Debug, Serialize, Deserialize, Clone)]
2351pub struct AgentSummary {
2352 pub agent_id: String,
2353 pub memory_count: usize,
2354 pub session_count: usize,
2355 pub active_sessions: usize,
2356}
2357
2358#[derive(Debug, Serialize)]
2360pub struct AgentStats {
2361 pub agent_id: String,
2362 pub total_memories: usize,
2363 pub memories_by_type: std::collections::HashMap<String, usize>,
2364 pub total_sessions: usize,
2365 pub active_sessions: usize,
2366 pub avg_importance: f32,
2367 pub oldest_memory_at: Option<u64>,
2368 pub newest_memory_at: Option<u64>,
2369}
2370
2371#[derive(Debug, Serialize)]
2377pub struct WakeUpResponse {
2378 pub agent_id: String,
2379 pub memories: Vec<Memory>,
2381 pub total_available: usize,
2383}
2384
2385#[derive(Debug, Deserialize)]
2387pub struct KnowledgeGraphRequest {
2388 pub agent_id: String,
2389 pub memory_id: String,
2390 #[serde(default = "default_graph_depth")]
2391 pub depth: usize,
2392 #[serde(default = "default_graph_min_similarity")]
2393 pub min_similarity: f32,
2394}
2395
2396fn default_graph_depth() -> usize {
2397 2
2398}
2399
2400fn default_graph_min_similarity() -> f32 {
2401 0.7
2402}
2403
2404#[derive(Debug, Serialize)]
2406pub struct KnowledgeGraphNode {
2407 pub memory: Memory,
2408 pub similarity: f32,
2409 pub related: Vec<KnowledgeGraphEdge>,
2410}
2411
2412#[derive(Debug, Serialize)]
2414pub struct KnowledgeGraphEdge {
2415 pub memory_id: String,
2416 pub similarity: f32,
2417 pub shared_tags: Vec<String>,
2418}
2419
2420#[derive(Debug, Serialize)]
2422pub struct KnowledgeGraphResponse {
2423 pub root: KnowledgeGraphNode,
2424 pub total_nodes: usize,
2425}
2426
2427fn default_full_graph_max_nodes() -> usize {
2432 200
2433}
2434
2435fn default_full_graph_min_similarity() -> f32 {
2436 0.50
2437}
2438
2439fn default_full_graph_cluster_threshold() -> f32 {
2440 0.60
2441}
2442
2443fn default_full_graph_max_edges_per_node() -> usize {
2444 8
2445}
2446
2447#[derive(Debug, Deserialize)]
2449pub struct FullKnowledgeGraphRequest {
2450 pub agent_id: String,
2451 #[serde(default = "default_full_graph_max_nodes")]
2452 pub max_nodes: usize,
2453 #[serde(default = "default_full_graph_min_similarity")]
2454 pub min_similarity: f32,
2455 #[serde(default = "default_full_graph_cluster_threshold")]
2456 pub cluster_threshold: f32,
2457 #[serde(default = "default_full_graph_max_edges_per_node")]
2458 pub max_edges_per_node: usize,
2459}
2460
2461#[derive(Debug, Serialize)]
2463pub struct FullGraphNode {
2464 pub id: String,
2465 pub content: String,
2466 pub memory_type: String,
2467 pub importance: f32,
2468 pub tags: Vec<String>,
2469 pub created_at: Option<String>,
2470 pub cluster_id: usize,
2471 pub centrality: f32,
2472}
2473
2474#[derive(Debug, Serialize)]
2476pub struct FullGraphEdge {
2477 pub source: String,
2478 pub target: String,
2479 pub similarity: f32,
2480 pub shared_tags: Vec<String>,
2481}
2482
2483#[derive(Debug, Serialize)]
2485pub struct GraphCluster {
2486 pub id: usize,
2487 pub node_count: usize,
2488 pub top_tags: Vec<String>,
2489 pub avg_importance: f32,
2490}
2491
2492#[derive(Debug, Serialize)]
2494pub struct GraphStats {
2495 pub total_memories: usize,
2496 pub included_memories: usize,
2497 pub total_edges: usize,
2498 pub cluster_count: usize,
2499 pub density: f32,
2500 pub hub_memory_id: Option<String>,
2501}
2502
2503#[derive(Debug, Serialize)]
2505pub struct FullKnowledgeGraphResponse {
2506 pub nodes: Vec<FullGraphNode>,
2507 pub edges: Vec<FullGraphEdge>,
2508 pub clusters: Vec<GraphCluster>,
2509 pub stats: GraphStats,
2510}
2511
2512#[derive(Debug, Deserialize)]
2514pub struct SummarizeRequest {
2515 pub agent_id: String,
2516 pub memory_ids: Vec<String>,
2517 #[serde(default)]
2518 pub target_type: Option<MemoryType>,
2519}
2520
2521#[derive(Debug, Serialize)]
2523pub struct SummarizeResponse {
2524 pub summary_memory: Memory,
2525 pub source_count: usize,
2526}
2527
2528#[derive(Debug, Deserialize)]
2530pub struct DeduplicateRequest {
2531 pub agent_id: String,
2532 #[serde(default = "default_dedup_threshold")]
2533 pub threshold: f32,
2534 #[serde(default)]
2535 pub memory_type: Option<MemoryType>,
2536 #[serde(default)]
2538 pub dry_run: bool,
2539}
2540
2541fn default_dedup_threshold() -> f32 {
2542 0.92
2543}
2544
2545#[derive(Debug, Serialize)]
2547pub struct DuplicateGroup {
2548 pub canonical_id: String,
2549 pub duplicate_ids: Vec<String>,
2550 pub avg_similarity: f32,
2551}
2552
2553#[derive(Debug, Serialize)]
2555pub struct DeduplicateResponse {
2556 pub groups: Vec<DuplicateGroup>,
2557 pub duplicates_found: usize,
2558 pub duplicates_merged: usize,
2559}
2560
2561fn default_cross_agent_min_similarity() -> f32 {
2566 0.3
2567}
2568
2569fn default_cross_agent_max_nodes_per_agent() -> usize {
2570 50
2571}
2572
2573fn default_cross_agent_max_cross_edges() -> usize {
2574 200
2575}
2576
2577#[derive(Debug, Deserialize)]
2579pub struct CrossAgentNetworkRequest {
2580 #[serde(default)]
2582 pub agent_ids: Option<Vec<String>>,
2583 #[serde(default = "default_cross_agent_min_similarity")]
2585 pub min_similarity: f32,
2586 #[serde(default = "default_cross_agent_max_nodes_per_agent")]
2588 pub max_nodes_per_agent: usize,
2589 #[serde(default)]
2591 pub min_importance: f32,
2592 #[serde(default = "default_cross_agent_max_cross_edges")]
2594 pub max_cross_edges: usize,
2595}
2596
2597#[derive(Debug, Serialize)]
2599pub struct AgentNetworkInfo {
2600 pub agent_id: String,
2601 pub memory_count: usize,
2602 pub avg_importance: f32,
2603}
2604
2605#[derive(Debug, Serialize)]
2607pub struct AgentNetworkNode {
2608 pub id: String,
2609 pub agent_id: String,
2610 pub content: String,
2611 pub importance: f32,
2612 pub tags: Vec<String>,
2613 pub memory_type: String,
2614 pub created_at: u64,
2615}
2616
2617#[derive(Debug, Serialize)]
2619pub struct AgentNetworkEdge {
2620 pub source: String,
2621 pub target: String,
2622 pub source_agent: String,
2623 pub target_agent: String,
2624 pub similarity: f32,
2625}
2626
2627#[derive(Debug, Serialize)]
2629pub struct AgentNetworkStats {
2630 pub total_agents: usize,
2631 pub total_nodes: usize,
2632 pub total_cross_edges: usize,
2633 pub density: f32,
2634}
2635
2636#[derive(Debug, Serialize)]
2638pub struct CrossAgentNetworkResponse {
2639 pub node_count: usize,
2640 pub agents: Vec<AgentNetworkInfo>,
2641 pub nodes: Vec<AgentNetworkNode>,
2642 pub edges: Vec<AgentNetworkEdge>,
2643 pub stats: AgentNetworkStats,
2644}
2645
2646#[derive(Debug, Deserialize, Default)]
2654pub struct BatchMemoryFilter {
2655 #[serde(default)]
2657 pub tags: Option<Vec<String>>,
2658 #[serde(default)]
2660 pub min_importance: Option<f32>,
2661 #[serde(default)]
2663 pub max_importance: Option<f32>,
2664 #[serde(default)]
2666 pub created_after: Option<u64>,
2667 #[serde(default)]
2669 pub created_before: Option<u64>,
2670 #[serde(default)]
2672 pub memory_type: Option<MemoryType>,
2673 #[serde(default)]
2675 pub session_id: Option<String>,
2676}
2677
2678impl BatchMemoryFilter {
2679 pub fn has_any(&self) -> bool {
2681 self.tags.is_some()
2682 || self.min_importance.is_some()
2683 || self.max_importance.is_some()
2684 || self.created_after.is_some()
2685 || self.created_before.is_some()
2686 || self.memory_type.is_some()
2687 || self.session_id.is_some()
2688 }
2689
2690 pub fn matches(&self, memory: &Memory) -> bool {
2692 if let Some(ref tags) = self.tags {
2693 if !tags.is_empty() && !tags.iter().all(|t| memory.tags.contains(t)) {
2694 return false;
2695 }
2696 }
2697 if let Some(min) = self.min_importance {
2698 if memory.importance < min {
2699 return false;
2700 }
2701 }
2702 if let Some(max) = self.max_importance {
2703 if memory.importance > max {
2704 return false;
2705 }
2706 }
2707 if let Some(after) = self.created_after {
2708 if memory.created_at < after {
2709 return false;
2710 }
2711 }
2712 if let Some(before) = self.created_before {
2713 if memory.created_at > before {
2714 return false;
2715 }
2716 }
2717 if let Some(ref mt) = self.memory_type {
2718 if memory.memory_type != *mt {
2719 return false;
2720 }
2721 }
2722 if let Some(ref sid) = self.session_id {
2723 if memory.session_id.as_ref() != Some(sid) {
2724 return false;
2725 }
2726 }
2727 true
2728 }
2729}
2730
2731#[derive(Debug, Deserialize)]
2733pub struct BatchRecallRequest {
2734 pub agent_id: String,
2736 #[serde(default)]
2738 pub filter: BatchMemoryFilter,
2739 #[serde(default = "default_batch_limit")]
2741 pub limit: usize,
2742}
2743
2744fn default_batch_limit() -> usize {
2745 100
2746}
2747
2748#[derive(Debug, Serialize)]
2750pub struct BatchRecallResponse {
2751 pub memories: Vec<Memory>,
2752 pub total: usize,
2753 pub filtered: usize,
2754}
2755
2756#[derive(Debug, Deserialize)]
2758pub struct BatchForgetRequest {
2759 pub agent_id: String,
2761 pub filter: BatchMemoryFilter,
2763}
2764
2765#[derive(Debug, Serialize)]
2767pub struct BatchForgetResponse {
2768 pub deleted_count: usize,
2769}
2770
2771#[derive(Debug, Deserialize)]
2779pub struct BatchStoreMemoryItem {
2780 pub content: String,
2781 #[serde(default)]
2782 pub memory_type: MemoryType,
2783 #[serde(skip_serializing_if = "Option::is_none")]
2784 pub session_id: Option<String>,
2785 #[serde(default = "default_importance")]
2786 pub importance: f32,
2787 #[serde(default)]
2788 pub tags: Vec<String>,
2789 #[serde(skip_serializing_if = "Option::is_none")]
2790 pub metadata: Option<serde_json::Value>,
2791 #[serde(skip_serializing_if = "Option::is_none")]
2792 pub ttl_seconds: Option<u64>,
2793 #[serde(skip_serializing_if = "Option::is_none")]
2794 pub expires_at: Option<u64>,
2795 #[serde(skip_serializing_if = "Option::is_none")]
2797 pub id: Option<String>,
2798}
2799
2800#[derive(Debug, Deserialize)]
2807pub struct BatchStoreMemoryRequest {
2808 pub agent_id: String,
2809 pub memories: Vec<BatchStoreMemoryItem>,
2810}
2811
2812#[derive(Debug, Serialize)]
2814pub struct BatchStoreMemoryResponse {
2815 pub stored: Vec<Memory>,
2816 pub stored_count: usize,
2817 pub total_embedding_time_ms: u64,
2818}
2819
2820#[derive(Debug, Deserialize)]
2827pub struct NamespaceEntityConfigRequest {
2828 pub extract_entities: bool,
2830 #[serde(default)]
2833 pub entity_types: Vec<String>,
2834}
2835
2836#[derive(Debug, Serialize, Deserialize)]
2838pub struct NamespaceEntityConfigResponse {
2839 pub namespace: String,
2840 pub extract_entities: bool,
2841 pub entity_types: Vec<String>,
2842}
2843
2844#[derive(Debug, Deserialize)]
2847pub struct ExtractEntitiesRequest {
2848 pub content: String,
2850 #[serde(default)]
2853 pub entity_types: Vec<String>,
2854}
2855
2856#[derive(Debug, Clone, Serialize, Deserialize)]
2858pub struct EntityResult {
2859 pub entity_type: String,
2860 pub value: String,
2861 pub score: f32,
2862 pub start: usize,
2863 pub end: usize,
2864 pub tag: String,
2866}
2867
2868#[derive(Debug, Serialize)]
2870pub struct ExtractEntitiesResponse {
2871 pub entities: Vec<EntityResult>,
2872 pub count: usize,
2873}
2874
2875#[derive(Debug, Deserialize)]
2881pub struct GraphTraverseQuery {
2882 #[serde(default = "default_ce5_graph_depth")]
2884 pub depth: u32,
2885}
2886
2887fn default_ce5_graph_depth() -> u32 {
2888 3
2889}
2890
2891#[derive(Debug, Deserialize)]
2893pub struct GraphPathQuery {
2894 pub to: String,
2896}
2897
2898#[derive(Debug, Deserialize)]
2900pub struct MemoryLinkRequest {
2901 pub target_id: String,
2903 #[serde(skip_serializing_if = "Option::is_none")]
2905 pub label: Option<String>,
2906 pub agent_id: String,
2908}
2909
2910#[derive(Debug, Serialize)]
2912pub struct GraphTraverseResponse {
2913 pub root_id: String,
2914 pub depth: u32,
2915 pub node_count: usize,
2916 pub nodes: Vec<GraphNodeResponse>,
2917}
2918
2919#[derive(Debug, Serialize)]
2921pub struct GraphNodeResponse {
2922 pub memory_id: String,
2923 pub depth: u32,
2924 pub edges: Vec<GraphEdgeResponse>,
2925}
2926
2927#[derive(Debug, Serialize)]
2929pub struct GraphEdgeResponse {
2930 pub from_id: String,
2931 pub to_id: String,
2932 pub edge_type: String,
2933 pub weight: f32,
2934 pub created_at: u64,
2935}
2936
2937#[derive(Debug, Serialize)]
2939pub struct GraphPathResponse {
2940 pub from_id: String,
2941 pub to_id: String,
2942 pub path: Vec<String>,
2944 pub hop_count: usize,
2945}
2946
2947#[derive(Debug, Serialize)]
2949pub struct MemoryLinkResponse {
2950 pub from_id: String,
2951 pub to_id: String,
2952 pub edge_type: String,
2953}
2954
2955#[derive(Debug, Serialize)]
2957pub struct GraphExportResponse {
2958 pub agent_id: String,
2959 pub namespace: String,
2960 pub node_count: usize,
2961 pub edge_count: usize,
2962 pub edges: Vec<GraphEdgeResponse>,
2963}
2964
2965#[derive(Debug, Deserialize)]
2971pub struct KgQueryParams {
2972 pub agent_id: String,
2974 #[serde(default)]
2976 pub root_id: Option<String>,
2977 #[serde(default)]
2979 pub edge_type: Option<String>,
2980 #[serde(default)]
2982 pub min_weight: Option<f32>,
2983 #[serde(default = "default_kg_depth")]
2985 pub max_depth: u32,
2986 #[serde(default = "default_kg_limit")]
2988 pub limit: usize,
2989}
2990
2991fn default_kg_depth() -> u32 {
2992 3
2993}
2994
2995fn default_kg_limit() -> usize {
2996 100
2997}
2998
2999#[derive(Debug, Serialize)]
3001pub struct KgQueryResponse {
3002 pub agent_id: String,
3003 pub node_count: usize,
3004 pub edge_count: usize,
3005 pub edges: Vec<GraphEdgeResponse>,
3006}
3007
3008#[derive(Debug, Deserialize)]
3010pub struct KgPathParams {
3011 pub agent_id: String,
3013 pub from: String,
3015 pub to: String,
3017}
3018
3019#[derive(Debug, Serialize)]
3021pub struct KgPathResponse {
3022 pub agent_id: String,
3023 pub from_id: String,
3024 pub to_id: String,
3025 pub hop_count: usize,
3026 pub path: Vec<String>,
3027}
3028
3029#[derive(Debug, Deserialize)]
3031pub struct KgExportParams {
3032 pub agent_id: String,
3034 #[serde(default = "default_kg_format")]
3036 pub format: String,
3037}
3038
3039fn default_kg_format() -> String {
3040 "json".to_string()
3041}
3042
3043#[derive(Debug, Serialize)]
3045pub struct KgExportJsonResponse {
3046 pub agent_id: String,
3047 pub format: String,
3048 pub node_count: usize,
3049 pub edge_count: usize,
3050 pub edges: Vec<GraphEdgeResponse>,
3051}
3052
3053fn default_working_ttl() -> Option<u64> {
3058 Some(14_400) }
3060fn default_episodic_ttl() -> Option<u64> {
3061 Some(2_592_000) }
3063fn default_semantic_ttl() -> Option<u64> {
3064 Some(31_536_000) }
3066fn default_procedural_ttl() -> Option<u64> {
3067 Some(63_072_000) }
3069fn default_working_decay() -> DecayStrategy {
3070 DecayStrategy::Exponential
3071}
3072fn default_episodic_decay() -> DecayStrategy {
3073 DecayStrategy::PowerLaw
3074}
3075fn default_semantic_decay() -> DecayStrategy {
3076 DecayStrategy::Logarithmic
3077}
3078fn default_procedural_decay() -> DecayStrategy {
3079 DecayStrategy::Flat
3080}
3081fn default_sr_factor() -> f64 {
3082 1.0
3083}
3084fn default_sr_base_interval() -> u64 {
3085 86_400 }
3087fn default_consolidation_enabled() -> bool {
3088 false
3089}
3090fn default_policy_consolidation_threshold() -> f32 {
3091 0.92
3092}
3093fn default_consolidation_interval_hours() -> u32 {
3094 24
3095}
3096fn default_store_dedup_threshold() -> f32 {
3097 0.95
3098}
3099
3100#[derive(Debug, Clone, Serialize, Deserialize)]
3105pub struct MemoryPolicy {
3106 #[serde(
3109 default = "default_working_ttl",
3110 skip_serializing_if = "Option::is_none"
3111 )]
3112 pub working_ttl_seconds: Option<u64>,
3113 #[serde(
3115 default = "default_episodic_ttl",
3116 skip_serializing_if = "Option::is_none"
3117 )]
3118 pub episodic_ttl_seconds: Option<u64>,
3119 #[serde(
3121 default = "default_semantic_ttl",
3122 skip_serializing_if = "Option::is_none"
3123 )]
3124 pub semantic_ttl_seconds: Option<u64>,
3125 #[serde(
3127 default = "default_procedural_ttl",
3128 skip_serializing_if = "Option::is_none"
3129 )]
3130 pub procedural_ttl_seconds: Option<u64>,
3131
3132 #[serde(default = "default_working_decay")]
3135 pub working_decay: DecayStrategy,
3136 #[serde(default = "default_episodic_decay")]
3138 pub episodic_decay: DecayStrategy,
3139 #[serde(default = "default_semantic_decay")]
3141 pub semantic_decay: DecayStrategy,
3142 #[serde(default = "default_procedural_decay")]
3144 pub procedural_decay: DecayStrategy,
3145
3146 #[serde(default = "default_sr_factor")]
3151 pub spaced_repetition_factor: f64,
3152 #[serde(default = "default_sr_base_interval")]
3154 pub spaced_repetition_base_interval_seconds: u64,
3155
3156 #[serde(default = "default_consolidation_enabled")]
3159 pub consolidation_enabled: bool,
3160 #[serde(default = "default_policy_consolidation_threshold")]
3162 pub consolidation_threshold: f32,
3163 #[serde(default = "default_consolidation_interval_hours")]
3165 pub consolidation_interval_hours: u32,
3166 #[serde(default)]
3168 pub consolidated_count: u64,
3169
3170 #[serde(default)]
3174 pub rate_limit_enabled: bool,
3175 #[serde(default, skip_serializing_if = "Option::is_none")]
3178 pub rate_limit_stores_per_minute: Option<u32>,
3179 #[serde(default, skip_serializing_if = "Option::is_none")]
3182 pub rate_limit_recalls_per_minute: Option<u32>,
3183
3184 #[serde(default)]
3192 pub dedup_on_store: bool,
3193 #[serde(default = "default_store_dedup_threshold")]
3195 pub dedup_threshold: f32,
3196}
3197
3198impl Default for MemoryPolicy {
3199 fn default() -> Self {
3200 Self {
3201 working_ttl_seconds: default_working_ttl(),
3202 episodic_ttl_seconds: default_episodic_ttl(),
3203 semantic_ttl_seconds: default_semantic_ttl(),
3204 procedural_ttl_seconds: default_procedural_ttl(),
3205 working_decay: default_working_decay(),
3206 episodic_decay: default_episodic_decay(),
3207 semantic_decay: default_semantic_decay(),
3208 procedural_decay: default_procedural_decay(),
3209 spaced_repetition_factor: default_sr_factor(),
3210 spaced_repetition_base_interval_seconds: default_sr_base_interval(),
3211 consolidation_enabled: default_consolidation_enabled(),
3212 consolidation_threshold: default_policy_consolidation_threshold(),
3213 consolidation_interval_hours: default_consolidation_interval_hours(),
3214 consolidated_count: 0,
3215 rate_limit_enabled: false,
3216 rate_limit_stores_per_minute: None,
3217 rate_limit_recalls_per_minute: None,
3218 dedup_on_store: false,
3219 dedup_threshold: default_store_dedup_threshold(),
3220 }
3221 }
3222}
3223
3224impl MemoryPolicy {
3225 pub fn ttl_for_type(&self, memory_type: &MemoryType) -> Option<u64> {
3227 match memory_type {
3228 MemoryType::Working => self.working_ttl_seconds,
3229 MemoryType::Episodic => self.episodic_ttl_seconds,
3230 MemoryType::Semantic => self.semantic_ttl_seconds,
3231 MemoryType::Procedural => self.procedural_ttl_seconds,
3232 }
3233 }
3234
3235 pub fn decay_for_type(&self, memory_type: &MemoryType) -> DecayStrategy {
3237 match memory_type {
3238 MemoryType::Working => self.working_decay,
3239 MemoryType::Episodic => self.episodic_decay,
3240 MemoryType::Semantic => self.semantic_decay,
3241 MemoryType::Procedural => self.procedural_decay,
3242 }
3243 }
3244
3245 pub fn spaced_repetition_extension(&self, access_count: u32) -> u64 {
3249 if self.spaced_repetition_factor <= 0.0 {
3250 return 0;
3251 }
3252 let ext = access_count as f64
3253 * self.spaced_repetition_factor
3254 * self.spaced_repetition_base_interval_seconds as f64;
3255 ext.round() as u64
3256 }
3257}
3258
3259#[cfg(test)]
3260mod tests {
3261 use super::*;
3262
3263 #[test]
3266 fn test_memory_to_vector_from_vector_roundtrip() {
3267 let memory = Memory {
3268 id: "mem_abc123".to_string(),
3269 memory_type: MemoryType::Episodic,
3270 content: "Test content".to_string(),
3271 agent_id: "test-agent".to_string(),
3272 session_id: Some("sess_001".to_string()),
3273 importance: 0.8,
3274 tags: vec!["tag1".to_string(), "tag2".to_string()],
3275 metadata: Some(serde_json::json!({"key": "value"})),
3276 created_at: 1_700_000_000,
3277 last_accessed_at: 1_700_001_000,
3278 access_count: 5,
3279 ttl_seconds: None,
3280 expires_at: None,
3281 };
3282
3283 let embedding = vec![0.1f32, 0.2, 0.3];
3284 let vector = memory.to_vector(embedding.clone());
3285 assert_eq!(vector.id, memory.id);
3286 assert_eq!(vector.values, embedding);
3287
3288 let recovered =
3289 Memory::from_vector(&vector).expect("should reconstruct memory from vector");
3290 assert_eq!(recovered.id, memory.id);
3291 assert_eq!(recovered.content, memory.content);
3292 assert_eq!(recovered.agent_id, memory.agent_id);
3293 assert_eq!(recovered.session_id, memory.session_id);
3294 assert_eq!(recovered.tags, memory.tags);
3295 assert_eq!(recovered.access_count, memory.access_count);
3296 assert_eq!(recovered.created_at, memory.created_at);
3297 assert_eq!(recovered.last_accessed_at, memory.last_accessed_at);
3298 assert!(
3300 (recovered.importance - memory.importance).abs() < 1e-5,
3301 "importance mismatch: {} vs {}",
3302 recovered.importance,
3303 memory.importance
3304 );
3305 }
3306
3307 #[test]
3308 fn test_memory_from_vector_rejects_session_type() {
3309 let mut meta = serde_json::Map::new();
3310 meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
3311 let vector = Vector {
3312 id: "v1".to_string(),
3313 values: vec![],
3314 metadata: Some(serde_json::Value::Object(meta)),
3315 ttl_seconds: None,
3316 expires_at: None,
3317 };
3318 assert!(
3319 Memory::from_vector(&vector).is_none(),
3320 "from_vector should return None for wrong _dakera_type"
3321 );
3322 }
3323
3324 #[test]
3325 fn test_memory_from_vector_rejects_missing_metadata() {
3326 let vector = Vector {
3327 id: "v1".to_string(),
3328 values: vec![],
3329 metadata: None,
3330 ttl_seconds: None,
3331 expires_at: None,
3332 };
3333 assert!(Memory::from_vector(&vector).is_none());
3334 }
3335
3336 #[test]
3339 fn test_session_to_vector_from_vector_roundtrip() {
3340 let mut session = Session::new("sess_xyz".to_string(), "agent-007".to_string());
3341 session.ended_at = Some(1_700_005_000);
3342 session.summary = Some("Session ended cleanly".to_string());
3343 session.memory_count = 42;
3344
3345 let embedding = vec![0.5f32, 0.5, 0.5];
3346 let vector = session.to_vector(embedding.clone());
3347 assert_eq!(vector.id, session.id);
3348 assert_eq!(vector.values, embedding);
3349
3350 let recovered = Session::from_vector(&vector).expect("should reconstruct session");
3351 assert_eq!(recovered.id, session.id);
3352 assert_eq!(recovered.agent_id, session.agent_id);
3353 assert_eq!(recovered.ended_at, session.ended_at);
3354 assert_eq!(recovered.summary, session.summary);
3355 assert_eq!(recovered.memory_count, session.memory_count);
3356 }
3357
3358 #[test]
3359 fn test_session_from_vector_rejects_memory_type() {
3360 let mut meta = serde_json::Map::new();
3361 meta.insert("_dakera_type".to_string(), serde_json::json!("memory"));
3362 let vector = Vector {
3363 id: "sess_1".to_string(),
3364 values: vec![],
3365 metadata: Some(serde_json::Value::Object(meta)),
3366 ttl_seconds: None,
3367 expires_at: None,
3368 };
3369 assert!(
3370 Session::from_vector(&vector).is_none(),
3371 "from_vector should return None for wrong _dakera_type"
3372 );
3373 }
3374
3375 #[test]
3376 fn test_session_active_has_no_ended_at() {
3377 let session = Session::new("sess_active".to_string(), "agent-1".to_string());
3378 let vector = session.to_vector(vec![0.1]);
3379 let recovered = Session::from_vector(&vector).unwrap();
3380 assert!(recovered.ended_at.is_none());
3381 assert_eq!(recovered.memory_count, 0);
3382 }
3383
3384 #[test]
3387 fn test_pagination_cursor_encode_decode_roundtrip() {
3388 let cursor = PaginationCursor::new(0.75, "mem_abc".to_string());
3389 let encoded = cursor.encode();
3390 let decoded = PaginationCursor::decode(&encoded).expect("should decode valid cursor");
3391 assert!(
3392 (decoded.last_score - cursor.last_score).abs() < 1e-6,
3393 "score mismatch after decode"
3394 );
3395 assert_eq!(decoded.last_id, cursor.last_id);
3396 }
3397
3398 #[test]
3399 fn test_pagination_cursor_decode_invalid_returns_none() {
3400 assert!(PaginationCursor::decode("not-valid-base64!!!").is_none());
3401 assert!(PaginationCursor::decode("").is_none());
3402 assert!(PaginationCursor::decode("aGVsbG8=").is_none()); }
3405
3406 #[test]
3409 fn test_distance_metric_serde_round_trip() {
3410 let cases = [
3411 (DistanceMetric::Cosine, "\"cosine\""),
3412 (DistanceMetric::Euclidean, "\"euclidean\""),
3413 (DistanceMetric::DotProduct, "\"dot_product\""),
3414 ];
3415 for (metric, expected_json) in &cases {
3416 let serialized = serde_json::to_string(metric).unwrap();
3417 assert_eq!(
3418 &serialized, expected_json,
3419 "serialized form mismatch for {:?}",
3420 metric
3421 );
3422 let deserialized: DistanceMetric = serde_json::from_str(&serialized).unwrap();
3423 assert_eq!(*metric, deserialized);
3424 }
3425 }
3426
3427 #[test]
3430 fn test_vector_is_expired_at_not_yet_expired() {
3431 let vector = Vector {
3432 id: "v1".to_string(),
3433 values: vec![1.0],
3434 metadata: None,
3435 ttl_seconds: None,
3436 expires_at: Some(u64::MAX),
3437 };
3438 assert!(!vector.is_expired_at(0));
3439 assert!(!vector.is_expired_at(1_000_000));
3440 }
3441
3442 #[test]
3443 fn test_vector_is_expired_at_past_expiry() {
3444 let vector = Vector {
3445 id: "v1".to_string(),
3446 values: vec![1.0],
3447 metadata: None,
3448 ttl_seconds: None,
3449 expires_at: Some(100),
3450 };
3451 assert!(vector.is_expired_at(100), "at boundary should be expired");
3452 assert!(vector.is_expired_at(200));
3453 assert!(!vector.is_expired_at(99));
3454 }
3455
3456 #[test]
3457 fn test_vector_no_expiry_never_expires() {
3458 let vector = Vector {
3459 id: "v1".to_string(),
3460 values: vec![1.0],
3461 metadata: None,
3462 ttl_seconds: None,
3463 expires_at: None,
3464 };
3465 assert!(!vector.is_expired_at(u64::MAX));
3466 }
3467
3468 #[test]
3471 fn test_column_upsert_mismatched_vectors_length_errors() {
3472 let req = ColumnUpsertRequest {
3473 ids: vec!["a".to_string(), "b".to_string()],
3474 vectors: vec![vec![1.0, 2.0]], attributes: Default::default(),
3476 ttl_seconds: None,
3477 dimension: None,
3478 };
3479 assert!(
3480 req.to_vectors().is_err(),
3481 "should error when vectors.len() != ids.len()"
3482 );
3483 }
3484
3485 #[test]
3486 fn test_column_upsert_mismatched_attribute_length_errors() {
3487 let mut attrs = std::collections::HashMap::new();
3488 attrs.insert(
3489 "score".to_string(),
3490 vec![serde_json::json!(1.0)], );
3492 let req = ColumnUpsertRequest {
3493 ids: vec!["a".to_string(), "b".to_string()],
3494 vectors: vec![vec![1.0], vec![2.0]],
3495 attributes: attrs,
3496 ttl_seconds: None,
3497 dimension: None,
3498 };
3499 assert!(
3500 req.to_vectors().is_err(),
3501 "should error when attribute array length != ids.len()"
3502 );
3503 }
3504
3505 #[test]
3506 fn test_column_upsert_mismatched_dimension_errors() {
3507 let req = ColumnUpsertRequest {
3508 ids: vec!["a".to_string(), "b".to_string()],
3509 vectors: vec![vec![1.0, 2.0], vec![1.0]], attributes: Default::default(),
3511 ttl_seconds: None,
3512 dimension: None,
3513 };
3514 assert!(
3515 req.to_vectors().is_err(),
3516 "should error on dimension mismatch between vectors"
3517 );
3518 }
3519
3520 #[test]
3521 fn test_column_upsert_success() {
3522 let req = ColumnUpsertRequest {
3523 ids: vec!["a".to_string(), "b".to_string()],
3524 vectors: vec![vec![1.0, 0.0], vec![0.0, 1.0]],
3525 attributes: Default::default(),
3526 ttl_seconds: None,
3527 dimension: Some(2),
3528 };
3529 let result = req.to_vectors().expect("valid request should succeed");
3530 assert_eq!(result.len(), 2);
3531 assert_eq!(result[0].id, "a");
3532 assert_eq!(result[1].id, "b");
3533 assert_eq!(result[0].values, vec![1.0, 0.0]);
3534 assert_eq!(result[1].values, vec![0.0, 1.0]);
3535 }
3536
3537 #[test]
3538 fn test_ce141_to_vector_metadata_surfaces_content_date() {
3539 let mut memory = Memory::new(
3540 "m1".to_string(),
3541 "test content".to_string(),
3542 "agent1".to_string(),
3543 MemoryType::Episodic,
3544 );
3545 let mut meta = serde_json::Map::new();
3546 meta.insert(
3547 "_dakera_content_date".to_string(),
3548 serde_json::json!(1625097600_i64),
3549 );
3550 memory.metadata = Some(serde_json::Value::Object(meta));
3551
3552 let vec_meta = memory.to_vector_metadata();
3553 let obj = vec_meta.as_object().unwrap();
3554 assert_eq!(
3555 obj.get("_dakera_content_date"),
3556 Some(&serde_json::json!(1625097600_i64))
3557 );
3558 assert!(obj
3559 .get("user_metadata")
3560 .unwrap()
3561 .get("_dakera_content_date")
3562 .is_some());
3563 }
3564
3565 #[test]
3566 fn test_ce141_to_vector_metadata_no_content_date_when_absent() {
3567 let memory = Memory::new(
3568 "m1".to_string(),
3569 "test content".to_string(),
3570 "agent1".to_string(),
3571 MemoryType::Episodic,
3572 );
3573 let vec_meta = memory.to_vector_metadata();
3574 let obj = vec_meta.as_object().unwrap();
3575 assert!(obj.get("_dakera_content_date").is_none());
3576 }
3577}