semantic bufo search find-bufo.com
bufo
at main 102 lines 2.7 kB view raw
1//! voyage AI embedding implementation 2//! 3//! implements the `Embedder` trait for voyage's multimodal-3 model. 4 5use crate::providers::{Embedder, EmbeddingError}; 6use reqwest::Client; 7use serde::{Deserialize, Serialize}; 8 9const VOYAGE_API_URL: &str = "https://api.voyageai.com/v1/multimodalembeddings"; 10const VOYAGE_MODEL: &str = "voyage-multimodal-3"; 11 12#[derive(Debug, Serialize)] 13struct VoyageRequest { 14 inputs: Vec<MultimodalInput>, 15 model: String, 16 #[serde(skip_serializing_if = "Option::is_none")] 17 input_type: Option<String>, 18} 19 20#[derive(Debug, Serialize)] 21struct MultimodalInput { 22 content: Vec<ContentSegment>, 23} 24 25#[derive(Debug, Serialize)] 26#[serde(tag = "type", rename_all = "snake_case")] 27enum ContentSegment { 28 Text { text: String }, 29} 30 31#[derive(Debug, Deserialize)] 32struct VoyageResponse { 33 data: Vec<VoyageEmbeddingData>, 34} 35 36#[derive(Debug, Deserialize)] 37struct VoyageEmbeddingData { 38 embedding: Vec<f32>, 39} 40 41/// voyage AI multimodal embedding client 42/// 43/// uses the voyage-multimodal-3 model which produces 1024-dimensional vectors. 44/// designed for early fusion of text and image content. 45#[derive(Clone)] 46pub struct VoyageEmbedder { 47 client: Client, 48 api_key: String, 49} 50 51impl VoyageEmbedder { 52 pub fn new(api_key: String) -> Self { 53 Self { 54 client: Client::new(), 55 api_key, 56 } 57 } 58} 59 60impl Embedder for VoyageEmbedder { 61 async fn embed(&self, text: &str) -> Result<Vec<f32>, EmbeddingError> { 62 let request = VoyageRequest { 63 inputs: vec![MultimodalInput { 64 content: vec![ContentSegment::Text { 65 text: text.to_string(), 66 }], 67 }], 68 model: VOYAGE_MODEL.to_string(), 69 input_type: Some("query".to_string()), 70 }; 71 72 let response = self 73 .client 74 .post(VOYAGE_API_URL) 75 .header("Authorization", format!("Bearer {}", self.api_key)) 76 .json(&request) 77 .send() 78 .await?; 79 80 if !response.status().is_success() { 81 let status = response.status().as_u16(); 82 let body = response.text().await.unwrap_or_default(); 83 return Err(EmbeddingError::Api { status, body }); 84 } 85 86 let voyage_response: VoyageResponse = response.json().await.map_err(|e| { 87 EmbeddingError::Other(anyhow::anyhow!("failed to parse response: {}", e)) 88 })?; 89 90 voyage_response 91 .data 92 .into_iter() 93 .next() 94 .map(|d| d.embedding) 95 .ok_or(EmbeddingError::EmptyResponse) 96 } 97 98 fn name(&self) -> &'static str { 99 "voyage-multimodal-3" 100 } 101} 102