Skip to main content

gemini_rust/tools/
model.rs

1use schemars::{generate::SchemaSettings, JsonSchema, SchemaGenerator};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use snafu::{ResultExt, Snafu};
5
6/// Tool that can be used by the model
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8#[serde(untagged)]
9pub enum Tool {
10    /// Function-based tool
11    Function {
12        /// The function declaration for the tool
13        function_declarations: Vec<FunctionDeclaration>,
14    },
15    /// Google Search tool
16    GoogleSearch {
17        /// The Google Search configuration
18        google_search: GoogleSearchConfig,
19    },
20    URLContext {
21        url_context: URLContextConfig,
22    },
23    /// Google Maps grounding tool
24    GoogleMaps {
25        /// The Google Maps configuration
26        google_maps: GoogleMapsConfig,
27    },
28    /// Code Execution tool
29    CodeExecution {
30        #[serde(rename = "codeExecution")]
31        code_execution: CodeExecutionConfig,
32    },
33    /// File Search tool for RAG
34    FileSearch {
35        /// The File Search configuration
36        file_search: FileSearchConfig,
37    },
38}
39
40/// Empty configuration for Google Search tool
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct GoogleSearchConfig {}
43
44/// Empty configuration for URL Context tool
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46pub struct URLContextConfig {}
47
48/// Configuration for Google Maps grounding tool
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50#[serde(rename_all = "camelCase")]
51pub struct GoogleMapsConfig {
52    /// Optional: Enable widget context token generation
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub enable_widget: Option<bool>,
55}
56
57/// Configuration for Code Execution tool. Currently accepts no configuration parameters.
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
59pub struct CodeExecutionConfig {}
60
61/// Code generated by the model for execution.
62///
63/// Contains executable code that can be run to compute results as part of the generation process.
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
65#[serde(rename_all = "camelCase")]
66pub struct ExecutableCode {
67    /// Programming language of the code.
68    pub language: CodeLanguage,
69    /// The source code to execute.
70    pub code: String,
71}
72
73/// Programming language for code execution
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
75#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
76pub enum CodeLanguage {
77    /// Python programming language
78    Python,
79}
80
81/// Result of code execution.
82///
83/// Contains the outcome and output of executed code.
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
85#[serde(rename_all = "camelCase")]
86pub struct CodeExecutionResult {
87    /// Whether the execution succeeded or failed.
88    pub outcome: CodeExecutionOutcome,
89    /// The output produced by the code execution (stdout/stderr).
90    pub output: String,
91}
92
93/// Outcome of code execution
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
96pub enum CodeExecutionOutcome {
97    /// Code executed successfully
98    OutcomeOk,
99    /// Code execution failed (e.g., runtime error, exception)
100    OutcomeFailed,
101    /// Code execution exceeded time limit
102    OutcomeDeadlineExceeded,
103}
104
105/// Configuration for File Search tool
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
107#[serde(rename_all = "camelCase")]
108pub struct FileSearchConfig {
109    /// File search store names to search
110    pub file_search_store_names: Vec<String>,
111
112    /// Optional metadata filter (AIP-160 syntax)
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub metadata_filter: Option<String>,
115}
116
117impl Tool {
118    /// Create a new tool with a single function declaration
119    pub fn new(function_declaration: FunctionDeclaration) -> Self {
120        Self::Function {
121            function_declarations: vec![function_declaration],
122        }
123    }
124
125    /// Create a new tool with multiple function declarations
126    pub fn with_functions(function_declarations: Vec<FunctionDeclaration>) -> Self {
127        Self::Function {
128            function_declarations,
129        }
130    }
131
132    /// Create a new Google Search tool
133    pub fn google_search() -> Self {
134        Self::GoogleSearch {
135            google_search: GoogleSearchConfig {},
136        }
137    }
138
139    /// Create a new URL Context tool
140    pub fn url_context() -> Self {
141        Self::URLContext {
142            url_context: URLContextConfig {},
143        }
144    }
145
146    /// Create a new Google Maps grounding tool
147    pub fn google_maps(enable_widget: Option<bool>) -> Self {
148        Self::GoogleMaps {
149            google_maps: GoogleMapsConfig { enable_widget },
150        }
151    }
152
153    /// Create a new Code Execution tool
154    ///
155    /// Enables the model to generate and execute Python code as part of the generation process.
156    /// Useful for mathematical calculations, data analysis, and other computational tasks.
157    pub fn code_execution() -> Self {
158        Self::CodeExecution {
159            code_execution: CodeExecutionConfig {},
160        }
161    }
162
163    /// Create a new File Search tool
164    pub fn file_search(store_names: Vec<String>, metadata_filter: Option<String>) -> Self {
165        Self::FileSearch {
166            file_search: FileSearchConfig {
167                file_search_store_names: store_names,
168                metadata_filter,
169            },
170        }
171    }
172}
173
174/// Defines the function behavior
175#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
176#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
177pub enum Behavior {
178    /// `default` If set, the system will wait to receive the function response before
179    /// continuing the conversation.
180    #[default]
181    Blocking,
182    /// If set, the system will not wait to receive the function response. Instead, it will
183    /// attempt to handle function responses as they become available while maintaining the
184    /// conversation between the user and the model.
185    NonBlocking,
186}
187
188/// Declaration of a function that can be called by the model
189#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
190pub struct FunctionDeclaration {
191    /// The name of the function
192    pub name: String,
193    /// The description of the function
194    pub description: String,
195    /// `Optional` Specifies the function Behavior. Currently only supported by the BidiGenerateContent method.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub behavior: Option<Behavior>,
198    /// `Optional` The parameters for the function
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub(crate) parameters: Option<Value>,
201    /// `Optional` The parameters for the function in JSON Schema format.
202    #[serde(
203        rename = "parametersJsonSchema",
204        skip_serializing_if = "Option::is_none"
205    )]
206    pub(crate) parameters_json_schema: Option<Value>,
207    /// `Optional` Describes the output from this function in JSON Schema format. Reflects the
208    /// Open API 3.03 Response Object. The Schema defines the type used for the response value
209    /// of the function.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub(crate) response: Option<Value>,
212    /// `Optional` Describes the output from this function in JSON Schema format.
213    #[serde(rename = "responseJsonSchema", skip_serializing_if = "Option::is_none")]
214    pub(crate) response_json_schema: Option<Value>,
215}
216
217/// Returns JSON Schema for the given parameters
218fn generate_parameters_schema<Parameters>() -> Value
219where
220    Parameters: JsonSchema + Serialize,
221{
222    // Create SchemaSettings with Gemini-optimized settings, see: https://ai.google.dev/api/caching#Schema
223    let schema_generator = SchemaGenerator::new(SchemaSettings::openapi3().with(|s| {
224        s.inline_subschemas = true;
225        s.meta_schema = None;
226    }));
227
228    let schema = schema_generator.into_root_schema_for::<Parameters>();
229    let mut value = serde_json::to_value(&schema).expect("schema should serialize to JSON value");
230    sanitize_json_schema_openapi3(&mut value);
231    value
232}
233
234fn sanitize_json_schema_openapi3(value: &mut Value) {
235    if let Value::Object(map) = value {
236        map.remove("title");
237        map.remove("components");
238    }
239}
240
241/// Returns JSON Schema for the given parameters (JSON Schema field for Gemini).
242fn generate_parameters_json_schema<Parameters>() -> Value
243where
244    Parameters: JsonSchema + Serialize,
245{
246    let schema_generator = SchemaGenerator::new(SchemaSettings::draft07().with(|s| {
247        s.inline_subschemas = true;
248        s.meta_schema = None;
249    }));
250
251    let schema = schema_generator.into_root_schema_for::<Parameters>();
252    let mut value = serde_json::to_value(&schema).expect("schema should serialize to JSON value");
253    if let Value::Object(map) = &mut value {
254        map.remove("title");
255        map.remove("$schema");
256        map.remove("definitions");
257        map.remove("$defs");
258    }
259    value
260}
261
262impl FunctionDeclaration {
263    /// Create a new function declaration
264    pub fn new(
265        name: impl Into<String>,
266        description: impl Into<String>,
267        behavior: Option<Behavior>,
268    ) -> Self {
269        Self {
270            name: name.into(),
271            description: description.into(),
272            behavior,
273            ..Default::default()
274        }
275    }
276
277    /// Set the parameters for the function using a struct that implements `JsonSchema`
278    pub fn with_parameters<Parameters>(mut self) -> Self
279    where
280        Parameters: JsonSchema + Serialize,
281    {
282        self.parameters = Some(generate_parameters_schema::<Parameters>());
283        self.parameters_json_schema = None;
284        self
285    }
286
287    /// Set the parameters for the function using a JSON Schema representation.
288    pub fn with_parameters_json_schema<Parameters>(mut self) -> Self
289    where
290        Parameters: JsonSchema + Serialize,
291    {
292        self.parameters_json_schema = Some(generate_parameters_json_schema::<Parameters>());
293        self.parameters = None;
294        self
295    }
296
297    /// Set the parameters for the function using a JSON serde value.
298    pub fn with_parameters_value(mut self, mut value: Value) -> Self {
299        sanitize_json_schema_openapi3(&mut value);
300        self.parameters = Some(value);
301        self.parameters_json_schema = None;
302        self
303    }
304
305    /// Set the response schema for the function using a struct that implements `JsonSchema`
306    pub fn with_response<Response>(mut self) -> Self
307    where
308        Response: JsonSchema + Serialize,
309    {
310        self.response = Some(generate_parameters_schema::<Response>());
311        self.response_json_schema = None;
312        self
313    }
314
315    /// Set the response schema for the function using a JSON Schema representation.
316    pub fn with_response_json_schema<Response>(mut self) -> Self
317    where
318        Response: JsonSchema + Serialize,
319    {
320        self.response_json_schema = Some(generate_parameters_json_schema::<Response>());
321        self.response = None;
322        self
323    }
324
325    /// Set the response schema for the function using a JSON serde value.
326    pub fn with_response_value(mut self, mut value: Value) -> Self {
327        sanitize_json_schema_openapi3(&mut value);
328        self.response = Some(value);
329        self.response_json_schema = None;
330        self
331    }
332}
333
334/// A function call made by the model
335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
336pub struct FunctionCall {
337    /// The name of the function
338    pub name: String,
339    /// The arguments for the function
340    pub args: serde_json::Value,
341    /// The thought signature for the function call (Gemini 2.5 series only)
342    #[serde(skip_serializing_if = "Option::is_none")]
343    pub thought_signature: Option<String>,
344}
345
346#[derive(Debug, Snafu)]
347pub enum FunctionCallError {
348    #[snafu(display("failed to deserialize parameter '{key}'"))]
349    Deserialization {
350        source: serde_json::Error,
351        key: String,
352    },
353
354    #[snafu(display("parameter '{key}' is missing in arguments '{args}'"))]
355    MissingParameter {
356        key: String,
357        args: serde_json::Value,
358    },
359
360    #[snafu(display("arguments should be an object; actual: {actual}"))]
361    ArgumentTypeMismatch { actual: String },
362}
363
364impl FunctionCall {
365    /// Create a new function call
366    pub fn new(name: impl Into<String>, args: serde_json::Value) -> Self {
367        Self {
368            name: name.into(),
369            args,
370            thought_signature: None,
371        }
372    }
373
374    /// Create a new function call with thought signature
375    pub fn with_thought_signature(
376        name: impl Into<String>,
377        args: serde_json::Value,
378        thought_signature: impl Into<String>,
379    ) -> Self {
380        Self {
381            name: name.into(),
382            args,
383            thought_signature: Some(thought_signature.into()),
384        }
385    }
386
387    /// Get a parameter from the arguments
388    pub fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Result<T, FunctionCallError> {
389        match &self.args {
390            serde_json::Value::Object(obj) => {
391                if let Some(value) = obj.get(key) {
392                    serde_json::from_value(value.clone()).with_context(|_| DeserializationSnafu {
393                        key: key.to_string(),
394                    })
395                } else {
396                    Err(MissingParameterSnafu {
397                        key: key.to_string(),
398                        args: self.args.clone(),
399                    }
400                    .build())
401                }
402            }
403            _ => Err(ArgumentTypeMismatchSnafu {
404                actual: self.args.to_string(),
405            }
406            .build()),
407        }
408    }
409}
410
411/// A response from a function
412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
413pub struct FunctionResponse {
414    /// The name of the function
415    pub name: String,
416    /// The response from the function
417    /// This must be a valid JSON object
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub response: Option<serde_json::Value>,
420}
421
422impl FunctionResponse {
423    /// Create a new function response with a JSON value
424    pub fn new(name: impl Into<String>, response: serde_json::Value) -> Self {
425        Self {
426            name: name.into(),
427            response: Some(response),
428        }
429    }
430
431    /// Create a new function response from a serializable type that will be parsed as JSON
432    pub fn from_schema<Response>(
433        name: impl Into<String>,
434        response: Response,
435    ) -> Result<Self, serde_json::Error>
436    where
437        Response: JsonSchema + Serialize,
438    {
439        let json = serde_json::to_value(&response)?;
440        Ok(Self {
441            name: name.into(),
442            response: Some(json),
443        })
444    }
445
446    /// Create a new function response with a string that will be parsed as JSON
447    pub fn from_str(
448        name: impl Into<String>,
449        response: impl Into<String>,
450    ) -> Result<Self, serde_json::Error> {
451        let json = serde_json::from_str(&response.into())?;
452        Ok(Self {
453            name: name.into(),
454            response: Some(json),
455        })
456    }
457}
458
459/// Configuration for tools
460#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
461pub struct ToolConfig {
462    /// The function calling config
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub function_calling_config: Option<FunctionCallingConfig>,
465    /// Whether to include server-side tool invocations
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub include_server_side_tool_invocations: Option<bool>,
468    /// The retrieval config for location-based tools like Google Maps
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub retrieval_config: Option<RetrievalConfig>,
471}
472
473/// Configuration for function calling
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
475pub struct FunctionCallingConfig {
476    /// The mode for function calling
477    pub mode: FunctionCallingMode,
478}
479
480/// Mode for function calling
481#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
482#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
483pub enum FunctionCallingMode {
484    /// The model may use function calling
485    Auto,
486    /// The model must use function calling
487    Any,
488    /// The model must not use function calling
489    None,
490}
491
492/// Retrieval configuration for location-based tools
493#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
494#[serde(rename_all = "camelCase")]
495pub struct RetrievalConfig {
496    /// Optional: Latitude and longitude for location context
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub lat_lng: Option<LatLng>,
499}
500
501/// Geographic coordinates
502#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
503pub struct LatLng {
504    /// Latitude in degrees
505    pub latitude: f64,
506    /// Longitude in degrees
507    pub longitude: f64,
508}
509
510impl LatLng {
511    /// Create a new LatLng coordinate
512    pub fn new(latitude: f64, longitude: f64) -> Self {
513        Self {
514            latitude,
515            longitude,
516        }
517    }
518}