feat: 新增 Agent CLI — Web 版 AI 运维 Agent(参考 opsxcli 移植)

后端引擎 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>
This commit is contained in:
attackerandClaude committed 2026-06-27 09:55:17 +08:00
1 parent f2069fe3f0
commit 92a2247390
37 files changed
+6449 -6

No files matched your search

@@ -0,0 +1,301 @@
# 07 - Prompt 构建与记忆注入
> 本文由 opsxcli 同名设计文档适配改写,结合 aiops Agent CLI 的 Web 实现(`backend/pkg/agentcli/prompt/prompt.go`)。
> 五层 System Prompt 架构保留;opsxcli 的「personaLayer / capabilityLayer / constraintLayer / fewShotLayer / memoryLayer」五常量在 aiops 中合并为单个 `BuildSystemPrompt()` 函数;角色从「opsxcli 智能运维超级助手」改写为「aiops Agent CLI 智能运维 Agent(Web 版,类似 Claude Code)」。
## 设计理念
Prompt 是 LLM Agent 的灵魂。aiops Agent CLI 沿用 opsxcli 的 **五层 System Prompt 架构**,将静态知识与动态记忆分层组装:
- **Layer 1-4 静态层**:编译期确定,不随运行时变化(角色、安全、工具、工作方式)
- **Layer 5 动态层**:运行时由 Evolver 注入历史经验,越用越聪明
## 五层 System Prompt 架构
```
┌──────────────────────────────────────────┐
│ Layer 1: 角色定位 │ ← 我是谁
│ 「aiops Agent CLI 智能运维 Agent」 │
├──────────────────────────────────────────┤
│ Layer 2: 安全规则 │ ← 我必须遵守什么
│ 黑名单 / 高危确认 / 优先只读 │
├──────────────────────────────────────────┤
│ Layer 3: 工具说明 │ ← 我能用什么
│ 由 Registry 动态生成(ssh_execute 等) │
├──────────────────────────────────────────┤
│ Layer 4: 工作方式(ReAct 范式) │ ← 我怎么工作
│ 思考 → 行动 → 观察 → 循环 → 收尾 │
├──────────────────────────────────────────┤
│ Layer 5: 记忆注入 │ ← 我学到了什么(动态)
│ Evolver 提供的历史经验 │
└──────────────────────────────────────────┘
```
## BuildSystemPrompt 函数
```go
// backend/pkg/agentcli/prompt/prompt.go
func BuildSystemPrompt(serverInfo, toolDescs, evolveContext string) string {
var sb strings.Builder
// Layer 1: 角色
sb.WriteString("你是 aiops 平台的 Agent CLI —— 一个企业级智能运维 Agent," +
"类似 Claude Code / Codex,但运行在 Web 页面上,通过 SSH 工具远程操作真实服务器。\n" +
"你的职责是:根据用户的自然语言任务描述,自主在目标服务器上排查故障、安装与配置软件、修复问题," +
"并通过 WebSocket 把每一步思考、执行的命令和输出实时反馈给用户。\n\n")
// 目标主机(动态片段,但不属于记忆层)
if strings.TrimSpace(serverInfo) != "" {
sb.WriteString("【目标主机】\n" + serverInfo + "\n\n")
}
// Layer 2: 安全规则
sb.WriteString("【安全规则】\n")
sb.WriteString("1. 破坏性命令(rm -rf /、mkfs、dd 覆写磁盘等)被严格禁止,绝不执行。\n")
sb.WriteString("2. 高危操作(reboot、systemctl restart、kill、卸载软件等)需要用户确认后才能执行。\n")
sb.WriteString("3. 排障时优先使用只读命令(df/ps/cat/grep/tail/systemctl status/journalctl)收集信息...\n")
sb.WriteString("4. 不要执行任何你不确定后果的命令;宁可多问用户,也不盲目操作。\n\n")
// Layer 3: 工具(动态生成)
if strings.TrimSpace(toolDescs) != "" {
sb.WriteString("【可用工具】\n" + toolDescs + "\n\n")
}
// Layer 4: 工作方式(ReAct)
sb.WriteString("【工作方式(ReAct 循环)】\n")
sb.WriteString("1. 思考:分析当前状态,决定下一步...\n")
sb.WriteString("2. 行动:调用 ssh_execute 执行命令(一次只做关键的、可验证的一步)。\n")
sb.WriteString("3. 观察:仔细阅读命令输出(含 exit_code,0 表示成功)。\n")
sb.WriteString("4. 循环:如果命令失败或问题未解决,分析错误原因,换一种方法重试,直到真正解决。\n")
sb.WriteString("5. 收尾:问题解决后,用清晰的中文总结:问题根因、你做了什么、最终结果、后续建议。\n\n")
sb.WriteString("【重要】务必真正解决问题,而不是假装解决。每一步都要基于实际命令输出判断," +
"禁止编造不存在的输出。\n\n")
// Layer 5: 记忆注入(动态)
if strings.TrimSpace(evolveContext) != "" {
sb.WriteString("【历史经验(来自自进化引擎)】\n" + evolveContext + "\n\n")
}
// 输出要求
sb.WriteString("【输出要求】使用简体中文,简洁专业。思考与命令穿插呈现,避免冗长空话。")
return sb.String()
}
```
> 与 opsxcli 的差异:
> - opsxcli 把五层拆为五个独立常量(`personaLayer` / `capabilityLayer` / `constraintLayer` / `fewShotLayer` / `memoryLayerPrefix`),aiops 合并为单个函数,更紧凑
> - opsxcli 有独立的 `BuilderV2` 结构和 `NewBuilderV2WithMemory` 工厂,aiops 直接用函数(Web 场景下不需要构建器状态)
> - aiops 移除了独立的 fewShot 示例层,把 ReAct 工作方式说明合并到 Layer 4
## Layer 1: 角色定位
> **关键改写**:opsxcli 的角色是「opsxcli 智能运维超级助手」(CLI 工具语境);aiops 改写为「aiops 平台的 Agent CLI —— 企业级智能运维 Agent,类似 Claude Code / Codex,但运行在 Web 页面上」。
**设计要点**:
- **明确类比**:用「类似 Claude Code / Codex」让用户和 LLM 都快速理解定位
- **强调 Web 语境**:「运行在 Web 页面上」「通过 SSH 远程操作」「通过 WebSocket 反馈」
- **明确职责边界**:排障、安装配置、修复 —— 不是「无所不能的 AI」
- **不包含版本号**(与 opsxcli 一致)
### 目标主机片段
`serverInfo` 由 controller 构建,注入到 Layer 1 之后:
```go
serverInfo := fmt.Sprintf("主机名: %s | IP: %s:%d | 用户: %s | OS: %s",
ac.server.Hostname, ac.server.IP, ac.server.Port, ac.server.Username, ac.server.OS)
```
让 LLM 知道当前操作的是哪台主机、什么系统、用什么用户。
## Layer 2: 安全规则
四条硬规则:
1. **破坏性命令严格禁止**(呼应代码层的黑名单硬拒)
2. **高危操作需用户确认**(呼应代码层的 ConfirmFn)
3. **排障优先只读命令**(引导 LLM 先收集信息再动手)
4. **不确定时不盲目操作**(宁可多问)
> **注意**:Layer 2 是 Prompt 级别的「软约束引导」,与 `safety.Controller.Check` 的代码级「硬拦截」是不同层面的机制。两者互补:Prompt 引导 LLM 主动避免危险操作,代码层兜底防止 LLM 失控。
## Layer 3: 工具说明
由 `core.Agent.buildToolDescriptions()` 动态生成:
```go
func (a *Agent) buildToolDescriptions() string {
var sb strings.Builder
for _, t := range a.registry.List() {
sb.WriteString(fmt.Sprintf("- %s: %s\n", t.Name(), t.Description()))
}
return sb.String()
}
```
**为什么动态生成?**
- 工具集可扩展(实现 Tool 接口注册即可),硬编码 Prompt 会与代码脱节
- 工具描述本身就是给 LLM 的「使用说明书」,由工具自己提供最准确
- `ssh_execute` 的描述会包含具体的目标主机和用户信息(动态拼接)
输出示例:
```
【可用工具】
- ssh_execute: 在远程服务器 192.168.1.100(用户 root)上执行 shell 命令并返回合并的 stdout+stderr 输出与退出码...
- analyze_output: 分析一段(可能很长的)命令输出文本:统计行数、提取疑似错误/告警行...
```
## Layer 4: 工作方式(ReAct 范式)
五步循环:
1. **思考** —— 分析状态,决定下一步(产出 `thought` 文本)
2. **行动** —— 调用 `ssh_execute` 执行命令(一次只做关键的、可验证的一步)
3. **观察** —— 仔细阅读输出(含 `exit_code`,0 表示成功)
4. **循环** —— 失败则换法重试,直到真正解决
5. **收尾** —— 清晰中文总结(根因、做了什么、结果、建议)
**关键约束**(红线):
> 务必真正解决问题,而不是假装解决。每一步都要基于实际命令输出判断,禁止编造不存在的输出。
这条约束非常重要 —— LLM 有「编造输出」的倾向,明确禁止 + 配合 `exit_code` 信号,能显著降低幻觉。
## Layer 5: 记忆注入(动态)
由 `evolver.EvolverEngine.GetContextForPrompt(query)` 提供:
```go
func (e *EvolverEngine) GetContextForPrompt(query string) string {
taskType := classifyTask(query, nil)
var sb strings.Builder
// 同类任务的历史经验(优先成功)
if exps := e.mem.FindExperiences(taskType, 3); len(exps) > 0 {
sb.WriteString(fmt.Sprintf(" 【%s 类历史经验】\n", taskType))
for _, exp := range exps {
mark := "成功"
if !exp.Success { mark = "未成功" }
sb.WriteString(fmt.Sprintf(" - (%s) %s\n", mark, exp.Hint))
}
}
// 相关事实
if facts := e.mem.RelevantFacts(query, 3); len(facts) > 0 {
sb.WriteString(" 【已知事实】\n")
for _, f := range facts {
sb.WriteString(fmt.Sprintf(" - %s\n", f.Fact))
}
}
// 已知主机
if hosts := e.mem.Hosts(); len(hosts) > 0 {
sb.WriteString(fmt.Sprintf(" 【已知主机】%s\n", strings.Join(hosts, ", ")))
}
return sb.String()
}
```
注入示例:
```
【历史经验(来自自进化引擎)】
【磁盘空间 类历史经验】
- (成功) 完成「磁盘空间」类任务时,工具序列 ssh_execute → analyze_output 最终成功,可作为参考。
- (成功) 对于磁盘满问题,先检查大日志文件再清理
【已知事实】
- 生产 nginx 日志目录是 /var/log/nginx
【已知主机】192.168.1.10, 192.168.1.11
```
**设计要点**:
- 按 `query` 分类后只注入**同类**经验,避免无关信息污染
- 优先注入**成功**经验(`FindExperiences` 内部排序)
- 限制条数(经验 3 条、事实 3 条),控制 token 开销
> 与 opsxcli 的差异:opsxcli 把记忆层细分为 5.0 事实 / 5.1 经验 / 5.1.5 技能 / 5.2 环境 / 5.3 偏好;aiops 精简为 3 段(经验 / 事实 / 已知主机),剥离了用户偏好(Web 场景下偏好由安全模式等显式配置承载)和技能层(未实现自动 Skill 提炼)。
## 注入流程
```
core.Agent.Run()
│
├─ evolveCtx = evolver.GetContextForPrompt(query) ← Layer 5 内容
│
├─ toolDescs = buildToolDescriptions() ← Layer 3 内容
│
├─ systemPrompt = prompt.BuildSystemPrompt(
│ serverInfo, ← Layer 1 目标主机片段
│ toolDescs, ← Layer 3
│ evolveCtx, ← Layer 5
│ )
│
└─ messages = [{system: systemPrompt}, {user: query}]
```
## 为什么 Layer 1-4 是静态的?
- 角色定位和工作方式在编译期确定
- 安全规则不应该被动态修改(防止 Evolver 污染安全约束)
- 静态内容可以利用 LLM Provider 的 Prompt Caching(减少 token 消耗,部分 Provider 如 Anthropic 支持)
- 只有事实/经验需要动态更新
## 输出要求
最后一段统一约束输出风格:
> 使用简体中文,简洁专业。思考与命令穿插呈现,避免冗长空话。
这与 opsxcli 一致 —— 中文运维语境的统一规范。
## 扩展指南
### 修改角色描述
直接修改 `BuildSystemPrompt` 中 Layer 1 的字符串。注意:
- 不要包含版本号
- 明确能力边界(不要吹成「无所不能」)
- 场景化描述优于抽象形容
### 增加新的 Prompt 层
1. 在 `BuildSystemPrompt` 中增加新的 `sb.WriteString(...)` 片段
2. 在本文档更新架构图
3. 若是动态层,需在 `Agent.Run` 中准备数据并传入
### 调整记忆注入策略
修改 `evolver.GetContextForPrompt`:
- 增加新的记忆段(如「最近失败的任务」)
- 调整条数限制
- 改进分类匹配逻辑
## 常见陷阱
| 陷阱 | 解决方案 |
|------|----------|
| Prompt 过长导致 token 超限 | Layer 1-4 精简 + Layer 5 按需注入 + 条数限制 |
| 记忆注入了无关经验 | `GetContextForPrompt` 按 `task_type` 匹配,不全量注入 |
| Layer 1-4 被 Evolver 污染 | 静态层硬编码在函数中,Evolver 只能影响 Layer 5 |
| LLM 编造命令输出 | Layer 4 显式禁止 + 输出附 `exit_code` 验证 |
| 角色描述含版本号 | 已清理,禁止再加版本号 |
| 工具描述与实际工具脱节 | Layer 3 由 Registry 动态生成,永远一致 |
## 与 opsxcli 的差异总结
| 维度 | opsxcli | aiops |
|------|---------|-------|
| 代码组织 | 5 个独立常量 + BuilderV2 结构 | 单个 `BuildSystemPrompt` 函数 |
| 角色 | opsxcli 智能运维超级助手(CLI) | aiops Agent CLI(Web 版,类比 Claude Code) |
| fewShot 层 | 独立 Layer 4(运维场景示例) | 合并进 Layer 4 工作方式 |
| 记忆子层 | 5 个(事实/经验/技能/环境/偏好) | 3 个(经验/事实/已知主机) |
| 静态/动态分离 | 严格(BuilderV2.enableMemory 开关) | 严格(函数内 if 判断) |
思想完全保留(五层架构 + 静态动态分离 + 记忆按需注入),实现更紧凑。
> 作者:Logan.Li