PrecomputedSemanticIndex

Struct PrecomputedSemanticIndex 

Source
pub struct PrecomputedSemanticIndex { /* private fields */ }
Expand description

A semantic index built ahead of time and searched in process.

Implementations§

Source§

impl PrecomputedSemanticIndex

Source

pub async fn build( records: &[CanonicalMemory], embedder: Arc<dyn Embedder>, ) -> Result<Self, MemoryError>

Embed a corpus and build the index.

Only active records are indexed: a superseded fact should not be retrievable by paraphrase when it is not retrievable by name.

Each record is embedded as embedding_text renders it — the statement plus its frontmatter as prose — which is the text that measured best by a wide margin. Passing anything else is the single easiest way to lose most of what the semantic layer is worth.

Source

pub fn from_vectors( vectors: Vec<(MemoryId, String, Vec<f32>)>, embedder: Arc<dyn Embedder>, ) -> Self

Build from vectors that were embedded elsewhere.

The path for a caller that already batches its embedding — concurrently, or in a nightly job — rather than awaiting one record at a time as build does.

Each entry is (id, the text that was embedded, the vector). The text is required rather than convenient: the index hashes it so that SemanticFallback::reconcile can tell an unchanged record from one whose wording has moved. Without it every reconcile would re-embed the whole corpus, which is the cost this type exists to avoid.

Source

pub async fn restore( store: Arc<dyn VectorStore>, embedder: Arc<dyn Embedder>, ) -> Result<Self, MemoryError>

Keep vectors in store, and load whatever it already holds.

This is the constructor a long-lived process wants. Without it every start pays one embedding round trip per record — 259 ms each, so an hour and a quarter at 16,000 records before the first semantic answer, again on every deploy and every replica.

A stored vector is only trusted while the text that produced it is unchanged; SemanticFallback::reconcile checks the hash and re-embeds anything that has moved. So a restore is a fast start, never a stale one.

Source

pub fn with_store(self, store: Arc<dyn VectorStore>) -> Self

Attach a store to an index built in memory.

Source

pub fn without_rerank(self) -> Self

Drop the float vectors, keeping only the packed codes.

Trades about one question in 93 for roughly 24× less memory — 2 MB against 49 MB at 16,000 records. Worth it when the index is resident per user and there are many users; not worth it otherwise.

Source

pub fn len(&self) -> usize

How many records are indexed.

Source

pub fn is_empty(&self) -> bool

Whether the index holds nothing.

Source

pub fn bytes_per_record(&self) -> usize

Bytes held per record, packed codes plus float vectors if reranking.

Exposed because the memory figure is the reason to quantize at all, and a number a caller can assert on is more useful than a claim in a doc comment.

Source

pub fn search_vector(&self, query: &[f32], limit: usize) -> Vec<MemoryId>

Rank ids against an already-embedded query.

Separated from search so the scan can be measured, and used, without a network call in the way.

Trait Implementations§

Source§

impl SemanticFallback for PrecomputedSemanticIndex

Source§

fn reconcile<'life0, 'life1, 'async_trait>( &'life0 self, active: &'life1 [(MemoryId, String)], revision: u64, ) -> Pin<Box<dyn Future<Output = Result<(), MemoryError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Bring the index in line with the active corpus.

Idempotent by construction: active is the whole desired state, so this embeds the ids it does not hold, drops the ids no longer present, and leaves the rest alone. Calling it twice costs one pass over a hash set the second time.

Only genuinely new records are embedded, which is the difference between a correction costing one 259 ms round trip and costing one per record in the corpus. The lock is not held across any of those awaits — embedding happens first, and the index is only taken for the swap at the end — so a recall running concurrently sees either the old set or the new one and never blocks on the network.

Source§

fn search<'life0, 'life1, 'async_trait>( &'life0 self, query: &'life1 str, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<MemoryId>, MemoryError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Embed the query, then scan.

The embed is the only network call on this path, and on the interactive budget it is almost certainly too slow — 259 ms measured against 10 ms. The retriever bounds it with a timeout and treats a miss as “no semantic opinion”, so an over-budget embedder degrades to lexical results rather than delaying the turn. That is a real degradation, not a free one: with a remote embedder the semantic layer effectively only runs on the speculative path, and only if that budget is raised past the round trip.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<S> FromSample<S> for S

§

fn from_sample_(s: S) -> S

§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> IntoRequest<T> for T

§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
§

impl<L> LayerExt<L> for L

§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in [Layered].
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,