用代码文档约束AI Agent:把业务规则写进操作手册
最近在折腾 AI agents 的时候遇到一个非常典型的痛点模型明明能力很强但让它去改代码、补文档、做重构时它总是按自己的理解来而不是按我的想法来。无论我怎么在对话里强调“注意边界条件”“保持接口不变”它转头就忘。后来我意识到真正的问题不在于提示词而在于我给它看的代码与资料没有形成一套完整的“行为说明书”。这篇文章就来聊一聊如何用高质量的代码文档把 agents 的行为约束到我们期望的轨道上也就是标题想表达的Get agents to do what I want with code documentation。会包含环境说明、原理拆解、一个完整的订单折扣模块实战案例以及常见问题和工程建议。无论你用的是 Claude Code、Cursor、Copilot还是基于 LangChain 自研的 agent这篇文章的思路都适用。1. 背景与核心概念1.1 为什么说 AI agents 总是“不听话”AI agents 本质上就是“大语言模型 工具调用 循环执行”的组合。它接到任务后会基于上下文进行概率推理生成行动计划再调用工具执行最后观察结果并迭代。问题往往出在第一步上下文里只有代码没有足够的意图说明时模型只能依赖它在预训练阶段形成的“常识”来补全缺失信息。而这些常识几乎不可能和你的业务规则完全一致。举个例子假设你有一个函数# 文件路径src/order/price.py def calc(price, level, codeNone): if level vip: result price * 0.8 elif level normal: result price * 0.9 else: result price if code SAVE10: # 注意这里拼写错误SAVE100 永远不生效 result result - 100 return result现在让一个 agent 修复 bug“客户反馈优惠券 SAVE100 没有生效”。没有额外说明时agent 大概率只会把SAVE10改成SAVE100然后收工。它不会主动去考虑“最终金额能不能小于 0”“客户等级大小写是否敏感”“优惠券是否支持叠加”这些问题因为代码里没有任何线索告诉它这些规则存在。这就是“不听话”的根源不是 agent 故意捣乱而是它缺少必要的约束信息。你脑子里的业务规则并没有进入 agent 的上下文。1.2 代码文档正在变成 agents 的“操作手册”传统的代码文档默认读者是人。人看到一段注释会结合自己的经验去理解但 agent 不同它对代码库的理解几乎完全来自上下文中的文本信息。当 agent 通过 RAG 检索、完整文件注入或工具调用来读取代码库时docstring、README、行内注释都会成为它推理的依据。也就是说代码文档已经不再是“给人看的说明书”而是“给 AI 看的操作手册”。你可以把文档理解为对话中的“系统提示词”。当文档写清楚了输入输出约定、边界条件、业务规则和禁止事项后模型生成代码时的搜索空间会被大幅压缩输出质量自然提升。这也是当前 agents 相关讨论中一个越来越强烈的共识想要构建“高效 agent”toward efficient agents不是换更大的模型而是提高任务输入的信息密度。代码文档就是成本最低、收益最明显的一种信息注入方式。1.3 本文适用读者与阅读收益这篇文章适合三类人后端开发工程师经常让 agent 帮忙改代码但总觉得它“不靠谱”。AI 应用工程师正在构建基于 RAG 或工具调用的 agent需要设计知识库结构。技术负责人希望把 agent 辅助编码能力固化到团队工程规范中。读完本文后你会掌握代码文档为什么能影响 agent 行为背后的原理是什么。函数注释、模块 README、项目级规范分别应该怎么写。通过一个订单折扣模块的实战案例直观看到“无文档”和“有文档”时 agent 输出的差距。常见的文档与 agent 配合问题以及对应的排查思路。2. 环境准备与版本说明2.1 常见的 agent 构建方式“AI agents”本身并没有唯一的实现标准不同人使用的方式差异很大。目前常见的有几类方式代表工具特点IDE 内置 agentCursor、GitHub Copilot、JetBrains AI Assistant直接在编辑器里交互能读取当前文件、终端输出命令行 agentClaude Code、OpenCode、Aider以终端为载体能读写文件、执行命令、跑测试自定义 agent 流程LangChain、LlamaIndex、自研调度通过代码编排大模型、工具、记忆和检索流程测试类 agentPlaywright Test Agents 等自动生成、执行和修复端到端测试虽然工具形态各不相同但它们读取代码库的方式是类似的要么把整个文件放入上下文要么通过检索把相关片段注入上下文。所以文档质量对 agent 行为的影响是跨工具存在的本文的示例思路具有通用性。2.2 本文示例环境本文的实战案例使用 Python 编写重点演示“文档如何影响 agent 在编码任务中的表现”。具体的版本参数可以按实际环境调整思路不需要依赖特定版本操作系统Windows / macOS / Linux 均可。Python3.10 及以上需要支持str | None这类联合类型语法。测试框架pytest。agent 工具Claude Code、Cursor、Copilot 或任意支持读取仓库文件的 agent 工具。不需要额外的大模型 API Key只需要你能在本地运行一个 agent 工具即可。如果你的项目是 Java、Go、TypeScript 等其他语言把 docstring 替换成对应语言的注释规范即可核心方法是相同的。2.3 示例项目结构我们把实战案例设计成一个极简的订单服务模块order-service/ ├── src/ │ └── order/ │ ├── __init__.py │ ├── price.py │ └── README.md ├── tests/ │ └── test_price.py └── agent_task.mdsrc/order/price.py订单金额计算核心文件是 agent 要修改的目标。src/order/README.md模块级文档描述模块职责、计算规则和测试要求。tests/test_price.pypytest 测试文件。agent_task.md交给 agent 的任务描述文件。3. 核心原理代码文档是如何影响 agent 行为的3.1 文档是 agent 的“短时记忆”大语言模型有上下文窗口限制。agent 在处理一个大型代码仓库时不可能把全部文件一次性塞进上下文它需要依赖检索系统或工具调用在合适的时机读取文件片段。这时文档就相当于 agent 的“短时记忆”当 agent 决定要修改price.py时它会读取这个文件同时可能读取同目录的README.md以及相关测试文件。这些文本会成为它当前决策的全部依据。如果你的文档写得模糊agent 就会把“模糊”当成自由度自行脑补规则如果你的文档写得很精确agent 就会把“精确”当成约束严格按照规则生成代码。这里有一个很微妙的点对文档精度要求最高的并不是传统意义上的代码注释而是“决策规则”。一个函数写着 “计算折扣价格” 没有任何约束力但写着 “任何扣减不能把最终金额扣成负数” 就有了明确的边界。模型在执行时面对负数结果会主动修正。3.2 文档检索让 agent 在正确的时间看到正确的内容在 RAG 模式下文档会被切分成 chunk然后通过向量检索或关键词检索把与任务最相关的片段注入上下文。文档组织得越结构化检索命中率越高。比如函数级 docstring 解决“这个函数怎么用”的问题。模块级 README 解决“这个模块有哪些规则”的问题。项目级约定解决“整个仓库的代码风格和禁止事项”的问题。当 agent 需要修改折扣逻辑时它搜索到的内容最好能直接回答下面几个问题输入参数有哪些限制折扣和优惠券的执行顺序是什么最终金额是否允许为 0哪些情况需要抛异常新增规则后需要补哪些测试如果这些信息分散在邮件、IM 聊天记录或者某个人的脑海里agent 是永远“看不见”的。文档的意义就是把隐性知识显性化并放到 agent 能检索到的位置。3.3 高质量文档的信息密度原则给 agent 看的文档和给人看的文档写作目标不同。人可以通过语境推断agent 更依赖字面约束。因此写 agent 友好的文档时我建议遵循“信息密度优先”原则原则说明写清输入输出约束参数类型、取值范围、是否可空、默认值写清业务规则折扣优先级、优惠券是否叠加、金额保底写清异常行为什么时候抛异常抛什么异常写清禁止事项什么操作不允许做避免 agent 自作主张写清测试要求修改代码后必须补充哪些用例这些内容不一定要很长但一定要准确。一个 5 行的 docstring如果覆盖了上述关键信息胜过 50 行口号式的“注意代码质量”“请遵循良好实践”。4. 完整实战案例用文档让 agent 正确重构订单折扣模块4.1 场景设定与业务规则假设我们的订单服务上线后收到一个客户反馈我是 VIP 用户买了 300 元的商品使用了优惠券 SAVE100结果后台显示我只需要付 200 元。但我同事计算说应该是 170 元你们算错了。我们来分析一下。正确业务规则是先按客户等级打折再使用优惠券减免。VIP 享受 9 折示例中为了区分SVIP 享受 8 折。SAVE100 是立减 100 元。优惠券不可叠加一个订单只能用一个。最终支付金额不能小于 0。那么 300 元的商品VIP 打 9 折后是 270 元再减 100 元最后是 170 元。这是符合业务预期的。现在的问题可能是代码中优惠券的判断条件写错了导致没有真正减 100或者存在某种路径让金额计算异常。我们把它设计为一个编码任务让 agent 来修复。4.2 无文档版本的 agent 输出首先看一下最初的有 bug 代码# 文件路径src/order/price.py def calc(price, level, codeNone): if level vip: result price * 0.8 elif level normal: result price * 0.9 else: result price if code SAVE10: # bug: 拼写错误SAVE100 永远不生效 result result - 100 return result注意这段代码里还隐藏着两个与业务规则冲突的问题VIP 应该是 9 折但代码里写的是 8 折。正常用户应该是 9 折但代码写的是 8 折逻辑明显颠倒了。如果让 agent 在不看任何文档的情况下修复“SAVE100 未生效”的问题一个非常典型的输出是这样的# agent 的“无文档”修改结果 def calc(price, level, codeNone): if level vip: result price * 0.8 elif level normal: result price * 0.9 else: result price if code SAVE100: # 只是修正了拼写 result result - 100 return result看起来 agent 完成了任务但问题很大VIP 折扣 0.8 仍然是错的和业务规则要求的 0.9 不一致。没有处理price为负数的情况。没有处理最终金额小于 0 的情况。code为未知字符串时没有任何异常提示。返回值没有保留两位小数。这就是“无文档约束”的真实状态agent 只看到了拼写错误没有能力也不应该去猜测其他潜在问题。它把任务理解成了“尽可能少地改动代码”而不是“让代码符合业务规则”。4.3 编写 agent 友好的代码文档为了改善 agent 的表现我们首先修改src/order/README.md把模块级规则显性化# src/order 模块说明 ## 模块职责 本模块负责订单金额相关计算包括商品小计、客户等级折扣、优惠券减免和最终支付金额计算。 ## 核心入口 - calc_discount_price计算最终支付金额的唯一入口。所有价格计算必须收敛到该函数。 - 其他模块不允许绕过此函数直接计算订单金额。 ## 金额计算规则 1. 计算顺序先按客户等级打折再应用优惠券减免。 2. 折扣只作用于商品原始总价不允许先减免再打折。 3. 客户等级customer_level仅支持 - normal不打折 - vip9 折 - svip8 折 4. 优惠券coupon_code目前仅支持 - SAVE100立减 100 元 - 其他编码一律视为不合法需要抛出 ValueError。 5. 每种等级折扣只能应用一次优惠券不可叠加。 6. 最终支付金额不得小于 0。如果计算结果小于 0统一返回 0.00。 7. 所有金额结果统一保留两位小数。 ## 参数校验规则 - original_price 必须大于等于 0否则抛出 ValueError。 - customer_level 必须是上述三种取值之一大小写敏感否则抛出 ValueError。 - coupon_code 只能为 None 或 SAVE100。 ## 测试要求 - 新增或修改规则时必须同步补充 tests/test_price.py 中的 pytest 用例。 - 至少覆盖以下场景 - normal 无优惠券 - vip SAVE100 - svip SAVE100 - 超额优惠导致金额小于 0 时返回 0.00 - 不合法客户等级抛出 ValueError然后修改src/order/price.py中的函数补上完整的 docstring# 文件路径src/order/price.py def calc_discount_price(original_price, customer_level, coupon_codeNone): 根据客户等级和优惠券计算订单的最终支付金额。 业务规则 1. 折扣优先级先按客户等级打折再应用优惠券减免。 2. 客户等级customer_level仅支持 normal、vip、svip - normal不打折 - vip享受 9 折 - svip享受 8 折 3. 优惠券coupon_code目前只支持 SAVE100立减 100 元。 4. 优惠券不可叠加使用同一订单只允许一个 coupon_code。 5. 最终支付金额不能小于 0计算结果如果小于 0统一返回 0.00。 6. 结果统一保留两位小数。 参数说明 - original_price: float商品原始总价必须大于等于 0。 - customer_level: str客户等级标识大小写敏感。 - coupon_code: str | None优惠券编码没有优惠券时传 None。 返回值 - float最终支付金额四舍五入保留两位小数。 异常 - ValueError当 original_price 小于 0或 customer_level 不受支持 或 coupon_code 非法时抛出。 同时我们可以在agent_task.md里给 agent 一个清晰的任务描述# Agent 任务 请修复订单模块中的优惠券计算 bug用户反馈 SAVE100 优惠券没有生效。 ## 要求 1. 先阅读 src/order/README.md 和 src/order/price.py 中的 docstring。 2. 修改代码使 SAVE100 优惠券可以正常减免 100 元。 3. 修改过程中必须遵守 README 中声明的全部业务规则。 4. 修改完成后运行 pytest tests/test_price.py -v确保测试通过。 5. 如果 README 中的规则与旧代码实现不一致以 README 为准并同步调整代码。4.4 有文档版本的 agent 输出有了以上文档约束后一个表现合格的 agent通常会在分析阶段就列出需要关注的问题然后给出类似下面的修改结果# 文件路径src/order/price.py def calc_discount_price(original_price, customer_level, coupon_codeNone): if original_price 0: raise ValueError(original_price 不能为负数) if customer_level not in (normal, vip, svip): raise ValueError(f不支持的客户等级: {customer_level}) if customer_level normal: result original_price elif customer_level vip: result original_price * 0.9 elif customer_level svip: result original_price * 0.8 if coupon_code is not None and coupon_code ! SAVE100: raise ValueError(f不支持的优惠券: {coupon_code}) if coupon_code SAVE100: result result - 100 result max(result, 0.0) return round(result, 2)可以看到这次 agent 的变化非常明显它保留了“先等级折扣后优惠券减免”的顺序。修正了 VIP 折扣为 9 折。补上了original_price的负数校验。补上了客户等级非法校验。补上了优惠券非法校验。增加了金额保底逻辑max(result, 0.0)。返回值保留两位小数。agent 还可能会顺手生成一份测试文件# 文件路径tests/test_price.py import pytest from src.order.price import calc_discount_price def test_normal_without_coupon(): assert calc_discount_price(300, normal) 300.0 def test_vip_with_save100(): assert calc_discount_price(300, vip, SAVE100) 170.0 def test_svip_with_save100(): assert calc_discount_price(300, svip, SAVE100) 140.0 def test_amount_floor_at_zero(): assert calc_discount_price(100, svip, SAVE100) 0.0 def test_invalid_customer_level(): with pytest.raises(ValueError): calc_discount_price(300, gold) def test_invalid_coupon_code(): with pytest.raises(ValueError): calc_discount_price(300, vip, UNKNOWN)4.5 对比分析文档到底改变了什么我们把“无文档版”和“有文档版”的 agent 输出放在一张表里对比维度无文档 agent 输出有文档 agent 输出对 SAVE100 拼写错误的修复修复了修复了VIP 折扣率保持了错误的 0.8修正为 0.9负数金额校验没有有非法客户等级校验没有有非法优惠券校验没有有金额保底 0 元没有有结果保留两位小数没有有补全测试用例没有有换句话说没有文档时agent 只能做“最小改动”有文档时agent 才能做“符合预期的完整修复”。这不是模型能力的差距而是任务上下文信息量的差距。5. 常见问题与排查思路在实际使用中把文档写好之后agent 的表现也不一定立刻符合预期。下面整理了几个高频问题。问题现象常见原因解决思路agent 完全不读取 README任务提示中没有要求 agent 阅读文档或 README 文件名不规范在任务描述中明确要求先读 README或者在系统提示词中声明agent 读了文档但没遵守规则文档规则被大量无关内容淹没信息密度不足精简文档把业务规则放到“规则”小节用编号列表写清楚agent 对文档中的示例过度泛化文档只给了正例没有给反例和边界情况在文档中补充“禁止事项”和“异常场景”agent 修改代码后测试仍然失败文档与代码实现不同步agent 以旧实现为准保证文档与代码同步更新并让 pytest 测试作为最后防线文档太长超出上下文窗口单个 README 或 docstring 包含太多无关信息文档分层函数级聚焦函数模块级聚焦规则项目级聚焦全局约定agent 不理解某些业务术语文档默认读者熟悉业务但模型并不熟悉首次出现术语时用括号给出简单解释或示例下面针对几个高频问题稍微展开。5.1 agent 完全忽略文档这是最让人头疼的情况。原因通常不是 agent 能力不行而是任务描述中没有“强制”它去读文档。你可能在对话里说“看一下 README”但 agent 判断依赖代码也能完成就会跳过。解决方法是在任务描述和系统提示词里明确写修改代码前必须先阅读README.md和docstring。如果文档与代码冲突以文档为准。要让 agent 感觉文档是“硬约束”而不是可选项。5.2 agent 误解文档中的示例文档中如果只给了一个正常的计算示例比如calc_discount_price(300, vip, SAVE100) 170.0agent 可能会以为只要这个 case 正确就算完成。它不会主动推导“如果价格更高折扣后仍然大于 0 会怎样”。所以文档里不仅要给正常示例还要给边界示例。比如明确写“当svip用户购买 100 元商品并使用SAVE100时计算结果应该为 0.00而不是 -20.00”。5.3 文档过长导致上下文失效这个问题在大型项目中非常常见。一个模块的 README 写了 1000 行里面既有历史演进说明也有权限矩阵还有部署步骤。agent 在检索或读取文档时关键的业务规则被埋在大量无关信息里最终“看得多记得少”。推荐的做法是“分层文档”函数级 docstring 控制在 20 行以内只描述函数本身的输入输出约束和业务规则。模块级 README 控制在 80 行以内只描述模块边界、核心入口和全局规则。项目级文档控制在一屏以内让 agent 快速理解全局结构。如果规则确实很多可以考虑用表格浓缩。5.4 文档与实现不同步这是工程团队最容易忽视的问题。文档写的是“VIP 9 折”代码已经改成了“VIP 8 折”agent 拿到相互矛盾的资料后往往会选择一种自己更“熟悉”的方式处理结果导致规则混乱。应对策略很简单把文档变更和代码变更绑定到同一个 PR 里。任何修改业务规则的代码提交必须同时更新对应文档。如果测试能跑文档检测也可以做成 CI 的一部分至少保证 README 中提到的接口不存在明显过期。6. 最佳实践与工程建议6.1 把文档当成 agent 的“系统提示词”如果你的团队已经在使用 Claude Code、Cursor 或自定义 agent建议把仓库里最重要的规则整理到一个固定位置的文档中比如AGENTS.md或CLAUDE.md并在系统提示词中让 agent 自动加载。示例# 文件路径AGENTS.md ## 项目简介 这是一个基于 FastAPI 的订单服务核心业务模块位于 src/order。 ## Agent 必须遵守的规则 1. 修改任何业务代码前必须先阅读对应模块的 README.md。 2. 如果 docstring 与代码实现冲突以 docstring 为准并报告冲突。 3. 新增业务规则时必须同步补充 pytest 测试。 4. 不要修改 src/order/price.py 之外的不会影响金额计算的文件。 5. 禁止直接调用 calc_discount_price 之外的方式计算订单金额。系统提示词示例# 文件路径agent_boot.py核心片段 SYSTEM_PROMPT 你是一名资深 Python 工程师负责维护 src/order 模块。 在修改代码之前必须先阅读 AGENTS.md、src/order/README.md 和对应函数的 docstring。 所有修改必须满足文档中声明的业务规则尤其是金额保底、折扣优先级和异常行为。 如果文档与你的常识冲突以文档为准并在回复中说明冲突点。 这其实就是“building effective agents”中反复提到的核心思路把规则前置而不是依赖模型在生成过程中临时猜测。6.2 为 agent 设计“可执行的文档”“可执行”的意思是文档中的每一条规则最好都能对应到具体的测试断言。这样 agent 在修改代码后可以用测试结果来验证自己是否真的遵守了文档。比如 README 中写了“金额不能小于 0”那么tests/test_price.py中就应该有一个用例def test_amount_floor_at_zero(): assert calc_discount_price(100, svip, SAVE100) 0.0当 agent 读到这条测试时它的理解会比单纯看到文字更精确。测试失败会触发“deep agents interrupt”机制——agent 执行过程中被打断、观察失败、修正策略。这种“测试驱动 agent”的做法在 Playwright test agents 等场景中已经被验证是一种高效手段让 agent 自己写测试、自己跑测试、再根据失败结果修正代码。所以与其告诉 agent“要保证代码正确”不如给它一组能表达正确含义的测试。测试和文档一起构成了双重约束。6.3 用“禁止事项”约束 agent 的自由度人类开发者看到“禁止事项”会多留一个心眼agent 也一样。文档中明确写“禁止做什么”比只写“你应该做什么”更有效。例如在订单模块文档中写## 禁止事项 - 禁止在 price.py 之外计算最终支付金额。 - 禁止修改 calc_discount_price 的函数签名。 - 禁止把金额结果直接截断为整数必须保留两位小数。 - 禁止在未同步更新测试的情况下修改业务规则。这些“禁止”会显著缩小 agent 的探索空间防止它在重构时顺手“优化”掉你不希望改动的部分。6.4 文档与测试对齐的模板如果你正在团队内推动 agent 友好代码库建设可以参考下面的文档模板# 模块名 ## 模块职责 一句话说明该模块负责什么和周边的边界在哪里。 ## 核心入口 - 函数A职责说明。 - 函数B职责说明。 ## 业务规则 1. 规则一。 2. 规则二。 ## 参数与异常 | 参数 | 类型 | 约束 | 异常 | | --- | --- | --- | --- | | price | float | 0 | ValueError | ## 禁止事项 - 禁止 XX。 ## 测试要求 - 新增规则需要补充哪些测试。我自己的经验是一旦团队所有核心模块都按这个模板整理agent 的“一次修改成功率”会明显提升代码 review 的沟通成本也会下降。6.5 面向 agents 的代码库维护策略最后给几个工程层面的建议文档优先写在离代码最近的地方。docstring 和模块 README 比 wiki 更有用因为 agent 能更快检索到。保持文档与代码同步。建议在 CI 中加入简单的文本匹配检测比如 README 中提到的函数名必须存在于代码中。定期用真实任务做验证。拿一个历史 bug 或新需求让 agent 基于当前仓库完成检查它是否遵守了文档。不要只靠大模型自动生成文档。模型生成的文档可能很流畅但可能漏掉真实的业务规则。文档的价值恰恰来自你积累的那些“模型不知道的东西”。7. 总结与学习路线这篇文章从一个很具体的痛点出发AI agents 不听话其实是因为上下文缺少约束。代码文档在这里起到了“操作手册”的作用它能把你脑子里的业务规则显性化让 agent 看得见、读得懂、遵守得了。通过订单折扣模块的实战我们看到了同一份 bug在没有文档时 agent 只做最小修复在有文档时 agent 会主动补齐异常处理、金额保底、折扣率修正和测试用例。差别不在于模型而在于输入的信息密度。如果你想继续深入建议按这个路线学习先把你最常用的模块补上 docstring 和 README。把团队里最容易出错的业务规则写进“禁止事项”。再给关键规则补上 pytest 测试让 agent 能通过跑测试验证自己的修改。最后把 AGENTS.md 方案固化到仓库并让所有成员在编写新模块时同步维护文档。如果你最近也在为 agents 的输出不稳定而头疼不要急着换更大的模型先把自己仓库里的文档补齐。很多时候不是模型不够聪明而是我们没把“正确”这件事说清楚。如果本文对你有帮助可以收藏备用然后在自己的项目里试着给一个模块补全文档让 agent 重跑一次历史任务你会非常直观地感受到变化。