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}