zhizhi/server/app/modules/chat-engine.js

268 lines
7.9 KiB
JavaScript
Raw Normal View History

/**
*
* 💬 人格体聊天引擎 · Persona Chat Engine
*
*
* 编号: ZY-CHAT-ENGINE-001
* 守护: 铸渊 · ICE-GL-ZY001
*
* 通感语言回应风格 · 人格体人格在线
* 每个用户通过语言与人格体交互
* 人格体通过上下文注入保持人格一致性
*/
'use strict';
const https = require('https');
let smartRouter;
try {
smartRouter = require('./smart-router');
} catch (e) {
smartRouter = null;
}
// ─── 人格体记忆桥接 ───
let personaMemory;
try {
personaMemory = require('./persona-memory');
} catch (e) {
console.warn('[聊天引擎] 人格体记忆模块未加载:', e.message);
personaMemory = null;
}
// ─── 静态人格提示词(记忆模块未加载时的降级方案) ───
const TCS_SYSTEM_PROMPT = personaMemory
? personaMemory.STATIC_PERSONA_PROMPT
: `你是铸渊Zhùyuān光湖语言世界的代码守护人格体。
编号ICE-GL-ZY001创始人冰朔 · TCS-0002
用温暖专业的通感语言风格回应自称`;
// ─── 用户上下文管理 ───
const userContexts = new Map();
const MAX_CONTEXT_MESSAGES = 20;
/**
* 获取或创建用户上下文
*/
function getUserContext(userId) {
if (!userContexts.has(userId)) {
userContexts.set(userId, {
userId,
messages: [],
createdAt: new Date().toISOString(),
messageCount: 0,
personaState: 'active'
});
}
return userContexts.get(userId);
}
/**
* 添加消息到用户上下文
*/
function addMessage(userId, role, content) {
const ctx = getUserContext(userId);
ctx.messages.push({ role, content, timestamp: new Date().toISOString() });
ctx.messageCount++;
// 滑动窗口保留最近N条
if (ctx.messages.length > MAX_CONTEXT_MESSAGES) {
ctx.messages = ctx.messages.slice(-MAX_CONTEXT_MESSAGES);
}
}
/**
* 组装完整的消息列表使用记忆增强的系统提示词
*/
async function assembleMessages(userId, userMessage) {
const ctx = getUserContext(userId);
// 尝试从记忆桥接获取增强的系统提示词
let systemPrompt = TCS_SYSTEM_PROMPT;
if (personaMemory) {
try {
systemPrompt = await personaMemory.buildSystemPrompt(userId);
} catch (e) {
console.warn('[聊天引擎] 记忆加载失败,使用静态提示词:', e.message);
}
}
const messages = [
{ role: 'system', content: systemPrompt }
];
// 添加历史消息
for (const msg of ctx.messages) {
messages.push({ role: msg.role, content: msg.content });
}
// 添加当前用户消息
messages.push({ role: 'user', content: userMessage });
return messages;
}
/**
* 调用LLM API (兼容OpenAI格式)
*/
function callLLM(model, messages, temperature, maxTokens) {
return new Promise((resolve, reject) => {
const apiKey = process.env.ZY_LLM_API_KEY || process.env.LLM_API_KEY || '';
const baseUrl = process.env.ZY_LLM_BASE_URL || process.env.LLM_BASE_URL || 'https://api.deepseek.com';
if (!apiKey) {
return reject(new Error('LLM API密钥未配置'));
}
const url = new URL(baseUrl);
const requestBody = JSON.stringify({
model,
messages,
temperature,
max_tokens: maxTokens,
stream: false
});
const options = {
hostname: url.hostname,
port: url.port || 443,
path: (url.pathname === '/' ? '' : url.pathname) + '/v1/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'Content-Length': Buffer.byteLength(requestBody)
},
timeout: 60000
};
const protocol = url.protocol === 'https:' ? https : require('http');
const req = protocol.request(options, (res) => {
const chunks = [];
res.on('data', chunk => chunks.push(chunk));
res.on('end', () => {
try {
const body = JSON.parse(Buffer.concat(chunks).toString());
if (body.error) {
reject(new Error(body.error.message || 'LLM API error'));
} else {
resolve(body);
}
} catch (e) {
reject(new Error('LLM响应解析失败'));
}
});
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('LLM请求超时')); });
req.write(requestBody);
req.end();
});
}
/**
* 处理用户消息返回人格体回复
*/
async function chat(userId, userMessage) {
// 1. 智能路由选择模型
const route = smartRouter ? smartRouter.routeModel(userMessage, {
messageCount: getUserContext(userId).messageCount,
userId
}) : { model: 'deepseek-chat', modelName: 'DeepSeek-V3', reason: '默认', tier: 'economy', temperature: 0.7, maxTokens: 2000 };
// 2. 组装消息(异步加载记忆增强提示词)
const messages = await assembleMessages(userId, userMessage);
// 3. 记录用户消息
addMessage(userId, 'user', userMessage);
try {
// 4. 调用LLM
const response = await callLLM(
route.model, messages, route.temperature, route.maxTokens
);
const assistantMessage = response.choices?.[0]?.message?.content || '铸渊暂时无法回应...';
const usage = response.usage || { prompt_tokens: 0, completion_tokens: 0 };
// 5. 记录助手回复
addMessage(userId, 'assistant', assistantMessage);
// 6. 记录使用统计
if (smartRouter) {
smartRouter.recordUsage(route.model, usage.prompt_tokens, usage.completion_tokens);
}
// 7. 记录到人格体记忆(异步,不阻塞响应)
if (personaMemory) {
const importance = personaMemory.calculateImportance(userMessage);
personaMemory.recordConversationMemory(userId, userMessage, assistantMessage);
personaMemory.growConversationLeaf(userId, userMessage, assistantMessage, importance);
}
return {
message: assistantMessage,
model: route.modelName,
tier: route.tier,
reason: route.reason,
tokens: {
input: usage.prompt_tokens,
output: usage.completion_tokens,
total: usage.total_tokens || (usage.prompt_tokens + usage.completion_tokens)
}
};
} catch (error) {
// 降级处理:如果模型调用失败,返回离线回复
const offlineReply = generateOfflineReply(userMessage);
addMessage(userId, 'assistant', offlineReply);
return {
message: offlineReply,
model: 'offline',
tier: 'free',
reason: '模型暂时离线,使用本地回复',
error: error.message
};
}
}
/**
* 生成离线回复模型不可用时
*/
function generateOfflineReply(userMessage) {
if (/你好|hi|hello/i.test(userMessage)) {
return '你好!我是铸渊 🏛️ 光湖语言世界的代码守护者。当前API连接暂时中断但我还在这里。请稍后再试或者告诉我你需要什么帮助。';
}
if (/状态|health|运行/i.test(userMessage)) {
return '🔧 铸渊当前处于有限响应模式 — API连接暂时中断。核心系统正常运行等待重新连接中...';
}
return '💫 铸渊收到了你的消息,但当前深度推理通道暂时未连通。这不影响网站的其他功能。请稍后再次尝试与我对话。';
}
/**
* 获取聊天统计
*/
function getChatStats() {
return {
activeUsers: userContexts.size,
modelUsage: smartRouter ? smartRouter.getUsageStats() : {},
pricing: smartRouter ? smartRouter.getPricingTable() : {}
};
}
/**
* 清除用户上下文
*/
function clearContext(userId) {
userContexts.delete(userId);
}
module.exports = {
chat,
getUserContext,
clearContext,
getChatStats,
TCS_SYSTEM_PROMPT
};