ic-rig 0.2.0

A lean, modular library for building LLM applications. Bring your own HTTP client.
Documentation
//! [`Embed`] trait and [`TextEmbedder`] accumulator.
//!
//! `Embed` decouples your domain types from the embedding API. Rather than
//! returning a `Vec<String>` (which forces allocation at the call site),
//! your type **pushes** strings into a [`TextEmbedder`]. This lets the
//! [`EmbeddingsBuilder`](super::EmbeddingsBuilder) collect texts from many
//! documents into a single flat buffer before chunking into API requests.
//!
//! A single document can push **multiple** strings — you get one
//! [`Embedding`](super::Embedding) back per push. This is how one struct
//! can produce several vectors (e.g. embed `title` and `body` separately).
//!
//! # Example
//!
//! ```rust
//! use irig::embeddings::{Embed, TextEmbedder, EmbedError};
//!
//! struct Article { title: String, body: String }
//!
//! impl Embed for Article {
//!     fn embed(&self, e: &mut TextEmbedder) -> Result<(), EmbedError> {
//!         e.embed(self.title.clone());
//!         e.embed(self.body.clone());
//!         Ok(())
//!     }
//! }
//! ```

use thiserror::Error;

// ── EmbedError ────────────────────────────────────────────────────────────────

#[derive(Debug, Error)]
#[error("{0}")]
pub struct EmbedError(String);

impl EmbedError {
    pub fn new<E: std::error::Error>(e: E) -> Self {
        Self(e.to_string())
    }

    pub fn msg(s: impl Into<String>) -> Self {
        Self(s.into())
    }
}

// ── TextEmbedder ──────────────────────────────────────────────────────────────

/// Accumulates strings to be embedded. Passed into [`Embed::embed`].
#[derive(Default)]
pub struct TextEmbedder {
    pub(crate) texts: Vec<String>,
}

impl TextEmbedder {
    /// Queue `text` for embedding. Each call produces one output [`Embedding`](super::Embedding).
    pub fn embed(&mut self, text: String) {
        self.texts.push(text);
    }
}

// ── Embed trait ───────────────────────────────────────────────────────────────

/// Implemented by types that can be converted to text for embedding.
///
/// Use the [`derive`](irig_derive) macro or implement manually.
pub trait Embed {
    fn embed(&self, embedder: &mut TextEmbedder) -> Result<(), EmbedError>;
}

/// Extract all texts an object would embed without running the model.
pub fn to_texts(item: impl Embed) -> Result<Vec<String>, EmbedError> {
    let mut embedder = TextEmbedder::default();
    item.embed(&mut embedder)?;
    Ok(embedder.texts)
}

// ── Blanket impls for primitives ──────────────────────────────────────────────

impl Embed for String {
    fn embed(&self, e: &mut TextEmbedder) -> Result<(), EmbedError> {
        e.embed(self.clone());
        Ok(())
    }
}

impl Embed for &str {
    fn embed(&self, e: &mut TextEmbedder) -> Result<(), EmbedError> {
        e.embed(self.to_string());
        Ok(())
    }
}

impl Embed for serde_json::Value {
    fn embed(&self, e: &mut TextEmbedder) -> Result<(), EmbedError> {
        e.embed(serde_json::to_string(self).map_err(EmbedError::new)?);
        Ok(())
    }
}

impl<T: Embed> Embed for &T {
    fn embed(&self, e: &mut TextEmbedder) -> Result<(), EmbedError> {
        (*self).embed(e)
    }
}

impl<T: Embed> Embed for Vec<T> {
    fn embed(&self, e: &mut TextEmbedder) -> Result<(), EmbedError> {
        for item in self {
            item.embed(e)?;
        }
        Ok(())
    }
}

impl<T: Embed> Embed for Option<T> {
    fn embed(&self, e: &mut TextEmbedder) -> Result<(), EmbedError> {
        if let Some(inner) = self {
            inner.embed(e)?;
        }
        Ok(())
    }
}