Skip to main content

gemini_rust/
models.rs

1//! # Core Gemini API Primitives
2//!
3//! This module contains the fundamental building blocks used across the Gemini API.
4//! These core data structures are shared by multiple modules and form the foundation
5//! for constructing requests and parsing responses.
6//!
7//! ## Core Types
8//!
9//! - [`Role`] - Represents the speaker in a conversation (User or Model)
10//! - [`Part`] - Content fragments that make up messages (text, images, function calls)
11//! - [`Blob`] - Binary data with MIME type for inline content
12//! - [`Content`] - Container for parts with optional role assignment
13//! - [`Message`] - Complete message with content and explicit role
14//! - [`Modality`] - Output format types (text, image, audio)
15//!
16//! ## Usage
17//!
18//! These types are typically used in combination with the domain-specific modules:
19//! - `generation` - For content generation requests and responses
20//! - `embedding` - For text embedding operations
21//! - `safety` - For content moderation settings
22//! - `tools` - For function calling capabilities
23//! - `batch` - For batch processing operations
24//! - `cache` - For content caching
25//! - `files` - For file management
26
27#![allow(clippy::enum_variant_names)]
28
29use serde::{Deserialize, Serialize};
30
31use crate::{File, FileHandle, FilesError};
32
33/// Role of a message in a conversation
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
35#[serde(rename_all = "lowercase")]
36pub enum Role {
37    /// Message from the user
38    User,
39    /// Message from the model
40    Model,
41}
42
43/// Content part that can be included in a message
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45#[serde(untagged)]
46pub enum Part {
47    /// Text content
48    Text {
49        /// The text content
50        text: String,
51        /// Whether this is a thought summary (Gemini 2.5 series only)
52        #[serde(skip_serializing_if = "Option::is_none")]
53        thought: Option<bool>,
54        /// The thought signature for the text (Gemini 2.5 series only)
55        #[serde(rename = "thoughtSignature", skip_serializing_if = "Option::is_none")]
56        thought_signature: Option<String>,
57    },
58    InlineData {
59        /// The blob data
60        #[serde(rename = "inlineData")]
61        inline_data: Blob,
62        /// Per-part media resolution override.
63        /// If specified, overrides the global media_resolution setting for this specific inline data.
64        #[serde(skip_serializing_if = "Option::is_none")]
65        media_resolution: Option<super::generation::model::MediaResolution>,
66    },
67    /// Function call from the model
68    FunctionCall {
69        /// The function call details
70        #[serde(rename = "functionCall")]
71        function_call: super::tools::FunctionCall,
72        /// The thought signature for the function call (Gemini 2.5 series only)
73        #[serde(rename = "thoughtSignature", skip_serializing_if = "Option::is_none")]
74        thought_signature: Option<String>,
75    },
76    /// Function response (results from executing a function call)
77    FunctionResponse {
78        /// The function response details
79        #[serde(rename = "functionResponse")]
80        function_response: super::tools::FunctionResponse,
81    },
82    /// Server-side tool call (built-in tools e.g. google_search via includeServerSideToolInvocations)
83    ToolCall {
84        #[serde(rename = "toolCall")]
85        tool_call: serde_json::Value,
86        #[serde(rename = "thoughtSignature", skip_serializing_if = "Option::is_none")]
87        thought_signature: Option<String>,
88    },
89    /// Server-side tool response (built-in tool results)
90    ToolResponse {
91        #[serde(rename = "toolResponse")]
92        tool_response: serde_json::Value,
93        #[serde(rename = "thoughtSignature", skip_serializing_if = "Option::is_none")]
94        thought_signature: Option<String>,
95    },
96    /// File reference for previously uploaded files
97    FileData {
98        #[serde(rename = "fileData")]
99        file_data: FileData,
100    },
101    /// Code generated by the model
102    ExecutableCode {
103        /// The executable code details
104        #[serde(rename = "executableCode")]
105        executable_code: super::tools::ExecutableCode,
106    },
107    /// Result of code execution
108    CodeExecutionResult {
109        /// The code execution result details
110        #[serde(rename = "codeExecutionResult")]
111        code_execution_result: super::tools::CodeExecutionResult,
112    },
113}
114
115/// Coordinates for a previously uploaded file.
116///
117/// This struct contains the coordinates needed to reference a file that was
118/// uploaded to the Gemini API. The file URI and MIME type are provided by
119/// the API when a file is successfully uploaded.
120///
121/// Implements `TryFrom` for [`FileHandle`] (or `&FileHandle` to be precise) for user convenience,
122/// as an uploaded file is represented via the [`File`] type but a [`FileHandle`] is required to
123/// upload a file or search for files.
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
125#[serde(rename_all = "camelCase")]
126pub struct FileData {
127    /// The IANA standard MIME type of the file
128    pub mime_type: String,
129    /// The URI of the uploaded file
130    pub file_uri: String,
131}
132
133impl TryFrom<&FileHandle> for FileData {
134    type Error = FilesError;
135
136    fn try_from(file_handle: &FileHandle) -> Result<Self, Self::Error> {
137        let File { mime_type, uri, .. } = file_handle.get_file_meta();
138
139        let none_fields: Vec<_> = [
140            mime_type.is_none().then_some("mime_type"),
141            uri.is_none().then_some("uri"),
142        ]
143        .into_iter()
144        .flatten()
145        .map(String::from)
146        .collect();
147
148        if !none_fields.is_empty() {
149            return Err(FilesError::Incomplete {
150                fields: none_fields,
151            });
152        }
153
154        Ok(Self {
155            mime_type: mime_type
156                .as_ref()
157                .expect("Some-ness checked above")
158                .to_string(),
159            file_uri: uri.as_ref().expect("Some-ness checked above").to_string(),
160        })
161    }
162}
163
164/// Blob for a message part
165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
166#[serde(rename_all = "camelCase")]
167pub struct Blob {
168    /// The MIME type of the data
169    pub mime_type: String,
170    /// Base64 encoded data
171    pub data: String,
172}
173
174impl Blob {
175    /// Create a new blob with mime type and data
176    pub fn new(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
177        Self {
178            mime_type: mime_type.into(),
179            data: data.into(),
180        }
181    }
182}
183
184/// Content of a message
185#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
186#[serde(rename_all = "camelCase")]
187pub struct Content {
188    /// Parts of the content
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub parts: Option<Vec<Part>>,
191    /// Role of the content
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub role: Option<Role>,
194}
195
196impl Content {
197    /// Create a new text content
198    pub fn text(text: impl Into<String>) -> Self {
199        Self {
200            parts: Some(vec![Part::Text {
201                text: text.into(),
202                thought: None,
203                thought_signature: None,
204            }]),
205            role: None,
206        }
207    }
208
209    /// Create a new content with a function call
210    pub fn function_call(mut function_call: super::tools::FunctionCall) -> Self {
211        let thought_signature = function_call.thought_signature.take();
212        Self {
213            parts: Some(vec![Part::FunctionCall {
214                function_call,
215                thought_signature,
216            }]),
217            role: None,
218        }
219    }
220
221    /// Create a new content with a function call and thought signature
222    pub fn function_call_with_thought(
223        mut function_call: super::tools::FunctionCall,
224        thought_signature: impl Into<String>,
225    ) -> Self {
226        function_call.thought_signature = None;
227        Self {
228            parts: Some(vec![Part::FunctionCall {
229                function_call,
230                thought_signature: Some(thought_signature.into()),
231            }]),
232            role: None,
233        }
234    }
235
236    /// Create a new text content with thought signature
237    pub fn text_with_thought_signature(
238        text: impl Into<String>,
239        thought_signature: impl Into<String>,
240    ) -> Self {
241        Self {
242            parts: Some(vec![Part::Text {
243                text: text.into(),
244                thought: None,
245                thought_signature: Some(thought_signature.into()),
246            }]),
247            role: None,
248        }
249    }
250
251    /// Create a new thought content with thought signature
252    pub fn thought_with_signature(
253        text: impl Into<String>,
254        thought_signature: impl Into<String>,
255    ) -> Self {
256        Self {
257            parts: Some(vec![Part::Text {
258                text: text.into(),
259                thought: Some(true),
260                thought_signature: Some(thought_signature.into()),
261            }]),
262            role: None,
263        }
264    }
265
266    /// Create a new content with a function response
267    pub fn function_response(function_response: super::tools::FunctionResponse) -> Self {
268        Self {
269            parts: Some(vec![Part::FunctionResponse { function_response }]),
270            role: None,
271        }
272    }
273
274    /// Create a new content with a function response from name and JSON value
275    pub fn function_response_json(name: impl Into<String>, response: serde_json::Value) -> Self {
276        Self {
277            parts: Some(vec![Part::FunctionResponse {
278                function_response: super::tools::FunctionResponse::new(name, response),
279            }]),
280            role: None,
281        }
282    }
283
284    /// Create a new content with inline data (blob data)
285    pub fn inline_data(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
286        Self {
287            parts: Some(vec![Part::InlineData {
288                inline_data: Blob::new(mime_type, data),
289                media_resolution: None,
290            }]),
291            role: None,
292        }
293    }
294
295    /// Create a new content with inline data and media resolution
296    pub fn inline_data_with_resolution(
297        mime_type: impl Into<String>,
298        data: impl Into<String>,
299        resolution: super::generation::model::MediaResolutionLevel,
300    ) -> Self {
301        Self {
302            parts: Some(vec![Part::InlineData {
303                inline_data: Blob::new(mime_type, data),
304                media_resolution: Some(super::generation::model::MediaResolution {
305                    level: resolution,
306                }),
307            }]),
308            role: None,
309        }
310    }
311
312    /// Create a new content with text and coordinates to a previously uploaded file
313    pub fn text_with_file(
314        text: impl Into<String>,
315        file_handle: &FileHandle,
316    ) -> Result<Self, FilesError> {
317        Ok(Self {
318            parts: Some(vec![
319                Part::Text {
320                    text: text.into(),
321                    thought: None,
322                    thought_signature: None,
323                },
324                Part::FileData {
325                    file_data: FileData::try_from(file_handle)?,
326                },
327            ]),
328            role: None,
329        })
330    }
331
332    /// Add a role to this content
333    pub fn with_role(mut self, role: Role) -> Self {
334        self.role = Some(role);
335        self
336    }
337}
338
339/// Message in a conversation
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct Message {
342    /// Content of the message
343    pub content: Content,
344    /// Role of the message
345    pub role: Role,
346}
347
348impl Message {
349    /// Create a new user message with text content
350    pub fn user(text: impl Into<String>) -> Self {
351        Self {
352            content: Content::text(text).with_role(Role::User),
353            role: Role::User,
354        }
355    }
356
357    /// Create a new model message with text content
358    pub fn model(text: impl Into<String>) -> Self {
359        Self {
360            content: Content::text(text).with_role(Role::Model),
361            role: Role::Model,
362        }
363    }
364
365    /// Create a new embedding message with text content
366    pub fn embed(text: impl Into<String>) -> Self {
367        Self {
368            content: Content::text(text),
369            role: Role::Model,
370        }
371    }
372
373    /// Create a new function message with function response content from JSON
374    pub fn function(name: impl Into<String>, response: serde_json::Value) -> Self {
375        Self {
376            content: Content::function_response_json(name, response).with_role(Role::Model),
377            role: Role::Model,
378        }
379    }
380
381    /// Create a new function message with function response from a JSON string
382    pub fn function_str(
383        name: impl Into<String>,
384        response: impl Into<String>,
385    ) -> Result<Self, serde_json::Error> {
386        let response_str = response.into();
387        let json = serde_json::from_str(&response_str)?;
388        Ok(Self {
389            content: Content::function_response_json(name, json).with_role(Role::Model),
390            role: Role::Model,
391        })
392    }
393}
394
395/// Content modality type - specifies the format of model output
396#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
397#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
398pub enum Modality {
399    /// Default value.
400    ModalityUnspecified,
401    /// Indicates the model should return a (json) document.
402    Document,
403    /// Indicates the model should return text.
404    Text,
405    /// Indicates the model should return images.
406    Image,
407    /// Indicates the model should return audio.
408    Audio,
409    /// Indicates the model should return video.
410    Video,
411    #[serde(untagged)]
412    Other(String),
413}