Skip to content

Commit b2d65c7

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
chore: Move AgentData translation to server
PiperOrigin-RevId: 948000584
1 parent 3335750 commit b2d65c7

5 files changed

Lines changed: 491 additions & 112 deletions

File tree

agentplatform/_genai/_evals_common.py

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -840,15 +840,28 @@ def _merge_text_parts_in_agent_data(
840840
def _agent_tools_to_config_tools(
841841
agent_tools: Optional[list[Any]],
842842
) -> Optional[list[genai_types.Tool]]:
843-
"""Maps Gemini Agents API tools to ``genai_types.Tool`` for an AgentConfig.
844-
845-
The Gemini Agents API returns built-in tool variants (``google_search``,
846-
``code_execution``, ``url_context``) whose schema differs from
847-
``genai_types.Tool``. Each recognised built-in variant is mapped to the
848-
matching ``genai_types.Tool`` field. Tools with a non-empty body after
849-
stripping the ``type`` key (e.g. ``function_declarations``) are passed
850-
through ``model_validate``. Variants without a ``genai_types.Tool``
851-
equivalent (e.g. ``filesystem``, ``mcp_server``) are skipped.
843+
"""Maps Gemini Agents API tools to ``genai_types.Tool`` for display.
844+
845+
NOTE: This is a low-fidelity, DISPLAY-ONLY mapping. The Gemini Agents API
846+
(``GET agents/{id}``) returns each tool as a bare type discriminator
847+
(e.g. ``{"type": "code_execution"}``) with no parameter schema or
848+
description. The authoritative, full-fidelity expansion of built-in tool
849+
types into function declarations (e.g. ``code_execution`` -> ``run_command``
850+
with parameters, ``filesystem`` -> file tools, sandbox tools) is performed
851+
server-side in ``interaction_converter.py`` whenever the server is handed a
852+
``gemini_agent_config``. The result of this function must therefore only be
853+
used to render the local System Topology in ``show()`` and must never be
854+
sent to the server-side autorater or scenario generator (which resolve the
855+
agent's tools themselves).
856+
857+
Mapping rules:
858+
* Recognised built-in variants (``google_search``, ``code_execution``,
859+
``url_context``) are mapped to the matching ``genai_types.Tool`` field.
860+
* Tools that already carry ``function_declarations`` are passed through
861+
via ``model_validate`` so real function tools keep their full schema.
862+
* Any other typed built-in (e.g. ``filesystem``, ``mcp_server``) is
863+
represented as a single named ``FunctionDeclaration`` so the tool is
864+
still shown by name in the display instead of being dropped.
852865
853866
Args:
854867
agent_tools: The ``tools`` list from a fetched Gemini agent dict.
@@ -864,6 +877,7 @@ def _agent_tools_to_config_tools(
864877
if not isinstance(tool, dict):
865878
continue
866879
tool_type = tool.get("type")
880+
remainder = {k: v for k, v in tool.items() if k != "type"}
867881
if tool_type == "google_search":
868882
tools.append(genai_types.Tool(google_search=genai_types.GoogleSearch()))
869883
elif tool_type == "code_execution":
@@ -872,12 +886,31 @@ def _agent_tools_to_config_tools(
872886
)
873887
elif tool_type == "url_context":
874888
tools.append(genai_types.Tool(url_context=genai_types.UrlContext()))
875-
else:
876-
# For non-built-in tools (e.g. function_declarations), strip the
877-
# type key and validate through genai_types.Tool.
878-
remainder = {k: v for k, v in tool.items() if k != "type"}
879-
if remainder:
880-
tools.append(genai_types.Tool.model_validate(remainder))
889+
elif "function_declarations" in remainder:
890+
# Real function tool with explicit declarations: pass through so
891+
# the display shows the full name/parameter schema.
892+
tools.append(genai_types.Tool.model_validate(remainder))
893+
elif tool_type:
894+
# Other built-in tool type with no genai_types.Tool variant (e.g.
895+
# "filesystem", "mcp_server"). Represent it as a named declaration
896+
# so it is shown by name rather than dropped. Full parameter
897+
# schemas are only available from the server-side expansion.
898+
description = None
899+
if tool_type == "mcp_server":
900+
label = remainder.get("name") or remainder.get("url")
901+
description = f"MCP server: {label}" if label else "MCP server."
902+
tools.append(
903+
genai_types.Tool(
904+
function_declarations=[
905+
genai_types.FunctionDeclaration(
906+
name=tool_type, description=description
907+
)
908+
]
909+
)
910+
)
911+
elif remainder:
912+
# Untyped tool body: best-effort pass-through.
913+
tools.append(genai_types.Tool.model_validate(remainder))
881914
return tools or None
882915

883916

@@ -988,33 +1021,6 @@ def _agent_data_response_text(agent_data: types.evals.AgentData) -> Optional[str
9881021
return "".join(text_parts) or None
9891022

9901023

991-
def _agent_resource_to_agent_info(
992-
agent: str, api_client: BaseApiClient
993-
) -> "types.evals.AgentInfo":
994-
"""Builds an `AgentInfo` from a Gemini Agents API agent resource name.
995-
996-
Fetches the agent through the SDK's `api_client` (so replay recording is
997-
preserved) via `_fetch_agent_config_dict` and derives a single-agent
998-
`AgentInfo`: the agent's short name is the agents-map key and
999-
`root_agent_id`.
1000-
1001-
Args:
1002-
agent: The Gemini Agents API agent resource name
1003-
(`projects/{p}/locations/{l}/agents/{name}`).
1004-
api_client: The API client used to fetch the agent.
1005-
1006-
Returns:
1007-
An `AgentInfo` describing the fetched agent.
1008-
"""
1009-
agent_config = _fetch_agent_config_dict(api_client, agent)
1010-
short_name = agent_config.agent_id
1011-
return types.evals.AgentInfo( # pytype: disable=missing-parameter
1012-
name=short_name,
1013-
agents={short_name: agent_config},
1014-
root_agent_id=short_name,
1015-
)
1016-
1017-
10181024
_INTERACTION_TERMINAL_STATES = frozenset(
10191025
["completed", "failed", "cancelled", "incomplete", "budget_exceeded"]
10201026
)
@@ -1109,6 +1115,12 @@ def _run_gemini_agent_inference(
11091115
interactions_client = _get_interactions_client(api_client)
11101116

11111117
agent_short_id = gemini_agent.split("/")[-1]
1118+
1119+
# Best-effort: fetch the agent config (instruction, tools, description)
1120+
# once, so every row's agent_data carries the agents map and the display
1121+
# can render the System Topology section.
1122+
agent_config = _fetch_agent_config_dict(api_client, gemini_agent)
1123+
11121124
prompts: list[str] = []
11131125
responses: list[Optional[str]] = []
11141126
interaction_ids: list[Optional[str]] = []
@@ -1128,6 +1140,7 @@ def _run_gemini_agent_inference(
11281140
)
11291141
interaction = _await_interaction(interactions_client, interaction)
11301142
agent_data_obj = _interaction_dict_to_agent_data(interaction)
1143+
agent_data_obj.agents = {agent_config.agent_id: agent_config}
11311144
_merge_text_parts_in_agent_data(agent_data_obj)
11321145
responses.append(_agent_data_response_text(agent_data_obj))
11331146
interaction_ids.append(interaction.get("id"))

agentplatform/_genai/_evals_visualization.py

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -372,19 +372,42 @@ 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+
// A built-in tool variant is a Tool object with no
378+
// function_declarations, e.g. {{"google_search": {{...}}}} or
379+
// {{"code_execution": {{}}}}. Collect every non-null key (other than
380+
// function_declarations) as the built-in tool's name so it renders
381+
// by name instead of falling through to a raw JSON dump. Null keys
382+
// (from non-exclude_none serialization) are ignored.
383+
function collectFromTool(tool) {{
384+
if (!tool || typeof tool !== 'object') return;
385+
if (Array.isArray(tool.function_declarations) && tool.function_declarations.length > 0) {{
386+
functions = functions.concat(tool.function_declarations);
387+
return;
388+
}}
389+
if (tool.name && tool.parameters) {{
390+
functions.push(tool);
391+
return;
392+
}}
393+
Object.keys(tool).forEach(k => {{
394+
if (k === 'function_declarations') return;
395+
if (tool[k] === null || tool[k] === undefined) return;
396+
builtins.push(k);
382397
}});
383-
}} else if (typeof toolDeclarations === 'object' && toolDeclarations.function_declarations) {{
384-
functions = toolDeclarations.function_declarations;
385398
}}
386399
387-
if (functions.length === 0) {{
400+
if (Array.isArray(toolDeclarations)) {{
401+
toolDeclarations.forEach(collectFromTool);
402+
}} else if (typeof toolDeclarations === 'object') {{
403+
if (toolDeclarations.function_declarations) {{
404+
functions = functions.concat(toolDeclarations.function_declarations);
405+
}} else {{
406+
collectFromTool(toolDeclarations);
407+
}}
408+
}}
409+
410+
if (functions.length === 0 && builtins.length === 0) {{
388411
return `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(toolDeclarations, null, 2))}}</pre>`;
389412
}}
390413
@@ -402,6 +425,9 @@ def get_evaluation_html(eval_result_json: str) -> str:
402425
}});
403426
html += '</div>';
404427
}});
428+
builtins.forEach(name => {{
429+
html += `<div class="tool-declaration"><strong>${{DOMPurify.sanitize(name)}}</strong> <em>(built-in tool)</em></div>`;
430+
}});
405431
html += '</div>';
406432
return html;
407433
}}
@@ -914,19 +940,42 @@ def get_comparison_html(eval_result_json: str) -> str:
914940
function formatToolDeclarations(toolDeclarations) {{
915941
if (!toolDeclarations) return '';
916942
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-
}}
943+
const builtins = [];
944+
945+
// A built-in tool variant is a Tool object with no
946+
// function_declarations, e.g. {{"google_search": {{...}}}} or
947+
// {{"code_execution": {{}}}}. Collect every non-null key (other than
948+
// function_declarations) as the built-in tool's name so it renders
949+
// by name instead of falling through to a raw JSON dump. Null keys
950+
// (from non-exclude_none serialization) are ignored.
951+
function collectFromTool(tool) {{
952+
if (!tool || typeof tool !== 'object') return;
953+
if (Array.isArray(tool.function_declarations) && tool.function_declarations.length > 0) {{
954+
functions = functions.concat(tool.function_declarations);
955+
return;
956+
}}
957+
if (tool.name && tool.parameters) {{
958+
functions.push(tool);
959+
return;
960+
}}
961+
Object.keys(tool).forEach(k => {{
962+
if (k === 'function_declarations') return;
963+
if (tool[k] === null || tool[k] === undefined) return;
964+
builtins.push(k);
924965
}});
925-
}} else if (typeof toolDeclarations === 'object' && toolDeclarations.function_declarations) {{
926-
functions = toolDeclarations.function_declarations;
927966
}}
928967
929-
if (functions.length === 0) {{
968+
if (Array.isArray(toolDeclarations)) {{
969+
toolDeclarations.forEach(collectFromTool);
970+
}} else if (typeof toolDeclarations === 'object') {{
971+
if (toolDeclarations.function_declarations) {{
972+
functions = functions.concat(toolDeclarations.function_declarations);
973+
}} else {{
974+
collectFromTool(toolDeclarations);
975+
}}
976+
}}
977+
978+
if (functions.length === 0 && builtins.length === 0) {{
930979
return `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(toolDeclarations, null, 2))}}</pre>`;
931980
}}
932981
@@ -944,6 +993,9 @@ def get_comparison_html(eval_result_json: str) -> str:
944993
}});
945994
html += '</div>';
946995
}});
996+
builtins.forEach(name => {{
997+
html += `<div class="tool-declaration"><strong>${{DOMPurify.sanitize(name)}}</strong> <em>(built-in tool)</em></div>`;
998+
}});
947999
html += '</div>';
9481000
return html;
9491001
}}

0 commit comments

Comments
 (0)