后端引擎 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
06 - Agent 核心引擎设计
本文由 opsxcli 同名设计文档适配改写,结合 aiops Agent CLI 的 Web 实现(
backend/pkg/agentcli/core/agent.go)。 核心算法(16 轮 ReAct 主循环、自适应检查点、循环检测、连续失败追踪、Evolver 集成)全部保留;TUI 回调(Bubble Tea)剥离,改为EventCallback推 WebSocket 事件流。
设计理念
Agent 核心引擎遵循 ReAct (Reasoning + Acting) 范式:
- Reasoning:LLM 推理决定下一步行动(产出
thought文本 + 工具调用计划) - Acting:调用工具执行具体操作(
ssh_execute/analyze_output) - Observing:观察工具结果(
exit_code、输出、耗时),反馈到下一轮推理
通过 16 轮迭代循环,让 Agent 从「盲目执行命令」进化为「有目的地探索和解决问题」。
核心结构
// backend/pkg/agentcli/core/agent.go
type Agent struct {
llm llm.Client // LLM 客户端(OpenAI 兼容)
registry *tools.Registry // 工具注册中心
config *Config // 运行配置
safetyCtl *safety.Controller // 安全控制器
evolver *evolver.EvolverEngine // 自进化引擎(可为 nil,降级运行)
callback EventCallback // 事件回调(controller 注入,推 WS + 落库)
totalTokens int // token 消耗统计
mu sync.Mutex
}
与 opsxcli 的差异:
- 剥离
tokenizer *TokenEstimator独立结构,改为内联的estimateTokens函数- 剥离
lastEvolveResult+evolveResultMu,改为 Evolver 内部维护- 剥离
evolveWg sync.WaitGroup,进化 goroutine 自带recover(),不阻塞主循环
依赖关系
┌─────────┐
│ Agent │
└────┬────┘
┌───────┬───────┼───────┬──────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────────┐┌──────┐┌──────┐┌──────┐┌────────┐
│Config ││Safety││Tools ││Evolver││Prompt │
│配置 ││安全 ││工具 ││进化 ││构建器 │
└────────┘└──────┘└──────┘└──────┘└────────┘
EventCallback —— 替代 TUI 的事件流
opsxcli 通过 ToolCallback 驱动 Bubble Tea TUI 实时渲染;aiops 改为 EventCallback,由 controller 序列化后推 WebSocket + 落库:
type EventType string
const (
EventThought EventType = "thought" // AI 思考文本
EventToolCall EventType = "tool_call" // 即将执行的工具(名/参数/风险)
EventToolResult EventType = "tool_result" // 工具执行结果(输出/成败/耗时)
EventProgress EventType = "progress" // 轮次 + token
EventDone EventType = "done" // 任务完成(最终答案)
EventError EventType = "error" // 错误(含达到最大轮次)
)
type EventCallback func(Event)
controller 的 onEvent 实现同时做两件事:
func (ac *agentConn) onEvent(e core.Event) {
ac.write(wsEvent{...}) // 1. 推 WebSocket
ac.persistMsg(...) // 2. 落库 agent_cli_messages(progress 除外)
}
Config 配置
// backend/pkg/agentcli/core/agent.go
type Config struct {
MaxIterations int // 16 - 最大迭代次数
Temperature float64 // 0.3 - LLM 温度(偏低 = 更确定)
ToolTimeout time.Duration // 120s - 工具执行超时
MaxTokens int // 4096 - 单次 LLM 最大输出 token
SafetyMode safety.Mode // balanced
OutputMaxLength int // 10000 - 输出截断长度
MaxContextTokens int // 8000 - 上下文窗口(裁剪阈值 90%)
MemoryDir string // data/agentcli - Evolver 记忆目录
}
默认值通过 DefaultConfig() 提供。controller 可通过 EngineConfig.Core 覆盖。
与 opsxcli 的差异:opsxcli 用
LoadConfig()从环境变量加载(OPSXCLI_AGENT_*),aiops 改为代码默认值 +EngineConfig显式覆盖(Web 场景下环境变量管理不直观)。
16 轮 ReAct 主循环
流程图
用户输入
│
▼
┌─────────────────────────────────────────────┐
│ ReAct Main Loop │
│ │
│ Round 1 ──→ LLM 推理 → 工具调用 → 观察 │
│ Round 2 ──→ LLM 推理 → 工具调用 → 观察 │
│ Round 3 ──→ LLM 推理 → 工具调用 → 观察 │
│ ═══════ CHECKPOINT 1 (Round 4) ═══════ │
│ Round 4 ──→ 启发式评估 + 纠偏提示 │
│ ... │
│ Round 7 ──→ LLM 推理 → 工具调用 → 观察 │
│ ═══════ CHECKPOINT 2 (Round 8) ═══════ │
│ Round 8 ──→ 强制中间总结 │
│ ... │
│ Round 11 ──→ LLM 推理 → 工具调用 → 观察 │
│ ═══════ CHECKPOINT 3 (Round 12) ═════ │
│ Round 12 ──→ 最终提醒,敦促收尾 │
│ ... │
│ Round 16 ──→ 最大迭代,强制结束 │
└──────────────┬──────────────────────────────┘
│
▼
最终回答 / 总结
│
▼ (后台异步)
┌──────────────┐
│ Evolver 学习 │
└──────────────┘
为什么是 16 轮?
| 迭代次数 | 优点 | 缺点 |
|---|---|---|
| 8 轮 | 快速响应 | 复杂运维任务不够用 |
| 16 轮 | 覆盖 90% 运维场景 | 偶尔多消耗 token |
| 32 轮 | 兜底极端场景 | 成本高、用户等待久 |
16 轮是能力与效率的平衡点,配合自适应检查点避免无意义循环。
主循环核心代码(精简版)
for iter := 0; iter < a.config.MaxIterations; iter++ {
if err := ctx.Err(); err != nil { return nil, err } // 中断检查
a.emit(Event{Type: EventProgress, Round: iter + 1})
// 自适应检查点
if hint := a.checkAdaptiveCheckpoint(iter, ...); hint != "" {
messages = append(messages, llm.Message{Role: "user", Content: hint})
}
trimmed := a.trimMessages(messages) // 裁剪历史
resp, err := a.llm.Complete(ctx, &llm.CompletionRequest{
Messages: trimmed, Tools: llmTools,
Temperature: a.config.Temperature, MaxTokens: a.config.MaxTokens,
})
// ...
a.emit(Event{Type: EventThought, Round: iter + 1, Thought: resp.Message.Content})
if len(resp.Message.ToolCalls) == 0 {
// 最终答案
a.emit(Event{Type: EventDone, Answer: resp.Message.Content, ...})
a.triggerEvolve(ctx, query, serverInfo, answer, toolRecords, true, start)
return &tools.Result{Success: true, Output: answer}, nil
}
// 处理工具调用(含安全检查、循环检测、风险事件)
observations, records, failInc := a.processToolCalls(ctx, resp.Message.ToolCalls, ...)
messages = append(messages, observations...)
}
// 达到最大轮次
a.triggerEvolve(ctx, query, serverInfo, "", toolRecords, false, start)
return nil, fmt.Errorf("达到最大迭代次数(%d)...", a.config.MaxIterations)
自适应检查点机制
checkAdaptiveCheckpoint()
func (a *Agent) checkAdaptiveCheckpoint(iter int, history map[string]int, consecFail, totalCalls int) string {
switch iter {
case 3: // 第 4 轮(0-indexed 为 3)
repeat := 0
for _, c := range history {
if c > 1 { repeat += c - 1 }
}
if totalCalls > 0 && (float64(repeat)/float64(totalCalls) > 0.5 || consecFail >= 3) {
return "【系统提示】检测到重复使用相同工具或连续失败,请尝试换一种思路或不同命令。"
}
case 7: // 第 8 轮
return "【系统提示】已执行到中段,请总结进展。信息足够就直接给出最终答案,否则说明剩余步骤。"
case 11: // 第 12 轮
return fmt.Sprintf("【系统提示】仅剩 %d 步,请立即整理已有信息给出最终答案,不要再调用新工具。",
a.config.MaxIterations-iter-1)
}
return ""
}
| 检查点 | 轮次 | 策略 | 触发条件 |
|---|---|---|---|
| Tier 1 | Round 4 | 启发式纠偏 | 重复工具调用率 >50% 或 连续失败 ≥3 |
| Tier 2 | Round 8 | 强制中间总结 | 始终注入 |
| Tier 3 | Round 12 | 敦促收尾 | 始终注入 |
设计决策:为什么用消息注入而不是额外 LLM 调用?
| 方案 | 优点 | 缺点 |
|---|---|---|
| ❌ 额外 LLM 调用评估状态 | 精确 | 每次 +1 API 调用,成本翻倍 |
| ✅ 消息注入 | 零额外成本 | 依赖 LLM 自主理解提示 |
把引导消息以 user 角色注入对话,LLM 在下一轮推理时自然调整行为。这是「计算型反馈 + 积极的 prompt injection」的典型应用(见 03 - Harness 工程化)。
循环检测
防止 LLM 陷入「用相同参数反复调用同一工具」的死循环:
key := tc.Function.Name + ":" + tc.Function.Arguments
history[key]++
if history[key] > 3 {
obs = append(obs, llm.Message{
Role: "tool", ToolCallID: tc.ID, Name: tc.Function.Name,
Content: "错误: 检测到循环调用,相同参数已调用超过3次,请换一种方式",
})
continue
}
阈值 >3 次:允许 LLM 重试 2~3 次(合理调试),超过则强制换法。
连续失败追踪
func (a *Agent) updateConsecutiveFailures(obs []llm.Message, current int) int {
errs := 0
for _, o := range obs {
c := o.Content
if strings.HasPrefix(c, "错误") || strings.HasPrefix(c, "失败") ||
strings.HasPrefix(c, "执行错误") || strings.HasPrefix(c, "执行失败") {
errs++
}
}
if errs == 0 { return 0 } // 有成功就重置
return current + errs
}
连续失败 ≥3 次会触发 Tier 1 检查点的纠偏提示。
Token 估算与消息裁剪
estimateTokens(粗略估算)
func estimateTokens(s string) int {
return len([]rune(s))/3 + 1
}
中文约 1 字/token,英文约 1/4,折中 rune/3。够用即可,不需要精确 tokenizer。
trimMessages
func (a *Agent) trimMessages(msgs []llm.Message) []llm.Message {
maxT := a.config.MaxContextTokens * 9 / 10 // 留 10% 余量
sysT := estimateTokens(msgs[0].Content)
kept := ... // 从末尾向前累加,超 maxT 停止
if len(kept) < 4 && len(msgs) > 4 {
kept = msgs[len(msgs)-4:] // 最少保留最近 4 条
}
result := append([]llm.Message{msgs[0]}, kept...) // system + 最近消息
return ensureToolMessageIntegrity(result)
}
ensureToolMessageIntegrity(关键)
LLM API 要求 role: tool 消息必须紧跟在带 tool_calls 的 assistant 消息之后,且 ID 匹配。裁剪后可能出现孤立 tool 消息,导致 API 报错:
Messages with role 'tool' must be a response to a preceding message with 'tool_calls'
ensureToolMessageIntegrity 修复这种孤立:
// 1. 收集所有 assistant.tool_calls 的 ID
// 2. 遍历 tool 消息:
// - 若 ID 不在 validIDs 中 → 删除
// - 若前一条不是 assistant 或 ID 不匹配 → 删除
工具调用处理(processToolCalls)
完整流程:
对每个 tool_call:
├─ 循环检测(>3 次拒绝)
├─ registry.Get(name) 查找工具
├─ json.Unmarshal 解析参数
├─ risk = toolRisk(tool, args) ← 优先 DynamicRiskTool
├─ emit EventToolCall(name, args, risk)
├─ safetyCtl.Check(tool, args) ← 高危触发 ConfirmFn
│ ├─ 黑名单(Critical)→ 拒绝
│ ├─ 阈值内 / AutoApprove → 放行
│ └─ 达阈值 → confirmFn → controller 推 WS confirm → 等前端回传
├─ tool.Execute(ctx, args) ← context.WithTimeout(ToolTimeout)
├─ 输出截断(> OutputMaxLength)
├─ emit EventToolResult(output, success, duration)
└─ 构建 observation 消息追加到 messages
Evolver 集成
triggerEvolve(异步进化)
func (a *Agent) triggerEvolve(ctx context.Context, query, serverHost, answer string,
records []evolver.ToolCallRecord, success bool, start time.Time) {
if a.evolver == nil { return } // 降级
exec := &evolver.TaskExecution{...}
go func() {
defer func() { _ = recover() }() // panic 恢复
eCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_ = a.evolver.Evolve(eCtx, exec) // 错误忽略
}()
}
三层保护:
- 后台 goroutine:不阻塞主循环,用户立即拿到答案
- 30 秒超时:防止进化过程无限运行
- panic 恢复:捕获异常,确保 goroutine 崩溃不影响主流程
GetContextForPrompt(经验注入)
任务开始时(Run 入口):
evolveCtx := ""
if a.evolver != nil {
evolveCtx = a.evolver.GetContextForPrompt(query) // 按 task_type 取历史经验
}
systemPrompt := prompt.BuildSystemPrompt(serverInfo, toolDescs, evolveCtx)
注入的内容会被放入 Prompt Layer 5(详见 07 - Prompt 构建 与 08 - Evolver)。
Agent 初始化流程
NewAgent(client, registry, cfg, sc, ev)
│
├─ cfg == nil → DefaultConfig()
├─ sc == nil → safety.NewController(balanced)
├─ MaxContextTokens <= 0 → 强制设为 8000(防御性校验)
└─ return Agent
优雅降级设计:Evolver 初始化失败时,agentcli.NewEngine 把 ev 设为 nil,Agent 继续正常工作,只是没有自学习能力。
扩展指南
增加新的迭代策略
- 在
Config中增加策略配置项(如新的检查点轮次) - 在
checkAdaptiveCheckpoint()中添加新 case - 编写对应的中文提示文本
自定义事件类型
- 在
EventType常量中新增(如EventWarning) - 在
Event结构中增加字段 - 在 controller 的
onEvent中增加 case 处理(推 WS + 落库)
常见陷阱
| 陷阱 | 解决方案 |
|---|---|
| MaxIterations 设太大导致 token 失控 | 默认 16 + 自适应检查点 |
| trimMessages 后 tool 消息孤立 | ensureToolMessageIntegrity() 修复 |
| Evolver 失败导致 Agent 不可用 | 优雅降级,evolver=nil 时跳过进化 |
| MaxContextTokens=0 导致上下文异常 | 防御性校验,<=0 时强制 8000 |
| 高并发下 messages 切片竞争 | Agent 不是并发安全的,每个任务用独立实例(controller 已保证:每次 startTask 新建 Engine) |
| LLM 不返回 tool_calls 也不返回 content | 视为完成,emit done(空答案) |
与 opsxcli 的差异总结
| 维度 | opsxcli | aiops |
|---|---|---|
| 回调机制 | ToolCallback 驱动 Bubble Tea TUI |
EventCallback 推 WebSocket + 落库 |
| Token 估算 | 独立 TokenEstimator 结构 |
内联 estimateTokens 函数 |
| 配置加载 | 环境变量 OPSXCLI_AGENT_* |
DefaultConfig() + EngineConfig 覆盖 |
| 进化 goroutine 同步 | evolveWg.Wait() |
自带 recover + 30s 超时,不等 |
| MaxContextTokens 默认 | 6000(为 8k 模型留余量) | 8000(适配现代 128k 模型) |
核心算法(16 轮循环 / 检查点 / 循环检测 / 连续失败追踪 / Evolver 集成)完全保留,差异仅在工程外壳。
作者:Logan.Li