fix(search): OR+prefix FTS query + title-weighted bm25 to fix natural-language recall#465
Closed
omarhash95 wants to merge 1 commit into
Closed
fix(search): OR+prefix FTS query + title-weighted bm25 to fix natural-language recall#465omarhash95 wants to merge 1 commit into
omarhash95 wants to merge 1 commit into
Conversation
sanitizeFTS joined quoted terms with spaces = FTS5 implicit-AND, so any natural-language / paraphrased / multi-term query needed EVERY term to match and returned nothing even when the answer was stored verbatim (measured 0% recall on a LoCoMo slice and on real project memory). - sanitizeFTS now emits `"t1"* OR "t2"* OR ...` — prefix (mel→melanie) + OR (any term) restores recall; internal quotes escaped. - Search() orders by weighted bm25 (title 10x, topic_key 8x, content 2x) so the OR expansion still surfaces the most on-topic observation first. Measured: natural-language queries that returned "No memories found" on v1.16.1 now return the correct observation (exact doc at Gentleman-Programming#1 in the majority of cases); precise keyword queries still rank tightly via bm25. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR tunes SQLite FTS5 search to improve recall and ranking quality by expanding the MATCH expression and applying weighted BM25 ordering.
Changes:
- Replace
ORDER BY fts.rankwith a weightedbm25(...)ranking to prioritize title/topic matches. - Change
sanitizeFTSto generate an OR-joined, prefix (*) MATCH query for higher recall. - Expand inline documentation explaining the new FTS tuning rationale.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // most on-topic observation first instead of any doc that merely shares a | ||
| // common term. Column order matches the FTS5 schema: | ||
| // title, content, tool_name, type, project, topic_key. (regtime/fts-tuning) | ||
| sqlQ += " ORDER BY bm25(observations_fts, 10.0, 2.0, 1.0, 1.0, 1.0, 8.0) LIMIT ?" |
Comment on lines
+6238
to
+6239
| // LoCoMo slice). OR restores recall; the bm25 `ORDER BY fts.rank` in Search() | ||
| // still ranks documents matching more (and rarer) terms on top, so precision |
Comment on lines
+6251
to
+6254
| w = strings.ReplaceAll(w, `"`, `""`) | ||
| terms = append(terms, `"`+w+`"*`) | ||
| } | ||
| return strings.Join(words, " ") | ||
| return strings.Join(terms, " OR ") |
Collaborator
|
Closing this because the search-recall problem is already being handled through #352 and #475 with a safer |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Search()runsmem_searchthroughsanitizeFTS, which quotes each term and joins them with spaces — i.e. FTS5 implicit AND. Every term must match, so any natural-language / paraphrased / multi-term query returns nothing even when the answer is stored verbatim. Examples that returnedNo memories foundon v1.16.1 against a populated DB:"how do I permanently remove a saved memory from engram""should we adopt X for better memory recall""How long have Mel ..."(where the stored text saysMelanie)The longer / more natural the query, the more certain the miss. (I measured ~0% recall on a multi-question retrieval slice with raw questions; short keyword queries worked, natural questions did not.)
Change
Two small, localized edits in
internal/store/store.go:sanitizeFTSnow emits"t1"* OR "t2"* OR ...instead of space-joined AND:ORrestores recall (any term may match),*) makesmelmatchmelanie,deletmatchdeleted,"").Search()ORDER BY uses weightedbm25(observations_fts, 10,2,1,1,1,8)(title 10×, topic_key 8×, content 2×) so the OR expansion still surfaces the most on-topic observation first rather than any doc that merely shares a common term.Existing
internal/storetests pass.Result (same DB, before → after)
Precise keyword queries still rank tightly because bm25 orders multi-term matches highest.
Tradeoff + happy to adjust
Defaulting to
ORfavors recall over precision, which is a behavior change. If you'd prefer to preserve strict-AND precision, two options I'm glad to implement instead:I can also add unit tests for
sanitizeFTSand aSearchrecall test if you'd like. Let me know which direction you prefer.