Skip to content

Commit 77a74f0

Browse files
hackermengzhicopybara-github
authored andcommitted
fix: copy events and search results in InMemoryMemoryService
Merge #7566 InMemoryMemoryService now stores deep copies of ingested events and search_memory returns a deep copy of each event's content, so editing an ingested event or a returned MemoryEntry no longer changes stored memory or the source session. Fixes #7565 PiperOrigin-RevId: 1003200266
1 parent 6727f8f commit 77a74f0

2 files changed

Lines changed: 73 additions & 3 deletions

File tree

‎src/google/adk/memory/in_memory_memory_service.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ async def add_session_to_memory(self, session: Session) -> None:
103103
deleted_ids = self._deleted_memory_ids.get(user_key, set())
104104
self._session_events[user_key] = self._session_events.get(user_key, {})
105105
self._session_events[user_key][session.id] = [
106-
event
106+
event.model_copy(deep=True)
107107
for event in session.events
108108
if event.content
109109
and event.content.parts
@@ -136,7 +136,7 @@ async def add_events_to_memory(
136136
existing_ids = {event.id for event in existing_events}
137137
for event in events_to_add:
138138
if event.id not in existing_ids and event.id not in deleted_ids:
139-
existing_events.append(event)
139+
existing_events.append(event.model_copy(deep=True))
140140
existing_ids.add(event.id)
141141
self._session_events[user_key][scoped_session_id] = existing_events
142142

@@ -182,7 +182,7 @@ async def search_memory(
182182
matched_words,
183183
MemoryEntry(
184184
id=event.id,
185-
content=event.content,
185+
content=event.content.model_copy(deep=True),
186186
author=event.author,
187187
timestamp=_utils.format_timestamp(event.timestamp),
188188
),

‎tests/unittests/memory/test_in_memory_memory_service.py‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,3 +941,73 @@ def reader():
941941
assert (
942942
not errors
943943
), f'search_memory raced with concurrent deletes: {errors[0]!r}'
944+
945+
946+
@pytest.mark.parametrize('ingest_session', [True, False])
947+
async def test_ingested_events_are_detached_from_the_caller(
948+
ingest_session: bool,
949+
) -> None:
950+
"""Changing an ingested event must not rewrite stored memory."""
951+
service = InMemoryMemoryService()
952+
session = MOCK_SESSION_1.model_copy(deep=True)
953+
event = session.events[0]
954+
original_content = event.content.model_copy(deep=True)
955+
if ingest_session:
956+
await service.add_session_to_memory(session)
957+
else:
958+
await service.add_events_to_memory(
959+
app_name=session.app_name,
960+
user_id=session.user_id,
961+
session_id=session.id,
962+
events=[event],
963+
)
964+
965+
event.id = 'changed-id'
966+
event.content.parts[0].text = 'replaced by the caller'
967+
result = await service.search_memory(
968+
app_name=session.app_name, user_id=session.user_id, query='toolkit'
969+
)
970+
971+
assert len(result.memories) == 1
972+
assert result.memories[0].id == 'event-1a'
973+
assert result.memories[0].content == original_content
974+
975+
976+
@pytest.mark.parametrize('ingest_session', [True, False])
977+
async def test_search_results_are_detached_from_stored_memory(
978+
ingest_session: bool,
979+
) -> None:
980+
"""Editing returned content must not affect memory or the source session."""
981+
service = InMemoryMemoryService()
982+
session = MOCK_SESSION_1.model_copy(deep=True)
983+
event = session.events[0]
984+
event.content.parts.append(
985+
types.Part.from_function_call(
986+
name='lookup', args={'options': {'color': 'blue'}}
987+
)
988+
)
989+
original_content = event.content.model_copy(deep=True)
990+
if ingest_session:
991+
await service.add_session_to_memory(session)
992+
else:
993+
await service.add_events_to_memory(
994+
app_name=session.app_name,
995+
user_id=session.user_id,
996+
session_id=session.id,
997+
events=[event],
998+
)
999+
result = await service.search_memory(
1000+
app_name=session.app_name, user_id=session.user_id, query='toolkit'
1001+
)
1002+
1003+
returned_content = result.memories[0].content
1004+
returned_content.parts[0].text = 'replaced by the caller'
1005+
returned_content.parts[1].function_call.args['options']['color'] = 'red'
1006+
returned_content.parts.append(types.Part(text='extra content'))
1007+
repeated = await service.search_memory(
1008+
app_name=session.app_name, user_id=session.user_id, query='toolkit'
1009+
)
1010+
1011+
assert len(repeated.memories) == 1
1012+
assert repeated.memories[0].content == original_content
1013+
assert event.content == original_content

0 commit comments

Comments
 (0)