From e36f36503944645e348f3dd3bb5b617823d4b6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E6=9C=9D=E4=BC=9F?= Date: Mon, 31 Aug 2026 04:09:50 +0000 Subject: [PATCH 1/2] feat(memory): add delete methods for GDPR-style memory wipe BaseMemoryService only had add/search, so deleting a user's long-term memory required bypassing ADK. Add delete_memory and delete_memories, implement them for InMemoryMemoryService, and wrap Vertex memories.delete on VertexAiMemoryBankService. Fixes #6949 --- docs/guides/memory/memory_service/index.md | 16 ++ src/google/adk/memory/base_memory_service.py | 32 +++ .../adk/memory/in_memory_memory_service.py | 23 +++ .../memory/vertex_ai_memory_bank_service.py | 137 +++++++++++++ .../memory/test_in_memory_memory_service.py | 109 +++++++++++ .../test_vertex_ai_memory_bank_service.py | 182 ++++++++++++++++++ 6 files changed, 499 insertions(+) diff --git a/docs/guides/memory/memory_service/index.md b/docs/guides/memory/memory_service/index.md index 09d97562e86..4270297b0d4 100644 --- a/docs/guides/memory/memory_service/index.md +++ b/docs/guides/memory/memory_service/index.md @@ -134,6 +134,22 @@ holding `memories`, a list of `MemoryEntry`. Each entry carries `content` (a `custom_metadata`. Memory is scoped by the `(app_name, user_id)` pair, so one user never sees another's memories. +### Deletion + +Two optional methods remove memories. A service that does not support them +raises `NotImplementedError`. A missing memory, or a user with nothing stored, +is a no-op. + +* `delete_memory(*, app_name, user_id, memory_id)` deletes one entry by the + `id` returned on `MemoryEntry`. +* `delete_memories(*, app_name, user_id)` deletes every memory stored for + that user. + +`InMemoryMemoryService` and `VertexAiMemoryBankService` implement both. +`VertexAiMemoryBankService.delete_memory` wraps Memory Bank +`memories.delete` after checking the memory belongs to the requested +`(app_name, user_id)` scope. + ### From inside an agent `Context` — what tools and callbacks receive — exposes the same operations diff --git a/src/google/adk/memory/base_memory_service.py b/src/google/adk/memory/base_memory_service.py index 55b4e8d0e9c..aec0832ba70 100644 --- a/src/google/adk/memory/base_memory_service.py +++ b/src/google/adk/memory/base_memory_service.py @@ -138,3 +138,35 @@ async def search_memory( Returns: A SearchMemoryResponse containing the matching memories. """ + + async def delete_memory( + self, *, app_name: str, user_id: str, memory_id: str + ) -> None: + """Deletes a single memory entry by id. + + Missing memories are a no-op. Implementations that cannot delete by id + raise ``NotImplementedError``. + + Args: + app_name: The application name for memory scope. + user_id: The user ID for memory scope. + memory_id: The id of the memory to delete. This is the ``MemoryEntry.id`` + returned by ``search_memory`` or supplied to ``add_memory``. + """ + raise NotImplementedError( + 'This memory service does not support deleting memories.' + ) + + async def delete_memories(self, *, app_name: str, user_id: str) -> None: + """Deletes every memory stored for the given app and user. + + A user with no stored memories is a no-op. Implementations that cannot + delete memories raise ``NotImplementedError``. + + Args: + app_name: The application name for memory scope. + user_id: The user ID for memory scope. + """ + raise NotImplementedError( + 'This memory service does not support deleting memories.' + ) diff --git a/src/google/adk/memory/in_memory_memory_service.py b/src/google/adk/memory/in_memory_memory_service.py index 94639964c3e..b6317ff481b 100644 --- a/src/google/adk/memory/in_memory_memory_service.py +++ b/src/google/adk/memory/in_memory_memory_service.py @@ -146,6 +146,7 @@ async def search_memory( content=event.content, author=event.author, timestamp=_utils.format_timestamp(event.timestamp), + id=event.id or None, ), )) @@ -158,3 +159,25 @@ async def search_memory( return SearchMemoryResponse( memories=[memory for _, memory in scored_memories[:_MAX_SEARCH_RESULTS]] ) + + @override + async def delete_memory( + self, *, app_name: str, user_id: str, memory_id: str + ) -> None: + user_key = _user_key(app_name, user_id) + + with self._lock: + session_events = self._session_events.get(user_key) + if not session_events: + return + for session_id, events in session_events.items(): + remaining = [event for event in events if event.id != memory_id] + if len(remaining) != len(events): + session_events[session_id] = remaining + + @override + async def delete_memories(self, *, app_name: str, user_id: str) -> None: + user_key = _user_key(app_name, user_id) + + with self._lock: + self._session_events.pop(user_key, None) diff --git a/src/google/adk/memory/vertex_ai_memory_bank_service.py b/src/google/adk/memory/vertex_ai_memory_bank_service.py index c5ae3f36a95..e794c9123cf 100644 --- a/src/google/adk/memory/vertex_ai_memory_bank_service.py +++ b/src/google/adk/memory/vertex_ai_memory_bank_service.py @@ -20,11 +20,13 @@ import datetime from functools import lru_cache import logging +import re from typing import Optional from typing import TYPE_CHECKING from google.auth.credentials import Credentials from google.genai import types +from google.genai.errors import ClientError from typing_extensions import override from ..utils.vertex_ai_utils import get_express_mode_api_key @@ -83,6 +85,8 @@ _ENABLE_CONSOLIDATION_KEY = 'enable_consolidation' +_MEMORY_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]+$') + def _should_use_generate_memories( custom_metadata: Mapping[str, object] | None, @@ -579,6 +583,7 @@ async def search_memory( role='user', ), timestamp=update_time.isoformat() if update_time else None, + id=_memory_id_from_resource(memory), ) ) except AttributeError: @@ -592,6 +597,79 @@ async def search_memory( ) return SearchMemoryResponse(memories=memory_events) + @override + async def delete_memory( + self, *, app_name: str, user_id: str, memory_id: str + ) -> None: + """Deletes one Memory Bank memory after confirming it belongs to the user. + + A missing memory is a no-op. A memory that exists but belongs to a + different ``(app_name, user_id)`` scope raises ``ValueError``. + """ + memory_resource_name = _memory_resource_name( + self._agent_engine_id, memory_id + ) + api_client = self._get_api_client() + try: + existing = await api_client.agent_engines.memories.get( + name=memory_resource_name + ) + except ClientError as e: + if e.code == 404: + return + raise + if not _memory_belongs_to_scope( + existing, app_name=app_name, user_id=user_id + ): + raise ValueError( + f'Memory {memory_id} does not belong to user {user_id}.' + ) + + try: + await api_client.agent_engines.memories.delete(name=memory_resource_name) + except ClientError as e: + if e.code == 404: + return + raise + + @override + async def delete_memories(self, *, app_name: str, user_id: str) -> None: + """Deletes every Memory Bank memory stored for the given app and user. + + Lists memories for the scope, then wraps ``memories.delete`` for each. + """ + api_client = self._get_api_client() + retrieved_memories_iterator = ( + await api_client.agent_engines.memories.retrieve( + name='reasoningEngines/' + self._agent_engine_id, + scope={ + 'app_name': app_name, + 'user_id': user_id, + }, + ) + ) + + memory_names: list[str] = [] + try: + async for retrieved_memory in retrieved_memories_iterator: + memory = getattr(retrieved_memory, 'memory', None) + if memory is None: + continue + name = getattr(memory, 'name', None) + if isinstance(name, str) and name: + memory_names.append(name) + except Exception: + logger.exception('Error while listing memories to delete.') + raise + + for memory_name in memory_names: + try: + await api_client.agent_engines.memories.delete(name=memory_name) + except ClientError as e: + if e.code == 404: + continue + raise + async def retrieve_profiles( self, *, @@ -1033,3 +1111,62 @@ def _to_vertex_metadata_value( ) return None return {'string_value': str(value)} + + +def _extract_short_memory_id( + memory_id: str, expected_engine_id: str | None = None +) -> str: + """Extracts the short memory ID if a full resource name is provided.""" + if '/' in memory_id: + parts = memory_id.split('/') + if len(parts) >= 2 and parts[-2] == 'memories': + if ( + len(parts) >= 4 + and parts[-4] == 'reasoningEngines' + and expected_engine_id + ): + passed_engine_id = parts[-3] + if passed_engine_id != expected_engine_id: + raise ValueError( + 'Memory resource name mismatch: memory belongs to ' + f'reasoningEngine {passed_engine_id!r}, but service is ' + f'configured for {expected_engine_id!r}.' + ) + return parts[-1] + return memory_id + + +def _validate_memory_id(memory_id: str) -> None: + """Rejects memory IDs that could escape the URL path segment.""" + if not memory_id or not _MEMORY_ID_PATTERN.fullmatch(memory_id): + raise ValueError( + f'Invalid memory_id {memory_id!r}: must match' + f' {_MEMORY_ID_PATTERN.pattern}.' + ) + + +def _memory_resource_name(agent_engine_id: str, memory_id: str) -> str: + """Returns the Memory Bank resource name for a caller-supplied id.""" + short_id = _extract_short_memory_id( + memory_id, expected_engine_id=agent_engine_id + ) + _validate_memory_id(short_id) + return f'reasoningEngines/{agent_engine_id}/memories/{short_id}' + + +def _memory_id_from_resource(memory: object) -> str | None: + """Returns the last path component of a Memory Bank resource name.""" + name = getattr(memory, 'name', None) + if not isinstance(name, str) or not name: + return None + return name.rsplit('/', 1)[-1] + + +def _memory_belongs_to_scope( + memory: object, *, app_name: str, user_id: str +) -> bool: + """Returns whether a Memory Bank memory is scoped to this app and user.""" + scope = getattr(memory, 'scope', None) + if not isinstance(scope, Mapping): + return False + return scope.get('app_name') == app_name and scope.get('user_id') == user_id diff --git a/tests/unittests/memory/test_in_memory_memory_service.py b/tests/unittests/memory/test_in_memory_memory_service.py index 5524226b270..167dec9cc9e 100644 --- a/tests/unittests/memory/test_in_memory_memory_service.py +++ b/tests/unittests/memory/test_in_memory_memory_service.py @@ -259,6 +259,7 @@ async def test_search_memory_simple_match(): assert len(result.memories) == 1 assert result.memories[0].content.parts[0].text == 'I like to code in Python.' assert result.memories[0].author == 'user' + assert result.memories[0].id == 'event-2a' @pytest.mark.asyncio @@ -364,6 +365,114 @@ async def test_search_memory_does_not_collide_on_slash_in_identifiers(): assert not result.memories +@pytest.mark.asyncio +async def test_delete_memory_removes_entry_from_later_search(): + """Deleting a memory by id removes it from later search results.""" + memory_service = InMemoryMemoryService() + await memory_service.add_session_to_memory(MOCK_SESSION_1) + await memory_service.add_session_to_memory(MOCK_SESSION_2) + + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, memory_id='event-2a' + ) + + result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='Python' + ) + + assert not result.memories + remaining = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='ADK' + ) + assert len(remaining.memories) == 2 + + +@pytest.mark.asyncio +async def test_delete_memory_missing_id_is_a_noop(): + """Deleting an unknown memory id does not raise or change stored memories.""" + memory_service = InMemoryMemoryService() + await memory_service.add_session_to_memory(MOCK_SESSION_1) + + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, memory_id='missing-id' + ) + + result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='ADK' + ) + assert len(result.memories) == 2 + + +@pytest.mark.asyncio +async def test_delete_memory_does_not_remove_other_users_memory(): + """delete_memory is scoped to the requested user even when ids collide.""" + memory_service = InMemoryMemoryService() + colliding_session = Session( + app_name=MOCK_APP_NAME, + user_id=MOCK_OTHER_USER_ID, + id='session-collision', + last_update_time=3000, + events=[ + Event( + id='event-1a', + invocation_id='inv-collision', + author='user', + timestamp=60000, + content=types.Content( + parts=[types.Part(text='The ADK is a secret.')] + ), + ), + ], + ) + await memory_service.add_session_to_memory(MOCK_SESSION_1) + await memory_service.add_session_to_memory(colliding_session) + + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, memory_id='event-1a' + ) + + own_result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='ADK' + ) + other_result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_OTHER_USER_ID, query='secret' + ) + assert len(own_result.memories) == 1 + assert len(other_result.memories) == 1 + + +@pytest.mark.asyncio +async def test_delete_memories_clears_only_the_requested_user(): + """delete_memories wipes one user and leaves other users untouched.""" + memory_service = InMemoryMemoryService() + await memory_service.add_session_to_memory(MOCK_SESSION_1) + await memory_service.add_session_to_memory(MOCK_SESSION_2) + await memory_service.add_session_to_memory(MOCK_SESSION_DIFFERENT_USER) + + await memory_service.delete_memories( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID + ) + + own_result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='ADK Python' + ) + other_result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_OTHER_USER_ID, query='secret' + ) + assert not own_result.memories + assert len(other_result.memories) == 1 + + +@pytest.mark.asyncio +async def test_delete_memories_missing_user_is_a_noop(): + """Deleting memories for a user with none stored does not raise.""" + memory_service = InMemoryMemoryService() + + await memory_service.delete_memories( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID + ) + + # --- Non-Latin language tests --- diff --git a/tests/unittests/memory/test_vertex_ai_memory_bank_service.py b/tests/unittests/memory/test_vertex_ai_memory_bank_service.py index df923f808c5..fdfa56d13b5 100644 --- a/tests/unittests/memory/test_vertex_ai_memory_bank_service.py +++ b/tests/unittests/memory/test_vertex_ai_memory_bank_service.py @@ -27,6 +27,7 @@ from google.adk.sessions.session import Session from google.auth.credentials import Credentials from google.genai import types +from google.genai.errors import ClientError import pytest from vertexai import types as vertex_types @@ -285,6 +286,8 @@ def mock_vertexai_client(): mock.AsyncMock() ) mock_async_client.agent_engines.memories.ingest_events = mock.AsyncMock() + mock_async_client.agent_engines.memories.get = mock.AsyncMock() + mock_async_client.agent_engines.memories.delete = mock.AsyncMock() mock_client = mock.MagicMock() mock_client.aio = mock_async_client @@ -1176,6 +1179,30 @@ async def test_search_memory(mock_vertexai_client): assert result.memories[0].content.parts[0].text == 'test_content' +@pytest.mark.asyncio +async def test_search_memory_populates_id_from_resource_name( + mock_vertexai_client, +): + """search_memory exposes the Memory Bank memory id for later delete.""" + retrieved_memory = mock.MagicMock() + retrieved_memory.memory.fact = 'test_content' + retrieved_memory.memory.update_time = datetime.datetime(2024, 1, 1) + retrieved_memory.memory.name = ( + 'projects/p/locations/l/reasoningEngines/123/memories/mem-abc' + ) + + mock_vertexai_client.agent_engines.memories.retrieve.return_value = ( + _AsyncListIterator([retrieved_memory]) + ) + memory_service = mock_vertex_ai_memory_bank_service() + + result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='query' + ) + + assert result.memories[0].id == 'mem-abc' + + @pytest.mark.asyncio async def test_search_memory_empty_results(mock_vertexai_client): mock_vertexai_client.agent_engines.memories.retrieve.return_value = ( @@ -1392,3 +1419,158 @@ async def failing_async_iterator(): assert len(result.memories) == 1 assert result.memories[0].content.parts[0].text == 'good fact' + + +def _owned_memory(*, name: str = 'reasoningEngines/123/memories/mem-abc'): + memory = mock.MagicMock() + memory.name = name + memory.scope = {'app_name': MOCK_APP_NAME, 'user_id': MOCK_USER_ID} + return memory + + +@pytest.mark.asyncio +async def test_delete_memory_calls_vertex_delete(mock_vertexai_client): + """delete_memory wraps memories.delete after confirming ownership.""" + mock_vertexai_client.agent_engines.memories.get.return_value = _owned_memory() + memory_service = mock_vertex_ai_memory_bank_service() + + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, memory_id='mem-abc' + ) + + mock_vertexai_client.agent_engines.memories.get.assert_awaited_once_with( + name='reasoningEngines/123/memories/mem-abc' + ) + mock_vertexai_client.agent_engines.memories.delete.assert_awaited_once_with( + name='reasoningEngines/123/memories/mem-abc' + ) + + +@pytest.mark.asyncio +async def test_delete_memory_accepts_full_resource_name(mock_vertexai_client): + """A full Memory Bank resource name is reduced to the configured engine.""" + mock_vertexai_client.agent_engines.memories.get.return_value = _owned_memory() + memory_service = mock_vertex_ai_memory_bank_service() + + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, + user_id=MOCK_USER_ID, + memory_id=( + 'projects/p/locations/l/reasoningEngines/123/memories/mem-abc' + ), + ) + + mock_vertexai_client.agent_engines.memories.delete.assert_awaited_once_with( + name='reasoningEngines/123/memories/mem-abc' + ) + + +@pytest.mark.asyncio +async def test_delete_memory_missing_is_a_noop(mock_vertexai_client): + """A 404 from memories.get is treated as already deleted.""" + mock_vertexai_client.agent_engines.memories.get.side_effect = ClientError( + code=404, + response_json={'message': 'Memory not found.'}, + response=None, + ) + memory_service = mock_vertex_ai_memory_bank_service() + + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, memory_id='missing' + ) + + mock_vertexai_client.agent_engines.memories.delete.assert_not_called() + + +@pytest.mark.asyncio +async def test_delete_memory_rejects_other_users_memory(mock_vertexai_client): + """delete_memory must not delete a memory owned by a different user.""" + other_users_memory = _owned_memory() + other_users_memory.scope = { + 'app_name': MOCK_APP_NAME, + 'user_id': 'someone-else', + } + mock_vertexai_client.agent_engines.memories.get.return_value = ( + other_users_memory + ) + memory_service = mock_vertex_ai_memory_bank_service() + + with pytest.raises(ValueError, match='does not belong to user'): + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, memory_id='mem-abc' + ) + + mock_vertexai_client.agent_engines.memories.delete.assert_not_called() + + +@pytest.mark.asyncio +async def test_delete_memory_rejects_invalid_memory_id(mock_vertexai_client): + """Memory ids that could escape the resource path are rejected.""" + memory_service = mock_vertex_ai_memory_bank_service() + + with pytest.raises(ValueError, match='Invalid memory_id'): + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, + user_id=MOCK_USER_ID, + memory_id='../escape', + ) + + mock_vertexai_client.agent_engines.memories.get.assert_not_called() + mock_vertexai_client.agent_engines.memories.delete.assert_not_called() + + +@pytest.mark.asyncio +async def test_delete_memory_rejects_engine_mismatch(mock_vertexai_client): + """A resource name for another reasoning engine is rejected.""" + memory_service = mock_vertex_ai_memory_bank_service() + + with pytest.raises(ValueError, match='Memory resource name mismatch'): + await memory_service.delete_memory( + app_name=MOCK_APP_NAME, + user_id=MOCK_USER_ID, + memory_id='reasoningEngines/999/memories/mem-abc', + ) + + mock_vertexai_client.agent_engines.memories.get.assert_not_called() + mock_vertexai_client.agent_engines.memories.delete.assert_not_called() + + +@pytest.mark.asyncio +async def test_delete_memories_deletes_each_scoped_memory(mock_vertexai_client): + """delete_memories lists the user scope then wraps memories.delete.""" + first = mock.MagicMock() + first.memory = _owned_memory(name='reasoningEngines/123/memories/mem-a') + second = mock.MagicMock() + second.memory = _owned_memory(name='reasoningEngines/123/memories/mem-b') + mock_vertexai_client.agent_engines.memories.retrieve.return_value = ( + _AsyncListIterator([first, second]) + ) + memory_service = mock_vertex_ai_memory_bank_service() + + await memory_service.delete_memories( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID + ) + + mock_vertexai_client.agent_engines.memories.retrieve.assert_awaited_once_with( + name='reasoningEngines/123', + scope={'app_name': MOCK_APP_NAME, 'user_id': MOCK_USER_ID}, + ) + mock_vertexai_client.agent_engines.memories.delete.assert_has_awaits([ + mock.call(name='reasoningEngines/123/memories/mem-a'), + mock.call(name='reasoningEngines/123/memories/mem-b'), + ]) + + +@pytest.mark.asyncio +async def test_delete_memories_empty_scope_is_a_noop(mock_vertexai_client): + """A user with no stored memories does not call memories.delete.""" + mock_vertexai_client.agent_engines.memories.retrieve.return_value = ( + _AsyncListIterator([]) + ) + memory_service = mock_vertex_ai_memory_bank_service() + + await memory_service.delete_memories( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID + ) + + mock_vertexai_client.agent_engines.memories.delete.assert_not_called() From 41f3ae03a7d36e36bdd985c0b739c328a9d90564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E6=9C=9D=E4=BC=9F?= Date: Mon, 31 Aug 2026 04:10:52 +0000 Subject: [PATCH 2/2] style(memory): apply pyink formatting to delete methods --- src/google/adk/memory/base_memory_service.py | 4 ++-- src/google/adk/memory/vertex_ai_memory_bank_service.py | 4 +--- tests/unittests/memory/test_vertex_ai_memory_bank_service.py | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/google/adk/memory/base_memory_service.py b/src/google/adk/memory/base_memory_service.py index aec0832ba70..b738267f43a 100644 --- a/src/google/adk/memory/base_memory_service.py +++ b/src/google/adk/memory/base_memory_service.py @@ -154,7 +154,7 @@ async def delete_memory( returned by ``search_memory`` or supplied to ``add_memory``. """ raise NotImplementedError( - 'This memory service does not support deleting memories.' + "This memory service does not support deleting memories." ) async def delete_memories(self, *, app_name: str, user_id: str) -> None: @@ -168,5 +168,5 @@ async def delete_memories(self, *, app_name: str, user_id: str) -> None: user_id: The user ID for memory scope. """ raise NotImplementedError( - 'This memory service does not support deleting memories.' + "This memory service does not support deleting memories." ) diff --git a/src/google/adk/memory/vertex_ai_memory_bank_service.py b/src/google/adk/memory/vertex_ai_memory_bank_service.py index e794c9123cf..d19a12529c1 100644 --- a/src/google/adk/memory/vertex_ai_memory_bank_service.py +++ b/src/google/adk/memory/vertex_ai_memory_bank_service.py @@ -621,9 +621,7 @@ async def delete_memory( if not _memory_belongs_to_scope( existing, app_name=app_name, user_id=user_id ): - raise ValueError( - f'Memory {memory_id} does not belong to user {user_id}.' - ) + raise ValueError(f'Memory {memory_id} does not belong to user {user_id}.') try: await api_client.agent_engines.memories.delete(name=memory_resource_name) diff --git a/tests/unittests/memory/test_vertex_ai_memory_bank_service.py b/tests/unittests/memory/test_vertex_ai_memory_bank_service.py index fdfa56d13b5..cb9486d0d20 100644 --- a/tests/unittests/memory/test_vertex_ai_memory_bank_service.py +++ b/tests/unittests/memory/test_vertex_ai_memory_bank_service.py @@ -1455,9 +1455,7 @@ async def test_delete_memory_accepts_full_resource_name(mock_vertexai_client): await memory_service.delete_memory( app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, - memory_id=( - 'projects/p/locations/l/reasoningEngines/123/memories/mem-abc' - ), + memory_id='projects/p/locations/l/reasoningEngines/123/memories/mem-abc', ) mock_vertexai_client.agent_engines.memories.delete.assert_awaited_once_with(