Agent 提示词工程:从「写 Prompt」到「设计行为控制系统」

你有没有想过,为什么 ChatGPT 问一句答一句很聪明,但让它连续干 50 轮活就开始"发癫"?

答案藏在两种 Prompt 的本质差异里。


一、Chat Prompt vs Agent Prompt:两种完全不同的生物

先做个不太恰当但很好记的类比:

  • Chat Prompt 像相亲:你精心打扮,把最好的一面展示出来,目标是一击命中,拿下高质量的一次对话。
  • Agent Prompt 像婚姻:你要跟这个人(模型)过日子,跑 50 轮、100 轮,不能第 3 轮就掀桌子。
维度 Chat Prompt Agent Prompt
目标 单次回答质量 多轮决策稳定性
生命周期 一问一答 可能连续跑 50+ 轮
失败代价 这次答得不好 整个任务链崩盘
优化方向 措辞、示例、结构 行为边界、工具规范、状态管理

核心认知升级 :Agent 里的 system prompt 不是"一段话",而是一套行为控制系统。它约束的不是"怎么回答",而是"怎么行动"。

写 Chat Prompt 你在做文案,写 Agent Prompt 你在做架构。


二、问题一:Prompt 怎么拆成可维护的模块?

先看反面教材

很多人写 Agent Prompt 是这样的------一坨意大利面:

js

arduino 复制代码
const systemPrompt = `
你是一个代码助手,帮用户完成编码任务,先读文件再修改。
不要加没被要求的功能。执行危险命令要确认。
输出要简洁,不要用"好的我这就帮您"这种废话。
哦对了,环境是 Node 20,包管理器用 pnpm。
如果用户开了 auto-commit 就自动提交,否则不要。
......(此处省略 800 字)
`;

改一个"输出风格"要翻半天,团队里三个人改出三个版本,合并冲突能打起来。

正确姿势:模块化组装

js

csharp 复制代码
const systemPrompt = [
  identitySection(),        // "你是 XX,负责 YY"
  systemRulesSection(),     // 环境约束与硬性规则
  taskGuidelinesSection(),  // 做事方式:先读再写,不过度发挥
  riskGuidelinesSection(),  // 行动准则:危险命令要确认
  toolUsageGuide(tools),    // 工具指南:根据实际工具列表动态生成
  outputStyle(),            // 输出风格:简洁,别啰嗦
].join('\n\n');

模块化的三重心智模型

1. 独立修改

输出风格出问题?只动 outputStyle()。身份定义要换?只动 identitySection()。每个模块是一个独立的责任单元,改动影响面被锁死。

2. 条件组装

不是所有场景都需要所有模块。比如一个"只读分析 Agent"根本不需要 riskGuidelinesSection()(它又不执行命令)。模块化让你可以按场景拼装:

js

css 复制代码
const sections = [  identitySection(),  systemRulesSection(),  ...(canWrite ? [taskGuidelinesSection(), riskGuidelinesSection()] : []),
  toolUsageGuide(tools),
  outputStyle(),
];

3. 缓存友好

这是最容易被忽略、但对成本影响最大的一点------静态模块可以整体缓存,动态模块才需要每轮重算。这就引出了下一个问题。


三、问题二:什么该缓存,什么每轮都变?

一条分界线切开两个世界

js

arduino 复制代码
const systemPrompt = [
  // ========== 静态区:全局可缓存,所有用户共享 ==========
  identitySection(),        // "你是 XX,负责 YY"
  systemRulesSection(),     // 环境约束
  taskGuidelines(),         // 做事方式
  riskGuidelines(),         // 行动准则
  toolUsageGuide(tools),    // 工具指南(工具集不变就可缓存)
  outputStyle(),            // 输出风格

  // ================= 分界线 =================

  // ========== 动态区:每会话甚至每轮变化 ==========
  envInfo(cwd, gitStatus),  // 工作目录、Git 状态
  userConfig(claudeMd),     // 用户自定义规则
  languagePref(lang),       // 语言偏好
  memoryContext(memories),  // Memory 内容
];

为什么这么分?

现代 LLM 的 Prompt Caching 机制(Anthropic、OpenAI 都有)本质是:前缀不变就能命中缓存。

所以排列顺序至关重要:

text

css 复制代码
[静态前缀] → [动态后缀]
     ↑            ↑
  能缓存       每次重算

如果你把"今天日期"这种每秒都变的东西塞在开头,恭喜你,整个 Prompt 的缓存全部失效,token 成本原地起飞。

缓存决策清单

内容 变化频率 放哪 缓存?
身份、规则、工具说明 几乎不变 Prompt 前部 ✅ 全局缓存
输出风格 很少变 Prompt 前部 ✅
用户配置(CLAUDE.md) 会话级 Prompt 后部 ⚠️ 会话缓存
cwd、git 状态 每轮可能变 Prompt 尾部/消息注入 ❌
当前时间、打开的文件 每秒变 消息里注入 ❌

记住一句口诀:静态的往前放,动态的往后放,超高频的踢出 system prompt。


四、问题三:用户自定义 Agent 行为的可扩展性设计

用户想改 Agent 行为,你总不能让他去改你的源码吧?所以要有分层配置。

核心原则:通用的优先级低,具体的优先级高

text

markdown 复制代码
优先级从低到高:
  平台默认规则
    ↓
  组织/团队规范
    ↓
  项目配置(如 .cursorrules / CLAUDE.md)
    ↓
  用户个人偏好
    ↓
  当前会话临时指令

为什么高优先级要放后面?

因为 模型对 Prompt 末尾的内容更敏感(recency bias)。这不是玄学,是 Transformer 注意力机制的实证现象------越靠近生成位置的信息,影响越大。

所以组装逻辑应该是:

js

csharp 复制代码
const finalPrompt = [
  ...lowPrioritySections,   // 先加载:平台默认
  ...midPrioritySections,   // 再加载:项目配置
  ...highPrioritySections,  // 后加载:用户指令(覆盖前面)
].join('\n\n');

低优先级先出现、高优先级后出现,模型读到最后时,用户的最新意图占据"近因优势",自然覆盖掉前面的默认行为。

这也是为什么 CLAUDE.md 这种用户配置文件,应该注入在动态区的靠后位置。


五、问题四:高频变化的信息放哪?

答案:注入到对话消息里,不要写进 system prompt。

原因很简单:

  1. 写进 system prompt → 每次变化都导致缓存失效
  2. 写进消息 → system prompt 缓存稳如泰山,动态信息随消息走

实战示例

用户实际发送的消息:

js

ini 复制代码
const userMessage = "帮我看看 auth.ts 有什么问题";

系统在发送给模型前,把它"夹心"一下:

js

scss 复制代码
const enrichedMessage = `
<system-context>
当前 IDE 打开的文件:src/auth.ts (第 42 行)
相关 Skill:@security-review(安全审查最佳实践)
今天日期:2026-04-07
</system-context>

帮我看看 auth.ts 有什么问题
`;

这种设计的三个好处

  1. 缓存友好:system prompt 一动不动,动态上下文随消息走,不污染前缀
  2. 语义清晰 :用 <system-context> 标签包裹,模型能明确区分"这是环境注入"而非"用户原话"
  3. 可追溯:出问题时你能清楚看到"这一轮到底喂了什么上下文"

六、一张图总结整套设计

text

sql 复制代码
┌─────────────────────────────────────────┐
│  System Prompt                          │
│  ┌───────────────────────────────────┐  │
│  │ 静态区(全局缓存)                 │  │
│  │  · identity                       │  │
│  │  · systemRules                    │  │
│  │  · taskGuidelines                 │  │
│  │  · riskGuidelines                 │  │
│  │  · toolUsageGuide                 │  │
│  │  · outputStyle                    │  │
│  ├───────────────────────────────────┤  │
│  │ 动态区(会话级)                   │  │
│  │  · envInfo / userConfig / memory  │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
              ↓ 每轮拼接
┌─────────────────────────────────────────┐
│  对话消息流                              │
│  ┌───────────────────────────────────┐  │
│  │ <system-context>                  │  │
│  │   高频变化信息(时间/文件/状态)    │  │
│  │ </system-context>                 │  │
│  │                                   │  │
│  │ 用户真实消息                       │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

七、写在最后

写 Agent Prompt 和写 Chat Prompt,是两种职业。

  • 前者是产品经理:思考边界、流程、异常、可扩展性
  • 后者是文案大师:打磨措辞、节奏、感染力

当你开始把 system prompt 当"代码"而不是"文本"来设计时------拆模块、分缓存、定优先级、管状态------你就从"会写 Prompt 的人"变成了"会设计 Agent 的人"。

这两者之间的差距,大概就是"会用 ChatGPT"和"能做出 Claude Code"的差距。

所以下次再写 Agent Prompt 时,别急着敲字。先问自己四个问题:

  1. 这段该拆成哪个模块?
  2. 它变化频率如何,该缓存还是该动态注入?
  3. 用户能不能覆盖它,优先级排第几?
  4. 它是"长期规则"还是"这轮上下文"?

想清楚这四个问题,你的 Agent 就能稳定跑完 50 轮不"发癫"了。

相关推荐
夏天要喝冰可乐1 小时前
Trae 每天自动签到:Serverless 定时任务完整复盘
前端·python
用户7783366132111 小时前
前端开发别拿生产 Key 刷数据:用 MSW 给搜索接口做 Mock
前端·api
光影少年1 小时前
RN启动流程(bundle加载→Bridge初始化→首屏渲染)
前端·react native·react.js
恋猫de小郭1 小时前
Flutter Golden Tests:给 AI Agent 的 UI 测试系统
android·前端·flutter
特级业务专家1 小时前
X6 框选拖拽性能内幕:从 issue 4823 到开源内核(系列 3 篇)之二
前端
特级业务专家1 小时前
X6 框选拖拽性能内幕:从 issue 4823 到开源内核(系列 3 篇)之三
前端
OpenTiny社区1 小时前
码道结合 OpenTiny 智能化 Skill,让页面会“听话”!
前端·人工智能·开源
kyriewen1 小时前
我手写了一版 React Compiler:AI 最常漏的 3 个 memo 场景
前端·javascript·ai编程
IT_陈寒1 小时前
Python的列表推导式差点让我加班到凌晨
前端·人工智能·后端