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
40pub struct LanceDbVectorIndex<M: EmbeddingModel> {
54 model: M,
56 table: lancedb::Table,
58 id_field: String,
60 search_params: SearchParams,
62}
63
64impl<M> LanceDbVectorIndex<M>
65where
66 M: EmbeddingModel,
67{
68 pub async fn new(
72 table: lancedb::Table,
73 model: M,
74 id_field: &str,
75 search_params: SearchParams,
76 ) -> Result<Self, lancedb::Error> {
77 Ok(Self {
78 table,
79 model,
80 id_field: id_field.to_string(),
81 search_params,
82 })
83 }
84
85 fn build_query(&self, mut query: VectorQuery) -> VectorQuery {
88 let SearchParams {
89 distance_type,
90 search_type,
91 nprobes,
92 refine_factor,
93 post_filter,
94 column,
95 } = self.search_params.clone();
96
97 if let Some(distance_type) = distance_type {
98 query = query.distance_type(distance_type);
99 }
100
101 if let Some(SearchType::Flat) = search_type {
102 query = query.bypass_vector_index();
103 }
104
105 if let Some(SearchType::Approximate) = search_type {
106 if let Some(nprobes) = nprobes {
107 query = query.nprobes(nprobes);
108 }
109 if let Some(refine_factor) = refine_factor {
110 query = query.refine_factor(refine_factor);
111 }
112 }
113
114 if let Some(true) = post_filter {
115 query = query.postfilter();
116 }
117
118 if let Some(column) = column {
119 query = query.column(column.as_str())
120 }
121
122 query
123 }
124}
125
126#[derive(Debug, Clone)]
128pub enum SearchType {
129 Flat,
131 Approximate,
133}
134
135#[derive(Debug, Clone)]
137pub struct LanceDBFilter(Result<String, FilterError>);
138
139impl serde::Serialize for LanceDBFilter {
140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141 where
142 S: serde::Serializer,
143 {
144 match &self.0 {
145 Ok(s) => serializer.serialize_str(s),
146 Err(e) => serializer.collect_str(e),
147 }
148 }
149}
150
151impl<'de> serde::Deserialize<'de> for LanceDBFilter {
152 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153 where
154 D: serde::Deserializer<'de>,
155 {
156 let s = String::deserialize(deserializer)?;
157 Ok(LanceDBFilter(Ok(s)))
159 }
160}
161
162fn zip_result(
163 l: Result<String, FilterError>,
164 r: Result<String, FilterError>,
165) -> Result<(String, String), FilterError> {
166 l.and_then(|l| r.map(|r| (l, r)))
167}
168
169impl SearchFilter for LanceDBFilter {
170 type Value = serde_json::Value;
171
172 fn eq(key: impl AsRef<str>, value: Self::Value) -> Self {
173 Self(escape_value(value).map(|s| format!("{} = {s}", key.as_ref())))
174 }
175
176 fn gt(key: impl AsRef<str>, value: Self::Value) -> Self {
177 Self(escape_value(value).map(|s| format!("{} > {s}", key.as_ref())))
178 }
179
180 fn lt(key: impl AsRef<str>, value: Self::Value) -> Self {
181 Self(escape_value(value).map(|s| format!("{} < {s}", key.as_ref())))
182 }
183
184 fn and(self, rhs: Self) -> Self {
185 Self(zip_result(self.0, rhs.0).map(|(l, r)| format!("({l}) AND ({r})")))
186 }
187
188 fn or(self, rhs: Self) -> Self {
189 Self(zip_result(self.0, rhs.0).map(|(l, r)| format!("({l}) OR ({r})")))
190 }
191}
192
193fn escape_value(value: serde_json::Value) -> Result<String, FilterError> {
194 use serde_json::Value::*;
195
196 match value {
197 Null => Ok("NULL".into()),
198 Bool(b) => Ok(b.to_string()),
199 Number(n) => Ok(n.to_string()),
200 String(s) => Ok(format!("'{}'", s.replace("'", "''"))),
201 Array(xs) => Ok(format!(
202 "({})",
203 xs.into_iter()
204 .map(escape_value)
205 .collect::<Result<Vec<_>, _>>()?
206 .join(", ")
207 )),
208 Object(_) => Err(FilterError::TypeError(
209 "objects not supported in SQLite backend".into(),
210 )),
211 }
212}
213
214impl LanceDBFilter {
215 pub fn into_inner(self) -> Result<String, FilterError> {
216 self.0
217 }
218
219 #[allow(clippy::should_implement_trait)]
220 pub fn not(self) -> Self {
221 Self(self.0.map(|s| format!("NOT ({s})")))
222 }
223
224 pub fn in_values(key: String, values: Vec<<Self as SearchFilter>::Value>) -> Self {
226 Self(
227 values
228 .into_iter()
229 .map(escape_value)
230 .collect::<Result<Vec<_>, FilterError>>()
231 .map(|xs| xs.join(","))
232 .map(|xs| format!("{key} IN ({xs})")),
233 )
234 }
235
236 pub fn like<S>(key: String, pattern: S) -> Self
238 where
239 S: AsRef<str>,
240 {
241 Self(
242 escape_value(serde_json::Value::String(pattern.as_ref().into()))
243 .map(|pat| format!("{key} LIKE {pat}")),
244 )
245 }
246
247 pub fn ilike<S>(key: String, pattern: S) -> Self
249 where
250 S: AsRef<str>,
251 {
252 Self(
253 escape_value(serde_json::Value::String(pattern.as_ref().into()))
254 .map(|pat| format!("{key} ILIKE {pat}")),
255 )
256 }
257
258 pub fn is_null(key: String) -> Self {
260 Self(Ok(format!("{key} IS NULL")))
261 }
262
263 pub fn is_not_null(key: String) -> Self {
265 Self(Ok(format!("{key} IS NOT NULL")))
266 }
267
268 pub fn array_has_any(key: String, values: Vec<<Self as SearchFilter>::Value>) -> Self {
270 Self(
271 values
272 .into_iter()
273 .map(escape_value)
274 .collect::<Result<Vec<_>, FilterError>>()
275 .map(|xs| xs.join(","))
276 .map(|xs| format!("array_has_any({key}, ARRAY[{xs}])")),
277 )
278 }
279
280 pub fn array_has_all(key: String, values: Vec<<Self as SearchFilter>::Value>) -> Self {
282 Self(
283 values
284 .into_iter()
285 .map(escape_value)
286 .collect::<Result<Vec<_>, FilterError>>()
287 .map(|xs| xs.join(","))
288 .map(|xs| format!("array_has_all({key}, ARRAY[{xs}])")),
289 )
290 }
291
292 pub fn array_length(key: String, length: i32) -> Self {
294 Self(Ok(format!("array_length({key}) = {length}")))
295 }
296
297 pub fn between<T>(key: String, Range { start, end }: Range<T>) -> Self
299 where
300 T: PartialOrd + std::fmt::Display + Into<serde_json::Number>,
301 {
302 Self(Ok(format!("{key} BETWEEN {start} AND {end}")))
303 }
304}
305
306#[derive(Debug, Clone, Default)]
312pub struct SearchParams {
313 distance_type: Option<DistanceType>,
314 search_type: Option<SearchType>,
315 nprobes: Option<usize>,
316 refine_factor: Option<u32>,
317 post_filter: Option<bool>,
318 column: Option<String>,
319}
320
321impl SearchParams {
322 pub fn distance_type(mut self, distance_type: DistanceType) -> Self {
326 self.distance_type = Some(distance_type);
327 self
328 }
329
330 pub fn search_type(mut self, search_type: SearchType) -> Self {
334 self.search_type = Some(search_type);
335 self
336 }
337
338 pub fn nprobes(mut self, nprobes: usize) -> Self {
342 self.nprobes = Some(nprobes);
343 self
344 }
345
346 pub fn refine_factor(mut self, refine_factor: u32) -> Self {
350 self.refine_factor = Some(refine_factor);
351 self
352 }
353
354 pub fn post_filter(mut self, post_filter: bool) -> Self {
358 self.post_filter = Some(post_filter);
359 self
360 }
361
362 pub fn column(mut self, column: &str) -> Self {
366 self.column = Some(column.to_string());
367 self
368 }
369}
370
371impl<M> VectorStoreIndex for LanceDbVectorIndex<M>
372where
373 M: EmbeddingModel + Sync + Send,
374{
375 type Filter = LanceDBFilter;
376
377 async fn top_n<T: for<'a> Deserialize<'a> + Send>(
396 &self,
397 req: VectorSearchRequest<LanceDBFilter>,
398 ) -> Result<Vec<(f64, String, T)>, VectorStoreError> {
399 let prompt_embedding = self.model.embed_text(req.query()).await?;
400
401 let mut query = self
402 .table
403 .vector_search(prompt_embedding.vec.clone())
404 .map_err(VectorStoreError::datastore)?
405 .limit(req.samples() as usize)
406 .distance_range(None, req.threshold().map(|x| x as f32))
407 .select(lancedb::query::Select::Columns(
408 self.table
409 .schema()
410 .await
411 .map_err(VectorStoreError::datastore)?
412 .filter_embeddings(),
413 ));
414
415 if let Some(filter) = req.filter() {
416 query = query.only_if(filter.clone().into_inner()?)
417 }
418
419 self.build_query(query)
420 .execute_query()
421 .await?
422 .into_iter()
423 .enumerate()
424 .map(|(i, value)| {
425 Ok((
426 match value.get("_distance") {
427 Some(Value::Number(distance)) => distance.as_f64().unwrap_or_default(),
428 _ => 0.0,
429 },
430 match value.get(self.id_field.clone()) {
431 Some(Value::String(id)) => id.to_string(),
432 _ => format!("unknown{i}"),
433 },
434 serde_json::from_value(value).map_err(VectorStoreError::JsonError)?,
435 ))
436 })
437 .collect()
438 }
439
440 async fn top_n_ids(
459 &self,
460 req: VectorSearchRequest<LanceDBFilter>,
461 ) -> Result<Vec<(f64, String)>, VectorStoreError> {
462 let prompt_embedding = self.model.embed_text(req.query()).await?;
463
464 let mut query = self
465 .table
466 .query()
467 .select(lancedb::query::Select::Columns(vec![self.id_field.clone()]))
468 .nearest_to(prompt_embedding.vec.clone())
469 .map_err(VectorStoreError::datastore)?
470 .distance_range(None, req.threshold().map(|x| x as f32))
471 .limit(req.samples() as usize);
472
473 if let Some(filter) = req.filter() {
474 query = query.only_if(filter.clone().into_inner()?)
475 }
476
477 self.build_query(query)
478 .execute_query()
479 .await?
480 .into_iter()
481 .map(|value| {
482 Ok((
483 match value.get("distance") {
484 Some(Value::Number(distance)) => distance.as_f64().unwrap_or_default(),
485 _ => 0.0,
486 },
487 match value.get(self.id_field.clone()) {
488 Some(Value::String(id)) => id.to_string(),
489 _ => "".to_string(),
490 },
491 ))
492 })
493 .collect()
494 }
495}