semantic bufo search
find-bufo.com
bufo
1use anyhow::{Context, Result};
2use reqwest::Client;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Serialize)]
6struct VoyageEmbeddingRequest {
7 inputs: Vec<MultimodalInput>,
8 model: String,
9 #[serde(skip_serializing_if = "Option::is_none")]
10 input_type: Option<String>,
11}
12
13#[derive(Debug, Serialize)]
14struct MultimodalInput {
15 content: Vec<ContentSegment>,
16}
17
18#[derive(Debug, Serialize)]
19#[serde(tag = "type", rename_all = "snake_case")]
20enum ContentSegment {
21 Text { text: String },
22}
23
24#[derive(Debug, Deserialize)]
25struct VoyageEmbeddingResponse {
26 data: Vec<VoyageEmbeddingData>,
27}
28
29#[derive(Debug, Deserialize)]
30struct VoyageEmbeddingData {
31 embedding: Vec<f32>,
32}
33
34pub struct EmbeddingClient {
35 client: Client,
36 api_key: String,
37}
38
39impl EmbeddingClient {
40 pub fn new(api_key: String) -> Self {
41 Self {
42 client: Client::new(),
43 api_key,
44 }
45 }
46
47 pub async fn embed_text(&self, text: &str) -> Result<Vec<f32>> {
48 let request = VoyageEmbeddingRequest {
49 inputs: vec![MultimodalInput {
50 content: vec![ContentSegment::Text {
51 text: text.to_string(),
52 }],
53 }],
54 model: "voyage-multimodal-3".to_string(),
55 input_type: Some("query".to_string()),
56 };
57
58 let response = self
59 .client
60 .post("https://api.voyageai.com/v1/multimodalembeddings")
61 .header("Authorization", format!("Bearer {}", self.api_key))
62 .json(&request)
63 .send()
64 .await
65 .context("failed to send embedding request")?;
66
67 if !response.status().is_success() {
68 let status = response.status();
69 let body = response.text().await.unwrap_or_default();
70 anyhow::bail!("voyage api error ({}): {}", status, body);
71 }
72
73 let embedding_response: VoyageEmbeddingResponse = response
74 .json()
75 .await
76 .context("failed to parse embedding response")?;
77
78 let embedding = embedding_response
79 .data
80 .into_iter()
81 .next()
82 .map(|d| d.embedding)
83 .context("no embedding returned")?;
84
85 Ok(embedding)
86 }
87}