Skip to main content

gemini_rust/generation/
builder.rs

1use std::sync::Arc;
2use tracing::instrument;
3
4use crate::{
5    cache::CachedContentHandle,
6    client::{Error as ClientError, GeminiClient, GenerationStream},
7    files::Error as FilesError,
8    generation::{
9        GenerateContentRequest, ImageConfig, MediaResolutionLevel, SpeakerVoiceConfig,
10        SpeechConfig, ThinkingConfig, ThinkingLevel,
11    },
12    tools::{FunctionCallingConfig, ToolConfig},
13    Content, FileHandle, FunctionCallingMode, FunctionDeclaration, GenerationConfig,
14    GenerationResponse, Message, Role, SafetySetting, Tool,
15};
16
17/// Builder for content generation requests
18#[deprecated(
19    since = "1.8.0",
20    note = "Use crate::interactions::InteractionBuilder instead. See migration guide: interactions-api/migration-plan.md"
21)]
22#[derive(Clone)]
23pub struct ContentBuilder {
24    client: Arc<GeminiClient>,
25    pub contents: Vec<Content>,
26    generation_config: Option<GenerationConfig>,
27    safety_settings: Option<Vec<SafetySetting>>,
28    tools: Option<Vec<Tool>>,
29    tool_config: Option<ToolConfig>,
30    system_instruction: Option<Content>,
31    cached_content: Option<String>,
32}
33
34impl ContentBuilder {
35    /// Creates a new `ContentBuilder`.
36    pub(crate) fn new(client: Arc<GeminiClient>) -> Self {
37        Self {
38            client,
39            contents: Vec::new(),
40            generation_config: None,
41            safety_settings: None,
42            tools: None,
43            tool_config: None,
44            system_instruction: None,
45            cached_content: None,
46        }
47    }
48
49    /// Sets the safety settings for the request.
50    pub fn with_safety_settings(mut self, safety_settings: Vec<SafetySetting>) -> Self {
51        self.safety_settings = Some(safety_settings);
52        self
53    }
54
55    /// Sets the system prompt for the request.
56    ///
57    /// This is an alias for [`with_system_instruction()`](Self::with_system_instruction).
58    pub fn with_system_prompt(self, text: impl Into<String>) -> Self {
59        self.with_system_instruction(text)
60    }
61
62    /// Sets the system instruction for the request.
63    ///
64    /// System instructions are used to provide high-level guidance to the model, such as
65    /// setting a persona, providing context, or defining the desired output format.
66    pub fn with_system_instruction(mut self, text: impl Into<String>) -> Self {
67        let content = Content::text(text);
68        self.system_instruction = Some(content);
69        self
70    }
71
72    /// Adds a user message to the conversation history.
73    pub fn with_user_message(mut self, text: impl Into<String>) -> Self {
74        let message = Message::user(text);
75        self.contents.push(message.content);
76        self
77    }
78
79    /// Adds a user message, together with coordinates for a previously uploaded file.
80    ///
81    /// Uploading a file and using it avoids encoding large files and sending them, in particular
82    /// when this would need to happen more than once with a file.
83    ///
84    /// # Errors
85    ///
86    /// Returns an error if the file metadata is incomplete (missing MIME type or URI).
87    pub fn with_user_message_and_file(
88        mut self,
89        text: impl Into<String>,
90        file_handle: &FileHandle,
91    ) -> Result<Self, FilesError> {
92        let content = Content::text_with_file(text, file_handle)?.with_role(Role::User);
93        self.contents.push(content);
94        Ok(self)
95    }
96
97    /// Adds a model message to the conversation history.
98    pub fn with_model_message(mut self, text: impl Into<String>) -> Self {
99        let message = Message::model(text);
100        self.contents.push(message.content);
101        self
102    }
103
104    /// Adds inline data (e.g., an image) to the request.
105    ///
106    /// The data should be base64-encoded.
107    pub fn with_inline_data(
108        mut self,
109        data: impl Into<String>,
110        mime_type: impl Into<String>,
111    ) -> Self {
112        let content = Content::inline_data(mime_type, data).with_role(Role::User);
113        self.contents.push(content);
114        self
115    }
116
117    /// Adds inline data with explicit media resolution control.
118    ///
119    /// This allows fine-grained control over the resolution used for processing
120    /// the inline data, which affects both quality and token consumption.
121    /// This method is useful for optimizing token usage.
122    /// The data should be base64-encoded.
123    pub fn with_inline_data_and_resolution(
124        mut self,
125        data: impl Into<String>,
126        mime_type: impl Into<String>,
127        resolution: MediaResolutionLevel,
128    ) -> Self {
129        let content =
130            Content::inline_data_with_resolution(mime_type, data, resolution).with_role(Role::User);
131        self.contents.push(content);
132        self
133    }
134
135    /// Adds a function response to the request using a `Serialize` response.
136    ///
137    /// This is used to provide the model with the result of a function call it has requested.
138    pub fn with_function_response<Response>(
139        mut self,
140        name: impl Into<String>,
141        response: Response,
142    ) -> std::result::Result<Self, serde_json::Error>
143    where
144        Response: serde::Serialize,
145    {
146        let content = Content::function_response_json(name, serde_json::to_value(response)?)
147            .with_role(Role::User);
148        self.contents.push(content);
149        Ok(self)
150    }
151
152    /// Adds a function response to the request using a JSON string.
153    ///
154    /// This is a convenience method that parses the string into a `serde_json::Value`.
155    pub fn with_function_response_str(
156        mut self,
157        name: impl Into<String>,
158        response: impl Into<String>,
159    ) -> std::result::Result<Self, serde_json::Error> {
160        let response_str = response.into();
161        let json = serde_json::from_str(&response_str)?;
162        let content = Content::function_response_json(name, json).with_role(Role::User);
163        self.contents.push(content);
164        Ok(self)
165    }
166
167    /// Adds a `Message` to the conversation history.
168    pub fn with_message(mut self, message: Message) -> Self {
169        let content = message.content.clone();
170        let role = content.role.clone().unwrap_or(message.role);
171        self.contents.push(content.with_role(role));
172        self
173    }
174
175    /// Uses cached content for this request.
176    ///
177    /// This allows reusing previously cached system instructions and conversation history,
178    /// which can reduce latency and cost.
179    pub fn with_cached_content(mut self, cached_content: &CachedContentHandle) -> Self {
180        self.cached_content = Some(cached_content.name().to_string());
181        self
182    }
183
184    /// Adds multiple messages to the conversation history.
185    pub fn with_messages(mut self, messages: impl IntoIterator<Item = Message>) -> Self {
186        for message in messages {
187            self = self.with_message(message);
188        }
189        self
190    }
191
192    /// Sets the generation configuration for the request.
193    pub fn with_generation_config(mut self, config: GenerationConfig) -> Self {
194        self.generation_config = Some(config);
195        self
196    }
197
198    /// Sets the temperature for the request.
199    ///
200    /// Temperature controls the randomness of the output. Higher values (e.g., 1.0) produce
201    /// more creative results, while lower values (e.g., 0.2) produce more deterministic results.
202    pub fn with_temperature(mut self, temperature: f32) -> Self {
203        self.generation_config
204            .get_or_insert_with(Default::default)
205            .temperature = Some(temperature);
206        self
207    }
208
209    /// Sets the top-p value for the request.
210    ///
211    /// Top-p is a sampling method that selects the next token from a cumulative probability
212    /// distribution. It can be used to control the diversity of the output.
213    pub fn with_top_p(mut self, top_p: f32) -> Self {
214        self.generation_config
215            .get_or_insert_with(Default::default)
216            .top_p = Some(top_p);
217        self
218    }
219
220    /// Sets the top-k value for the request.
221    ///
222    /// Top-k is a sampling method that selects the next token from the `k` most likely candidates.
223    pub fn with_top_k(mut self, top_k: i32) -> Self {
224        self.generation_config
225            .get_or_insert_with(Default::default)
226            .top_k = Some(top_k);
227        self
228    }
229
230    /// Sets the seed for the request.
231    pub fn with_seed(mut self, seed: i32) -> Self {
232        self.generation_config
233            .get_or_insert_with(Default::default)
234            .seed = Some(seed);
235        self
236    }
237
238    /// Sets the maximum number of output tokens for the request.
239    pub fn with_max_output_tokens(mut self, max_output_tokens: i32) -> Self {
240        self.generation_config
241            .get_or_insert_with(Default::default)
242            .max_output_tokens = Some(max_output_tokens);
243        self
244    }
245
246    /// Sets the number of candidate responses to generate.
247    pub fn with_candidate_count(mut self, candidate_count: i32) -> Self {
248        self.generation_config
249            .get_or_insert_with(Default::default)
250            .candidate_count = Some(candidate_count);
251        self
252    }
253
254    /// Sets the stop sequences for the request.
255    ///
256    /// The model will stop generating text when it encounters one of these sequences.
257    pub fn with_stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
258        self.generation_config
259            .get_or_insert_with(Default::default)
260            .stop_sequences = Some(stop_sequences);
261        self
262    }
263
264    /// Sets the response MIME type for the request.
265    ///
266    /// This can be used to request structured output, such as JSON.
267    pub fn with_response_mime_type(mut self, mime_type: impl Into<String>) -> Self {
268        self.generation_config
269            .get_or_insert_with(Default::default)
270            .response_mime_type = Some(mime_type.into());
271        self
272    }
273
274    /// Sets the response schema for structured output.
275    ///
276    /// When used with a JSON MIME type, this schema will be used to validate the model's
277    /// output.
278    pub fn with_response_schema(mut self, schema: serde_json::Value) -> Self {
279        let config = self.generation_config.get_or_insert_with(Default::default);
280        config.response_schema = Some(schema);
281        config.response_json_schema = None;
282        self
283    }
284
285    /// Sets the response JSON schema for strict structured output.
286    ///
287    /// Prefer this over `with_response_schema` for modern Gemini models.
288    pub fn with_response_json_schema(mut self, schema: serde_json::Value) -> Self {
289        let config = self.generation_config.get_or_insert_with(Default::default);
290        config.response_json_schema = Some(schema);
291        config.response_schema = None;
292        self
293    }
294
295    /// Adds a tool to the request.
296    ///
297    /// Tools allow the model to interact with external systems, such as APIs or databases.
298    pub fn with_tool(mut self, tool: Tool) -> Self {
299        self.tools.get_or_insert_with(Vec::new).push(tool);
300        self
301    }
302
303    /// Adds a function declaration as a tool.
304    ///
305    /// This is a convenience method for creating a `Tool` from a `FunctionDeclaration`.
306    pub fn with_function(mut self, function: FunctionDeclaration) -> Self {
307        let tool = Tool::new(function);
308        self = self.with_tool(tool);
309        self
310    }
311
312    /// Sets the function calling mode for the request.
313    pub fn with_function_calling_mode(mut self, mode: FunctionCallingMode) -> Self {
314        self.tool_config
315            .get_or_insert_with(Default::default)
316            .function_calling_config = Some(FunctionCallingConfig { mode });
317        self
318    }
319
320    /// Sets the tool configuration for the request.
321    pub fn with_tool_config(mut self, tool_config: ToolConfig) -> Self {
322        self.tool_config = Some(tool_config);
323        self
324    }
325
326    /// Sets the thinking configuration for the request (Gemini 2.5 series only).
327    pub fn with_thinking_config(mut self, thinking_config: ThinkingConfig) -> Self {
328        self.generation_config
329            .get_or_insert_with(Default::default)
330            .thinking_config = Some(thinking_config);
331        self
332    }
333
334    /// Sets the thinking budget for the request (Gemini 2.5 series only).
335    ///
336    /// A budget of -1 enables dynamic thinking.
337    /// This is mutually exclusive with `thinking_level` (Gemini 3 models).
338    pub fn with_thinking_budget(mut self, budget: i32) -> Self {
339        let config = self
340            .generation_config
341            .get_or_insert_with(Default::default)
342            .thinking_config
343            .get_or_insert_with(Default::default);
344        config.thinking_budget = Some(budget);
345        config.thinking_level = None;
346        self
347    }
348
349    /// Enables dynamic thinking, which allows the model to decide its own thinking budget
350    /// (Gemini 2.5 series only).
351    ///
352    /// Note: This only enables the *capability*. To receive thoughts in the response,
353    /// you must also call `[.with_thoughts_included(true)](Self::with_thoughts_included)`.
354    pub fn with_dynamic_thinking(self) -> Self {
355        self.with_thinking_budget(-1)
356    }
357
358    /// Includes thought summaries in the response (Gemini 2.5 series only).
359    ///
360    /// This requires `with_dynamic_thinking()` or `with_thinking_budget()` to be enabled.
361    pub fn with_thoughts_included(mut self, include: bool) -> Self {
362        self.generation_config
363            .get_or_insert_with(Default::default)
364            .thinking_config
365            .get_or_insert_with(Default::default)
366            .include_thoughts = Some(include);
367        self
368    }
369
370    /// Sets the thinking level for Gemini 3 Pro.
371    ///
372    /// This controls the depth of reasoning the model applies. Use `Low` for simpler
373    /// queries requiring faster responses, or `High` for complex problems requiring
374    /// deeper analysis.
375    ///
376    /// Note: This is mutually exclusive with `thinking_budget` (used by Gemini 2.5 models).
377    /// Setting this will be ignored by Gemini 2.5 models.
378    pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
379        let config = self
380            .generation_config
381            .get_or_insert_with(Default::default)
382            .thinking_config
383            .get_or_insert_with(Default::default);
384        config.thinking_level = Some(level);
385        config.thinking_budget = None;
386        self
387    }
388
389    /// Sets the global media resolution level.
390    ///
391    /// This controls the token usage for all images and PDFs in the request.
392    /// Individual parts can override this setting using `with_inline_data_and_resolution()`.
393    /// Higher resolutions provide better quality but consume more tokens.
394    pub fn with_media_resolution(mut self, level: MediaResolutionLevel) -> Self {
395        self.generation_config
396            .get_or_insert_with(Default::default)
397            .media_resolution = Some(level);
398        self
399    }
400
401    /// Adds the code execution tool to the request.
402    ///
403    /// This allows the model to generate and execute Python code as part of the
404    /// generation process. Useful for mathematical calculations, data analysis,
405    /// and other computational tasks. Currently supports Python only.
406    pub fn with_code_execution(self) -> Self {
407        self.with_tool(Tool::code_execution())
408    }
409
410    /// Enables audio output (text-to-speech).
411    pub fn with_audio_output(mut self) -> Self {
412        self.generation_config
413            .get_or_insert_with(Default::default)
414            .response_modalities = Some(vec!["AUDIO".to_string()]);
415        self
416    }
417
418    /// Sets the image generation configuration.
419    pub fn with_image_config(mut self, image_config: ImageConfig) -> Self {
420        self.generation_config
421            .get_or_insert_with(Default::default)
422            .image_config = Some(image_config);
423        self
424    }
425
426    /// Sets the speech configuration for text-to-speech generation.
427    pub fn with_speech_config(mut self, speech_config: SpeechConfig) -> Self {
428        self.generation_config
429            .get_or_insert_with(Default::default)
430            .speech_config = Some(speech_config);
431        self
432    }
433
434    /// Sets a single voice for text-to-speech generation.
435    pub fn with_voice(self, voice_name: impl Into<String>) -> Self {
436        let speech_config = SpeechConfig::single_voice(voice_name);
437        self.with_speech_config(speech_config).with_audio_output()
438    }
439
440    /// Sets multi-speaker configuration for text-to-speech generation.
441    pub fn with_multi_speaker_config(self, speakers: Vec<SpeakerVoiceConfig>) -> Self {
442        let speech_config = SpeechConfig::multi_speaker(speakers);
443        self.with_speech_config(speech_config).with_audio_output()
444    }
445
446    /// Builds the `GenerateContentRequest`.
447    pub fn build(self) -> GenerateContentRequest {
448        GenerateContentRequest {
449            contents: self.contents,
450            generation_config: self.generation_config,
451            safety_settings: self.safety_settings,
452            tools: self.tools,
453            tool_config: self.tool_config,
454            system_instruction: self.system_instruction,
455            cached_content: self.cached_content,
456        }
457    }
458
459    /// Executes the content generation request.
460    #[instrument(skip_all, fields(
461        messages.parts.count = self.contents.len(),
462        tools.present = self.tools.is_some(),
463        system.instruction.present = self.system_instruction.is_some(),
464        cached.content.present = self.cached_content.is_some(),
465    ))]
466    pub async fn execute(self) -> Result<GenerationResponse, ClientError> {
467        let client = self.client.clone();
468        let request = self.build();
469        client.generate_content_raw(request).await
470    }
471
472    /// Executes the content generation request as a stream.
473    #[instrument(skip_all, fields(
474        messages.parts.count = self.contents.len(),
475        tools.present = self.tools.is_some(),
476        system.instruction.present = self.system_instruction.is_some(),
477        cached.content.present = self.cached_content.is_some(),
478    ))]
479    pub async fn execute_stream(self) -> Result<GenerationStream, ClientError> {
480        let client = self.client.clone();
481        let request = self.build();
482        client.generate_content_stream(request).await
483    }
484
485    /// Counts the number of tokens in the content generation request.
486    #[instrument(skip_all, fields(
487        messages.parts.count = self.contents.len(),
488    ))]
489    pub async fn count_tokens(self) -> Result<super::model::CountTokensResponse, ClientError> {
490        let client = self.client.clone();
491        let request = self.build();
492        client.count_tokens(request).await
493    }
494}