fix(state): honor AND/NOT operators in the CJK LIKE fallback - #64559
fix(state): honor AND/NOT operators in the CJK LIKE fallback#64559solyanviktor-star wants to merge 1 commit into
Conversation
cd76275 to
a4b24dd
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tracing the short-token fallback and adding focused regression coverage. Current main still removes boolean operators and OR-joins the remaining terms in hermes_state.py:5068-5080, so the underlying bug is real.
Problems
hermes_state.py:4389does not fully match FTS5 precedence. SQLite FTS5 gives implicit AND tighter binding than NOT:报告 NOT 草稿 初版means报告 NOT (草稿 AND 初版). The proposed group emits报告 AND NOT 草稿 AND 初版instead. See SQLite FTS5 boolean operators.- The new tests at
tests/test_hermes_state.py:1688-1734cover simple NOT and AND, but not this NOT-plus-implicit-AND case.
Suggested changes
- Parse implicit-AND runs before applying NOT, then add a short-token regression for
报告 NOT 草稿 初版that distinguishes the two expressions.
Automated hermes-sweeper review.
| group_clauses = [] | ||
| for positives, negatives in groups: | ||
| parts = [_like_term(t) for t in positives] | ||
| parts += [f"NOT {_like_term(t)}" for t in negatives] |
There was a problem hiding this comment.
This bucket representation loses FTS5 precedence for 报告 NOT 草稿 初版: FTS5 treats whitespace as a tighter implicit AND (报告 NOT (草稿 AND 初版)), while this produces 报告 AND NOT 草稿 AND 初版. Parse implicit-AND runs before applying NOT and add a regression for this case.
|
Addressed in the follow-up commit. I verified the precedence against live SQLite FTS5 before reworking the parser:
The fallback now parses NOT as opening a conjunctive negated run that implicit adjacency extends and explicit AND/OR terminates, and emits each run as Tests: |
ee1517f to
682f84a
Compare
|
Rebased onto current main to resolve the conflict introduced by the Notes on the rebase:
|
|
The slice-4 CI failure ( |
682f84a to
fbe6081
Compare
The LIKE fallback OR-joined every non-operator token, so "报告 NOT 草稿"
returned exactly the drafts it was asked to exclude and AND degraded to
OR. Group tokens into OR-separated conjunctive buckets that mirror FTS5
semantics, including operator precedence verified against live FTS5:
implicit AND binds tighter than NOT ("a NOT b c" == "a NOT (b AND c)")
while explicit AND binds looser ("a NOT b AND c" == "(a NOT b) AND c").
NULL tool columns are COALESCEd inside negated terms so NOT does not
silently drop ordinary messages, and the snippet anchor is the first
positive token.
Rebased onto current main after the messages_fts_cjk bigram index
(NousResearch#65544) landed: the bigram route passes operators through to FTS5
correctly, but the LIKE fallback it retains — for DBs where the index
is absent or its backfill is pending, role='tool' queries, and lone
1-char CJK runs — still OR-joined everything. The regression tests now
set _fts_cjk_available = False so they keep exercising that path; all
six fail without the fix.
fbe6081 to
8e435f6
Compare
|
Rebased again onto current main — the read-path split (6623ee9, per-thread read-only connections) moved this query off Premise still holds on today's main: the CJK LIKE fallback continues to OR-join every non-operator token, so |
What does this PR do?
SessionDB.search_messagesroutes short-token CJK queries (any non-operator CJK token < 3 chars) to a LIKE-based fallback, because the trigram FTS5 index can't match them. That fallback joins every non-operator token with OR, regardless of the operators the query actually used:The trigram path forwards
AND/OR/NOTverbatim to FTS5, so the same query silently flips meaning depending on token length:报告 NOT 草稿("report NOT draft") — theNOTterm becomes a positive OR branch, so the search returns exactly the drafts it asked to exclude;广西 AND 桂林degrades to广西 OR 桂林and matches messages containing either term;广西 桂林, which FTS5 treats as AND) also degrades to OR.The fix groups tokens into OR-separated buckets of positive and NOT-negated terms: whitespace/
ANDjoins conjunctively within a bucket,ORsplits buckets,NOTnegates the following term. That matches FTS5 operator precedence for these flat queries (OR binds loosest), so the LIKE fallback now agrees with the trigram path. Two supporting details:COALESCEs the nullabletool_name/tool_callscolumns — without that, a negated term evaluates toNULL(= non-matching) on every row without tool metadata, andNOTwould wrongly exclude all plain chat messages;instr()snippet anchor uses the first positive term instead of the first token, so excluded terms don't drive snippet positioning.The OR behavior introduced for #20494 is preserved — its regression tests (
test_cjk_or_combined_short_tokens_returns_results,test_cjk_short_token_or_query_preserves_filters) pass unchanged.Related Issue
No existing issue — found while comparing the operator handling of the trigram path (quotes tokens, keeps operators) against the LIKE fallback right below it.
Type of Change
Changes Made
hermes_state.py: replace the flat OR-join in the LIKE fallback with OR-separated conjunctive groups honoringAND/NOT;COALESCEnullable columns inside negations; snippet anchor = first positive term.tests/test_hermes_state.py: four regression tests inTestCJKSearchFallback—NOTexcludes,ANDrequires both terms, implicit adjacency is conjunctive, andA AND B OR Cgroups as(A AND B) OR C. All four fail before the fix.How to Test
python -m pytest tests/test_hermes_state.py -q— 370 passed (366 pre-existing + 4 new).hermes_state.pyhunks and re-run: the four new tests fail (e.g.报告 NOT 草稿returns the draft message).search_messages("报告 NOT 草稿")— only the non-draft message must come back.Checklist
Code
Documentation & Housekeeping
cli-config.yaml.example— N/ACONTRIBUTING.md/AGENTS.md— N/A