|
| 1 | +# Copyright 2026 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | +# |
| 15 | +"""Built-in tool catalog for Gemini Agent evaluation display. |
| 16 | +
|
| 17 | +The Gemini Agents API (``GET agents/{id}``) returns each tool as a bare type |
| 18 | +discriminator (e.g. ``{"type": "code_execution"}``) with no parameter schema |
| 19 | +or description. The authoritative, full-fidelity expansion lives server-side |
| 20 | +in ``cloud/ai/platform/evaluation/utils/interaction_converter.py``. |
| 21 | +
|
| 22 | +This module is a **display-only duplicate** of that server catalog, kept here |
| 23 | +so ``show()`` can render tools with full names, descriptions, and parameter |
| 24 | +schemas without a server round-trip. |
| 25 | +
|
| 26 | +**If the server catalog changes, this SDK-side copy must be updated to match.** |
| 27 | +""" |
| 28 | + |
| 29 | +from typing import Any, Optional |
| 30 | + |
| 31 | +from google.genai import types as genai_types |
| 32 | + |
| 33 | + |
| 34 | +def _str_schema(description: str) -> genai_types.Schema: |
| 35 | + return genai_types.Schema(type="STRING", description=description) |
| 36 | + |
| 37 | + |
| 38 | +# Maps a built-in Gemini Agent tool type to the concrete FunctionDeclarations |
| 39 | +# the agent actually exposes for that type. |
| 40 | +# |
| 41 | +# Source of truth: interaction_converter.py, _BUILTIN_TOOL_FUNCTION_DECLARATIONS |
| 42 | +BUILTIN_TOOL_DECLARATIONS: dict[str, list[genai_types.FunctionDeclaration]] = { |
| 43 | + "code_execution": [ |
| 44 | + genai_types.FunctionDeclaration( |
| 45 | + name="run_command", |
| 46 | + description=( |
| 47 | + "Runs a shell command on the sandbox VM. If the command does" |
| 48 | + " not complete within WaitMsBeforeAsync, it is sent to the" |
| 49 | + " background and a CommandId is returned for use with" |
| 50 | + " command_status." |
| 51 | + ), |
| 52 | + parameters=genai_types.Schema( |
| 53 | + type="OBJECT", |
| 54 | + properties={ |
| 55 | + "CommandLine": _str_schema("The shell command to run."), |
| 56 | + "Cwd": _str_schema( |
| 57 | + "The current working directory for the command." |
| 58 | + ), |
| 59 | + "WaitMsBeforeAsync": genai_types.Schema( |
| 60 | + type="INTEGER", |
| 61 | + description=( |
| 62 | + "Milliseconds to wait for the command to complete" |
| 63 | + " before sending it to the background. Default:" |
| 64 | + " 10000." |
| 65 | + ), |
| 66 | + ), |
| 67 | + "SafeToAutoRun": genai_types.Schema( |
| 68 | + type="BOOLEAN", |
| 69 | + description=( |
| 70 | + "Whether the command is safe to auto-run without" |
| 71 | + " user approval." |
| 72 | + ), |
| 73 | + ), |
| 74 | + }, |
| 75 | + required=["CommandLine", "Cwd"], |
| 76 | + ), |
| 77 | + ), |
| 78 | + ], |
| 79 | + "filesystem": [ |
| 80 | + genai_types.FunctionDeclaration( |
| 81 | + name="view_file", |
| 82 | + description="Reads the content of a workspace file.", |
| 83 | + ), |
| 84 | + genai_types.FunctionDeclaration( |
| 85 | + name="create_file", |
| 86 | + description="Writes content to a new or existing file.", |
| 87 | + ), |
| 88 | + genai_types.FunctionDeclaration( |
| 89 | + name="edit_file", |
| 90 | + description="Replaces a specific block of text in a file.", |
| 91 | + ), |
| 92 | + genai_types.FunctionDeclaration( |
| 93 | + name="list_dir", |
| 94 | + description="Lists the files in a directory.", |
| 95 | + ), |
| 96 | + genai_types.FunctionDeclaration( |
| 97 | + name="delete_file", |
| 98 | + description="Removes a file from the workspace.", |
| 99 | + ), |
| 100 | + genai_types.FunctionDeclaration( |
| 101 | + name="move_file", |
| 102 | + description="Renames or moves a file.", |
| 103 | + ), |
| 104 | + ], |
| 105 | +} |
| 106 | + |
| 107 | + |
| 108 | +# Sandbox-environment orchestration tool declarations. |
| 109 | +# |
| 110 | +# Source of truth: interaction_converter.py, _SANDBOX_FUNCTION_DECLARATIONS |
| 111 | +SANDBOX_DECLARATIONS: list[genai_types.FunctionDeclaration] = [ |
| 112 | + genai_types.FunctionDeclaration( |
| 113 | + name="provision_sandbox", |
| 114 | + description="Provisions a sandbox environment.", |
| 115 | + parameters=genai_types.Schema( |
| 116 | + type="OBJECT", |
| 117 | + properties={ |
| 118 | + "display_name": _str_schema( |
| 119 | + "The display name of the sandbox environment." |
| 120 | + ), |
| 121 | + "poll_creation_lro": genai_types.Schema( |
| 122 | + type="BOOLEAN", |
| 123 | + description=( |
| 124 | + "Whether to poll the creation long-running operation." |
| 125 | + ), |
| 126 | + ), |
| 127 | + }, |
| 128 | + ), |
| 129 | + ), |
| 130 | + genai_types.FunctionDeclaration( |
| 131 | + name="load_sandbox", |
| 132 | + description="Loads a previously provisioned sandbox environment.", |
| 133 | + parameters=genai_types.Schema( |
| 134 | + type="OBJECT", |
| 135 | + properties={ |
| 136 | + "reasoning_engine_resource_name": _str_schema( |
| 137 | + "The resource name of the reasoning engine. Format:" |
| 138 | + " projects/{project}/locations/{location}/reasoningEngines/{id}" |
| 139 | + ), |
| 140 | + "display_name": _str_schema( |
| 141 | + "The display name of the sandbox environment. Format: any" |
| 142 | + " string." |
| 143 | + ), |
| 144 | + }, |
| 145 | + ), |
| 146 | + ), |
| 147 | +] |
| 148 | + |
| 149 | + |
| 150 | +def agent_tools_to_config_tools( |
| 151 | + agent_tools: Optional[list[Any]], |
| 152 | + has_environment: bool = False, |
| 153 | +) -> Optional[list[genai_types.Tool]]: |
| 154 | + """Maps Gemini Agents API tools to ``genai_types.Tool`` for display. |
| 155 | +
|
| 156 | + Expands built-in agent tool types into their concrete function declarations |
| 157 | + using ``BUILTIN_TOOL_DECLARATIONS`` (a display-only duplicate of the |
| 158 | + server-side catalog in ``interaction_converter.py``). |
| 159 | +
|
| 160 | + Mapping rules: |
| 161 | + * ``code_execution`` is expanded to ``run_command`` with full parameter |
| 162 | + schema. |
| 163 | + * ``filesystem`` is expanded to ``view_file``, ``create_file``, |
| 164 | + ``edit_file``, ``list_dir``, ``delete_file``, ``move_file``. |
| 165 | + * ``google_search`` and ``url_context`` are mapped to their typed |
| 166 | + ``genai_types.Tool`` variant. |
| 167 | + * ``mcp_server`` is represented as a named declaration with a |
| 168 | + human-readable label. |
| 169 | + * Tools carrying explicit ``function_declarations`` are passed through. |
| 170 | + * When ``has_environment`` is True, sandbox orchestration tools |
| 171 | + (``provision_sandbox``, ``load_sandbox``) are appended. |
| 172 | +
|
| 173 | + Args: |
| 174 | + agent_tools: The ``tools`` list from a fetched Gemini agent dict. |
| 175 | + has_environment: Whether the agent has a sandbox environment configured. |
| 176 | +
|
| 177 | + Returns: |
| 178 | + A list of ``genai_types.Tool``, or ``None`` if there are no mappable |
| 179 | + tools. |
| 180 | + """ |
| 181 | + if not agent_tools and not has_environment: |
| 182 | + return None |
| 183 | + tools: list[genai_types.Tool] = [] |
| 184 | + for tool in agent_tools or []: |
| 185 | + if not isinstance(tool, dict): |
| 186 | + continue |
| 187 | + tool_type = tool.get("type") |
| 188 | + remainder = {k: v for k, v in tool.items() if k != "type"} |
| 189 | + |
| 190 | + # Check the built-in catalog first (code_execution, filesystem). |
| 191 | + catalog_decls = BUILTIN_TOOL_DECLARATIONS.get(tool_type or "") |
| 192 | + if catalog_decls: |
| 193 | + tools.append(genai_types.Tool(function_declarations=list(catalog_decls))) |
| 194 | + elif tool_type == "google_search": |
| 195 | + tools.append(genai_types.Tool(google_search=genai_types.GoogleSearch())) |
| 196 | + elif tool_type == "url_context": |
| 197 | + tools.append(genai_types.Tool(url_context=genai_types.UrlContext())) |
| 198 | + elif "function_declarations" in remainder: |
| 199 | + # Real function tool with explicit declarations. |
| 200 | + tools.append(genai_types.Tool.model_validate(remainder)) |
| 201 | + elif tool_type == "mcp_server": |
| 202 | + label = remainder.get("name") or remainder.get("url") |
| 203 | + description = f"MCP server: {label}" if label else "MCP server." |
| 204 | + tools.append( |
| 205 | + genai_types.Tool( |
| 206 | + function_declarations=[ |
| 207 | + genai_types.FunctionDeclaration( |
| 208 | + name="mcp_server", description=description |
| 209 | + ) |
| 210 | + ] |
| 211 | + ) |
| 212 | + ) |
| 213 | + elif tool_type: |
| 214 | + # Unknown built-in: show by name so it isn't silently dropped. |
| 215 | + tools.append( |
| 216 | + genai_types.Tool( |
| 217 | + function_declarations=[ |
| 218 | + genai_types.FunctionDeclaration(name=tool_type) |
| 219 | + ] |
| 220 | + ) |
| 221 | + ) |
| 222 | + elif remainder: |
| 223 | + tools.append(genai_types.Tool.model_validate(remainder)) |
| 224 | + |
| 225 | + if has_environment: |
| 226 | + tools.append(genai_types.Tool(function_declarations=list(SANDBOX_DECLARATIONS))) |
| 227 | + |
| 228 | + return tools or None |
0 commit comments