Skip to content

Commit cc1b241

Browse files
jsondaicopybara-github
authored andcommitted
chore: GenAI Client(evals) - add class methods functions for parsing raw Agent Session history into the new AgentData structure. Add agent_resource_name attribute to AgentConfig and loading methods.
PiperOrigin-RevId: 869945268
1 parent 7c661d7 commit cc1b241

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

vertexai/_genai/_evals_metric_handlers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,9 @@ def _eval_case_to_agent_data(
879879
eval_case: types.EvalCase,
880880
) -> Optional[types.evals.AgentData]:
881881
"""Converts an EvalCase object to an AgentData object."""
882+
if getattr(eval_case, "agent_data", None):
883+
return eval_case.agent_data
884+
882885
if not eval_case.agent_info and not eval_case.intermediate_events:
883886
return None
884887
tools = None

vertexai/_genai/types/evals.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ class AgentConfig(_common.BaseModel):
122122
This ID is used to refer to this agent, e.g., in AgentEvent.author, or in
123123
the `sub_agents` field. It must be unique within the `agents` map.""",
124124
)
125+
agent_resource_name: Optional[str] = Field(
126+
default=None,
127+
description="""The Agent Engine resource name, formatted as `projects/{project}/locations/{location}/reasoningEngines/{reasoning_engine_id}`.""",
128+
)
125129
agent_type: Optional[str] = Field(
126130
default=None,
127131
description="""The type or class of the agent (e.g., "LlmAgent", "RouterAgent",
@@ -160,6 +164,51 @@ class AgentConfig(_common.BaseModel):
160164
description="""A field containing instructions from the developer for the agent.""",
161165
)
162166

167+
@staticmethod
168+
def _get_tool_declarations_from_agent(agent: Any) -> genai_types.ToolListUnion:
169+
"""Gets tool declarations from an agent.
170+
171+
Args:
172+
agent: The agent to get the tool declarations from. Data type is google.adk.agents.LLMAgent type, use Any to avoid dependency on ADK.
173+
174+
Returns:
175+
The tool declarations of the agent.
176+
"""
177+
tool_declarations: genai_types.ToolListUnion = []
178+
for tool in agent.tools:
179+
tool_declarations.append(
180+
{
181+
"function_declarations": [
182+
genai_types.FunctionDeclaration.from_callable_with_api_option(
183+
callable=tool
184+
)
185+
]
186+
}
187+
)
188+
return tool_declarations
189+
190+
@classmethod
191+
def from_agent(
192+
cls, agent: Any, agent_resource_name: Optional[str] = None
193+
) -> "AgentConfig":
194+
"""Creates an AgentConfig from an ADK agent object.
195+
196+
Args:
197+
agent: The agent to get the agent info from, data type is google.adk.agents.LLMAgent type, use Any to avoid dependency on ADK.
198+
agent_resource_name: Optional. The agent engine resource name.
199+
200+
Returns:
201+
An AgentConfig object populated with the agent's metadata.
202+
"""
203+
return cls( # pytype: disable=missing-parameter
204+
agent_id=getattr(agent, "name", "default_agent") or "default_agent",
205+
agent_resource_name=agent_resource_name,
206+
agent_type=agent.__class__.__name__,
207+
description=getattr(agent, "description", None),
208+
instruction=getattr(agent, "instruction", None),
209+
tools=AgentConfig._get_tool_declarations_from_agent(agent),
210+
)
211+
163212

164213
class AgentConfigDict(TypedDict, total=False):
165214
"""Represents configuration for an Agent."""
@@ -169,6 +218,9 @@ class AgentConfigDict(TypedDict, total=False):
169218
This ID is used to refer to this agent, e.g., in AgentEvent.author, or in
170219
the `sub_agents` field. It must be unique within the `agents` map."""
171220

221+
agent_resource_name: Optional[str]
222+
"""The Agent Engine resource name, formatted as `projects/{project}/locations/{location}/reasoningEngines/{reasoning_engine_id}`."""
223+
172224
agent_type: Optional[str]
173225
"""The type or class of the agent (e.g., "LlmAgent", "RouterAgent",
174226
"ToolUseAgent"). Useful for the autorater to understand the expected
@@ -334,6 +386,98 @@ class AgentData(_common.BaseModel):
334386
)
335387
events: Optional[Events] = Field(default=None, description="""A list of events.""")
336388

389+
@classmethod
390+
def from_session(cls, agent: Any, session_history: list[Any]) -> "AgentData":
391+
"""Creates an AgentData object from a session history.
392+
393+
Segments the flat list of session events into ConversationTurns. A new turn
394+
is initiated by a User message.
395+
396+
Args:
397+
agent: The agent instance used in the session.
398+
session_history: A list of raw events/messages from the session.
399+
400+
Returns:
401+
An AgentData object containing the segmented history and agent config.
402+
"""
403+
agent_config = AgentConfig.from_agent(agent)
404+
agent_id = agent_config.agent_id or "default_agent"
405+
agents_map = {agent_id: agent_config}
406+
407+
turns = []
408+
current_turn_events = []
409+
410+
for event in session_history:
411+
is_user = False
412+
if isinstance(event, dict):
413+
if event.get("role") == "user":
414+
is_user = True
415+
elif (
416+
isinstance(event.get("content"), dict)
417+
and event["content"].get("role") == "user"
418+
):
419+
is_user = True
420+
elif hasattr(event, "role") and event.role == "user":
421+
is_user = True
422+
423+
if is_user and current_turn_events:
424+
turns.append(
425+
ConversationTurn( # pytype: disable=missing-parameter
426+
turn_index=len(turns),
427+
turn_id=f"turn_{len(turns)}",
428+
events=current_turn_events,
429+
)
430+
)
431+
current_turn_events = []
432+
433+
author = "user" if is_user else agent_id
434+
435+
content = None
436+
if isinstance(event, dict):
437+
if "content" in event:
438+
raw_content = event["content"]
439+
if isinstance(raw_content, genai_types.Content):
440+
content = raw_content
441+
elif isinstance(raw_content, dict):
442+
try:
443+
content = genai_types.Content.model_validate(raw_content)
444+
except Exception as e:
445+
raise ValueError(
446+
f"Failed to validate Content from dictionary in session history: {raw_content}"
447+
) from e
448+
elif isinstance(raw_content, str):
449+
content = genai_types.Content(
450+
parts=[genai_types.Part(text=raw_content)]
451+
)
452+
elif "parts" in event:
453+
try:
454+
content = genai_types.Content.model_validate(event)
455+
except Exception as e:
456+
raise ValueError(
457+
f"Failed to validate Content from event with 'parts': {event}"
458+
) from e
459+
elif hasattr(event, "content") and isinstance(
460+
event.content, genai_types.Content
461+
):
462+
content = event.content
463+
464+
agent_event = AgentEvent( # pytype: disable=missing-parameter
465+
author=author,
466+
content=content,
467+
)
468+
current_turn_events.append(agent_event)
469+
470+
if current_turn_events:
471+
turns.append(
472+
ConversationTurn( # pytype: disable=missing-parameter
473+
turn_index=len(turns),
474+
turn_id=f"turn_{len(turns)}",
475+
events=current_turn_events,
476+
)
477+
)
478+
479+
return cls(agents=agents_map, turns=turns) # pytype: disable=missing-parameter
480+
337481

338482
class AgentDataDict(TypedDict, total=False):
339483
"""Represents data specific to multi-turn agent evaluations."""

0 commit comments

Comments
 (0)