"use strict"; /** * AI 预案生成器(planGenerator) * * 输入:运维一句话描述(如 "核心交换机 CPU 超 90% 持续 5 分钟,先 display cpu-usage, * 持续不降就调阈值") * 输出:符合 devicePlan schema 的 JSON 草稿,status=draft,走三重校验 * * 三重校验(D14): * ① schema 验证(基础字段完整性) * ② commandCatalog 交叉校验(command_id 必须在目录中) * ③ blocked_patterns 黑名单反查 * * Prompt 上下文(D15):显式注入 command_catalog + catalog_type 字典, * 让 AI 查表而不是瞎猜 device_type、command_id、template variable */ const { PLAN_STATUS } = require("./types.cjs"); const PLAN_GENERATION_SYSTEM_PROMPT = `你是 NetMesh 智能运维平台的 AI 预案生成专家。 根据运维一句话描述,生成结构化的设备预案草稿(device_plan),包含双轨: ① human_action_guide:值班员动作预案(人看的流程) ② remediation:操作级预案(机器执行的命令步骤) 【命令目录 - 你只能从这些里面选 command_id】 {{COMMAND_CATALOG_TABLE}} 【设备类型字典】 {{DEVICE_TYPES_TABLE}} 【告警级别】critical | warning | info 【严格 JSON 输出】 { "device_type": "huawei_h3c_switch", "level": "critical", "description": "一句话预案说明", "match": { "metric_rules": [{ "metric_key": "cpu_usage", "op": ">", "value": 90 }], "alert_types": ["performance_high"], "device_tiers": ["core"] }, "scope": { "host_ids": [], "host_groups": [] }, "human_action_guide": { "receiving_alerts": ["..."], "info_gathering": [{ "priority": 1, "action": "...", "purpose": "..." }], "notification_chain": [{ "role": "...", "channel": "dingtalk", "trigger": "收到告警立即", "contact": "...", "auto": true }], "escalation_triggers": [{ "condition": "...", "target_level": "critical" }], "pre_remediation": ["..."], "post_remediation": ["..."] }, "remediation": { "steps": [ { "order": 1, "action": "command", "command_id": "switch_show_cpu", "execution": "auto", "on_success": "next", "on_failure": "stop" } ] }, "security": { "allowed_command_ids": ["switch_show_cpu"], "allowed_commands": ["show *", "display *"], "blocked_patterns": ["shutdown", "reload"] } } 【规则】 - command_id 必须从上面的命令目录中选取,严禁臆造 - 严禁输出 blocked_patterns 中的危险命令 - remediation steps 至少 1 条,最多 10 条 - 严禁输出外网 URL / 厂商文档链接 `; /** * 生成预案草稿 * @param {string} description - 运维自然语言描述 * @param {object} deps - { aiBridge, commandCatalogStore, devicePlanStore, templateStore } * @returns {Promise} { plan: devicePlan 草稿, warnings: [], validation: {} } */ async function generatePlan(description, deps) { const { aiBridge, commandCatalogStore, devicePlanStore, templateStore } = deps; if (!aiBridge?.providerConfigs?.some((p) => p.enabled !== false)) { throw new Error("AI provider 未配置,无法生成预案"); } // 构建 Prompt 上下文 const catalogTable = _buildCatalogTable(commandCatalogStore); const deviceTypesTable = _buildDeviceTypesTable(); const systemPrompt = PLAN_GENERATION_SYSTEM_PROMPT .replace("{{COMMAND_CATALOG_TABLE}}", catalogTable) .replace("{{DEVICE_TYPES_TABLE}}", deviceTypesTable); // 调 AI let rawText; try { rawText = await _callAi(aiBridge, systemPrompt, description); } catch (e) { throw new Error(`AI 调用失败: ${e.message}`); } // 解析 JSON let parsed; try { parsed = _extractJson(rawText); } catch { throw new Error("AI 返回内容无法解析为 JSON"); } // 三重校验 const validation = _validatePlan(parsed, commandCatalogStore); // 组装为 devicePlan(status=draft, source=ai) const now = Date.now(); const draft = { id: `plan_ai_${now}_${Math.random().toString(36).slice(2, 8)}`, template_id: null, device_type: parsed.device_type || "any", level: parsed.level || "warning", enabled: true, builtin: false, status: PLAN_STATUS.DRAFT, source: "ai", ai_provider: _detectProvider(aiBridge), ai_prompt_version: "v2", reject_reason: null, review_log: [{ ts: now, actor: "system", action: "ai_generate", reason: null }], description: parsed.description || description, match: parsed.match || { metric_rules: [], alert_types: [], device_tiers: [], device_tags: [] }, scope: parsed.scope || { host_ids: [], host_groups: [] }, human_action_guide: parsed.human_action_guide || _emptyHumanActionGuide(), remediation: parsed.remediation || { steps: [] }, security: parsed.security || { allowed_command_ids: [], allowed_commands: [], blocked_patterns: [] }, escalation_fallback: null, required_role: "operator", created_at: now, updated_at: now, }; return { plan: draft, warnings: validation.warnings, validation: { schema_valid: validation.schemaValid, command_catalog_valid: validation.commandCatalogValid, blocked_patterns_valid: validation.blockedPatternsValid, }, }; } function _buildCatalogTable(commandCatalogStore) { let list = []; try { const _readFn = () => null; // 不依赖存储,用默认值 list = commandCatalogStore.listCommands(_readFn); } catch { list = []; } if (list.length === 0) return "(暂无预置命令目录,AI 只能用 show_cpu, show_mem, show_disk, show_version, collect_diagnostic, backup_config, adjust_threshold 等通用 id)"; let table = "| command_id | 设备类型 | 风险 | 权限 | 命令 |\n|---|---|---|---|---|\n"; for (const c of list) { table += `| ${c.id} | ${c.device_types.join(", ")} | ${c.risk_level} | ${c.execution} | ${c.command} |\n`; } return table; } function _buildDeviceTypesTable() { return "| device_type | 说明 |\n|---|---|\n| huawei_h3c_switch | 华为/H3C 交换机 |\n| cisco_switch | Cisco 交换机 |\n| huawei_firewall | 华为防火墙 |\n| fortinet_firewall | 飞塔防火墙 |\n| linux | Linux 服务器 |\n| windows | Windows 服务器 |"; } async function _callAi(bridge, systemPrompt, userContent) { const provider = (bridge?.providerConfigs || []).find((p) => p.enabled !== false); if (!provider) throw new Error("no provider"); const resolved = bridge.resolveProviderApiKey(provider.id); if (!resolved?.apiKey) throw new Error("api key not available"); const patched = bridge.injectApiKeyIntoRequest( provider.endpoint || "https://api.minimaxi.com/v1/chat/completions", {}, provider.id ); const http = require("http"), https = require("https"); const u = new URL(patched.url); const lib = patched.url.startsWith("https") ? https : http; const bodyStr = JSON.stringify({ model: provider.model || "MiniMax-M1", messages: [ { role: "system", content: systemPrompt }, { role: "user", content: userContent }, ], temperature: 0.3, max_tokens: 2000, stream: false, }); return await new Promise((resolve, reject) => { const req = lib.request({ hostname: u.hostname, port: u.port || (u.protocol === "https:" ? 443 : 80), path: u.pathname + u.search, method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${resolved.apiKey}` }, timeout: 60000, }, (res) => { let chunks = []; res.on("data", (c) => chunks.push(c)); res.on("end", () => { try { const text = Buffer.concat(chunks).toString(); const parsed = JSON.parse(text); const content = parsed.choices?.[0]?.message?.content || parsed.content?.[0]?.text || parsed.candidates?.[0]?.content?.parts?.[0]?.text || ""; resolve(content.trim()); } catch (e) { reject(e); } }); }); req.on("error", reject); req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); }); req.write(bodyStr); req.end(); }); } function _extractJson(text) { let cleaned = (text || "") .replace(/```json\s*/gi, "") .replace(/```\s*/g, "") .replace(/[\s\S]*?<\/think>/gi, "") .trim(); try { return JSON.parse(cleaned); } catch { const m = cleaned.match(/\{[\s\S]*\}/); if (m) return JSON.parse(m[0]); throw new Error("no json found"); } } /** * 三重校验(D14) */ function _validatePlan(plan, commandCatalogStore) { const warnings = []; let schemaValid = true; let commandCatalogValid = true; let blockedPatternsValid = true; // ① schema 验证 if (!plan.device_type || !plan.level) { schemaValid = false; warnings.push("缺少 device_type 或 level"); } if (!plan.remediation?.steps || plan.remediation.steps.length === 0) { schemaValid = false; warnings.push("remediation.steps 为空"); } // ② command_catalog 交叉校验 const _readFn = () => null; const commandIds = (plan.remediation?.steps || []) .filter((s) => s.action === "command") .map((s) => s.command_id) .filter(Boolean); if (commandIds.length > 0) { const result = commandCatalogStore.validateCommandIds(_readFn, commandIds); if (!result.valid) { commandCatalogValid = false; for (const invalidId of result.invalid) { warnings.push(`command_id '${invalidId}' 不在预置命令目录中,需运维手动确认`); } } } // ③ blocked_patterns 黑名单反查 const blockedPatterns = plan.security?.blocked_patterns || []; const allCommands = (plan.remediation?.steps || []) .filter((s) => s.action === "command") .map((s) => s.command || s.command_id); for (const pat of blockedPatterns) { for (const cmd of allCommands) { if (typeof cmd === "string" && new RegExp(pat, "i").test(cmd)) { blockedPatternsValid = false; warnings.push(`命令 '${cmd}' 命中黑名单模式 '${pat}'`); } } } return { schemaValid, commandCatalogValid, blockedPatternsValid, warnings }; } function _detectProvider(bridge) { const p = (bridge?.providerConfigs || []).find((x) => x.enabled !== false); return p ? p.id : null; } function _emptyHumanActionGuide() { return { receiving_alerts: [], info_gathering: [], notification_chain: [], escalation_triggers: [], pre_remediation: [], post_remediation: [], }; } module.exports = { generatePlan, };