gemini_genai_rs/embed/
mod.rs1use serde::{Deserialize, Serialize};
6
7use crate::client::Client;
8use crate::client::http::HttpError;
9use crate::protocol::types::{Content, ModelId};
10use crate::transport::auth::ServiceEndpoint;
11
12#[derive(Debug, Clone)]
14pub struct EmbedContentConfig {
15 pub content: Content,
17 pub task_type: Option<TaskType>,
19 pub title: Option<String>,
21 pub output_dimensionality: Option<u32>,
23}
24
25impl EmbedContentConfig {
26 pub fn from_text(text: impl Into<String>) -> Self {
28 Self {
29 content: Content::user(text),
30 task_type: None,
31 title: None,
32 output_dimensionality: None,
33 }
34 }
35
36 pub fn task_type(mut self, task_type: TaskType) -> Self {
38 self.task_type = Some(task_type);
39 self
40 }
41
42 pub fn title(mut self, title: impl Into<String>) -> Self {
44 self.title = Some(title.into());
45 self
46 }
47
48 pub fn output_dimensionality(mut self, dim: u32) -> Self {
50 self.output_dimensionality = Some(dim);
51 self
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
58pub enum TaskType {
59 RetrievalQuery,
61 RetrievalDocument,
63 SemanticSimilarity,
65 Classification,
67 Clustering,
69 QuestionAnswering,
71 FactVerification,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct EmbedContentResponse {
79 pub embedding: ContentEmbedding,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct ContentEmbedding {
87 pub values: Vec<f32>,
89}
90
91#[derive(Debug, thiserror::Error)]
93pub enum EmbedError {
94 #[error(transparent)]
95 Http(#[from] HttpError),
97 #[error("Failed to parse response: {0}")]
98 Parse(#[from] serde_json::Error),
100 #[error("Auth error: {0}")]
101 Auth(#[from] crate::session::AuthError),
103}
104
105impl Client {
106 pub async fn embed_content(
108 &self,
109 text: impl Into<String>,
110 ) -> Result<EmbedContentResponse, EmbedError> {
111 self.embed_content_with(EmbedContentConfig::from_text(text), None)
112 .await
113 }
114
115 pub async fn embed_content_with(
117 &self,
118 config: EmbedContentConfig,
119 model: Option<&ModelId>,
120 ) -> Result<EmbedContentResponse, EmbedError> {
121 let model = model.unwrap_or(self.default_model());
122 let url = self.rest_url_for(ServiceEndpoint::EmbedContent, model);
123 let headers = self.auth_headers().await?;
124
125 let mut body = serde_json::json!({
126 "content": config.content,
127 });
128
129 if let Some(task_type) = config.task_type {
130 body["taskType"] = serde_json::to_value(task_type).unwrap();
131 }
132 if let Some(title) = config.title {
133 body["title"] = serde_json::Value::String(title);
134 }
135 if let Some(dim) = config.output_dimensionality {
136 body["outputDimensionality"] = serde_json::json!(dim);
137 }
138
139 let json = self.http_client().post_json(&url, headers, &body).await?;
140 Ok(serde_json::from_value(json)?)
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn parse_embed_response() {
150 let json = serde_json::json!({
151 "embedding": {
152 "values": [0.1, 0.2, 0.3, 0.4]
153 }
154 });
155 let resp: EmbedContentResponse = serde_json::from_value(json).unwrap();
156 assert_eq!(resp.embedding.values.len(), 4);
157 assert!((resp.embedding.values[0] - 0.1).abs() < f32::EPSILON);
158 }
159
160 #[test]
161 fn embed_config_builder() {
162 let config = EmbedContentConfig::from_text("Hello")
163 .task_type(TaskType::RetrievalQuery)
164 .output_dimensionality(256);
165 assert_eq!(config.task_type, Some(TaskType::RetrievalQuery));
166 assert_eq!(config.output_dimensionality, Some(256));
167 }
168}