rig_core/providers/gemini/
embedding.rs1use serde_json::{Map, Value, json};
13
14use crate::embeddings;
15use crate::error::EncodeError;
16use crate::providers::internal::wire::classify_marker_keyed_frame;
17use crate::wire::{Body, Capabilities, Descriptor, DocumentDecoder, Encoded, Framing, Mode, Wire};
18
19pub const EMBEDDING_001: &str = "gemini-embedding-001";
21pub const EMBEDDING_004: &str = "text-embedding-004";
23
24pub fn default_ndims(model: &str) -> usize {
30 match model {
31 EMBEDDING_001 => 3072,
32 _ => 768,
33 }
34}
35
36#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
41pub struct Embeddings {
42 pub provider: super::GeminiConfig,
44 pub model: String,
46 pub ndims: usize,
48}
49
50impl Embeddings {
51 pub fn new(
54 provider: super::GeminiConfig,
55 model: impl Into<String>,
56 ndims: Option<usize>,
57 ) -> Self {
58 let model = model.into();
59 let ndims = ndims.unwrap_or_else(|| default_ndims(&model));
60 Self {
61 provider,
62 model,
63 ndims,
64 }
65 }
66}
67
68impl Wire for Embeddings {
69 type Op = crate::operation::Embedding;
70 type Payload = crate::wire::Encoded;
71 type Frame = crate::wire::WireFrame;
72 type Event = gemini_api_types::EmbeddingResponse;
73 type Decoder<'id> = DocumentDecoder<Self::Op, Self::Event>;
74 type Reassembler = crate::wire::document::Unreassembled;
75
76 fn describe(&self) -> Descriptor<'_> {
77 Descriptor::new(super::PROVIDER_NAME)
78 .model(self.model.as_str())
79 .capabilities(Capabilities::embedding(1024, self.ndims))
80 }
81
82 fn encode(&self, request: Vec<String>, _mode: Mode) -> Result<Encoded, EncodeError> {
83 let model = format!("models/{}", self.model);
84 let requests = request.iter().map(|doc| {
85 json!({
86 "model": model,
87 "content": { "parts": [{ "text": doc }] },
88 "output_dimensionality": self.ndims,
89 })
90 });
91 let body = Value::Object(Map::from_iter([(
92 "requests".to_owned(),
93 Value::Array(requests.collect()),
94 )]));
95
96 if tracing::enabled!(target: "rig::embedding", tracing::Level::TRACE)
98 && let Ok(pretty_body) = serde_json::to_string_pretty(&body)
99 {
100 tracing::trace!(
101 target: "rig::embedding",
102 "Sending embedding request to Gemini API {pretty_body}"
103 );
104 }
105
106 let request = http::Request::post(format!(
107 "{}/v1beta/models/{}:batchEmbedContents?key={}",
108 self.provider.base_url,
109 self.model,
110 self.provider.api_key.expose()
111 ))
112 .header(http::header::CONTENT_TYPE, "application/json")
113 .body(Body::Bytes(serde_json::to_vec(&body)?))?;
114 Ok(Encoded::new(request, Framing::Whole))
117 }
118
119 fn decoder<'id>(&self) -> Self::Decoder<'id> {
120 DocumentDecoder::new(
121 |body| classify_marker_keyed_frame(body, &["embeddings"]),
122 |reply| {
123 let vectors = reply
124 .embeddings
125 .into_iter()
126 .map(|embedding| embedding.values);
127 Ok(embeddings::EmbeddingResponse::from_vectors(vectors))
129 },
130 )
131 }
132}
133
134pub mod gemini_api_types {
142 use serde::{Deserialize, Serialize};
143
144 #[derive(Debug, Clone, Serialize, Deserialize)]
145 pub struct EmbeddingResponse {
146 pub embeddings: Vec<EmbeddingValues>,
147 }
148
149 #[derive(Debug, Clone, Serialize, Deserialize)]
150 pub struct EmbeddingValues {
151 #[serde(default)]
152 pub values: Vec<f64>,
153 }
154}
155
156impl super::GeminiConfig {
157 pub(crate) fn embedding(&self, model: impl Into<String>, ndims: Option<usize>) -> Embeddings {
160 Embeddings::new(self.clone(), model, ndims)
161 }
162}
163
164#[cfg(test)]
165mod tests;