# 02 - 整体架构设计 > 本文由 opsxcli 同名设计文档适配改写,结合 aiops Agent CLI 的 Web 实现(`backend/pkg/agentcli/`)。 > opsxcli 的「cmd/ + plugins/ + internal/」CLI 三层在 aiops 中改造为「controller/(HTTP/WS handler)+ model/(GORM)+ pkg/agentcli/(引擎库)+ frontend/(React UI)」四层。 ## 项目结构(Agent CLI 相关部分) ``` aiops/ ├── backend/ │ ├── controller/ │ │ └── agent_cli.go # WebSocket 控制器(HTTP/WS handler 层) │ │ # - /ws/agent-cli?id= 入口 │ │ # - 构建 Engine、注入回调、事件落库 │ │ # - ListSessions/GetSession/DeleteSession REST │ ├── model/ │ │ ├── agent_cli.go # AgentCLISession / AgentCLIMessage(GORM 模型) │ │ └── llm.go # LLMConfig(ai_llm_configs 表) │ ├── pkg/ │ │ ├── agentcli/ # Agent 引擎库(零业务依赖,核心) │ │ │ ├── agentcli.go # Engine 顶层入口 │ │ │ ├── core/ # ReAct 主循环 + EventCallback │ │ │ ├── tools/ # Registry + ssh_execute + analyze_output + risk │ │ │ ├── safety/ # 安全控制器(ConfirmFn 注入) │ │ │ ├── prompt/ # 五层 System Prompt │ │ │ ├── evolver/ # 四层记忆 + 精简进化循环 │ │ │ └── llm/ # OpenAI 兼容客户端 + 类型定义 │ │ └── remoteexec/ # 复用 aiops 既有 Go-native SSH │ └── router/routes.go # 路由注册:WS + REST 会话管理 │ ├── frontend/ │ ├── src/ │ │ ├── components/aiops/AgentCLI.tsx # React 对话流组件 │ │ ├── api/agentCli.ts # 会话历史 REST 客户端 │ │ └── components/ui/confirm-dialog.tsx# useConfirm() 高危确认 │ └── ... │ └── data/agentcli/ # Evolver 记忆目录(运行时生成) ├── experience.jsonl # 经验记忆 ├── factual.jsonl # 事实记忆 ├── procedural.jsonl # 程序记忆(接口位) └── environment.json # 环境记忆(已知主机) ``` ## 架构设计原则 ### 1. 分层 + 依赖注入 aiops Agent CLI 的核心约束是「引擎库零业务依赖」: - **controller 层** —— 负责协议适配(WebSocket)、凭据构建、事件持久化、用户隔离 - **model 层** —— GORM 模型(会话、消息、LLM 配置、Server) - **pkg/agentcli 层** —— 纯算法库,只依赖 `pkg/remoteexec` 和自己的子包,**不 import model/controller** - **frontend 层** —— React 对话流 UI,纯事件消费 + 控制消息发送 依赖通过函数参数注入: ```go // controller/agent_cli.go 关键注入点 engine.SetCallback(ac.onEvent) // EventCallback:推 WS + 落库 engine.SetConfirmFn(ac.confirmFn) // ConfirmFn:高危确认(阻塞等前端回传) ``` 引擎因此可被任何调度方复用(不仅是 WebSocket,未来也可被 cron 或审批流调用)。 ### 2. 工具化设计 每个工具都是独立的 struct,实现 `Tool` 接口即可注册: ``` pkg/agentcli/tools/ ├── registry.go # Registry(线程安全) + Tool/DynamicRiskTool 接口 + RiskLevel ├── ssh_execute.go # 远程命令执行(DynamicRiskTool) ├── builtin.go # analyze_output(只读 Safe) └── risk.go # 黑名单 + AssessBashRisk 动态评估 ``` > 与 opsxcli 的差异:opsxcli 把风险评估放在 `safety/` 包,aiops 为避免 `tools` ↔ `safety` 循环依赖,把 `risk.go` 置于 `tools` 包,由 `ssh_execute` 直接调用。 ### 3. 配置集中化(DB 驱动) opsxcli 通过 `~/.opsxcli/config.yaml` 和环境变量加载配置;aiops 配置来自 DB: | 配置项 | opsxcli | aiops | |--------|---------|-------| | LLM 配置 | `~/.opsxcli/providers/*.json` | DB 表 `ai_llm_configs`(model.LLMConfig) | | LLM 配置入口 | CLI 向导 `wizard.go` | 「大模型设置」Web 页面 | | SSH 凭据 | `~/.ssh/config` 解析 | model.Server 关联凭证系统 v2 | | 引擎运行参数 | config.yaml + 环境变量 | `core.DefaultConfig()`(硬编码默认,必要时通过 EngineConfig 覆盖) | | 记忆目录 | `~/.opsxcli/agent/` | 工作目录 `data/agentcli/`(容器化友好) | ### 4. 协议适配层(controller) controller 是「Web 世界」与「引擎世界」的翻译器: | Web 协议 | 引擎调用 | 引擎事件 → Web | |----------|----------|----------------| | `WS start {query}` | `engine.Run(ctx, query, serverInfo)` | emit `thought`/`tool_call`/`tool_result`/`progress`/`done`/`error` → 序列化为 `wsEvent` 推 WS | | `WS confirm/deny {confirm_id}` | 解锁 `ConfirmFn` 的 chan | emit 前由 controller 推 `confirm` 事件 | | `WS abort` | `cancel()` 取消 ctx | emit `error`(ctx.Err()) | | `GET /sessions` | — | 直接查 `agent_cli_sessions` 表 | --- ## 核心流程图 ### WebSocket 握手 + 任务执行完整流程 以用户在 Agent CLI 页面输入「检查 nginx 启动失败的原因」为例: ``` 用户在 Agent CLI 页面选择主机、输入任务 "检查 nginx 启动失败的原因" │ ▼ ┌──────────────────────────────────────────────────┐ │ 1. WebSocket 握手 (controller/agent_cli.go: Run) │ │ ├─ 校验 JWT (middleware.JWTAuth) │ │ ├─ 加载 model.Server (按 ?id=) │ │ ├─ buildSSHConfig: 解析凭证系统 v2 凭据 │ │ ├─ 加载 model.LLMConfig (Enabled=true) │ │ └─ upgrader.Upgrade 升级 WS │ └────────────────────┬─────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────┐ │ 2. 推送 session 事件(主机上下文) │ │ wsEvent{type:"session", message:"已连接主机…"}│ └────────────────────┬─────────────────────────────┘ │ ▼ (前端发来 {type:"start", query:"..."}) ┌──────────────────────────────────────────────────┐ │ 3. startTask │ │ ├─ DB 创建 AgentCLISession (status=running) │ │ ├─ DB 存用户消息 (role=user) │ │ ├─ 构建 Engine (注入 LLM/SSH/SafetyMode) │ │ ├─ engine.SetCallback(ac.onEvent) │ │ ├─ engine.SetConfirmFn(ac.confirmFn) │ │ └─ go engine.Run(ctx, query, serverInfo) │ └────────────────────┬─────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────┐ │ 4. Engine.Run → core.Agent.Run(ReAct 循环) │ │ │ │ ┌───────────────────────────────────┐ │ │ │ Prompt 构建(五层) │ │ │ │ L1 角色 / L2 安全 / L3 工具 │ │ │ │ L4 工作方式 / L5 Evolver 记忆 │ │ │ └──────────────┬────────────────────┘ │ │ ▼ │ │ ┌───────────────────────────────────┐ │ │ │ for iter := 0; iter < 16; iter++ { │ │ │ │ emit progress(round) │ │ │ │ 自适应检查点(iter 3/7/11) │ │ │ │ trimMessages(裁剪历史) │ │ │ │ resp = llm.Complete(...) │ │ │ │ emit thought(resp.Content) │ │ │ │ if 无 tool_calls: │ │ │ │ emit done → triggerEvolve │ │ │ │ return │ │ │ │ for tc in resp.ToolCalls: │ │ │ │ risk = toolRisk(...) │ │ │ │ emit tool_call(name,args,risk) │ │ │ │ approved = safetyCtl.Check(..)│ ←──┐ │ │ │ if !approved: 跳过 │ │ 高危 │ │ │ result = tool.Execute(ctx,args)│ │ 触发 │ │ │ emit tool_result(output,...) │ │ confirm │ │ │ records.append(..) │ │ 事件 │ │ │ } │ │ │ │ │ 循环检测 / 连续失败追踪 │ │ │ │ │ } │ │ │ │ └────────────────────────────────────┘ │ │ └──────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────┐ │ confirmFn (controller) │ │ 推 wsEvent{type:"confirm"}│ │ ch := make(chan bool) │ │ select { │ │ case approved := <-ch: │ │ case <-5min超时: │ │ } │ └─────────┬─────────────────┘ │ 前端 useConfirm() 弹框 │ 发回 {type:"confirm"/"deny"} ▼ resolveConfirm(confirmID, approved) │ ch <- approved ▼ 解锁 safety.Check ``` ### 事件落库映射 `onEvent` 回调把每个事件同时推送 WS 与落库 `agent_cli_messages`: | Event.Type | DB 字段 type | DB 字段 role | 备注 | |------------|--------------|--------------|------| | `thought` | thought | assistant | AI 思考文本 | | `tool_call` | tool_call | tool | 含工具名、参数 JSON、风险等级 | | `tool_result` | tool_result | tool | 含输出、成败、耗时 | | `done` | done | assistant | 最终答案 + token 统计 | | `error` | error | assistant | 错误信息(含达到最大轮次) | | `progress` | — | — | 太频繁,不入库 | ### 系统数据流 ``` ┌────── controller 层(协议适配) ──────┐ │ agent_cli.go (WS handler) │ │ ├─ 加载 Server/LLMConfig │ │ ├─ DB 创建 Session/Message │ │ └─ 注入 Callback/ConfirmFn │ └─────────────────┬─────────────────────┘ │ 依赖注入 ▼ ┌────── pkg/agentcli 层(引擎库) ──────┐ │ agentcli.go (Engine) │ │ ├─ core.Agent (ReAct) │ │ │ ├─ llm.Client (OpenAI 兼容) │ │ │ ├─ tools.Registry │ │ │ │ ├─ ssh_execute │ │ │ │ └─ analyze_output │ │ │ ├─ safety.Controller │ │ │ ├─ prompt.BuildSystemPrompt │ │ │ └─ evolver.EvolverEngine │ │ └─ (零 model/controller 依赖) │ └─────────────────┬─────────────────────┘ │ 调用 ▼ ┌────── pkg/remoteexec(Go-native SSH)─┐ │ Exec(cfg, command, timeout) │ └───────────────────────────────────────┘ ``` ## 技术栈 | 层 | 技术 | |----|------| | 后端框架 | Gin + GORM + gorilla/websocket | | LLM 协议 | OpenAI 兼容 `/chat/completions` + function calling | | SSH | aiops 既有 `pkg/remoteexec`(Go-native,无外部依赖) | | 前端 | React 19 + TypeScript + shadcn/ui(`useConfirm` 弹框) | | 持久化 | MySQL(主库)/ SQLite(降级) —— 复用 aiops 既有 DB | | 记忆存储 | 文件 `data/agentcli/*.jsonl`(与 opsxcli 原生一致,零 DB 依赖) | > 为什么记忆用文件而会话用 DB?记忆是引擎库内部的产物(学习沉淀),与业务无关;会话需要多用户隔离、列表查询、删除等业务操作,必须走 DB。这是「引擎库零业务依赖」原则的延伸。 ## 路由注册 `backend/router/routes.go`: ```go // Agent CLI — AI 运维 Agent(WebSocket 推流 + 会话历史回放) agentCLIHandler := controller.NewAgentCLIHandler(db) r.GET("/ws/agent-cli", middleware.JWTAuth(&cfg.JWT, db), agentCLIHandler.Run) agentCLIGroup := authorized.Group("/aiops/agent-cli") { agentCLIGroup.GET("/sessions", agentCLIHandler.ListSessions) agentCLIGroup.GET("/sessions/:id", agentCLIHandler.GetSession) agentCLIGroup.DELETE("/sessions/:id", agentCLIHandler.DeleteSession) } ``` WebSocket 因无法设自定义 header,JWT 通过 `?token=` query param 由 `JWTAuth` 中间件兼容解析。 ## 添加新模块示例 以「增加一个新工具 `ping_check`」为例: 1. **实现 Tool 接口**(`pkg/agentcli/tools/ping_check.go`): ```go type PingCheckTool struct{ cfg remoteexec.SSHConfig } func (t *PingCheckTool) Name() string { return "ping_check" } func (t *PingCheckTool) Description() string { return "在目标主机 ping 指定地址" } func (t *PingCheckTool) Parameters() map[string]interface{} { /* JSON Schema */ } func (t *PingCheckTool) RiskLevel() tools.RiskLevel { return tools.RiskSafe } func (t *PingCheckTool) Execute(ctx context.Context, args map[string]interface{}) (*tools.Result, error) { // 调用 remoteexec.Exec 执行 ping } ``` 2. **在 Engine 中注册**(`pkg/agentcli/agentcli.go: NewEngine`): ```go registry.Register(tools.NewSSHExecuteTool(cfg.SSH, coreCfg.ToolTimeout)) registry.Register(tools.NewAnalyzeOutputTool()) registry.Register(tools.NewPingCheckTool(cfg.SSH)) // 新增 ``` 3. 工具自动出现在 LLM 的 `tools` 参数中(`registry.ToLLMTools()`),Prompt Layer 3 工具描述自动生成(`buildToolDescriptions()`)。无需改 Prompt、无需改 controller、无需改前端。 ## 设计目标 1. **Web 友好**:所有交互经 WebSocket,适配浏览器对话流 2. **零业务耦合**:引擎库可独立测试与复用 3. **复用 aiops 既有能力**:LLM 配置、SSH、凭证系统、DB 4. **易于扩展**:Tool 接口 + Registry,实现接口即扩展 5. **可审计**:每条事件落库,会话历史可回放 > 作者:Logan.Li