Skip to content

Commit 4d9656b

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Delegate agent resolution to server via geminiAgentConfig
PiperOrigin-RevId: 949000060
1 parent 23145f3 commit 4d9656b

4 files changed

Lines changed: 52 additions & 75 deletions

File tree

agentplatform/_genai/_evals_common.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,33 +1016,6 @@ def _agent_data_response_text(agent_data: types.evals.AgentData) -> Optional[str
10161016
return "".join(text_parts) or None
10171017

10181018

1019-
def _agent_resource_to_agent_info(
1020-
agent: str, api_client: BaseApiClient
1021-
) -> "types.evals.AgentInfo":
1022-
"""Builds an `AgentInfo` from a Gemini Agents API agent resource name.
1023-
1024-
Fetches the agent through the SDK's `api_client` (so replay recording is
1025-
preserved) via `_fetch_agent_config_dict` and derives a single-agent
1026-
`AgentInfo`: the agent's short name is the agents-map key and
1027-
`root_agent_id`.
1028-
1029-
Args:
1030-
agent: The Gemini Agents API agent resource name
1031-
(`projects/{p}/locations/{l}/agents/{name}`).
1032-
api_client: The API client used to fetch the agent.
1033-
1034-
Returns:
1035-
An `AgentInfo` describing the fetched agent.
1036-
"""
1037-
agent_config = _fetch_agent_config_dict(api_client, agent)
1038-
short_name = agent_config.agent_id
1039-
return types.evals.AgentInfo( # pytype: disable=missing-parameter
1040-
name=short_name,
1041-
agents={short_name: agent_config},
1042-
root_agent_id=short_name,
1043-
)
1044-
1045-
10461019
_INTERACTION_TERMINAL_STATES = frozenset(
10471020
["completed", "failed", "cancelled", "incomplete", "budget_exceeded"]
10481021
)

agentplatform/_genai/evals.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,11 @@ def _GenerateUserScenariosParameters_to_vertex(
725725
getv(from_object, ["allow_cross_region_model"]),
726726
)
727727

728+
if getv(from_object, ["gemini_agent_config"]) is not None:
729+
setv(
730+
to_object, ["geminiAgentConfig"], getv(from_object, ["gemini_agent_config"])
731+
)
732+
728733
return to_object
729734

730735

@@ -1518,6 +1523,7 @@ def _generate_user_scenarios(
15181523
] = None,
15191524
config: Optional[types.GenerateUserScenariosConfigOrDict] = None,
15201525
allow_cross_region_model: Optional[bool] = None,
1526+
gemini_agent_config: Optional[types.GeminiAgentConfigOrDict] = None,
15211527
) -> types.GenerateUserScenariosResponse:
15221528
"""
15231529
Generates user scenarios for agent evaluation.
@@ -1530,6 +1536,7 @@ def _generate_user_scenarios(
15301536
user_scenario_generation_config=user_scenario_generation_config,
15311537
config=config,
15321538
allow_cross_region_model=allow_cross_region_model,
1539+
gemini_agent_config=gemini_agent_config,
15331540
)
15341541

15351542
request_url_dict: Optional[dict[str, str]]
@@ -2938,16 +2945,16 @@ def generate_conversation_scenarios(
29382945
and the agent under test.
29392946
29402947
Exactly one of `agent_info` or `agent` must be provided. When `agent` is
2941-
a Gemini Agents API agent resource name, the agent is fetched and an
2942-
`AgentInfo` is derived from it.
2948+
a Gemini Agents API agent resource name, the agent config is resolved
2949+
server-side via ``gemini_agent_config``.
29432950
29442951
Args:
29452952
agent_info: The agent info to generate user scenarios for. Mutually
29462953
exclusive with `agent`.
29472954
agent: A Gemini Agents API agent resource name
29482955
(`projects/{p}/locations/{l}/agents/{name}`). When provided, the
2949-
agent is fetched and its configuration is used to build the agent
2950-
info. Mutually exclusive with `agent_info`.
2956+
server resolves the agent config directly. Mutually exclusive
2957+
with `agent_info`.
29512958
config: Configuration for generating user scenarios.
29522959
allow_cross_region_model: Opt-in flag to authorize cross-region
29532960
routing for model inference.
@@ -2967,21 +2974,23 @@ def generate_conversation_scenarios(
29672974
"`agent` must be a Gemini Agents API agent resource name of the"
29682975
" form projects/{project}/locations/{location}/agents/{agent}."
29692976
)
2970-
parsed_agent_info = _evals_common._agent_resource_to_agent_info(
2971-
agent, self._api_client
2977+
response = self._generate_user_scenarios(
2978+
gemini_agent_config=types.GeminiAgentConfig(gemini_agent=agent),
2979+
user_scenario_generation_config=config,
2980+
allow_cross_region_model=allow_cross_region_model,
29722981
)
29732982
else:
29742983
parsed_agent_info = (
29752984
evals_types.AgentInfo.model_validate(agent_info)
29762985
if isinstance(agent_info, dict)
29772986
else agent_info
29782987
)
2979-
response = self._generate_user_scenarios(
2980-
agents=parsed_agent_info.agents,
2981-
root_agent_id=parsed_agent_info.root_agent_id,
2982-
user_scenario_generation_config=config,
2983-
allow_cross_region_model=allow_cross_region_model,
2984-
)
2988+
response = self._generate_user_scenarios(
2989+
agents=parsed_agent_info.agents,
2990+
root_agent_id=parsed_agent_info.root_agent_id,
2991+
user_scenario_generation_config=config,
2992+
allow_cross_region_model=allow_cross_region_model,
2993+
)
29852994
return _evals_utils._postprocess_user_scenarios_response(response)
29862995

29872996
def generate_loss_clusters(
@@ -3683,6 +3692,7 @@ async def _generate_user_scenarios(
36833692
] = None,
36843693
config: Optional[types.GenerateUserScenariosConfigOrDict] = None,
36853694
allow_cross_region_model: Optional[bool] = None,
3695+
gemini_agent_config: Optional[types.GeminiAgentConfigOrDict] = None,
36863696
) -> types.GenerateUserScenariosResponse:
36873697
"""
36883698
Generates user scenarios for agent evaluation.
@@ -3695,6 +3705,7 @@ async def _generate_user_scenarios(
36953705
user_scenario_generation_config=user_scenario_generation_config,
36963706
config=config,
36973707
allow_cross_region_model=allow_cross_region_model,
3708+
gemini_agent_config=gemini_agent_config,
36983709
)
36993710

37003711
request_url_dict: Optional[dict[str, str]]
@@ -4729,16 +4740,16 @@ async def generate_conversation_scenarios(
47294740
and the agent under test.
47304741
47314742
Exactly one of `agent_info` or `agent` must be provided. When `agent` is
4732-
a Gemini Agents API agent resource name, the agent is fetched and an
4733-
`AgentInfo` is derived from it.
4743+
a Gemini Agents API agent resource name, the agent config is resolved
4744+
server-side via ``gemini_agent_config``.
47344745
47354746
Args:
47364747
agent_info: The agent info to generate user scenarios for. Mutually
47374748
exclusive with `agent`.
47384749
agent: A Gemini Agents API agent resource name
47394750
(`projects/{p}/locations/{l}/agents/{name}`). When provided, the
4740-
agent is fetched and its configuration is used to build the agent
4741-
info. Mutually exclusive with `agent_info`.
4751+
server resolves the agent config directly. Mutually exclusive
4752+
with `agent_info`.
47424753
config: Configuration for generating user scenarios.
47434754
allow_cross_region_model: Opt-in flag to authorize cross-region
47444755
routing for model inference.
@@ -4758,21 +4769,23 @@ async def generate_conversation_scenarios(
47584769
"`agent` must be a Gemini Agents API agent resource name of the"
47594770
" form projects/{project}/locations/{location}/agents/{agent}."
47604771
)
4761-
parsed_agent_info = _evals_common._agent_resource_to_agent_info(
4762-
agent, self._api_client
4772+
response = await self._generate_user_scenarios(
4773+
gemini_agent_config=types.GeminiAgentConfig(gemini_agent=agent),
4774+
user_scenario_generation_config=config,
4775+
allow_cross_region_model=allow_cross_region_model,
47634776
)
47644777
else:
47654778
parsed_agent_info = (
47664779
evals_types.AgentInfo.model_validate(agent_info)
47674780
if isinstance(agent_info, dict)
47684781
else agent_info
47694782
)
4770-
response = await self._generate_user_scenarios(
4771-
agents=parsed_agent_info.agents,
4772-
root_agent_id=parsed_agent_info.root_agent_id,
4773-
user_scenario_generation_config=config,
4774-
allow_cross_region_model=allow_cross_region_model,
4775-
)
4783+
response = await self._generate_user_scenarios(
4784+
agents=parsed_agent_info.agents,
4785+
root_agent_id=parsed_agent_info.root_agent_id,
4786+
user_scenario_generation_config=config,
4787+
allow_cross_region_model=allow_cross_region_model,
4788+
)
47764789
return _evals_utils._postprocess_user_scenarios_response(response)
47774790

47784791
async def generate_loss_clusters(

agentplatform/_genai/types/common.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5501,6 +5501,11 @@ class _GenerateUserScenariosParameters(_common.BaseModel):
55015501
default=None,
55025502
description="""Opt-in flag to authorize cross-region routing for LLM models.""",
55035503
)
5504+
gemini_agent_config: Optional[GeminiAgentConfig] = Field(
5505+
default=None,
5506+
description="""If set, the server derives the agents map and root_agent_id
5507+
from the referenced Gemini Agent server-side.""",
5508+
)
55045509

55055510

55065511
class _GenerateUserScenariosParametersDict(TypedDict, total=False):
@@ -5524,6 +5529,10 @@ class _GenerateUserScenariosParametersDict(TypedDict, total=False):
55245529
allow_cross_region_model: Optional[bool]
55255530
"""Opt-in flag to authorize cross-region routing for LLM models."""
55265531

5532+
gemini_agent_config: Optional[GeminiAgentConfigDict]
5533+
"""If set, the server derives the agents map and root_agent_id
5534+
from the referenced Gemini Agent server-side."""
5535+
55275536

55285537
_GenerateUserScenariosParametersOrDict = Union[
55295538
_GenerateUserScenariosParameters, _GenerateUserScenariosParametersDict

tests/unit/agentplatform/genai/test_evals.py

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9409,19 +9409,9 @@ async def test_async_generate_conversation_scenarios(self):
94099409
request_body = call_args[0][2] # Third positional arg is the request dict
94109410
assert request_body.get("allowCrossRegionModel") is True
94119411

9412-
@mock.patch.object(_evals_common, "_fetch_agent_config_dict")
9413-
def test_generate_conversation_scenarios_from_gemini_agent(
9414-
self, mock_fetch_agent_config
9415-
):
9416-
mock_fetch_agent_config.return_value = (
9417-
agentplatform_genai_types.evals.AgentConfig(
9418-
agent_id="test-agent",
9419-
instruction="You are a helpful travel assistant.",
9420-
description="An agent that books flights.",
9421-
tools=[genai_types.Tool(google_search=genai_types.GoogleSearch())],
9422-
)
9423-
)
9424-
9412+
def test_generate_conversation_scenarios_from_gemini_agent(self):
9413+
"""When `agent` is a Gemini agent resource, gemini_agent_config is
9414+
forwarded to the server (no client-side synthesis)."""
94259415
evals_module = evals.Evals(api_client_=self.mock_api_client)
94269416

94279417
with mock.patch.object(
@@ -9433,18 +9423,10 @@ def test_generate_conversation_scenarios_from_gemini_agent(
94339423
config={"count": 2},
94349424
)
94359425

9436-
mock_fetch_agent_config.assert_called_once_with(
9437-
self.mock_api_client, _TEST_GEMINI_AGENT
9438-
)
94399426
call_kwargs = mock_generate_user_scenarios.call_args.kwargs
9440-
assert call_kwargs["root_agent_id"] == "test-agent"
9441-
agents = call_kwargs["agents"]
9442-
assert "test-agent" in agents
9443-
derived_config = agents["test-agent"]
9444-
assert derived_config.instruction == "You are a helpful travel assistant."
9445-
assert derived_config.description == "An agent that books flights."
9446-
assert derived_config.tools is not None
9447-
assert derived_config.tools[0].google_search is not None
9427+
assert call_kwargs["gemini_agent_config"].gemini_agent == _TEST_GEMINI_AGENT
9428+
assert call_kwargs.get("agents") is None
9429+
assert call_kwargs.get("root_agent_id") is None
94489430

94499431
def test_generate_conversation_scenarios_agent_and_agent_info_raises(self):
94509432
evals_module = evals.Evals(api_client_=self.mock_api_client)

0 commit comments

Comments
 (0)