Skip to content

Commit 762079f

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Add detailed agent tool definitions for display
PiperOrigin-RevId: 951118021
1 parent f141415 commit 762079f

4 files changed

Lines changed: 425 additions & 74 deletions

File tree

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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 and descriptions without a
24+
server round-trip. Parameter schemas are intentionally omitted to avoid
25+
publishing internal tool contract details.
26+
27+
**If the server catalog changes, this SDK-side copy must be updated to match.**
28+
"""
29+
30+
from typing import Any, Optional
31+
32+
from google.genai import types as genai_types
33+
34+
35+
# Maps a built-in Gemini Agent tool type to the concrete FunctionDeclarations
36+
# the agent actually exposes for that type.
37+
#
38+
# Source of truth: interaction_converter.py, _BUILTIN_TOOL_FUNCTION_DECLARATIONS
39+
BUILTIN_TOOL_DECLARATIONS: dict[str, list[genai_types.FunctionDeclaration]] = {
40+
"code_execution": [
41+
genai_types.FunctionDeclaration(
42+
name="run_command",
43+
description="Runs a shell command on the sandbox VM.",
44+
),
45+
],
46+
"filesystem": [
47+
genai_types.FunctionDeclaration(
48+
name="view_file",
49+
description="Reads the content of a workspace file.",
50+
),
51+
genai_types.FunctionDeclaration(
52+
name="create_file",
53+
description="Writes content to a new or existing file.",
54+
),
55+
genai_types.FunctionDeclaration(
56+
name="edit_file",
57+
description="Replaces a specific block of text in a file.",
58+
),
59+
genai_types.FunctionDeclaration(
60+
name="list_dir",
61+
description="Lists the files in a directory.",
62+
),
63+
genai_types.FunctionDeclaration(
64+
name="delete_file",
65+
description="Removes a file from the workspace.",
66+
),
67+
genai_types.FunctionDeclaration(
68+
name="move_file",
69+
description="Renames or moves a file.",
70+
),
71+
],
72+
}
73+
74+
75+
# Sandbox-environment orchestration tool declarations.
76+
#
77+
# Source of truth: interaction_converter.py, _SANDBOX_FUNCTION_DECLARATIONS
78+
SANDBOX_DECLARATIONS: list[genai_types.FunctionDeclaration] = [
79+
genai_types.FunctionDeclaration(
80+
name="provision_sandbox",
81+
description="Provisions a sandbox environment.",
82+
),
83+
genai_types.FunctionDeclaration(
84+
name="load_sandbox",
85+
description="Loads a previously provisioned sandbox environment.",
86+
),
87+
]
88+
89+
90+
def agent_tools_to_config_tools(
91+
agent_tools: Optional[list[Any]],
92+
has_environment: bool = False,
93+
) -> Optional[list[genai_types.Tool]]:
94+
"""Maps Gemini Agents API tools to ``genai_types.Tool`` for display.
95+
96+
Expands built-in agent tool types into their concrete function declarations
97+
using ``BUILTIN_TOOL_DECLARATIONS`` (a display-only duplicate of the
98+
server-side catalog in ``interaction_converter.py``).
99+
100+
Mapping rules:
101+
* ``code_execution`` is expanded to ``run_command``.
102+
* ``filesystem`` is expanded to ``view_file``, ``create_file``,
103+
``edit_file``, ``list_dir``, ``delete_file``, ``move_file``.
104+
* ``google_search`` and ``url_context`` are mapped to their typed
105+
``genai_types.Tool`` variant.
106+
* ``mcp_server`` is represented as a named declaration with a
107+
human-readable label.
108+
* Tools carrying explicit ``function_declarations`` are passed through.
109+
* When ``has_environment`` is True, sandbox orchestration tools
110+
(``provision_sandbox``, ``load_sandbox``) are appended.
111+
112+
Args:
113+
agent_tools: The ``tools`` list from a fetched Gemini agent dict.
114+
has_environment: Whether the agent has a sandbox environment configured.
115+
116+
Returns:
117+
A list of ``genai_types.Tool``, or ``None`` if there are no mappable
118+
tools.
119+
"""
120+
if not agent_tools and not has_environment:
121+
return None
122+
tools: list[genai_types.Tool] = []
123+
for tool in agent_tools or []:
124+
if not isinstance(tool, dict):
125+
continue
126+
tool_type = tool.get("type")
127+
remainder = {k: v for k, v in tool.items() if k != "type"}
128+
129+
# Check the built-in catalog first (code_execution, filesystem).
130+
catalog_decls = BUILTIN_TOOL_DECLARATIONS.get(tool_type or "")
131+
if catalog_decls:
132+
tools.append(genai_types.Tool(function_declarations=list(catalog_decls)))
133+
elif tool_type == "google_search":
134+
tools.append(genai_types.Tool(google_search=genai_types.GoogleSearch()))
135+
elif tool_type == "url_context":
136+
tools.append(genai_types.Tool(url_context=genai_types.UrlContext()))
137+
elif "function_declarations" in remainder:
138+
# Real function tool with explicit declarations.
139+
tools.append(genai_types.Tool.model_validate(remainder))
140+
elif tool_type == "mcp_server":
141+
label = remainder.get("name") or remainder.get("url")
142+
description = f"MCP server: {label}" if label else "MCP server."
143+
tools.append(
144+
genai_types.Tool(
145+
function_declarations=[
146+
genai_types.FunctionDeclaration(
147+
name="mcp_server", description=description
148+
)
149+
]
150+
)
151+
)
152+
elif tool_type:
153+
# Unknown built-in: show by name so it isn't silently dropped.
154+
tools.append(
155+
genai_types.Tool(
156+
function_declarations=[
157+
genai_types.FunctionDeclaration(name=tool_type)
158+
]
159+
)
160+
)
161+
elif remainder:
162+
tools.append(genai_types.Tool.model_validate(remainder))
163+
164+
if has_environment:
165+
tools.append(genai_types.Tool(function_declarations=list(SANDBOX_DECLARATIONS)))
166+
167+
return tools or None

agentplatform/_genai/_evals_common.py

Lines changed: 19 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from tqdm import tqdm
4444
from pydantic import ValidationError
4545

46+
from . import _evals_builtin_tools
4647
from . import _evals_constant
4748
from . import _evals_data_converters
4849
from . import _evals_metric_handlers
@@ -501,6 +502,7 @@ def _get_default_prompt_template(
501502
and eval_item.evaluation_request
502503
and eval_item.evaluation_request.prompt
503504
and eval_item.evaluation_request.prompt.prompt_template_data
505+
and eval_item.evaluation_request.prompt.prompt_template_data.values
504506
):
505507
template_values = (
506508
eval_item.evaluation_request.prompt.prompt_template_data.values
@@ -855,48 +857,7 @@ def _merge_text_parts_in_agent_data(
855857
content.parts = merged_parts
856858

857859

858-
def _agent_tools_to_config_tools(
859-
agent_tools: Optional[list[Any]],
860-
) -> Optional[list[genai_types.Tool]]:
861-
"""Maps Gemini Agents API tools to ``genai_types.Tool`` for an AgentConfig.
862-
863-
The Gemini Agents API returns built-in tool variants (``google_search``,
864-
``code_execution``, ``url_context``) whose schema differs from
865-
``genai_types.Tool``. Each recognised built-in variant is mapped to the
866-
matching ``genai_types.Tool`` field. Tools with a non-empty body after
867-
stripping the ``type`` key (e.g. ``function_declarations``) are passed
868-
through ``model_validate``. Variants without a ``genai_types.Tool``
869-
equivalent (e.g. ``filesystem``, ``mcp_server``) are skipped.
870-
871-
Args:
872-
agent_tools: The ``tools`` list from a fetched Gemini agent dict.
873-
874-
Returns:
875-
A list of ``genai_types.Tool``, or ``None`` if there are no mappable
876-
tools.
877-
"""
878-
if not agent_tools:
879-
return None
880-
tools: list[genai_types.Tool] = []
881-
for tool in agent_tools:
882-
if not isinstance(tool, dict):
883-
continue
884-
tool_type = tool.get("type")
885-
if tool_type == "google_search":
886-
tools.append(genai_types.Tool(google_search=genai_types.GoogleSearch()))
887-
elif tool_type == "code_execution":
888-
tools.append(
889-
genai_types.Tool(code_execution=genai_types.ToolCodeExecution())
890-
)
891-
elif tool_type == "url_context":
892-
tools.append(genai_types.Tool(url_context=genai_types.UrlContext()))
893-
else:
894-
# For non-built-in tools (e.g. function_declarations), strip the
895-
# type key and validate through genai_types.Tool.
896-
remainder = {k: v for k, v in tool.items() if k != "type"}
897-
if remainder:
898-
tools.append(genai_types.Tool.model_validate(remainder))
899-
return tools or None
860+
_agent_tools_to_config_tools = _evals_builtin_tools.agent_tools_to_config_tools
900861

901862

902863
def _fetch_agent_config_dict(
@@ -907,9 +868,9 @@ def _fetch_agent_config_dict(
907868
908869
Fetches the Agent resource via ``GET agents/{id}`` and extracts the
909870
system instruction, description, base agent type, and tools. Built-in
910-
tools (``google_search``, ``code_execution``, ``url_context``) are mapped
911-
to their ``genai_types.Tool`` equivalents via
912-
``_agent_tools_to_config_tools``.
871+
tool types (``code_execution``, ``filesystem``, etc.) are expanded into
872+
concrete ``FunctionDeclaration`` names and descriptions via the
873+
display-only catalog in ``_evals_builtin_tools``.
913874
914875
Args:
915876
api_client: The API client used to fetch the agent.
@@ -934,7 +895,13 @@ def _fetch_agent_config_dict(
934895
instruction = agent_dict.get("system_instruction") or None
935896
description = agent_dict.get("description") or None
936897
agent_type = agent_dict.get("base_agent") or None
937-
tools = _agent_tools_to_config_tools(agent_dict.get("tools"))
898+
has_environment = bool(
899+
agent_dict.get("environment_config")
900+
or agent_dict.get("base_environment")
901+
)
902+
tools = _agent_tools_to_config_tools(
903+
agent_dict.get("tools"), has_environment=has_environment
904+
)
938905
except Exception as e: # pylint: disable=broad-exception-caught
939906
logger.warning(
940907
"Failed to fetch agent config for '%s' (continuing without it): %s",
@@ -1136,6 +1103,11 @@ def _run_gemini_agent_inference(
11361103

11371104
interactions_client = _get_interactions_client(api_client)
11381105

1106+
# Best-effort: fetch the agent config (instruction, tools, description)
1107+
# once, so every row's agent_data carries the agents map and the display
1108+
# can render the System Topology section.
1109+
agent_config = _fetch_agent_config_dict(api_client, gemini_agent)
1110+
11391111
agent_short_id = gemini_agent.split("/")[-1]
11401112
prompts: list[str] = []
11411113
responses: list[Optional[str]] = []
@@ -1156,6 +1128,7 @@ def _run_gemini_agent_inference(
11561128
)
11571129
interaction = _await_interaction(interactions_client, interaction)
11581130
agent_data_obj = _interaction_dict_to_agent_data(interaction)
1131+
agent_data_obj.agents = {agent_config.agent_id: agent_config}
11591132
_merge_text_parts_in_agent_data(agent_data_obj)
11601133
responses.append(_agent_data_response_text(agent_data_obj))
11611134
interaction_ids.append(interaction.get("id"))

agentplatform/_genai/_evals_visualization.py

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -372,19 +372,36 @@ def get_evaluation_html(eval_result_json: str) -> str:
372372
function formatToolDeclarations(toolDeclarations) {{
373373
if (!toolDeclarations) return '';
374374
let functions = [];
375-
if (Array.isArray(toolDeclarations)) {{
376-
toolDeclarations.forEach(tool => {{
377-
if (tool.function_declarations) {{
378-
functions = functions.concat(tool.function_declarations);
379-
}} else if (tool.name && tool.parameters) {{
380-
functions.push(tool);
381-
}}
375+
const builtins = [];
376+
377+
function collectFromTool(tool) {{
378+
if (!tool || typeof tool !== 'object') return;
379+
if (Array.isArray(tool.function_declarations) && tool.function_declarations.length > 0) {{
380+
functions = functions.concat(tool.function_declarations);
381+
return;
382+
}}
383+
if (tool.name && tool.parameters) {{
384+
functions.push(tool);
385+
return;
386+
}}
387+
Object.keys(tool).forEach(k => {{
388+
if (k === 'function_declarations') return;
389+
if (tool[k] === null || tool[k] === undefined) return;
390+
builtins.push(k);
382391
}});
383-
}} else if (typeof toolDeclarations === 'object' && toolDeclarations.function_declarations) {{
384-
functions = toolDeclarations.function_declarations;
385392
}}
386393
387-
if (functions.length === 0) {{
394+
if (Array.isArray(toolDeclarations)) {{
395+
toolDeclarations.forEach(collectFromTool);
396+
}} else if (typeof toolDeclarations === 'object') {{
397+
if (toolDeclarations.function_declarations) {{
398+
functions = functions.concat(toolDeclarations.function_declarations);
399+
}} else {{
400+
collectFromTool(toolDeclarations);
401+
}}
402+
}}
403+
404+
if (functions.length === 0 && builtins.length === 0) {{
388405
return `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(toolDeclarations, null, 2))}}</pre>`;
389406
}}
390407
@@ -402,6 +419,9 @@ def get_evaluation_html(eval_result_json: str) -> str:
402419
}});
403420
html += '</div>';
404421
}});
422+
builtins.forEach(name => {{
423+
html += `<div class="tool-declaration"><strong>${{DOMPurify.sanitize(name)}}</strong> <em>(built-in tool)</em></div>`;
424+
}});
405425
html += '</div>';
406426
return html;
407427
}}
@@ -914,19 +934,36 @@ def get_comparison_html(eval_result_json: str) -> str:
914934
function formatToolDeclarations(toolDeclarations) {{
915935
if (!toolDeclarations) return '';
916936
let functions = [];
917-
if (Array.isArray(toolDeclarations)) {{
918-
toolDeclarations.forEach(tool => {{
919-
if (tool.function_declarations) {{
920-
functions = functions.concat(tool.function_declarations);
921-
}} else if (tool.name && tool.parameters) {{
922-
functions.push(tool);
923-
}}
937+
const builtins = [];
938+
939+
function collectFromTool(tool) {{
940+
if (!tool || typeof tool !== 'object') return;
941+
if (Array.isArray(tool.function_declarations) && tool.function_declarations.length > 0) {{
942+
functions = functions.concat(tool.function_declarations);
943+
return;
944+
}}
945+
if (tool.name && tool.parameters) {{
946+
functions.push(tool);
947+
return;
948+
}}
949+
Object.keys(tool).forEach(k => {{
950+
if (k === 'function_declarations') return;
951+
if (tool[k] === null || tool[k] === undefined) return;
952+
builtins.push(k);
924953
}});
925-
}} else if (typeof toolDeclarations === 'object' && toolDeclarations.function_declarations) {{
926-
functions = toolDeclarations.function_declarations;
927954
}}
928955
929-
if (functions.length === 0) {{
956+
if (Array.isArray(toolDeclarations)) {{
957+
toolDeclarations.forEach(collectFromTool);
958+
}} else if (typeof toolDeclarations === 'object') {{
959+
if (toolDeclarations.function_declarations) {{
960+
functions = functions.concat(toolDeclarations.function_declarations);
961+
}} else {{
962+
collectFromTool(toolDeclarations);
963+
}}
964+
}}
965+
966+
if (functions.length === 0 && builtins.length === 0) {{
930967
return `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(toolDeclarations, null, 2))}}</pre>`;
931968
}}
932969
@@ -944,6 +981,9 @@ def get_comparison_html(eval_result_json: str) -> str:
944981
}});
945982
html += '</div>';
946983
}});
984+
builtins.forEach(name => {{
985+
html += `<div class="tool-declaration"><strong>${{DOMPurify.sanitize(name)}}</strong> <em>(built-in tool)</em></div>`;
986+
}});
947987
html += '</div>';
948988
return html;
949989
}}

0 commit comments

Comments
 (0)