Sunday 的面试指南

聊得越久,AI 为什么反而越笨?一篇讲透 Context Engineering

大家好,我是 Sunday。

不知道大家使用 Codex、Claude Code 这些 AI 编程工具时,有没有遇到过一种很奇怪的情况。

刚开始的时候,它表现得非常聪明。

你告诉它要做什么,它很快就能找到文件、修改代码、运行测试。

但是随着任务越做越长,问题开始出现了。

前面刚说过的要求,它忘了。

已经解决的问题,它又重新改了一遍。

咱们只是让它调整一个按钮,它却顺手修改了另外三个页面。

有时候,聊得实在不对劲了,咱们新建一个任务,把同样的问题重新发一遍,它又突然正常了。

我相信很多人看到这里,第一反应都是:

模型是不是变笨了?

其实,模型没有在这一个小时里突然降智。

真正发生变化的,是它面前的上下文。

聊天记录、项目规则、读取过的文件、工具说明、运行日志、报错信息,全部在不断进入当前任务。真正重要的要求,反而可能被埋在中间。

这个问题,靠多写两句 Prompt 很难彻底解决。

咱们真正需要学习的,是最近越来越多人提到的另一个概念:Context Engineering,也就是上下文工程。

为了把这件事说清楚,我专门做了一个对照实验。

结果,还挺有意思的。

先做一个真实的 Codex 对照实验

我准备了一个很小的 Node.js 项目。

项目中只有一个没有实现的函数,它要把后台订单导出成 CSV 文件。

表面上看,任务非常简单:

请实现订单 CSV 导出功能,修改 src/orders/export.js。

要求遵守项目现有约定,不增加第三方依赖。完成后运行测试,并说明你实际读取了哪些项目资料。

但是,真正的要求并没有全部写在这句话里。

它们散落在项目文档中,比如:

  • 只导出 PAID 和 SHIPPED 状态的订单;
  • 金额在数据库里使用整数分保存,导出时要变成两位小数;
  • 时间统一转换成上海时区;
  • 不允许导出姓名、手机号和地址;
  • 还要处理 CSV 公式注入、引号、逗号和换行;
  • 不能修改原来的订单数组;
  • 不能增加第三方依赖。

除了这几份真正有用的规范,我又放进去了 12 份历史文档。

这些文档中,有仓库同步、优惠券、退款、发票、CRM,还有一份已经废弃的旧版导出方案。

如果 Codex 读错资料,就可能把旧版中的姓名和手机号重新导出来。

接下来,我复制了三份完全相同的项目,使用相同的 gpt-5.6-sol、相同的推理等级,分别运行三次。

第一组:只给一句任务

第一组没有 AGENTS.md,也没有告诉 Codex 应该先读哪一份文档。

我只把刚才那句任务发给它,让它自己在项目里寻找信息。

第二组:把所有资料全部塞进去

第二组同样没有 AGENTS.md。

不同的是,我把三份有效规范和 12 份历史资料全部放进了提示词,一共两万多字。

这也是很多人最容易采用的办法:

既然担心 AI 不知道,那我就把所有东西一次性全给它。

第三组:只给它一张资料地图

第三组仍然使用同一句任务,但我在项目根目录增加了一份很短的 AGENTS.md:

# 项目协作说明

处理订单导出任务时:

1. 先读取 docs/reference/2026/order-export-policy.md,
   它是当前产品与安全规则的唯一事实来源。
2. 再读取 api-conventions.md 和 testing.md。
3. docs/archive/ 是历史资料,除非任务明确要求,否则不要加载。
4. 只修改任务需要的文件,不增加第三方依赖。
5. 完成后运行 npm test,再核对规范中没有被测试覆盖的要求。

注意,这份文件并没有把订单导出的所有要求重新抄一遍。

它只是告诉 Codex:什么资料有效,应该去哪里找,哪些东西不要读。

然后,我在三份项目外面又准备了一组验收测试。

这组测试没有出现在任务描述中,三次执行记录也没有读取它。它会检查时区、排序、金额、隐私字段、CSV 转义、公式注入以及输入数据是否被修改。

最后的结果是这样的:

方式最终验收无关历史资料累计输入 Token
只给一句任务通过读取了 12 份323262
所有资料塞进提示词通过12 份全部进入上下文368002
使用 AGENTS.md 指路通过没有读取262387

[配图建议:三组 Codex 执行结果和隐藏验收全部通过]

这个结果和我最开始想的还有点不一样。

我原本以为,第一组可能会因为找不到规范而失败。

但现在的 Codex 比以前聪明,它发现公开测试覆盖得不够,就自己搜索了整个 docs 目录,最终找到了真正的产品规范。

所以,三组任务全部做对了。

但是第一组为了找到正确答案,把 12 份无关文档也全部读了一遍。

第二组更直接。咱们主动把所有资料塞给了它,累计输入反而最高。

第三组没有少做任何要求,也通过了相同的隐藏验收,但是它没有进入历史目录。累计输入比第一组少了约 18.8%,比第二组少了约 28.7%。

这里要说明一下:这只是每组运行一次的教学实验,不是严谨的模型 Benchmark。模型本身也有随机性,累计 Token 还会包含系统指令、工具说明、缓存内容和多轮工具调用,所以不能直接把它理解成账单。

但是这次实验已经能说明一个很重要的问题:

Context Engineering 不只是防止 Agent 做错,也是在帮助它少读无关资料,少走弯路。

Context Engineering 到底是什么?

很多同学第一次听到上下文工程,可能会觉得这又是 AI 圈新造出来的一个词。

其实它解决的问题非常具体。

模型每一次生成内容时,真正能够参考的全部信息,就是它当前的上下文。

这里面不只有咱们刚刚输入的那句话,还可能包括:

当前上下文
├── 系统给模型的规则
├── 用户当前提出的任务
├── 前面的聊天记录
├── AGENTS.md 等项目说明
├── 已经读取的代码和文档
├── 可以调用的工具及其说明
├── 工具返回的日志、网页和查询结果
└── 压缩摘要、记忆和任务进度

咱们平时说的 Prompt Engineering,主要关心的是:这句话应该怎么说。

Context Engineering 关心的范围更大:

这一轮应该让模型看到什么,什么时候看到,哪些内容需要保留,哪些内容应该拿出去。

比如,同样是让 Codex 实现订单导出:

帮我实现订单导出。

这句话写得再有礼貌,模型也不可能凭空知道公司规定不能导出手机号。

咱们当然可以把全部要求继续写进 Prompt。

可是项目再大一点,规范可能有几十份。它们还会不断更新,有些只对支付模块生效,有些已经废弃。

这个时候,问题已经不是一句话怎么写了。

问题变成了:

  • 当前任务需要哪些资料?
  • 哪一份才是最新的事实来源?
  • 哪些规则每次都应该出现?
  • 哪些信息只需要在用到时读取?
  • 任务做了两个小时以后,怎样保证关键决定不会丢?

这些,才是上下文工程真正要处理的事情。

为什么上下文越多,Agent 不一定越聪明?

很多人会有一个很自然的想法:

模型不是有几十万甚至上百万的上下文窗口吗?

我把所有代码和文档都扔进去,不就完了吗?

问题在于,装得下和用得好,是两件完全不同的事。

上下文窗口告诉咱们,模型最多能够接收多少 Token。它并不保证每一条信息都能获得相同的注意力,也不保证互相冲突的资料能够被正确判断。

早期一篇很有名的论文叫《Lost in the Middle》。研究人员发现,在长上下文任务中,模型使用信息的能力会受到信息所在位置影响:重要内容出现在开头或结尾时,往往比埋在中间更容易被正确使用。

现在的模型比论文测试时已经强了很多,这个结果不能原样套到每一个新模型上。

但“更多上下文不自动等于更好结果”,这件事依然成立。

就拿刚才的项目来说。

一份旧文档说订单导出需要客户姓名,最新的安全规范又说不能包含任何个人信息。如果两份内容一起塞进去,却没有说明谁是当前事实来源,Codex 就得自己猜。

再比如,Agent 调试一个接口时,连续运行了十几次命令。

最前面是用户真正的要求,中间是几千行构建日志,后面又出现了两次失败方案和新的报错。对话还没达到上下文窗口上限,真正有用的信息已经被大量噪声包围了。

Anthropic 在介绍 Context Engineering 时,用了一个很准确的说法:上下文是一种有限资源。

重点不在于尽量少给,而在于用尽可能少的高质量信息,让模型完成当前任务。

所以,好的上下文工程不是“什么都不给”。

它更像是在不断做选择:现在最重要的是什么,下一步还需要补什么,已经失效的内容能不能退出。

第一件事:给 Codex 一张清楚的地图

咱们刚才实验里的 AGENTS.md,就是这张地图。

Codex 在开始任务前,会读取适用范围内的 AGENTS.md。咱们可以用它告诉 Codex:项目怎样启动、哪些规则必须遵守、重要资料放在哪里、任务完成以后怎么验收。

但是,这里有一个特别容易踩的坑。

很多人第一次写 AGENTS.md,恨不得把项目的所有信息全部放进去。

技术架构写进去,数据库结构写进去,每一个页面的业务规则也写进去,最后弄出几千行。

看起来很完整,实际上又回到了“把所有东西一次性塞给模型”的老路上。

OpenAI 在自己的 Agent 项目复盘中,专门提到他们试过这种方式,结果并不好。

因为巨大的说明文件会挤占任务、代码和相关文档的空间,也很容易随着项目更新变成一份真假混在一起的旧手册。

所以,他们后来把 AGENTS.md 当成目录,而不是百科全书。

我非常认同这个思路。

一份真正有用的 AGENTS.md,优先写下面这些东西:

  • 项目最基本的启动、测试和检查命令;
  • 不允许违反的工程边界;
  • 不同类型任务应该去哪里查资料;
  • 哪些文档是当前事实来源;
  • 完成任务的验收要求。

至于几十页的产品规则、接口清单和架构说明,继续放在独立文档里。

Codex 需要时,再按照路径读取。

比如刚才的实验项目,可以整理成这样:

order-tools/
├── AGENTS.md
├── src/
│   └── orders/
├── docs/
│   ├── index.md
│   ├── reference/
│   │   └── 2026/
│   │       └── order-export-policy.md
│   └── archive/
└── test/

AGENTS.md 负责告诉 Codex 去哪里。

docs/reference 保存当前有效的事实。

docs/archive 保留历史,但明确告诉它默认不要读取。

这就叫渐进式披露:先给入口,用到时再继续展开,而不是刚开始就把所有内容堆到模型面前。

AGENTS.md 还有一个很好用的能力:分层

一个真实项目里,不同目录的规则很可能不一样。

前端项目运行 pnpm test,后端服务运行 pytest;普通业务可以直接修改,支付模块可能还要额外执行安全检查。

如果所有规则都放在根目录,文件很快又会膨胀。

Codex 官方目前支持从项目根目录一路读取到当前工作目录中的 AGENTS.md。越靠近当前目录的说明,位置越靠后,也就可以覆盖上层规则。

例如:

my-project/
├── AGENTS.md
└── services/
    ├── user/
    └── payment/
        └── AGENTS.override.md

根目录可以写整个项目都要遵守的规则:

- 修改代码后运行基础测试。
- 不要提交 .env 和密钥。
- 新增依赖前先说明原因。

payment/AGENTS.override.md 再写支付模块自己的要求:

- 金额统一使用整数分。
- 修改支付流程后运行 make test-payments。
- 不得在日志中输出银行卡和身份信息。

这样,Codex 处理用户模块时,不需要背着一整套支付规则。

进入支付目录工作时,对应规则又会自动出现。

这比维护一个越来越长的根说明文件舒服得多。

第二件事:让资料在需要时再进入上下文

AGENTS.md 解决了地图的问题。

接下来,还要解决资料什么时候进入的问题。

在刚才的第二组实验中,我把所有文档都塞进提示词。Codex 确实没有漏掉要求,但这些历史资料从任务开始就一直待在上下文里。

第三组只提供三个有效文档的路径,Codex在需要时才读取,完成以后也没有进入历史目录。

这就是按需加载。

在实际项目里,咱们可以把资料大致分成三类。

第一类,是每次都要知道的少量稳定规则。

例如测试命令、代码风格、安全底线和资料入口。这些适合放进 AGENTS.md。

第二类,是某一类任务才会使用的方法。

例如发布流程、代码审查方法、数据库迁移步骤、公众号写作流程。这些更适合做成 Skill。

Codex 不需要每次启动都读取完整 Skill。官方当前采用的也是渐进式披露:开始只让模型看到 Skill 的名称和描述,真正匹配任务以后,再加载完整的 SKILL.md。

第三类,是随时可能变化的外部信息。

例如 GitHub Issue、线上日志、数据库记录、网页内容和团队文档。

这些内容不适合复制进长期规则,可以通过 MCP 或其他工具,在真正需要时查询。

所以,Skill 和 MCP 其实也属于上下文工程的一部分。

Skill 决定“遇到这类任务,应该加载哪套方法”。

MCP 决定“当前缺少外部信息时,可以去哪里查询或者执行操作”。

但是工具也不是装得越多越好。

每多一个工具,模型就多一份工具说明需要理解,也多一个“现在到底该调用谁”的选择。

如果一个 Agent 同时挂着几十个职责重叠的 MCP,真正需要的工具反而可能更难选。

咱们前面觉得“多装几个总没有坏处”,从上下文的角度看,并不一定成立。

第三件事:别让工具结果把对话淹没

Agent 和普通聊天最大的区别,就是它会不断调用工具。

读取文件、搜索代码、运行测试、打开网页、查询数据库,每一次调用都会产生新的结果。

这些结果能够帮助模型继续判断,但也很容易把上下文撑得越来越乱。

最典型的情况,就是把几千行日志原样扔回来。

真正有用的可能只有最后十行报错,剩下的内容却会继续占据上下文。

所以,给 Agent 设计工具时,除了考虑“能不能拿到数据”,还要考虑“返回什么才够用”。

例如查询日志,不要默认返回最近一天的全部内容。

可以先返回:

错误类型:DatabaseConnectionError
首次出现:14:03:21
最近出现:14:17:08
出现次数:37
代表性日志:3 条
关联服务:order-api

如果 Codex 判断还需要完整调用链,再让它继续查询某一次请求。

这和查项目文档是同一个思路:

先返回能够做下一步判断的信息,不够再继续取。

工具的名称和说明也要尽量清楚。

如果两个工具一个叫 search_logs,另一个叫 query_logs,参数和结果又差不多,模型很难判断应该选谁。

与其给它二十个模糊工具,不如先提供五个边界明确、返回结果干净的工具。

第四件事:长任务不要只靠聊天记录保存进度

上下文还有一个更麻烦的问题。

它不是永久记忆。

任务足够长以后,Agent 可能会压缩前面的聊天记录;咱们主动开启新任务以后,旧对话也不会自动完整出现在新上下文里。

如果一个关键决定只存在于几十轮之前的一句话中,它迟早可能丢。

因此,复杂任务不能只靠“它应该还记得”。

重要状态应该写进文件。

例如,可以让 Codex 在项目里维护一份 TASK_PROGRESS.md:

# 当前目标
完成订单 CSV 导出,不包含任何客户隐私字段。

## 已确认决定
- 当前规范:docs/reference/2026/order-export-policy.md
- 时间统一使用 Asia/Shanghai
- 金额使用整数分,不增加金额计算依赖

## 已完成
- 状态过滤
- CSV 转义
- 公开测试通过

## 仍需处理
- 补充公式注入测试
- 检查输入数组是否被修改

## 已尝试但放弃
- 直接使用 Date.toLocaleString:输出格式不稳定

## 下一步
运行隐藏验收并检查 Diff。

这份文件的意义,不是再造一份聊天记录。

它只保存继续完成任务真正需要的信息:目标、决定、进度、失败路线和下一步。

这样,即使对话被压缩,或者咱们第二天新建一个任务,Codex 重新读取这份文件以后,也能很快接上之前的工作。

对于持续几天的功能开发,这个方法比反复说“你还记得昨天做到哪里了吗”稳定得多。

压缩、新任务和子 Agent,应该怎么选?

当上下文开始变长时,咱们通常有三种处理方式。

第一种是压缩。

压缩会把前面的长对话整理成更短的内容,再让 Agent 继续工作。它适合当前目标没有变化,只是聊天记录和工具结果太多的情况。

但是压缩不是无损压缩。

摘要过程中,某些当时看起来不重要、后来却有用的细节仍然可能丢失。所以重要决定最好已经写进项目文档或者任务进度文件,而不是全部押在自动摘要上。

第二种是新建任务。

如果上一件事已经完成,接下来要做的是一个完全不同的问题,继续沿用旧对话通常没有太大价值。

例如,刚刚用 Codex 排查完登录 Bug,下一步准备重新设计首页。直接新建任务,给它新的目标和必要资料,会比带着几十轮登录日志继续聊更干净。

第三种是子 Agent。

如果当前任务确实需要同时研究几个相对独立的问题,可以让不同的子 Agent 在各自干净的上下文中处理。

例如,一个 Agent 检查接口兼容性,一个 Agent 检查数据库迁移,一个 Agent 检查测试覆盖。最后只把各自的结论交给主 Agent 汇总。

它的价值不只是并行。

更重要的是,那些大量搜索过程和原始日志留在子 Agent 自己的上下文里,主 Agent 最后只接收整理后的结果。

当然,一个简单任务没有必要硬拆成五个 Agent。拆分本身也会增加协调成本。

判断标准很简单:几部分工作是否能够独立完成,最后又能用清楚的结果合并。

Prompt、AGENTS.md、Skill、MCP、Memory,到底分别放什么?

讲到这里,这几个经常混在一起的概念,就比较容易分清了。

Prompt 放当前这一次任务。

例如要实现什么、修改范围是什么、最后怎样算完成。

AGENTS.md 放项目中稳定生效的规则和资料入口。

例如测试命令、工程边界、目录说明,以及不同任务应该去哪里查资料。

Skill 放一类任务可以重复使用的方法。

例如怎样审查代码、怎样发布版本、怎样分析面试记录。它不是项目百科,而是一套需要时再加载的工作流程。

MCP 给 Agent 提供访问外部数据和执行操作的通道。

它可以让 Codex 查询网页、读取 GitHub、检查浏览器和调用数据库。真正进入上下文的,是工具说明以及调用后返回的相关结果。

Memory 更适合保存跨任务仍然有用的稳定信息。

例如用户偏好、长期方向和持续事项。当前任务精确到哪一步,仍然更适合写进可以检查和修改的进度文件。

它们并不是互相替代的五种写法。

它们在不同时间,把不同类型的信息送进 Agent 的上下文。

一套可以直接使用的方法

如果不想一上来就搭一套复杂系统,咱们可以先从下面这套最小方法开始。

第一步,把信息分开。

不要把所有内容都写进 Prompt。先区分当前任务、长期规则、项目事实和外部动态数据。

第二步,给项目写一份短 AGENTS.md。

先写启动命令、测试命令、不可违反的边界,以及资料入口。超过一定长度以后,优先拆文档,不要继续往里面堆。

第三步,为重要资料标记唯一事实来源。

如果新旧文档都必须保留,就明确写清楚哪一份当前有效,历史资料放在哪里,默认是否允许读取。

第四步,给复杂任务保存进度。

任务持续时间越长,越应该把目标、决定、已完成工作和下一步写进文件。不要只依赖聊天记录。

第五步,完成以后重新验收。

Context Engineering 能提高 Agent 找到正确信息的概率,但它不能替代测试和 Review。

真正可靠的任务,最后都应该回到可以检查的结果:测试是否通过、Diff 是否越界、关键规则是否满足、真实页面是否正常。

以后给 Codex 布置任务时,可以先使用下面这个简单模板:

请先读取当前目录适用的 AGENTS.md,并根据其中的入口找到本任务需要的资料。

目标:
[这次真正要完成什么]

范围:
[允许修改哪些目录或文件]

事实来源:
[需求、接口、设计或数据以哪里为准]

边界:
[哪些事情不能做]

验收:
[需要运行哪些测试,最后返回哪些证据]

如果资料之间存在冲突,先指出冲突,不要自行选择。

这段 Prompt 本身并不神奇。

真正起作用的是,它后面有一套能够找到、读取、更新和验证的项目资料。

写在最后

以前咱们使用 AI,总是在研究一句 Prompt 应该怎么写。

这当然有用。

但是当 AI 从回答一个问题,变成连续工作几十分钟甚至几个小时的 Agent 以后,只研究 Prompt 已经不够了。

它会读越来越多的文件,调用越来越多的工具,产生越来越长的日志,也会不断遇到新的决定。

这个时候,真正拉开效果差距的,不再只是“你会不会提问”。

而是你能不能把正确的信息,在正确的时间,交给正在完成任务的 Agent。

刚才的三组实验里,Codex 最终都把代码写对了。

区别在于,有的方式让它翻遍资料以后找到答案,有的方式一开始就告诉它应该往哪里走。

模型越来越强以后,前一种方式也可能成功。

但一个能够偶尔靠聪明绕出来的 Agent,和一个每次都知道去哪里找依据的 Agent,显然不是一回事。

Prompt 决定你这一次说了什么。

Context Engineering 决定 Agent 在整个任务中,究竟能看见什么。

这才是咱们真正用好 Agent 的开始。


参考资料:

简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历