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