1#![cfg_attr(
2 test,
3 allow(
4 clippy::expect_used,
5 clippy::indexing_slicing,
6 clippy::panic,
7 clippy::unwrap_used,
8 clippy::unreachable
9 )
10)]
11use std::ops::Range;
22
23use lancedb::{
24 DistanceType,
25 query::{QueryBase, VectorQuery},
26};
27use rig_core::{
28 embeddings::embedding::EmbeddingModel,
29 vector_store::{
30 VectorStoreError, VectorStoreIndex,
31 request::{FilterError, SearchFilter, VectorSearchRequest},
32 },
33};
34use serde::Deserialize;
35use serde_json::Value;
36use utils::{FilterTableColumns, QueryToJson};
37
38mod utils;
39
40fn lancedb_to_rig_error(e: lancedb::Error) -> VectorStoreError {
41 VectorStoreError::DatastoreError(Box::new(e))
42}
43
44fn serde_to_rig_error(e: serde_json::Error) -> VectorStoreError {
45 VectorStoreError::JsonError(e)
46}
47
48pub struct LanceDbVectorIndex<M: EmbeddingModel> {
62 model: M,
64 table: lancedb::Table,
66 id_field: String,
68 search_params: SearchParams,
70}
71
72impl<M> LanceDbVectorIndex<M>
73where
74 M: EmbeddingModel,
75{
76 pub async fn new(
80 table: lancedb::Table,
81 model: M,
82 id_field: &str,
83 search_params: SearchParams,
84 ) -> Result<Self, lancedb::Error> {
85 Ok(Self {
86 table,
87 model,
88 id_field: id_field.to_string(),
89 search_params,
90 })
91 }
92
93 fn build_query(&self, mut query: VectorQuery) -> VectorQuery {
96 let SearchParams {
97 distance_type,
98 search_type,
99 nprobes,
100 refine_factor,
101 post_filter,
102 column,
103 } = self.search_params.clone();
104
105 if let Some(distance_type) = distance_type {
106 query = query.distance_type(distance_type);
107 }
108
109 if let Some(SearchType::Flat) = search_type {
110 query = query.bypass_vector_index();
111 }
112
113 if let Some(SearchType::Approximate) = search_type {
114 if let Some(nprobes) = nprobes {
115 query = query.nprobes(nprobes);
116 }
117 if let Some(refine_factor) = refine_factor {
118 query = query.refine_factor(refine_factor);
119 }
120 }
121
122 if let Some(true) = post_filter {
123 query = query.postfilter();
124 }
125
126 if let Some(column) = column {
127 query = query.column(column.as_str())
128 }
129
130 query
131 }
132}
133
134#[derive(Debug, Clone)]
136pub enum SearchType {
137 Flat,
139 Approximate,
141}
142
143#[derive(Debug, Clone)]
145pub struct LanceDBFilter(Result<String, FilterError>);
146
147impl serde::Serialize for LanceDBFilter {
148 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: serde::Serializer,
151 {
152 match &self.0 {
153 Ok(s) => serializer.serialize_str(s),
154 Err(e) => serializer.collect_str(e),
155 }
156 }
157}
158
159impl<'de> serde::Deserialize<'de> for LanceDBFilter {
160 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161 where
162 D: serde::Deserializer<'de>,
163 {
164 let s = String::deserialize(deserializer)?;
165 Ok(LanceDBFilter(Ok(s)))
167 }
168}
169
170fn zip_result(
171 l: Result<String, FilterError>,
172 r: Result<String, FilterError>,
173) -> Result<(String, String), FilterError> {
174 l.and_then(|l| r.map(|r| (l, r)))
175}
176
177impl SearchFilter for LanceDBFilter {
178 type Value = serde_json::Value;
179
180 fn eq(key: impl AsRef<str>, value: Self::Value) -> Self {
181 Self(escape_value(value).map(|s| format!("{} = {s}", key.as_ref())))
182 }
183
184 fn gt(key: impl AsRef<str>, value: Self::Value) -> Self {
185 Self(escape_value(value).map(|s| format!("{} > {s}", key.as_ref())))
186 }
187
188 fn lt(key: impl AsRef<str>, value: Self::Value) -> Self {
189 Self(escape_value(value).map(|s| format!("{} < {s}", key.as_ref())))
190 }
191
192 fn and(self, rhs: Self) -> Self {
193 Self(zip_result(self.0, rhs.0).map(|(l, r)| format!("({l}) AND ({r})")))
194 }
195
196 fn or(self, rhs: Self) -> Self {
197 Self(zip_result(self.0, rhs.0).map(|(l, r)| format!("({l}) OR ({r})")))
198 }
199}
200
201fn escape_value(value: serde_json::Value) -> Result<String, FilterError> {
202 use serde_json::Value::*;
203
204 match value {
205 Null => Ok("NULL".into()),
206 Bool(b) => Ok(b.to_string()),
207 Number(n) => Ok(n.to_string()),
208 String(s) => Ok(format!("'{}'", s.replace("'", "''"))),
209 Array(xs) => Ok(format!(
210 "({})",
211 xs.into_iter()
212 .map(escape_value)
213 .collect::<Result<Vec<_>, _>>()?
214 .join(", ")
215 )),
216 Object(_) => Err(FilterError::TypeError(
217 "objects not supported in SQLite backend".into(),
218 )),
219 }
220}
221
222impl LanceDBFilter {
223 pub fn into_inner(self) -> Result<String, FilterError> {
224 self.0
225 }
226
227 #[allow(clippy::should_implement_trait)]
228 pub fn not(self) -> Self {
229 Self(self.0.map(|s| format!("NOT ({s})")))
230 }
231
232 pub fn in_values(key: String, values: Vec<<Self as SearchFilter>::Value>) -> Self {
234 Self(
235 values
236 .into_iter()
237 .map(escape_value)
238 .collect::<Result<Vec<_>, FilterError>>()
239 .map(|xs| xs.join(","))
240 .map(|xs| format!("{key} IN ({xs})")),
241 )
242 }
243
244 pub fn like<S>(key: String, pattern: S) -> Self
246 where
247 S: AsRef<str>,
248 {
249 Self(
250 escape_value(serde_json::Value::String(pattern.as_ref().into()))
251 .map(|pat| format!("{key} LIKE {pat}")),
252 )
253 }
254
255 pub fn ilike<S>(key: String, pattern: S) -> Self
257 where
258 S: AsRef<str>,
259 {
260 Self(
261 escape_value(serde_json::Value::String(pattern.as_ref().into()))
262 .map(|pat| format!("{key} ILIKE {pat}")),
263 )
264 }
265
266 pub fn is_null(key: String) -> Self {
268 Self(Ok(format!("{key} IS NULL")))
269 }
270
271 pub fn is_not_null(key: String) -> Self {
273 Self(Ok(format!("{key} IS NOT NULL")))
274 }
275
276 pub fn array_has_any(key: String, values: Vec<<Self as SearchFilter>::Value>) -> Self {
278 Self(
279 values
280 .into_iter()
281 .map(escape_value)
282 .collect::<Result<Vec<_>, FilterError>>()
283 .map(|xs| xs.join(","))
284 .map(|xs| format!("array_has_any({key}, ARRAY[{xs}])")),
285 )
286 }
287
288 pub fn array_has_all(key: String, values: Vec<<Self as SearchFilter>::Value>) -> Self {
290 Self(
291 values
292 .into_iter()
293 .map(escape_value)
294 .collect::<Result<Vec<_>, FilterError>>()
295 .map(|xs| xs.join(","))
296 .map(|xs| format!("array_has_all({key}, ARRAY[{xs}])")),
297 )
298 }
299
300 pub fn array_length(key: String, length: i32) -> Self {
302 Self(Ok(format!("array_length({key}) = {length}")))
303 }
304
305 pub fn between<T>(key: String, Range { start, end }: Range<T>) -> Self
307 where
308 T: PartialOrd + std::fmt::Display + Into<serde_json::Number>,
309 {
310 Self(Ok(format!("{key} BETWEEN {start} AND {end}")))
311 }
312}
313
314#[derive(Debug, Clone, Default)]
320pub struct SearchParams {
321 distance_type: Option<DistanceType>,
322 search_type: Option<SearchType>,
323 nprobes: Option<usize>,
324 refine_factor: Option<u32>,
325 post_filter: Option<bool>,
326 column: Option<String>,
327}
328
329impl SearchParams {
330 pub fn distance_type(mut self, distance_type: DistanceType) -> Self {
334 self.distance_type = Some(distance_type);
335 self
336 }
337
338 pub fn search_type(mut self, search_type: SearchType) -> Self {
342 self.search_type = Some(search_type);
343 self
344 }
345
346 pub fn nprobes(mut self, nprobes: usize) -> Self {
350 self.nprobes = Some(nprobes);
351 self
352 }
353
354 pub fn refine_factor(mut self, refine_factor: u32) -> Self {
358 self.refine_factor = Some(refine_factor);
359 self
360 }
361
362 pub fn post_filter(mut self, post_filter: bool) -> Self {
366 self.post_filter = Some(post_filter);
367 self
368 }
369
370 pub fn column(mut self, column: &str) -> Self {
374 self.column = Some(column.to_string());
375 self
376 }
377}
378
379impl<M> VectorStoreIndex for LanceDbVectorIndex<M>
380where
381 M: EmbeddingModel + Sync + Send,
382{
383 type Filter = LanceDBFilter;
384
385 async fn top_n<T: for<'a> Deserialize<'a> + Send>(
404 &self,
405 req: VectorSearchRequest<LanceDBFilter>,
406 ) -> Result<Vec<(f64, String, T)>, VectorStoreError> {
407 let prompt_embedding = self.model.embed_text(req.query()).await?;
408
409 let mut query = self
410 .table
411 .vector_search(prompt_embedding.vec.clone())
412 .map_err(lancedb_to_rig_error)?
413 .limit(req.samples() as usize)
414 .distance_range(None, req.threshold().map(|x| x as f32))
415 .select(lancedb::query::Select::Columns(
416 self.table
417 .schema()
418 .await
419 .map_err(lancedb_to_rig_error)?
420 .filter_embeddings(),
421 ));
422
423 if let Some(filter) = req.filter() {
424 query = query.only_if(filter.clone().into_inner()?)
425 }
426
427 self.build_query(query)
428 .execute_query()
429 .await?
430 .into_iter()
431 .enumerate()
432 .map(|(i, value)| {
433 Ok((
434 match value.get("_distance") {
435 Some(Value::Number(distance)) => distance.as_f64().unwrap_or_default(),
436 _ => 0.0,
437 },
438 match value.get(self.id_field.clone()) {
439 Some(Value::String(id)) => id.to_string(),
440 _ => format!("unknown{i}"),
441 },
442 serde_json::from_value(value).map_err(serde_to_rig_error)?,
443 ))
444 })
445 .collect()
446 }
447
448 async fn top_n_ids(
467 &self,
468 req: VectorSearchRequest<LanceDBFilter>,
469 ) -> Result<Vec<(f64, String)>, VectorStoreError> {
470 let prompt_embedding = self.model.embed_text(req.query()).await?;
471
472 let mut query = self
473 .table
474 .query()
475 .select(lancedb::query::Select::Columns(vec![self.id_field.clone()]))
476 .nearest_to(prompt_embedding.vec.clone())
477 .map_err(lancedb_to_rig_error)?
478 .distance_range(None, req.threshold().map(|x| x as f32))
479 .limit(req.samples() as usize);
480
481 if let Some(filter) = req.filter() {
482 query = query.only_if(filter.clone().into_inner()?)
483 }
484
485 self.build_query(query)
486 .execute_query()
487 .await?
488 .into_iter()
489 .map(|value| {
490 Ok((
491 match value.get("distance") {
492 Some(Value::Number(distance)) => distance.as_f64().unwrap_or_default(),
493 _ => 0.0,
494 },
495 match value.get(self.id_field.clone()) {
496 Some(Value::String(id)) => id.to_string(),
497 _ => "".to_string(),
498 },
499 ))
500 })
501 .collect()
502 }
503}