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