模块二:AI 协作方法论 | 第03讲:上下文工程——99% 的人忽略的成本与质量平衡
项目:VibeNote 智能笔记 · 技术栈:Next.js / React / TypeScript / Tailwind
一句话:上下文不是「堆料」,是「调度」;调得好,质量上去、成本下来。
本讲和上一讲是组合拳:Workflow 决定「何时提供何种材料」,上下文工程决定「材料本身是否干净」。
一、开场:为什么「上下文越多越好」是伪命题
你在工具链文章里会看到一条被反复验证的判断:很多问题表面像「模型不够强」,根子上是规格不清、目标不明、验收标准缺失。上下文(context)就是把规格与目标送进模型工作记忆里的载体。于是很多人走向另一个极端:把所有文件、所有历史对话都塞进去,以为「多就是稳」。
这与参考实践 vibe-coding-methodology.md 里的经验相反:作者刻意强调稳定优先、小步慢跑,原因之一就是上下文过长会导致模型迷路。再结合核心概念文章对 Agent 的描述——Agent 的价值在于维持上下文、调用工具并连续完成任务——你会意识到:上下文不是无限仓库,而是昂贵的工作记忆。
本讲目标:建立一套「成本—质量」权衡框架,让你在给 VibeNote 加功能时,知道什么时候该 @ 整个目录,什么时候只该贴二十行关键代码。
我想再强调一个容易被忽略的事实:上下文质量会直接决定你对 AI 的信任曲线。一开始你可能觉得「多塞点更稳」,等你吃过几次张冠李戴的亏,又会走向「什么都不信」。这两种极端都不健康。我们要的是可解释的稳定:你能说清楚「我为什么带这几份材料」,而不是玄学祈祷。
二、上下文到底是什么:别把它混同于「聊天记录」
在工程语境里,我建议你把上下文拆成三类东西:
- 决策上下文:为什么要这么做、非目标是什么、验收标准是什么。
- 系统上下文:目录结构、技术栈、约束、风格、关键接口。
- 任务上下文:本次要改的路径、相关类型、报错栈、复现步骤。
聊天记录往往三类混在一起,噪声极高;上下文工程要做的是分区投放,让模型优先看到决策与系统,其次才是任务细节。
flowchart TB
D[决策上下文] --> P[模型注意力]
S[系统上下文] --> P
T[任务上下文] --> P
N[噪声: 无关日志/过期对话] --> X[稀释注意力]
X --> P
三、上下文窗口:硬上限与「软退化」
模型的上下文窗口(context window)是硬上限;超过就要截断、摘要、换模型或拆任务。更重要的是软退化:即便没触顶,上下文里无关段落越多,模型越容易「看错重点」——表现为张冠李戴 import、改错文件、把 A 需求的假设带到 B 需求。
对 VibeNote 这种全栈项目,软退化常见触发点包括:
- 把整页
pnpm build日志不加筛选贴进去 - 把无关页面的组件一起
@ - 把三个月前的对话原样复制
经验法则:先写「我将提供以下片段,其余请不要假设」,再贴最小相关片段。
四、Token 成本:钱包与延迟
除了质量,上下文还直接关联费用与响应时间。个人项目可能不在意几美分,但一旦你养成「全仓库 @codebase」的习惯,你会遇到两件事:迭代变慢、账单变丑。企业场景里,这还会变成合规与审计成本(敏感信息更容易被误贴)。
所以建议你在团队里约定:默认最小上下文原则;只有定位不清时才扩大范围。
flowchart LR
C[上下文体积] --> Q[质量: 先升后降]
C --> L[延迟与费用: 单调上升]
Q --> O[最优点: 充分且干净]
曲线的直觉是:干净的小上下文往往打败肮脏的大上下文。
五、主动上下文 vs 被动上下文
这是 03-toolchain-frameworks.md 里一条特别好用的主线:被动上下文往往比按需检索更稳——因为很多东西不是「会搜就行」,而是「每轮都在眼前」才不容易漏。
被动上下文(Passive)
例如 AGENTS.md、.cursorrules、CLAUDE.md、放在仓库里的栈约定。特点是:高频、稳定、短。适合「我们的 Next.js 用 App Router」「禁止客户端暴露密钥」。
主动上下文(Active)
例如你在输入框里 @file、@folder,或粘贴报错。特点是:与当前任务强相关、可变。
反模式:把本该被动的长规范每次手动粘贴——既浪费 token,又容易版本不一致。
六、分形文档结构:让上下文「自我更新」
vibe-coding-methodology.md 给了一套很「程序员浪漫」的做法,我建议你认真试试:
- 根目录主
md:任何功能、架构、写法更新,工作结束后同步子文档。 - 每个文件夹一个极简说明(三行内)+ 文件列表(名字、地位、功能);文件开头声明「目录变化请更新我」。
- 每个源码文件头三行:
input/output/pos(地位),并声明「我被更新要同步注释与目录 md」。
它的工程意义是:把上下文维护从「心血来潮」变成「修改即触发」。AI 改文件时,如果规则写得好,会顺带把文档涟漪更新,避免「代码是新的、文档是旧的」这种最 poison 的上下文。
flowchart TD
F[修改代码文件] --> H[更新文件头三行注释]
H --> M[更新所属目录 README]
M --> R[必要时更新根架构文档]
七、如何为 VibeNote 组织项目上下文:分层建议
Layer 0:团队共识(被动)
- 技术栈、包管理器、目录约定、环境变量命名、禁止事项。
Layer 1:模块地图(被动 + 轻量)
app/负责路由与页面;components/负责可复用 UI;lib/负责纯逻辑与数据访问。
Layer 2:任务包(主动)
- 只带相关页面、相关 server action、相关类型定义。
Layer 3:运行时证据(主动)
- 精简报错、网络请求失败信息、复现数据。
再补一层「产品上下文」(可选 Layer 2.5):当功能涉及文案、权限提示、空状态插图时,把用户可见字符串的约束写清楚,比贴 UI 代码更省 token。很多 UI bug 本质是状态机缺状态,不是 CSS。
八、Cursor 的 @file、@folder、@codebase:使用策略
@file
最常用、最可控。适合:单点修改、对照实现、贴类型定义。
@folder
适合:一个功能竖切(例如 components/editor/)。注意 folder 越大,噪声越大;可配合子目录拆分。
@codebase
适合:你不知道改哪里,需要全局检索定位。代价最高,建议先让模型输出「候选文件列表」,再缩小到 @file。
实践口诀:能 file 不 folder,能 folder 不 codebase。
九、可运行示例:估算「提示文本」的体量(字符统计)
下面脚本不依赖模型,只帮你养成习惯:贴之前先看看自己塞了多少字。
// scripts/context-size.ts
// 运行: npx tsx scripts/context-size.ts "你的粘贴内容"
const text = process.argv.slice(2).join(" ") || "";
const han = (text.match(/[\u4e00-\u9fff]/g) ?? []).length;
const ascii = text.replace(/[\u4e00-\u9fff]/g, "").length;
console.log({
chars: text.length,
approxHan: han,
nonCjkChars: ascii,
roughTokensGuess: Math.ceil(text.length / 3),
});
roughTokensGuess 很粗,但足够你在团队里做「别超过 X」的纪律训练。
十、上下文裁剪清单(贴之前逐条勾)
- 这条信息能否压缩成链接/文件路径而不是全文?
- 这段日志能否只保留首尾各 30 行?
- 这条需求是否重复了
AGENTS.md已有内容? - 是否存在过期信息(旧分支、旧 API)?
- 是否包含密钥/手机号/内部域名?(必须脱敏)
你可以把清单打印成便签贴在显示器边。它看起来很土,但比「收藏一百篇提示词文章」更能降低事故率。上下文工程的一部分,就是对抗你自己的懒惰与侥幸心理:你知道该脱敏,但手快会忘;清单是外置大脑。
十一、与课程原稿的关系
课程内容/2.3 上下文越多越好吗?99%的人都忽略了成本问题.md 原稿偏模板化;本讲把它落到:窗口、成本、主动/被动、分形文档、Cursor 策略五条可操作轴。你可以把原稿当目录,把本讲当说明书。
十二、VibeNote 场景:三类任务的上下文配方
A. 新增 Route Handler
被动:栈与安全约束。主动:相关 lib/ 方法、已有类似 route、环境变量名。避免:整个 app/。
B. 调整编辑器交互
被动:组件风格约定。主动:NoteEditor 文件、键盘事件相关 util。避免:所有页面组件。
C. 修复构建失败
主动:第一条报错栈 + 相关文件片段。避免:把全量 node_modules 相关抱怨贴进来。
十三、上下文与 Workflow 的接口(承接第02讲)
第02讲强调 Plan/Review/Implement/Verify。上下文工程在 Plan 阶段就要回答:我需要哪些证据与哪些地图。Review 阶段则要回答:哪些上下文会误导实现。
十四、常见反模式
- 复制整个聊天:历史噪声会把当前任务冲散。
- 多任务混聊:A、B 需求共用一个 thread。
- 只给结论不给证据:模型只能猜。
- 过度依赖 @codebase:成本高且易引入无关文件。
- 文档与代码长期分裂:分形结构就是为了治这个。
十五、进阶:什么时候该「换会话」而不是「继续堆」
当你发现模型开始引用不存在文件、或把旧方案当现行方案,通常说明会话上下文已经脏了。此时更有效的是:新开对话,携带「摘要 + 当前分支 + 相关文件」三件套,而不是继续往上滚。
十六、再谈被动上下文:为什么 Vercel 团队说「评测更稳」
03-toolchain-frameworks.md 提到 AGENTS.md 这类被动上下文在评测中更稳,本质是:减少触发不确定性。Skills/工具如果未被可靠触发,会带来「以为带了,其实没带」的隐性问题;被动挂载则更像编译期的 include——更笨,但更稳。
十七、给 VibeNote 的「上下文预算」建议(团队可改成表)
| 任务类型 | 建议主动片段数 | 是否启用 codebase |
|---|---|---|
| 小改 UI | 1~3 个文件 | 否 |
| 新 API | 2~5 个文件 | 否 |
| 定位神秘 bug | 先 codebase 出清单,再收敛 | 短暂启用 |
| 大重构 | 分阶段会话 + 目录级 @ | 谨慎 |
十八、可运行示例 2:从目录生成「文件夹 README 骨架」
配合分形文档,你可以用脚本生成待填骨架(Node 16+)。
// scripts/gen-folder-readme.mjs
import fs from "node:fs";
import path from "node:path";
const target = process.argv[2] || "components";
const lines = [];
lines.push(`# ${target}/`);
lines.push("");
lines.push("三行架构说明:(用一句话说明这个目录负责什么边界)");
lines.push("");
lines.push("## 文件列表");
for (const name of fs.readdirSync(target)) {
if (name === "README.md") continue;
const p = path.join(target, name);
const stat = fs.statSync(p);
lines.push(`- \`${name}\` — ${stat.isDirectory() ? "目录" : "文件"}:职责待补`);
}
fs.writeFileSync(path.join(target, "README.md"), lines.join("\n"), "utf8");
console.log("written:", path.join(target, "README.md"));
运行:node scripts/gen-folder-readme.mjs components
十九、上下文「蒸馏」:把 10 屏聊天压成 10 行摘要
建议你养成一个习惯:每个里程碑结束,写一段不会过期的摘要放进 docs/sessions/2026-03-xx.md,结构固定为:
- 目标一句话
- 关键决策三 bullet(含「不做什么」)
- 触碰的文件列表
- 未解决问题与下一步
这段摘要会成为下一次会话最贵的资产:它比整段聊天便宜得多,但信息密度更高。参考方法论强调用文档持久化关键对话与技术决策,这就是落地版。
二十、与「规范是新的源代码」的咬合
核心概念文章列表里提到:当 AI 大规模承担实现,规范会越来越像驱动产出的源头文件。上下文工程在这里的角色是:把规范放在正确层级——稳定规范进被动上下文;临时约束进当次工单。否则你会看到一种怪现象:规范写得很全,但模型每次都没读到,因为只躺在 Notion 里。
二十一、VibeNote 实战:一次「上下文过多」的复盘剧本
假设你在做「AI 改写」功能,第一次对话里你 @ 了整个 app/、贴了 200 行日志、还复制了另一项目的代码。模型很可能输出「看起来很专业」的通用方案:新依赖、全量重构、顺便改布局。根因不是坏,是注意力被噪声带走。
第二次你改成:被动上下文声明栈与密钥规则;主动只 @ NoteEditor + 一个已有 route.ts 示例;日志只保留首行错误类型与对应文件路径。通常第二次会更像「你的项目」。
二十二、上下文与安全的交界:你必须红线的三件事
- 密钥与 token:永远不进聊天全文;用占位符 + 环境变量名。
- 用户数据:脱敏到「结构相同、内容不同」即可。
- 内网地址:改成
https://internal.example这类占位。
上下文工程不是纯技术问题,一次粘贴就可能把事故贴进模型提供方日志。
二十三、什么时候该用「外部记忆」而不是「对话记忆」
长对话不是数据库。需要跨周复用的决策,请放进仓库:issue、ADR、docs/。外部记忆的好处是可检索、可 diff、可 code review。对话记忆的好处是快——但快不等于可靠。
二十四、Cursor 组合技:@Docs + @file 的分工
如果你使用官方文档索引(不同版本功能略有差异),经验是:语法/框架行为问 @Docs,项目细节问 @file。把两者混在一个问题里时,写清楚边界:「官方推荐怎么做」与「我们项目当前代码长什么样」分别要什么。
二十五、分形结构的「最小可行版」
如果你觉得三行注释 + 目录 README 太重,可以先只做两层:
- 根
docs/ARCHITECTURE.md维护模块地图 - 每个大目录一个
README.md只列文件职责
等团队习惯后,再加文件头三行。上下文工程最怕一次性上太重导致没人维护。
二十六、对比表:主动检索 vs 被动挂载
| 维度 | 主动检索(搜/问) | 被动挂载(规则/AGENTS) |
|---|---|---|
| 稳定性 | 依赖触发正确 | 高 |
| token 成本 | 按需 | 每轮固定 |
| 适合内容 | 临时、长尾 | 高频、短、硬约束 |
| 风险 | 「没带技能」 | 「规则太长」 |
二十七、再补一张图:上下文生命周期
stateDiagram-v2
[*] --> Collect: 收集证据
Collect --> Trim: 裁剪噪声
Trim --> Bind: 绑定到任务
Bind --> Use: 模型推理
Use --> Archive: 蒸馏进文档
Archive --> [*]
二十八、FAQ
Q:@codebase 是不是越新越强就越该常用?
A:越强越要克制,否则你会用算力买噪声。
Q:上下文要不要包含测试代码?
A:要,当你改动会影响行为时;但要精确定位到相关 *.test.ts,别把整个测试目录塞进去。
Q:中文注释会不会干扰模型?
A:不会本质干扰,但要统一语言策略:公共规则里写清「注释中文/英文」即可。
Q:我能不能让模型自己总结上下文?
A:可以,但要做「二次校验」:模型摘要可能漏掉关键约束。最稳的是摘要后你再补三条硬约束(安全/兼容/不改动范围)。
Q:分形文档会不会变成形式主义?
A:会,如果没人 review。把它绑定到 PR:改目录必须同步 README,一条机器人评论就能救。
Q:小团队只有一个人也要做吗?
A:更要。你未来的自己会感谢现在留下的地图;否则三周后你会在「我当初为什么这样写」里浪费时间。
Q:上下文要不要包含 package.json?
A:不必整文件;通常贴 dependencies 里与任务相关的几行即可,或说明 Next/React 版本号。
二十八(续)、把「噪声」定义清楚:五类高频噪声清单
- 重复规则:已在
AGENTS.md写过却又粘贴一遍。 - 过期 diff:来自旧分支的代码片段。
- 情绪性文字:长篇抱怨对定位没有信息增益。
- 无关页面:为了「显得完整」而
@的全站组件。 - 巨型日志:未裁剪的 trace。
二十九、与无效调试循环的关系(预告第05讲)
参考方法论描述过:项目变大后,如果把报错随手丢给模型而不先分析,容易进入「按了葫芦起了瓢」的循环。上下文工程在这里的作用是:把根因证据组织成模型可消费的形态,而不是把混乱原样放大。换句话说,调试不是「更多上下文」,而是更准的上下文。
三十、团队练习:「上下文审计」15 分钟
挑一个真实 issue,三个人分别独立列出「应携带的文件清单」,再对比差异。你会惊讶地发现:大家对系统边界的理解不一致,而这正是返工源头。把差异合并进 docs/ARCHITECTURE.md,比多写十个 Prompt 更有用。
三十一、本讲小结
- 上下文是昂贵的工作记忆,不是堆料比赛。
- 主动 vs 被动要想清楚:稳定短规则应被动挂载。
- 分形文档解决「代码新、文档旧」的慢性中毒。
- Cursor:
@file优先,@codebase用于定位后迅速收敛。 - 会话脏了要会「换盆」:新会话 + 蒸馏摘要,而不是无限往上卷。
把小结再压成一句行动:每次贴上下文前,先问「这条信息会改变决策吗?」不改决策的,别贴。
三十二、延伸:RAG/知识库与「仓库内文档」怎么选
很多团队会问:要不要上向量检索?对 VibeNote 这种代码仓库型项目,我的务实建议是:先把仓库内文档写短、写准、写进分支。RAG 解决的是「海量外部知识」,而你们最大的痛点通常是「地图不在仓库里」。当文档真的不够用时,再引入检索;否则你会用复杂系统替代本该有的纪律。
再补一句落地判断:如果你连自己该读哪三个文件都说不清,RAG 只会让你更自信地读错。先把「文件地图」写进仓库,比先上向量数据库更划算。
三十三、延伸:多文件修改时如何防止上下文「串线」
当你必须同时修改 5 个文件时,不要在一个消息里扔 5 个互不关联的问题。更好的做法是:一次会话只解决一个竖切目标,或者要求模型输出「文件 A 完成后再处理 B」的检查点。上下文工程不仅是「放什么」,还包括对话节奏:节奏错了,材料再全也会串。
三十四、把「上下文」写进 Definition of Done
建议你给团队加一条简单 DoD:合并前确认相关 README/ADR 已同步。这不是为了写论文,是为了让下一次 AI 协作更便宜。上下文工程最终会变成组织习惯;习惯比工具贵,但也更持久。
如果你用的是 Pull Request 模板,也可以加一项:「本次变更是否需要更新架构文档/目录 README?」——勾「否」要给出理由。理由本身也是信息,能防止想当然。
三十五、思考题
- 回顾你最近一次超长对话:其中真正必要的片段占比多少?你会如何改写?
- 你的项目里哪些信息应该从「每次粘贴」迁移到「被动上下文」?迁移的触发条件是什么?
- 分形文档结构你最怕的维护成本是什么?有没有折中版(只要求两层)?
附加题:为 VibeNote 写一个「上下文配方」模板(Feature/Bugfix 各一份),下次直接复制填空。
再想一想:如果你只能减少一种噪声,你会先减哪一种?为什么它对当前项目伤害最大?
三十六、下讲预告
下一讲进入规则文件与复用模板:AGENTS.md、.cursorrules、CLAUDE.md 各自解决什么问题,如何把「好规范」写成智能体成功率的核心变量,并借鉴 Addy Osmani「如何为 AI 智能体编写好的规范」那条线的思路,把 VibeNote 的规则库搭起来。你可以提前整理一份清单:哪些规则你一周要重复讲三次以上——它们最值得进规则文件。
参考阅读:reference/practice/vibe-coding-methodology.md(分形文档、上下文过长风险);reference/articles/03-toolchain-frameworks.md(被动上下文 vs Skills);课程内容/2.3 上下文越多越好吗?99%的人都忽略了成本问题.md。
最后一句话:别用更大的窗口逃避更清晰的地图。窗口会越来越大,但你的工程纪律不该越来越松。
如果你读完只记住一件事:记住最小充分上下文——它会让你少花钱、少返工、少熬夜。真的够了,去练一周比再看十篇文章重要;练完记得复盘,把浪费的 token 记下来,下次就少交学费,划算得很,别偷懒。