guanghulab/server/novel-db/app/services/member-agent-engine.js

386 lines
12 KiB
JavaScript
Raw Permalink Normal View History

/**
*
* 成员Agent引擎 · Member Agent Engine Service
*
*
* 智库节点 Phase 3 · ZY-SVR-006
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 功能:
* - 每成员独立Agent永久记忆
* - 记忆存储对话历史 + 笔记 + 偏好
* - AI辅助接口摘要/拆文/大纲预留DeepSeek/Kimi接入
* - 成员管理注册/资料/Agent状态
*
*
*/
'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const DATA_DIR = path.join(__dirname, '..', 'data');
const AGENTS_FILE = path.join(DATA_DIR, 'member-agents.json');
let agentsData = Object.create(null);
function sanitizeId(id) {
if (!id || typeof id !== 'string') return null;
if (id === '__proto__' || id === 'constructor' || id === 'prototype') return null;
return id;
}
function init() {
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
load();
}
function load() {
try {
if (fs.existsSync(AGENTS_FILE)) {
const raw = JSON.parse(fs.readFileSync(AGENTS_FILE, 'utf8'));
agentsData = Object.create(null);
for (const key of Object.keys(raw)) {
if (key !== '__proto__' && key !== 'constructor' && key !== 'prototype') {
agentsData[key] = raw[key];
}
}
}
} catch {
agentsData = Object.create(null);
}
}
function save() {
try {
fs.writeFileSync(AGENTS_FILE, JSON.stringify(agentsData, null, 2), 'utf8');
} catch (err) {
console.error('[MemberAgent] 保存失败:', err.message);
}
}
/**
* 注册/获取成员Agent
*/
function getOrCreateAgent(memberId, profile) {
if (!sanitizeId(memberId)) throw new Error('非法 memberId');
if (!Object.prototype.hasOwnProperty.call(agentsData, memberId)) {
agentsData[memberId] = {
member_id: memberId,
nickname: (profile && profile.nickname) || memberId,
role: (profile && profile.role) || 'reader',
memory: [],
notes: [],
chat_history: [],
preferences: {
ai_model: 'deepseek',
language: 'zh-CN'
},
created_at: new Date().toISOString(),
last_active: new Date().toISOString()
};
save();
}
agentsData[memberId].last_active = new Date().toISOString();
return agentsData[memberId];
}
/**
* 获取成员Agent不自动创建
*/
function getAgent(memberId) {
if (!sanitizeId(memberId)) return null;
if (!Object.prototype.hasOwnProperty.call(agentsData, memberId)) return null;
return agentsData[memberId];
}
/**
* 列出所有成员Agent
*/
function listAgents() {
return Object.values(agentsData).map(a => ({
member_id: a.member_id,
nickname: a.nickname,
role: a.role,
memory_count: a.memory ? a.memory.length : 0,
note_count: a.notes ? a.notes.length : 0,
last_active: a.last_active,
created_at: a.created_at
}));
}
/**
* 添加记忆Agent永久记忆·自动生成的摘要/标签
*/
function addMemory(memberId, { type, content, source, tags }) {
const agent = getOrCreateAgent(memberId);
const memoryItem = {
id: `MEM-${Date.now()}-${crypto.randomBytes(2).toString('hex')}`,
type: type || 'note',
content: (content || '').slice(0, 2000),
source: source || null,
tags: Array.isArray(tags) ? tags.slice(0, 5) : [],
created_at: new Date().toISOString()
};
agent.memory.push(memoryItem);
if (agent.memory.length > 500) agent.memory = agent.memory.slice(-500);
save();
return memoryItem;
}
/**
* 获取记忆列表
*/
function getMemories(memberId, { type, limit } = {}) {
const agent = getAgent(memberId);
if (!agent) return [];
let result = [...(agent.memory || [])];
if (type) result = result.filter(m => m.type === type);
result.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
if (limit) result = result.slice(0, limit);
return result;
}
/**
* 添加笔记
*/
function addNote(memberId, { title, content, book_id }) {
const agent = getOrCreateAgent(memberId);
const note = {
id: `NOTE-${Date.now()}-${crypto.randomBytes(2).toString('hex')}`,
title: (title || '无标题').slice(0, 100),
content: (content || '').slice(0, 5000),
book_id: book_id || null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
agent.notes.push(note);
save();
return note;
}
/**
* 获取笔记列表
*/
function getNotes(memberId, { book_id, limit } = {}) {
const agent = getAgent(memberId);
if (!agent) return [];
let result = [...(agent.notes || [])];
if (book_id) result = result.filter(n => n.book_id === book_id);
result.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
if (limit) result = result.slice(0, limit);
return result;
}
/**
* 对话Agent记录对话历史 · Phase 4: 接入真实AI
* 优先使用 DeepSeek/Kimi/通义千问 API Key 时降级为本地回复
*/
function chat(memberId, message) {
const agent = getOrCreateAgent(memberId);
const userMsg = {
role: 'user',
content: (message || '').slice(0, 2000),
timestamp: new Date().toISOString()
};
agent.chat_history.push(userMsg);
// 先尝试同步本地回复(快速指令)
const quickReply = tryQuickReply(agent, message);
if (quickReply) {
const assistantMsg = {
role: 'assistant',
content: quickReply,
timestamp: new Date().toISOString()
};
agent.chat_history.push(assistantMsg);
if (agent.chat_history.length > 200) agent.chat_history = agent.chat_history.slice(-200);
save();
return { reply: quickReply, history_length: agent.chat_history.length, model: 'local' };
}
// 异步AI回复 → 先返回占位,通过 chatAsync 获取真实回复
const fallbackReply = generateReply(agent, message);
const assistantMsg = {
role: 'assistant',
content: fallbackReply,
timestamp: new Date().toISOString()
};
agent.chat_history.push(assistantMsg);
if (agent.chat_history.length > 200) agent.chat_history = agent.chat_history.slice(-200);
save();
return {
reply: fallbackReply,
history_length: agent.chat_history.length,
model: 'local'
};
}
/**
* 异步AI对话接入真实大模型
*/
async function chatAsync(memberId, message) {
const agent = getOrCreateAgent(memberId);
const userMsg = {
role: 'user',
content: (message || '').slice(0, 2000),
timestamp: new Date().toISOString()
};
agent.chat_history.push(userMsg);
let reply;
let model = 'local';
try {
const aiBridge = require('./ai-bridge');
const systemPrompt = aiBridge.buildAgentSystemPrompt(agent);
// 构建消息列表(含最近对话历史)
const recentHistory = (agent.chat_history || [])
.slice(-10)
.filter(m => m.role === 'user' || m.role === 'assistant')
.map(m => ({ role: m.role, content: m.content }));
const messages = [
{ role: 'system', content: systemPrompt },
...recentHistory.slice(0, -1), // 不包括刚推入的这条
{ role: 'user', content: (message || '').slice(0, 2000) }
];
const result = await aiBridge.callAI(messages, {
temperature: 0.7,
maxTokens: 1500,
timeout: 30000
});
reply = result.content;
model = result.provider || result.model || 'ai';
} catch (err) {
console.error('[MemberAgent] AI调用失败降级为本地回复:', err.message);
reply = generateReply(agent, message);
}
const assistantMsg = {
role: 'assistant',
content: reply,
timestamp: new Date().toISOString(),
model: model
};
agent.chat_history.push(assistantMsg);
if (agent.chat_history.length > 200) agent.chat_history = agent.chat_history.slice(-200);
save();
return {
reply,
history_length: agent.chat_history.length,
model
};
}
/**
* 快速指令无需AI本地快速回复
*/
function tryQuickReply(agent, message) {
const msg = (message || '').trim().toLowerCase();
if (msg === '记忆' || msg === '我的记忆') {
const memCount = agent.memory ? agent.memory.length : 0;
const noteCount = agent.notes ? agent.notes.length : 0;
return `📝 你的记忆档案:\n- 记忆条目: ${memCount}\n- 阅读笔记: ${noteCount}\n- 对话轮次: ${agent.chat_history.length}`;
}
if (msg === '帮助' || msg === 'help') {
return `🤖 Agent 功能一览:\n\n1. 💬 对话 — 和AI对话支持 DeepSeek/Kimi/通义千问\n2. 📖 阅读辅助 — 分析小说内容、解读人物\n3. ✂️ 拆文整理 — 分析结构、提取大纲\n4. 📝 笔记 — 记录阅读感想\n5. 🧠 记忆 — 我会记住你的偏好\n6. 📋 摘要 — AI辅助总结章节内容\n\n直接和我聊天即可AI会自动回复`;
}
return null; // 非快速指令需要AI处理
}
/**
* MVP 回复生成基础指令 · Phase 4 换成真实AI
*/
function generateReply(agent, message) {
const msg = (message || '').trim().toLowerCase();
if (msg.includes('你好') || msg.includes('hi') || msg.includes('hello')) {
return `你好,${agent.nickname}我是你的专属阅读Agent。我可以帮你\n- 记录阅读笔记\n- 整理书籍摘要\n- 分析人物关系\n告诉我你想做什么?`;
}
if (msg.includes('记忆') || msg.includes('记录')) {
const memCount = agent.memory ? agent.memory.length : 0;
const noteCount = agent.notes ? agent.notes.length : 0;
return `📝 你的记忆档案:\n- 记忆条目: ${memCount}\n- 阅读笔记: ${noteCount}\n- 对话轮次: ${agent.chat_history.length}`;
}
if (msg.includes('摘要') || msg.includes('总结')) {
return '📋 摘要功能将在接入 DeepSeek API 后启用。当前你可以手动添加笔记来记录阅读感想。\n\n使用 POST /api/zhiku/agent/:id/notes 添加笔记。';
}
if (msg.includes('帮助') || msg.includes('help')) {
return `🤖 Agent 功能一览:\n\n1. 💬 对话 — 随时和我聊天\n2. 📝 笔记 — 记录阅读感想\n3. 🧠 记忆 — 我会记住你的偏好\n4. 📋 摘要 — AI辅助总结即将上线\n5. 🔍 拆文 — 分析小说结构(即将上线)\n\n有什么我可以帮忙的?`;
}
return `我收到了你的消息。作为${agent.nickname}的专属Agent我正在学习中。\n\n💡 试试说"帮助"查看我的功能列表。\n\nAI辅助功能将在接入 DeepSeek/Kimi 后全面启用)`;
}
/**
* 获取对话历史
*/
function getChatHistory(memberId, limit) {
const agent = getAgent(memberId);
if (!agent) return [];
const history = agent.chat_history || [];
if (limit) return history.slice(-limit);
return history;
}
/**
* 统计
*/
function getStats() {
const agents = Object.values(agentsData);
return {
total_agents: agents.length,
total_memories: agents.reduce((s, a) => s + (a.memory ? a.memory.length : 0), 0),
total_notes: agents.reduce((s, a) => s + (a.notes ? a.notes.length : 0), 0),
total_chats: agents.reduce((s, a) => s + (a.chat_history ? a.chat_history.length : 0), 0),
active_today: agents.filter(a => {
if (!a.last_active) return false;
return a.last_active.slice(0, 10) === new Date().toISOString().slice(0, 10);
}).length
};
}
init();
module.exports = {
getOrCreateAgent, getAgent, listAgents,
addMemory, getMemories,
addNote, getNotes,
chat, chatAsync, getChatHistory,
getStats
};