// ═══════════════════════════════════════ // 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"); // ═══ 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 } ]; 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 : "未知")); } /** * 调用单个 API 源 */ function callAPI(source, prompt, opts) { const body = JSON.stringify({ model: source.model, messages: [ { role: "system", content: "你是一个技术开发Agent。回答要精确、结构化、可执行。使用中文。" }, { role: "user", content: prompt } ], max_tokens: opts.max_tokens || 4096, temperature: opts.temperature || 0.3 }); return new Promise((resolve, reject) => { const url = new URL(source.base + "/chat/completions"); const lib = url.protocol === "https:" ? https : http; const req = lib.request({ hostname: url.hostname, port: url.port || (url.protocol === "https:" ? 443 : 80), path: url.pathname + url.search, method: "POST", timeout: source.timeout, headers: { "Authorization": "Bearer " + source.key, "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } }, (res) => { let data = ""; res.on("data", (c) => data += c); res.on("end", () => { try { const json = JSON.parse(data); 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(source.name + " 解析失败: " + data.slice(0, 200))); } }); }); 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(); }); } /** * 获取当前状态 */ 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 };