Skip to main content

ferrin_google/
tools.rs

1//! Factories for Google provider-executed tools.
2//!
3//! Each factory returns a [`ferrin_tool::Tool`] whose definition is a
4//! `ToolDefinition::Provider` with the `google.<tool>` id; the request
5//! preparation converts the arguments to the wire format.
6//! Schemas follow Vercel AI SDK (Apache-2.0, Copyright 2023 Vercel, Inc.);
7//! translated and modified; see `NOTICE`.
8
9use ferrin_spec::JsonObject;
10use ferrin_spec::JsonValue;
11use ferrin_tool::Schema;
12use ferrin_tool::Tool;
13use serde::Serialize;
14use serde_json::json;
15
16use crate::prepare_tools::ids;
17
18fn args<T: Serialize>(value: &T) -> JsonObject {
19    match serde_json::to_value(value) {
20        Ok(JsonValue::Object(mut object)) => {
21            object.retain(|_, value| !value.is_null());
22            object
23        }
24        _ => JsonObject::new(),
25    }
26}
27
28fn executed(id: &str, args: JsonObject) -> Tool {
29    let schema = Schema::from_provider_json_schema(json!({"type":"object","properties":{}}));
30    Tool::provider_executed(id, args)
31        .input_schema(schema.clone())
32        .output_schema(schema)
33        .build()
34}
35
36/// Search types of `google.google_search`.
37#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
38#[serde(rename_all = "camelCase")]
39pub struct SearchTypes {
40    /// Enable web search.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub web_search: Option<JsonObject>,
43    /// Enable image search.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub image_search: Option<JsonObject>,
46}
47
48/// Time range filter of `google.google_search`.
49#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
50#[serde(rename_all = "camelCase")]
51pub struct TimeRangeFilter {
52    /// Start time (RFC 3339).
53    pub start_time: String,
54    /// End time (RFC 3339).
55    pub end_time: String,
56}
57
58/// Arguments of `google.google_search`.
59#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
60#[serde(rename_all = "camelCase")]
61pub struct GoogleSearchArgs {
62    /// Search types.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub search_types: Option<SearchTypes>,
65    /// Time range filter.
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub time_range_filter: Option<TimeRangeFilter>,
68}
69
70/// Arguments of `google.file_search`.
71#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub struct FileSearchArgs {
74    /// File search store names (`fileSearchStores/...`).
75    pub file_search_store_names: Vec<String>,
76    /// Number of chunks to retrieve.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub top_k: Option<u32>,
79    /// Metadata filter expression.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub metadata_filter: Option<String>,
82}
83
84/// Arguments of `google.vertex_rag_store`.
85#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct VertexRagStoreArgs {
88    /// RAG corpus resource name.
89    pub rag_corpus: String,
90    /// Number of results.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub top_k: Option<u32>,
93}
94
95/// Provider-executed tool factories.
96#[derive(Debug, Clone, Copy, Default)]
97pub struct GoogleTools;
98
99impl GoogleTools {
100    /// Creates the factory namespace.
101    #[must_use]
102    pub fn new() -> Self {
103        Self
104    }
105
106    /// `google.google_search`: Google Search grounding.
107    #[must_use]
108    pub fn google_search(&self, config: GoogleSearchArgs) -> Tool {
109        executed(ids::GOOGLE_SEARCH, args(&config))
110    }
111
112    /// `google.enterprise_web_search` (Vertex AI).
113    #[must_use]
114    pub fn enterprise_web_search(&self) -> Tool {
115        executed(ids::ENTERPRISE_WEB_SEARCH, JsonObject::new())
116    }
117
118    /// `google.url_context`: fetch URLs mentioned in the prompt.
119    #[must_use]
120    pub fn url_context(&self) -> Tool {
121        executed(ids::URL_CONTEXT, JsonObject::new())
122    }
123
124    /// `google.code_execution`: server-side Python execution.
125    #[must_use]
126    pub fn code_execution(&self) -> Tool {
127        Tool::provider_executed(ids::CODE_EXECUTION, JsonObject::new())
128            .input_schema(Schema::from_provider_json_schema(json!({
129                "type": "object",
130                "properties": {
131                    "language": {"type": "string", "description": "The programming language of the code."},
132                    "code": {"type": "string", "description": "The code to be executed."}
133                },
134                "required": ["language", "code"]
135            })))
136            .output_schema(Schema::from_provider_json_schema(json!({
137                "type": "object",
138                "properties": {
139                    "outcome": {"type": "string", "description": "The outcome of the execution (e.g., \"OUTCOME_OK\")."},
140                    "output": {"type": "string", "description": "The output from the code execution."}
141                },
142                "required": ["outcome", "output"]
143            })))
144            .build()
145    }
146
147    /// `google.file_search`: retrieval from file search stores.
148    #[must_use]
149    pub fn file_search(&self, config: FileSearchArgs) -> Tool {
150        executed(ids::FILE_SEARCH, args(&config))
151    }
152
153    /// `google.vertex_rag_store` (Vertex AI).
154    #[must_use]
155    pub fn vertex_rag_store(&self, config: VertexRagStoreArgs) -> Tool {
156        executed(ids::VERTEX_RAG_STORE, args(&config))
157    }
158
159    /// `google.google_maps`: Google Maps grounding.
160    #[must_use]
161    pub fn google_maps(&self) -> Tool {
162        executed(ids::GOOGLE_MAPS, JsonObject::new())
163    }
164}