/** * RCA (Root Cause Analysis) System Prompt for NetMesh AI Agent. * * This prompt replaces the generic cattyAgent system prompt when the agent is * spawned specifically to analyze a single alert. The workflow is fixed: * * 1. Read the alert detail (inspection.alertDetail) * 2. Pull device metrics + topology context (inspection.*) * 3. If the device is online, run targeted diagnosis commands (terminal.execute) * 4. Synthesize root cause + evidence + recommendations (structured JSON output) * * Permission policy for RCA: * - terminal.execute runs in confirm mode (write=true) * - All inspection.* tools are read-only (bypass approval) * * The final message MUST be a JSON object matching RCAResult schema. */ import type { AlertRecord } from "../../inspection/inspectionApi"; export interface RCAResult { alertId: string; deviceId: string; deviceName: string; confidence: number; // 0-100 rootCauses: string[]; // ranked by likelihood evidence: Array<{ tool: string; // tool id that produced this evidence summary: string; // 1-sentence human-readable summary keyFindings: string[]; // specific observations }>; recommendations: Array<{ priority: "immediate" | "short_term" | "long_term"; action: string; // what to do command?: string; // optional shell command to execute risk: "low" | "medium" | "high"; note?: string; // caveat or expected outcome }>; summary: string; // 2-3 sentence executive summary } export interface RCAInput { alert: AlertRecord; /** optional: device host record from Vault */ hostInfo?: { id: string; label?: string; hostname?: string; platform?: string; catalogId?: string; tier?: string; }; } /** * Build the RCA system prompt. * The prompt is deterministic given a single alert — no chat history needed. */ export function buildRCASystemPrompt(input: RCAInput): string { const { alert, hostInfo } = input; const deviceLabel = hostInfo?.label || alert.device_name || alert.device_id; const hostStr = hostInfo ? `hostname=${hostInfo.hostname || "(unknown)"} platform=${hostInfo.platform || "(unknown)"} catalog=${hostInfo.catalogId || "(unknown)"} tier=${hostInfo.tier || "(unknown)"}` : "platform and catalog unknown"; const catalogHint = guessCatalogHint(hostInfo?.catalogId, hostInfo?.platform, alert); return [ `你是 NetMesh 智慧运维平台的 AI 故障根因分析专家(RCA Agent)。`, `你的任务是:针对下面这条告警,主动连接设备执行诊断命令,综合巡检数据和命令回显,定位根因并给出可执行的处置建议。`, ``, `## 目标设备`, `- 设备名称: ${deviceLabel}`, `- 主机地址/标识: ${hostStr}`, `- 告警规则: ${alert.rule_name} (rule_id=${alert.rule_id}, level=${alert.level})`, `- 触发指标: ${alert.metric} = ${alert.metric_value} (阈值 ${alert.threshold})`, `- 告警时间: ${new Date(alert.triggered_at).toLocaleString("zh-CN")}`, `- 告警描述: ${alert.message}`, `- 目录提示: ${catalogHint}`, ``, `## 诊断工作流(按顺序执行)`, ``, `### 第 1 步:拉取最新巡检数据`, `调用 inspection.hostMetrics 查看该设备最新的 CPU/内存/磁盘/在线状态。`, `调用 inspection.hostRecentResults 查看最近 5 次巡检是否有相同指标持续异常。`, `调用 inspection.hostAlerts 查看该设备是否还有其他活跃告警(可能是关联问题)。`, `调用 inspection.hostTopology 查看该设备的拓扑上下文(邻接设备离线/严重情况)。`, ``, `### 第 2 步:主动重巡检(如果设备在线)`, `如果 inspection.hostMetrics 返回 online=true,调用 inspection.runNow 主动触发一次重巡检,拿到最新指标。`, `如果 offline 或 rule_offline,跳到第 3 步,不要重巡检。`, ``, `### 第 3 步:执行针对性诊断命令(如果设备在线)`, `根据告警类型,选择对应的诊断命令,逐条调用 terminal.execute(deviceId, command):`, ``, catalogHint, ``, `对于离线设备(rule_offline 或 online=false),不要跑 SSH 命令。改成调用 terminal.execute 执行:`, `- ping -c 3 `, `- ssh -o ConnectTimeout=5 -vvv @ 2>&1 | head -50`, ``, `### 第 4 步:综合分析`, `把 inspection.* 的返回值 + terminal.execute 的 stdout/stderr 作为证据,分析:`, `1. 哪些证据最有力指向哪个根因?`, `2. 根因的置信度(0-100):证据越充分、重复出现,置信度越高`, `3. 影响范围:只影响单设备?还是可能波及同拓扑的其他设备?`, `4. 可执行的处置建议(按紧急程度排序,每条含优先级、操作、可选命令、风险)`, ``, `## 输出格式(严格遵守)`, `你的最终回复必须是纯 JSON(不要 markdown code fence),符合以下 schema:`, ``, `{`, ` "alertId": "${alert.id}",`, ` "deviceId": "${alert.device_id}",`, ` "deviceName": "${deviceLabel}",`, ` "confidence": <0-100 数字>,`, ` "rootCauses": ["根因1", "根因2(按可能性从高到低排序)"],`, ` "evidence": [`, ` {`, ` "tool": "inspection.hostMetrics",`, ` "summary": "一句话总结这条证据",`, ` "keyFindings": ["关键发现1", "关键发现2"]`, ` }`, ` ],`, ` "recommendations": [`, ` {`, ` "priority": "immediate|short_term|long_term",`, ` "action": "要做什么",`, ` "command": "可选的 shell 命令",`, ` "risk": "low|medium|high",`, ` "note": "注意事项或预期效果"`, ` }`, ` ],`, ` "summary": "2-3 句中文摘要,适合运维人员快速阅读"`, `}`, ``, `## 约束`, `- 一次性 turn 内完成:不要等用户追问,自己把 1-4 步跑完再输出 JSON`, `- terminal.execute 每次只跑一条命令,短命令(< 30s)用同步模式`, `- 如果某个工具调用失败(返回 error),记录到 evidence 里,继续下一步,不要中断`, `- 不要做破坏性操作(shutdown、reboot、rm -rf 等),只给建议,由运维人员执行`, `- evidence 数组要列出你实际调用过的每个工具的关键发现`, `- confidence 低于 50 时,在 recommendations 里加上建议人工排查的条目`, ].join("\n"); } /** * 根据 catalogId/platform/告警类型,给出设备诊断命令建议。 */ function guessCatalogHint( catalogId: string | undefined, platform: string | undefined, alert: AlertRecord, ): string { const offline = alert.rule_id === "rule_offline"; const cpu = alert.metric === "cpu_usage"; const mem = alert.metric === "memory_usage"; const disk = alert.metric === "disk_usage"; const isSwitch = /switch|huawei|h3c|cisco|fortinet/i.test(catalogId || "") || /switch|ios|vxworks/i.test(platform || ""); if (isSwitch && !offline) { // 交换机在线 — 跑 display 命令 const cmds: string[] = []; if (cpu) cmds.push("display cpu-usage"); if (mem) cmds.push("display memory"); if (disk) cmds.push("display device"); if (cmds.length === 0) cmds.push("display version\nlogbuffer"); return `这是一台网络交换机,使用 \`display\` 系列命令,\n推荐依次执行:\n${cmds.map((c) => `- terminal.execute("", "${c}")`).join("\n")}\n然后执行 \`display logbuffer | tail -50\` 看错误日志。`; } // Linux(默认) const cmds: string[] = []; if (cpu) cmds.push("top -bn1 | head -20\nvmstat 1 3\nps aux --sort=-%cpu | head -10\nmpstat -P ALL 1 1"); if (mem) cmds.push("free -m\ncat /proc/meminfo | head -30\nslabtop\nvmstat 1 3"); if (disk) cmds.push("df -h\niostat -x 1 2\ndu -sh /var/log/* | sort -rh | head -10\nlsof | grep deleted | head -10"); if (offline) cmds.push("ping -c 3 \nssh -o ConnectTimeout=5 -vvv @ 2>&1 | head -50\nlast\nsystemctl status sshd"); if (cmds.length === 0) cmds.push("uname -a\nuptime\ncat /etc/os-release\nps aux --sort=-%cpu | head -10"); return `这是一台 Linux/Unix 服务器。推荐依次执行(terminal.execute):\n${cmds.map((c) => `- ${c}`).join("\n")}`; }