Skip to content

Commit 5a88c87

Browse files
jsondaicopybara-github
authored andcommitted
chore: GenAI Client(evals) - fix eval generate failures on ADK 2.x with built-in tools and invalid default app_name
PiperOrigin-RevId: 926253895
1 parent a76a4b6 commit 5a88c87

5 files changed

Lines changed: 52 additions & 25 deletions

File tree

agentplatform/_genai/_evals_common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2334,10 +2334,10 @@ async def _execute_local_agent_run_with_retry_async(
23342334
if "session_inputs" in row.index and row.get("session_inputs") is not None:
23352335
session_inputs = _get_session_inputs(row)
23362336
user_id = session_inputs.user_id or str(uuid.uuid4())
2337-
app_name = session_inputs.app_name or "local agent run"
2337+
app_name = session_inputs.app_name or "local_agent_run"
23382338
else:
23392339
user_id = str(uuid.uuid4())
2340-
app_name = "local agent run"
2340+
app_name = "local_agent_run"
23412341
session_id = str(uuid.uuid4())
23422342

23432343
session_service = InMemorySessionService()

agentplatform/_genai/types/evals.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,17 @@ def _get_tool_declarations_from_agent(agent: Any) -> genai_types.ToolListUnion:
8484
"""
8585
tool_declarations: genai_types.ToolListUnion = []
8686
for tool in agent.tools:
87-
# ADK tools (e.g. AgentTool) provide their own declaration via
88-
# _get_declaration(). Use it when available to avoid calling
89-
# typing.get_type_hints() on tool instances whose classes use
90-
# `from __future__ import annotations`, which causes NameError.
87+
# ADK tools (e.g. AgentTool, VertexAiSearchTool) own their declaration
88+
# via _get_declaration(). A None result means the tool has no function
89+
# declaration (e.g. built-in retrieval tools). In both cases, skip the
90+
# plain-callable path, which calls typing.get_type_hints() on the
91+
# instance and raises NameError for classes using
92+
# `from __future__ import annotations`.
9193
if hasattr(tool, "_get_declaration") and callable(tool._get_declaration):
9294
declaration = tool._get_declaration()
9395
if declaration is not None:
9496
tool_declarations.append({"function_declarations": [declaration]})
95-
continue
97+
continue
9698

9799
tool_declarations.append(
98100
{

tests/unit/agentplatform/genai/test_evals.py

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3680,7 +3680,7 @@ def run_async_side_effect(*args, **kwargs):
36803680
assert mock_session_service.call_count == 2
36813681
mock_runner.assert_called_with(
36823682
agent=mock_agent_instance,
3683-
app_name="local agent run",
3683+
app_name="local_agent_run",
36843684
session_service=mock_session_service.return_value,
36853685
)
36863686
assert mock_runner.call_count == 2
@@ -3795,6 +3795,10 @@ def run_async_side_effect(*args, **kwargs):
37953795
assert inference_result.candidate_name == "mock_agent"
37963796
assert inference_result.gcs_source is None
37973797

3798+
def test_local_agent_run_default_app_name_is_valid(self):
3799+
"""Default app_name must satisfy ADK 2.x's app name validation regex."""
3800+
assert re.fullmatch(r"[a-zA-Z][a-zA-Z0-9_-]*", "local_agent_run")
3801+
37983802
def test_run_inference_with_litellm_string_prompt_format(
37993803
self,
38003804
mock_api_client_fixture,
@@ -5850,8 +5854,8 @@ def my_plain_tool(query: str) -> str:
58505854
]
58515855
mock_from_callable.assert_called_once_with(callable=my_plain_tool)
58525856

5853-
def test_load_from_agent_with_none_declaration_falls_back(self):
5854-
"""Tests that tools returning None from _get_declaration fall back to from_callable."""
5857+
def test_load_from_agent_with_none_declaration_is_skipped(self):
5858+
"""Tools whose _get_declaration() returns None are skipped, not introspected."""
58555859
mock_tool = mock.Mock()
58565860
mock_tool._get_declaration = mock.Mock(return_value=None)
58575861
mock_tool.__name__ = "mock_tool"
@@ -5867,19 +5871,38 @@ def test_load_from_agent_with_none_declaration_falls_back(self):
58675871
with mock.patch.object(
58685872
genai_types.FunctionDeclaration, "from_callable_with_api_option"
58695873
) as mock_from_callable:
5870-
mock_callable_declaration = mock.Mock(spec=genai_types.FunctionDeclaration)
5871-
mock_from_callable.return_value = mock_callable_declaration
5872-
58735874
agent_info = agentplatform_genai_types.evals.AgentInfo.load_from_agent(
58745875
agent=mock_agent,
58755876
)
58765877

5877-
assert len(agent_info.agents["mock_agent"].tools) == 1
5878-
assert agent_info.agents["mock_agent"].tools[0].function_declarations == [
5879-
mock_callable_declaration
5880-
]
5878+
assert agent_info.agents["mock_agent"].tools == []
58815879
mock_tool._get_declaration.assert_called_once()
5882-
mock_from_callable.assert_called_once_with(callable=mock_tool)
5880+
mock_from_callable.assert_not_called()
5881+
5882+
def test_load_from_agent_none_declaration_skips_get_type_hints(self):
5883+
"""A None-returning native tool must not trigger get_type_hints (NameError repro)."""
5884+
5885+
class _BuiltinTool:
5886+
def _get_declaration(self):
5887+
return None
5888+
5889+
def run(self, query: "Optional[str]" = None): # noqa: F821
5890+
return query
5891+
5892+
builtin_tool = _BuiltinTool()
5893+
5894+
mock_agent = mock.Mock()
5895+
mock_agent.name = "mock_agent"
5896+
mock_agent.instruction = "mock instruction"
5897+
mock_agent.description = "mock description"
5898+
mock_agent.tools = [builtin_tool]
5899+
mock_agent.sub_agents = []
5900+
5901+
agent_info = agentplatform_genai_types.evals.AgentInfo.load_from_agent(
5902+
agent=mock_agent,
5903+
)
5904+
5905+
assert agent_info.agents["mock_agent"].tools == []
58835906

58845907

58855908
class TestValidateDatasetAgentData:

vertexai/_genai/_evals_common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2334,10 +2334,10 @@ async def _execute_local_agent_run_with_retry_async(
23342334
if "session_inputs" in row.index and row.get("session_inputs") is not None:
23352335
session_inputs = _get_session_inputs(row)
23362336
user_id = session_inputs.user_id or str(uuid.uuid4())
2337-
app_name = session_inputs.app_name or "local agent run"
2337+
app_name = session_inputs.app_name or "local_agent_run"
23382338
else:
23392339
user_id = str(uuid.uuid4())
2340-
app_name = "local agent run"
2340+
app_name = "local_agent_run"
23412341
session_id = str(uuid.uuid4())
23422342

23432343
session_service = InMemorySessionService()

vertexai/_genai/types/evals.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,17 @@ def _get_tool_declarations_from_agent(agent: Any) -> genai_types.ToolListUnion:
8484
"""
8585
tool_declarations: genai_types.ToolListUnion = []
8686
for tool in agent.tools:
87-
# ADK tools (e.g. AgentTool) provide their own declaration via
88-
# _get_declaration(). Use it when available to avoid calling
89-
# typing.get_type_hints() on tool instances whose classes use
90-
# `from __future__ import annotations`, which causes NameError.
87+
# ADK tools (e.g. AgentTool, VertexAiSearchTool) own their declaration
88+
# via _get_declaration(). A None result means the tool has no function
89+
# declaration (e.g. built-in retrieval tools). In both cases, skip the
90+
# plain-callable path, which calls typing.get_type_hints() on the
91+
# instance and raises NameError for classes using
92+
# `from __future__ import annotations`.
9193
if hasattr(tool, "_get_declaration") and callable(tool._get_declaration):
9294
declaration = tool._get_declaration()
9395
if declaration is not None:
9496
tool_declarations.append({"function_declarations": [declaration]})
95-
continue
97+
continue
9698

9799
tool_declarations.append(
98100
{

0 commit comments

Comments
 (0)