1mod filter;
2
3use reqwest::StatusCode;
4use rig::{
5 Embed, OneOrMany,
6 embeddings::{Embedding, EmbeddingModel},
7 vector_store::{
8 InsertDocuments, VectorStoreError, VectorStoreIndex,
9 request::{SearchFilter, VectorSearchRequest},
10 },
11};
12use serde::{Deserialize, Serialize};
13
14use crate::filter::Filter;
15
16pub struct MilvusVectorStore<M> {
18 model: M,
20 base_url: String,
21 client: reqwest::Client,
22 database_name: String,
23 collection_name: String,
24 token: Option<String>,
25}
26
27#[derive(Debug, Serialize, Deserialize)]
28pub struct CreateRecord {
29 document: String,
30 embedded_text: String,
31 embedding: Vec<f64>,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36struct InsertRequest<'a> {
37 data: Vec<CreateRecord>,
38 collection_name: &'a str,
39 db_name: &'a str,
40}
41
42#[derive(Debug, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44struct SearchRequest<'a> {
45 collection_name: &'a str,
46 db_name: &'a str,
47 data: Vec<f64>,
48 #[serde(skip_serializing_if = "String::is_empty")]
49 filter: String,
50 anns_field: &'a str,
51 limit: usize,
52 output_fields: Vec<&'a str>,
53}
54
55#[derive(Debug, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57struct SearchResult<T> {
58 code: i64,
59 data: Vec<SearchResultData<T>>,
60}
61
62#[derive(Debug, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64struct SearchResultData<T> {
65 id: i64,
66 distance: f64,
67 document: T,
68 embedded_text: String,
69}
70
71#[derive(Debug, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73struct SearchResultOnlyId {
74 code: i64,
75 data: Vec<SearchResultDataOnlyId>,
76}
77
78#[derive(Debug, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80struct SearchResultDataOnlyId {
81 id: i64,
82 distance: f64,
83}
84
85impl<M> MilvusVectorStore<M>
86where
87 M: EmbeddingModel,
88{
89 pub fn new(model: M, base_url: String, database_name: String, collection_name: String) -> Self {
97 Self {
98 model,
99 base_url,
100 client: reqwest::Client::new(),
101 database_name,
102 collection_name,
103 token: None,
104 }
105 }
106
107 pub fn auth(mut self, username: String, password: String) -> Self {
109 let str = format!("{username}:{password}");
110 self.token = Some(str);
111
112 self
113 }
114
115 fn create_insert_request(&self, data: Vec<CreateRecord>) -> InsertRequest<'_> {
117 InsertRequest {
118 data,
119 collection_name: &self.collection_name,
120 db_name: &self.database_name,
121 }
122 }
123
124 fn create_search_request(
126 &self,
127 data: Vec<f64>,
128 req: &VectorSearchRequest<Filter>,
129 id_only: bool,
130 ) -> SearchRequest<'_> {
131 const OUTPUT_FIELDS: [&str; 4] = ["id", "distance", "document", "embeddedText"];
132 const OUTPUT_FIELDS_ID_ONLY: [&str; 2] = ["id", "distance"];
133
134 let output_fields = if id_only {
135 OUTPUT_FIELDS_ID_ONLY.to_vec()
136 } else {
137 OUTPUT_FIELDS.to_vec()
138 };
139
140 let threshold = req
141 .threshold()
142 .map(|thresh| Filter::gte("distance".into(), thresh.into()));
143
144 let filter = match (threshold, req.filter()) {
145 (Some(thresh), Some(filter)) => thresh.and(filter.clone()).into_inner(),
146 (Some(thresh), _) => thresh.into_inner(),
147 (_, Some(filter)) => filter.clone().into_inner(),
148 _ => String::new(),
149 };
150
151 SearchRequest {
152 collection_name: &self.collection_name,
153 db_name: &self.database_name,
154 data,
155 filter,
156 anns_field: "embedding",
157 limit: req.samples() as usize,
158 output_fields,
159 }
160 }
161}
162
163impl<Model> InsertDocuments for MilvusVectorStore<Model>
164where
165 Model: EmbeddingModel + Send + Sync,
166{
167 async fn insert_documents<Doc: Serialize + Embed + Send>(
168 &self,
169 documents: Vec<(Doc, OneOrMany<Embedding>)>,
170 ) -> Result<(), VectorStoreError> {
171 let url = format!(
172 "{base_url}/v2/vectordb/entities/insert",
173 base_url = self.base_url
174 );
175
176 let data = documents
177 .into_iter()
178 .map(|(document, embeddings)| {
179 let json_document: serde_json::Value = serde_json::to_value(&document)?;
180 let json_document_as_string = serde_json::to_string(&json_document)?;
181
182 let embeddings = embeddings
183 .into_iter()
184 .map(|embedding| {
185 let embedded_text = embedding.document;
186 let embedding: Vec<f64> = embedding.vec;
187
188 CreateRecord {
189 document: json_document_as_string.clone(),
190 embedded_text,
191 embedding,
192 }
193 })
194 .collect::<Vec<CreateRecord>>();
195 Ok(embeddings)
196 })
197 .collect::<Result<Vec<Vec<CreateRecord>>, VectorStoreError>>()?
198 .into_iter()
199 .flatten()
200 .collect::<Vec<CreateRecord>>();
201
202 let mut client = self.client.post(url);
203 if let Some(ref token) = self.token {
204 client = client.header("Authentication", format!("Bearer {token}"));
205 }
206
207 let insert_request = self.create_insert_request(data);
208
209 let body = serde_json::to_string(&insert_request).unwrap();
210
211 let res = client.body(body).send().await?;
212
213 if res.status() != StatusCode::OK {
214 let status = res.status();
215 let text = res.text().await?;
216
217 return Err(VectorStoreError::ExternalAPIError(status, text));
218 }
219
220 Ok(())
221 }
222}
223
224impl<M> VectorStoreIndex for MilvusVectorStore<M>
225where
226 M: EmbeddingModel,
227{
228 type Filter = Filter;
229
230 async fn top_n<T: for<'a> Deserialize<'a> + Send>(
233 &self,
234 req: VectorSearchRequest<Filter>,
235 ) -> Result<Vec<(f64, String, T)>, VectorStoreError> {
236 let embedding = self.model.embed_text(req.query()).await?;
237 let url = format!(
238 "{base_url}/v2/vectordb/entities/search",
239 base_url = self.base_url
240 );
241
242 let body = self.create_search_request(embedding.vec, &req, false);
243
244 let mut client = self.client.post(url);
245 if let Some(ref token) = self.token {
246 client = client.header("Authentication", format!("Bearer {token}"));
247 }
248
249 let body = serde_json::to_string(&body)?;
250
251 let res = client.body(body).send().await?;
252
253 if res.status() != StatusCode::OK {
254 let status = res.status();
255 let text = res.text().await?;
256
257 return Err(VectorStoreError::ExternalAPIError(status, text));
258 }
259
260 let json: SearchResult<T> = res.json().await?;
261
262 let res = json
263 .data
264 .into_iter()
265 .map(|x| (x.distance, x.id.to_string(), x.document))
266 .collect();
267
268 Ok(res)
269 }
270
271 async fn top_n_ids(
274 &self,
275 req: VectorSearchRequest<Filter>,
276 ) -> Result<Vec<(f64, String)>, VectorStoreError> {
277 let embedding = self.model.embed_text(req.query()).await?;
278 let url = format!(
279 "{base_url}/v2/vectordb/entities/search",
280 base_url = self.base_url
281 );
282
283 let body = self.create_search_request(embedding.vec, &req, true);
284
285 let mut client = self.client.post(url);
286 if let Some(ref token) = self.token {
287 client = client.header("Authentication", format!("Bearer {token}"));
288 }
289
290 let body = serde_json::to_string(&body)?;
291
292 let res = client.body(body).send().await?;
293
294 if res.status() != StatusCode::OK {
295 let status = res.status();
296 let text = res.text().await?;
297
298 return Err(VectorStoreError::ExternalAPIError(status, text));
299 }
300
301 let json: SearchResultOnlyId = res.json().await?;
302
303 let res = json
304 .data
305 .into_iter()
306 .map(|x| (x.distance, x.id.to_string()))
307 .collect();
308
309 Ok(res)
310 }
311}