nexo_memory/
vector_backend.rs1use async_trait::async_trait;
19use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25pub struct VectorRecord {
26 pub id: String,
27 pub content: String,
28 pub embedding: Vec<f32>,
29 #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
33 pub metadata: serde_json::Value,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41pub struct VectorQuery {
42 pub embedding: Vec<f32>,
43 pub limit: u32,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub filter: Option<serde_json::Value>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52pub struct VectorMatch {
53 pub id: String,
54 pub content: String,
55 pub score: f32,
56 #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
57 pub metadata: serde_json::Value,
58}
59
60#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
61pub struct UpsertAck {
62 pub count: u32,
63}
64
65#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
66pub struct DeleteAck {
67 pub count: u32,
68}
69
70#[async_trait]
74pub trait VectorBackend: Send + Sync + 'static {
75 fn name(&self) -> &str;
79
80 async fn upsert(
81 &self,
82 collection: &str,
83 records: Vec<VectorRecord>,
84 ) -> anyhow::Result<UpsertAck>;
85
86 async fn search(
87 &self,
88 collection: &str,
89 query: VectorQuery,
90 ) -> anyhow::Result<Vec<VectorMatch>>;
91
92 async fn delete(&self, collection: &str, ids: Vec<String>) -> anyhow::Result<DeleteAck>;
93}
94
95#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn vector_record_round_trips() {
103 let r = VectorRecord {
104 id: "r1".into(),
105 content: "hello".into(),
106 embedding: vec![0.1, 0.2, 0.3],
107 metadata: serde_json::json!({"source": "kb"}),
108 };
109 let s = serde_json::to_string(&r).unwrap();
110 let back: VectorRecord = serde_json::from_str(&s).unwrap();
111 assert_eq!(back, r);
112 }
113
114 #[test]
115 fn vector_query_round_trips() {
116 let q = VectorQuery {
117 embedding: vec![0.4, 0.5],
118 limit: 10,
119 filter: Some(serde_json::json!({"namespace": "tenant-1"})),
120 };
121 let s = serde_json::to_string(&q).unwrap();
122 let back: VectorQuery = serde_json::from_str(&s).unwrap();
123 assert_eq!(back, q);
124
125 let q_no_filter = VectorQuery {
127 embedding: vec![1.0],
128 limit: 5,
129 filter: None,
130 };
131 let s = serde_json::to_string(&q_no_filter).unwrap();
132 assert!(!s.contains("filter"));
133 }
134
135 #[test]
136 fn vector_match_round_trips() {
137 let m = VectorMatch {
138 id: "r1".into(),
139 content: "hello".into(),
140 score: 0.97,
141 metadata: serde_json::json!({"source": "kb"}),
142 };
143 let s = serde_json::to_string(&m).unwrap();
144 let back: VectorMatch = serde_json::from_str(&s).unwrap();
145 assert_eq!(back, m);
146 }
147
148 #[test]
149 fn upsert_ack_round_trips() {
150 let a = UpsertAck { count: 42 };
151 let s = serde_json::to_string(&a).unwrap();
152 let back: UpsertAck = serde_json::from_str(&s).unwrap();
153 assert_eq!(back, a);
154
155 let d = DeleteAck { count: 7 };
156 let s = serde_json::to_string(&d).unwrap();
157 let back: DeleteAck = serde_json::from_str(&s).unwrap();
158 assert_eq!(back, d);
159 }
160}