指令卫生:前沿模型仍然需要你说清什么
原标题:Instructions Hygiene – What Frontier Models Still Need You to Say
指令文件(instructions file)有个通病:只会越长越长。模型出了错,有人加一条规则;工具变了,有人补一个临时方案;新模型上线了,旧指导却原封不动地留着。用不了多久,仓库里的指令文件就变成了入职手册、风格指南、排错日记和提示词工程时间胶囊的混合体。
把该说的都说全,听起来比漏掉什么更稳妥。但实际上,这反而会让 AI 编程智能体变得更低效。现代前沿模型和前几代相比,需要的程序性指导少得多。它们更擅长自己探索代码仓库、识别常见框架、遵循既有模式,也能从常规错误中自我恢复。
但它们毕竟没法知道你们团队脑子里那些私下决策、隐性约束和来之不易的实战经验。指令卫生的目标不是把文件压缩到最短,而是只保留最小的一组高信号信息——那些确实能改变结果的信息。
把上下文当成预算
指令文件是模型每次处理请求时上下文的一部分。每一行内容,都要和开发者的任务、相关代码、工具输出、对话历史以及其他指令争夺注意力。上下文窗口再大,也不意味着多塞几个 token 就无所谓。
Anthropic 给的有效上下文工程的定义是:找到最小的一组高信号 token,最大化得到理想结果的概率。GitHub 自己的指南也建议,指令文件应该包含项目概述、技术栈、编码规范、项目结构和对重要资源的引用——都要简洁。
所以,该问的问题不是「我们能告诉模型关于这个仓库的什么信息?」,而是「有什么信息,是模型自己可靠发现不了、推理不出来、也检索不到的?」——这个区分,才是健康指令文件的根基。
模型仍然需要你交代什么
前沿模型能力很强,但能力强不等于拥有「本地知识」。指令文件最值钱的地方,在于提供三种信息:具体的、影响结果的、难以靠推理得出的。
关于系统的一些不显而易见的事实
简要说明这个仓库是干什么的,并指出那些容易被误解的架构边界。例如:
Billing.Api掌控着公开的 HTTP 契约;Billing.Worker不得暴露任何端点。- 领域规则应该放在
src/Core中,而不是放在控制器或持久化模型里。 legacy/目录仍在生产环境使用,不是仅供参考的代码。src/Clients/Generated下的生成客户端必须通过生成器来更新,绝不能手动编辑。
模型可以查看文件夹树结构,但除非仓库自己说明清楚,否则它们无法可靠判断:哪条边界是有意设计的,哪个看起来老旧的组件仍然举足轻重,哪个源文件是自动生成的。
- 通往验证的最短可靠路径
记录那些权威的命令,尤其是当显而易见的命令不完整或错误时。
构建与验证
-
首次构建前,先运行
dotnet restore App.slnx。 -
用
dotnet build App.slnx --no-restore进行构建。 -
改 API 后,运行
dotnet test tests/App.Api.Tests/App.Api.Tests.csproj跑对应测试。 -
修改契约(contracts)后,执行
./eng/verify-generated.ps1。 -
集成测试依赖 Docker,且必须串行执行。
这类信息在指令文件里的价值最高。它能帮代理少做无用功,绕开已知的坑,还能给代理一个明确的“完成标准”。但只写你实际验证过的命令——一个自信满满的错误命令,比不写任何命令更糟糕。
3. 代码库本身定不了的事情
如果存在多种合理做法,明确写出团队最终选定的是哪一种。例如:
-
新测试用 MSTest 写。
-
新接口用 Minimal API,而不是控制器。
-
预期内的业务失败返回
Result<T>;异常只留给意外故障。 -
统一使用共享的
TimeProvider,不要直接调用DateTime.UtcNow。 -
优先复用已有的仓储抽象,而不是新引入依赖。
这些不是放诸四海皆准的编程真理,而是局部决策。正因为如此,它们才最适合写进指令。
4. 硬约束与代价高昂的错误
要告诉模型:哪些东西碰不得,违反规则可能引发安全、兼容、合规或运维问题。
-
除非任务明确要求破坏性变更,否则保持公开 JSON 契约不变。
-
绝不在日志里写入客户数据。
-
数据库迁移必须与上一个应用版本向后兼容。
-
没有明确的部署任务时,不要动
infra/production下的任何文件。
注意,别用一堆泛泛而谈的警告把文件填满。“必须”“绝不能”“一律”这类强约束词汇,只留给真正不可逾越的规则。
5. 权威信息来源在哪
指令文件不需要事无巨细,只要能指向可靠、聚焦的信息源即可。
-
API 设计规范:
docs/api-guidelines.md -
支持的运行时版本:
global.json和Directory.Build.props -
部署流程:
docs/deployment.md
架构决策:docs/decisions/
这种做法的本质是渐进式披露:给模型足够的信息,让它在需要时能找到正确的上下文,而不是一开始就把所有内容都加载进来。
模型通常不需要什么
改进旧指令文件最快的方法,往往是做减法。
通用软件工程建议
这类规则很少能给仓库上下文增加什么有用信息:
- 写干净、可维护的代码。
- 遵循最佳实践。
- 使用有意义的变量名。
- 妥善处理错误。
- 考虑性能和安全性。
- 编写高质量的测试。
前沿编码模型早就懂这些。更重要的是,这些说法太模糊,根本解决不了实际决策。与其留它们,不如替换成具体的本地标准,或者直接删掉。把「妥善处理错误」换成具体说法,例如:
使用现有的 ProblemDetails 辅助方法,把验证失败映射为 400、缺失资源映射为 404、并发冲突映射为 409。
冗长的仓库清单
完整的目录列表很快就会过时,而且模型几秒钟就能自己查到的信息,你重复写一遍也是白费。只列出那些用途不明显、或者会实质影响改动位置的路径。
工具已经强制执行的信息
不要解释格式化工具会自动套用的规则,也不用复述每条编译器和 linter 配置。直接告诉模型执行哪些命令就行。与其列二十条格式偏好,不如写:
运行
dotnet format --verify-no-changes;不要手动覆盖仓库的 analyzer 规则。
只有当工具管不了、或者模型在做技术选型前就必须知道这条规则时,才需要写进文档。
重复的文档
把 README、架构指南和贡献指南复制进指令文件,维护成本会变高,还容易出现前后矛盾的说法。正确做法是:总结模型需要做的决策,再附上权威来源的链接。
提示词陈规和针对模型的诱导
旧文件里经常能看到这类话:
- 深呼吸一下。
- 一步步思考。
- 扮演世界级资深工程师。
- 做事要极其细致。
- 改动前先读完每个文件。
指令卫生:前沿模型仍需要你交代什么?
-
“不到完美不罢休”——这类措辞并不是项目知识。能力足够强的推理模型并不需要打鸡血式的激励,而死板的流程指令反而可能引发不必要的探索,或和特定环境中的可用工具发生冲突。正确的做法是描述清楚结果、约束和验证方式:做出解决根本问题的最小改动,保持对外行为不变,并针对受影响的项目运行对应测试。
-
旧失败日志——变通方案只有在仍有必要时才应写进指令文件。一旦脚本、依赖或平台问题被修复,就应该删掉对应的警告。否则,智能体会一直绕开那些已经不存在的问题。
-
按模型类别编写,而不是针对某个模型版本——模型升级是复查指令的好理由,但指令不应变成一堆条件分支,比如:如果使用模型 A,先检查三个文件;如果使用模型 B,先请求确认。
如果指令写的是“用 Model C 时请一步步推理”,这种写法很脆弱——模型的可获得性和行为变化,远比仓库架构变化快得多。更好的做法是写与具体模型无关、只描述工作本身的指令:需要交付什么结果、有哪些本地约束、以哪条命令为准、完成之前需要什么证据、哪些环节必须人工批准。当新模型不靠旧的辅助脚手架就能完成任务时,就把脚手架拆掉;当它因为缺少仓库知识而失败时,把知识写进文档,而不是规定一套只对某个模型有效的仪式。
把指令放在合适的范围
不是每条规则都该写进仓库级文件。GitHub Copilot 支持在 .github/copilot-instructions.md 中放仓库级指令,在 .github/instructions/ 下放路径专属文件,也支持 AGENTS.md 这样的代理指令。当仓库级指令和匹配的路径级指令同时存在时,两者都会被采用。选择仍然准确的最宽范围:
- 仓库级:架构、共享命令、通用约束,以及通用的“完成”定义。
- 路径级:框架约定、测试模式、生成代码的规则,或只适用于仓库某一部分的校验逻辑。
- 链接文档:详细解释、教程、架构历史,以及不常用的流程。
例如,关于 React 组件测试的规则,不应该在数据库迁移时抢占注意力。把它放进只作用于前端测试目录的路径级文件就好。合理的范围划分能让全局文件保持精简,又不丢掉有用的指导。
用“保留、删除、移动、核查”来审查指令
每当换用能力明显更强的新模型、改动构建系统、重组仓库,或发现代理反复无视、曲解指导时,都重新审查一遍指令文件。对每一条指令,选择一个动作:
| 动作 | 什么时候用 |
|---|---|
| 保留 | 信息仍然真实、重要,而且模型不容易自己推断出来 |
| 删除 | 模型已经能自己处理、有工具负责强制、内容含糊,或已经过时 |
| 移动 | 规则有用,但更适合放进路径级文件或链接文档 |
验证:指令里描述的命令、变通方式、版本或依赖可能已经失效。先在代表性任务上测一下精简后的文件:让当前的前沿模型完成一个常见、范围明确的任务,观察它实际怎么失败,而不是你预想中的失败。然后只添加防止再次失败所必需的最小指令。再换一个任务重新测试。
这正好扭转了一种常见的反模式:与其把每条旧指令都一直带着,直到有人证明它们多余,不如先假设模型本身能胜任,只有当证据表明它确实缺少上下文时,再补上对应的说明。
一个精简示例:
下面是一份许多团队都可以参考的仓库级文件:
Project context
工程决策
- 以
global.json中指定的 .NET 版本为目标。 - 新端点使用 Minimal APIs。
- 预期的领域失败使用现有的
Result<T>模式。 - 使用
TimeProvider;不要直接调用系统时钟。 - 不要编辑
src/Generated下的文件;运行./eng/generate.ps1。
验证
- 构建:
dotnet build Orders.slnx - 单元测试:
dotnet test tests/Orders.UnitTests - API 变更:同时运行
dotnet test tests/Orders.Api.Tests - 契约变更:运行
./eng/verify-generated.ps1
约束
- 除非明确有破坏性变更,否则保持公共 JSON 契约不变。
- 切勿记录机密、令牌或客户负载。
- 保持迁移与先前部署的版本兼容。
参考
- 架构决策:
docs/decisions/
以部署流程为例:docs/deployment.md。注意这个文件里缺了什么:没有完整的文件树,没有通用的编码建议,没有长篇人设,也没有关于“模型应该怎么思考”的详细规定。它只聚焦于那些会改变实现选择的事实。
让指令文件成为工程维护的一部分。指令文件会影响代码,所以应该像审查代码一样审查它们。几个轻量级做法:
- 把指令文件的改动纳入正常的 Pull Request 审查流程。
- 让审查者判断:一条新规则是通用可复用的,还是只修了某一个任务的坑。
- 为运维命令和环境要求指定明确的负责人。
- 在修复根本问题的同一个 PR 里,顺手清理临时绕过方案。
- 升级 SDK、框架、测试运行器或构建流水线之后,重新核验命令是否仍然有效。
- 定期让一个前沿模型找出指令中重复、模糊、互相冲突,或者“模型自己就能发现”的内容,然后人工核实它给出的建议。
不要只用行数衡量质量。一份 30 行的文件如果命令是错的,比一份 100 行但包含必要 monorepo 边界的文件更糟。紧凑不是硬性目标,而是内容足够聚焦之后的自然结果。
应该追求的标准是:一份健康的指令文件,能让一个有能力的模型不用你教它“怎么当一个有能力的模型”,就可以快速开始做有用的事。留下那些只有你的团队才能提供的信息:
- 系统是什么
- 重要边界在哪里
- 哪些本地选择是有意为之
- 如何可靠地构建和验证
- 什么东西绝对不能弄坏
- 更深层的真相该去哪里查
然后去掉那些模型本身能推断、仓库自己会暴露、或者工具链已经强制约束的信息。
随着前沿模型不断进步,最好的指令文件不会消失,只会变得更聚焦。它的持久价值不在于教模型如何推理,而在于在事实真正起作用的那一刻,以正确的范围把正确的事实交给模型。
延伸阅读
- Adding repository custom instructions for GitHub Copilot
- 5 tips for writing better custom instructions for Copilot
- Effective context engineering for AI agents
- GPT-5 prompting guide
本文首发于 .NET Blog。