Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/google/adk/tools/preload_memory_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,17 @@ async def process_llm_request(
llm_request: LlmRequest,
) -> None:
user_content = tool_context.user_content
if (
not user_content
or not user_content.parts
or not user_content.parts[0].text
):
if not user_content or not user_content.parts:
return

user_query = ' '.join(part.text for part in user_content.parts if part.text)
if not user_query:
return

user_query: str = user_content.parts[0].text
try:
response = await tool_context.search_memory(user_query)
except Exception:
logging.warning('Failed to preload memory for query: %s', user_query)
logger.warning('Failed to preload memory for query: %s', user_query)
return

if not response.memories:
Expand Down
50 changes: 50 additions & 0 deletions tests/unittests/tools/test_preload_memory_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from unittest import mock

from google.adk.memory.base_memory_service import SearchMemoryResponse
Expand Down Expand Up @@ -149,3 +150,52 @@ async def test_preload_memory_search_failure_is_noop():
)

assert request == original


@pytest.mark.asyncio
async def test_preload_memory_uses_text_from_every_part():
"""The search query is not limited to the first content part.

A leading non-text part (e.g. an inline file or a provider-required
placeholder) must not blank out or replace the user's actual question.
"""
request = LlmRequest(contents=[types.UserContent('current query')])
tool_context = mock.Mock()
tool_context.user_content = types.Content(
role='user',
parts=[
types.Part(text=''),
types.Part.from_text(text='what tea do I like'),
],
)
tool_context.search_memory = mock.AsyncMock(
return_value=SearchMemoryResponse(memories=[])
)

await PreloadMemoryTool().process_llm_request(
tool_context=tool_context,
llm_request=request,
)

tool_context.search_memory.assert_awaited_once_with('what tea do I like')


@pytest.mark.asyncio
async def test_preload_memory_logs_search_failure_on_own_logger(caplog):
"""Retrieval failures must be observable via the module's own logger.

Applications that configure logging by the `google_adk` namespace would
otherwise never see a memory backend outage, since a fail-open retrieval
error is indistinguishable from "no memories matched".
"""
request = LlmRequest(contents=[types.UserContent('current query')])
tool_context = _tool_context()
tool_context.search_memory.side_effect = RuntimeError('unavailable')

with caplog.at_level(logging.WARNING, logger='google_adk'):
await PreloadMemoryTool().process_llm_request(
tool_context=tool_context,
llm_request=request,
)

assert any(record.name.startswith('google_adk') for record in caplog.records)