Skip to content

Commit 736262c

Browse files
authored
Merge pull request #34 from huangrichao2020/codex/cjk-memory-search
fix(memory): recover short CJK search matches
2 parents 1851c27 + ef2ecc8 commit 736262c

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

.agent/memory/memory_search.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
when any memory file changes, is renamed, or is deleted.
2222
"""
2323
import json
24+
import re
2425
import shutil
2526
import sys
2627
import sqlite3
@@ -34,6 +35,7 @@
3435

3536
# Files we consider "memory documents" for both indexing and search.
3637
MEMORY_SUFFIXES = (".md", ".jsonl")
38+
CJK_RE = re.compile(r"[\u3400-\u9fff]")
3739

3840

3941
def feature_enabled() -> bool:
@@ -174,6 +176,15 @@ def search_fts5(query: str):
174176
"SELECT filename, substr(content, 1, 300) FROM memories WHERE content LIKE ?",
175177
(f"%{query}%",),
176178
).fetchall()
179+
180+
# SQLite's unicode61 tokenizer is good for mixed Latin/CJK text, but it
181+
# does not segment every short CJK substring the way users expect. Keep the
182+
# fast FTS path first, then recover exact CJK substring matches.
183+
if not rows and CJK_RE.search(query):
184+
rows = conn.execute(
185+
"SELECT filename, substr(content, 1, 300) FROM memories WHERE content LIKE ?",
186+
(f"%{query}%",),
187+
).fetchall()
177188
conn.close()
178189
return rows
179190

test_memory_search.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import importlib.util
2+
import json
3+
import tempfile
4+
import unittest
5+
from pathlib import Path
6+
7+
8+
ROOT = Path(__file__).resolve().parent
9+
MEMORY_SEARCH = ROOT / ".agent" / "memory" / "memory_search.py"
10+
11+
12+
def load_memory_search():
13+
spec = importlib.util.spec_from_file_location("memory_search", MEMORY_SEARCH)
14+
module = importlib.util.module_from_spec(spec)
15+
spec.loader.exec_module(module)
16+
return module
17+
18+
19+
class MemorySearchTest(unittest.TestCase):
20+
def with_memory_root(self):
21+
tmp = tempfile.TemporaryDirectory()
22+
root = Path(tmp.name) / ".agent" / "memory"
23+
(root / "semantic").mkdir(parents=True)
24+
(root / "episodic").mkdir()
25+
(root / ".features.json").write_text(
26+
json.dumps({"memory_search_fts": {"enabled": True}}),
27+
encoding="utf-8",
28+
)
29+
30+
module = load_memory_search()
31+
module.MEMORY_DIR = root
32+
module.INDEX_DIR = root / ".index"
33+
module.INDEX_PATH = module.INDEX_DIR / "memory.db"
34+
module.FEATURES_PATH = root / ".features.json"
35+
return tmp, root, module
36+
37+
def test_short_cjk_query_matches_inside_longer_phrase(self):
38+
tmp, root, memory_search = self.with_memory_root()
39+
self.addCleanup(tmp.cleanup)
40+
41+
(root / "semantic" / "LESSONS.md").write_text(
42+
"- 中文优先 when the user writes in Chinese.\n",
43+
encoding="utf-8",
44+
)
45+
46+
rows = memory_search.search_fts5("中文")
47+
48+
self.assertEqual(rows[0][0], "semantic/LESSONS.md")
49+
self.assertIn("中文优先", rows[0][1])
50+
51+
def test_mixed_english_cjk_query_uses_fts5(self):
52+
tmp, root, memory_search = self.with_memory_root()
53+
self.addCleanup(tmp.cleanup)
54+
55+
(root / "semantic" / "LESSONS.md").write_text(
56+
"- OpenClaw 飞书 document retrieval needs tab-aware recovery.\n",
57+
encoding="utf-8",
58+
)
59+
60+
rows = memory_search.search_fts5("OpenClaw 飞书")
61+
62+
self.assertEqual(rows[0][0], "semantic/LESSONS.md")
63+
self.assertIn("OpenClaw", rows[0][1])
64+
self.assertIn("飞书", rows[0][1])
65+
66+
def test_deleted_memory_file_is_removed_on_rebuild(self):
67+
tmp, root, memory_search = self.with_memory_root()
68+
self.addCleanup(tmp.cleanup)
69+
70+
note = root / "semantic" / "LESSONS.md"
71+
note.write_text("- Keep stale anchors out of search results.\n", encoding="utf-8")
72+
self.assertTrue(memory_search.search_fts5("stale anchors"))
73+
74+
note.unlink()
75+
rows = memory_search.search_fts5("stale anchors")
76+
77+
self.assertEqual(rows, [])
78+
79+
80+
if __name__ == "__main__":
81+
unittest.main()

0 commit comments

Comments
 (0)