后端引擎 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>
302 lines
14 KiB
Markdown
302 lines
14 KiB
Markdown
# 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
|