Home
avatar

.Sam

【AI Native开发-03】上下文工程:让 AI 真正理解你的项目

这是“AI Native开发”系列的第 3 篇。前两篇建立了 SDD 的四阶段与四道闸门,并用一次完整交付演示了错误如何被拦在闸门之外。但那次演示有一个隐含前提:AI 开工前就理解了这个项目。本篇专门讨论这个前提——如果它不成立,再好的 Spec 也会被错误实现。

摘要

本篇回答一个问题:为什么同一个模型,在 A 项目里像资深同事,在 B 项目里像第一天入职的外包? 结论是模型不是变量,上下文才是。文章给出上下文失效的四种模式(缺失 / 过时 / 淹没 / 冲突)、上下文分层模型(L0–L4)、四条组织原则(就近、单一事实来源、新鲜度、按需加载),以及可以直接落地的入口文件骨架、踩坑笔记模板和上下文健康度自检表。

先给结论:模型不是变量,上下文才是

很多人评估 AI Coding 效果时的思路是错的:他们换模型、调温度、改提示词,试图让“AI 更聪明”。但在固定模型、固定任务复杂度的前提下,决定产出质量的最大变量是上下文——AI 在动手那一刻,能看到、且能注意到的关于这个项目的全部信息。

同一段需求:“给文章加删除功能”

在上下文完备的项目里:
  AI 知道文章是否公开由 frontmatter 的 hide 字段决定
  AI 知道权限走 CMS 身份,不是自建用户表
  AI 知道本项目没有测试脚本,验证靠 pnpm build 加人工确认
  → 一次产出可审查的实现

在上下文缺失的项目里:
  AI 假设状态存在 article.status 字段
  AI 自建一套 user/role 权限表
  AI 猜了一个 npm test 命令,实际不存在
  → 结构完整、测试“通过”、但全盘皆错

上一篇教学案例里的事故——Spec 写“已发布不可删除”,实现去读 article.status,而项目实际用 hide: true 布尔字段——本质不是模型能力问题,而是上下文里缺了一条“这个项目怎么表达状态”的事实

SDD 的四道闸门能拦住错误,但闸门的输入质量取决于上下文。上下文工程,就是在闸门之前保证 AI“看得见事实、注意得到约束”。

上下文失效的四种模式

上下文出问题,不是“有没有文档”这么简单。归纳为四种失效模式,每一种的症状和修法都不同。

C1:缺失——约束不存在,AI 就会发明

AI 面对空白时不会停下来问“这个项目怎么规定”,它会用训练数据里最常见的做法补全。缺上下文时,AI 的默认行为是按最流行的开源项目套路实现,而你的项目几乎必然不是那个套路。

  • 症状:AI 引入了项目里不存在的抽象(自建状态字段、自建权限层、引入没装过的依赖)。
  • 对应上一篇的失效模式:F1(状态假设)、F2(权限默认放行)大多源于 C1。
  • 修法:把“这个项目怎么做 X”写成显式事实,放到 AI 必读的位置。

C2:过时——文档说 A,代码是 B

这是最危险的一种。过时上下文比没有上下文更糟:没有时 AI 还会去读代码确认;过时时,AI 会信任文档而跳过代码核实

  • 典型场景:重构后状态字段从 status 改成了 hide,但文档里的示例还在写 status
  • 症状:AI 严格按照过时文档实现,代码评审时人一眼看出不对,但 AI “有据可依”。
  • 修法:上下文文件必须和代码在同一次变更里更新;评审时把上下文 diff 当作代码 diff 的一部分审。

C3:淹没——关键信息被无关内容稀释

上下文窗口很大,但注意力是稀缺资源。把整个仓库、所有历史聊天记录一股脑塞给 AI,结果不是“它知道得更多”,而是关键约束淹没在噪声里,被忽略的概率显著上升。

  • 症状:你明明在 README 里写了规则,AI 还是违反了——但那条规则在第 800 行,夹在两段无关的安装说明中间。
  • 经验规律:入口文件超过一屏,AI 对入口文件后半段的遵从度明显下降。
  • 修法:入口要薄,细节下沉到专门文件,用“按需读取”的索引代替“一次性全给”。

C4:冲突——两个来源互相矛盾

同一条规则在两个地方写法不同,AI 会自己挑一个——而它挑的那个往往是表述更自信、出现得更近的那个,不一定是对的。

  • 典型场景:README 说“用 pnpm”,某个旧脚本注释里写“用 npm”;A 文档说删除走软删除,B 文档说直接物理删除。
  • 症状:同一条规则,不同任务里 AI 的实现互相矛盾。
  • 修法:单一事实来源(见下文原则二),冲突必须消灭而不是并存。
失效模式速查:

C1 缺失   → AI 发明约束        修:显式写出来
C2 过时   → AI 信任旧文档      修:上下文随代码一起改
C3 淹没   → 关键约束被稀释     修:入口做薄、按需加载
C4 冲突   → AI 随机选一个      修:单一事实来源

上下文分层模型:L0 到 L4

不是所有信息都该放在同一个地方。按生命周期和使用者分五层:

内容生命周期谁主要维护典型载体
L0 隐式上下文代码本身:类型、命名、测试随代码实时变化全体开发者仓库源码
L1 项目入口项目是什么、怎么跑、铁律、索引长期稳定,低频更新技术负责人AGENTS.md / CLAUDE.md
L2 架构与约定架构决策、模块边界、编码规范随架构演进,按决策更新架构决策人ARCHITECTURE.mddocs/decisions/
L3 活的记忆踩坑、当前 Spec/Plan、已知技术债高频更新,随任务沉淀每个任务的执行者docs/pitfalls.mdspecs/plans/
L4 会话上下文本轮任务的临时说明单次任务当前操作者对话、任务指令

几个关键判断:

  • L0 永远是最终事实来源。 文档(L1–L3)和代码冲突时,以代码为准——然后立刻修文档,消灭 C2/C4。
  • L1 是 AI 的“第一印象”,决定它会不会读 L2/L3。 入口文件里最重要的内容不是规则本身,而是索引:告诉 AI“遇到 X 类问题,去读 Y 文件”。
  • L3 是 SDD 的天然副产品。 Spec、Plan、Verify 报告、踩坑笔记本来就在仓库里(第 2 篇强调过“Spec 是仓库里的文件,不是聊天记录”),它们同时就是上下文资产。
  • L4 最不可靠,最不该承载长期规则。 只在对话里说过的约束,换个会话就丢了——这正是要把规则下沉到 L1–L3 的原因。

四条组织原则

原则一:就近——规则放在它约束的东西旁边

规则离被约束的代码越近,被读到的概率越高,也越不容易在重构时被漏掉。

差:所有规则集中在根目录一个 3000 行的 KNOWLEDGE.md 里
好:
  根目录 AGENTS.md        → 只放全局铁律 + 索引
  src/content/ 旁的说明    → 放内容模型的规则
  specs/article-delete/   → 放这个功能自己的 Spec/Plan/Verify
  pitfalls.md 里的条目     → 每条标注涉及的文件路径

“删除草稿文章”那个状态字段事故,最该沉淀的位置不是通用文档,而是内容模型旁边——因为下一个动文章状态的人(或 AI)一定会出现在那里。

原则二:单一事实来源(SSOT)

同一条规则,全仓库只允许有一个权威定义处。其他地方需要引用时,引用而不是复制。

反例(C4 冲突温床):
  README 里写了一遍测试命令
  CONTRIBUTING 里又写了一遍
  CI 配置里再写一遍
  → 三处迟早不一致

正例:
  AGENTS.md 写:“测试与构建命令以 package.json scripts 为准”
  package.json 是唯一定义处
  CI 和文档都引用它

判断方法:如果你要改一条规则,需要改超过一个文件,就违反了 SSOT。

原则三:新鲜度——上下文随代码一起变更

把上下文文件当成代码对待:它在同一个 PR 里变更,接受同样的评审。

  • 改了数据模型 → 同一次提交更新架构说明和相关 pitfalls 条目。
  • 发现一个新坑 → 任务收尾时写进 pitfalls.md,而不是记在聊天里。
  • 评审清单里加一条:“这次变更有没有让某条现有上下文变假?”

这条原则对应 C2 的根治。过时上下文不是“文档质量问题”,是变更流程没有把文档纳入

原则四:按需加载——别把窗口当垃圾桶

给 AI 的上下文要像给新同事的 onboarding:先给一页纸总览,告诉他“需要细节时去哪查”,而不是第一天把所有档案柜钥匙塞给他。

  • 入口文件控制在一屏到两屏,超过就拆分下沉。
  • 在入口里写明路由规则:“动权限相关代码前,先读 docs/decisions/0007-auth-model.md”。
  • 让 AI 在 Plan 阶段显式列出“我读了哪些上下文文件”,这既是检查也是留痕。

一个真实的上下文沉淀:pitfalls.md 如何救回下一次实现

第 2 篇的事故根因是:Spec 假设了 status 字段,实际项目用 hide 布尔。那次归因结束后,结论没有停在聊天记录里,而是写进了踩坑笔记:

# docs/pitfalls.md

## 内容模型的状态表达

本项目文章是否公开用 frontmatter 的 hide 布尔字段表达,
不存在 status 字符串字段。
- hide: true         → 不在列表/RSS 中出现(可删除)
- hide: false 或缺省 → 已公开(不可直接删除)

历史事故:曾因 Spec 写「已发布不可删除」而实现读取
article.status,值为 undefined 导致校验被绕过,
TC-004 期望 409 实际返回 204。

给 AI 的指令:任何涉及文章公开状态的判断,都基于 hide 布尔;
不要引入 status、state、draft、is_published 等新字段,除非
Spec 明确要求并同步修改 content.config.ts 的 schema。

这条笔记的价值在下一个任务兑现:后来做“文章批量归档”时,AI 在 Plan 阶段主动读到了这条,直接基于 hide 字段设计,并在 Plan 里注明“遵循 pitfalls.md 的状态表达约定”。一次代价不小的事故,变成了之后所有相关任务的免费免疫力。

这就是上下文工程和“写文档”的本质区别:文档是写给人看的归档,上下文是写给 AI(和下一个人)执行的约束,它必须包含事实、事故、以及“下次该怎么做”的明确指令。

入口文件长什么样:AGENTS.md 最小骨架

L1 入口不需要面面俱到,它只需要回答四件事:这是什么、怎么跑、铁律是什么、细节去哪找。下面按本博客项目的真实情况写一份:

# AGENTS.md

## 这个项目是什么
Astro 5 静态博客。文章是 src/content/blog/ 下的 Markdown 文件,
按 年/月 分目录。内容后台目前 Keystatic 与 Decap 两套并存
(@keystatic/astro + decap-server),没有自建服务端和用户表。

## 怎么跑
- 安装依赖:pnpm install
- 本地开发:pnpm dev(默认 4321 端口)
- 带 CMS 开发:pnpm dev:cms(并行起 astro dev 和 decap-server)
- 构建:pnpm build
- 新建文章:pnpm newpost,不要手写目录和 frontmatter
- 本项目尚未引入测试框架,没有 pnpm test;
  验证手段是 pnpm build 通过 + 人工确认页面
- 包管理器统一用 pnpm,不要用 npm/yarn

## 结构地图
- src/content/blog/     文章正文(Markdown + frontmatter)
- src/content.config.ts 内容集合与 frontmatter schema(zod)
- src/pages/            路由与页面
- src/plugins/          Markdown 自定义插件
- specs/                各功能的 Spec / Plan / Verify 记录(待建)
- docs/pitfalls.md      踩坑笔记,动相关代码前必读对应条目(待建)

## 铁律(不可违反)
1. 文章是否公开用 frontmatter 的 hide 布尔,没有 status/draft 字段
2. frontmatter 字段必须在 content.config.ts 的 schema 里声明
3. 不要引入服务端框架:本站是静态构建,写操作走 CMS/Git
4. 删除任何文件前必须确认;不要改动 dist/(构建产物)

## 上下文路由
- 改内容模型 / frontmatter → 先读 src/content.config.ts 和 pitfalls「内容模型的状态表达」
- 改文章相关功能 → 先读 specs/ 下对应功能目录
- 不确定命令是否存在 → 先看 package.json 的 scripts,不要猜

注意最后两节。铁律只放违反了会直接导致返工或事故的少量规则(对应 C1 最高频的坑);上下文路由是入口文件真正的杠杆——它把 L1 从“规则堆积地”变成“按需加载的目录页”,治 C3。

怎么知道上下文够不够:几个可观察信号

上下文质量没有分数,但有可观察的行为信号:

信号说明指向的问题
AI 反复问同一个已经“写过”的问题写了但放错位置或没被索引C3 淹没 / 索引缺失
AI 频繁引入项目里不存在的字段、依赖、抽象该有的事实没有显式化C1 缺失
实现和文档描述不一致但各自“都对”文档没跟上代码C2 过时
不同任务里 AI 对同一件事做法矛盾规则有多个出处C4 冲突
Plan 阶段列不出“读了哪些文件”AI 没建立先读上下文的习惯L1 路由失效

这些信号同时也是第 2 篇闸门度量指标的补充:如果“计划外文件修改数”长期偏高,先别怀疑模型,先查上下文。

附录 A:上下文文件清单

下面是一份完整形态的参照,不是本博客仓库的现状——本站目前这些文件一个都没建,本系列里出现的 AGENTS.mddocs/pitfalls.mdspecs/ 都属于方法论示意。列出来是为了说明每一层该放什么,以及从哪两个文件开始最划算。

AGENTS.md / CLAUDE.md   L1 入口:是什么、怎么跑、铁律、路由索引
ARCHITECTURE.md         L2 架构:模块边界、数据模型、关键决策概览
docs/decisions/         L2 决策记录(ADR):每个重要决策一个文件,含背景与取舍
docs/pitfalls.md        L3 踩坑笔记:事故 → 根因 → 下次怎么做
specs/<feature>/        L3 功能档案:spec.md / plan.md / tests.md / verify.md
docs/glossary.md        L2 业务术语表:消除同名歧义(对应第 0 篇术语约定)

落地建议:不要一次性建齐。从 AGENTS.md + pitfalls.md 两个文件开始,其余在任务中自然长出来——上下文工程本身也该小步迭代,而不是先花两周写一份没人维护的“完整文档”(那只会制造 C2)。

附录 B:上下文健康度自检表

[ ] 新人(或新会话的 AI)只看 AGENTS.md 能跑起项目
[ ] 每条铁律都能对应一次真实事故或高频错误,没有“正确的废话”
[ ] 同一条规则在全仓库只有一个权威定义处
[ ] 最近三次重构,相关上下文都在同一次变更里更新了
[ ] pitfalls.md 每条都包含:事实、事故、给 AI 的明确指令
[ ] 入口文件不超过两屏,细节都有索引可查
[ ] AI 的 Plan 里会显式列出“本次读取了哪些上下文”
[ ] 没有任何长期规则只存在于聊天记录里

一条使用经验:这份表自检时最容易打勾、实际最容易破的是第 4 条(变更时更新上下文)。它靠的不是自觉,是把“上下文 diff”加进评审清单。


上下文解决的是“AI 理解现状”的问题。但理解现状之后,AI 还需要一样东西:一份它能无歧义执行的需求。下一篇,我们进入 SDD 的第一个正式产物——怎么写一份 AI 能执行的 Spec:它和需求文档有什么本质区别,十项结构分别解决什么问题,以及为什么“非目标”是整篇 Spec 里最被低估的一节。

AI Native SDD 上下文工程 软件工程