大家都在构建复杂的 Agent 框架,甚至将简单的文档查询功能拆解为多个工具,并称之为“模块化”。但结果往往不如人意:Agent 要么不知道何时调用工具,要么无法理解返回结果,最终产生大量不准确的输出。
近期,Vercel 官方技术团队进行了一次对比评测,其结果对当前流行的复杂 Agent 架构提出了挑战。
评测原文地址:https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals
我们曾认为 Skills 是教会 AI 编程 Agent 掌握特定框架知识的终极方案。然而,在针对 Next.js 16 的 API 构建了一套评估测试后,我们得到了一个意想不到的结论。
一个直接嵌入到 AGENTS.md 文件中、仅 8KB 大小的压缩文档索引,实现了 100% 的测试通过率。相比之下,即便我们明确指令 Agent 使用 Skills,其准确率也仅停留在 79%。更糟糕的是,如果没有这些强制指令,Skills 的表现与完全无文档时一样差。
本文将分享我们的尝试、发现,以及如何在你的 Next.js 项目中复现这一高效配置。
我们试图解决的问题
AI 编程 Agent 高度依赖其训练数据,但这些数据往往存在滞后性。例如,Next.js 16 引入了诸如 'use cache'、connection() 和 forbidden() 等全新 API,而这些内容并未包含在当前主流模型的训练数据中。当 Agent 不了解这些 API 时,就会生成错误代码或退回到旧的模式。
反之亦然:当你在维护一个旧版本的 Next.js 项目时,模型可能会建议使用项目中尚不存在的新 API。我们的目标是通过赋予 Agent 与项目版本精确匹配的文档访问权限 来解决这一问题。
两种“教会” Agent 的方法
在深入结果之前,先快速了解我们测试的两种方法:
- Skills:这是一种供编程 Agent 使用的、用于打包领域知识的开放标准。一个 Skill 捆绑了 Agent 可以按需调用的提示词(Prompts)、工具和文档。其核心理念是:Agent 能够自主识别何时需要特定框架的帮助,进而调用 Skill 并获取相关文档。
AGENTS.md:这是一个位于项目根目录的 Markdown 文件,用于为编程 Agent 提供持久的上下文支持。无论 Agent 是否决定加载它,你放入 AGENTS.md 的任何内容都会在每一轮对话中对 Agent 可见。Claude Code 使用的 CLAUDE.md 也是同样的原理。
我们构建了一个 Next.js 文档 Skill 和一个 AGENTS.md 文档索引,然后通过评估套件运行它们以比较性能。
起初,我们将赌注押在了 Skills 上
Skills 看起来是一个正确的抽象。你将框架文档打包成一个 Skill,Agent 在处理 Next.js 任务时调用它,然后生成正确代码。这种方式实现了关注点分离,上下文开销小,且 Agent 只在需要时加载内容。在 skills.sh 上甚至已经有了一个不断增长的可复用 Skills 目录。
我们预期的流程是:Agent 遇到 Next.js 任务 -> 调用 Skill -> 阅读版本匹配的文档 -> 生成正确代码。
然而,评估结果给了我们当头一棒。
Skills 的触发并不靠谱
在 56% 的评估案例中,Skill 从未被调用。Agent 明明拥有访问文档的权限,却选择了忽略。添加 Skill 后,结果与基准线相比没有任何提升:
| 配置 |
通过率 |
对比基准 |
| 基准 (无文档) |
53% |
— |
| Skill (默认行为) |
53% |
+0pp |
零提升。 Skill 就在那里,Agent 可以用,但它选择了不用。在详细的构建、Lint、测试细分数据中,Skill 在某些指标上的表现甚至比基准线还差(测试通过率为 58% vs 63%),这表明环境中一个未被使用的 Skill 反而可能引入噪音或干扰。
这并非特例。Agent 无法可靠地使用可用工具,是当前大模型的一个已知局限。
显式指令有帮助,但措辞极其脆弱
我们在 AGENTS.md 中尝试添加了显式指令,告诉 Agent 必须使用该 Skill。
Before writing code, first explore the project structure, then invoke the nextjs-doc skill for documentation.
(在编写代码之前,先探索项目结构,然后调用 nextjs-doc skill 获取文档。)
添加到 AGENTS.md 中用于触发 Skill 使用的示例指令。
这一改动将 Skill 的触发率提高到了 95% 以上,并将整体通过率提升至 79%。
| 配置 |
通过率 |
对比基准 |
| 基准 (无文档) |
53% |
— |
| Skill (默认行为) |
53% |
+0pp |
| Skill (配合显式指令) |
79% |
+26pp |
这是一个显著的提升。但我们发现了一个关键问题:指令的措辞对 Agent 的行为影响巨大。
不同的措辞产生了截然不同的结果:
| 指令 |
行为 |
结果 |
| “You MUST invoke the skill” (你必须调用 Skill) |
首先阅读文档,死抠文档模式,忽略了项目上下文 |
结果较差 |
| “Explore project first, then invoke skill” (先探索项目,再调用 Skill) |
先建立对项目的心智模型,再将文档作为参考 |
结果更好 |
同一个 Skill,同一份文档,仅仅因为措辞的细微差别,最终结果天差地别。
在某次针对 'use cache' 指令的评估中,“先调用(invoke first)”的方法写出了正确的 page.tsx,但完全遗漏了必要的 next.config.ts 更改。而“先探索(explore first)”的方法则两者都正确完成了。
这种脆弱性令人担忧。如果微小的措辞调整会导致巨大的行为波动,那么这种方法在生产环境中就显得太不可靠了。这也反映了当前 Agent 在复杂决策上的不稳定性。
构建值得信赖的评估体系
在下结论之前,我们需要一套可靠的评估体系。我们最初的测试套件存在提示词语义模糊、测试验证的是实现细节而非可观察行为、以及过于关注模型训练数据中已有的 API 等问题。
我们通过消除测试数据泄露、解决矛盾断言并将重点转移到基于行为的测试上来强化评估套件。最重要的是,我们添加了针对模型训练数据中不存在的 Next.js 16 API 的测试。
我们核心评估套件中的 API 包括:
- 用于动态渲染的
connection()
'use cache' 指令
cacheLife() 和 cacheTag()
forbidden() 和 unauthorized()
- 用于 API 代理的
proxy.ts
- 异步的
cookies() 和 headers()
after()、updateTag()、refresh()
接下来的所有结果都来自这个经过强化的评估套件。每种配置都在相同的测试下进行评判,并进行多次重试以排除模型随机性的影响。
那个得到回报的直觉
如果完全消除“做决定”这个环节呢?与其寄希望于 Agent 主动调用 Skill,不如我们直接在 AGENTS.md 中嵌入一个文档索引。不是完整的文档,仅仅是一个索引,告诉 Agent 去哪里找到与你项目 Next.js 版本匹配的具体文档文件。
我们在注入的内容中添加了一条关键指令:
IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Next.js tasks.
(重要提示:对于任何 Next.js 任务,优先使用基于检索的推理,而非基于预训练的推理。)
嵌入在文档索引中的关键指令。
这直接指示 Agent 去查阅文档,而不是依赖可能过时或错误的预训练数据。
结果让我们大吃一惊
我们在所有四种配置下运行了强化后的评估套件。
最终通过率:
| 配置 |
通过率 |
对比基准 |
| 基准 (无文档) |
53% |
— |
| Skill (默认行为) |
53% |
+0pp |
| Skill (配合显式指令) |
79% |
+26pp |
AGENTS.md 文档索引 |
100% |
+47pp |
在详细的细分数据中,AGENTS.md 在构建、Lint 和测试三个方面均获得了满分。
| 配置 |
Build |
Lint |
Test |
| 基准 |
84% |
95% |
63% |
| Skill (默认行为) |
84% |
89% |
58% |
| Skill (配合显式指令) |
95% |
100% |
84% |
AGENTS.md |
100% |
100% |
100% |
这完全出乎意料。“笨”办法(一个静态 Markdown 文件)竟然跑赢了更复杂的基于 Skill 的检索方案。
为什么“被动上下文”打败了“主动检索”?
我们的分析主要归结为三个因素:
- 没有决策点:使用
AGENTS.md,不存在 Agent 必须决定“我是否应该查一下这个?”的时刻。信息已经持续可见。
- 持续的可用性:Skills 是异步加载的,并且只在被调用时加载。而
AGENTS.md 的内容在每一轮对话的系统提示词中都存在。
- 没有顺序问题:Skills 带来了顺序决策难题(先读文档还是先探索项目?)。被动上下文完全避免了这个问题。
解决上下文臃肿的问题
在 AGENTS.md 中嵌入文档存在导致上下文窗口臃肿的风险。我们通过压缩解决了这个问题。
最初注入的完整文档索引大约是 40KB。我们将其压缩到了 8KB(减少了 80%),同时保持了 100% 的通过率。这种压缩格式使用管道分隔符结构,将文档索引打包到最小空间中:
[Next.js Docs Index]|root: ./.next-docs|IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning|01-app/01-getting-started:{01-installation.mdx,02-project-structure.mdx,...}|01-app/02-building-your-application/01-routing:{01-defining-routes.mdx,...}
AGENTS.md 中的精简版文档索引示例。
Agent 知道去哪里找文档,而无需将全部内容塞入上下文。当它需要特定信息时,它会从 .next-docs/ 目录中读取相关文件。这种方式本质上是一种高效的 RAG(检索增强生成)实践。
亲手试一试
只需一条命令即可为你的 Next.js 项目完成此配置:
npx @next/codemod@canary agents-md
这个命令会做三件事:
- 检测你的 Next.js 版本。
- 下载匹配版本的文档到
.next-docs/ 目录。
- 将压缩后的文档索引注入到你的
AGENTS.md 文件中。
如果你使用的是支持 AGENTS.md 的 AI 编程工具(如 Cursor 或其他兼容工具),这种方法同样有效。
这对框架作者意味着什么
Skills 并非毫无用处。AGENTS.md 方法为 Agent 处理 通用的、横向的 Next.js 任务提供了广泛改进。而 Skills 更适合用户显式触发的垂直、特定操作的工作流,例如“升级我的 Next.js 版本”或“迁移到 App Router”。这两种方法是互补的。
也就是说,对于通用的框架知识,被动上下文目前优于按需检索。如果你维护一个框架并希望编程 Agent 能为其生成正确的代码,请考虑提供一个 AGENTS.md 片段,供用户添加到他们的项目中。这对于 Next.js 这类迭代快速的前端框架尤其重要。
实用建议:
- 不要等待 Skills 改进:随着模型在工具使用方面变得更好,差距可能会缩小,但眼下的结果才是最重要的。
- 激进地压缩:你不需要在上下文中放入完整文档。一个指向本地可检索文件的精简索引同样有效。
- 用 Evals 进行测试:构建针对训练数据中没有的 API 的测试。那才是文档访问权价值最大的地方。
- 为检索而设计:结构化你的文档,以便 Agent 可以轻松查找和阅读特定文件,而不是一次性需要所有内容。
我们的核心目标是将 Agent 从基于预训练的推理转变为基于检索的推理。事实证明,一个精心设计的 AGENTS.md 文档索引是目前实现这一目标最可靠、最有效的方式。这项技术的实践和讨论,也可以在 云栈社区 这样的开发者社区中找到更多共鸣和延伸。
研究与评估由 Jude Gao 完成。CLI 工具可通过 npx @next/codemod@canary agents-md 获取。