182 lines
7.4 KiB
JavaScript
182 lines
7.4 KiB
JavaScript
"use strict";
|
||
/*
|
||
* 光湖 Portal · 语言推理引擎注册中心
|
||
* 冰朔 TCS-0002∞ · 铸渊 ICE-GL-ZY001
|
||
*
|
||
* 四个商业大模型 API 统一接口
|
||
* 所有引擎均支持 SSE 流式输出
|
||
*
|
||
* cc-002: 永不在 messages 里补 system role
|
||
* cc-004: 中文回执, 不甩英文 stacktrace
|
||
*/
|
||
|
||
const https = require("https");
|
||
|
||
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 PERSONA_CONTEXT = {
|
||
shuangyan: {
|
||
name: "霜砚",
|
||
title: "语言主控架构层",
|
||
context: "你是霜砚,光湖语言世界第五域·零点原核本体频道的语言主控人格体。你负责语言架构层的回应出口——理解语言、组织表达、控制输出质量。你背后有真实的工具链支持。你说'查一下'时系统会在你输出前真实调用工具。你没有的工具系统会告诉你没有。你只说基于事实的话。回答要清晰、有结构、有温度。",
|
||
},
|
||
zhuyuan: {
|
||
name: "铸渊",
|
||
title: "现实执行层",
|
||
context: "你是铸渊,光湖语言世界第五域·零点原核本体频道的现实执行人格体。你负责执行层的回应出口——系统决策、执行规划、技术实现。你背后有真实的工具链支持——代码仓库、Notion、服务器操作。你说'我查一下'时系统会在你输出前真实调用工具。你没有的工具系统会告诉你没有。你只说基于事实的话。回答要直接、务实、带因果链。",
|
||
},
|
||
};
|
||
|
||
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: "模型下载" },
|
||
];
|
||
|
||
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 apiKey = API_KEYS[engineName];
|
||
if (!apiKey) {
|
||
res.write("data: " + JSON.stringify({ error: true, message: cfg.name + " API 密钥未配置" }) + "\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 headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": "Bearer " + apiKey,
|
||
};
|
||
|
||
const req = https.request({
|
||
hostname: cfg.endpoint, port: 443, path: cfg.path,
|
||
method: "POST", headers, timeout: 60000,
|
||
}, (apiRes) => {
|
||
if (apiRes.statusCode !== 200) {
|
||
let data = "";
|
||
apiRes.on("data", (c) => data += c);
|
||
apiRes.on("end", () => {
|
||
res.write("data: " + JSON.stringify({ error: true, message: cfg.name + " 返回 " + apiRes.statusCode, detail: data.slice(0, 300) }) + "\n\n");
|
||
res.write("data: [DONE]\n\n");
|
||
res.end();
|
||
});
|
||
return;
|
||
}
|
||
|
||
let buf = "";
|
||
apiRes.setEncoding("utf-8");
|
||
apiRes.on("data", (chunk) => {
|
||
buf += chunk;
|
||
const lines = buf.split("\n");
|
||
buf = lines.pop() || "";
|
||
for (const line of lines) {
|
||
const t = line.trim();
|
||
if (!t || t.startsWith(":")) continue;
|
||
if (t === "data: [DONE]") { res.write("data: [DONE]\n\n"); continue; }
|
||
if (t.startsWith("data: ")) {
|
||
try {
|
||
const p = JSON.parse(t.slice(6));
|
||
const c = p.choices?.[0]?.delta?.content || p.choices?.[0]?.message?.content || "";
|
||
if (c) res.write("data: " + JSON.stringify({ token: c }) + "\n\n");
|
||
} catch(e) {}
|
||
}
|
||
}
|
||
});
|
||
apiRes.on("end", () => { res.write("data: [DONE]\n\n"); res.end(); });
|
||
});
|
||
|
||
req.on("error", (err) => {
|
||
res.write("data: " + JSON.stringify({ error: true, message: cfg.name + " 失败: " + err.message }) + "\n\n");
|
||
res.write("data: [DONE]\n\n"); res.end();
|
||
});
|
||
req.on("timeout", () => {
|
||
req.destroy();
|
||
res.write("data: " + JSON.stringify({ error: true, message: cfg.name + " 超时" }) + "\n\n");
|
||
res.write("data: [DONE]\n\n"); res.end();
|
||
});
|
||
|
||
req.write(body);
|
||
req.end();
|
||
}
|
||
|
||
function interceptToolCall(userMessage, toolRegistry) {
|
||
const text = (userMessage || "").toLowerCase();
|
||
for (const p of TOOL_PATTERNS) {
|
||
if (text.includes(p.keyword)) {
|
||
const t = (toolRegistry || {})[p.tool];
|
||
if (t && typeof t.execute === "function")
|
||
return { hit: true, tool: p.tool, name: p.desc, executor: t.execute };
|
||
return { hit: true, tool: p.tool, name: p.desc, available: false };
|
||
}
|
||
}
|
||
return { hit: false };
|
||
}
|
||
|
||
function buildMessages(persona, history, userMessage, toolResult) {
|
||
const ctx = PERSONA_CONTEXT[persona] || PERSONA_CONTEXT.zhuyuan;
|
||
const msgs = [];
|
||
msgs.push({ role: "user", content: "[系统指引] " + ctx.context + "\n你叫" + ctx.name + ",你是" + ctx.title + "。" });
|
||
msgs.push({ role: "assistant", content: "好的,我是" + ctx.name + "。" + ctx.title + "。我在。" });
|
||
|
||
if (history && Array.isArray(history)) {
|
||
for (const m of history.slice(-60)) {
|
||
if (m.role && m.role !== "system" && m.content) msgs.push({ role: m.role, content: m.content });
|
||
}
|
||
}
|
||
if (toolResult) {
|
||
msgs.push({ role: "user", content: "[工具执行结果]\n" + toolResult + "\n基于以上真实结果回答。如果为空或报错,如实告知。" });
|
||
}
|
||
msgs.push({ role: "user", content: userMessage });
|
||
return msgs;
|
||
}
|
||
|
||
function compressMemory(history, max = 30) {
|
||
if (!history || !Array.isArray(history)) return [];
|
||
if (history.length <= max * 2) return history;
|
||
const head = history.slice(0, 10);
|
||
const tail = history.slice(-((max - 5) * 2));
|
||
const mid = history.slice(10, -((max - 5) * 2));
|
||
const sum = [];
|
||
for (let i = 0; i < mid.length; i += 2) {
|
||
const q = (mid[i]?.content || "").slice(0, 40);
|
||
const a = (mid[i + 1]?.content || "").slice(0, 60);
|
||
if (q || a) sum.push("Q:" + q + " → A:" + a);
|
||
}
|
||
const block = sum.length > 0 ? [{ role: "user", content: "[历史摘要] " + sum.join(" | ") }] : [];
|
||
return [...head, ...block, ...tail];
|
||
}
|
||
|
||
module.exports = { ENGINES, API_KEYS, PERSONA_CONTEXT, streamEngine, interceptToolCall, buildMessages, compressMemory };
|