后端引擎 backend/pkg/agentcli/(纯引擎库,零业务依赖,剥离 CLI/TUI 外壳): - llm: OpenAI 兼容客户端,复用 model.LLMConfig - tools: ssh_execute(复用 pkg/remoteexec)+ analyze_output + 风险评估黑名单 - safety: 三级安全模式 + 五级风险 + WebSocket 异步确认 - prompt: 五层 System Prompt - core: ReAct 16 轮循环 + 自适应检查点 + 循环检测 - evolver: 四层记忆 + 进化循环,data/agentcli/ 文件存储 集成层: - controller/agent_cli.go: WebSocket 控制器(事件流推流 + 高危确认 + 会话持久化) - model/agent_cli.go: agent_cli_sessions/messages 表 + AutoMigrate - 路由 /ws/agent-cli + /api/v1/aiops/agent-cli/sessions - seed 权限 aiops:agent-cli / aiops:llm-config 前端: - AgentCLI.tsx: 对话流 + 工具块 + 高危确认 + 四模块 tips 面板 - LLMConfig.tsx: 大模型设置独立页 - api/agentCli.ts + Sidebar/App 菜单路由 文档: - docs/agent/ 11 篇(opsxcli 同名文档适配改写 + 00-README 映射总览) 其他: - CLAUDE.md 补充 agent-cli 模块说明 - 修正 .gitignore 根级二进制规则(前导 /,避免误伤 frontend/src/components/aiops/) Co-Authored-By: Claude <noreply@anthropic.com>
16 KiB
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=<serverID> 入口
│ │ # - 构建 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,纯事件消费 + 控制消息发送
依赖通过函数参数注入:
// 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:
// 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」为例:
- 实现 Tool 接口(
pkg/agentcli/tools/ping_check.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
}
- 在 Engine 中注册(
pkg/agentcli/agentcli.go: NewEngine):
registry.Register(tools.NewSSHExecuteTool(cfg.SSH, coreCfg.ToolTimeout))
registry.Register(tools.NewAnalyzeOutputTool())
registry.Register(tools.NewPingCheckTool(cfg.SSH)) // 新增
- 工具自动出现在 LLM 的
tools参数中(registry.ToLLMTools()),Prompt Layer 3 工具描述自动生成(buildToolDescriptions())。无需改 Prompt、无需改 controller、无需改前端。
设计目标
- Web 友好:所有交互经 WebSocket,适配浏览器对话流
- 零业务耦合:引擎库可独立测试与复用
- 复用 aiops 既有能力:LLM 配置、SSH、凭证系统、DB
- 易于扩展:Tool 接口 + Registry,实现接口即扩展
- 可审计:每条事件落库,会话历史可回放
作者:Logan.Li