如果你的团队已经在用各种 AI 编程助手和 Agent 做日常开发,大概率见过这样一种场景:AI 生成的代码看似合理,一运行就报错;AI 编程工具给出的答案引用了过时的 API;你让 Agent 去调用内部服务,它明明看到了文档,却用错了参数。这些问题的表面原因各不相同,但背后往往指向同一个被低估的环节:文档。
最近,沃顿商学院教授 Ethan Mollick 提出的一个观点值得开发者重新审视:AI 实验室应该学会“自写文档”。这里说的不是用 ChatGPT 把 README 翻译成英文,而是指当 AI Agent 成为新的文档读者之后,文档的写作方式、组织结构和校验手段都需要重新设计。所谓“自写文档”,本质上是让 AI 系统在构建过程中,主动产出能被 AI 自身理解、解析和运行的上下文。
本文会拆解 Mollick 呼吁背后的逻辑,尽可能落到真实开发场景中。我会先解释为什么文档正在从“给人看的知识库”变成“给 AI 看的产品说明书”,再给出人机共读文档的写法、代码示例、团队落地步骤,以及常见的问题和排查思路。读完这篇文章,你可以直接找一个小模块做改造实验,评估自己团队的文档是不是已经具备“AI 原生”的基础。
1. 为什么 AI 实验室需要自写文档
Ethan Mollick 长期研究 AI 对人类学习和工作方式的影响,他的观察往往领先于多数技术博客的判断。从公开讨论看,他在多个场合强调过一个核心变化:我们正在进入一个“双重读者”时代。过去,文档只有人类读者;现在,AI Agent、大模型微调数据、语义检索系统,都在以极快的速度消费文档。如果文档仍然只面向人类组织,AI 就会在阅读时出现明显的信息损耗。
为什么损耗这么明显?原因在于 LLM 和 Agent 阅读文档的方式和人不一样。人类阅读文档时可以跳读、脑补上下文、利用格式直觉;AI Agent 更多依赖明文结构、确定性的锚点、可验证的示例。一份文档如果缺少结构化元数据,或者同一概念在不同章节使用了不同叫法,人类可能完全无感,但 Agent 检索后被混入不同实体,回答就会变得不可靠。
Mollick 呼吁 AI 实验室“自写文档”,更深一层是在提醒:当 AI 系统本身成为生产工具,文档不能再是开发完成后的“附加作业”,而应当成为开发流程中的一等公民。如果不能主动产出面向 AI 的文档,AI 开发工具的上限就会一直卡在“能用,但不可控”的状态。这也是为什么现在的 AI 编程工具、Agent 框架、模型厂商都在快速补齐文档工程能力——文档正在成为 AI 时代的接口资产。
对普通开发者来说,这个判断带来的直接行动是:我们不需要等 AI 实验室写完文档再学习,而是可以先把“自己项目的文档”改造成人机共读结构。这个改造不复杂,但收益非常直接——AI 编程工具在你代码库里的表现会大幅提升,Agent 调用的准确率也会明显改善。
2. 从人类文档到 AI 原生文档:概念拆解
2.1 什么是 AI 原生文档
所谓“AI 原生文档”,指的是文档在写作之初就考虑 AI 的读取方式,而不是事后为了让 AI 检索再补做一遍结构化。它和传统文档的关键区别在于:传统文档回答“这个功能怎么用”,AI 原生文档回答“这个功能是什么、边界在哪、输入输出如何、在什么条件下能调用”。
举个例子。一个普通 API 接口文档可能这样写:
登录接口,支持用户名密码登录,返回 token。而 AI 原生文档会这样写:
POST /api/v1/login 用途:用户登录后获取访问令牌。 请求体字段:username(string,必填,邮箱格式)、password(string,必填,8-32位)。 成功响应:200,返回 access_token,有效期 24 小时。 失败响应:401,表示用户名或密码错误;429,表示请求频率超限。 安全约束:本接口需要配合 https 使用;禁止在日志中记录明文密码。对比之下,第二种写法让 Agent 更容易解析字段、类型、边界和错误语义。AI 编程工具在生成调用代码时,可以直接把请求体和错误分支都写好。
2.2 人读文档和 AI 读文档的差异
| 维度 | 人类阅读文档 | AI 读取文档 |
|---|---|---|
| 信息组织 | 允许口语化、上下文推断、非线性阅读 | 依赖明确的层级、字段、命名一致性 |
| 示例代码 | 能容忍简化版 | 需要可直接复制运行的最小示例 |
| 版本信息 | 较少关注兼容性说明 | 必须说明适配的框架/语言/环境版本 |
| 语义一致性 | 对人类阅读影响小 | 直接影响向量检索和上下文理解 |
| 错误分支 | 常被忽略 | 必须有明确错误码和处理方式 |
| 更新频率 | 可以阶段性更新 | 必须与代码变更同步 |
这张表不是说以后写文档必须变成冷冰冰的机器语言。好的 AI 原生文档,依然可以保留面向人类的可读性,只是在结构上增加“机器可读层”。这就好比一个接口同时支持 JSON 和人类友好的注释,并不冲突。
2.3 文档正在从知识库变成运行时上下文
过去我们习惯把文档看成知识管理的一部分,技术团队用来沉淀经验和交接项目。但在 AI Agent 的应用链路里,文档已经不是静态的知识库,而是 Agent 运行的上下文来源。
一个 Agent 要完成“查询订单状态并发送提醒”的任务,它至少要读取订单接口文档、数据库字段约定、权限范围说明、失败重试策略。这些信息如果分散在 Chat 记录和同事的口头说明中,Agent 就无法稳定执行。当文档成为运行时上下文,它的地位就等同于配置文件、环境变量和 API 契约,属于系统的一部分。这也是为什么热门的 Agent 框架和文档工具,例如 LangGraph、OpenSpec、各类知识库检索工具,都在强调“上下文工程”(Context Engineering)——上下文的质量决定 Agent 的稳定性。
从这个角度看,Mollick 说“AI 实验室应该自写文档”,其实是在推动一种新角色:文档工程师不再是只会写说明书的人,而是要为 AI 设计高质量上下文的工程角色。
3. AI 读不懂文档,问题出在哪里
很多团队一上来就怪 AI 编程工具不够聪明,其实更多时候是文档没有准备好。AI 读不懂文档,常见的根因有五类。
3.1 文档结构缺少“机器可读层”
很多项目的 README 写得非常用心,有背景、有截图、有架构图,但 AI Agent 在解析时找不到稳定的接口定义。比如同一个项目里,“用户 ID”在不同文档中分别写成了 userId、user_id、用户ID,没有统一规范。人类阅读时可以猜出来,但 AI 做参数映射时会出错。
3.2 示例与真实环境脱节
文档里的示例往往是最简版本,没有依赖说明,没有初始化语句,没有异常处理。Agent 如果直接复制示例代码,往往会因为缺少环境变量或前置条件而运行失败。运行失败后,Agent 并不擅长“猜”文档里没写的部分,它会继续按自己的理解补全,问题就越来越偏。
3.3 缺少版本和废弃标记
代码库已经升级到 v2 接口,但文档还挂着 v1 的调用方式。AI 在训练和检索中可能同时吸收两个版本的信息,如果没有清楚的“deprecated”标记,它很可能输出过时代码。这也是为什么很多 AI 编程助手生成的旧 API 代码反而比新代码多。
3.4 上下文太长,缺少优先级
一份文档动辄几千行,Agent 的上下文窗口有限。它不知道该优先读取哪一段。人类拿到文档会先看目录和概述,但普通文档目录没有为 AI 标出“关键信息入口”。Agent 采用向量检索时,如果文档没有明确的摘要和结论前置,召回质量会很不稳定。
3.5 缺少可验证的自动化测试
文档里说“该函数返回结果”,但没人验证过这份文档是否正确。代码改了,文档没有跟着改。等到出了问题,人可能去读代码定位,而 Agent 会优先相信文档。错误的文档比没有文档危害更大,因为它的错误信息会被 AI 当成“事实”输出。
这些问题单看都不严重,组合起来就会让 AI 开发体验变差。真实项目里,AI 编程助手“一本正经地胡说八道”,有相当一部分原因不是模型能力不足,而是喂给它的文档本身就不具备确定性。
4. AI 原生文档的推荐结构
既然文档要同时服务人类和 AI,就需要设计一种“双层可读”的结构。下面是一套经过实践检验的模板,可以根据项目类型适当裁剪。
4.1 元数据区
文档开头用 YAML 或 JSON 描述文档本身的信息,包括文档适用范围、负责人、版本、更新日期、关联代码路径。这样 AI 在检索时可以快速判断文档是否适用于当前任务。
--- name: user-service-api description: 用户中心服务的接口文档,包含注册、登录、资料查询等接口。 version: 2.1.0 updated: 2025-06-01 maintainer: platform-team applies_to: user-service related_code: services/user-service/src/main/java/com/example/user tags: [auth, user, api] ---需要注意,日期和版本号只是示例,实际项目应当根据自己的版本管理规范来填。关键是description要写清用途,applies_to要明确对应哪个服务或模块。
4.2 摘要区
摘要区给 AI 一句话结论,避免上下文过长时丢失重点。结构上可以固定为“这个模块是什么”、“解决什么问题”、“在什么条件下使用”。
## Summary user-service 提供用户生命周期管理能力,包括注册、登录、资料更新、注销。 外部服务通过 REST API 调用本服务,认证使用 Bearer Token。 本服务不处理文件上传,文件上传由 file-service 负责。4.3 快速开始区
快速开始区必须是一个可复制的真实最小示例,而不是抽象的伪代码。每个示例都应该标注前置条件、环境变量和预期输出。
## Quick Start ### 前置条件 - JDK 17 - Maven 3.8+ - 本地已启动 MySQL,端口 3306,数据库名 user_db ### 启动服务 ```bash mvn spring-boot:run -Dspring-boot.run.profiles=local验证
curl -X POST http://localhost:8080/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"test@example.com","password":"12345678"}'预期返回 200 和 access_token。
注意这里的 Markdown 嵌套代码块是为了展示效果,实际写作时按正常代码块书写即可。 ### 4.4 API 定义区 这是 AI 读取最关键的部分。每个接口尽量写明请求方法、路径、请求体字段、响应字段、错误码和限流信息。字段表中应该明确类型、必填、默认值和约束。 | 参数名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | username | string | 是 | 用户邮箱,需符合邮箱格式 | | password | string | 是 | 密码,8-32 位,至少包含一个字母和一个数字 | | remember_me | boolean | 否 | 是否长期保持登录,默认 false | 错误码表也一样,要把 Agent 可能遇到的错误都列出来。 | 错误码 | HTTP 状态码 | 含义 | | --- | --- | --- | | AUTH_INVALID_USERNAME | 401 | 用户名不存在 | | AUTH_INVALID_PASSWORD | 401 | 密码错误 | | AUTH_LOCKED | 423 | 账号已被锁定 | | RATE_LIMITED | 429 | 请求频率超限 | ### 4.5 变更记录区 变更记录不是简单的日期列表,而是要明确“废弃了什么”、“新增了什么”、“对调用方有什么影响”。 ```markdown ## Changelog ### v2.1.0 - 新增 `/api/v1/user/profile` 接口,用于查询当前用户资料。 - 废弃 `/api/v1/user/info`,计划在 v2.3.0 移除。 - 登录接口新增 `remember_me` 可选参数。 迁移提示:调用方应在 30 天内切换到新接口。这种结构的好处是,AI 读到废弃信息时能及时更新自己的“知识”,而不是继续按照旧接口生成代码。
5. 从“写完代码再补文档”到“文档驱动开发”
仅仅把文档结构改好还不够,团队流程也需要微调。更稳妥的做法是引入“文档驱动开发”(Documentation-Driven Development),也就是先写文档契约,再实现代码。
5.1 传统流程的问题
传统流程通常是:开发功能 → 调试通过 → 补文档。这个模式的漏洞在于,文档永远是最后写,时间紧张时最先被砍。代码改过三个版本,文档还停留在第一版,人和 AI 都在读旧文档。
5.2 文档驱动开发的流程
文档驱动开发的顺序是:
- 写一篇简短的意图文档,说明这个功能解决什么问题。
- 写接口契约,包括请求、响应、错误码。
- 写一个最小可运行的示例。
- 让 AI 编程工具按照契约生成实现代码。
- 代码实现后再回填具体逻辑说明,校验文档与实现是否一致。
这样的流程下,文档从“产出物”变成了“开发输入”。AI 编程工具在生成代码时,也能把文档当成约束条件,而不是事后参考。
5.3 结合 AI 编程工具的具体操作
假设你在 Cursor、Copilot 或其他 AI 编程工具中开发一个用户注册接口。你可以先在项目里写好docs/user-registration.md,然后在 Prompt 中让 AI 按该文档实现。
请阅读 docs/user-registration.md,严格按照其中的接口契约实现注册接口。 注意以下约束: - 请求参数以文档字段表为准。 - 错误码必须与文档一致。 - 必须包含示例中的异常处理逻辑。 - 不要修改文档中标注为“禁止修改”的部分。这种写法的好处是,AI 可以拿到明确的“事实来源”,减少凭空发挥的空间。很多团队反馈 AI 编程工具生成代码后“需要大量返工”,问题往往不是 AI 能力不足,而是没有给 AI 提供足够清晰的约束文档。
6. 让 Agent 能真正消费文档:以函数和 MCP 工具为例
AI 原生文档的思想不仅作用于 README,也体现在代码注释和工具描述上。当一个 Agent 需要调用你的服务时,它看到的不只是接口文档,还包括函数签名、工具描述和参数说明。下面以 Python 函数和 MCP 工具描述为例。
6.1 函数级文档示例
一个普通的 Python 函数注释可能这样写:
def parse_order_id(raw_input): """解析订单ID""" ...而面向 AI 消费的注释应该更明确:
def parse_order_id(raw_input: str) -> str: """ 从用户输入中提取订单号。 Args: raw_input: 可能包含前缀和空格的原始字符串, 例如 "订单号: ORD-20250601-001"。 Returns: 提取后的订单号,例如 "ORD-20250601-001"。 Raises: InvalidOrderIdError: 如果输入中不包含合法订单号。 OrderIdTooLongError: 如果订单号长度超过32个字符。 Examples: >>> parse_order_id("订单号: ORD-20250601-001") "ORD-20250601-001" """这段注释对人和 AI 都有价值。它明确了输入输出类型、异常类型和示例。AI 编程工具在补全代码或生成测试时,能够直接理解函数的边界条件。
6.2 MCP 工具描述示例
在 AI Agent 生态里,MCP 工具描述是文档的另一种形式。工具描述写得越清晰,Agent 选择工具和参数的准确率就越高。
{ "name": "create_order", "description": "创建订单。调用前必须确认用户已登录且商品库存充足。订单金额单位是分,不要传元。", "inputSchema": { "type": "object", "properties": { "user_id": { "type": "string", "description": "用户ID,必须是 UUID 格式" }, "product_id": { "type": "string", "description": "商品ID" }, "quantity": { "type": "integer", "description": "购买数量,范围 1 到 99" } }, "required": ["user_id", "product_id", "quantity"] } }这里的关键细节是“订单金额单位是分,不要传元”。这种业务性说明如果只写在文档里,Agent 不一定能看到;写在工具描述中,Agent 调用时就能直接遵循。AI 应用开发中,这种“文档下沉”的思路正在成为常态。
6.3 用 AI 反向检查文档
当文档写入代码基础库后,可以定期让 AI 做一次“文档-代码一致性检查”。方式很简单:把某个模块的代码路径和对应文档路径贴给 AI 编程工具,让它列出不一致的地方。
请对比 services/user-service 下的代码和 docs/user-service.md 文档。 找出: 1. 已废弃但文档仍在对外宣传的接口。 2. 代码中已存在但文档未记录的接口。 3. 错误码与文档不一致的地方。 4. 示例代码中的参数与当前代码实际参数不匹配的地方。 每个问题都给出文件路径和修复建议。这种检查并不神秘,本质上是利用 AI 的高速阅读能力做一致性审计。跑一轮之后,你通常会发现自己团队的文档存在大量“过期信息”。修复这些信息,能让 AI 编程工具的整个使用体验上一个台阶。
7. 常见问题与排查方法
把文档改造成 AI 原生格式时,团队会遇到一些共性问题。这里整理了一份排查表,遇到问题时可以按表定位。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| AI 生成的代码不遵守文档约束 | 文档结构不够清晰,AI 没有识别约束的关键词 | 检查文档是否有明确字段表、错误码表和“禁止”类表述 | 使用 4.x 节的结构化模板重写文档 |
| Agent 调用接口时参数单位错误 | 文档和工具描述中没有写清单位或边界 | 检查接口文档的参数说明和 MCP 工具描述 | 在参数描述中显式写明单位、默认值、取值范围 |
| 文档更新后 AI 仍使用旧接口 | 变更记录未标注废弃信息和迁移说明 | 查看 Changelog 是否清晰 | 在每个废弃接口上增加“deprecated”标记和迁移指引 |
| AI 检索不到某个功能说明 | 文档缺少摘要区或关键词 | 使用向量检索工具查看召回结果 | 在文档开头增加 Summary 区,并统一术语表述 |
| 文档和代码不一致 | 文档驱动开发流程未落地 | 检查是否有 CI 文档校验步骤 | 增加文档-代码一致性检查脚本,或定期用 AI 审计 |
| 文档太长,Agent 处理不过来 | 没有按模块拆分文档 | 检查文档目录是否能精确对应代码模块 | 按服务或模块拆分文档,避免单文件超过 500 行 |
| 相同概念在不同文档中命名不同 | 缺少术语表 | 搜索项目中同一概念的不同写法 | 建立术语表文档,统一命名并全局替换 |
这些问题多数不是一次能改完的。更好的策略是先选一个模块做试点,把一份文档按照 AI 原生模板改写,然后观察 AI 编程工具在该模块上的正确率变化。有了效果,再横向推广。
8. 最佳实践与工程建议
如果现在开始改造团队文档,建议按下面几组原则推进。
8.1 先改存量,再立规范
不必第一天就要求所有文档全部重写。先从高频被 AI 消费的文档开始,比如核心服务 API、数据库表结构说明、环境变量配置文档、内部工具使用说明。这些文档改造后的收益最明显。
8.2 建立单一事实来源
同一个知识点只允许在一处详细说明,其他文档只能引用。比如用户 ID 的格式约定,只在术语表或核心数据字典中定义,其他文档写“用户 ID 格式参见数据字典”。这样可以避免多份文档各自维护一份观点,互相冲突。
8.3 用 CI 检查基础文档质量
文档也可以进 CI 流水线。至少可以检查:
- Markdown 结构是否合法。
- 接口文档中的字段表是否包含必填和类型列。
- 是否存在未更新日期的文档。
- 是否有明显过期的废弃标记。
更进一步,可以写一个脚本,把文档中的示例代码提取出来,编译或运行一遍。这个成本较高,但效果最好。
8.4 注意安全边界和最小权限
AI Agent 读取文档时,文档本身就是它的信息来源。因此,严禁在文档中写入生产环境密钥、数据库密码、内部网络地址等敏感信息。如果 Agent 需要连接环境,应通过环境变量和密钥管理服务注入,而不是写在 Markdown 里。给 Agent 分配的权限,同样遵循最小权限原则,避免它基于文档进行不受控的操作。
8.5 为 Agent 设计“失败提示”
好的文档不仅要告诉 AI 能做什么,还要告诉 AI 遇到什么情况应该停下来。可以在文档中增加“边界说明”小节,明确哪些操作不允许自动执行。
## Guardrails 本服务只允许查询 30 天内的订单。 禁止批量删除用户数据。 任何金额字段大于 10000 的订单修改操作,必须经过人工审批。这类说明在 Agent 调用工具时能形成安全护栏,减少误操作。
9. 总结与后续学习方向
Ethan Mollick 的呼吁,表面上是让 AI 实验室更重视文档,实际却是在提醒所有开发者:文档正在成为 AI Agent 运行链路里的关键输入。把文档做好,不是增加负担,而是降低 AI 编程工具和 Agent 的不确定性。
这篇文章重点梳理了几件事:
- AI 原生文档的含义和它与传统文档的区别。
- AI 读不懂文档的常见原因。
- 一套适合人机共读的文档结构模板。
- 文档驱动开发的落地方式。
- 函数注释、MCP 工具描述等贴近代码的文档写法。
- 常见问题的排查思路和工程实践建议。
接下来你可以做的,是找一个实际项目里最影响你效率的模块,挑一份文档,按第 4 节的结构改写一遍。然后在 AI 编程工具里让它基于这份文档生成代码,对比一下改版前后的表现。你会发现,文档质量对 AI 输出效果的影响,往往比换一个更大的模型还明显。
值得继续深入的方向包括:围绕 LangGraph、OpenSpec、LangChain4j 等工具做上下文工程,研究文档和向量检索的配合方式,以及在团队里逐步建立文档评审机制。文档这件事,一旦引入 AI 参与,就不再是“写没写”的问题,而是“AI 能不能确认它正确”的问题。先跑通一个小模块,再扩大范围,团队的 AI 开发效率会因此变得扎实很多。