Improve pgvector keyword recall ranking without changing hybrid sort.

Rank ILIKE matches by lexical coverage instead of recency and attach lexical scores to keyword rows so MemFuseBench candidate recall rises sharply while Chinese hybrid ordering stays unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-02 10:01:42 +08:00
parent 0b6f79ae16
commit 2c0d903ecf
3 changed files with 89 additions and 6 deletions
+20 -3
View File
@@ -85,6 +85,8 @@ function dedupeContentPrefix(text, length = 96) {
return normalizeSearchText(String(text ?? '').slice(0, length));
}
const KEYWORD_FETCH_CAP = 500;
async function fetchKeywordCandidates(pool, {
userId,
query,
@@ -96,17 +98,32 @@ async function fetchKeywordCandidates(pool, {
if (!terms.length) return [];
const clauses = terms.map((_term, index) => `content ILIKE $${index + 2}`);
const params = [userId, ...terms.map((term) => `%${term}%`)];
const safeLimit = Math.max(1, Math.min(50, Number(limit) || 20));
const fetchCap = Math.min(KEYWORD_FETCH_CAP, Math.max(safeLimit, safeLimit * 10));
const sql = `
SELECT id, content, type, created_at, updated_at, 1.0 AS score
FROM ${tableName}
WHERE user_id = $1
AND (${clauses.join(' OR ')})
ORDER BY updated_at DESC
LIMIT $${params.length + 1}
`;
params.push(Math.max(1, Math.min(50, Number(limit) || 20)));
params.push(fetchCap);
const result = await pool.query(sql, params);
return result?.rows ?? [];
const rows = result?.rows ?? [];
return rows
.map((row) => ({
row,
lexicalScore: lexicalQueryCoverage(query, row.content ?? ''),
updatedAt: timestampValue(row.updated_at ?? row.created_at),
}))
.sort((left, right) => {
if (left.lexicalScore !== right.lexicalScore) {
return right.lexicalScore - left.lexicalScore;
}
return right.updatedAt - left.updatedAt;
})
.slice(0, safeLimit)
.map(({ row, lexicalScore }) => ({ ...row, score: lexicalScore }));
}
function timestampValue(value) {