Skip to main content

uni_xervo/traits/
multimodal.rs

1//! Multimodal embedding types and traits (image / audio / mixed-modality).
2//!
3//! These types accompany the trait definitions for embedders that consume
4//! non-text inputs. Each trait sits alongside
5//! [`EmbeddingModel`](crate::traits::EmbeddingModel) and returns an
6//! [`EmbedResult`] so remote providers can attach per-call usage counts.
7
8use crate::error::Result;
9use crate::traits::{EmbedResult, ImageInput, ModelInfo};
10use async_trait::async_trait;
11
12/// A model that produces dense vector embeddings from images.
13///
14/// One vector is returned per input image, each of dimension
15/// [`dimensions`](ImageEmbeddingModel::dimensions). Implementations
16/// typically batch internally for GPU efficiency; callers should pass as
17/// large a batch as fits their latency budget.
18#[async_trait]
19pub trait ImageEmbeddingModel: ModelInfo {
20    /// Embed a batch of images.
21    ///
22    /// # Errors
23    /// Returns an error if the provider fails to load any input, runs out
24    /// of memory, or the upstream API rejects the request.
25    async fn embed(&self, images: Vec<ImageInput>) -> Result<EmbedResult>;
26
27    /// The dimensionality of vectors produced by this model.
28    fn dimensions(&self) -> u32;
29
30    /// Optional warmup hook (e.g. load weights into memory on first access).
31    /// The default is a no-op.
32    ///
33    /// # Errors
34    /// Returns an error if the underlying model fails to initialize.
35    async fn warmup(&self) -> Result<()> {
36        Ok(())
37    }
38}
39
40/// A model that produces dense vector embeddings from audio.
41///
42/// One vector is returned per input audio, each of dimension
43/// [`dimensions`](AudioEmbeddingModel::dimensions). Use cases include
44/// CLAP-style audio similarity, music tagging, and audio-text retrieval.
45#[async_trait]
46pub trait AudioEmbeddingModel: ModelInfo {
47    /// Embed a batch of audio inputs.
48    ///
49    /// # Errors
50    /// Returns an error if the provider cannot decode an input or runs out
51    /// of memory.
52    async fn embed(&self, audios: Vec<AudioInput>) -> Result<EmbedResult>;
53
54    /// The dimensionality of vectors produced by this model.
55    fn dimensions(&self) -> u32;
56
57    /// Optional warmup hook. The default is a no-op.
58    ///
59    /// # Errors
60    /// Returns an error if the underlying model fails to initialize.
61    async fn warmup(&self) -> Result<()> {
62        Ok(())
63    }
64}
65
66/// A model that produces a single dense vector from heterogeneous content
67/// (text + image + audio together).
68///
69/// Supports models like Cohere Embed v4, Jina Embeddings v4, and Gemini
70/// Embedding 2 that map mixed-modality inputs into a single shared embedding
71/// space.
72#[async_trait]
73pub trait MultimodalEmbeddingModel: ModelInfo {
74    /// Embed a batch of mixed-modality inputs.
75    ///
76    /// Each [`MultimodalInput`] produces one vector spanning all its blocks.
77    ///
78    /// # Errors
79    /// Returns an error if the provider rejects an unsupported modality
80    /// combination or runs out of memory.
81    async fn embed(&self, inputs: Vec<MultimodalInput>) -> Result<EmbedResult>;
82
83    /// The dimensionality of vectors produced by this model.
84    fn dimensions(&self) -> u32;
85
86    /// Modalities accepted by this model (e.g. `&[Text, Image]` for Cohere
87    /// Embed v4 vs `&[Text, Image, Audio, Video]` for Gemini Embedding 2).
88    fn supported_modalities(&self) -> &[Modality];
89
90    /// Optional warmup hook. The default is a no-op.
91    ///
92    /// # Errors
93    /// Returns an error if the underlying model fails to initialize.
94    async fn warmup(&self) -> Result<()> {
95        Ok(())
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use std::sync::Arc;
103
104    // Dyn-safety smoke checks. These will fail to compile if any of these
105    // traits accidentally becomes non-object-safe.
106    #[test]
107    fn image_embedding_model_is_dyn_safe() {
108        fn _accept(_: Arc<dyn ImageEmbeddingModel>) {}
109    }
110
111    #[test]
112    fn audio_embedding_model_is_dyn_safe() {
113        fn _accept(_: Arc<dyn AudioEmbeddingModel>) {}
114    }
115
116    #[test]
117    fn multimodal_embedding_model_is_dyn_safe() {
118        fn _accept(_: Arc<dyn MultimodalEmbeddingModel>) {}
119    }
120}
121
122/// An audio input to a transcription or audio embedding model.
123///
124/// Two variants reflect the realistic input shapes:
125/// raw container bytes for HTTP handlers, and pre-decoded PCM for in-process
126/// audio capture. Local-file ingest is the caller's responsibility — uni-xervo
127/// providers do not perform file I/O, matching the [`ImageInput`] design.
128#[derive(Debug, Clone)]
129pub enum AudioInput {
130    /// Raw container bytes (e.g. WAV, MP3, FLAC). The provider decides how to
131    /// decode based on `media_type`.
132    Bytes {
133        /// Audio payload bytes in container format.
134        data: Vec<u8>,
135        /// MIME type indicating the container format (e.g. `"audio/wav"`).
136        media_type: String,
137    },
138    /// Pre-decoded PCM samples. The provider skips the decode step.
139    Pcm {
140        /// Samples per second (Hz).
141        sample_rate: u32,
142        /// Number of interleaved channels (1 = mono, 2 = stereo, …).
143        channels: u16,
144        /// Sample data, length `sample_rate * channels * duration_seconds`.
145        samples: Vec<f32>,
146    },
147}
148
149/// One block of input to a [`MultimodalEmbeddingModel`].
150///
151/// Distinct from [`ContentBlock`](crate::traits::ContentBlock) — that type
152/// is for conversational generation contexts and only carries text + image.
153/// This type extends to audio for use cases like Cohere Embed v4 and Gemini
154/// Embedding 2 that produce a single embedding spanning multiple modalities.
155#[derive(Debug, Clone)]
156pub enum MultimodalBlock {
157    /// Plain text content.
158    Text(String),
159    /// An image input (URL or raw bytes — see [`ImageInput`]).
160    Image(ImageInput),
161    /// An audio input.
162    Audio(AudioInput),
163}
164
165/// A heterogeneous input to a [`MultimodalEmbeddingModel`].
166///
167/// Each input produces one embedding spanning all of its blocks. Multiple
168/// inputs are batched together by passing a `Vec<MultimodalInput>`.
169#[derive(Debug, Clone)]
170pub struct MultimodalInput {
171    /// Ordered sequence of content blocks that share a single embedding.
172    pub blocks: Vec<MultimodalBlock>,
173}
174
175/// Modality labels reported by [`MultimodalEmbeddingModel::supported_modalities`].
176///
177/// The exact set varies per model: Cohere Embed v4 supports `Text + Image`,
178/// Gemini Embedding 2 supports `Text + Image + Audio + Video`, etc.
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
180pub enum Modality {
181    /// Plain text.
182    Text,
183    /// Still images.
184    Image,
185    /// Audio (speech, music, ambient).
186    Audio,
187    /// Video (frame sequences with optional audio).
188    Video,
189}