79 lines
2.8 KiB
JavaScript
79 lines
2.8 KiB
JavaScript
|
|
const CJK_QUERY_STOP_WORDS = new Set([
|
|||
|
|
'之前', '什么', '哪个', '多少', '记得', '记住', '有没有', '是否', '我的', '你是',
|
|||
|
|
'我们', '一下', '可以', '这个', '那个', '请问', '告诉', '说过', '提到', '聊过',
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
function extractQueryTokens(query) {
|
|||
|
|
const q = String(query ?? '').trim();
|
|||
|
|
if (!q) return [];
|
|||
|
|
const tokens = new Set();
|
|||
|
|
for (const part of q.replace(/[^\p{L}\p{N}]+/gu, ' ').split(/\s+/)) {
|
|||
|
|
const token = part.trim();
|
|||
|
|
if (token.length >= 2 && !CJK_QUERY_STOP_WORDS.has(token)) tokens.add(token);
|
|||
|
|
}
|
|||
|
|
for (const run of q.match(/[\p{Script=Han}]{2,}/gu) ?? []) {
|
|||
|
|
if (run.length <= 6 && !CJK_QUERY_STOP_WORDS.has(run)) tokens.add(run);
|
|||
|
|
for (let i = 0; i < run.length - 1; i += 1) {
|
|||
|
|
const bigram = run.slice(i, i + 2);
|
|||
|
|
if (!CJK_QUERY_STOP_WORDS.has(bigram)) tokens.add(bigram);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return [...tokens];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function scoreMemoryForRecall(text, tokens) {
|
|||
|
|
let score = 0;
|
|||
|
|
for (const token of tokens) {
|
|||
|
|
if (text.includes(token)) score += 1;
|
|||
|
|
}
|
|||
|
|
if (/别名.{0,8}是/u.test(text)) score += 5;
|
|||
|
|
if (/[??]\s*$/.test(text)) score -= 3;
|
|||
|
|
if (/^用户记住/u.test(text)) score -= 2;
|
|||
|
|
return score;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function filterMemoriesByQuery(memories, query) {
|
|||
|
|
const items = Array.isArray(memories) ? memories : [];
|
|||
|
|
const tokens = extractQueryTokens(query);
|
|||
|
|
if (!tokens.length || !items.length) return items.slice(0, Math.min(items.length, 5));
|
|||
|
|
const matched = items
|
|||
|
|
.map((item) => {
|
|||
|
|
const text = String(item?.text ?? item?.memory_text ?? '').trim();
|
|||
|
|
return { item, text, score: scoreMemoryForRecall(text, tokens) };
|
|||
|
|
})
|
|||
|
|
.filter(({ text, score }) => text && score > 0)
|
|||
|
|
.sort((a, b) => b.score - a.score)
|
|||
|
|
.map(({ item }) => item);
|
|||
|
|
return matched.length ? matched.slice(0, 5) : items.slice(0, 5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function resolveMemoriesWithLegacyFallback({
|
|||
|
|
memoryV2 = null,
|
|||
|
|
conversationMemoryService = null,
|
|||
|
|
userId,
|
|||
|
|
sessionId = null,
|
|||
|
|
query = null,
|
|||
|
|
limit = 20,
|
|||
|
|
recallQuestion = false,
|
|||
|
|
} = {}) {
|
|||
|
|
if (!userId || limit <= 0) return [];
|
|||
|
|
if (recallQuestion && conversationMemoryService?.listMemories) {
|
|||
|
|
const legacy = await conversationMemoryService.listMemories(userId, { limit }).catch(() => []);
|
|||
|
|
const filtered = filterMemoriesByQuery(legacy, query);
|
|||
|
|
if (filtered.length) return filtered;
|
|||
|
|
}
|
|||
|
|
if (memoryV2?.resolve) {
|
|||
|
|
const resolved = await memoryV2.resolve({
|
|||
|
|
userId,
|
|||
|
|
sessionId,
|
|||
|
|
query,
|
|||
|
|
limit,
|
|||
|
|
}).catch(() => null);
|
|||
|
|
const memories = Array.isArray(resolved?.memories) ? resolved.memories : [];
|
|||
|
|
if (memories.length) return memories;
|
|||
|
|
}
|
|||
|
|
if (!conversationMemoryService?.listMemories) return [];
|
|||
|
|
const legacy = await conversationMemoryService.listMemories(userId, { limit }).catch(() => []);
|
|||
|
|
return filterMemoriesByQuery(legacy, query);
|
|||
|
|
}
|