# 05 - Builtin 内置工具 > 本文由 opsxcli 同名设计文档适配改写,结合 aiops Agent CLI 的 Web 实现(`backend/pkg/agentcli/`)。 > opsxcli 原文讨论「builtin 基础命令(ls/cat/grep 等)的 Go 原生实现 vs 命令转发」抉择;aiops 版剥离了所有本地命令,仅保留 2 个工具:`ssh_execute`(远程执行)+ `analyze_output`(输出分析)。本文聚焦这 2 个工具的设计。 ## 设计目标 为 aiops Agent CLI 提供**最小而充分**的工具集: - `ssh_execute` 覆盖所有「在远程主机做事」的需求(排障、安装、配置、重启) - `analyze_output` 解决「命令输出过长导致 LLM token 爆炸」的问题 工具数量从 opsxcli 的 6 个精简为 2 个,但能力边界通过 LLM 自主编排命令来扩展,而非通过堆工具。 ## 工具 1:ssh_execute(远程命令执行) ### 定位 在目标主机上执行任意 shell 命令,返回合并的 stdout + stderr + exit_code。这是 Agent 真正「动手」的唯一通道。 ### 实现 ```go // backend/pkg/agentcli/tools/ssh_execute.go type SSHExecuteTool struct { cfg remoteexec.SSHConfig // 目标主机连接配置(含凭据,由 controller 注入) timeout time.Duration // 默认命令超时(60s) } func (t *SSHExecuteTool) Name() string { return "ssh_execute" } func (t *SSHExecuteTool) Description() string { return fmt.Sprintf( "在远程服务器 %s(用户 %s)上执行 shell 命令并返回合并的 stdout+stderr 输出与退出码。"+ "用于排障(查看日志/进程/磁盘/网络)、安装与配置软件、重启服务等。"+ "高危命令(rm -rf /、mkfs、reboot 等)需用户确认。", host, user) } func (t *SSHExecuteTool) Parameters() map[string]interface{} { return map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "command": map[string]interface{}{ "type": "string", "description": "要执行的 shell 命令,例如 df -h、systemctl status nginx、tail -n 100 /var/log/messages", }, "timeout": map[string]interface{}{ "type": "integer", "description": "命令超时秒数(默认 60)", }, }, "required": []string{"command"}, } } ``` ### 风险评估:DynamicRiskTool `ssh_execute` 是典型「静态等级无法反映真实风险」的工具,因此实现 `DynamicRiskTool`: ```go // 静态等级:High(保守) func (t *SSHExecuteTool) RiskLevel() RiskLevel { return RiskHigh } // 动态等级:看具体命令 func (t *SSHExecuteTool) RiskLevelForArgs(args map[string]interface{}) RiskLevel { return AssessBashRisk(StringParam(args, "command")) } ``` | 命令示例 | 动态风险 | 安全模式 balanced 下的行为 | |----------|----------|--------------------------| | `df -h` / `ps aux` / `cat /etc/hosts` | Safe | 直接执行 | | `mkdir /tmp/foo` / `touch x` | Low | 直接执行 | | `chmod 644 /etc/cfg` / `cp a b` | Medium | strict 需确认,balanced 放行 | | `systemctl restart nginx` / `kill 1234` | High | 弹框确认 | | `rm -rf /var/log/*` | Critical | **硬拒**(不可确认放行) | ### Execute 流程 ```go func (t *SSHExecuteTool) Execute(ctx context.Context, args map[string]interface{}) (*Result, error) { command := strings.TrimSpace(StringParam(args, "command")) if command == "" { return &Result{Success: false, Error: "参数 command 不能为空"}, nil } // ... timeout 解析 res, err := remoteexec.Exec(t.cfg, command, timeout) if err != nil { return &Result{Success: false, Error: "SSH 连接失败: " + err.Error()}, nil } // 关键:附带 exit_code,让 LLM 判断命令是否成功(排障关键信号) output := res.Output + fmt.Sprintf("[exit_code=%d]", res.ExitCode) return &Result{Success: res.Success, Output: output, Error: res.Error}, nil } ``` **设计要点**: - **复用 aiops 既有 `pkg/remoteexec`**:Go-native SSH,无外部依赖,凭据由 controller 从 `model.Server` 关联凭证系统构建后注入 - **`exit_code` 是关键信号**:LLM 通过 `exit_code` 判断命令是否真正成功(避免 LLM 误以为「输出有内容」就是「执行成功」) - **连接级失败与命令级失败分离**:SSH 连不上返回 `Error`,命令执行返回 `exit_code != 0`,让 LLM 区分排查方向 - **凭据不暴露给 LLM**:`SSHConfig` 是工具私有的,LLM 只看到 `command` 参数 ## 工具 2:analyze_output(输出分析) ### 定位 对一段(可能很长的)命令输出做摘要:统计行数、提取疑似错误/告警行、必要时截断保留首尾。**纯文本处理,不执行任何命令**,因此 `RiskSafe`。 ### 为什么需要它? LLM 上下文窗口有限(即使 128k 模型,超长输出也会显著增加成本和延迟)。`ssh_execute` 单次输出截断在 10000 字符,但即便如此,日志类输出(如 `journalctl -n 1000`)仍可能让 LLM 难以聚焦。 `analyze_output` 让 LLM 自主决定「输出太长时先分析再推理」: ``` Round 3: ssh_execute "journalctl -u nginx -n 500" → 输出 500 行 Round 4: analyze_output(output, focus="error") → 提取 8 行疑似错误 + 截断保留首尾 Round 5: LLM 基于精炼后的信息推理 ``` ### 实现 ```go // backend/pkg/agentcli/tools/builtin.go func (t *AnalyzeOutputTool) Execute(ctx context.Context, args map[string]interface{}) (*Result, error) { output := StringParam(args, "output") focus := strings.ToLower(StringParam(args, "focus")) // 1. 超长截断:保留前 40 + 后 40 行,中间用省略标注 // 2. 提取疑似错误/告警行(errorKeywords,含中英文) // - 英文:error / fail / exception / traceback / denied / timeout / refused / critical / fatal // - 中文:错误 / 失败 / 拒绝 / 超时 / 无法 / 异常 // - 若传 focus,把 focus 作为额外关键词优先匹配 // 3. 最多 20 条疑似错误行 // 4. 组装摘要:总行数 + 截断标注 + 错误行 + 输出片段 } ``` **错误关键词列表**: ```go errorKeywords := []string{ "error", "fail", "exception", "traceback", "denied", "permission", "timeout", "refused", "unavailable", "critical", "fatal", "错误", "失败", "拒绝", "超时", "无法", "异常", } ``` **输出格式示例**: ``` 总行数: 500(已截断保留首尾) 疑似错误/告警行 (8): nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) nginx: [emerg] bind() to [::]:80 failed (98: Address already in use) ... 输出片段: ... (前 40 行) ... [已省略 420 行] ... ... (后 40 行) ``` ## 工具组合:典型 ReAct 序列 `ssh_execute` 与 `analyze_output` 的组合覆盖绝大多数运维场景: ### 场景 1:磁盘满排障 ``` Round 1: ssh_execute "df -h" → 发现 /var 满 Round 2: ssh_execute "du -sh /var/*" → 定位 /var/log 最大 Round 3: ssh_execute "ls -lah /var/log | head -20" → 找到 access.log 巨大 Round 4: 给出清理建议(不直接 rm,让用户确认) ``` ### 场景 2:服务启动失败 ``` Round 1: ssh_execute "systemctl status nginx" → failed Round 2: ssh_execute "journalctl -u nginx -n 500" → 500 行日志 Round 3: analyze_output(output, focus="error") → 提取 8 行错误,端口被占 Round 4: ssh_execute "ss -tlnp | grep :80" → 确认 80 端口被占用 Round 5: 给出根因 + 解决建议 ``` ### 场景 3:性能排查 ``` Round 1: ssh_execute "top -bn1 | head -20" → CPU 100% 进程 Round 2: ssh_execute "ps aux --sort=-%cpu | head -10" → 定位 java 进程 Round 3: ssh_execute "jstack " → 线程栈 Round 4: analyze_output(jstack_output, focus="BLOCKED") → 死锁线索 Round 5: 给出根因(线程死锁) ``` ## 工具与 Prompt 的协作 Prompt Layer 2 明确告诉 LLM: > 排障时优先使用只读命令(df/ps/cat/grep/tail/systemctl status/journalctl)收集信息,确认根因后再动手修复。 Prompt Layer 3 自动列出工具描述(来自 `buildToolDescriptions()`): ``` 【可用工具】 - ssh_execute: 在远程服务器 ... 上执行 shell 命令 ... - analyze_output: 分析一段(可能很长的)命令输出文本 ... ``` 工具的命名和描述本身就是给 LLM 的「使用说明书」,描述写得好,LLM 就能正确选用。 ## 扩展指南 ### 何时应该新增工具(而非用 ssh_execute)? 满足以下任一条件: 1. **命令协议复杂、LLM 容易写错**(如 `kubectl apply -f` 需要 YAML 参数,用 `ssh_execute` 难以表达) 2. **需要结构化输入/输出**(如数据库查询返回表结构) 3. **需要专门的风险评估逻辑**(如 `kubectl delete` 需要按资源类型分级) 如果只是「执行某条命令」,用 `ssh_execute` 即可,不需要新工具。 ### 实现步骤 见 [04 - 插件架构设计](04-插件架构设计.md) 的「添加新工具」章节。 ## 常见陷阱 | 陷阱 | 解决方案 | |------|----------| | `ssh_execute` 输出过长撑爆上下文 | `OutputMaxLength=10000` 截断 + 引导调用 `analyze_output` | | LLM 把「输出有内容」误判为「命令成功」 | 输出末尾附 `[exit_code=N]`,Prompt 显式提醒 | | `analyze_output` 关键词覆盖不全 | 扩展 `errorKeywords` 列表(含中英文) | | 命令超时无反馈 | `context.WithTimeout(ctx, ToolTimeout)` + 失败信息回写 | | 凭据泄漏给 LLM | `SSHConfig` 是工具私有字段,不在 `Parameters` 中暴露 | ## 总结 aiops Agent CLI 的工具集设计遵循「少即是多」:2 个工具 + LLM 自主编排,覆盖绝大多数远程运维场景。`ssh_execute` 是「手」(动手做事),`analyze_output` 是「放大镜」(聚焦关键信息),二者配合让 Agent 既有行动力又有判断力。 > 作者:Logan.Li