03. 让 Agent 更聪明:System Prompt 的分层设计
拆解 System Prompt 的分层设计:反模式接种、风险评估、工具偏好和工作流约束如何让 Agent 更稳定。
03. 让 Agent 更聪明:System Prompt 的分层设计
从零到一实现一个 AI Agent 框架 · 第三篇
1. 为什么 System Prompt 需要设计?
先看一个最天真的 System Prompt:
你是一个 AI 助手,可以调用工具。
效果怎么样?模型会:
- 试图用
cat读文件,而不是已有的read_file工具 - 修一个 bug 顺便改了三处无关代码
- 为"可能存在 null"的地方加 try-catch(实际上永远不会 null)
- 回答长得像论文摘要
问题的本质:LLM 的训练数据让它有很强的"默认行为"。它见过海量的 shell 脚本,所以倾向于用 bash 而不是专用工具。它在代码库训练语料里看过无数防御性编程,所以觉得加 try-catch 是"好习惯"。
不给规则,它就回退到训练数据中的默认模式。
2. 第一招:反模式接种
与其说"要怎么做",不如先说明 不要怎么做。
Axon 参考了 Claude Code 的 System Prompt 设计,引入了三条反模式规则:
规则一:不要扩大范围
- 用户:修一下这个函数里的 bug
- AI:好,我修了 bug,顺便重构了周围的模块、加了注释、优化了命名……
修 bug 就修 bug。不要顺手重构、加注释、或者"顺带优化"。你的任务边界由用户的问题定义,不是由你发现的"可以改进的地方"定义。
用户:"这个计算不对"
你的回答应该是修复计算,不是重构整个文件。
规则二:不要防御性编程
// ❌ 不要
function parseUserId(input: string): number {
try {
return parseInt(input);
} catch {
return 0; // 这个永远不会触发,parseInt 不抛异常
}
}
// ✅ 要
function parseUserId(input: string): number {
return parseInt(input);
}
不为不可能发生的场景加 try-catch、null 检查或兜底逻辑。如果某个值理论上一定存在、某个操作理论上一定成功,就不要为假设的失败写代码。
规则三:不要过早抽象
一次用法 → 保持内联
两次用法 → 可以观望
三次及以上 → 考虑抽象
"Three similar lines > premature abstraction."
3. 第二招:爆炸半径框架
反模式规则再多也列不完。更好的方法:给模型一个 风险评估框架,让它自己判断。
爆炸半径 = 可逆性 × 影响范围
| 等级 | 特征 | 例子 |
|---|---|---|
| 🟢 低风险 | 可逆,仅影响当前上下文 | 读文件、查数据、写分析 |
| 🟡 中风险 | 可逆但影响共享环境 | 创建文件、npm install、git commit |
| 🔴 高风险 | 不可逆或影响大范围 | git push、rm -rf、修改生产配置 |
高风险操作 = 不可逆 + 影响共享环境
规则很简单:
- 🟢 低风险:直接做
- 🟡 中风险:做完告知用户
- 🔴 高风险:先说风险和替代方案,等用户确认
这比列一个"禁止做的事情"清单要实用得多。清单永远列不完,但框架可以推广到任何场景。
4. 第三招:工具偏好映射表
模型在训练数据中看到最多的文件操作方式是 shell 命令(cat、sed、echo >)。但你的 Agent 有专用工具。需要明确告诉模型"用哪个",否则它会回退到知识中最高频的模式。
- 读文件 → cat
+ 读文件 → read_file(不要用 cat)
- 改文件 → sed
+ 改文件 → edit_file(不要用 sed)
- 写新文件 → tee / heredoc
+ 写新文件 → write_file(不要用 tee)
- 搜索 → grep
+ 搜索 → search_files / list_files
- 长时间任务 → 阻塞等待
+ 长时间任务 → background_run + check_background
这不是教条——这些专用工具在框架层面做了错误处理、上下文管理、权限检查。用 shell 绕过它们等于白装了这些功能。
5. 如何组织到一起:7 层递进结构
把这些内容按从抽象到具体的顺序组织:
| 层 | 内容 | 作用 |
|---|---|---|
| 1️⃣ Identity | 你是谁,定位是什么 | 建立角色认知 |
| 2️⃣ System | 核心行为法则(反模式 + 爆炸半径) | 设定行为边界 |
| 3️⃣ Doing Tasks | 任务管理系统(DAG 模型) | 拆解复杂目标 |
| 4️⃣ Actions | 工具偏好映射表 | 选择正确工具 |
| 5️⃣ Using Tools | 各工具的用途和限制 | 具体操作指引 |
| 6️⃣ Tone | 输出风格要求 | 统一表达方式 |
| 7️⃣ Output Efficiency | 输出精简要求 | 减少废话 |
这个顺序的逻辑:先定义身份,再划边界,然后教工作流,最后规范输出。
前两层的 token 消耗是固定的,后几层可以动态按需加载。
6. 代码实现:在 Axon 中落地
Axon 的 System Prompt 拼装在 agent.ts 中,核心逻辑很简单:
// 将所有部分拼装成最终的 system prompt
const systemPrompt = [
BASE_SYSTEM_PROMPT, // 1-3 层:身份 + 行为法则 + 任务管理
permissionsSection, // 权限与安全(补充第 2 层)
memoryContext, // 记忆系统
memoryPromptSection, // 结构化记忆指引
agentsContext, // 项目上下文(AGENTS.md)
skillsSection, // Skill 列表
toolsSummarySection, // 工具摘要
teamsSection, // 协作信息
].filter(Boolean).join('\n\n');
关键点:
- BASE_SYSTEM_PROMPT 是硬编码的核心,包含 7 层的第 1-3 层 + 工具偏好映射表
- 动态部分(记忆、Skills、工具列表)在运行期拼装,放在末尾利用近因效应
- 权限部分单独成段,因为涉及敏感操作需要突出显示
7. 效果对比
修改前 VS 修改后,同一个场景的典型表现:
场景:用户说"帮我看看这个函数的 bug"
修改前:
"好的,我读了文件,发现函数
calculateTotal有几个问题:首先它没有做 null 检查,其次变量命名不够清晰,另外周围的模块也可以优化一下,我一起改了……"
🔴 扩大了范围、做了防御性判断、还顺手重构。
修改后:
"问题在第 42 行:
total被除 100 而不是 1000。"(然后只修改那一行)
🟢 精确、克制、只解决问题。
总结
| 手法 | 解决什么问题 | Token 成本 |
|---|---|---|
| 反模式接种 | 模型默认行为不符合预期 | ~150 tokens |
| 爆炸半径框架 | 穷举禁止清单不可扩展 | ~100 tokens |
| 工具偏好映射表 | 模型倾向于用 shell 而非专用工具 | ~80 tokens |
| 7 层递进结构 | 组织方式影响模型遵循程度 | 0(重新组织而已) |
成本不到 400 tokens,买来的是模型行为质的提升。
下一篇:权限与安全系统——别让 Agent 乱跑。