Skip to content

Commit ac6e0b4

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Add async methods for Sessions and Session Events
PiperOrigin-RevId: 818847205
1 parent 9a452cc commit ac6e0b4

5 files changed

Lines changed: 226 additions & 11 deletions

File tree

tests/unit/vertexai/genai/replays/test_list_agent_engine_session_events.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,31 @@
1515
# pylint: disable=protected-access,bad-continuation,missing-function-docstring
1616

1717
import datetime
18+
import pytest
1819

1920
from tests.unit.vertexai.genai.replays import pytest_helper
2021
from vertexai._genai import types
2122

2223

2324
def test_list_session_events(client):
2425
agent_engine = client.agent_engines.create()
25-
operation = client.agent_engines.create_session(
26+
operation = client.agent_engines.sessions.create(
2627
name=agent_engine.api_resource.name,
2728
user_id="test-user-123",
2829
)
2930
session = operation.response
3031
assert not list(
31-
client.agent_engines.list_session_events(
32+
client.agent_engines.sessions.events.list(
3233
name=session.name,
3334
)
3435
)
35-
client.agent_engines.append_session_event(
36+
client.agent_engines.sessions.events.append(
3637
name=session.name,
3738
author="test-user-123",
3839
invocation_id="test-invocation-id",
3940
timestamp=datetime.datetime.fromtimestamp(1234567890, tz=datetime.timezone.utc),
4041
)
41-
session_event_list = client.agent_engines.list_session_events(
42+
session_event_list = client.agent_engines.sessions.events.list(
4243
name=session.name,
4344
)
4445
assert len(session_event_list) == 1
@@ -48,5 +49,33 @@ def test_list_session_events(client):
4849
pytestmark = pytest_helper.setup(
4950
file=__file__,
5051
globals_for_file=globals(),
51-
test_method="agent_engines.list_session_events",
52+
test_method="agent_engines.sessions.events.list",
5253
)
54+
55+
56+
pytest_plugins = ("pytest_asyncio",)
57+
58+
59+
@pytest.mark.asyncio
60+
async def test_async_list_session_events(client):
61+
agent_engine = client.agent_engines.create()
62+
operation = await client.aio.agent_engines.sessions.create(
63+
name=agent_engine.api_resource.name,
64+
user_id="test-user-123",
65+
)
66+
session = operation.response
67+
pager = await client.aio.agent_engines.sessions.events.list(name=session.name)
68+
assert not [item async for item in pager]
69+
70+
await client.aio.agent_engines.sessions.events.append(
71+
name=session.name,
72+
author="test-user-123",
73+
invocation_id="test-invocation-id",
74+
timestamp=datetime.datetime.fromtimestamp(1234567890, tz=datetime.timezone.utc),
75+
)
76+
pager = await client.aio.agent_engines.sessions.events.list(name=session.name)
77+
session_event_list = [item async for item in pager]
78+
assert len(session_event_list) == 1
79+
assert isinstance(session_event_list[0], types.SessionEvent)
80+
81+
client.agent_engines.delete(name=agent_engine.api_resource.name, force=True)

tests/unit/vertexai/genai/replays/test_list_agent_engine_sessions.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,59 @@
1414
#
1515
# pylint: disable=protected-access,bad-continuation,missing-function-docstring
1616

17+
import pytest
18+
19+
1720
from tests.unit.vertexai.genai.replays import pytest_helper
1821
from vertexai._genai import types
1922

2023

2124
def test_list_sessions(client):
2225
agent_engine = client.agent_engines.create()
2326
assert not list(
24-
client.agent_engines.list_sessions(
27+
client.agent_engines.sessions.list(
2528
name=agent_engine.api_resource.name,
2629
)
2730
)
28-
client.agent_engines.create_session(
31+
client.agent_engines.sessions.create(
2932
name=agent_engine.api_resource.name,
3033
user_id="test-user-123",
3134
)
32-
session_list = client.agent_engines.list_sessions(
35+
session_list = client.agent_engines.sessions.list(
3336
name=agent_engine.api_resource.name,
3437
)
3538
assert len(session_list) == 1
3639
assert isinstance(session_list[0], types.Session)
3740

41+
client.agent_engines.delete(name=agent_engine.api_resource.name, force=True)
42+
3843

3944
pytestmark = pytest_helper.setup(
4045
file=__file__,
4146
globals_for_file=globals(),
42-
test_method="agent_engines.list_sessions",
47+
test_method="agent_engines.sessions.list",
4348
)
49+
50+
pytest_plugins = ("pytest_asyncio",)
51+
52+
53+
@pytest.mark.asyncio
54+
async def test_async_list_sessions(client):
55+
agent_engine = client.agent_engines.create()
56+
pager = await client.aio.agent_engines.sessions.list(
57+
name=agent_engine.api_resource.name
58+
)
59+
assert not [item async for item in pager]
60+
61+
await client.aio.agent_engines.sessions.create(
62+
name=agent_engine.api_resource.name,
63+
user_id="test-user-123",
64+
)
65+
pager = await client.aio.agent_engines.sessions.list(
66+
name=agent_engine.api_resource.name,
67+
)
68+
session_list = [item async for item in pager]
69+
assert len(session_list) == 1
70+
assert isinstance(session_list[0], types.Session)
71+
72+
client.agent_engines.delete(name=agent_engine.api_resource.name, force=True)

vertexai/_genai/_agent_engines_utils.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from typing import (
3131
Any,
3232
AsyncIterator,
33+
Awaitable,
3334
Callable,
3435
Coroutine,
3536
Dict,
@@ -391,6 +392,41 @@ def __call__(self, *, operation_name: str, **kwargs) -> AgentEngineOperationUnio
391392
pass
392393

393394

395+
class GetAsyncOperationFunction(Protocol):
396+
async def __call__(
397+
self, *, operation_name: str, **kwargs
398+
) -> Awaitable[AgentEngineOperationUnion]:
399+
pass
400+
401+
402+
async def _await_async_operation(
403+
*,
404+
operation_name: str,
405+
get_operation_fn: GetAsyncOperationFunction,
406+
poll_interval_seconds: float = 10,
407+
) -> Any:
408+
"""Waits for the operation for creating an agent engine to complete.
409+
410+
Args:
411+
operation_name (str):
412+
Required. The name of the operation for creating the Agent Engine.
413+
poll_interval_seconds (float):
414+
The number of seconds to wait between each poll.
415+
get_operation_fn (Callable[[str], Awaitable[Any]]):
416+
Optional. The async function to use for getting the operation. If not
417+
provided, `self._get_agent_operation` will be used.
418+
419+
Returns:
420+
The operation that has completed (i.e. `operation.done==True`).
421+
"""
422+
operation = await get_operation_fn(operation_name=operation_name)
423+
while not operation.done:
424+
await asyncio.sleep(poll_interval_seconds)
425+
operation = await get_operation_fn(operation_name=operation.name)
426+
427+
return operation
428+
429+
394430
def _await_operation(
395431
*,
396432
operation_name: str,

vertexai/_genai/session_events.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from google.genai import _common
2727
from google.genai._common import get_value_by_path as getv
2828
from google.genai._common import set_value_by_path as setv
29-
from google.genai.pagers import Pager
29+
from google.genai.pagers import AsyncPager, Pager
3030

3131
from . import types
3232

@@ -442,3 +442,32 @@ async def _list(
442442

443443
self._api_client._verify_response(return_value)
444444
return return_value
445+
446+
async def list(
447+
self,
448+
*,
449+
name: str,
450+
config: Optional[types.ListAgentEngineSessionEventsConfigOrDict] = None,
451+
) -> AsyncPager[types.SessionEvent]:
452+
"""Lists Agent Engine session events.
453+
454+
Args:
455+
name (str): Required. The name of the agent engine to list session
456+
events for.
457+
config (ListAgentEngineSessionEventsConfig): Optional. The configuration
458+
for the session events to list. Currently, the `filter` field in
459+
`config` only supports filtering by `timestamp`. The timestamp
460+
value must be enclosed in double quotes and include the time zone
461+
information. For example:
462+
`config={'filter': 'timestamp>="2025-08-07T19:44:38.4Z"'}`.
463+
464+
Returns:
465+
AsyncPager[SessionEvent]: An async pager of session events.
466+
"""
467+
468+
return AsyncPager(
469+
"session_events",
470+
functools.partial(self._list, name=name),
471+
await self._list(name=name, config=config),
472+
config,
473+
)

vertexai/_genai/sessions.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from google.genai import _common
2727
from google.genai._common import get_value_by_path as getv
2828
from google.genai._common import set_value_by_path as setv
29-
from google.genai.pagers import Pager
29+
from google.genai.pagers import AsyncPager, Pager
3030

3131
from . import _agent_engines_utils
3232
from . import types
@@ -1078,3 +1078,95 @@ async def _update(
10781078

10791079
self._api_client._verify_response(return_value)
10801080
return return_value
1081+
1082+
_events = None
1083+
1084+
@property
1085+
@_common.experimental_warning(
1086+
"The Vertex SDK GenAI agent_engines.sessions.events module is "
1087+
"experimental, and may change in future versions."
1088+
)
1089+
def events(self):
1090+
if self._events is None:
1091+
try:
1092+
# We need to lazy load the sessions.events module to handle the
1093+
# possibility of ImportError when dependencies are not installed.
1094+
self._events = importlib.import_module(".session_events", __package__)
1095+
except ImportError as e:
1096+
raise ImportError(
1097+
"The 'agent_engines.sessions.events' module requires"
1098+
"additional packages. Please install them using pip install "
1099+
"google-cloud-aiplatform[agent_engines]"
1100+
) from e
1101+
return self._events.AsyncSessionEvents(self._api_client)
1102+
1103+
async def create(
1104+
self,
1105+
*,
1106+
name: str,
1107+
user_id: str,
1108+
config: Optional[types.CreateAgentEngineSessionConfigOrDict] = None,
1109+
) -> types.AgentEngineSessionOperation:
1110+
"""Creates a new session in the Agent Engine.
1111+
1112+
Args:
1113+
name (str):
1114+
Required. The name of the agent engine to create the session for.
1115+
user_id (str):
1116+
Required. The user ID of the session.
1117+
config (CreateAgentEngineSessionConfig):
1118+
Optional. The configuration for the session to create.
1119+
1120+
Returns:
1121+
AgentEngineSessionOperation: The operation for creating the session.
1122+
"""
1123+
if config is None:
1124+
config = types.CreateAgentEngineSessionConfig()
1125+
elif isinstance(config, dict):
1126+
config = types.CreateAgentEngineSessionConfig.model_validate(config)
1127+
operation = await self._create(
1128+
name=name,
1129+
user_id=user_id,
1130+
config=config,
1131+
)
1132+
if config.wait_for_completion and not operation.done:
1133+
operation = await _agent_engines_utils._await_async_operation(
1134+
operation_name=operation.name,
1135+
get_operation_fn=self._get_session_operation,
1136+
poll_interval_seconds=0.5,
1137+
)
1138+
if operation.response:
1139+
operation.response = await self.get(name=operation.response.name)
1140+
elif operation.error:
1141+
raise RuntimeError(f"Failed to create session: {operation.error}")
1142+
else:
1143+
raise RuntimeError(
1144+
"Error retrieving session from the operation response. "
1145+
f"Operation name: {operation.name}"
1146+
)
1147+
return operation
1148+
1149+
async def list(
1150+
self,
1151+
*,
1152+
name: str,
1153+
config: Optional[types.ListAgentEngineSessionsConfigOrDict] = None,
1154+
) -> AsyncPager[types.Session]:
1155+
"""Lists Agent Engine sessions.
1156+
1157+
Args:
1158+
name (str): Required. The name of the agent engine to list sessions
1159+
for.
1160+
config (ListAgentEngineSessionConfig): Optional. The configuration
1161+
for the sessions to list.
1162+
1163+
Returns:
1164+
AsyncPager[Session]: An async pager of sessions.
1165+
"""
1166+
1167+
return AsyncPager(
1168+
"sessions",
1169+
functools.partial(self._list, name=name),
1170+
await self._list(name=name, config=config),
1171+
config,
1172+
)

0 commit comments

Comments
 (0)