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 #[serde(default)]
1120 pub cursor: Option<String>,
1121}
1122
1123#[derive(Debug, Clone, Serialize)]
1131pub struct RankByInput(pub RankBy);
1132
1133impl<'de> Deserialize<'de> for RankByInput {
1139 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1140 where
1141 D: serde::Deserializer<'de>,
1142 {
1143 let value = serde_json::Value::deserialize(deserializer)?;
1144 parse_rank_by(&value).map(RankByInput).ok_or_else(|| {
1145 serde::de::Error::custom(
1146 "invalid rank_by: expected a ranking expression such as \
1147 [\"vector\",\"ANN\",[...]], [\"text\",\"BM25\",\"query\"], \
1148 [\"field\",\"desc\"], or a Sum/Product combinator",
1149 )
1150 })
1151 }
1152}
1153
1154fn parse_rank_by(value: &serde_json::Value) -> Option<RankBy> {
1156 let arr = value.as_array()?;
1157 if arr.is_empty() {
1158 return None;
1159 }
1160
1161 let first = arr.first()?.as_str()?;
1162
1163 match first {
1164 "Sum" => {
1166 let rankings = arr.get(1)?.as_array()?;
1167 let parsed: Option<Vec<RankBy>> = rankings.iter().map(parse_rank_by).collect();
1168 Some(RankBy::Sum(parsed?))
1169 }
1170 "Max" => {
1171 let rankings = arr.get(1)?.as_array()?;
1172 let parsed: Option<Vec<RankBy>> = rankings.iter().map(parse_rank_by).collect();
1173 Some(RankBy::Max(parsed?))
1174 }
1175 "Product" => {
1176 let weight = arr.get(1)?.as_f64()? as f32;
1177 let ranking = parse_rank_by(arr.get(2)?)?;
1178 Some(RankBy::Product {
1179 weight,
1180 ranking: Box::new(ranking),
1181 })
1182 }
1183 "ANN" => {
1185 let query_vector = parse_vector_array(arr.get(1)?)?;
1186 Some(RankBy::VectorSearch {
1187 field: "vector".to_string(),
1188 method: VectorSearchMethod::ANN,
1189 query_vector,
1190 })
1191 }
1192 "kNN" => {
1193 let query_vector = parse_vector_array(arr.get(1)?)?;
1194 Some(RankBy::VectorSearch {
1195 field: "vector".to_string(),
1196 method: VectorSearchMethod::KNN,
1197 query_vector,
1198 })
1199 }
1200 field => {
1202 let second = arr.get(1)?;
1203
1204 if let Some(method_str) = second.as_str() {
1206 match method_str {
1207 "ANN" => {
1208 let query_vector = parse_vector_array(arr.get(2)?)?;
1209 Some(RankBy::VectorSearch {
1210 field: field.to_string(),
1211 method: VectorSearchMethod::ANN,
1212 query_vector,
1213 })
1214 }
1215 "kNN" => {
1216 let query_vector = parse_vector_array(arr.get(2)?)?;
1217 Some(RankBy::VectorSearch {
1218 field: field.to_string(),
1219 method: VectorSearchMethod::KNN,
1220 query_vector,
1221 })
1222 }
1223 "BM25" => {
1224 let query = arr.get(2)?.as_str()?;
1225 Some(RankBy::FullTextSearch {
1226 field: field.to_string(),
1227 method: "BM25".to_string(),
1228 query: query.to_string(),
1229 })
1230 }
1231 "asc" => Some(RankBy::AttributeOrder {
1232 field: field.to_string(),
1233 direction: SortDirection::Asc,
1234 }),
1235 "desc" => Some(RankBy::AttributeOrder {
1236 field: field.to_string(),
1237 direction: SortDirection::Desc,
1238 }),
1239 _ => None,
1240 }
1241 } else {
1242 None
1243 }
1244 }
1245 }
1246}
1247
1248fn parse_vector_array(value: &serde_json::Value) -> Option<Vec<f32>> {
1250 let arr = value.as_array()?;
1251 arr.iter().map(|v| v.as_f64().map(|n| n as f32)).collect()
1252}
1253
1254#[derive(Debug, Serialize, Deserialize)]
1256pub struct UnifiedQueryResponse {
1257 pub results: Vec<UnifiedSearchResult>,
1259 #[serde(skip_serializing_if = "Option::is_none")]
1261 pub next_cursor: Option<String>,
1262}
1263
1264#[derive(Debug, Serialize, Deserialize)]
1266pub struct UnifiedSearchResult {
1267 pub id: String,
1269 #[serde(rename = "$dist", skip_serializing_if = "Option::is_none")]
1272 pub dist: Option<f32>,
1273 #[serde(skip_serializing_if = "Option::is_none")]
1275 pub metadata: Option<serde_json::Value>,
1276 #[serde(skip_serializing_if = "Option::is_none")]
1278 pub vector: Option<Vec<f32>>,
1279}
1280
1281#[derive(Debug, Clone, Serialize, Deserialize)]
1287pub enum AggregateFunction {
1288 Count,
1290 Sum { field: String },
1292 Avg { field: String },
1294 Min { field: String },
1296 Max { field: String },
1298}
1299
1300#[derive(Debug, Clone, Serialize, Deserialize)]
1302#[serde(from = "serde_json::Value")]
1303pub struct AggregateFunctionInput(pub AggregateFunction);
1304
1305impl From<serde_json::Value> for AggregateFunctionInput {
1306 fn from(value: serde_json::Value) -> Self {
1307 parse_aggregate_function(&value)
1308 .map(AggregateFunctionInput)
1309 .unwrap_or_else(|| {
1310 AggregateFunctionInput(AggregateFunction::Count)
1312 })
1313 }
1314}
1315
1316fn parse_aggregate_function(value: &serde_json::Value) -> Option<AggregateFunction> {
1318 let arr = value.as_array()?;
1319 if arr.is_empty() {
1320 return None;
1321 }
1322
1323 let func_name = arr.first()?.as_str()?;
1324
1325 match func_name {
1326 "Count" => Some(AggregateFunction::Count),
1327 "Sum" => {
1328 let field = arr.get(1)?.as_str()?;
1329 Some(AggregateFunction::Sum {
1330 field: field.to_string(),
1331 })
1332 }
1333 "Avg" => {
1334 let field = arr.get(1)?.as_str()?;
1335 Some(AggregateFunction::Avg {
1336 field: field.to_string(),
1337 })
1338 }
1339 "Min" => {
1340 let field = arr.get(1)?.as_str()?;
1341 Some(AggregateFunction::Min {
1342 field: field.to_string(),
1343 })
1344 }
1345 "Max" => {
1346 let field = arr.get(1)?.as_str()?;
1347 Some(AggregateFunction::Max {
1348 field: field.to_string(),
1349 })
1350 }
1351 _ => None,
1352 }
1353}
1354
1355#[derive(Debug, Deserialize)]
1357pub struct AggregationRequest {
1358 pub aggregate_by: std::collections::HashMap<String, AggregateFunctionInput>,
1361 #[serde(default)]
1364 pub group_by: Vec<String>,
1365 #[serde(default)]
1367 pub filter: Option<FilterExpression>,
1368 #[serde(default = "default_agg_limit")]
1370 pub limit: usize,
1371}
1372
1373fn default_agg_limit() -> usize {
1374 100
1375}
1376
1377#[derive(Debug, Serialize, Deserialize)]
1379pub struct AggregationResponse {
1380 #[serde(skip_serializing_if = "Option::is_none")]
1382 pub aggregations: Option<std::collections::HashMap<String, serde_json::Value>>,
1383 #[serde(skip_serializing_if = "Option::is_none")]
1385 pub aggregation_groups: Option<Vec<AggregationGroup>>,
1386}
1387
1388#[derive(Debug, Serialize, Deserialize)]
1390pub struct AggregationGroup {
1391 #[serde(flatten)]
1393 pub group_key: std::collections::HashMap<String, serde_json::Value>,
1394 #[serde(flatten)]
1396 pub aggregations: std::collections::HashMap<String, serde_json::Value>,
1397}
1398
1399#[derive(Debug, Clone, Serialize, Deserialize)]
1405pub struct TextDocument {
1406 pub id: VectorId,
1408 pub text: String,
1410 #[serde(skip_serializing_if = "Option::is_none")]
1412 pub metadata: Option<serde_json::Value>,
1413 #[serde(skip_serializing_if = "Option::is_none")]
1415 pub ttl_seconds: Option<u64>,
1416}
1417
1418#[derive(Debug, Deserialize)]
1420pub struct TextUpsertRequest {
1421 pub documents: Vec<TextDocument>,
1423 #[serde(default)]
1425 pub model: Option<EmbeddingModelType>,
1426}
1427
1428#[derive(Debug, Serialize, Deserialize)]
1430pub struct TextUpsertResponse {
1431 pub upserted_count: usize,
1433 pub tokens_processed: usize,
1435 pub model: EmbeddingModelType,
1437 pub embedding_time_ms: u64,
1439}
1440
1441#[derive(Debug, Deserialize)]
1443pub struct TextQueryRequest {
1444 pub text: String,
1446 #[serde(default = "default_top_k")]
1448 pub top_k: usize,
1449 #[serde(default)]
1451 pub filter: Option<FilterExpression>,
1452 #[serde(default)]
1454 pub include_vectors: bool,
1455 #[serde(default = "default_true")]
1457 pub include_text: bool,
1458 #[serde(default)]
1460 pub model: Option<EmbeddingModelType>,
1461}
1462
1463#[derive(Debug, Serialize, Deserialize)]
1465pub struct TextQueryResponse {
1466 pub results: Vec<TextSearchResult>,
1468 pub model: EmbeddingModelType,
1470 pub embedding_time_ms: u64,
1472 pub search_time_ms: u64,
1474}
1475
1476#[derive(Debug, Serialize, Deserialize)]
1478pub struct TextSearchResult {
1479 pub id: VectorId,
1481 pub score: f32,
1483 #[serde(skip_serializing_if = "Option::is_none")]
1485 pub text: Option<String>,
1486 #[serde(skip_serializing_if = "Option::is_none")]
1488 pub metadata: Option<serde_json::Value>,
1489 #[serde(skip_serializing_if = "Option::is_none")]
1491 pub vector: Option<Vec<f32>>,
1492}
1493
1494#[derive(Debug, Deserialize)]
1496pub struct BatchTextQueryRequest {
1497 pub queries: Vec<String>,
1499 #[serde(default = "default_top_k")]
1501 pub top_k: usize,
1502 #[serde(default)]
1504 pub filter: Option<FilterExpression>,
1505 #[serde(default)]
1507 pub include_vectors: bool,
1508 #[serde(default)]
1510 pub model: Option<EmbeddingModelType>,
1511}
1512
1513#[derive(Debug, Serialize, Deserialize)]
1515pub struct BatchTextQueryResponse {
1516 pub results: Vec<Vec<TextSearchResult>>,
1518 pub model: EmbeddingModelType,
1520 pub embedding_time_ms: u64,
1522 pub search_time_ms: u64,
1524}
1525
1526#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1534pub enum EmbeddingModelType {
1535 #[default]
1537 #[serde(rename = "bge-large")]
1538 BgeLarge,
1539 #[serde(rename = "minilm")]
1541 MiniLM,
1542 #[serde(rename = "bge-small")]
1544 BgeSmall,
1545 #[serde(rename = "e5-small")]
1547 E5Small,
1548 #[serde(rename = "modernbert-embed-base")]
1550 ModernBertEmbedBase,
1551 #[serde(rename = "gte-modernbert-base")]
1553 GteModernBertBase,
1554}
1555
1556impl EmbeddingModelType {
1557 pub fn dimension(&self) -> usize {
1559 match self {
1560 EmbeddingModelType::BgeLarge => 1024,
1561 EmbeddingModelType::MiniLM => 384,
1562 EmbeddingModelType::BgeSmall => 384,
1563 EmbeddingModelType::E5Small => 384,
1564 EmbeddingModelType::ModernBertEmbedBase => 768,
1565 EmbeddingModelType::GteModernBertBase => 768,
1566 }
1567 }
1568}
1569
1570impl std::fmt::Display for EmbeddingModelType {
1571 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1572 match self {
1573 EmbeddingModelType::BgeLarge => write!(f, "bge-large"),
1574 EmbeddingModelType::MiniLM => write!(f, "minilm"),
1575 EmbeddingModelType::BgeSmall => write!(f, "bge-small"),
1576 EmbeddingModelType::E5Small => write!(f, "e5-small"),
1577 EmbeddingModelType::ModernBertEmbedBase => write!(f, "modernbert-embed-base"),
1578 EmbeddingModelType::GteModernBertBase => write!(f, "gte-modernbert-base"),
1579 }
1580 }
1581}
1582
1583#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1589#[serde(rename_all = "snake_case")]
1590#[derive(Default)]
1591pub enum MemoryType {
1592 #[default]
1594 Episodic,
1595 Semantic,
1597 Procedural,
1599 Working,
1601}
1602
1603impl std::fmt::Display for MemoryType {
1604 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1605 match self {
1606 MemoryType::Episodic => write!(f, "episodic"),
1607 MemoryType::Semantic => write!(f, "semantic"),
1608 MemoryType::Procedural => write!(f, "procedural"),
1609 MemoryType::Working => write!(f, "working"),
1610 }
1611 }
1612}
1613
1614#[derive(Debug, Clone, Serialize, Deserialize)]
1616pub struct Memory {
1617 pub id: String,
1618 #[serde(default)]
1619 pub memory_type: MemoryType,
1620 pub content: String,
1621 pub agent_id: String,
1622 #[serde(skip_serializing_if = "Option::is_none")]
1623 pub session_id: Option<String>,
1624 #[serde(default = "default_importance")]
1625 pub importance: f32,
1626 #[serde(default)]
1627 pub tags: Vec<String>,
1628 #[serde(skip_serializing_if = "Option::is_none")]
1629 pub metadata: Option<serde_json::Value>,
1630 pub created_at: u64,
1631 pub last_accessed_at: u64,
1632 #[serde(default)]
1633 pub access_count: u32,
1634 #[serde(skip_serializing_if = "Option::is_none")]
1635 pub ttl_seconds: Option<u64>,
1636 #[serde(skip_serializing_if = "Option::is_none")]
1637 pub expires_at: Option<u64>,
1638}
1639
1640fn default_importance() -> f32 {
1641 0.5
1642}
1643
1644impl Memory {
1645 pub fn new(id: String, content: String, agent_id: String, memory_type: MemoryType) -> Self {
1647 let now = std::time::SystemTime::now()
1648 .duration_since(std::time::UNIX_EPOCH)
1649 .unwrap_or_default()
1650 .as_secs();
1651 Self {
1652 id,
1653 memory_type,
1654 content,
1655 agent_id,
1656 session_id: None,
1657 importance: 0.5,
1658 tags: Vec::new(),
1659 metadata: None,
1660 created_at: now,
1661 last_accessed_at: now,
1662 access_count: 0,
1663 ttl_seconds: None,
1664 expires_at: None,
1665 }
1666 }
1667
1668 pub fn is_expired(&self) -> bool {
1670 if let Some(expires_at) = self.expires_at {
1671 let now = std::time::SystemTime::now()
1672 .duration_since(std::time::UNIX_EPOCH)
1673 .unwrap_or_default()
1674 .as_secs();
1675 now >= expires_at
1676 } else {
1677 false
1678 }
1679 }
1680
1681 pub fn to_vector_metadata(&self) -> serde_json::Value {
1683 let mut meta = serde_json::Map::new();
1684 meta.insert("_dakera_type".to_string(), serde_json::json!("memory"));
1685 meta.insert(
1686 "memory_type".to_string(),
1687 serde_json::json!(self.memory_type),
1688 );
1689 meta.insert("content".to_string(), serde_json::json!(self.content));
1690 meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1691 if let Some(ref sid) = self.session_id {
1692 meta.insert("session_id".to_string(), serde_json::json!(sid));
1693 }
1694 meta.insert("importance".to_string(), serde_json::json!(self.importance));
1695 meta.insert("tags".to_string(), serde_json::json!(self.tags));
1696 meta.insert("created_at".to_string(), serde_json::json!(self.created_at));
1697 meta.insert(
1698 "last_accessed_at".to_string(),
1699 serde_json::json!(self.last_accessed_at),
1700 );
1701 meta.insert(
1702 "access_count".to_string(),
1703 serde_json::json!(self.access_count),
1704 );
1705 if let Some(ref ttl) = self.ttl_seconds {
1706 meta.insert("ttl_seconds".to_string(), serde_json::json!(ttl));
1707 }
1708 if let Some(ref expires) = self.expires_at {
1709 meta.insert("expires_at".to_string(), serde_json::json!(expires));
1710 }
1711 if let Some(ref user_meta) = self.metadata {
1712 if let Some(cd) = user_meta.get("_dakera_content_date") {
1713 meta.insert("_dakera_content_date".to_string(), cd.clone());
1714 }
1715 meta.insert("user_metadata".to_string(), user_meta.clone());
1716 }
1717 serde_json::Value::Object(meta)
1718 }
1719
1720 pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1722 let mut v = Vector {
1723 id: self.id.clone(),
1724 values: embedding,
1725 metadata: Some(self.to_vector_metadata()),
1726 ttl_seconds: self.ttl_seconds,
1727 expires_at: self.expires_at,
1728 };
1729 v.apply_ttl();
1730 v
1731 }
1732
1733 pub fn from_vector(vector: &Vector) -> Option<Self> {
1735 let meta = vector.metadata.as_ref()?.as_object()?;
1736 let entry_type = meta.get("_dakera_type")?.as_str()?;
1737 if entry_type != "memory" {
1738 return None;
1739 }
1740
1741 Some(Memory {
1742 id: vector.id.clone(),
1743 memory_type: serde_json::from_value(meta.get("memory_type")?.clone())
1744 .unwrap_or_default(),
1745 content: meta.get("content")?.as_str()?.to_string(),
1746 agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1747 session_id: meta
1748 .get("session_id")
1749 .and_then(|v| v.as_str())
1750 .map(String::from),
1751 importance: meta
1752 .get("importance")
1753 .and_then(|v| v.as_f64())
1754 .unwrap_or(0.5) as f32,
1755 tags: meta
1756 .get("tags")
1757 .and_then(|v| serde_json::from_value(v.clone()).ok())
1758 .unwrap_or_default(),
1759 metadata: meta.get("user_metadata").cloned(),
1760 created_at: meta.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0),
1761 last_accessed_at: meta
1762 .get("last_accessed_at")
1763 .and_then(|v| v.as_u64())
1764 .unwrap_or(0),
1765 access_count: meta
1766 .get("access_count")
1767 .and_then(|v| v.as_u64())
1768 .unwrap_or(0) as u32,
1769 ttl_seconds: vector.ttl_seconds,
1770 expires_at: vector.expires_at,
1771 })
1772 }
1773}
1774
1775#[derive(Debug, Clone, Serialize, Deserialize)]
1777pub struct Session {
1778 pub id: String,
1779 pub agent_id: String,
1780 pub started_at: u64,
1781 #[serde(skip_serializing_if = "Option::is_none")]
1782 pub ended_at: Option<u64>,
1783 #[serde(skip_serializing_if = "Option::is_none")]
1784 pub summary: Option<String>,
1785 #[serde(skip_serializing_if = "Option::is_none")]
1786 pub metadata: Option<serde_json::Value>,
1787 #[serde(default)]
1789 pub memory_count: usize,
1790}
1791
1792impl Session {
1793 pub fn new(id: String, agent_id: String) -> Self {
1794 let now = std::time::SystemTime::now()
1795 .duration_since(std::time::UNIX_EPOCH)
1796 .unwrap_or_default()
1797 .as_secs();
1798 Self {
1799 id,
1800 agent_id,
1801 started_at: now,
1802 ended_at: None,
1803 summary: None,
1804 metadata: None,
1805 memory_count: 0,
1806 }
1807 }
1808
1809 pub fn to_vector_metadata(&self) -> serde_json::Value {
1811 let mut meta = serde_json::Map::new();
1812 meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
1813 meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1814 meta.insert("started_at".to_string(), serde_json::json!(self.started_at));
1815 if let Some(ref ended) = self.ended_at {
1816 meta.insert("ended_at".to_string(), serde_json::json!(ended));
1817 }
1818 if let Some(ref summary) = self.summary {
1819 meta.insert("summary".to_string(), serde_json::json!(summary));
1820 }
1821 if let Some(ref user_meta) = self.metadata {
1822 meta.insert("user_metadata".to_string(), user_meta.clone());
1823 }
1824 meta.insert(
1825 "memory_count".to_string(),
1826 serde_json::json!(self.memory_count),
1827 );
1828 serde_json::Value::Object(meta)
1829 }
1830
1831 pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1833 Vector {
1834 id: self.id.clone(),
1835 values: embedding,
1836 metadata: Some(self.to_vector_metadata()),
1837 ttl_seconds: None,
1838 expires_at: None,
1839 }
1840 }
1841
1842 pub fn from_vector(vector: &Vector) -> Option<Self> {
1844 let meta = vector.metadata.as_ref()?.as_object()?;
1845 let entry_type = meta.get("_dakera_type")?.as_str()?;
1846 if entry_type != "session" {
1847 return None;
1848 }
1849
1850 Some(Session {
1851 id: vector.id.clone(),
1852 agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1853 started_at: meta.get("started_at").and_then(|v| v.as_u64()).unwrap_or(0),
1854 ended_at: meta.get("ended_at").and_then(|v| v.as_u64()),
1855 summary: meta
1856 .get("summary")
1857 .and_then(|v| v.as_str())
1858 .map(String::from),
1859 metadata: meta.get("user_metadata").cloned(),
1860 memory_count: meta
1861 .get("memory_count")
1862 .and_then(|v| v.as_u64())
1863 .unwrap_or(0) as usize,
1864 })
1865 }
1866}
1867
1868#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1870#[serde(rename_all = "snake_case")]
1871#[derive(Default)]
1872pub enum DecayStrategy {
1873 #[default]
1874 Exponential,
1875 Linear,
1876 StepFunction,
1877 PowerLaw,
1879 Logarithmic,
1881 Flat,
1883}
1884
1885#[derive(Debug, Clone, Serialize, Deserialize)]
1887pub struct DecayConfig {
1888 #[serde(default)]
1889 pub strategy: DecayStrategy,
1890 #[serde(default = "default_half_life_hours")]
1891 pub half_life_hours: f64,
1892 #[serde(default = "default_min_importance")]
1893 pub min_importance: f32,
1894}
1895
1896fn default_half_life_hours() -> f64 {
1897 168.0 }
1899
1900fn default_min_importance() -> f32 {
1901 0.01
1902}
1903
1904impl Default for DecayConfig {
1905 fn default() -> Self {
1906 Self {
1907 strategy: DecayStrategy::default(),
1908 half_life_hours: default_half_life_hours(),
1909 min_importance: default_min_importance(),
1910 }
1911 }
1912}
1913
1914#[derive(Debug, Deserialize)]
1920pub struct StoreMemoryRequest {
1921 pub content: String,
1922 pub agent_id: String,
1923 #[serde(default)]
1924 pub memory_type: MemoryType,
1925 #[serde(skip_serializing_if = "Option::is_none")]
1926 pub session_id: Option<String>,
1927 #[serde(default = "default_importance")]
1928 pub importance: f32,
1929 #[serde(default)]
1930 pub tags: Vec<String>,
1931 #[serde(skip_serializing_if = "Option::is_none")]
1932 pub metadata: Option<serde_json::Value>,
1933 #[serde(skip_serializing_if = "Option::is_none")]
1934 pub ttl_seconds: Option<u64>,
1935 #[serde(skip_serializing_if = "Option::is_none")]
1940 pub expires_at: Option<u64>,
1941 #[serde(skip_serializing_if = "Option::is_none")]
1943 pub id: Option<String>,
1944}
1945
1946#[derive(Debug, Serialize)]
1948pub struct StoreMemoryResponse {
1949 pub memory: Memory,
1950 pub embedding_time_ms: u64,
1951}
1952
1953#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1957#[serde(rename_all = "lowercase")]
1958pub enum RoutingMode {
1959 #[default]
1961 Auto,
1962 Vector,
1964 Bm25,
1966 Hybrid,
1968}
1969
1970#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1975#[serde(rename_all = "lowercase")]
1976pub enum FusionStrategy {
1977 Rrf,
1982 #[default]
1985 MinMax,
1986}
1987
1988#[derive(Debug, Clone, Deserialize)]
1990pub struct RecallRequest {
1991 pub query: String,
1992 pub agent_id: String,
1993 #[serde(default = "default_top_k")]
1994 pub top_k: usize,
1995 #[serde(default)]
1996 pub memory_type: Option<MemoryType>,
1997 #[serde(default)]
1998 pub session_id: Option<String>,
1999 #[serde(default)]
2000 pub tags: Option<Vec<String>>,
2001 #[serde(default)]
2002 pub min_importance: Option<f32>,
2003 #[serde(default = "default_true")]
2005 pub importance_weighted: bool,
2006 #[serde(default)]
2008 pub include_associated: bool,
2009 #[serde(default)]
2011 pub associated_memories_cap: Option<usize>,
2012 #[serde(default)]
2014 pub since: Option<String>,
2015 #[serde(default)]
2017 pub until: Option<String>,
2018 #[serde(default)]
2021 pub associated_memories_depth: Option<u8>,
2022 #[serde(default)]
2025 pub associated_memories_min_weight: Option<f32>,
2026 #[serde(default)]
2030 pub routing: RoutingMode,
2031 #[serde(default = "default_true")]
2035 pub rerank: bool,
2036 #[serde(default)]
2039 pub fusion: FusionStrategy,
2040 #[serde(default)]
2044 pub vector_weight: Option<f32>,
2045 #[serde(default)]
2052 pub iterations: Option<u8>,
2053 #[serde(default = "default_true")]
2057 pub neighborhood: bool,
2058}
2059
2060#[derive(Debug, Serialize, Deserialize)]
2062pub struct RecallResult {
2063 pub memory: Memory,
2064 pub score: f32,
2065 #[serde(skip_serializing_if = "Option::is_none")]
2067 pub weighted_score: Option<f32>,
2068 #[serde(skip_serializing_if = "Option::is_none")]
2070 pub smart_score: Option<f32>,
2071 #[serde(skip_serializing_if = "Option::is_none")]
2074 pub depth: Option<u8>,
2075 #[serde(skip_serializing_if = "Option::is_none")]
2078 pub vector_score: Option<f32>,
2079 #[serde(skip_serializing_if = "Option::is_none")]
2082 pub text_score: Option<f32>,
2083}
2084
2085#[derive(Debug, Serialize)]
2087pub struct RecallResponse {
2088 pub memories: Vec<RecallResult>,
2089 pub query_embedding_time_ms: u64,
2090 pub search_time_ms: u64,
2091 #[serde(skip_serializing_if = "Option::is_none")]
2094 pub associated_memories: Option<Vec<RecallResult>>,
2095}
2096
2097#[derive(Debug, Deserialize)]
2099pub struct ForgetRequest {
2100 pub agent_id: String,
2101 #[serde(default)]
2102 pub memory_ids: Option<Vec<String>>,
2103 #[serde(default)]
2104 pub memory_type: Option<MemoryType>,
2105 #[serde(default)]
2106 pub session_id: Option<String>,
2107 #[serde(default)]
2108 pub tags: Option<Vec<String>>,
2109 #[serde(default)]
2111 pub below_importance: Option<f32>,
2112}
2113
2114#[derive(Debug, Serialize)]
2116pub struct ForgetResponse {
2117 pub deleted_count: usize,
2118}
2119
2120#[derive(Debug, Deserialize)]
2122pub struct UpdateMemoryRequest {
2123 #[serde(default)]
2124 pub content: Option<String>,
2125 #[serde(default)]
2126 pub importance: Option<f32>,
2127 #[serde(default)]
2128 pub tags: Option<Vec<String>>,
2129 #[serde(default)]
2130 pub metadata: Option<serde_json::Value>,
2131 #[serde(default)]
2132 pub memory_type: Option<MemoryType>,
2133}
2134
2135#[derive(Debug, Deserialize)]
2137pub struct UpdateImportanceRequest {
2138 pub memory_id: String,
2139 pub importance: f32,
2140 pub agent_id: String,
2141}
2142
2143#[derive(Debug, Deserialize)]
2145pub struct ConsolidateRequest {
2146 pub agent_id: String,
2147 #[serde(default)]
2149 pub memory_ids: Option<Vec<String>>,
2150 #[serde(default = "default_consolidation_threshold")]
2152 pub threshold: f32,
2153 #[serde(default)]
2155 pub target_type: Option<MemoryType>,
2156}
2157
2158fn default_consolidation_threshold() -> f32 {
2159 0.85
2160}
2161
2162#[derive(Debug, Serialize)]
2164pub struct ConsolidateResponse {
2165 pub consolidated_memory: Memory,
2166 pub source_memory_ids: Vec<String>,
2167 pub memories_removed: usize,
2168}
2169
2170#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2172#[serde(rename_all = "lowercase")]
2173pub enum FeedbackSignal {
2174 Upvote,
2176 Downvote,
2178 Flag,
2180 Positive,
2182 Negative,
2184}
2185
2186#[derive(Debug, Clone, Serialize, Deserialize)]
2188pub struct FeedbackHistoryEntry {
2189 pub signal: FeedbackSignal,
2190 pub timestamp: u64,
2191 pub old_importance: f32,
2192 pub new_importance: f32,
2193}
2194
2195#[derive(Debug, Deserialize)]
2197pub struct FeedbackRequest {
2198 pub agent_id: String,
2199 pub memory_id: String,
2200 pub signal: FeedbackSignal,
2201}
2202
2203#[derive(Debug, Deserialize)]
2205pub struct MemoryFeedbackRequest {
2206 pub agent_id: String,
2207 pub signal: FeedbackSignal,
2208}
2209
2210#[derive(Debug, Serialize)]
2212pub struct FeedbackResponse {
2213 pub memory_id: String,
2214 pub new_importance: f32,
2215 pub signal: FeedbackSignal,
2216}
2217
2218#[derive(Debug, Serialize)]
2220pub struct FeedbackHistoryResponse {
2221 pub memory_id: String,
2222 pub entries: Vec<FeedbackHistoryEntry>,
2223}
2224
2225#[derive(Debug, Serialize)]
2227pub struct AgentFeedbackSummary {
2228 pub agent_id: String,
2229 pub upvotes: u64,
2230 pub downvotes: u64,
2231 pub flags: u64,
2232 pub total_feedback: u64,
2233 pub health_score: f32,
2235}
2236
2237#[derive(Debug, Deserialize)]
2239pub struct MemoryImportancePatchRequest {
2240 pub agent_id: String,
2241 pub importance: f32,
2242}
2243
2244#[derive(Debug, Deserialize)]
2246pub struct FeedbackHealthQuery {
2247 pub agent_id: String,
2248}
2249
2250#[derive(Debug, Serialize)]
2252pub struct FeedbackHealthResponse {
2253 pub agent_id: String,
2254 pub health_score: f32,
2256 pub memory_count: usize,
2257 pub avg_importance: f32,
2258}
2259
2260#[derive(Debug, Deserialize)]
2262pub struct SearchMemoriesRequest {
2263 pub agent_id: String,
2264 #[serde(default)]
2265 pub query: Option<String>,
2266 #[serde(default)]
2267 pub memory_type: Option<MemoryType>,
2268 #[serde(default)]
2269 pub session_id: Option<String>,
2270 #[serde(default)]
2271 pub tags: Option<Vec<String>>,
2272 #[serde(default)]
2273 pub min_importance: Option<f32>,
2274 #[serde(default)]
2275 pub max_importance: Option<f32>,
2276 #[serde(default)]
2277 pub created_after: Option<u64>,
2278 #[serde(default)]
2279 pub created_before: Option<u64>,
2280 #[serde(default = "default_top_k")]
2281 pub top_k: usize,
2282 #[serde(default)]
2283 pub sort_by: Option<MemorySortField>,
2284 #[serde(default)]
2286 pub routing: RoutingMode,
2287 #[serde(default)]
2290 pub rerank: bool,
2291}
2292
2293#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
2295#[serde(rename_all = "snake_case")]
2296pub enum MemorySortField {
2297 CreatedAt,
2298 LastAccessedAt,
2299 Importance,
2300 AccessCount,
2301}
2302
2303#[derive(Debug, Serialize)]
2305pub struct SearchMemoriesResponse {
2306 pub memories: Vec<RecallResult>,
2307 pub total_count: usize,
2308}
2309
2310#[derive(Debug, Deserialize)]
2316pub struct SessionStartRequest {
2317 pub agent_id: String,
2318 #[serde(skip_serializing_if = "Option::is_none")]
2319 pub metadata: Option<serde_json::Value>,
2320 #[serde(skip_serializing_if = "Option::is_none")]
2322 pub id: Option<String>,
2323}
2324
2325#[derive(Debug, Serialize)]
2327pub struct SessionStartResponse {
2328 pub session: Session,
2329}
2330
2331#[derive(Debug, Deserialize)]
2333pub struct SessionEndRequest {
2334 #[serde(default)]
2335 pub summary: Option<String>,
2336 #[serde(default)]
2338 pub auto_summarize: bool,
2339}
2340
2341#[derive(Debug, Serialize)]
2343pub struct SessionEndResponse {
2344 pub session: Session,
2345 pub memory_count: usize,
2346}
2347
2348#[derive(Debug, Serialize)]
2350pub struct ListSessionsResponse {
2351 pub sessions: Vec<Session>,
2352 pub total: usize,
2353}
2354
2355#[derive(Debug, Serialize)]
2357pub struct SessionMemoriesResponse {
2358 pub session: Session,
2359 pub memories: Vec<Memory>,
2360 #[serde(skip_serializing_if = "Option::is_none")]
2362 pub total: Option<usize>,
2363}
2364
2365#[derive(Debug, Serialize, Deserialize, Clone)]
2371pub struct AgentSummary {
2372 pub agent_id: String,
2373 pub memory_count: usize,
2374 pub session_count: usize,
2375 pub active_sessions: usize,
2376}
2377
2378#[derive(Debug, Serialize)]
2380pub struct AgentStats {
2381 pub agent_id: String,
2382 pub total_memories: usize,
2383 pub memories_by_type: std::collections::HashMap<String, usize>,
2384 pub total_sessions: usize,
2385 pub active_sessions: usize,
2386 pub avg_importance: f32,
2387 pub oldest_memory_at: Option<u64>,
2388 pub newest_memory_at: Option<u64>,
2389}
2390
2391#[derive(Debug, Serialize)]
2397pub struct WakeUpResponse {
2398 pub agent_id: String,
2399 pub memories: Vec<Memory>,
2401 pub total_available: usize,
2403}
2404
2405#[derive(Debug, Deserialize)]
2407pub struct KnowledgeGraphRequest {
2408 pub agent_id: String,
2409 pub memory_id: String,
2410 #[serde(default = "default_graph_depth")]
2411 pub depth: usize,
2412 #[serde(default = "default_graph_min_similarity")]
2413 pub min_similarity: f32,
2414}
2415
2416fn default_graph_depth() -> usize {
2417 2
2418}
2419
2420fn default_graph_min_similarity() -> f32 {
2421 0.7
2422}
2423
2424#[derive(Debug, Serialize)]
2426pub struct KnowledgeGraphNode {
2427 pub memory: Memory,
2428 pub similarity: f32,
2429 pub related: Vec<KnowledgeGraphEdge>,
2430}
2431
2432#[derive(Debug, Serialize)]
2434pub struct KnowledgeGraphEdge {
2435 pub memory_id: String,
2436 pub similarity: f32,
2437 pub shared_tags: Vec<String>,
2438}
2439
2440#[derive(Debug, Serialize)]
2442pub struct KnowledgeGraphResponse {
2443 pub root: KnowledgeGraphNode,
2444 pub total_nodes: usize,
2445}
2446
2447fn default_full_graph_max_nodes() -> usize {
2452 200
2453}
2454
2455fn default_full_graph_min_similarity() -> f32 {
2456 0.50
2457}
2458
2459fn default_full_graph_cluster_threshold() -> f32 {
2460 0.60
2461}
2462
2463fn default_full_graph_max_edges_per_node() -> usize {
2464 8
2465}
2466
2467#[derive(Debug, Deserialize)]
2469pub struct FullKnowledgeGraphRequest {
2470 pub agent_id: String,
2471 #[serde(default = "default_full_graph_max_nodes")]
2472 pub max_nodes: usize,
2473 #[serde(default = "default_full_graph_min_similarity")]
2474 pub min_similarity: f32,
2475 #[serde(default = "default_full_graph_cluster_threshold")]
2476 pub cluster_threshold: f32,
2477 #[serde(default = "default_full_graph_max_edges_per_node")]
2478 pub max_edges_per_node: usize,
2479}
2480
2481#[derive(Debug, Serialize)]
2483pub struct FullGraphNode {
2484 pub id: String,
2485 pub content: String,
2486 pub memory_type: String,
2487 pub importance: f32,
2488 pub tags: Vec<String>,
2489 pub created_at: Option<String>,
2490 pub cluster_id: usize,
2491 pub centrality: f32,
2492}
2493
2494#[derive(Debug, Serialize)]
2496pub struct FullGraphEdge {
2497 pub source: String,
2498 pub target: String,
2499 pub similarity: f32,
2500 pub shared_tags: Vec<String>,
2501}
2502
2503#[derive(Debug, Serialize)]
2505pub struct GraphCluster {
2506 pub id: usize,
2507 pub node_count: usize,
2508 pub top_tags: Vec<String>,
2509 pub avg_importance: f32,
2510}
2511
2512#[derive(Debug, Serialize)]
2514pub struct GraphStats {
2515 pub total_memories: usize,
2516 pub included_memories: usize,
2517 pub total_edges: usize,
2518 pub cluster_count: usize,
2519 pub density: f32,
2520 pub hub_memory_id: Option<String>,
2521}
2522
2523#[derive(Debug, Serialize)]
2525pub struct FullKnowledgeGraphResponse {
2526 pub nodes: Vec<FullGraphNode>,
2527 pub edges: Vec<FullGraphEdge>,
2528 pub clusters: Vec<GraphCluster>,
2529 pub stats: GraphStats,
2530}
2531
2532#[derive(Debug, Deserialize)]
2534pub struct SummarizeRequest {
2535 pub agent_id: String,
2536 pub memory_ids: Vec<String>,
2537 #[serde(default)]
2538 pub target_type: Option<MemoryType>,
2539}
2540
2541#[derive(Debug, Serialize)]
2543pub struct SummarizeResponse {
2544 pub summary_memory: Memory,
2545 pub source_count: usize,
2546}
2547
2548#[derive(Debug, Deserialize)]
2550pub struct DeduplicateRequest {
2551 pub agent_id: String,
2552 #[serde(default = "default_dedup_threshold")]
2553 pub threshold: f32,
2554 #[serde(default)]
2555 pub memory_type: Option<MemoryType>,
2556 #[serde(default)]
2558 pub dry_run: bool,
2559}
2560
2561fn default_dedup_threshold() -> f32 {
2562 0.92
2563}
2564
2565#[derive(Debug, Serialize)]
2567pub struct DuplicateGroup {
2568 pub canonical_id: String,
2569 pub duplicate_ids: Vec<String>,
2570 pub avg_similarity: f32,
2571}
2572
2573#[derive(Debug, Serialize)]
2575pub struct DeduplicateResponse {
2576 pub groups: Vec<DuplicateGroup>,
2577 pub duplicates_found: usize,
2578 pub duplicates_merged: usize,
2579}
2580
2581fn default_cross_agent_min_similarity() -> f32 {
2586 0.3
2587}
2588
2589fn default_cross_agent_max_nodes_per_agent() -> usize {
2590 50
2591}
2592
2593fn default_cross_agent_max_cross_edges() -> usize {
2594 200
2595}
2596
2597#[derive(Debug, Deserialize)]
2599pub struct CrossAgentNetworkRequest {
2600 #[serde(default)]
2602 pub agent_ids: Option<Vec<String>>,
2603 #[serde(default = "default_cross_agent_min_similarity")]
2605 pub min_similarity: f32,
2606 #[serde(default = "default_cross_agent_max_nodes_per_agent")]
2608 pub max_nodes_per_agent: usize,
2609 #[serde(default)]
2611 pub min_importance: f32,
2612 #[serde(default = "default_cross_agent_max_cross_edges")]
2614 pub max_cross_edges: usize,
2615}
2616
2617#[derive(Debug, Serialize)]
2619pub struct AgentNetworkInfo {
2620 pub agent_id: String,
2621 pub memory_count: usize,
2622 pub avg_importance: f32,
2623}
2624
2625#[derive(Debug, Serialize)]
2627pub struct AgentNetworkNode {
2628 pub id: String,
2629 pub agent_id: String,
2630 pub content: String,
2631 pub importance: f32,
2632 pub tags: Vec<String>,
2633 pub memory_type: String,
2634 pub created_at: u64,
2635}
2636
2637#[derive(Debug, Serialize)]
2639pub struct AgentNetworkEdge {
2640 pub source: String,
2641 pub target: String,
2642 pub source_agent: String,
2643 pub target_agent: String,
2644 pub similarity: f32,
2645}
2646
2647#[derive(Debug, Serialize)]
2649pub struct AgentNetworkStats {
2650 pub total_agents: usize,
2651 pub total_nodes: usize,
2652 pub total_cross_edges: usize,
2653 pub density: f32,
2654}
2655
2656#[derive(Debug, Serialize)]
2658pub struct CrossAgentNetworkResponse {
2659 pub node_count: usize,
2660 pub agents: Vec<AgentNetworkInfo>,
2661 pub nodes: Vec<AgentNetworkNode>,
2662 pub edges: Vec<AgentNetworkEdge>,
2663 pub stats: AgentNetworkStats,
2664}
2665
2666#[derive(Debug, Deserialize, Default)]
2674pub struct BatchMemoryFilter {
2675 #[serde(default)]
2677 pub tags: Option<Vec<String>>,
2678 #[serde(default)]
2680 pub min_importance: Option<f32>,
2681 #[serde(default)]
2683 pub max_importance: Option<f32>,
2684 #[serde(default)]
2686 pub created_after: Option<u64>,
2687 #[serde(default)]
2689 pub created_before: Option<u64>,
2690 #[serde(default)]
2692 pub memory_type: Option<MemoryType>,
2693 #[serde(default)]
2695 pub session_id: Option<String>,
2696}
2697
2698impl BatchMemoryFilter {
2699 pub fn has_any(&self) -> bool {
2701 self.tags.is_some()
2702 || self.min_importance.is_some()
2703 || self.max_importance.is_some()
2704 || self.created_after.is_some()
2705 || self.created_before.is_some()
2706 || self.memory_type.is_some()
2707 || self.session_id.is_some()
2708 }
2709
2710 pub fn matches(&self, memory: &Memory) -> bool {
2712 if let Some(ref tags) = self.tags {
2713 if !tags.is_empty() && !tags.iter().all(|t| memory.tags.contains(t)) {
2714 return false;
2715 }
2716 }
2717 if let Some(min) = self.min_importance {
2718 if memory.importance < min {
2719 return false;
2720 }
2721 }
2722 if let Some(max) = self.max_importance {
2723 if memory.importance > max {
2724 return false;
2725 }
2726 }
2727 if let Some(after) = self.created_after {
2728 if memory.created_at < after {
2729 return false;
2730 }
2731 }
2732 if let Some(before) = self.created_before {
2733 if memory.created_at > before {
2734 return false;
2735 }
2736 }
2737 if let Some(ref mt) = self.memory_type {
2738 if memory.memory_type != *mt {
2739 return false;
2740 }
2741 }
2742 if let Some(ref sid) = self.session_id {
2743 if memory.session_id.as_ref() != Some(sid) {
2744 return false;
2745 }
2746 }
2747 true
2748 }
2749}
2750
2751#[derive(Debug, Deserialize)]
2753pub struct BatchRecallRequest {
2754 pub agent_id: String,
2756 #[serde(default)]
2758 pub filter: BatchMemoryFilter,
2759 #[serde(default = "default_batch_limit")]
2761 pub limit: usize,
2762}
2763
2764fn default_batch_limit() -> usize {
2765 100
2766}
2767
2768#[derive(Debug, Serialize)]
2770pub struct BatchRecallResponse {
2771 pub memories: Vec<Memory>,
2772 pub total: usize,
2773 pub filtered: usize,
2774}
2775
2776#[derive(Debug, Deserialize)]
2778pub struct BatchForgetRequest {
2779 pub agent_id: String,
2781 pub filter: BatchMemoryFilter,
2783}
2784
2785#[derive(Debug, Serialize)]
2787pub struct BatchForgetResponse {
2788 pub deleted_count: usize,
2789}
2790
2791#[derive(Debug, Deserialize)]
2799pub struct BatchStoreMemoryItem {
2800 pub content: String,
2801 #[serde(default)]
2802 pub memory_type: MemoryType,
2803 #[serde(skip_serializing_if = "Option::is_none")]
2804 pub session_id: Option<String>,
2805 #[serde(default = "default_importance")]
2806 pub importance: f32,
2807 #[serde(default)]
2808 pub tags: Vec<String>,
2809 #[serde(skip_serializing_if = "Option::is_none")]
2810 pub metadata: Option<serde_json::Value>,
2811 #[serde(skip_serializing_if = "Option::is_none")]
2812 pub ttl_seconds: Option<u64>,
2813 #[serde(skip_serializing_if = "Option::is_none")]
2814 pub expires_at: Option<u64>,
2815 #[serde(skip_serializing_if = "Option::is_none")]
2817 pub id: Option<String>,
2818}
2819
2820#[derive(Debug, Deserialize)]
2827pub struct BatchStoreMemoryRequest {
2828 pub agent_id: String,
2829 pub memories: Vec<BatchStoreMemoryItem>,
2830}
2831
2832#[derive(Debug, Serialize)]
2834pub struct BatchStoreMemoryResponse {
2835 pub stored: Vec<Memory>,
2836 pub stored_count: usize,
2837 pub total_embedding_time_ms: u64,
2838}
2839
2840#[derive(Debug, Deserialize)]
2847pub struct NamespaceEntityConfigRequest {
2848 pub extract_entities: bool,
2850 #[serde(default)]
2853 pub entity_types: Vec<String>,
2854}
2855
2856#[derive(Debug, Serialize, Deserialize)]
2858pub struct NamespaceEntityConfigResponse {
2859 pub namespace: String,
2860 pub extract_entities: bool,
2861 pub entity_types: Vec<String>,
2862}
2863
2864#[derive(Debug, Deserialize)]
2867pub struct ExtractEntitiesRequest {
2868 pub content: String,
2870 #[serde(default)]
2873 pub entity_types: Vec<String>,
2874}
2875
2876#[derive(Debug, Clone, Serialize, Deserialize)]
2878pub struct EntityResult {
2879 pub entity_type: String,
2880 pub value: String,
2881 pub score: f32,
2882 pub start: usize,
2883 pub end: usize,
2884 pub tag: String,
2886}
2887
2888#[derive(Debug, Serialize)]
2890pub struct ExtractEntitiesResponse {
2891 pub entities: Vec<EntityResult>,
2892 pub count: usize,
2893}
2894
2895#[derive(Debug, Deserialize)]
2901pub struct GraphTraverseQuery {
2902 #[serde(default = "default_ce5_graph_depth")]
2904 pub depth: u32,
2905}
2906
2907fn default_ce5_graph_depth() -> u32 {
2908 3
2909}
2910
2911#[derive(Debug, Deserialize)]
2913pub struct GraphPathQuery {
2914 pub to: String,
2916}
2917
2918#[derive(Debug, Deserialize)]
2920pub struct MemoryLinkRequest {
2921 pub target_id: String,
2923 #[serde(skip_serializing_if = "Option::is_none")]
2925 pub label: Option<String>,
2926 pub agent_id: String,
2928}
2929
2930#[derive(Debug, Serialize)]
2932pub struct GraphTraverseResponse {
2933 pub root_id: String,
2934 pub depth: u32,
2935 pub node_count: usize,
2936 pub nodes: Vec<GraphNodeResponse>,
2937}
2938
2939#[derive(Debug, Serialize)]
2941pub struct GraphNodeResponse {
2942 pub memory_id: String,
2943 pub depth: u32,
2944 pub edges: Vec<GraphEdgeResponse>,
2945}
2946
2947#[derive(Debug, Serialize)]
2949pub struct GraphEdgeResponse {
2950 pub from_id: String,
2951 pub to_id: String,
2952 pub edge_type: String,
2953 pub weight: f32,
2954 pub created_at: u64,
2955}
2956
2957#[derive(Debug, Serialize)]
2959pub struct GraphPathResponse {
2960 pub from_id: String,
2961 pub to_id: String,
2962 pub path: Vec<String>,
2964 pub hop_count: usize,
2965}
2966
2967#[derive(Debug, Serialize)]
2969pub struct MemoryLinkResponse {
2970 pub from_id: String,
2971 pub to_id: String,
2972 pub edge_type: String,
2973}
2974
2975#[derive(Debug, Serialize)]
2977pub struct GraphExportResponse {
2978 pub agent_id: String,
2979 pub namespace: String,
2980 pub node_count: usize,
2981 pub edge_count: usize,
2982 pub edges: Vec<GraphEdgeResponse>,
2983}
2984
2985#[derive(Debug, Deserialize)]
2991pub struct KgQueryParams {
2992 pub agent_id: String,
2994 #[serde(default)]
2996 pub root_id: Option<String>,
2997 #[serde(default)]
2999 pub edge_type: Option<String>,
3000 #[serde(default)]
3002 pub min_weight: Option<f32>,
3003 #[serde(default = "default_kg_depth")]
3005 pub max_depth: u32,
3006 #[serde(default = "default_kg_limit")]
3008 pub limit: usize,
3009}
3010
3011fn default_kg_depth() -> u32 {
3012 3
3013}
3014
3015fn default_kg_limit() -> usize {
3016 100
3017}
3018
3019#[derive(Debug, Serialize)]
3021pub struct KgQueryResponse {
3022 pub agent_id: String,
3023 pub node_count: usize,
3024 pub edge_count: usize,
3025 pub edges: Vec<GraphEdgeResponse>,
3026}
3027
3028#[derive(Debug, Deserialize)]
3030pub struct KgPathParams {
3031 pub agent_id: String,
3033 pub from: String,
3035 pub to: String,
3037}
3038
3039#[derive(Debug, Serialize)]
3041pub struct KgPathResponse {
3042 pub agent_id: String,
3043 pub from_id: String,
3044 pub to_id: String,
3045 pub hop_count: usize,
3046 pub path: Vec<String>,
3047}
3048
3049#[derive(Debug, Deserialize)]
3051pub struct KgExportParams {
3052 pub agent_id: String,
3054 #[serde(default = "default_kg_format")]
3056 pub format: String,
3057}
3058
3059fn default_kg_format() -> String {
3060 "json".to_string()
3061}
3062
3063#[derive(Debug, Serialize)]
3065pub struct KgExportJsonResponse {
3066 pub agent_id: String,
3067 pub format: String,
3068 pub node_count: usize,
3069 pub edge_count: usize,
3070 pub edges: Vec<GraphEdgeResponse>,
3071}
3072
3073fn default_working_ttl() -> Option<u64> {
3078 Some(14_400) }
3080fn default_episodic_ttl() -> Option<u64> {
3081 Some(2_592_000) }
3083fn default_semantic_ttl() -> Option<u64> {
3084 Some(31_536_000) }
3086fn default_procedural_ttl() -> Option<u64> {
3087 Some(63_072_000) }
3089fn default_working_decay() -> DecayStrategy {
3090 DecayStrategy::Exponential
3091}
3092fn default_episodic_decay() -> DecayStrategy {
3093 DecayStrategy::PowerLaw
3094}
3095fn default_semantic_decay() -> DecayStrategy {
3096 DecayStrategy::Logarithmic
3097}
3098fn default_procedural_decay() -> DecayStrategy {
3099 DecayStrategy::Flat
3100}
3101fn default_sr_factor() -> f64 {
3102 1.0
3103}
3104fn default_sr_base_interval() -> u64 {
3105 86_400 }
3107fn default_consolidation_enabled() -> bool {
3108 false
3109}
3110fn default_policy_consolidation_threshold() -> f32 {
3111 0.92
3112}
3113fn default_consolidation_interval_hours() -> u32 {
3114 24
3115}
3116fn default_store_dedup_threshold() -> f32 {
3117 0.95
3118}
3119
3120#[derive(Debug, Clone, Serialize, Deserialize)]
3125pub struct MemoryPolicy {
3126 #[serde(
3129 default = "default_working_ttl",
3130 skip_serializing_if = "Option::is_none"
3131 )]
3132 pub working_ttl_seconds: Option<u64>,
3133 #[serde(
3135 default = "default_episodic_ttl",
3136 skip_serializing_if = "Option::is_none"
3137 )]
3138 pub episodic_ttl_seconds: Option<u64>,
3139 #[serde(
3141 default = "default_semantic_ttl",
3142 skip_serializing_if = "Option::is_none"
3143 )]
3144 pub semantic_ttl_seconds: Option<u64>,
3145 #[serde(
3147 default = "default_procedural_ttl",
3148 skip_serializing_if = "Option::is_none"
3149 )]
3150 pub procedural_ttl_seconds: Option<u64>,
3151
3152 #[serde(default = "default_working_decay")]
3155 pub working_decay: DecayStrategy,
3156 #[serde(default = "default_episodic_decay")]
3158 pub episodic_decay: DecayStrategy,
3159 #[serde(default = "default_semantic_decay")]
3161 pub semantic_decay: DecayStrategy,
3162 #[serde(default = "default_procedural_decay")]
3164 pub procedural_decay: DecayStrategy,
3165
3166 #[serde(default = "default_sr_factor")]
3171 pub spaced_repetition_factor: f64,
3172 #[serde(default = "default_sr_base_interval")]
3174 pub spaced_repetition_base_interval_seconds: u64,
3175
3176 #[serde(default = "default_consolidation_enabled")]
3179 pub consolidation_enabled: bool,
3180 #[serde(default = "default_policy_consolidation_threshold")]
3182 pub consolidation_threshold: f32,
3183 #[serde(default = "default_consolidation_interval_hours")]
3185 pub consolidation_interval_hours: u32,
3186 #[serde(default)]
3188 pub consolidated_count: u64,
3189
3190 #[serde(default)]
3194 pub rate_limit_enabled: bool,
3195 #[serde(default, skip_serializing_if = "Option::is_none")]
3198 pub rate_limit_stores_per_minute: Option<u32>,
3199 #[serde(default, skip_serializing_if = "Option::is_none")]
3202 pub rate_limit_recalls_per_minute: Option<u32>,
3203
3204 #[serde(default)]
3212 pub dedup_on_store: bool,
3213 #[serde(default = "default_store_dedup_threshold")]
3215 pub dedup_threshold: f32,
3216}
3217
3218impl Default for MemoryPolicy {
3219 fn default() -> Self {
3220 Self {
3221 working_ttl_seconds: default_working_ttl(),
3222 episodic_ttl_seconds: default_episodic_ttl(),
3223 semantic_ttl_seconds: default_semantic_ttl(),
3224 procedural_ttl_seconds: default_procedural_ttl(),
3225 working_decay: default_working_decay(),
3226 episodic_decay: default_episodic_decay(),
3227 semantic_decay: default_semantic_decay(),
3228 procedural_decay: default_procedural_decay(),
3229 spaced_repetition_factor: default_sr_factor(),
3230 spaced_repetition_base_interval_seconds: default_sr_base_interval(),
3231 consolidation_enabled: default_consolidation_enabled(),
3232 consolidation_threshold: default_policy_consolidation_threshold(),
3233 consolidation_interval_hours: default_consolidation_interval_hours(),
3234 consolidated_count: 0,
3235 rate_limit_enabled: false,
3236 rate_limit_stores_per_minute: None,
3237 rate_limit_recalls_per_minute: None,
3238 dedup_on_store: false,
3239 dedup_threshold: default_store_dedup_threshold(),
3240 }
3241 }
3242}
3243
3244impl MemoryPolicy {
3245 pub fn ttl_for_type(&self, memory_type: &MemoryType) -> Option<u64> {
3247 match memory_type {
3248 MemoryType::Working => self.working_ttl_seconds,
3249 MemoryType::Episodic => self.episodic_ttl_seconds,
3250 MemoryType::Semantic => self.semantic_ttl_seconds,
3251 MemoryType::Procedural => self.procedural_ttl_seconds,
3252 }
3253 }
3254
3255 pub fn decay_for_type(&self, memory_type: &MemoryType) -> DecayStrategy {
3257 match memory_type {
3258 MemoryType::Working => self.working_decay,
3259 MemoryType::Episodic => self.episodic_decay,
3260 MemoryType::Semantic => self.semantic_decay,
3261 MemoryType::Procedural => self.procedural_decay,
3262 }
3263 }
3264
3265 pub fn spaced_repetition_extension(&self, access_count: u32) -> u64 {
3269 if self.spaced_repetition_factor <= 0.0 {
3270 return 0;
3271 }
3272 let ext = access_count as f64
3273 * self.spaced_repetition_factor
3274 * self.spaced_repetition_base_interval_seconds as f64;
3275 ext.round() as u64
3276 }
3277}
3278
3279#[cfg(test)]
3280mod tests {
3281 use super::*;
3282
3283 #[test]
3286 fn test_rank_by_input_valid_parses() {
3287 let json = serde_json::json!(["timestamp", "desc"]);
3288 let parsed: RankByInput = serde_json::from_value(json).expect("valid rank_by must parse");
3289 assert!(matches!(parsed.0, RankBy::AttributeOrder { .. }));
3290 }
3291
3292 #[test]
3293 fn test_rank_by_input_malformed_errors() {
3294 let json = serde_json::json!({"not": "a valid rank_by"});
3297 let result: Result<RankByInput, _> = serde_json::from_value(json);
3298 assert!(
3299 result.is_err(),
3300 "malformed rank_by must error, not silently default"
3301 );
3302 }
3303
3304 #[test]
3307 fn test_memory_to_vector_from_vector_roundtrip() {
3308 let memory = Memory {
3309 id: "mem_abc123".to_string(),
3310 memory_type: MemoryType::Episodic,
3311 content: "Test content".to_string(),
3312 agent_id: "test-agent".to_string(),
3313 session_id: Some("sess_001".to_string()),
3314 importance: 0.8,
3315 tags: vec!["tag1".to_string(), "tag2".to_string()],
3316 metadata: Some(serde_json::json!({"key": "value"})),
3317 created_at: 1_700_000_000,
3318 last_accessed_at: 1_700_001_000,
3319 access_count: 5,
3320 ttl_seconds: None,
3321 expires_at: None,
3322 };
3323
3324 let embedding = vec![0.1f32, 0.2, 0.3];
3325 let vector = memory.to_vector(embedding.clone());
3326 assert_eq!(vector.id, memory.id);
3327 assert_eq!(vector.values, embedding);
3328
3329 let recovered =
3330 Memory::from_vector(&vector).expect("should reconstruct memory from vector");
3331 assert_eq!(recovered.id, memory.id);
3332 assert_eq!(recovered.content, memory.content);
3333 assert_eq!(recovered.agent_id, memory.agent_id);
3334 assert_eq!(recovered.session_id, memory.session_id);
3335 assert_eq!(recovered.tags, memory.tags);
3336 assert_eq!(recovered.access_count, memory.access_count);
3337 assert_eq!(recovered.created_at, memory.created_at);
3338 assert_eq!(recovered.last_accessed_at, memory.last_accessed_at);
3339 assert!(
3341 (recovered.importance - memory.importance).abs() < 1e-5,
3342 "importance mismatch: {} vs {}",
3343 recovered.importance,
3344 memory.importance
3345 );
3346 }
3347
3348 #[test]
3349 fn test_memory_from_vector_rejects_session_type() {
3350 let mut meta = serde_json::Map::new();
3351 meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
3352 let vector = Vector {
3353 id: "v1".to_string(),
3354 values: vec![],
3355 metadata: Some(serde_json::Value::Object(meta)),
3356 ttl_seconds: None,
3357 expires_at: None,
3358 };
3359 assert!(
3360 Memory::from_vector(&vector).is_none(),
3361 "from_vector should return None for wrong _dakera_type"
3362 );
3363 }
3364
3365 #[test]
3366 fn test_memory_from_vector_rejects_missing_metadata() {
3367 let vector = Vector {
3368 id: "v1".to_string(),
3369 values: vec![],
3370 metadata: None,
3371 ttl_seconds: None,
3372 expires_at: None,
3373 };
3374 assert!(Memory::from_vector(&vector).is_none());
3375 }
3376
3377 #[test]
3380 fn test_session_to_vector_from_vector_roundtrip() {
3381 let mut session = Session::new("sess_xyz".to_string(), "agent-007".to_string());
3382 session.ended_at = Some(1_700_005_000);
3383 session.summary = Some("Session ended cleanly".to_string());
3384 session.memory_count = 42;
3385
3386 let embedding = vec![0.5f32, 0.5, 0.5];
3387 let vector = session.to_vector(embedding.clone());
3388 assert_eq!(vector.id, session.id);
3389 assert_eq!(vector.values, embedding);
3390
3391 let recovered = Session::from_vector(&vector).expect("should reconstruct session");
3392 assert_eq!(recovered.id, session.id);
3393 assert_eq!(recovered.agent_id, session.agent_id);
3394 assert_eq!(recovered.ended_at, session.ended_at);
3395 assert_eq!(recovered.summary, session.summary);
3396 assert_eq!(recovered.memory_count, session.memory_count);
3397 }
3398
3399 #[test]
3400 fn test_session_from_vector_rejects_memory_type() {
3401 let mut meta = serde_json::Map::new();
3402 meta.insert("_dakera_type".to_string(), serde_json::json!("memory"));
3403 let vector = Vector {
3404 id: "sess_1".to_string(),
3405 values: vec![],
3406 metadata: Some(serde_json::Value::Object(meta)),
3407 ttl_seconds: None,
3408 expires_at: None,
3409 };
3410 assert!(
3411 Session::from_vector(&vector).is_none(),
3412 "from_vector should return None for wrong _dakera_type"
3413 );
3414 }
3415
3416 #[test]
3417 fn test_session_active_has_no_ended_at() {
3418 let session = Session::new("sess_active".to_string(), "agent-1".to_string());
3419 let vector = session.to_vector(vec![0.1]);
3420 let recovered = Session::from_vector(&vector).unwrap();
3421 assert!(recovered.ended_at.is_none());
3422 assert_eq!(recovered.memory_count, 0);
3423 }
3424
3425 #[test]
3428 fn test_pagination_cursor_encode_decode_roundtrip() {
3429 let cursor = PaginationCursor::new(0.75, "mem_abc".to_string());
3430 let encoded = cursor.encode();
3431 let decoded = PaginationCursor::decode(&encoded).expect("should decode valid cursor");
3432 assert!(
3433 (decoded.last_score - cursor.last_score).abs() < 1e-6,
3434 "score mismatch after decode"
3435 );
3436 assert_eq!(decoded.last_id, cursor.last_id);
3437 }
3438
3439 #[test]
3440 fn test_pagination_cursor_decode_invalid_returns_none() {
3441 assert!(PaginationCursor::decode("not-valid-base64!!!").is_none());
3442 assert!(PaginationCursor::decode("").is_none());
3443 assert!(PaginationCursor::decode("aGVsbG8=").is_none()); }
3446
3447 #[test]
3450 fn test_distance_metric_serde_round_trip() {
3451 let cases = [
3452 (DistanceMetric::Cosine, "\"cosine\""),
3453 (DistanceMetric::Euclidean, "\"euclidean\""),
3454 (DistanceMetric::DotProduct, "\"dot_product\""),
3455 ];
3456 for (metric, expected_json) in &cases {
3457 let serialized = serde_json::to_string(metric).unwrap();
3458 assert_eq!(
3459 &serialized, expected_json,
3460 "serialized form mismatch for {:?}",
3461 metric
3462 );
3463 let deserialized: DistanceMetric = serde_json::from_str(&serialized).unwrap();
3464 assert_eq!(*metric, deserialized);
3465 }
3466 }
3467
3468 #[test]
3471 fn test_vector_is_expired_at_not_yet_expired() {
3472 let vector = Vector {
3473 id: "v1".to_string(),
3474 values: vec![1.0],
3475 metadata: None,
3476 ttl_seconds: None,
3477 expires_at: Some(u64::MAX),
3478 };
3479 assert!(!vector.is_expired_at(0));
3480 assert!(!vector.is_expired_at(1_000_000));
3481 }
3482
3483 #[test]
3484 fn test_vector_is_expired_at_past_expiry() {
3485 let vector = Vector {
3486 id: "v1".to_string(),
3487 values: vec![1.0],
3488 metadata: None,
3489 ttl_seconds: None,
3490 expires_at: Some(100),
3491 };
3492 assert!(vector.is_expired_at(100), "at boundary should be expired");
3493 assert!(vector.is_expired_at(200));
3494 assert!(!vector.is_expired_at(99));
3495 }
3496
3497 #[test]
3498 fn test_vector_no_expiry_never_expires() {
3499 let vector = Vector {
3500 id: "v1".to_string(),
3501 values: vec![1.0],
3502 metadata: None,
3503 ttl_seconds: None,
3504 expires_at: None,
3505 };
3506 assert!(!vector.is_expired_at(u64::MAX));
3507 }
3508
3509 #[test]
3512 fn test_column_upsert_mismatched_vectors_length_errors() {
3513 let req = ColumnUpsertRequest {
3514 ids: vec!["a".to_string(), "b".to_string()],
3515 vectors: vec![vec![1.0, 2.0]], attributes: Default::default(),
3517 ttl_seconds: None,
3518 dimension: None,
3519 };
3520 assert!(
3521 req.to_vectors().is_err(),
3522 "should error when vectors.len() != ids.len()"
3523 );
3524 }
3525
3526 #[test]
3527 fn test_column_upsert_mismatched_attribute_length_errors() {
3528 let mut attrs = std::collections::HashMap::new();
3529 attrs.insert(
3530 "score".to_string(),
3531 vec![serde_json::json!(1.0)], );
3533 let req = ColumnUpsertRequest {
3534 ids: vec!["a".to_string(), "b".to_string()],
3535 vectors: vec![vec![1.0], vec![2.0]],
3536 attributes: attrs,
3537 ttl_seconds: None,
3538 dimension: None,
3539 };
3540 assert!(
3541 req.to_vectors().is_err(),
3542 "should error when attribute array length != ids.len()"
3543 );
3544 }
3545
3546 #[test]
3547 fn test_column_upsert_mismatched_dimension_errors() {
3548 let req = ColumnUpsertRequest {
3549 ids: vec!["a".to_string(), "b".to_string()],
3550 vectors: vec![vec![1.0, 2.0], vec![1.0]], attributes: Default::default(),
3552 ttl_seconds: None,
3553 dimension: None,
3554 };
3555 assert!(
3556 req.to_vectors().is_err(),
3557 "should error on dimension mismatch between vectors"
3558 );
3559 }
3560
3561 #[test]
3562 fn test_column_upsert_success() {
3563 let req = ColumnUpsertRequest {
3564 ids: vec!["a".to_string(), "b".to_string()],
3565 vectors: vec![vec![1.0, 0.0], vec![0.0, 1.0]],
3566 attributes: Default::default(),
3567 ttl_seconds: None,
3568 dimension: Some(2),
3569 };
3570 let result = req.to_vectors().expect("valid request should succeed");
3571 assert_eq!(result.len(), 2);
3572 assert_eq!(result[0].id, "a");
3573 assert_eq!(result[1].id, "b");
3574 assert_eq!(result[0].values, vec![1.0, 0.0]);
3575 assert_eq!(result[1].values, vec![0.0, 1.0]);
3576 }
3577
3578 #[test]
3579 fn test_to_vector_metadata_surfaces_content_date() {
3580 let mut memory = Memory::new(
3581 "m1".to_string(),
3582 "test content".to_string(),
3583 "agent1".to_string(),
3584 MemoryType::Episodic,
3585 );
3586 let mut meta = serde_json::Map::new();
3587 meta.insert(
3588 "_dakera_content_date".to_string(),
3589 serde_json::json!(1625097600_i64),
3590 );
3591 memory.metadata = Some(serde_json::Value::Object(meta));
3592
3593 let vec_meta = memory.to_vector_metadata();
3594 let obj = vec_meta.as_object().unwrap();
3595 assert_eq!(
3596 obj.get("_dakera_content_date"),
3597 Some(&serde_json::json!(1625097600_i64))
3598 );
3599 assert!(obj
3600 .get("user_metadata")
3601 .unwrap()
3602 .get("_dakera_content_date")
3603 .is_some());
3604 }
3605
3606 #[test]
3607 fn test_to_vector_metadata_no_content_date_when_absent() {
3608 let memory = Memory::new(
3609 "m1".to_string(),
3610 "test content".to_string(),
3611 "agent1".to_string(),
3612 MemoryType::Episodic,
3613 );
3614 let vec_meta = memory.to_vector_metadata();
3615 let obj = vec_meta.as_object().unwrap();
3616 assert!(obj.get("_dakera_content_date").is_none());
3617 }
3618}