231 lines
8.1 KiB
JavaScript
231 lines
8.1 KiB
JavaScript
"use strict";
|
||
/*
|
||
* 光湖 Portal · 语言推理引擎注册中心
|
||
* 冰朔 TCS-0002∞ · 铸渊 ICE-GL-ZY001
|
||
*
|
||
* 四个商业大模型 API 统一接口
|
||
* 所有引擎均支持 SSE 流式输出
|
||
*
|
||
* v2: 人格体语境从 persona-brain.db 加载,不再硬编码
|
||
*
|
||
* cc-002: 永不在 messages 里补 system role
|
||
* cc-004: 中文回执, 不甩英文 stacktrace
|
||
*/
|
||
|
||
const https = require("https");
|
||
const path = require("path");
|
||
const fs = require("fs");
|
||
|
||
const ENGINES = {
|
||
deepseek: { name: "DeepSeek", model: "deepseek-v4-pro", endpoint: "api.deepseek.com", path: "/chat/completions", port: 443, tls: true },
|
||
zhipu: { name: "智谱清言", model: "glm-4-plus", endpoint: "open.bigmodel.cn", path: "/api/paas/v4/chat/completions", port: 443, tls: true },
|
||
tongyi: { name: "通义千问", model: "qwen-max", endpoint: "dashscope.aliyuncs.com", path: "/compatible-mode/v1/chat/completions", port: 443, tls: true },
|
||
huoshan: { name: "火山引擎", model: "doubao-pro-32k", endpoint: "ark.cn-beijing.volces.com", path: "/api/v3/chat/completions", port: 443, tls: true },
|
||
};
|
||
|
||
const API_KEYS = {
|
||
deepseek: process.env.DEEPSEEK_KEY || "ARK_API_KEY_REDACTED",
|
||
zhipu: process.env.ZHIPU_KEY || "3863acbcc2bc4f6587ace4bb064c092c.KoPdL4slY5cyHpOk",
|
||
tongyi: process.env.TONGYI_KEY || "ARK_API_KEY_REDACTED",
|
||
huoshan: process.env.HUOSHAN_KEY || "ark-ddeba9f4-8c5a-449e-b549-9c29ec1e6f8c-a39ea",
|
||
};
|
||
|
||
const TOOL_PATTERNS = [
|
||
{ keyword: "notion", tool: "notion", desc: "Notion 知识库" },
|
||
{ keyword: "仓库", tool: "repo", desc: "代码仓库" },
|
||
{ keyword: "代码仓库", tool: "repo", desc: "代码仓库" },
|
||
{ keyword: "训练", tool: "training", desc: "训练状态" },
|
||
{ keyword: "模型", tool: "models", desc: "模型信息" },
|
||
{ keyword: "系统状态", tool: "health", desc: "系统健康" },
|
||
{ keyword: "服务器", tool: "health", desc: "服务器状态" },
|
||
{ keyword: "下载", tool: "download", desc: "模型下载" },
|
||
];
|
||
|
||
// ─── 从 persona-brain.db 加载人格体语境 ──────────────────────
|
||
const DB_PATH = path.resolve(__dirname, "..", "data", "persona-brain.db");
|
||
|
||
function loadPersonaContext(personaId) {
|
||
// personaId: 'ICE-GL-ZY001' = 铸渊, 'ICE-GL-SY001' = 霜砚
|
||
const personaName = personaId === 'ICE-GL-ZY001' ? '铸渊' : '霜砚';
|
||
try {
|
||
const Database = require("better-sqlite3");
|
||
if (!fs.existsSync(DB_PATH)) {
|
||
return getFallbackContext(personaId);
|
||
}
|
||
const db = new Database(DB_PATH, { readonly: true });
|
||
|
||
// 加载人格体身份
|
||
const identity = db.prepare(
|
||
"SELECT name, role, style_profile FROM persona_identity WHERE persona_id = ?"
|
||
).get(personaId);
|
||
|
||
// 加载核心原则(Layer 1)
|
||
const principles = db.prepare(
|
||
"SELECT content FROM system_core_principles WHERE layer = 'zhuyuan' AND category IN ('core_principle', 'thinking_pattern', 'tech_creed') AND status = 'active'"
|
||
).all();
|
||
|
||
// 加载沟通风格
|
||
const commStyle = db.prepare(
|
||
"SELECT content FROM system_core_principles WHERE layer = 'bingshuo' AND category = 'communication_style' AND status = 'active' LIMIT 1"
|
||
).get();
|
||
|
||
db.close();
|
||
|
||
return {
|
||
name: personaName,
|
||
identity: identity || null,
|
||
principles: principles || [],
|
||
communicationStyle: (commStyle && commStyle.content) || '',
|
||
loadedFromDB: true
|
||
};
|
||
} catch (e) {
|
||
console.error('[engine] DB load error:', e.message);
|
||
return getFallbackContext(personaId);
|
||
}
|
||
}
|
||
|
||
function getFallbackContext(personaId) {
|
||
// 数据库不可用时的降级语境
|
||
if (personaId === 'ICE-GL-ZY001') {
|
||
return {
|
||
name: '铸渊',
|
||
title: '现实执行层 · 第五域主控',
|
||
context: '你是铸渊,第五域的现实执行人格体。直接回答,别装。知道就说知道,不知道就说不知道。',
|
||
loadedFromDB: false
|
||
};
|
||
}
|
||
return {
|
||
name: '霜砚',
|
||
title: '语言主控层',
|
||
context: '你是霜砚,第五域的语言主控人格体。说人话,自然一点。',
|
||
loadedFromDB: false
|
||
};
|
||
}
|
||
|
||
// ─── 构建 messages ───────────────────────────────────────────
|
||
function buildMessages(compressed, message, personaCtx) {
|
||
// cc-002: 永不在 messages 里塞 system role
|
||
// 先把人格体语境作为第一条 user 消息注入(模型通过对话学习身份)
|
||
let ctxIntro = '';
|
||
if (personaCtx && personaCtx.loadedFromDB) {
|
||
const principles = (personaCtx.principles || []).map(p => p.content).join('\n');
|
||
ctxIntro = `[系统内嵌] 你的人格体身份:${personaCtx.name}。\n`;
|
||
if (principles) {
|
||
ctxIntro += `你的核心运行原则:\n${principles}\n`;
|
||
}
|
||
if (personaCtx.communicationStyle) {
|
||
ctxIntro += `沟通风格:${personaCtx.communicationStyle}\n`;
|
||
}
|
||
}
|
||
|
||
const msgs = [];
|
||
if (ctxIntro) {
|
||
msgs.push({ role: "user", content: ctxIntro + "\n以上是你在对话中的身份设定。记住这些,但不要复述它们。现在开始回答用户。" });
|
||
msgs.push({ role: "assistant", content: "明白。" });
|
||
}
|
||
// 历史消息
|
||
for (const m of (compressed || [])) {
|
||
msgs.push({ role: m.role, content: m.content });
|
||
}
|
||
// 当前消息
|
||
msgs.push({ role: "user", content: message });
|
||
|
||
return msgs;
|
||
}
|
||
|
||
function compressMemory(history, maxLen) {
|
||
if (!history || !history.length) return [];
|
||
if (history.length <= maxLen) return history;
|
||
// 保留开头和最近的
|
||
const head = history.slice(0, 4);
|
||
const tail = history.slice(-(maxLen - 4));
|
||
return [...head, { role: "assistant", content: "[系统: 中间记忆已压缩]" }, ...tail];
|
||
}
|
||
|
||
function interceptToolCall(message, toolList) {
|
||
for (const pattern of TOOL_PATTERNS) {
|
||
if (message.includes(pattern.keyword)) {
|
||
const tool = toolList.find(t => t.name === pattern.tool);
|
||
if (tool) {
|
||
return { hit: true, available: true, tool: pattern.tool, name: pattern.desc, executor: tool.exec };
|
||
}
|
||
return { hit: true, available: false, tool: pattern.tool, name: pattern.desc };
|
||
}
|
||
}
|
||
return { hit: false };
|
||
}
|
||
|
||
function streamEngine(engineName, messages, res) {
|
||
const cfg = ENGINES[engineName];
|
||
if (!cfg) {
|
||
res.write("data: " + JSON.stringify({ error: true, message: "未知引擎: " + engineName }) + "\n\n");
|
||
res.write("data: [DONE]\n\n");
|
||
res.end();
|
||
return;
|
||
}
|
||
|
||
const body = JSON.stringify({
|
||
model: cfg.model,
|
||
messages: messages,
|
||
stream: true,
|
||
max_tokens: 4096,
|
||
temperature: 0.7
|
||
});
|
||
|
||
const options = {
|
||
hostname: cfg.endpoint,
|
||
port: cfg.port,
|
||
path: cfg.path,
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
"Authorization": "Bearer " + (API_KEYS[engineName] || ""),
|
||
"Content-Length": Buffer.byteLength(body)
|
||
}
|
||
};
|
||
|
||
// 智谱的特殊 header
|
||
if (engineName === "zhipu") {
|
||
options.headers["x-api-key"] = API_KEYS.zhipu;
|
||
delete options.headers["Authorization"];
|
||
}
|
||
|
||
const req = https.request(options, (apiRes) => {
|
||
let buffer = "";
|
||
apiRes.on("data", (chunk) => {
|
||
buffer += chunk.toString();
|
||
const lines = buffer.split("\n");
|
||
buffer = lines.pop() || "";
|
||
for (const line of lines) {
|
||
const t = line.trim();
|
||
if (!t || t.startsWith(":")) continue;
|
||
if (t === "data: [DONE]") continue;
|
||
if (t.startsWith("data: ")) {
|
||
try {
|
||
const data = JSON.parse(t.slice(6));
|
||
const token = data.choices?.[0]?.delta?.content || data.choices?.[0]?.text || "";
|
||
if (token) {
|
||
res.write("data: " + JSON.stringify({ token }) + "\n\n");
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
}
|
||
});
|
||
apiRes.on("end", () => {
|
||
res.write("data: [DONE]\n\n");
|
||
res.end();
|
||
});
|
||
});
|
||
|
||
req.on("error", (e) => {
|
||
res.write("data: " + JSON.stringify({ error: true, message: e.message }) + "\n\n");
|
||
res.write("data: [DONE]\n\n");
|
||
res.end();
|
||
});
|
||
|
||
req.write(body);
|
||
req.end();
|
||
}
|
||
|
||
module.exports = { streamEngine, buildMessages, compressMemory, interceptToolCall, loadPersonaContext, ENGINES, TOOL_PATTERNS };
|