Skip to content

fix(rag): rank keyword candidates in SQL before the LIMIT truncates them - #523

Merged
pushkarverma3698 merged 1 commit into
betafrom
fix/keyword-rag-ranking
Aug 20, 2026
Merged

fix(rag): rank keyword candidates in SQL before the LIMIT truncates them#523
pushkarverma3698 merged 1 commit into
betafrom
fix/keyword-rag-ranking

Conversation

@pushkarverma3698

Copy link
Copy Markdown
Owner

Fixes the 0.0% keyword recall measured by #515 (AG-010). #515 measured the defect; this fixes it.

The bug

keywordSearchRagTable ORed the per-term ILIKE patterns, then applied LIMIT 20 with no ORDER BY. Postgres was free to return any 20 matching rows. rankByTerms then ranked that arbitrary sample precisely.

The comment said // over-fetch, then rank precisely. It did neither.

Nothing errored and nothing came back empty, so from the outside a dead keyword lane and an unhelpful one were indistinguishable — for weeks.

Failing test first

tests/unit/db/keyword-rag-ranking.test.ts captures the SQL the function actually sends and asserts the ranking happens before the truncation. Before the fix it captured, verbatim:

SELECT content, metadata
FROM brain.turicks_brain
WHERE content ILIKE $1 OR content ILIKE $2 OR content ILIKE $3 OR content ILIKE $4
LIMIT $5

3 of 4 assertions failed. All 4 pass after.

Measured on the prod corpus, not asserted

Golden case adr-langgraph — 7 significant terms, 256 of 478 chunks match at least one:

candidates target-doc chunks in pool term overlap
before 20 0 1–2 of 7
after 20 3 2–6 of 7

The target document was never in the pool. recall@5 = 0% was structurally guaranteed, not a ranking weakness.

The fix

Build the SQL mirror of scoreByTerms — one CASE WHEN content ILIKE … THEN 1 ELSE 0 END per term, summed as match_count — then ORDER BY match_count DESC, content before LIMIT. The second sort key keeps ties deterministic (rule #16: retrieval is reproducible, two identical runs cannot disagree). rankByTerms still refines the top candidates in JS, and now receives the right ones.

Test plan

  • pnpm gate298 files / 3198 tests green
  • pnpm verify:arch — all six gates = baseline
  • pnpm lint — exit 0
  • Before/after measured against the live prod corpus (above)
  • NOT VERIFIED: full pnpm eval:retrieval recall@5/MRR re-run. It needs a local corpus + Ollama; this machine has no local Postgres up. The numbers to beat are hybrid 83.8% / MRR 0.674 vs vector-only 83.8% / 0.761 — fusion is currently negative-value because one input is noise. Re-run before judging RRF.

keywordSearchRagTable ORed the per-term ILIKE patterns and then applied
LIMIT 20 with no ORDER BY, so Postgres could return any 20 of the matching
rows. rankByTerms then ranked that arbitrary sample precisely. The comment
claimed "over-fetch, then rank precisely" and did neither.

Measured on the prod corpus for golden case adr-langgraph (7 terms, 256 of
478 chunks match at least one):

  before  20 candidates, 0 chunks of the target doc, overlap 1-2 of 7
  after   20 candidates, 3 chunks of the target doc, overlap 2-6 of 7

The target was never in the candidate pool, which is why keyword-only
recall@5 measured 0.0% on 37/37 golden queries with no error and no empty
result to make it visible.

Fix: build the SQL mirror of scoreByTerms (one CASE arm per term, summed),
ORDER BY match_count DESC, content — the second key keeps ties deterministic
per rule #16 — then LIMIT. rankByTerms still refines the top candidates.

Gate: 298 files / 3198 tests green; all six architecture gates at baseline.
@pushkarverma3698
pushkarverma3698 force-pushed the fix/keyword-rag-ranking branch from d5dac40 to 43654c8 Compare August 20, 2026 06:39
@pushkarverma3698
pushkarverma3698 merged commit 879d55b into beta Aug 20, 2026
6 checks passed
@pushkarverma3698
pushkarverma3698 deleted the fix/keyword-rag-ranking branch August 20, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant