Files
aiops/docs/agent/07-Prompt构建与记忆注入.md
T
attackerandClaude 92a2247390 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>
2026-06-27 09:55:17 +08:00

302 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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