Skip to main content

adk_gemini/tools/
model.rs

1use schemars::{JsonSchema, SchemaGenerator, generate::SchemaSettings};
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    /// Google Maps tool
21    GoogleMaps {
22        /// The Google Maps configuration
23        google_maps: Value,
24    },
25    /// Code execution tool
26    CodeExecution {
27        /// The code execution configuration
28        code_execution: Value,
29    },
30    /// URL context tool
31    URLContext {
32        /// The URL context configuration
33        url_context: URLContextConfig,
34    },
35    /// File search tool
36    FileSearch {
37        /// The file search configuration
38        file_search: Value,
39    },
40    /// Computer use tool
41    ComputerUse {
42        /// The computer use configuration
43        computer_use: Value,
44    },
45    /// MCP server tool
46    McpServer {
47        /// The MCP server configuration
48        #[serde(rename = "mcp_server")]
49        mcp_server: Value,
50    },
51}
52
53/// Empty configuration for Google Search tool
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
55pub struct GoogleSearchConfig {}
56
57/// Empty configuration for URL Context tool
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
59pub struct URLContextConfig {}
60
61impl Tool {
62    /// Create a new tool with a single function declaration
63    pub fn new(function_declaration: FunctionDeclaration) -> Self {
64        Self::Function { function_declarations: vec![function_declaration] }
65    }
66
67    /// Create a new tool with multiple function declarations
68    pub fn with_functions(function_declarations: Vec<FunctionDeclaration>) -> Self {
69        Self::Function { function_declarations }
70    }
71
72    /// Create a new Google Search tool
73    pub fn google_search() -> Self {
74        Self::GoogleSearch { google_search: GoogleSearchConfig {} }
75    }
76
77    /// Create a new URL Context tool
78    pub fn url_context() -> Self {
79        Self::URLContext { url_context: URLContextConfig {} }
80    }
81
82    /// Create a new Google Maps tool
83    pub fn google_maps(config: Value) -> Self {
84        Self::GoogleMaps { google_maps: config }
85    }
86
87    /// Create a new code execution tool
88    pub fn code_execution() -> Self {
89        Self::CodeExecution { code_execution: Value::Object(Default::default()) }
90    }
91
92    /// Create a new file search tool
93    pub fn file_search(config: Value) -> Self {
94        Self::FileSearch { file_search: config }
95    }
96
97    /// Create a new computer use tool
98    pub fn computer_use(config: Value) -> Self {
99        Self::ComputerUse { computer_use: config }
100    }
101
102    /// Create a new MCP server tool
103    pub fn mcp_server(config: Value) -> Self {
104        Self::McpServer { mcp_server: config }
105    }
106
107    /// Returns `true` if this tool is a server-side built-in tool (e.g., Google Search,
108    /// URL Context, Google Maps, Code Execution) that Gemini 3 executes internally.
109    ///
110    /// When server-side tools are present, `includeServerSideToolInvocations` should be
111    /// set in the `ToolConfig` so Gemini 3 returns `toolCall`/`toolResponse` parts instead
112    /// of silently truncating the response.
113    pub fn is_server_side(&self) -> bool {
114        !matches!(self, Self::Function { .. })
115    }
116}
117
118/// Defines the function behavior
119#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
120#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
121pub enum Behavior {
122    /// `default` If set, the system will wait to receive the function response before
123    /// continuing the conversation.
124    #[default]
125    Blocking,
126    /// If set, the system will not wait to receive the function response. Instead, it will
127    /// attempt to handle function responses as they become available while maintaining the
128    /// conversation between the user and the model.
129    NonBlocking,
130}
131
132/// Declaration of a function that can be called by the model
133#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
134pub struct FunctionDeclaration {
135    /// The name of the function
136    pub name: String,
137    /// The description of the function
138    pub description: String,
139    /// `Optional` Specifies the function Behavior. Currently only supported by the BidiGenerateContent method.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub behavior: Option<Behavior>,
142    /// `Optional` The parameters for the function
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub(crate) parameters: Option<Value>,
145    /// `Optional` Describes the output from this function in JSON Schema format. Reflects the
146    /// Open API 3.03 Response Object. The Schema defines the type used for the response value
147    /// of the function.
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub(crate) response: Option<Value>,
150}
151
152/// Returns JSON Schema for the given parameters
153fn generate_parameters_schema<Parameters>() -> Value
154where
155    Parameters: JsonSchema + Serialize,
156{
157    // Create SchemaSettings with Gemini-optimized settings, see: https://ai.google.dev/api/caching#Schema
158    let schema_generator = SchemaGenerator::new(SchemaSettings::openapi3().with(|s| {
159        s.inline_subschemas = true;
160        s.meta_schema = None;
161    }));
162
163    let mut schema = schema_generator.into_root_schema_for::<Parameters>();
164
165    // Root schemas always include a title field, which we don't want or need
166    schema.remove("title");
167    schema.to_value()
168}
169
170impl FunctionDeclaration {
171    /// Create a new function declaration
172    pub fn new(
173        name: impl Into<String>,
174        description: impl Into<String>,
175        behavior: Option<Behavior>,
176    ) -> Self {
177        Self { name: name.into(), description: description.into(), behavior, ..Default::default() }
178    }
179
180    /// Set the parameters for the function using a struct that implements `JsonSchema`
181    pub fn with_parameters<Parameters>(mut self) -> Self
182    where
183        Parameters: JsonSchema + Serialize,
184    {
185        self.parameters = Some(generate_parameters_schema::<Parameters>());
186        self
187    }
188
189    /// Set the response schema for the function using a struct that implements `JsonSchema`
190    pub fn with_response<Response>(mut self) -> Self
191    where
192        Response: JsonSchema + Serialize,
193    {
194        self.response = Some(generate_parameters_schema::<Response>());
195        self
196    }
197}
198
199/// A function call made by the model
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
201pub struct FunctionCall {
202    /// The name of the function
203    pub name: String,
204    /// The arguments for the function
205    pub args: serde_json::Value,
206    /// Unique identifier for this function call (Gemini 3 series).
207    ///
208    /// Gemini 3 models return an `id` on each function call to correlate with
209    /// the corresponding `FunctionResponse`. Earlier models may omit this field.
210    #[serde(skip_serializing_if = "Option::is_none", default)]
211    pub id: Option<String>,
212    /// The thought signature for the function call (Gemini 2.5 series only).
213    ///
214    /// Gemini expects this at the enclosing `Part::FunctionCall` level, not inside the
215    /// `functionCall` object. Preserve it in-memory for callers, but never emit it from the
216    /// inner wire type.
217    #[serde(
218        skip_serializing_if = "Option::is_none",
219        default,
220        rename = "thoughtSignature",
221        alias = "thought_signature"
222    )]
223    pub thought_signature: Option<String>,
224}
225
226/// Errors that can occur when extracting parameters from a [`FunctionCall`].
227#[derive(Debug, Snafu)]
228pub enum FunctionCallError {
229    /// Failed to deserialize a parameter value.
230    #[snafu(display("failed to deserialize parameter '{key}'"))]
231    Deserialization {
232        /// The underlying deserialization error.
233        source: serde_json::Error,
234        /// The parameter key that failed to deserialize.
235        key: String,
236    },
237
238    /// A required parameter is missing from the arguments.
239    #[snafu(display("parameter '{key}' is missing in arguments '{args}'"))]
240    MissingParameter {
241        /// The missing parameter key.
242        key: String,
243        /// The arguments object that was searched.
244        args: serde_json::Value,
245    },
246
247    /// The arguments value is not a JSON object.
248    #[snafu(display("arguments should be an object; actual: {actual}"))]
249    ArgumentTypeMismatch {
250        /// String representation of the actual value type.
251        actual: String,
252    },
253}
254
255impl FunctionCall {
256    /// Create a new function call
257    pub fn new(name: impl Into<String>, args: serde_json::Value) -> Self {
258        Self { name: name.into(), args, id: None, thought_signature: None }
259    }
260
261    /// Create a new function call with thought signature
262    pub fn with_thought_signature(
263        name: impl Into<String>,
264        args: serde_json::Value,
265        thought_signature: impl Into<String>,
266    ) -> Self {
267        Self {
268            name: name.into(),
269            args,
270            id: None,
271            thought_signature: Some(thought_signature.into()),
272        }
273    }
274
275    /// Get a parameter from the arguments
276    pub fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Result<T, FunctionCallError> {
277        match &self.args {
278            serde_json::Value::Object(obj) => {
279                if let Some(value) = obj.get(key) {
280                    serde_json::from_value(value.clone())
281                        .with_context(|_| DeserializationSnafu { key: key.to_string() })
282                } else {
283                    Err(MissingParameterSnafu { key: key.to_string(), args: self.args.clone() }
284                        .build())
285                }
286            }
287            _ => Err(ArgumentTypeMismatchSnafu { actual: self.args.to_string() }.build()),
288        }
289    }
290}
291
292/// A response from a function
293#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
294pub struct FunctionResponse {
295    /// The name of the function
296    pub name: String,
297    /// Unique identifier correlating this response with its [`FunctionCall`].
298    ///
299    /// Gemini 3.x models enforce strict response matching: every `FunctionResponse`
300    /// must echo the `id` from the corresponding `FunctionCall`, the `name` must match,
301    /// and the response count must equal the call count. Mismatches cause the model to
302    /// return empty responses with `finish_reason: STOP`. Earlier models ignore this field.
303    #[serde(skip_serializing_if = "Option::is_none", default)]
304    pub id: Option<String>,
305    /// The response from the function
306    /// This must be a valid JSON object
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub response: Option<serde_json::Value>,
309    /// Multimodal parts nested inside the functionResponse wire object.
310    /// Contains `inlineData` and/or `fileData` entries that accompany the JSON response.
311    /// Gemini 3 expects these inside the `functionResponse`, not as sibling Content parts.
312    #[serde(default, skip_serializing_if = "Vec::is_empty")]
313    pub parts: Vec<FunctionResponsePart>,
314}
315
316/// A part nested inside a `functionResponse` wire object.
317///
318/// Gemini 3 expects multimodal data (images, audio, files) as `inlineData` or `fileData`
319/// entries in a `parts` array within the `functionResponse` JSON.
320#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
321#[serde(untagged)]
322pub enum FunctionResponsePart {
323    /// Inline binary data (base64-encoded).
324    InlineData {
325        /// The inline blob data.
326        #[serde(rename = "inlineData")]
327        inline_data: crate::Blob,
328    },
329    /// File data referenced by URI.
330    FileData {
331        /// The file data reference.
332        #[serde(rename = "fileData")]
333        file_data: crate::FileDataRef,
334    },
335}
336
337impl FunctionResponse {
338    /// Create a new function response with a JSON value
339    pub fn new(name: impl Into<String>, response: serde_json::Value) -> Self {
340        let response = match response {
341            serde_json::Value::Object(_) => response,
342            other => serde_json::json!({ "result": other }),
343        };
344        Self { name: name.into(), id: None, response: Some(response), parts: Vec::new() }
345    }
346
347    /// Set the `id` correlating this response with its [`FunctionCall`].
348    ///
349    /// Required for Gemini 3.x strict response matching — pass the `id` from the
350    /// originating function call.
351    pub fn with_id(mut self, id: impl Into<String>) -> Self {
352        self.id = Some(id.into());
353        self
354    }
355
356    /// Create with JSON response and inline data blobs.
357    pub fn with_inline_data(
358        name: impl Into<String>,
359        response: serde_json::Value,
360        inline_data: Vec<crate::Blob>,
361    ) -> Self {
362        let response = match response {
363            serde_json::Value::Object(_) => response,
364            other => serde_json::json!({ "result": other }),
365        };
366        let parts = inline_data
367            .into_iter()
368            .map(|blob| FunctionResponsePart::InlineData { inline_data: blob })
369            .collect();
370        Self { name: name.into(), id: None, response: Some(response), parts }
371    }
372
373    /// Create with JSON response and file data references.
374    pub fn with_file_data(
375        name: impl Into<String>,
376        response: serde_json::Value,
377        file_data: Vec<crate::FileDataRef>,
378    ) -> Self {
379        let response = match response {
380            serde_json::Value::Object(_) => response,
381            other => serde_json::json!({ "result": other }),
382        };
383        let parts = file_data
384            .into_iter()
385            .map(|fdr| FunctionResponsePart::FileData { file_data: fdr })
386            .collect();
387        Self { name: name.into(), id: None, response: Some(response), parts }
388    }
389
390    /// Create with inline data only (no JSON response).
391    pub fn inline_data_only(name: impl Into<String>, inline_data: Vec<crate::Blob>) -> Self {
392        let parts = inline_data
393            .into_iter()
394            .map(|blob| FunctionResponsePart::InlineData { inline_data: blob })
395            .collect();
396        Self { name: name.into(), id: None, response: None, parts }
397    }
398
399    /// Create a new function response from a serializable type that will be parsed as JSON
400    pub fn from_schema<Response>(
401        name: impl Into<String>,
402        response: Response,
403    ) -> Result<Self, serde_json::Error>
404    where
405        Response: JsonSchema + Serialize,
406    {
407        let json = serde_json::to_value(&response)?;
408        Ok(Self::new(name, json))
409    }
410
411    /// Create a new function response with a string that will be parsed as JSON
412    pub fn from_str(
413        name: impl Into<String>,
414        response: impl Into<String>,
415    ) -> Result<Self, serde_json::Error> {
416        let json = serde_json::from_str(&response.into())?;
417        Ok(Self::new(name, json))
418    }
419}
420
421/// Configuration for tools
422#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
423pub struct ToolConfig {
424    /// The function calling config
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub function_calling_config: Option<FunctionCallingConfig>,
427    /// When true, tells Gemini 3 to include server-side tool invocation parts
428    /// (`toolCall`/`toolResponse`) in the response instead of silently truncating.
429    #[serde(skip_serializing_if = "Option::is_none", rename = "includeServerSideToolInvocations")]
430    pub include_server_side_tool_invocations: Option<bool>,
431    /// Retrieval configuration used by provider-native tools such as Google Maps.
432    #[serde(skip_serializing_if = "Option::is_none", rename = "retrievalConfig")]
433    pub retrieval_config: Option<Value>,
434}
435
436/// Configuration for function calling
437#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
438pub struct FunctionCallingConfig {
439    /// The mode for function calling
440    pub mode: FunctionCallingMode,
441    /// Restricts which functions the model may call.
442    /// Only applicable when mode is `Any`. The model will only call functions
443    /// whose names are in this list.
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub allowed_function_names: Option<Vec<String>>,
446}
447
448/// Mode for function calling
449#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
450#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
451pub enum FunctionCallingMode {
452    /// The model decides whether to call functions (default behavior)
453    Auto,
454    /// The model must call one of the provided functions
455    Any,
456    /// The model must not call any functions
457    None,
458    /// The model validates function calls against the schema but does not force calling.
459    /// Available in Gemini 3 series models.
460    Validated,
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466
467    #[test]
468    fn tool_config_include_server_side_tool_invocations_serde_round_trip() {
469        let config = ToolConfig {
470            function_calling_config: None,
471            include_server_side_tool_invocations: Some(true),
472            retrieval_config: None,
473        };
474
475        let json = serde_json::to_value(&config).unwrap();
476        assert_eq!(json["includeServerSideToolInvocations"], true);
477        // field should use camelCase on the wire
478        assert!(json.get("include_server_side_tool_invocations").is_none());
479
480        let deserialized: ToolConfig = serde_json::from_value(json).unwrap();
481        assert_eq!(deserialized, config);
482    }
483
484    #[test]
485    fn tool_config_default_omits_server_side_flag() {
486        let config = ToolConfig::default();
487        assert_eq!(config.include_server_side_tool_invocations, None);
488        assert_eq!(config.retrieval_config, None);
489
490        let json = serde_json::to_value(&config).unwrap();
491        assert!(json.get("includeServerSideToolInvocations").is_none());
492    }
493
494    #[test]
495    fn function_calling_mode_validated_serde_round_trip() {
496        let config = FunctionCallingConfig {
497            mode: FunctionCallingMode::Validated,
498            allowed_function_names: None,
499        };
500        let json = serde_json::to_value(&config).unwrap();
501        assert_eq!(json["mode"], "VALIDATED");
502        let deserialized: FunctionCallingConfig = serde_json::from_value(json).unwrap();
503        assert_eq!(deserialized.mode, FunctionCallingMode::Validated);
504    }
505
506    #[test]
507    fn function_calling_config_with_allowed_names() {
508        let config = FunctionCallingConfig {
509            mode: FunctionCallingMode::Any,
510            allowed_function_names: Some(vec!["get_weather".to_string(), "search".to_string()]),
511        };
512        let json = serde_json::to_value(&config).unwrap();
513        assert_eq!(json["mode"], "ANY");
514        assert_eq!(json["allowed_function_names"], serde_json::json!(["get_weather", "search"]));
515
516        let deserialized: FunctionCallingConfig = serde_json::from_value(json).unwrap();
517        assert_eq!(deserialized, config);
518    }
519
520    #[test]
521    fn function_calling_config_omits_none_allowed_names() {
522        let config =
523            FunctionCallingConfig { mode: FunctionCallingMode::Auto, allowed_function_names: None };
524        let json = serde_json::to_value(&config).unwrap();
525        assert!(json.get("allowed_function_names").is_none());
526    }
527
528    #[test]
529    fn function_call_with_id_serde_round_trip() {
530        let call = FunctionCall {
531            name: "get_weather".to_string(),
532            args: serde_json::json!({"city": "Tokyo"}),
533            id: Some("fc_001".to_string()),
534            thought_signature: None,
535        };
536        let json = serde_json::to_value(&call).unwrap();
537        assert_eq!(json["id"], "fc_001");
538
539        let deserialized: FunctionCall = serde_json::from_value(json).unwrap();
540        assert_eq!(deserialized.id, Some("fc_001".to_string()));
541    }
542
543    #[test]
544    fn function_call_without_id_omits_field() {
545        let call = FunctionCall::new("get_weather", serde_json::json!({"city": "Tokyo"}));
546        let json = serde_json::to_value(&call).unwrap();
547        assert!(json.get("id").is_none());
548    }
549
550    #[test]
551    fn function_call_deserializes_without_id() {
552        let json = serde_json::json!({
553            "name": "get_weather",
554            "args": {"city": "Tokyo"}
555        });
556        let call: FunctionCall = serde_json::from_value(json).unwrap();
557        assert_eq!(call.id, None);
558        assert_eq!(call.name, "get_weather");
559    }
560}