Files
adk-python/tests/unittests/sessions/test_vertex_ai_session_service.py
T

298 lines
8.8 KiB
Python
Raw Normal View History

2025-04-08 17:22:09 +00:00
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import this
from typing import Any
from unittest import mock
2025-05-15 12:46:12 -07:00
2025-04-08 17:22:09 +00:00
from dateutil.parser import isoparse
from google.adk.events import Event
from google.adk.events import EventActions
from google.adk.sessions import Session
from google.adk.sessions import VertexAiSessionService
from google.genai import types
import pytest
MOCK_SESSION_JSON_1 = {
'name': (
'projects/test-project/locations/test-location/'
'reasoningEngines/123/sessions/1'
),
'createTime': '2024-12-12T12:12:12.123456Z',
'updateTime': '2024-12-12T12:12:12.123456Z',
'sessionState': {
'key': {'value': 'test_value'},
},
'userId': 'user',
}
MOCK_SESSION_JSON_2 = {
'name': (
'projects/test-project/locations/test-location/'
'reasoningEngines/123/sessions/2'
),
'updateTime': '2024-12-13T12:12:12.123456Z',
'userId': 'user',
}
MOCK_SESSION_JSON_3 = {
'name': (
'projects/test-project/locations/test-location/'
'reasoningEngines/123/sessions/3'
),
'updateTime': '2024-12-14T12:12:12.123456Z',
'userId': 'user2',
}
MOCK_EVENT_JSON = [
{
'name': (
'projects/test-project/locations/test-location/'
2025-04-17 19:50:22 +00:00
'reasoningEngines/123/sessions/1/events/123'
2025-04-08 17:22:09 +00:00
),
'invocationId': '123',
'author': 'user',
'timestamp': '2024-12-12T12:12:12.123456Z',
'content': {
'parts': [
{'text': 'test_content'},
],
},
'actions': {
'stateDelta': {
'key': {'value': 'test_value'},
},
'transferAgent': 'agent',
},
'eventMetadata': {
'partial': False,
'turnComplete': True,
'interrupted': False,
'branch': '',
'longRunningToolIds': ['tool1'],
},
},
]
MOCK_SESSION = Session(
app_name='123',
user_id='user',
id='1',
state=MOCK_SESSION_JSON_1['sessionState'],
last_update_time=isoparse(MOCK_SESSION_JSON_1['updateTime']).timestamp(),
events=[
Event(
id='123',
invocation_id='123',
author='user',
timestamp=isoparse(MOCK_EVENT_JSON[0]['timestamp']).timestamp(),
content=types.Content(parts=[types.Part(text='test_content')]),
actions=EventActions(
transfer_to_agent='agent',
state_delta={'key': {'value': 'test_value'}},
),
partial=False,
turn_complete=True,
interrupted=False,
branch='',
long_running_tool_ids={'tool1'},
),
],
)
SESSION_REGEX = r'^reasoningEngines/([^/]+)/sessions/([^/]+)$'
2025-04-17 19:50:22 +00:00
SESSIONS_REGEX = r'^reasoningEngines/([^/]+)/sessions\?filter=user_id=([^/]+)$'
2025-04-08 17:22:09 +00:00
EVENTS_REGEX = r'^reasoningEngines/([^/]+)/sessions/([^/]+)/events$'
LRO_REGEX = r'^operations/([^/]+)$'
class MockApiClient:
"""Mocks the API Client."""
def __init__(self) -> None:
"""Initializes MockClient."""
this.session_dict: dict[str, Any] = {}
this.event_dict: dict[str, list[Any]] = {}
async def async_request(
self, http_method: str, path: str, request_dict: dict[str, Any]
):
2025-04-08 17:22:09 +00:00
"""Mocks the API Client request method."""
if http_method == 'GET':
if re.match(SESSION_REGEX, path):
match = re.match(SESSION_REGEX, path)
if match:
session_id = match.group(2)
if session_id in self.session_dict:
return self.session_dict[session_id]
else:
raise ValueError(f'Session not found: {session_id}')
elif re.match(SESSIONS_REGEX, path):
2025-04-17 19:50:22 +00:00
match = re.match(SESSIONS_REGEX, path)
2025-04-08 17:22:09 +00:00
return {
2025-04-17 19:50:22 +00:00
'sessions': [
session
for session in self.session_dict.values()
if session['userId'] == match.group(2)
],
2025-04-08 17:22:09 +00:00
}
elif re.match(EVENTS_REGEX, path):
match = re.match(EVENTS_REGEX, path)
if match:
2025-04-17 19:50:22 +00:00
return {
'sessionEvents': (
self.event_dict[match.group(2)]
if match.group(2) in self.event_dict
else []
)
}
2025-04-08 17:22:09 +00:00
elif re.match(LRO_REGEX, path):
return {
'name': (
'projects/test-project/locations/test-location/'
2025-04-17 19:50:22 +00:00
'reasoningEngines/123/sessions/4'
2025-04-08 17:22:09 +00:00
),
'done': True,
}
else:
raise ValueError(f'Unsupported path: {path}')
elif http_method == 'POST':
2025-04-17 19:50:22 +00:00
new_session_id = '4'
self.session_dict[new_session_id] = {
2025-04-08 17:22:09 +00:00
'name': (
'projects/test-project/locations/test-location/'
'reasoningEngines/123/sessions/'
2025-04-17 19:50:22 +00:00
+ new_session_id
2025-04-08 17:22:09 +00:00
),
'userId': request_dict['user_id'],
2025-04-17 19:50:22 +00:00
'sessionState': request_dict.get('session_state', {}),
2025-04-08 17:22:09 +00:00
'updateTime': '2024-12-12T12:12:12.123456Z',
}
return {
'name': (
'projects/test_project/locations/test_location/'
2025-04-17 19:50:22 +00:00
'reasoningEngines/123/sessions/'
+ new_session_id
+ '/operations/111'
2025-04-08 17:22:09 +00:00
),
'done': False,
}
elif http_method == 'DELETE':
match = re.match(SESSION_REGEX, path)
if match:
self.session_dict.pop(match.group(2))
else:
raise ValueError(f'Unsupported http method: {http_method}')
def mock_vertex_ai_session_service():
"""Creates a mock Vertex AI Session service for testing."""
return VertexAiSessionService(
2025-04-08 17:22:09 +00:00
project='test-project', location='test-location'
)
@pytest.fixture
def mock_get_api_client():
api_client = MockApiClient()
api_client.session_dict = {
2025-04-08 17:22:09 +00:00
'1': MOCK_SESSION_JSON_1,
'2': MOCK_SESSION_JSON_2,
'3': MOCK_SESSION_JSON_3,
}
api_client.event_dict = {
2025-04-08 17:22:09 +00:00
'1': MOCK_EVENT_JSON,
}
with mock.patch(
"google.adk.sessions.vertex_ai_session_service._get_api_client",
return_value=api_client,
):
yield
2025-04-08 17:22:09 +00:00
2025-05-15 12:46:12 -07:00
@pytest.mark.asyncio
@pytest.mark.usefixtures('mock_get_api_client')
2025-05-15 12:46:12 -07:00
async def test_get_empty_session():
2025-04-08 17:22:09 +00:00
session_service = mock_vertex_ai_session_service()
with pytest.raises(ValueError) as excinfo:
2025-05-15 12:46:12 -07:00
assert await session_service.get_session(
2025-04-08 17:22:09 +00:00
app_name='123', user_id='user', session_id='0'
)
assert str(excinfo.value) == 'Session not found: 0'
2025-05-15 12:46:12 -07:00
@pytest.mark.asyncio
@pytest.mark.usefixtures('mock_get_api_client')
2025-05-15 12:46:12 -07:00
async def test_get_and_delete_session():
2025-04-08 17:22:09 +00:00
session_service = mock_vertex_ai_session_service()
assert (
2025-05-15 12:46:12 -07:00
await session_service.get_session(
2025-04-08 17:22:09 +00:00
app_name='123', user_id='user', session_id='1'
)
== MOCK_SESSION
)
2025-05-15 12:46:12 -07:00
await session_service.delete_session(
app_name='123', user_id='user', session_id='1'
)
2025-04-08 17:22:09 +00:00
with pytest.raises(ValueError) as excinfo:
2025-05-15 12:46:12 -07:00
assert await session_service.get_session(
2025-04-08 17:22:09 +00:00
app_name='123', user_id='user', session_id='1'
)
assert str(excinfo.value) == 'Session not found: 1'
2025-05-15 12:46:12 -07:00
@pytest.mark.asyncio
@pytest.mark.usefixtures('mock_get_api_client')
2025-05-15 12:46:12 -07:00
async def test_list_sessions():
2025-04-17 19:50:22 +00:00
session_service = mock_vertex_ai_session_service()
2025-05-15 12:46:12 -07:00
sessions = await session_service.list_sessions(app_name='123', user_id='user')
2025-04-17 19:50:22 +00:00
assert len(sessions.sessions) == 2
assert sessions.sessions[0].id == '1'
assert sessions.sessions[1].id == '2'
2025-04-08 17:22:09 +00:00
2025-04-17 19:50:22 +00:00
2025-05-15 12:46:12 -07:00
@pytest.mark.asyncio
@pytest.mark.usefixtures('mock_get_api_client')
2025-05-15 12:46:12 -07:00
async def test_create_session():
2025-04-17 19:50:22 +00:00
session_service = mock_vertex_ai_session_service()
state = {'key': 'value'}
2025-05-15 12:46:12 -07:00
session = await session_service.create_session(
2025-04-17 19:50:22 +00:00
app_name='123', user_id='user', state=state
)
assert session.state == state
assert session.app_name == '123'
assert session.user_id == 'user'
assert session.last_update_time is not None
session_id = session.id
2025-05-15 12:46:12 -07:00
assert session == await session_service.get_session(
2025-04-17 19:50:22 +00:00
app_name='123', user_id='user', session_id=session_id
)
2025-05-15 12:46:12 -07:00
@pytest.mark.asyncio
@pytest.mark.usefixtures('mock_get_api_client')
2025-05-15 12:46:12 -07:00
async def test_create_session_with_custom_session_id():
session_service = mock_vertex_ai_session_service()
with pytest.raises(ValueError) as excinfo:
2025-05-15 12:46:12 -07:00
await session_service.create_session(
app_name='123', user_id='user', session_id='1'
)
assert str(excinfo.value) == (
'User-provided Session id is not supported for VertexAISessionService.'
)