提示词最佳实践
Codex 不是聊天机器人,而是一个会读文件、改代码、跑命令的代理。你写下的那段话,相当于交给一位新同事的任务单:他很能干,但不知道你脑子里的背景,也不知道「做完」长什么样。任务单写得含糊,他就只能猜,猜错了你还得花时间返工。
这一页讲怎么写一份好任务单:该包含哪几块、怎样把 Codex 引到正确的文件、什么时候让它先出计划、怎样让它自己验证结果,以及任务跑偏时如何纠正。最后给出一组好坏对照和常见反模式。
一份任务单的四个要素
不管任务大小,下面四块想清楚了,结果通常就不会差太远:
| 要素 | 回答的问题 | 例子 |
|---|---|---|
| 目标 | 要达成什么结果,而不只是「改哪里」 | 用户在订单列表能按下单时间倒序排序 |
| 上下文 | 相关代码在哪、现状如何、为什么要改 | 列表接口在 internal/handler/order.go,前端表格在 OrderTable.vue |
| 约束 | 什么不能动、要遵守什么约定 | 不改数据库表结构;排序参数沿用已有的 sort_by 写法 |
| 完成标准 | 怎样算做完、用什么验证 | go test ./internal/handler/... 通过;新增一个排序的单元测试 |
把它们组织成文字,大致是这样:
目标:订单列表支持按下单时间排序(默认倒序,可切换正序)。
相关代码:
- 后端接口 internal/handler/order.go 的 ListOrders
- 前端表格 web/src/views/orders/OrderTable.vue
约束:
- 不改数据库表结构,created_at 上已有索引
- 排序参数沿用项目里已有的 sort_by / sort_order 写法,参考 ListUsers
完成标准:
- 为 ListOrders 补一个排序的表格驱动测试,go test ./internal/handler/... 通过
- pnpm run typecheck 通过
- 最后列出改了哪些文件,每个文件一句话说明不必每次都写成这么正式的格式,一两句话的小任务也完全可以。关键是这四类信息别缺:缺目标,Codex 会做「字面上的修改」;缺上下文,它要花很多轮去搜代码;缺约束,它可能顺手重构你不想动的地方;缺完成标准,它会在「看起来差不多」的时候停下来。
长期有效的约束写进 AGENTS.md
「用 pnpm 不用 npm」「提交前跑 make lint」这类每次都适用的规矩,不要每次在提示词里重复,写进项目根目录的 AGENTS.md,Codex 每次启动都会读到。详见 AGENTS.md。
指向具体文件和位置
Codex 能自己搜代码,但你直接告诉它位置,省下的是好几轮读文件的时间和 token。
- 用
@引用文件:在输入框里打@,会弹出模糊搜索,选中后把文件路径插入提示词。路径写对了,Codex 会直接去读那个文件。 - 写清函数或符号名:「
order.go里的ListOrders」比「订单那个接口」精确得多。 - 给出参照物:「仿照
ListUsers的分页写法」能让新代码自然地和项目风格一致,比描述一大段风格要求有效。 - 附截图:界面问题直接贴图。命令行启动时可以用
codex -i screenshot.png "按截图修复按钮错位",交互界面里也可以粘贴图片。
# 启动时就带上任务和截图
codex -i ./bug.png "订单详情页的金额在窄屏下换行错位,按截图修复,只改 OrderDetail.vue 的样式"先计划,再动手
改动涉及多个模块、或者你自己也没想清楚方案时,先让 Codex 出计划,确认后再写代码。两种做法:
- 用计划模式:输入
/plan切换到 Plan 模式,Codex 会先调研并给出方案,不直接改文件。你看过、调整过之后再让它执行。 - 在提示词里明确要求:
先不要改代码。阅读 internal/billing/ 下的代码,告诉我:
1. 现在的扣费流程经过哪些函数
2. 要支持「按请求次数计费」,你打算改哪些地方、新增哪些类型
3. 有哪些风险或你不确定的点
等我确认后再动手。计划阶段是纠偏成本最低的时候。方案里出现「顺便重构一下 X」「改用另一个库」这类你不想要的东西,在这里删掉,比代码写完再回滚省事得多。
把大任务拆小
一个提示词塞进「实现完整的会员系统」,Codex 也会尝试,但改动面太大,你很难审查,出了问题也难定位。更好的做法是按依赖关系拆成几步,每步都能独立验证:
第 1 步:在 ent/schema 里新增 Membership 实体(字段见下),运行 make generate,确保编译通过。
做完这一步就停下,把生成的文件列表给我看。确认后再发第 2 步「仓储层的增删改查和测试」,然后第 3 步「接口和权限」,第 4 步「前端页面」。每一步的 diff 都不大,用 /diff 看一眼就能判断对不对。
拆分的原则:
- 每一步都有能跑的验证(编译、测试、页面能打开)。
- 先做被依赖的部分(数据结构、接口定义),后做依赖它的部分。
- 互不相关的几块,可以分别开新会话或用 Worktree 并行做,避免一个会话的上下文越堆越长。
让它自己验证
Codex 最大的优势之一是能运行命令。告诉它怎么验证,它就会在改完后自己跑、自己看报错、自己修,而不是把半成品交给你。
修复 parseDuration 对 "1h30m" 返回 0 的问题。
先写一个能复现这个问题的失败测试,确认它确实失败;
再修改实现,直到 go test ./pkg/timeutil/... 全部通过。
不要修改已有测试的期望值。这段提示词里有三个关键点:先写失败的测试(证明问题被复现了)、明确的通过条件、不许改测试期望值(防止它通过改测试来「修好」问题)。
没有现成测试的项目,也可以给出其他验证方式:
- 「改完后运行
pnpm build,确保没有类型错误」 - 「启动服务后用 curl 调一次
/api/orders?sort_by=created_at,把响应贴出来」 - 「跑一下
scripts/check_links.py,输出里不能有 404」
验证命令受沙箱和审批约束
Codex 跑测试、装依赖、访问网络时,会受到沙箱和审批策略的限制,比如默认不能联网。需要联网的验证步骤,要么调整权限,要么由你来跑。详见 沙箱与审批。
报 bug 时给全信息
「登录有问题,修一下」是最常见、也最难处理的提示词。给 bug 至少要带上这些:
| 信息 | 为什么需要 |
|---|---|
| 复现步骤 | Codex 要能自己触发这个问题 |
| 期望结果和实际结果 | 否则它不知道「对」是什么样 |
| 完整报错和堆栈 | 堆栈里的文件名和行号是最直接的线索,别只贴最后一行 |
| 最近的相关改动 | 「昨天升级了 axios 之后开始出现」能大幅缩小范围 |
| 环境 | 只在 Windows 或只在生产环境出现,往往就是关键 |
复现:本地 pnpm dev,用手机号登录,输入正确验证码后点「登录」。
期望:跳转到 /dashboard。
实际:页面停在登录页,控制台报错:
TypeError: Cannot read properties of undefined (reading 'token')
at handleLogin (src/views/auth/Login.vue:87:31)
线索:上周把 src/api/auth.ts 的返回值从 res.data 改成了 res.data.data,可能没改全。
先找到根因并解释,再修复。修完后用 pnpm exec vitest run src/views/auth 验证。好坏提示词对照
下面这些对照都来自日常开发里的常见场景。左边不是「错」,只是会让 Codex 多猜、多跑几轮;右边把关键信息补齐了。
| 场景 | 含糊的写法 | 更好的写法 |
|---|---|---|
| 加功能 | 给导出加个 Excel | 在 ReportPage.vue 的「导出」下拉里新增「Excel」选项,后端复用 export_service.go 里 CSV 的查询逻辑,用已有的 excelize 依赖生成文件;导出 1 万行在 5 秒内完成 |
| 修 bug | 分页好像不对 | 用户列表第 2 页和第 1 页有 3 条重复数据。怀疑 ListUsers 排序字段不唯一,先验证这个猜测,再修复并补测试 |
| 性能 | 让首页快一点 | 首页接口 /api/home 本地平均 1.2 秒。先找出耗时最多的查询并说明原因,目标降到 300 毫秒以内,不引入新的缓存组件 |
| 重构 | 把这个文件整理一下 | gateway_service.go 有 2000 行。只把重试相关的函数挪到新文件 gateway_retry.go,不改任何函数签名和行为,go test ./internal/service/... 必须仍然通过 |
| 写测试 | 给 utils 写点测试 | 为 pkg/money/format.go 的 FormatCNY 写表格驱动测试,覆盖 0、负数、超过一亿、小数第三位四舍五入;不要改实现 |
| 读代码 | 这个项目怎么工作的 | 我要给支付模块加一个新渠道。请说明一笔支付从下单到回调确认经过哪些文件和函数,标出我新增渠道时需要实现的接口 |
| 升级依赖 | 升级一下 vue | 把 vue 从 3.4 升到 3.5 最新版:用 pnpm 修改并提交 lock 文件,跑 pnpm build 和 pnpm run typecheck,把出现的破坏性变化逐条列出并修复 |
| 写文档 | 写个 README | 为 cmd/importer 写 README:用途一句话、安装、三个最常用参数的示例命令、常见报错。读者是运维同事,不需要解释内部实现 |
| 代码审查 | 看看我的代码 | 审查当前分支相对 main 的改动,重点关注并发安全和错误处理,按严重程度列出问题,每条给出文件和行号;先不要改代码 |
| 样式调整 | 按钮丑,改好看点 | 把设置页所有主按钮改成项目里 BaseButton 的 primary 样式,圆角和间距跟登录页保持一致,不要新增 CSS 变量 |
可以看出,好的写法并不是更长,而是更可检验:每一条都能让人(和 Codex)判断「做完了没有」。
迭代和纠偏
第一次结果不理想很正常,关键是怎么接着往下说。
- 任务进行中直接补充:Codex 干活时,你可以在输入框里继续打字。按 Enter,消息会立刻插入当前任务,适合「等一下,别改那个文件」这种及时纠偏;按 Tab,消息会排队到本轮结束后再发送。
- 方向完全错了就打断:按 Esc 中止当前任务,然后说明问题出在哪。不要让它在错误的方向上越走越远。
- 指出具体问题,而不是重新描述需求:「第 42 行的错误被吞掉了,要返回给调用方」比「再改改」有用得多。
- 说清楚保留什么:「排序逻辑是对的,保留;只把分页参数的校验改回原来的写法。」
- 结果满意但想换思路:用
/fork从当前会话分出一个副本去试另一种方案,两边互不影响。 - 想插问一个无关的小问题:用
/side开临时旁支,问完回到主线,不打乱主任务的上下文。
为什么「再试一次」效果往往不好
同一个会话里,Codex 能看到自己刚才的尝试和你的反馈。如果你只说「不对,再试一次」,它不知道错在哪里,很可能换一种方式犯同样的错误。指出具体问题,等于把排查范围直接缩小给它。如果会话已经被几轮失败尝试搞乱了,用 /new 开个新会话,把学到的教训写进新的提示词,往往比继续纠缠更快。
长任务的检查点
一次要跑几十分钟、改几十个文件的任务,需要中途有可以停下来检查的节点。
- 在提示词里预设检查点:「每完成一个模块就停下来,汇报改了什么、测试结果如何,等我回复『继续』再做下一个。」
- 用 Git 做存档:每个阶段确认无误后,自己提交一次(或让 Codex 提交)。后面出了问题,
git diff和回滚都有据可查。 - 用
/goal固定目标:对需要长时间自主推进的任务,可以用/goal设置一个明确目标,Codex 会围绕它持续工作,你可以随时查看、暂停或清除。 - 上下文快满时压缩:会话太长时用
/compact让 Codex 总结前面的对话、腾出空间;/status可以看当前 token 用量。 - 让它留下进度记录:特别长的任务,可以要求它在
docs/progress.md里记录已完成和待办事项,换会话后直接让新会话读这个文件接着做。
常见反模式
| 反模式 | 后果 | 改法 |
|---|---|---|
| 一句话包办整个系统 | 改动面巨大,无法审查,出错难定位 | 先出计划,再拆成可独立验证的小步 |
| 只说「哪里不对」,不给报错 | Codex 要花很多轮去猜和复现 | 贴完整报错、复现步骤、期望和实际结果 |
| 不给完成标准 | 它在「看起来差不多」时就停下 | 写明要跑哪些命令、哪些结果算通过 |
| 允许修改测试来让测试通过 | 问题被掩盖而不是被修复 | 明确「不要修改已有测试的期望值」 |
| 每次重复同样的项目规矩 | 浪费输入,还容易漏 | 写进 AGENTS.md |
| 在一个会话里连做互不相关的事 | 上下文越来越长,早期信息被挤掉或混淆 | 不相关的任务开新会话 |
| 盲目接受所有改动 | 隐藏的问题进入代码库 | 用 /diff 看改动,用 /review 让它自查 |
| 失败后只说「再试一次」 | 重复同类错误 | 指出具体错在哪、保留什么、换什么思路 |
小结
- 一份好任务单包含目标、上下文、约束、完成标准四块,关键是让结果可检验。
- 用
@引用文件、写清函数名、给出参照代码,能省掉大量搜索。 - 复杂任务先用
/plan或「先别改代码」拿到方案,再拆成能独立验证的小步。 - 让 Codex 自己跑测试和构建;修 bug 时先写失败的测试,并禁止它改测试期望值。
- 进行中用 Enter 即时纠偏、Esc 打断;长任务设检查点、用 Git 存档,必要时
/compact或开新会话。
下一步:把反复要说的规矩沉淀下来,见 AGENTS.md。