Skip to content

Commit e45d62b

Browse files
steipetelsdcc01
andcommitted
fix(memory): preserve BM25 relevance ordering (#33757, thanks @lsdcc01)
Land #33757 by @lsdcc01 without the unrelated dependency bump. Preserve negative FTS5 BM25 ordering in hybrid scoring and add changelog coverage for #5767. Co-authored-by: 丁春才0668000523 <[email protected]>
1 parent 99de651 commit e45d62b

3 files changed

Lines changed: 21 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai
4040
### Fixes
4141

4242
- Security/Config: fail closed when `loadConfig()` hits validation or read errors so invalid configs cannot silently fall back to permissive runtime defaults. (#9040) Thanks @joetomasone.
43+
- Memory/Hybrid search: preserve negative FTS5 BM25 relevance ordering in `bm25RankToScore()` so stronger keyword matches rank above weaker ones instead of collapsing or reversing scores. (#33757) Thanks @lsdcc01.
4344
- LINE/`requireMention` group gating: align inbound and reply-stage LINE group policy resolution across raw, `group:`, and `room:` keys (including account-scoped group config), preserve plugin-backed reply-stage fallback behavior, and add regression coverage for prefixed-only group/room config plus reply-stage policy resolution. (#35847) Thanks @kirisame-wang.
4445
- Onboarding/local setup: default unset local `tools.profile` to `coding` instead of `messaging`, restoring file/runtime tools for fresh local installs while preserving explicit user-set profiles. (from #38241, overlap with #34958) Thanks @cgdusek.
4546
- Gateway/Telegram stale-socket restart guard: only apply stale-socket restarts to channels that publish event-liveness timestamps, preventing Telegram providers from being misclassified as stale solely due to long uptime and avoiding restart/pairing storms after upgrade. (openclaw#38464)

src/memory/hybrid.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,18 @@ describe("memory hybrid helpers", () => {
1414
expect(bm25RankToScore(0)).toBeCloseTo(1);
1515
expect(bm25RankToScore(1)).toBeCloseTo(0.5);
1616
expect(bm25RankToScore(10)).toBeLessThan(bm25RankToScore(1));
17-
expect(bm25RankToScore(-100)).toBeCloseTo(1);
17+
expect(bm25RankToScore(-100)).toBeCloseTo(1, 1);
18+
});
19+
20+
it("bm25RankToScore preserves FTS5 BM25 relevance ordering", () => {
21+
const strongest = bm25RankToScore(-4.2);
22+
const middle = bm25RankToScore(-2.1);
23+
const weakest = bm25RankToScore(-0.5);
24+
25+
expect(strongest).toBeGreaterThan(middle);
26+
expect(middle).toBeGreaterThan(weakest);
27+
expect(strongest).not.toBe(middle);
28+
expect(middle).not.toBe(weakest);
1829
});
1930

2031
it("mergeHybridResults unions by id and combines weighted scores", async () => {

src/memory/hybrid.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,14 @@ export function buildFtsQuery(raw: string): string | null {
4444
}
4545

4646
export function bm25RankToScore(rank: number): number {
47-
const normalized = Number.isFinite(rank) ? Math.max(0, rank) : 999;
48-
return 1 / (1 + normalized);
47+
if (!Number.isFinite(rank)) {
48+
return 1 / (1 + 999);
49+
}
50+
if (rank < 0) {
51+
const relevance = -rank;
52+
return relevance / (1 + relevance);
53+
}
54+
return 1 / (1 + rank);
4955
}
5056

5157
export async function mergeHybridResults(params: {

0 commit comments

Comments
 (0)