Files
BaiLongma/src/long-term-memory.js
2026-05-25 12:21:05 +08:00

157 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Long Term Memory - 长期记忆优化
// 重要记忆持久化到本地文件 + 定期整理归纳
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
const MEMORY_DIR = './memory'
const USER_PROFILE_PATH = './memory/user-profile.json'
const CONVERSATION_SUMMARY_PATH = './memory/conversation-summaries.json'
const IMPORTANT_FACTS_PATH = './memory/important-facts.json'
function ensureDir() {
if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true })
}
function loadJSON(path, fallback) {
ensureDir()
if (!existsSync(path)) return fallback
try {
return JSON.parse(readFileSync(path, 'utf-8'))
} catch (e) {
return fallback
}
}
function saveJSON(path, data) {
ensureDir()
writeFileSync(path, JSON.stringify(data, null, 2))
}
// ═══ 用户画像 ═══
export function loadUserProfile() {
return loadJSON(USER_PROFILE_PATH, {
name: '',
preferences: {},
topics: [],
communicationStyle: '',
lastUpdated: Date.now()
})
}
export function updateUserProfile(updates) {
const profile = loadUserProfile()
Object.assign(profile, updates, { lastUpdated: Date.now() })
saveJSON(USER_PROFILE_PATH, profile)
console.log('[LongTermMemory] User profile updated')
}
// ═══ 重要事实 ═══
export function addImportantFact({ category, content, confidence = 0.8 }) {
const facts = loadJSON(IMPORTANT_FACTS_PATH, { facts: [] })
// 检查重复
const existing = facts.facts.findIndex(f =>
f.content.toLowerCase() === content.toLowerCase()
)
if (existing >= 0) {
facts.facts[existing].confidence = confidence
facts.facts[existing].updatedAt = Date.now()
facts.facts[existing].accessCount = (facts.facts[existing].accessCount || 0) + 1
} else {
facts.facts.unshift({
id: Date.now().toString(36),
category,
content: content.slice(0, 500),
confidence,
addedAt: Date.now(),
updatedAt: Date.now(),
accessCount: 0
})
if (facts.facts.length > 100) {
facts.facts = facts.facts.slice(0, 100)
}
}
saveJSON(IMPORTANT_FACTS_PATH, facts)
}
export function getImportantFacts(category = '', limit = 10) {
const facts = loadJSON(IMPORTANT_FACTS_PATH, { facts: [] })
let filtered = facts.facts
if (category) {
filtered = filtered.filter(f => f.category === category)
}
return filtered.slice(0, limit)
}
// ═══ 对话摘要 ═══
export function saveConversationSummary({ sessionId, summary, keyPoints = [], duration = 0 }) {
const summaries = loadJSON(CONVERSATION_SUMMARY_PATH, { summaries: [] })
summaries.summaries.unshift({
id: sessionId || Date.now().toString(36),
summary: summary.slice(0, 1000),
keyPoints: keyPoints.slice(0, 10),
duration,
createdAt: Date.now()
})
// 只保留最近30条
if (summaries.summaries.length > 30) {
summaries.summaries = summaries.summaries.slice(0, 30)
}
saveJSON(CONVERSATION_SUMMARY_PATH, summaries)
}
export function getRecentSummaries(limit = 5) {
const summaries = loadJSON(CONVERSATION_SUMMARY_PATH, { summaries: [] })
return summaries.summaries.slice(0, limit)
}
// ═══ 生成记忆提示词 ═══
export function buildMemoryPrompt() {
const profile = loadUserProfile()
const facts = getImportantFacts('', 5)
const summaries = getRecentSummaries(3)
let prompt = ''
// 用户画像
if (profile.name || (profile.topics && profile.topics.length > 0)) {
prompt += '\n## User Profile\n'
if (profile.name) prompt += `- 用户称呼:${profile.name}\n`
if (profile.communicationStyle) prompt += `- 沟通风格偏好:${profile.communicationStyle}\n`
if (profile.topics && profile.topics.length > 0) {
prompt += `- 关注话题:${profile.topics.join('、')}\n`
}
const prefs = Object.entries(profile.preferences || {})
if (prefs.length > 0) {
prompt += `- 偏好设置:${prefs.map(([k, v]) => `${k}=${v}`).join('')}\n`
}
}
// 重要事实
if (facts.length > 0) {
prompt += '\n## Important Facts\n'
facts.forEach(f => {
prompt += `- [${f.category}] ${f.content}\n`
})
}
// 最近对话摘要
if (summaries.length > 0) {
prompt += '\n## Recent Conversations\n'
summaries.forEach(s => {
const date = new Date(s.createdAt).toLocaleDateString('zh-CN')
prompt += `- [${date}] ${s.summary.slice(0, 100)}\n`
})
}
return prompt ? `\n${prompt}` : ''
}