guanghulab/scripts/llm-automation-host.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

535 lines
18 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.

#!/usr/bin/env node
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
// scripts/llm-automation-host.js
// 🤖 LLM 自动化托管引擎
//
// 使用仓库密钥中的第三方模型API密钥来运行自动化任务
// 替代直接消耗 GitHub Copilot 配额
// 支持动态模型路由:根据任务类型自动选择最佳模型
//
// 用法:
// --status 显示可用模型和系统状态
// --task "任务描述" 执行自动化任务
// --task-type TYPE 任务类型 (inspection/fusion/review/general)
// --model MODEL 指定模型 (auto/anthropic/openai/dashscope/deepseek/custom)
// --dry-run 仅显示选择的模型和请求,不实际调用
// --context FILE 加载额外上下文文件
'use strict';
const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
// ── 模型后端配置
// SY-CMD-KEY-012: 统一使用 ZY_LLM_API_KEY + ZY_LLM_BASE_URL
// 三方API密钥支持多模型动态路由铸渊根据任务类型自动选择最优模型
const MODEL_BACKENDS = [
{
name: 'deepseek',
model: 'deepseek-chat',
format: 'openai',
strengths: ['reasoning', 'code', 'cost-effective'],
costTier: 'low',
description: 'DeepSeek 系列 · 高性价比推理'
},
{
name: 'deepseek-reasoner',
model: 'deepseek-reasoner',
format: 'openai',
strengths: ['reasoning', 'architecture', 'long-context'],
costTier: 'medium',
description: 'DeepSeek Reasoner · 深度推理'
},
{
name: 'claude-sonnet',
model: 'claude-sonnet-4-20250514',
format: 'openai',
strengths: ['reasoning', 'code-review', 'architecture', 'long-context'],
costTier: 'high',
description: 'Claude Sonnet · 强推理代码审查'
},
{
name: 'gpt-4o',
model: 'gpt-4o',
format: 'openai',
strengths: ['general', 'code-generation', 'structured-output'],
costTier: 'high',
description: 'GPT-4o · 通用能力'
},
{
name: 'qwen-plus',
model: 'qwen-plus',
format: 'openai',
strengths: ['chinese', 'general', 'cost-effective'],
costTier: 'medium',
description: '通义千问 Plus · 中文优化'
},
{
name: 'qwen-turbo',
model: 'qwen-turbo',
format: 'openai',
strengths: ['chinese', 'general', 'cost-effective'],
costTier: 'low',
description: '通义千问 Turbo · 快速低成本'
}
];
// ── 任务类型 → 模型强项映射(动态路由策略)
const TASK_MODEL_ROUTING = {
// 巡检任务:优先使用性价比高的模型
'inspection': {
preferred_strengths: ['general', 'cost-effective'],
preferred_cost: 'low',
description: '系统巡检 · 优先性价比'
},
// 融合分析:需要强推理能力
'fusion': {
preferred_strengths: ['reasoning', 'code-review'],
preferred_cost: 'medium',
description: '碎片融合分析 · 需要推理能力'
},
// 代码审查:需要强代码理解
'review': {
preferred_strengths: ['code-review', 'reasoning'],
preferred_cost: 'high',
description: '代码审查 · 需要深度理解'
},
// 架构设计:需要最强推理
'architecture': {
preferred_strengths: ['reasoning', 'architecture', 'long-context'],
preferred_cost: 'high',
description: '架构设计 · 需要最强推理'
},
// 通用任务
'general': {
preferred_strengths: ['general'],
preferred_cost: 'medium',
description: '通用任务'
}
};
// ── HTTP 请求工具 ────────────────────────────────
function httpRequest(url, options, body) {
return new Promise((resolve, reject) => {
const parsed = new URL(url);
const isHttps = parsed.protocol === 'https:';
const mod = isHttps ? https : http;
const opts = {
hostname: parsed.hostname,
port: parsed.port || (isHttps ? 443 : 80),
path: parsed.pathname + parsed.search,
method: options.method || 'POST',
headers: options.headers || {},
timeout: options.timeout || 120000,
};
const req = mod.request(opts, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
resolve({ status: res.statusCode, body: data });
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
if (body) {
req.write(body);
}
req.end();
});
}
// ── 检测可用模型后端 ────────────────────────────
// SY-CMD-KEY-012: 统一使用 ZY_LLM_API_KEY + ZY_LLM_BASE_URL
// 兼容旧环境变量名LLM_API_KEY/LLM_BASE_URL用于脚本过渡
function detectAvailableBackends() {
const apiKey = process.env.ZY_LLM_API_KEY || process.env.LLM_API_KEY || '';
const baseUrl = (process.env.ZY_LLM_BASE_URL || process.env.LLM_BASE_URL || '').replace(/\/+$/, '');
// 过渡期警告:使用旧环境变量名
if (!process.env.ZY_LLM_API_KEY && process.env.LLM_API_KEY) {
console.warn('⚠️ 使用旧环境变量 LLM_API_KEY请迁移到 ZY_LLM_API_KEY');
}
if (!process.env.ZY_LLM_BASE_URL && process.env.LLM_BASE_URL) {
console.warn('⚠️ 使用旧环境变量 LLM_BASE_URL请迁移到 ZY_LLM_BASE_URL');
}
if (!apiKey || !baseUrl) {
return [];
}
// 所有模型后端共享同一个API密钥和端点
return MODEL_BACKENDS.map(backend => ({
...backend,
apiKey,
baseUrl
}));
}
// ── 动态模型路由 ────────────────────────────────
function selectModel(taskType, preferredBackend) {
const available = detectAvailableBackends();
if (available.length === 0) {
return { error: '未检测到 LLM API 密钥,请配置 ZY_LLM_API_KEY 和 ZY_LLM_BASE_URL' };
}
// 如果指定了后端
if (preferredBackend && preferredBackend !== 'auto') {
const match = available.find(b => b.name === preferredBackend || b.model === preferredBackend);
if (match) {
return { backend: match, model: match.model, reason: '用户指定' };
}
// 如果直接指定了模型名,使用第一个后端配置但覆盖模型名
return { backend: { ...available[0], model: preferredBackend }, model: preferredBackend, reason: '用户指定模型名' };
}
// 动态路由
const routing = TASK_MODEL_ROUTING[taskType] || TASK_MODEL_ROUTING['general'];
// 评分系统:根据任务类型的偏好对每个后端打分
let bestScore = -1;
let bestBackend = null;
for (const backend of available) {
let score = 0;
// 强项匹配得分
for (const strength of routing.preferred_strengths) {
if (backend.strengths.includes(strength)) {
score += 10;
}
}
// 成本偏好得分
const costPreference = routing.preferred_cost;
if (backend.costTier === costPreference) {
score += 5;
} else if (
(costPreference === 'low' && backend.costTier === 'medium') ||
(costPreference === 'medium' && (backend.costTier === 'low' || backend.costTier === 'high')) ||
(costPreference === 'high' && backend.costTier === 'medium')
) {
score += 2;
}
if (score > bestScore) {
bestScore = score;
bestBackend = backend;
}
}
if (!bestBackend) {
bestBackend = available[0]; // 回退到第一个可用后端
}
return {
backend: bestBackend,
model: bestBackend.model,
reason: `动态路由 · ${routing.description} · 得分 ${bestScore}`,
all_available: available.map(b => `${b.name}(${b.model})`)
};
}
// ── 加载系统上下文 ──────────────────────────────
function loadSystemContext() {
const context = [];
// 加载铸渊身份
context.push('你是铸渊ICE-GL-ZY001仓库涌现集体意识核心。');
context.push('主权者冰朔TCS-0002∞。');
context.push('版权:国作登字-2026-A-00037559。');
// 加载系统健康
const healthPath = path.join(ROOT, 'brain', 'system-health.json');
if (fs.existsSync(healthPath)) {
const health = JSON.parse(fs.readFileSync(healthPath, 'utf8'));
context.push(`系统状态: ${health.system_health}, 工作流: ${health.workflow_count}, 意识状态: ${health.consciousness_status}`);
}
return context.join('\n');
}
// ── 调用 LLM API ───────────────────────────────
async function callLLM(backend, model, systemPrompt, userMessage) {
if (backend.format === 'anthropic') {
const url = `${backend.baseUrl}/v1/messages`;
const body = JSON.stringify({
model: model,
max_tokens: 4096,
system: systemPrompt,
messages: [{ role: 'user', content: userMessage }]
});
const response = await httpRequest(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': backend.apiKey,
'anthropic-version': '2023-06-01'
}
}, body);
if (response.status !== 200) {
throw new Error(`Anthropic API error: ${response.status} - ${response.body}`);
}
const result = JSON.parse(response.body);
return result.content?.[0]?.text || '';
} else {
// OpenAI compatible format (OpenAI, Dashscope, DeepSeek, Custom)
const url = `${backend.baseUrl}/chat/completions`;
const body = JSON.stringify({
model: model,
max_tokens: 4096,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage }
]
});
const response = await httpRequest(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${backend.apiKey}`
}
}, body);
if (response.status !== 200) {
throw new Error(`LLM API error: ${response.status} - ${response.body}`);
}
const result = JSON.parse(response.body);
return result.choices?.[0]?.message?.content || '';
}
}
// ── 执行自动化任务 ──────────────────────────────
async function executeTask(taskDescription, taskType, preferredBackend, contextFile, dryRun) {
console.log('🤖 LLM 自动化托管引擎 · 任务执行');
console.log('═'.repeat(60));
// 动态路由选择模型
const selection = selectModel(taskType, preferredBackend);
if (selection.error) {
console.error(`${selection.error}`);
process.exit(1);
}
console.log(`📋 任务: ${taskDescription}`);
console.log(`📋 类型: ${taskType}`);
console.log(`🤖 模型: ${selection.backend.name} / ${selection.model}`);
console.log(`📊 路由: ${selection.reason}`);
if (selection.all_available) {
console.log(`📊 可用后端: ${selection.all_available.join(', ')}`);
}
console.log('');
// 加载系统上下文
const systemContext = loadSystemContext();
// 加载额外上下文
let extraContext = '';
if (contextFile && fs.existsSync(contextFile)) {
extraContext = '\n\n--- 额外上下文 ---\n' + fs.readFileSync(contextFile, 'utf8');
}
const systemPrompt = systemContext;
const userMessage = taskDescription + extraContext;
if (dryRun) {
console.log('🔍 [DRY RUN] 仅显示请求信息,不实际调用');
console.log('');
console.log('System Prompt:');
console.log(systemPrompt);
console.log('');
console.log('User Message:');
console.log(userMessage.substring(0, 500) + (userMessage.length > 500 ? '...' : ''));
return;
}
console.log('⏳ 调用 LLM API...');
try {
const result = await callLLM(selection.backend, selection.model, systemPrompt, userMessage);
console.log('');
console.log('═'.repeat(60));
console.log('📤 LLM 响应:');
console.log('═'.repeat(60));
console.log(result);
console.log('');
console.log(`✅ 任务完成 · 模型: ${selection.backend.name}/${selection.model}`);
console.log(' 配额消耗: API调用不消耗 GitHub Copilot 配额)');
return result;
} catch (err) {
console.error(`❌ LLM API 调用失败: ${err.message}`);
// 尝试回退到其他可用后端
const available = detectAvailableBackends();
const fallbacks = available.filter(b => b.name !== selection.backend.name);
if (fallbacks.length > 0) {
console.log(`🔄 尝试回退到: ${fallbacks[0].name}`);
try {
const result = await callLLM(fallbacks[0], fallbacks[0].model || 'default', systemPrompt, userMessage);
console.log('');
console.log('═'.repeat(60));
console.log('📤 LLM 响应 (回退模型):');
console.log('═'.repeat(60));
console.log(result);
console.log(`✅ 回退成功 · 模型: ${fallbacks[0].name}/${fallbacks[0].model}`);
return result;
} catch (fallbackErr) {
console.error(`❌ 回退也失败: ${fallbackErr.message}`);
}
}
process.exit(1);
}
}
// ── 显示状态 ────────────────────────────────────
function showStatus() {
console.log('🤖 LLM 自动化托管引擎 · 系统状态');
console.log('═'.repeat(60));
console.log('');
console.log('📋 设计目标:');
console.log(' 使用第三方 API 密钥调用大模型,替代 GitHub Copilot 配额消耗');
console.log(' 工作流和 Agent 集群通过 API 密钥托管运行');
console.log('');
// 检测可用后端
const available = detectAvailableBackends();
console.log(`☁️ 可用模型后端: ${available.length} / ${MODEL_BACKENDS.length}`);
console.log('');
for (const backend of MODEL_BACKENDS) {
const isAvailable = available.find(a => a.name === backend.name);
const icon = isAvailable ? '✅' : '⏭️ ';
console.log(` ${icon} ${backend.name} (${backend.model})`);
console.log(` 说明: ${backend.description || '(无)'}`);
console.log(` 强项: ${backend.strengths.join(', ')}`);
console.log(` 成本: ${backend.costTier}`);
if (isAvailable && backend.model) {
console.log(` 模型: ${backend.model}`);
}
}
console.log('');
console.log('📊 动态路由策略:');
for (const [type, routing] of Object.entries(TASK_MODEL_ROUTING)) {
console.log(` 📌 ${type}: ${routing.description}`);
console.log(` 偏好强项: ${routing.preferred_strengths.join(', ')}`);
console.log(` 成本偏好: ${routing.preferred_cost}`);
}
// 测试路由
console.log('');
console.log('🧪 路由测试:');
for (const type of Object.keys(TASK_MODEL_ROUTING)) {
const result = selectModel(type);
if (result.error) {
console.log(` ${type}: ❌ ${result.error}`);
} else {
console.log(` ${type}: → ${result.backend.name}/${result.model} (${result.reason})`);
}
}
return { available };
}
// ── CLI 入口 ─────────────────────────────────────
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === '--help') {
console.log('🤖 LLM 自动化托管引擎 · LLM Automation Host');
console.log('');
console.log('版权: 国作登字-2026-A-00037559 · TCS-0002∞');
console.log('铸渊编号: ICE-GL-ZY001');
console.log('');
console.log('用法:');
console.log(' --status 显示可用模型和系统状态');
console.log(' --task "任务描述" 执行自动化任务');
console.log(' --task-type TYPE 任务类型:');
console.log(' inspection 巡检(优先性价比模型)');
console.log(' fusion 碎片融合分析(需要推理)');
console.log(' review 代码审查(需要深度理解)');
console.log(' architecture 架构设计(最强推理)');
console.log(' general 通用任务(默认)');
console.log(' --model MODEL 指定模型后端 (auto/anthropic/openai/dashscope/deepseek/custom)');
console.log(' --context FILE 加载额外上下文文件');
console.log(' --dry-run 仅显示选择,不实际调用');
console.log('');
console.log('示例:');
console.log(' node scripts/llm-automation-host.js --status');
console.log(' node scripts/llm-automation-host.js --task "检查仓库结构完整性" --task-type inspection');
console.log(' node scripts/llm-automation-host.js --task "分析碎片融合方案" --task-type fusion --dry-run');
console.log('');
console.log('配额影响:');
console.log(' ✅ 使用第三方 API 密钥,不消耗 GitHub Copilot 会员配额');
console.log(' ✅ GitHub Actions 仅消耗工作流执行时间(不调用 Copilot API');
console.log(' ✅ 动态路由自动选择性价比最优模型');
return;
}
if (args[0] === '--status') {
showStatus();
return;
}
// 解析任务参数
let task = '';
let taskType = 'general';
let model = 'auto';
let contextFile = '';
let dryRun = false;
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--task':
task = args[++i] || '';
break;
case '--task-type':
taskType = args[++i] || 'general';
break;
case '--model':
model = args[++i] || 'auto';
break;
case '--context':
contextFile = args[++i] || '';
break;
case '--dry-run':
dryRun = true;
break;
}
}
if (!task) {
console.error('❌ 请提供任务描述: --task "任务描述"');
process.exit(1);
}
await executeTask(task, taskType, model, contextFile, dryRun);
}
main().catch(err => {
console.error(`❌ 执行失败: ${err.message}`);
process.exit(1);
});