From 596f1f30b4336b719450d24551bef60a844f936b Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Thu, 27 Aug 2026 04:11:53 -0700 Subject: [PATCH] Fix AdvancedSQLiteSession content search for non-ASCII text find_turns_by_content matches the search term against the message_data column with LIKE, but rows are written as json.dumps(item), which escapes every non-ASCII character as \uXXXX. A raw term therefore never matches non-ASCII content: searching a Japanese conversation for a Japanese word returns nothing. Re-encode the term the same way before building the LIKE pattern. ASCII searches are byte-identical to before; non-ASCII ones now match. The same column feeds create_branch_from_content, so that path is fixed too. --- .../memory/advanced_sqlite_session.py | 17 ++++++++- .../memory/test_advanced_sqlite_session.py | 35 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/agents/extensions/memory/advanced_sqlite_session.py b/src/agents/extensions/memory/advanced_sqlite_session.py index dbb8767f47..822ff5d68c 100644 --- a/src/agents/extensions/memory/advanced_sqlite_session.py +++ b/src/agents/extensions/memory/advanced_sqlite_session.py @@ -36,6 +36,17 @@ def _allow_all_sqlite_actions( return sqlite3.SQLITE_OK +def _stored_message_data_needle(search_term: str) -> str: + """Return ``search_term`` in the encoding used by the stored ``message_data`` column. + + Session rows are written as ``json.dumps(item)``, which escapes every non-ASCII character + as ``\\uXXXX`` and escapes ``"``, ``\\``, and control characters. Matching a raw search term + against that column therefore never finds non-ASCII content. Re-encoding the term the same + way keeps ASCII searches byte-identical while making non-ASCII searches match. + """ + return json.dumps(search_term)[1:-1] + + def _content_preview(content: Any, max_length: int | None = None) -> str: """Return a string preview of a stored user-message ``content``. @@ -1598,7 +1609,11 @@ def _search_sync(): AND am.message_data LIKE ? ORDER BY ms.branch_turn_number """, - (self.session_id, resolved_branch_id, f"%{search_term}%"), + ( + self.session_id, + resolved_branch_id, + f"%{_stored_message_data_needle(search_term)}%", + ), ) matches = [] diff --git a/tests/extensions/memory/test_advanced_sqlite_session.py b/tests/extensions/memory/test_advanced_sqlite_session.py index 5383e553fb..216ea8edaf 100644 --- a/tests/extensions/memory/test_advanced_sqlite_session.py +++ b/tests/extensions/memory/test_advanced_sqlite_session.py @@ -1802,6 +1802,41 @@ async def test_get_conversation_turns_with_list_content(): session.close() +async def test_find_turns_by_content_matches_non_ascii_content(): + """Non-ASCII and JSON-escaped user content must be searchable.""" + session_id = "find_turns_non_ascii_test" + session = AdvancedSQLiteSession(session_id=session_id, create_tables=True) + + await session.add_items( + [ + {"role": "user", "content": "東京の天気はどうですか"}, + {"role": "assistant", "content": "晴れです"}, + ] + ) + await session.add_items( + [ + {"role": "user", "content": "What is the weather in Tokyo?"}, + {"role": "assistant", "content": "Sunny."}, + ] + ) + await session.add_items( + [ + {"role": "user", "content": 'Le café est "très" bon'}, + {"role": "assistant", "content": "Oui."}, + ] + ) + + assert [turn["turn"] for turn in await session.find_turns_by_content("東京")] == [1] + assert [turn["turn"] for turn in await session.find_turns_by_content("weather")] == [2] + assert [turn["turn"] for turn in await session.find_turns_by_content('café est "très"')] == [3] + assert await session.find_turns_by_content("大阪") == [] + + branch_id = await session.create_branch_from_content("東京", "tokyo_branch") + assert branch_id == "tokyo_branch" + + session.close() + + async def test_find_turns_by_content_with_list_content(): """find_turns_by_content returns a string preview for list (multimodal) content.""" session_id = "find_turns_list_content_test"