feat(memory-core): expose hybrid search component scores

Expose raw `vectorScore` and `textScore` alongside the combined hybrid memory search `score`.

- Preserve vector/text component scores from `mergeHybridResults` output.
- Add optional component-score fields to both memory host SDK type surfaces.
- Extend hybrid merge tests for vector-only, text-only, and overlapping result cases.
- Document that component scores remain raw retrieval diagnostics while temporal decay/MMR only adjust or reorder the combined ranking `score`.

Closes #68166.

Maintainer verification:
- `pnpm test extensions/memory-core/src/memory/hybrid.test.ts`
- `pnpm check:changed`
- Fresh GitHub checks passed.

Co-authored-by: Alex Fries <alex@engramlabs.io>
This commit is contained in:
Alex Fries
2026-04-25 02:46:03 -04:00
committed by GitHub
parent a983ea61ac
commit 66e66f19c6
4 changed files with 16 additions and 0 deletions

View File

@@ -60,7 +60,11 @@ describe("memory hybrid helpers", () => {
const a = merged.find((r) => r.path === "memory/a.md");
const b = merged.find((r) => r.path === "memory/b.md");
expect(a?.score).toBeCloseTo(0.7 * 0.9);
expect(a?.vectorScore).toBeCloseTo(0.9);
expect(a?.textScore).toBe(0);
expect(b?.score).toBeCloseTo(0.3 * 1.0);
expect(b?.vectorScore).toBe(0);
expect(b?.textScore).toBeCloseTo(1.0);
});
it("mergeHybridResults prefers keyword snippet when ids overlap", async () => {
@@ -94,5 +98,7 @@ describe("memory hybrid helpers", () => {
expect(merged).toHaveLength(1);
expect(merged[0]?.snippet).toBe("kw-a");
expect(merged[0]?.score).toBeCloseTo(0.5 * 0.2 + 0.5 * 1.0);
expect(merged[0]?.vectorScore).toBeCloseTo(0.2);
expect(merged[0]?.textScore).toBeCloseTo(1.0);
});
});

View File

@@ -72,6 +72,8 @@ export async function mergeHybridResults(params: {
startLine: number;
endLine: number;
score: number;
vectorScore: number;
textScore: number;
snippet: string;
source: HybridSource;
}>
@@ -131,11 +133,15 @@ export async function mergeHybridResults(params: {
startLine: entry.startLine,
endLine: entry.endLine,
score,
vectorScore: entry.vectorScore,
textScore: entry.textScore,
snippet: entry.snippet,
source: entry.source,
};
});
// Keep component scores as raw retrieval diagnostics; temporal decay and MMR
// only adjust or reorder the combined ranking score.
const temporalDecayConfig = { ...DEFAULT_TEMPORAL_DECAY_CONFIG, ...params.temporalDecay };
const decayed = await applyTemporalDecayToHybridResults({
results: merged,

View File

@@ -5,6 +5,8 @@ export type MemorySearchResult = {
startLine: number;
endLine: number;
score: number;
vectorScore?: number;
textScore?: number;
snippet: string;
source: MemorySource;
citation?: string;

View File

@@ -5,6 +5,8 @@ export type MemorySearchResult = {
startLine: number;
endLine: number;
score: number;
vectorScore?: number;
textScore?: number;
snippet: string;
source: MemorySource;
citation?: string;