From e34b78b4f1bc7aac17dc277203cdcadaa16e8a3c Mon Sep 17 00:00:00 2001 From: GautamSharma99 Date: Sat, 11 Jul 2026 21:57:22 +0530 Subject: [PATCH] fix: preserve strong BM25-only matches --- .../graph-context/graph-rank.ts | 1 + scripts/check-bm25-tokenizer.js | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/libs/knowledge-graph/graph-context/graph-rank.ts b/libs/knowledge-graph/graph-context/graph-rank.ts index 15318db..8862e49 100644 --- a/libs/knowledge-graph/graph-context/graph-rank.ts +++ b/libs/knowledge-graph/graph-context/graph-rank.ts @@ -45,6 +45,7 @@ function applyPostGraphThreshold( ): RankedContextDocument[] { return ranked.filter((document) => document.signals.semantic_score >= config.ranking.semanticThreshold || + document.signals.bm25_score >= config.ranking.semanticThreshold || document.signals.graph_score >= config.ranking.semanticThreshold || document.signals.coherence_score >= config.ranking.semanticThreshold, ); diff --git a/scripts/check-bm25-tokenizer.js b/scripts/check-bm25-tokenizer.js index f880b45..645f763 100644 --- a/scripts/check-bm25-tokenizer.js +++ b/scripts/check-bm25-tokenizer.js @@ -3,6 +3,8 @@ import assert from "node:assert/strict"; const root = new URL("..", import.meta.url); const { tokenize, scoreBm25 } = await import(new URL("dist/libs/knowledge-graph/graph-context/bm25.js", root)); const { graphContextConfig } = await import(new URL("dist/libs/knowledge-graph/graph-context/config.js", root)); +const { applyGraphRanking } = await import(new URL("dist/libs/knowledge-graph/graph-context/graph-rank.js", root)); +const { rankContextDocuments } = await import(new URL("dist/libs/knowledge-graph/graph-context/rank.js", root)); assert.ok(tokenize("handleUserAuth").includes("user"), "camelCase should emit sub-token user"); assert.ok(tokenize("handleUserAuth").includes("auth"), "camelCase should emit sub-token auth"); @@ -25,4 +27,39 @@ const documents = [{ key: "doc:auth", text: "The handleUserAuth function validat const ranked = scoreBm25("user auth validation", documents, graphContextConfig); assert.equal(ranked[0]?.id, "doc:auth", "BM25 should match camelCase doc tokens to spaced query terms"); +const lexicalClaim = { + id: "claim.lexical_match", + kind: "fact", + text: "The processRareIdentifier function handles this workflow.", + truth: "unknown", + intent: "unknown", +}; +const lexicalDocument = { + key: "claim:claim.lexical_match", + type: "claim", + id: lexicalClaim.id, + text: lexicalClaim.text, + object: lexicalClaim, + about: [], +}; +const lexicalScores = scoreBm25("process rare identifier", [lexicalDocument], graphContextConfig); +const baseRanked = rankContextDocuments( + [lexicalDocument], + [{ id: lexicalDocument.key, score: 0, raw_score: 0, rank: 1 }], + lexicalScores, + graphContextConfig, +); +const graphRanked = applyGraphRanking( + { claims: baseRanked, components: [], flows: [] }, + { claims: [lexicalClaim], components: [], flows: [], sources: [], edges: [] }, + graphContextConfig, +); +assert.equal(baseRanked[0]?.signals.bm25_score, 1, "lexical match should have the strongest normalized BM25 score"); +assert.equal(baseRanked[0]?.signals.semantic_score, 0, "regression fixture must not rely on semantic similarity"); +assert.equal( + graphRanked.claims[0]?.document.id, + lexicalClaim.id, + "post-graph filtering should preserve a strong BM25-only match", +); + console.log("BM25 tokenizer checks passed.");