From 584ac6d2eaff064771a13c2b441bae334aaeab3d Mon Sep 17 00:00:00 2001 From: root Date: Sat, 30 May 2026 20:50:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20model-caller=20v2.0=20=E2=80=94=20?= =?UTF-8?q?=E4=BA=91=E9=9B=BEAPI=E4=B8=BB=E7=94=A8+DeepSeek=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=20=C2=B7=20=E8=87=AA=E5=8A=A8=E6=95=85=E9=9A=9C?= =?UTF-8?q?=E5=88=87=E6=8D=A2=20=C2=B7=20=E9=92=A5=E5=8C=99=E5=B7=B2?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=B8=BAZY=5FLLM=5FAPI=5FKEY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zhuyuan-dev-agent/modules/model-caller.js | 142 ++++++++++++++---- 1 file changed, 111 insertions(+), 31 deletions(-) diff --git a/agents/zhuyuan-dev-agent/modules/model-caller.js b/agents/zhuyuan-dev-agent/modules/model-caller.js index 3302bcd..591e8d5 100644 --- a/agents/zhuyuan-dev-agent/modules/model-caller.js +++ b/agents/zhuyuan-dev-agent/modules/model-caller.js @@ -1,36 +1,86 @@ // ═══════════════════════════════════════ -// 铸渊开发Agent · 大模型调用模块 -// 支持 OpenAI 兼容 API → DeepSeek/千问/等 +// HLDP-ZY://agents/zhuyuan-dev-agent/modules/model-caller +// @guardian: ICE-GL-ZY001 · 铸渊 +// @sovereign: TCS-0002∞ · 冰朔 +// @copyright: 国作登字-2026-A-00037559 +// ═══════════════════════════════════════ +// 模型调用模块 v2.0 — 云雾API主用 + DeepSeek官方备份 +// 主: https://api.yunwu.ai/v1 (OpenAI兼容) +// 备: https://api.deepseek.com/v1 (OpenAI兼容) +// 失败自动切换,不做数据丢失 // ═══════════════════════════════════════ const https = require("https"); +const http = require("http"); -function getConfig() { - const key = process.env.MODEL_API_KEY; - const base = process.env.MODEL_API_BASE || "https://api.deepseek.com/v1"; - const model = process.env.MODEL_NAME || "deepseek-chat"; - - if (!key) { - console.warn("[ModelCaller] MODEL_API_KEY 未配置"); - return null; +// ═══ API 源配置 ═══ +const API_SOURCES = [ + { + name: "yunwu", + base: process.env.YUNWU_BASE_URL || "https://api.yunwu.ai/v1", + key: process.env.ZY_LLM_API_KEY || process.env.MODEL_API_KEY, + model: process.env.YUNWU_MODEL || "deepseek-chat", + timeout: 60000 + }, + { + name: "deepseek", + base: process.env.DEEPSEEK_BASE_URL || "https://api.deepseek.com/v1", + key: process.env.DEEPSEEK_API_KEY || process.env.ZY_LLM_API_KEY, + model: process.env.DEEPSEEK_MODEL || "deepseek-chat", + timeout: 60000 } - return { key, base, model }; +]; + +let currentSourceIndex = 0; +let sourceFailCounts = {}; + +/** + * 调用大模型 — 主源失败自动切备份 + */ +async function call(prompt, opts = {}) { + const maxTries = API_SOURCES.length; + let lastError = null; + + for (let attempt = 0; attempt < maxTries; attempt++) { + const source = API_SOURCES[(currentSourceIndex + attempt) % API_SOURCES.length]; + + if (!source.key) { + console.warn("[ModelCaller] " + source.name + " 未配置key,跳过"); + continue; + } + + try { + console.log("[ModelCaller] 调用 " + source.name + " · 模型 " + source.model); + const result = await callAPI(source, prompt, opts); + console.log("[ModelCaller] " + source.name + " 返回 " + result.length + " 字符"); + + // 成功 → 记录此源可用 + sourceFailCounts[source.name] = 0; + return result; + + } catch (err) { + console.warn("[ModelCaller] " + source.name + " 失败: " + err.message); + sourceFailCounts[source.name] = (sourceFailCounts[source.name] || 0) + 1; + lastError = err; + + // 切换到下一个源 + if (attempt < maxTries - 1) { + console.log("[ModelCaller] 切换备用源..."); + } + } + } + + throw new Error("所有API源均失败。最后错误: " + (lastError ? lastError.message : "未知")); } /** - * 调用大模型 - * @param {string} prompt - 输入提示 - * @param {object} opts - { max_tokens, temperature } - * @returns {Promise} 模型回复文本 + * 调用单个 API 源 */ -async function call(prompt, opts = {}) { - const config = getConfig(); - if (!config) throw new Error("MODEL_API_KEY 未配置"); - +function callAPI(source, prompt, opts) { const body = JSON.stringify({ - model: config.model, + model: source.model, messages: [ - { role: "system", content: "你是一个技术开发Agent。回答要精确、结构化、可执行。" }, + { role: "system", content: "你是一个技术开发Agent。回答要精确、结构化、可执行。使用中文。" }, { role: "user", content: prompt } ], max_tokens: opts.max_tokens || 4096, @@ -38,14 +88,19 @@ async function call(prompt, opts = {}) { }); return new Promise((resolve, reject) => { - const url = new URL(config.base + "/chat/completions"); - const req = https.request({ + const url = new URL(source.base + "/chat/completions"); + const lib = url.protocol === "https:" ? https : http; + + const req = lib.request({ hostname: url.hostname, - path: url.pathname, + port: url.port || (url.protocol === "https:" ? 443 : 80), + path: url.pathname + url.search, method: "POST", + timeout: source.timeout, headers: { - "Authorization": "Bearer " + config.key, - "Content-Type": "application/json" + "Authorization": "Bearer " + source.key, + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(body) } }, (res) => { let data = ""; @@ -53,18 +108,43 @@ async function call(prompt, opts = {}) { res.on("end", () => { try { const json = JSON.parse(data); - if (json.error) return reject(new Error(json.error.message || "API错误")); - resolve(json.choices?.[0]?.message?.content || ""); + if (json.error) { + reject(new Error(source.name + " API错误: " + (json.error.message || JSON.stringify(json.error)))); + return; + } + const content = json.choices?.[0]?.message?.content || ""; + if (!content) { + reject(new Error(source.name + " 空回复")); + return; + } + resolve(content); } catch (e) { - reject(new Error("解析失败: " + data.slice(0, 200))); + reject(new Error(source.name + " 解析失败: " + data.slice(0, 200))); } }); }); - req.on("error", reject); + req.on("timeout", () => { + req.destroy(); + reject(new Error(source.name + " 请求超时 " + source.timeout + "ms")); + }); + + req.on("error", (e) => reject(new Error(source.name + " 网络错误: " + e.message))); req.write(body); req.end(); }); } -module.exports = { call }; +/** + * 获取当前状态 + */ +function getStatus() { + return { + primary: API_SOURCES[0].name, + backup: API_SOURCES[1] ? API_SOURCES[1].name : "none", + failCounts: sourceFailCounts, + currentIndex: currentSourceIndex + }; +} + +module.exports = { call, getStatus };