Skip to main content

ic_rig/embeddings/
embedding.rs

1//! [`EmbeddingModel`] trait, [`Embedding`] struct, and [`EmbeddingError`].
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6// ── Embedding ─────────────────────────────────────────────────────────────────
7
8/// A single document and its vector representation.
9///
10/// The `document` field preserves the original text so embeddings can be
11/// matched back to their source after batching or storage.
12#[derive(Clone, Debug, Default, Serialize, Deserialize)]
13pub struct Embedding {
14    /// The text that was embedded.
15    pub document: String,
16    /// The embedding vector returned by the model.
17    pub vec: Vec<f64>,
18}
19
20impl PartialEq for Embedding {
21    fn eq(&self, other: &Self) -> bool {
22        self.document == other.document
23    }
24}
25
26impl Eq for Embedding {}
27
28// ── EmbeddingError ────────────────────────────────────────────────────────────
29
30#[derive(Debug, Error)]
31pub enum EmbeddingError {
32    #[error("HTTP error: {0}")]
33    Http(String),
34
35    #[error("JSON error: {0}")]
36    Json(#[from] serde_json::Error),
37
38    #[error("Provider error ({status}): {message}")]
39    Provider { status: u16, message: String },
40
41    #[error("Response error: {0}")]
42    Response(String),
43}
44
45// ── EmbeddingModel ────────────────────────────────────────────────────────────
46
47/// Trait for models that can generate vector embeddings from text.
48///
49/// Implement this for each provider. The [`EmbeddingsBuilder`](super::EmbeddingsBuilder)
50/// uses `MAX_DOCUMENTS` to chunk requests so you never exceed the provider's
51/// per-request batch limit.
52///
53/// # Example
54///
55/// ```rust,ignore
56/// use irig::embeddings::{EmbeddingModel, Embedding, EmbeddingError};
57///
58/// pub struct MyEmbedder<H> { client: H, model: String }
59///
60/// impl<H: HttpClient> EmbeddingModel for MyEmbedder<H> {
61///     const MAX_DOCUMENTS: usize = 100;
62///     type Error = EmbeddingError;
63///
64///     fn ndims(&self) -> usize { 1536 }
65///
66///     async fn embed_texts(&self, texts: Vec<String>) -> Result<Vec<Embedding>, EmbeddingError> {
67///         // call API, return one Embedding per input text (same order)
68///         todo!()
69///     }
70/// }
71/// ```
72pub trait EmbeddingModel {
73    /// Maximum number of texts the provider accepts in a single request.
74    const MAX_DOCUMENTS: usize;
75
76    type Error: std::error::Error + 'static;
77
78    /// Dimensionality of the output vectors.
79    fn ndims(&self) -> usize;
80
81    /// Embed a batch of texts. Must return exactly one [`Embedding`] per input
82    /// text, in the same order.
83    fn embed_texts(
84        &self,
85        texts: Vec<String>,
86    ) -> impl std::future::Future<Output = Result<Vec<Embedding>, Self::Error>>;
87
88    /// Embed a single text. Provided as a default for convenience.
89    fn embed_text(
90        &self,
91        text: &str,
92    ) -> impl std::future::Future<Output = Result<Embedding, Self::Error>> {
93        async move {
94            self.embed_texts(vec![text.to_owned()])
95                .await?
96                .into_iter()
97                .next()
98                // embed_texts guarantees one result per input, so this can't fail.
99                .ok_or_else(|| unreachable!("embed_texts returned empty vec for one input"))
100        }
101    }
102}