Skip to main content

fathomdb_engine/embedder/
mod.rs

1//! Read-time query embedder trait and identity types.
2//!
3//! Phase 12.5a defines the always-on scaffolding that Phase 12.5b (the
4//! Candle + bge-small-en-v1.5 default implementation) plugs into behind the
5//! `default-embedder` feature flag. The trait lives in `fathomdb-engine`
6//! rather than `fathomdb-query` so that `fathomdb-query` stays a pure
7//! AST-to-plan compiler with no dyn trait objects or runtime state.
8//!
9//! The coordinator owns an `Option<Arc<dyn QueryEmbedder>>`. When present,
10//! `ExecutionCoordinator::fill_vector_branch` invokes `embed_query` on the
11//! raw natural-language query, serializes the returned `Vec<f32>` via
12//! `serde_json` into the JSON float-array literal that
13//! `CompiledVectorSearch::query_text` already expects, and drops a fully
14//! constructed `CompiledVectorSearch` into `plan.vector`. When absent, the
15//! plan's vector slot stays `None` and the Phase 12 v1 dormancy invariant
16//! on `search()` is preserved unchanged.
17
18use thiserror::Error;
19
20#[cfg(feature = "default-embedder")]
21pub mod builtin;
22
23#[cfg(feature = "default-embedder")]
24pub use builtin::BuiltinBgeSmallEmbedder;
25
26/// A read-time query embedder.
27///
28/// Implementations must be `Send + Sync` so the coordinator can share a
29/// single `Arc<dyn QueryEmbedder>` across reader threads without cloning
30/// per call. All methods are `&self` — embedders are expected to be
31/// internally immutable or to manage their own interior mutability.
32pub trait QueryEmbedder: Send + Sync + std::fmt::Debug {
33    /// Embed a single query string into a dense vector.
34    ///
35    /// # Errors
36    /// Returns [`EmbedderError::Unavailable`] if the embedder cannot
37    /// produce a vector right now (e.g. the model weights failed to load
38    /// under a feature-flag stub), or [`EmbedderError::Failed`] if the
39    /// embedding pipeline itself errored. The coordinator treats either
40    /// variant as a graceful degradation, NOT a hard query failure.
41    fn embed_query(&self, text: &str) -> Result<Vec<f32>, EmbedderError>;
42
43    /// Model identity / version / dimension / normalization identity.
44    ///
45    /// Must match the write-time contract for the corresponding vec table.
46    /// Phase 12.5a does not yet enforce the match at runtime; Phase 12.5b
47    /// will gate the vector branch on `identity()` equality with the
48    /// active vector profile.
49    fn identity(&self) -> QueryEmbedderIdentity;
50}
51
52/// Identity metadata for a [`QueryEmbedder`].
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct QueryEmbedderIdentity {
55    /// Stable model identifier (e.g. `"bge-small-en-v1.5"`).
56    pub model_identity: String,
57    /// Model version (e.g. `"1.5"`).
58    pub model_version: String,
59    /// Output dimension. Must match the active vector profile's dimension
60    /// or the vector branch will never fire.
61    pub dimension: usize,
62    /// Normalization policy identifier (e.g. `"l2"`, `"none"`).
63    pub normalization_policy: String,
64}
65
66/// Errors reported by a [`QueryEmbedder`].
67///
68/// Both variants are treated as capability misses by the coordinator:
69/// `plan.was_degraded_at_plan_time` is set and the vector branch is
70/// skipped, but the rest of the search pipeline proceeds normally.
71#[derive(Debug, Error)]
72pub enum EmbedderError {
73    /// The embedder is not available at all (e.g. the default-embedder
74    /// feature flag is disabled, or the model weights failed to load).
75    #[error("embedder unavailable: {0}")]
76    Unavailable(String),
77    /// The embedder is present but failed to embed this particular query.
78    #[error("embedding failed: {0}")]
79    Failed(String),
80}