lunaris_retrieve/operators/
vector.rs1use std::any::Any;
8
9use async_trait::async_trait;
10use lunaris_core::LunarisError;
11
12use super::{QueryContext, Retriever, clamp_k};
13use crate::types::{RawHit, SourceOp};
14
15#[derive(Clone, Debug)]
20#[must_use = "Vector is a query node — pass it to RetrievalBuilder::with_root() or chain via .and/.or/.fuse_rrf, otherwise it never executes"]
21pub struct Vector {
22 pub index: String,
23 pub k: usize,
24}
25
26impl Vector {
27 pub fn new(index: impl Into<String>, k: usize) -> Self {
32 Self { index: index.into(), k: clamp_k(k) }
33 }
34
35 pub fn and<R: Retriever + 'static>(self, other: R) -> super::combinators::AndRetriever {
39 super::combinators::AndRetriever::new(Box::new(self), Box::new(other))
40 }
41 pub fn or<R: Retriever + 'static>(self, other: R) -> super::combinators::OrRetriever {
42 super::combinators::OrRetriever::new(Box::new(self), Box::new(other))
43 }
44 pub fn then<R: Retriever + 'static>(self, other: R) -> super::combinators::ThenRetriever {
45 super::combinators::ThenRetriever::new(Box::new(self), Box::new(other))
46 }
47 pub fn top(self, n: usize) -> super::modifiers::TopRetriever {
49 super::modifiers::TopRetriever::new(Box::new(self), n)
50 }
51
52 pub fn rerank(
54 self,
55 reranker: std::sync::Arc<dyn lunaris_rerank::Reranker>,
56 ) -> super::rerank::RerankRetriever {
57 super::rerank::RerankRetriever::new(Box::new(self), reranker)
58 }
59
60 pub fn degraded_fallback<R: Retriever + 'static>(
63 self,
64 fallback: R,
65 ) -> super::degraded::DegradedFallbackRetriever {
66 super::degraded::DegradedFallbackRetriever::new(Box::new(self), Box::new(fallback))
67 }
68}
69
70#[async_trait]
71impl Retriever for Vector {
72 async fn retrieve(&self, ctx: &QueryContext) -> Result<Vec<RawHit>, LunarisError> {
73 let q_emb = ctx.embed_once().await?;
74 let hits = crate::missing_index::no_rows_if_index_absent(
80 ctx.storage
81 .vector_search(
82 &ctx.scope,
83 &self.index,
84 &q_emb,
85 self.k,
86 ctx.query.filter.as_ref(),
87 ctx.query.as_of,
88 false,
89 )
90 .await,
91 )?;
92 Ok(hits
93 .into_iter()
94 .map(|h| RawHit {
95 id: h.id,
96 score: h.score,
97 rerank_applied: h.rerank_applied,
98 degraded: false,
99 metadata: super::tag_leg_index(h.metadata, &self.index),
100 source_op: SourceOp::Vector,
101 })
102 .collect())
103 }
104
105 fn as_any(&self) -> &dyn Any {
106 self
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn k_is_clamped() {
116 let v = Vector::new("chunks", 1_000_000);
117 assert_eq!(v.k, super::super::MAX_K);
118 }
119
120 #[test]
121 fn vector_new_records_index() {
122 let v = Vector::new("chunks", 30);
123 assert_eq!(v.index, "chunks");
124 assert_eq!(v.k, 30);
125 }
126}