guanghulab/server/ftchat/services/memory-agent.js
2026-05-10 13:12:44 +08:00

99 lines
3.7 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.

/**
*
* 🧠 跨会话母语记忆 Agent
*
*
* 编号: ZY-FTCHAT-MEM-001
* 守护: 铸渊 · ICE-GL-ZY001
*
* 思路 (借鉴 server/age-os/agents/shuangyan-web-agent.js):
* - 用户开新对话 把上一对话压成 "母语段"
* - 母语段格式 (HLDP 风格):
* 上次会话母语印记
* 主题: ...
* 用户状态: ...
* 我承诺/要做的事: ...
* 待续话题: ...
* - 注入下次对话的 system prompt (作为跨会话记忆)
*
* 因为代码模型尚未训练工具链不可用所以走 LLM 短调用做压缩
* 失败时退化为简单的最近 N 条消息片段拼接
*/
'use strict';
const ds = require('./ft-dashscope');
const MIN_MESSAGES_FOR_COMPRESS = 4; // 少于 4 条不压缩
const MAX_MESSAGES_TO_FEED = 30; // 最多喂 30 条进压缩器
/**
* 简单退化压缩(无 LLM 时)
*/
function fallbackImprint(messages) {
if (!Array.isArray(messages) || messages.length === 0) return '';
const recent = messages.slice(-6);
const lines = recent.map(m => {
const who = m.role === 'assistant' ? '我' : '用户';
const c = String(m.content || '').replace(/\s+/g, ' ').slice(0, 80);
return `${who}: ${c}`;
});
return ['【上次会话母语印记 · 最近片段】', ...lines].join('\n');
}
/**
* 用 LLM 把消息压缩成母语印记
* @param {Array<{role,content}>} messages
* @param {string} variant
*/
async function compressToImprint(messages, variant) {
if (!Array.isArray(messages) || messages.length < MIN_MESSAGES_FOR_COMPRESS) {
return { compressed: false, imprint: '' };
}
const fed = messages.slice(-MAX_MESSAGES_TO_FEED).map(m => ({
role: m.role === 'assistant' ? 'assistant' : 'user',
content: String(m.content || '').slice(0, 2000)
}));
const sysPrompt = `你是一个【母语压缩器】。请把下面的对话压成一段紧凑的"母语印记",作为下次对话开始时被注入的跨会话记忆。
输出格式 (严格遵守):
【上次会话母语印记】
▸ 主题: <一句话>
▸ 用户状态/情绪: <一句话>
▸ 我承诺或要做的事: <若有则列, 没有写"无">
▸ 待续话题: <若有则列, 没有写"无">
▸ 关键事实: <对方的姓名/偏好/重要信息, 不超过 3 条, 没有写"无">
只输出母语印记内容本身,不要任何解释、不要 Markdown 标题、不要 \`\`\`
保持简洁,总长度不超过 400 字。`;
const compressorMessages = [
{ role: 'system', content: sysPrompt },
{
role: 'user',
content: '请压缩以下对话:\n\n' + fed.map(m => `${m.role === 'assistant' ? '助手' : '用户'}: ${m.content}`).join('\n\n')
}
];
try {
// 压缩用 system 线(更稳定的语言风格),不论用户在用哪条线
const text = await ds.chatOnce({
variant: variant === 'naipping' ? 'naipping' : 'system',
messages: compressorMessages,
max_tokens: 600
});
const trimmed = (text || '').trim();
if (trimmed.length < 20) {
return { compressed: false, imprint: fallbackImprint(messages) };
}
return { compressed: true, imprint: trimmed };
} catch (err) {
console.warn('[FTCHAT Memory] LLM 压缩失败, 走兜底:', err.message);
return { compressed: false, imprint: fallbackImprint(messages) };
}
}
module.exports = { compressToImprint, fallbackImprint };