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:
+62
-12
@@ -17,6 +17,7 @@ import {
|
||||
import { filterMemoriesByQuery } from './memory-legacy-fallback.mjs';
|
||||
import { matchDirectChatFaqRule } from './chat-intent-router-rules.mjs';
|
||||
import { isGoalRunIntent } from './goal-run-intent.mjs';
|
||||
import { pgvectorMemoryBackendInternals } from './memory-v2-pgvector.mjs';
|
||||
|
||||
export { matchDirectChatFaqRule, DIRECT_CHAT_FAQ_RULES, FAQ_EXCLUSION_PATTERNS } from './chat-intent-router-rules.mjs';
|
||||
|
||||
@@ -207,6 +208,61 @@ export function isMemoryRecallQuestion(text) {
|
||||
return MEMORY_RECALL_PATTERNS.some((pattern) => pattern.test(normalized));
|
||||
}
|
||||
|
||||
function memoryItemText(item) {
|
||||
return String(item?.text ?? item?.memory_text ?? item?.content ?? '').trim();
|
||||
}
|
||||
|
||||
function isEpisodicMemoryItem(item) {
|
||||
if (!item || typeof item !== 'object') return false;
|
||||
if (item.source === 'episodic' || item.source === 'episodic-index') return true;
|
||||
if (item.sessionId != null && String(item.sessionId).trim()) return true;
|
||||
return String(item.label ?? '').trim() === '历史会话';
|
||||
}
|
||||
|
||||
export function scoreAgentMemoryCandidate(item, query) {
|
||||
const text = memoryItemText(item);
|
||||
if (!text) return 0;
|
||||
const lexical = pgvectorMemoryBackendInternals.lexicalQueryCoverage(query, text);
|
||||
let keywordBoost = 0;
|
||||
for (const term of pgvectorMemoryBackendInternals.extractKeywordTerms(query)) {
|
||||
if (text.includes(term)) keywordBoost += term.length;
|
||||
}
|
||||
let score = (lexical * 100) + keywordBoost;
|
||||
if (isEpisodicMemoryItem(item) && score > 0) score += 5;
|
||||
return score;
|
||||
}
|
||||
|
||||
export function mergeAgentMemoryCandidates({
|
||||
personalMemories = [],
|
||||
episodicMemories = [],
|
||||
query = '',
|
||||
limit = 3,
|
||||
} = {}) {
|
||||
const ranked = [
|
||||
...(Array.isArray(episodicMemories) ? episodicMemories : []),
|
||||
...(Array.isArray(personalMemories) ? personalMemories : []),
|
||||
]
|
||||
.map((item, index) => ({
|
||||
item,
|
||||
index,
|
||||
score: scoreAgentMemoryCandidate(item, query),
|
||||
}))
|
||||
.sort((left, right) => {
|
||||
if (right.score !== left.score) return right.score - left.score;
|
||||
return left.index - right.index;
|
||||
});
|
||||
const memories = [];
|
||||
const seen = new Set();
|
||||
for (const { item } of ranked) {
|
||||
const key = String(item?.id ?? item?.sessionId ?? memoryItemText(item)).trim();
|
||||
if (!key || seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
memories.push(item);
|
||||
if (memories.length >= limit) break;
|
||||
}
|
||||
return memories;
|
||||
}
|
||||
|
||||
export function isAgentSessionContinueText(text) {
|
||||
const normalized = String(text ?? '').trim();
|
||||
if (!normalized) return false;
|
||||
@@ -1338,18 +1394,12 @@ export function createChatIntentRouter(options = {}) {
|
||||
agentMemoryPolicy.timeoutMs,
|
||||
'Memory V2 agent resolve',
|
||||
);
|
||||
const memories = [];
|
||||
const seen = new Set();
|
||||
for (const item of [
|
||||
...(Array.isArray(episodicResolved?.memories) ? episodicResolved.memories : []),
|
||||
...(Array.isArray(personalResolved?.memories) ? personalResolved.memories : []),
|
||||
]) {
|
||||
const key = String(item?.id ?? item?.sessionId ?? item?.text ?? item?.memory_text ?? '').trim();
|
||||
if (!key || seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
memories.push(item);
|
||||
if (memories.length >= limit) break;
|
||||
}
|
||||
const memories = mergeAgentMemoryCandidates({
|
||||
personalMemories: personalResolved?.memories,
|
||||
episodicMemories: episodicResolved?.memories,
|
||||
query: text,
|
||||
limit,
|
||||
});
|
||||
const degraded = personalFailed
|
||||
|| episodicFailed
|
||||
|| Boolean(personalResolved?.degraded)
|
||||
|
||||
Reference in New Issue
Block a user