link_r/embed/mod.rs
1// Author: support@lituus.io
2// Copyright: lituus-io, all rights reserved
3//
4//! Embedders: turn distilled text into dense vectors.
5//!
6//! [`Embedder`] is a GAT-based async trait so a synchronous embedder (the
7//! deterministic [`HashEmbedder`]) and an async one (a remote API) share one
8//! interface with no `Box<dyn Future>`. The index records an [`EmbedderId`] so a
9//! query can verify it is using a compatible embedder at open time.
10
11pub mod hash;
12#[cfg(feature = "onnx")]
13pub mod onnx;
14
15pub use hash::HashEmbedder;
16#[cfg(feature = "onnx")]
17pub use onnx::OnnxEmbedder;
18
19use crate::error::Result;
20use crate::metric::Metric;
21use std::future::Future;
22
23/// A stable identity for an embedder configuration (model + dimension + params).
24///
25/// Persisted in the index header; a search validates that the query embedder's
26/// identity matches, catching "indexed with model A, querying with model B".
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub struct EmbedderId(pub u64);
29
30impl EmbedderId {
31 /// Derive an id from a name and discriminating parameters.
32 #[must_use]
33 pub fn derive(name: &str, dim: usize, params: u64) -> Self {
34 use graph_r::hash::xxh3_64;
35 let mut seed = xxh3_64(name.as_bytes());
36 seed ^= (dim as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
37 seed ^= params.wrapping_mul(0xC2B2_AE3D_27D4_EB4F);
38 Self(seed)
39 }
40
41 /// The raw value (as stored in the index header).
42 #[must_use]
43 pub fn raw(self) -> u64 {
44 self.0
45 }
46}
47
48/// Turns text into dense vectors.
49///
50/// Implementors fill a caller-provided `out` buffer (`texts.len() × dim`,
51/// row-major) to keep allocation under the caller's control. Synchronous
52/// embedders return a ready future; remote ones return their request future.
53pub trait Embedder: Send + Sync {
54 /// The future returned by [`Embedder::embed_batch`].
55 type EmbedFuture<'a>: Future<Output = Result<()>> + Send + 'a
56 where
57 Self: 'a;
58
59 /// The embedding dimension.
60 fn dim(&self) -> usize;
61
62 /// The metric the produced vectors are intended for.
63 fn metric(&self) -> Metric;
64
65 /// This embedder's stable identity.
66 fn identity(&self) -> EmbedderId;
67
68 /// Embed `texts` into `out` (`texts.len() * dim` floats, row-major).
69 ///
70 /// Implementations must error (not panic) if `out.len() != texts.len() * dim`.
71 fn embed_batch<'a>(&'a self, texts: &'a [&'a str], out: &'a mut [f32])
72 -> Self::EmbedFuture<'a>;
73}
74
75/// Embed a single query string into a fresh vector.
76pub async fn embed_one<E: Embedder>(embedder: &E, text: &str) -> Result<Vec<f32>> {
77 let mut out = vec![0.0f32; embedder.dim()];
78 let texts = [text];
79 embedder.embed_batch(&texts, &mut out).await?;
80 Ok(out)
81}