fix(memory-core): score CJK keyword search instead of textScore=0 with trigram FTS5#92164
fix(memory-core): score CJK keyword search instead of textScore=0 with trigram FTS5#92164draix wants to merge 1 commit into
Conversation
…igram FTS5) Multi-character CJK query tokens were emitted as a single quoted trigram MATCH phrase that only matches an exact contiguous substring, so MATCH returned zero rows and (because the LIKE fallback only fired on a thrown MATCH) the keyword channel collapsed to textScore=0 for every CJK query. - planKeywordSearch: decompose any CJK-bearing token into per-character LIKE substring terms (keeping contiguous non-CJK runs whole), not just tokens shorter than 3 characters. - searchKeyword: fall back to per-term LIKE when MATCH succeeds but returns zero rows, via a shared runLikeFallback helper reused by the exception path so both use the correctly decomposed term set. Adds CJK FTS regression tests (per-character AND semantics, empty-MATCH fallback, mixed ASCII+CJK token splitting); the failing cases reproduce textScore=0 against main. Fixes openclaw#92061 Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Codex review: needs real behavior proof before merge. Reviewed June 15, 2026, 12:30 PM ET / 16:30 UTC. Summary PR surface: Source +50, Tests +97. Total +147 across 2 files. Reproducibility: yes. at source level: current main sends long CJK trigram tokens through MATCH and only falls back to LIKE on MATCH exceptions. I did not run a live OpenClaw memory_search instance with an embedding provider in this read-only review. Review metrics: 1 noteworthy metric.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land one maintainer-approved CJK fallback strategy, with focused regression tests plus real memory_search and latency proof, then close the linked CJK memory-search issues after merge. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main sends long CJK trigram tokens through MATCH and only falls back to LIKE on MATCH exceptions. I did not run a live OpenClaw memory_search instance with an embedding provider in this read-only review. Is this the best way to solve the issue? Unclear as merge-ready: the patch targets the right memory-core owner function and has useful tests, but the best final fix still needs maintainer-approved CJK split semantics plus real and latency proof. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 865e4db1cd0a. Label changesLabel justifications:
Evidence reviewedPR surface: Source +50, Tests +97. Total +147 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Thanks for the review — noting the patch-quality rating and the single remaining blocker (real behavior proof). On the proof gate: a full live On the red CI lanes: the failing |
|
Thanks @draix for the CJK trigram search fix in #92164. I am closing this as superseded by #92341 because #92341 is the open canonical path for the same #92061 textScore=0 root cause, covers the empty MATCH LIKE fallback as well, and currently has the stronger proof/check state. Clownfish will keep your source PR and attribution in the canonical review context so the implementation credit is preserved. If #92341 misses a behavior that this PR still covers, please reply and we can reopen or split that work back out. |
Summary
Hybrid memory search scored every Chinese/Japanese/Korean (CJK) query at
textScore: 0when the FTS5 store uses thetrigramtokenizer. Vector search still returned correct matches, but the BM25/full-text component contributed nothing, dragging down the final hybrid score for all CJK queries. English queries were unaffected.This fixes the two root causes pinpointed in #92061, both in
extensions/memory-core(planKeywordSearch/searchKeyword).Fixes #92061
Root cause
planKeywordSearchtokenizes the query with/[\p{L}\p{N}_]+/gu, which collapses a run of CJK characters into a single token (e.g.飞书插件配置→ one 6-char token). The previous code only diverted CJK to the LIKE path when the token was shorter than 3 characters:So any CJK token of 3+ characters became a quoted trigram phrase, e.g.
MATCH "飞书插件配置". A quoted phrase only matches the exact contiguous substring, which rarely exists verbatim in documents — so MATCH returned 0 rows.The LIKE fallback that could have rescued this only fired inside the
catchblock, i.e. when MATCH threw. An empty-but-successful MATCH skipped it entirely, leaving the keyword result set empty →textScore = 0for the whole hybrid result.I verified the mechanism directly against the real
node:sqliteFTS5 trigram engine (no mocks):Fix
splitCjkToken). Contiguous non-CJK runs are kept whole, soAPI配置→["API", "配", "置"]rather than letter-by-letter, preserving precision for embedded ASCII.runLikeFallbackhelper so both paths use the same correctly-decomposed term set (this also drops the old over-strict whole-CJK-token LIKE term from the exception path).CJK keyword hits now surface through the LIKE path with
textScore = 1(no BM25 rank available), exactly like the pre-existing short-CJK and MATCH-exception fallbacks.Before / After
trigramtokenizer)飞书插件配置如何配置飞书插件textScore 0textScore 1東京駅周辺東京の駅の周辺を歩くtextScore 0textScore 1id 配置(sub-trigram latin + CJK)id配置文档说明textScore 1API飞书查看API文档和飞书设置textScore 0textScore 1feishu 配置(latin ≥3 + CJK)feishu 配置文档说明Minimal reproduction
如何配置飞书插件.memory_searchwith query飞书插件配置.textScore: 0. After: keyword component contributestextScore: 1.Tests
Added a
CJK FTS textScore regression coverage (issue #92061)block tomanager-search.test.ts(runs under the existingitWithTrigramFtsguard, skipped where trigram FTS5 is unavailable):textScore = 1instead of0(core repro)id+ CJK), isolating Bug 2API飞书)The first five fail against
main(empty result set /textScore 0) and pass with the fix; I confirmed both directions by replaying the exact regex/SQL logic againstnode:sqliteFTS5 trigram.Architecture note
memory-coreruns lexical (FTS5) and vector (sqlite-vec) search independently and blends them in the hybrid scorer. The trigram tokenizer is the recommended option for CJK precisely becauseunicode61word-segmentation does not apply to scriptio-continua languages — but trigram needs ≥3 characters to form a token and a quoted phrase demands contiguity. The robust lexical primitive for CJK is therefore per-character substring (LIKE) matching, which this change makes the consistent path for all CJK tokens while keeping BM25-ranked MATCH for trigram-eligible Latin/numeric terms. The empty-MATCH fallback closes the remaining gap for mixed queries so the keyword channel never silently collapses to zero.Real behavior proof
This change is in a path that requires a live OpenClaw instance with an embedding provider (the issue used Ollama
qwen3-embedding:4b) plus a populated CJK memory index to exercise end-to-end. That hardware/provider setup is not available in my environment, so I proved the actual failing mechanism at the layer where the bug lives — the real SQLite FTS5 trigram engine thatmemory-coredrives:I also replayed
planKeywordSearch+searchKeyword's exact SQL/regex againstnode:sqlitefor all six test scenarios: the five regression cases return empty undermainand the correct hit under the patch.Since a full hybrid
memory_searchlive run with an embedding provider is not reproducible here, I'm requesting maintainers applyproof: overridefor the live-instance portion of the proof gate. Happy to run any additional maintainer-specified validation. The branch is pushed to a maintainer-pushable branch with edits enabled.🤖 Generated with Claude Code