Skip to content

Commit 043e287

Browse files
committed
feat: add error handling to VertexAiMemoryBankService.search_memory
1 parent 1063fa5 commit 043e287

2 files changed

Lines changed: 138 additions & 12 deletions

File tree

src/google/adk/memory/vertex_ai_memory_bank_service.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,19 +122,38 @@ async def search_memory(self, *, app_name: str, user_id: str, query: str):
122122

123123
logger.info('Search memory response received.')
124124

125-
memory_events = []
126-
for retrieved_memory in retrieved_memories_iterator:
127-
# TODO: add more complex error handling
128-
logger.debug('Retrieved memory: %s', retrieved_memory)
129-
memory_events.append(
130-
MemoryEntry(
131-
author='user',
132-
content=types.Content(
133-
parts=[types.Part(text=retrieved_memory.memory.fact)],
134-
role='user',
135-
),
136-
timestamp=retrieved_memory.memory.update_time.isoformat(),
125+
memory_events: list[MemoryEntry] = []
126+
try:
127+
for retrieved_memory in retrieved_memories_iterator:
128+
try:
129+
memory = retrieved_memory.memory
130+
if memory is None:
131+
logger.warning('Skipping memory entry with missing memory object.')
132+
continue
133+
fact = memory.fact
134+
if not fact:
135+
logger.warning('Skipping memory entry with empty or missing fact.')
136+
continue
137+
138+
update_time = memory.update_time
139+
memory_events.append(
140+
MemoryEntry(
141+
author='user',
142+
content=types.Content(
143+
parts=[types.Part(text=fact)],
144+
role='user',
145+
),
146+
timestamp=update_time.isoformat() if update_time else None,
147+
)
137148
)
149+
except AttributeError:
150+
logger.warning(
151+
'Skipping malformed memory entry: %s', retrieved_memory
152+
)
153+
except Exception: # pylint: disable=broad-except
154+
logger.exception(
155+
'Error while iterating memory results. Returning %d partial results.',
156+
len(memory_events),
138157
)
139158
return SearchMemoryResponse(memories=memory_events)
140159

tests/unittests/memory/test_vertex_ai_memory_bank_service.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,110 @@ async def test_search_memory_empty_results(mock_vertexai_client):
182182
)
183183

184184
assert len(result.memories) == 0
185+
186+
187+
@pytest.mark.asyncio
188+
async def test_search_memory_skips_entry_with_none_memory(mock_vertexai_client):
189+
bad_entry = mock.MagicMock()
190+
bad_entry.memory = None
191+
192+
good_entry = mock.MagicMock()
193+
good_entry.memory.fact = 'good fact'
194+
good_entry.memory.update_time = datetime(2024, 1, 1)
195+
196+
mock_vertexai_client.agent_engines.memories.retrieve.return_value = [
197+
bad_entry,
198+
good_entry,
199+
]
200+
memory_service = mock_vertex_ai_memory_bank_service()
201+
202+
result = await memory_service.search_memory(
203+
app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='query'
204+
)
205+
206+
assert len(result.memories) == 1
207+
assert result.memories[0].content.parts[0].text == 'good fact'
208+
209+
210+
@pytest.mark.asyncio
211+
async def test_search_memory_skips_entry_with_empty_fact(mock_vertexai_client):
212+
for empty_fact in [None, '']:
213+
bad_entry = mock.MagicMock()
214+
bad_entry.memory.fact = empty_fact
215+
bad_entry.memory.update_time = datetime(2024, 1, 1)
216+
217+
mock_vertexai_client.agent_engines.memories.retrieve.return_value = [
218+
bad_entry
219+
]
220+
memory_service = mock_vertex_ai_memory_bank_service()
221+
222+
result = await memory_service.search_memory(
223+
app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='query'
224+
)
225+
226+
assert len(result.memories) == 0
227+
228+
229+
@pytest.mark.asyncio
230+
async def test_search_memory_handles_missing_update_time(mock_vertexai_client):
231+
entry = mock.MagicMock()
232+
entry.memory.fact = 'some fact'
233+
entry.memory.update_time = None
234+
235+
mock_vertexai_client.agent_engines.memories.retrieve.return_value = [entry]
236+
memory_service = mock_vertex_ai_memory_bank_service()
237+
238+
result = await memory_service.search_memory(
239+
app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='query'
240+
)
241+
242+
assert len(result.memories) == 1
243+
assert result.memories[0].content.parts[0].text == 'some fact'
244+
assert result.memories[0].timestamp is None
245+
246+
247+
@pytest.mark.asyncio
248+
async def test_search_memory_skips_malformed_entry(mock_vertexai_client):
249+
malformed = mock.MagicMock(spec=[]) # no attributes → AttributeError
250+
251+
good_entry = mock.MagicMock()
252+
good_entry.memory.fact = 'good fact'
253+
good_entry.memory.update_time = datetime(2024, 1, 1)
254+
255+
mock_vertexai_client.agent_engines.memories.retrieve.return_value = [
256+
malformed,
257+
good_entry,
258+
]
259+
memory_service = mock_vertex_ai_memory_bank_service()
260+
261+
result = await memory_service.search_memory(
262+
app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='query'
263+
)
264+
265+
assert len(result.memories) == 1
266+
assert result.memories[0].content.parts[0].text == 'good fact'
267+
268+
269+
@pytest.mark.asyncio
270+
async def test_search_memory_returns_partial_results_on_iterator_error(
271+
mock_vertexai_client,
272+
):
273+
good_entry = mock.MagicMock()
274+
good_entry.memory.fact = 'good fact'
275+
good_entry.memory.update_time = datetime(2024, 1, 1)
276+
277+
def failing_iterator():
278+
yield good_entry
279+
raise RuntimeError('API stream error')
280+
281+
mock_vertexai_client.agent_engines.memories.retrieve.return_value = (
282+
failing_iterator()
283+
)
284+
memory_service = mock_vertex_ai_memory_bank_service()
285+
286+
result = await memory_service.search_memory(
287+
app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='query'
288+
)
289+
290+
assert len(result.memories) == 1
291+
assert result.memories[0].content.parts[0].text == 'good fact'

0 commit comments

Comments
 (0)