guanghulab/portal/engine-registry.js

231 lines
8.1 KiB
JavaScript
Raw Permalink 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.

"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 || "sk-a9b69e9cd2dc4ca68d6aceaa84f22afb",
zhipu: process.env.ZHIPU_KEY || "3863acbcc2bc4f6587ace4bb064c092c.KoPdL4slY5cyHpOk",
tongyi: process.env.TONGYI_KEY || "sk-96ca227be3ca47e2a4bbb9da74e42122",
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 };