fix(memory-v2): improve pgvector recall and WeChat memory injection

Add keyword fallback and dedupe for pgvector resolve, score personal vs episodic memories by relevance, record WeChat recall product events, and disable broken chatrecall on Postgres session storage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-01 22:26:47 +08:00
parent dac06753ce
commit dcf83e9b6f
8 changed files with 286 additions and 16 deletions
+54 -1
View File
@@ -86,11 +86,12 @@ test('pgvector backend performs parameterized vector lookup when explicitly enab
limit: 5,
});
assert.equal(queries.length, 1);
assert.equal(queries.length, 2);
assert.match(queries[0].sql, /FROM memory_embeddings/);
assert.match(queries[0].sql, /WITH vector_candidates/);
assert.match(queries[0].sql, /recent_candidates/);
assert.deepEqual(queries[0].params, ['user-1', '[0.25,0.5,0.75]', 50]);
assert.match(queries[1].sql, /ILIKE/);
assert.deepEqual(result.semanticMemories, ['用户关注 Memory V2 的 facade 边界']);
assert.deepEqual(result.memories, [
{
@@ -161,6 +162,58 @@ test('pgvector hybrid ranking keeps vector order when query has no lexical overl
assert.equal(ranked[1].id, '1');
});
test('pgvector keyword fallback retrieves topic memories missed by vector top-k', async () => {
const queries = [];
const backend = createPgvectorMemoryBackend({
enabled: true,
pool: {
async query(sql, params) {
queries.push({ sql, params });
if (String(sql).includes('ILIKE')) {
return {
rows: [{
id: 901,
content: '用户对日本战国人物德川家康感兴趣,并希望继续深入讨论',
type: 'interest',
score: 1,
created_at: '2026-08-01T13:57:00.000Z',
updated_at: '2026-08-01T13:57:00.000Z',
}],
};
}
return {
rows: [{
id: 1,
content: '用户以后只要说“帮我搜索今天国际国内热门新闻和小知识,做成页面”',
type: 'preference',
score: 0.91,
created_at: '2026-07-31T00:00:00.000Z',
updated_at: '2026-07-31T00:00:00.000Z',
}],
};
},
},
embedQuery: async () => [0.25, 0.5, 0.75],
});
const result = await backend.resolve({
userId: 'user-tang',
query: '我们继续聊聊德川家康',
limit: 2,
});
assert.match(result.memories[0].text, /德川家康/);
assert.equal(queries.some((entry) => String(entry.sql).includes('ILIKE')), true);
});
test('extractKeywordTerms keeps topic phrases and drops recall boilerplate', () => {
const terms = pgvectorMemoryBackendInternals.extractKeywordTerms('我们之前有聊过,你记得吗');
assert.equal(terms.includes('记得'), false);
assert.equal(terms.includes('我们'), false);
const topicTerms = pgvectorMemoryBackendInternals.extractKeywordTerms('我们继续聊聊德川家康');
assert.equal(topicTerms.some((term) => term.includes('德川')), true);
});
test('pgvector backend validates table names before building SQL', () => {
assert.throws(
() => createPgvectorMemoryBackend({ tableName: 'memory_embeddings;DROP TABLE users' }),