Improve MemFuse recall via hybrid ranking and candidate generation.

Add RRF fusion with English word-level lexical scoring, tiered keyword fetch, and vector margin expansion (0.15/200) to fix pre-rank truncation; wire DashScope embedding bench path and update baseline to 28.8% recall@20.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-02 13:41:12 +08:00
parent b923e54eff
commit fb3a442e73
11 changed files with 1436 additions and 97 deletions
+30
View File
@@ -25,6 +25,7 @@ import {
summarizeMemFuseBench,
} from '../memory-v2-memfuse-bench.mjs';
import { resolveEmbeddingModuleSpecifier } from '../memory-v2-recall-benchmark.mjs';
import { loadMemindEnvFiles } from './memind-runtime-profile.mjs';
function parseArgs(argv) {
const options = {
@@ -102,6 +103,12 @@ async function loadEmbedder(specifier) {
if (typeof embed !== 'function') {
throw new Error(`Embedding module must export embedText, embedQuery or default: ${specifier}`);
}
if (typeof imported.prefetchEmbedTexts === 'function') {
embed.prefetchEmbedTexts = imported.prefetchEmbedTexts.bind(imported);
}
if (typeof imported.flushEmbeddingCache === 'function') {
embed.flushEmbeddingCache = imported.flushEmbeddingCache.bind(imported);
}
return embed;
}
@@ -168,6 +175,7 @@ function printReport(report) {
}
async function main() {
loadMemindEnvFiles(process.cwd());
const options = parseArgs(process.argv.slice(2));
if (options.help) {
usage();
@@ -195,6 +203,27 @@ async function main() {
const embedText = options.embeddingModule
? await loadEmbedder(options.embeddingModule)
: null;
const prefetchEmbedTexts = embedText?.prefetchEmbedTexts ?? null;
if (options.embeddingModule) {
const resolved = resolveEmbeddingModuleSpecifier(options.embeddingModule);
const imported = await import(resolved);
if (typeof imported.probeEmbeddingAvailability === 'function') {
const probe = await imported.probeEmbeddingAvailability();
if (!probe.available) {
process.stderr.write(
`Embedding probe failed (${probe.reason}).\n` +
'Set MEMIND_EMBEDDING_API_KEY / OPENAI_API_KEY, or MEMIND_EMBEDDING_PROVIDER=ollama.\n',
);
return options.strict ? 1 : 0;
}
if (!options.quiet) {
process.stderr.write(
`embedding probe ok: provider=${probe.provider} model=${probe.model} dims=${probe.dimensions}\n`,
);
}
}
}
let lastScenario = null;
const report = await runMemFuseBench({
@@ -207,6 +236,7 @@ async function main() {
candidateLimit: options.candidateLimit,
includeSourceTags: options.sourceTags,
embedText,
prefetchEmbedTexts,
onProgress: options.quiet
? null
: (event) => {