公司动态

用代码文档约束AI智能体:从行为规范到检索增强实践

📅 2026/8/31 11:09:07
用代码文档约束AI智能体:从行为规范到检索增强实践
实际开发中越来越多的团队开始把“补测试、改接口、写组件”这类任务交给 AI 智能体agents完成。但很多人会遇到同一个问题智能体改代码很积极改出来的结果却不符合项目约定。有人把原因归结为模型能力不够其实在大多数情况下真正的问题是项目里缺少一份能让智能体读懂、能检索命中、能明确约束行为的代码文档。这里要说的不是“把文档写得更详细”这种笼统建议而是把代码文档当作智能体的行为规范来管理。文档的作用对象不再只有人还包括模型和工具循环。只有当文档中的规则、边界、示例和反例能被智能体在合适的时机读到它才可能做你真正想让它做的事。这篇博客围绕“如何用代码文档让智能体按你的意图执行”这条主线展开。先讲智能体为什么依赖文档再调整写文档的思路然后给一个最小落地示例接着处理上下文有限时的检索问题最后讲如何验证、排查以及生产环境里的文档治理方式。1. 为什么智能体总在做别的事问题根源在文档1.1 智能体执行任务的真实机制模型加工具循环要理解智能体为什么“不听话”先要看清楚它的执行机制。智能体本质上是一个大语言模型加工具循环系统提示、用户请求、工具返回结果共同构成上下文模型根据这些内容生成下一步动作动作可以是文本回复也可以是调用函数、读取文件、执行命令。这个机制决定了三件事智能体没有对项目的“常识”它只知道上下文里写了什么。如果项目约定没有出现在上下文里它只能根据训练数据中的常见模式去推测。推测的结果往往是最常见的写法而不是你的团队约定。代码文档在这里的作用不是“参考资料”而是智能体做出决策时的外部约束。只要关键规则在上下文中可见模型就更有可能在生成阶段走对方向而不是事后让人类修改。这个理解很重要因为很多人是在“生成之后人工检查”这个环节补规则而不是在“生成之前注入规则”。1.2 文档缺失时智能体会用训练数据的常见模式“脑补”当文档缺失或模糊时智能体会自动填补空缺。这里的“填补”不是严格推理而是概率补全它选出训练数据里最常出现的模式。举几个具体例子项目规定所有日志使用结构化 JSON 输出但上下文里没有这条规则智能体直接生成console.log或print。项目规定金额统一用整数分long传输文档没写智能体用Double表示并在边界产生精度问题。项目规定业务校验失败要返回 4xx 错误码文档没写智能体为了“尽快跑通测试”在catch里吞掉异常。这些问题的共同点是智能体的输出在语法上没有问题甚至能通过基础测试但不符合项目的长期约定。靠人 review 能发现一部分却无法覆盖每一次生成。文档就是把这些约定变成模型可依赖的显式信号的载体。1.3 代码文档的定位变化从人的说明书变成智能体的行为规范传统代码文档面向人读者能结合背景知识补全上下文能容忍含蓄表达。智能体文档则不同它的阅读者是一个“没有项目背景、但能严格按文字执行”的模型。两类读者的需求差异很大维度给人看给智能体看阅读方式浏览、搜索、跳转按上下文顺序读取或检索命中重点信息背景、动机、设计思路规则、边界、示例、反例表达风格可以含蓄、可留白要直接、无歧义默认假设开发者有项目背景智能体可能完全不认识项目过期影响开发者发现并修正智能体会继续按过期规则执行这里有一个常见误区以为“给智能体写文档”意味着把文档改造成机器人语言。实际上文档仍然要给人看只是需要额外增加一层“机器可提取的决策规则”。修改思路不是把文档写得像提示词而是让每条规则都能被清晰检索、独立理解、直接执行。2. 让文档成为智能体的“行为接口”写文档的思路调整2.1 智能体需要的是决策规则不是功能列表很多 README 的功能列表写得详细智能体却仍然不知道该怎么做。原因在于功能列表回答的是“系统有什么”而智能体真正需要的是“遇到什么情况怎么做”。以支付服务为例功能列表支持余额查询、转账、退款。决策规则金额从外部传入时单位必须是分使用long接收禁止使用Double。智能体执行任务时真正需要的是后者。它不需要你重复描述业务价值而是需要一组可以在生成代码时直接引用的判断条件。建议每份文档至少回答四类问题边界这个模块允许做什么禁止做什么。约束命名、类型、格式、事务边界、日志规范等。示例期望的输入输出最好带代码片段。反例常见错误写法明确写着“不要这样做”。这四类信息不是给人看的补充材料而是智能体生成过程中需要直接决策的关键点。2.2 一份 README 的改造前后对比改造前的 README 通常长这样项目名、技术栈、启动命令、几个接口路径。它不是没有用只是智能体读了之后仍然不知道写代码时要遵守哪些规则。改造后可以增加“行为约束”和“开发规则”章节。下面是一个最小模板# Payment Service ## 行为约束 - 金额统一使用整数分long传递禁止使用 Double。 - 对外接口使用 JSON字段命名使用 camelCase。 - 所有写操作必须在事务内执行禁止在事务内调用远程接口。 - 入参校验失败返回 4xx并携带 errorCode 字段。 ## 错误处理 - 业务校验失败返回 4xx响应体包含 errorCode 和 message。 - 非预期异常必须记录完整堆栈后抛出禁止 catch 后不处理。 ## 反例 - 不要这样做double balance 10.5; 正确做法long balanceInCents 1050L;这段文档的作用在智能体处理“新增一个转账接口”时非常明显。如果检索系统把“行为约束”这一块命中了模型就可能在设计接口时直接使用long而不是Double如果文档没写模型大概率会按训练数据里的常见方式生成后续再被 review 打回。注意这个模板要结合实际项目补充不能只抄结构。每一条规则都需要能被测试验证例如“字段命名使用 camelCase”可以用静态检查或接口测试校验。2.3 文档放哪里位置和命名直接影响检索命中文档位置不是小问题。智能体读取上下文时通常会优先读 README但不会主动翻遍每个子目录。如果规则分散在十几个 markdown 文件里又没有明显命名检索或上下文注入时很容易遗漏。推荐按下面这个结构组织repository-root/ ├── README.md ├── docs/ │ ├── behavioral-rules.md │ ├── api-contracts.md │ └── examples/ │ └── payment-codecs.md ├── src/ └── tests/README.md只放项目概览和入口详细规则放到docs/下用文件名直接表达规则领域。behavioral-rules.md这种命名在智能体检索时比doc1.md、help.md更容易命中。每个文档块尽量控制在能独立理解的大小避免一个章节超过几千字否则检索命中后也会占用大量上下文。3. 落地示例用一份智能体可读文档驱动“补测试”任务3.1 任务场景与仓库结构设计接下来看一个能直接复现的落地示例让智能体为支付服务补充一组单元测试。任务输入是“代码仓库 规则文档”输出是“符合项目约定的测试文件”。这个场景足够小能看出文档是否存在、是否被读到的差异。仓库结构按第 2 章设计payment-service/ ├── README.md ├── docs/ │ ├── behavioral-rules.md │ └── examples/ │ └── payment-codecs.md ├── src/ │ └── PaymentService.java └── tests/ └── PaymentServiceTest.java给智能体的任务描述可以是为 PaymentService 补充单元测试。 开始前先读取 docs/behavioral-rules.md。 测试代码必须遵守其中所有约束。这里的任务描述只有一句话真正约束行为的是文档。换句话说文档是“行为接口”任务描述只是入口。3.2 行为规则文档的关键内容docs/behavioral-rules.md除了上一节的“金额单位”“错误处理”规则还需要增加测试场景下模型最容易出错的部分## 单元测试约定 - 测试用例必须包含正常分支、边界分支和异常分支。 - 金额边界测试使用 Long.MAX_VALUE / 0L / 负数禁止只测正数。 - mock 远程调用时必须模拟成功和超时两种结果。 - 断言必须检查具体字段值禁止只检查返回值是否为 null。 - 反例不要写只验证“不抛异常”的测试那会给实现放水。这里的关键是“反例”。大模型生成测试时默认倾向是优先保证代码通过而不是暴露问题。如果文档没有“禁止只验证不抛异常”这类反例生成的测试很可能缺乏保护力。文档写出反例后模型才有机会在生成阶段意识到“要通过负向用例表达约束”。3.3 把文档注入智能体上下文的三种实践学习环境里最简单的方式是把文档拼进系统提示或任务提示你是该项目的开发智能体。 开始任务前先读取 docs/ 目录下的规则文档。 必须遵守 docs/behavioral-rules.md 中的约束。 如果规则与用户请求冲突以规则文档为准并说明原因。如果智能体工具支持自动读取仓库文件也可以直接把规则文档路径加入检索范围。对于小型仓库还可以在终端里把文档聚合到一个上下文文件cat docs/behavioral-rules.md docs/examples/payment-codecs.md .agent/context.md再让智能体读取.agent/context.md。这种方式适合本地手动实验不推荐作为生产方案因为一旦仓库变大全文塞入会消耗大量上下文而且长文档会让模型忽略关键部分。真正需要的是第 4 章的检索方式。4. 上下文有限时如何让关键文档被精确命中4.1 全文塞入、检索增强、工具调用怎么选当规则文档较少时全文塞入简单直接但项目进入一定规模后全文塞入会带来两个问题token 消耗高以及长文本中关键规则容易被模型忽略。这时需要在“检索增强”和“工具调用”之间选型。方式适合场景优点缺点全文塞入文档少、上下文窗口大信息完整实现简单长文档浪费 token关键规则可能被淹没检索增强文档多、按需取用精准节省上下文需要检索组件存在召回遗漏风险工具调用智能体主动读取指定文档实时、按需、灵活依赖智能体判断能力可能少读文件在高效智能体efficient agents的讨论中上下文管理是关键议题。一个常见建议是不要试图把所有知识塞进上下文而是把“决策规则”做成可检索片段让模型在需要时命中。针对代码文档这个场景最推荐的是“检索增强 工具调用”组合任务开始时先检索命中后把相关规则注入上下文。4.2 最小检索实现先把 Markdown 拆成可检索块前面提到检索是对“文档块”操作的。第一步是把 Markdown 按标题拆成多个块。下面是一个最小 Python 实现用于扫描目录下所有 markdown 文件import os from dataclasses import dataclass dataclass class DocChunk: title: str path: str text: str def load_markdown_chunks(root: str) - list[DocChunk]: chunks [] for base, _, files in os.walk(root): for name in files: if not name.endswith(.md): continue path os.path.join(base, name) with open(path, encodingutf-8) as f: lines f.read().splitlines() title name text_lines [] for line in lines: if line.startswith(## ): if text_lines: chunks.append(DocChunk(title, path, \n.join(text_lines))) text_lines [] title line[3:].strip() else: text_lines.append(line) if text_lines: chunks.append(DocChunk(title, path, \n.join(text_lines))) return chunks这个实现把二级标题作为切块边界每个块包含标题、文件路径和正文。实际项目中切块策略要根据文档结构调整如果三、四级标题也承载重要规则可以递归切更细如果文档短可以直接按文件切。第二步是给查询评分。为了降低门槛先用关键词评分def score(chunk: DocChunk, query: str) - int: text chunk.text.lower() terms [t.lower() for t in query.split()] return sum(1 for t in terms if t in text) def retrieve(query: str, chunks: list[DocChunk], top_k: int 3): scored sorted(chunks, keylambda c: score(c, query), reverseTrue) return scored[:top_k]真实项目可以用 BM25、向量检索或混合检索替换这里的评分函数。核心思路不变把文档切块、建立索引、根据任务查询返回最相关的规则片段。4.3 检索结果的优先级排序检索命中只是第一步返回顺序也很重要。如果一条规则是“禁止使用 Double”另一段示例代码恰好演示了double balance模型看到上下文时容易被示例带偏。因此要将约束类规则放在检索结果前面。可以给文档块增加权重标记def rank(chunk: DocChunk, query: str) - float: base_score score(chunk, query) if 行为约束 in chunk.title or 禁止 in chunk.text: base_score 2.0 if 示例 in chunk.title: base_score - 0.5 return base_score优先级排序的原则是行为约束先于功能描述反例先于示例规则先于背景。这样即使 Model 在长上下文中只读前几段也能优先看到硬性约束。这里的权重调节只是为了说明方向落地时要基于评估集反复调整。5. 验证智能体是否按文档执行从“看结果”到“看过程”5.1 用 Playwright 回放行为验证页面规则是否生效如果智能体的任务涉及网页交互Playwright 是验证行为是否符合文档的好选择。比如文档规定“支付页金额输入只允许整数分”你可以让智能体生成页面代码后用 Playwright 跑一遍行为测试检查金额输入是否真的拒绝小数import { test, expect } from playwright/test; test(支付页金额输入禁止出现小数, async ({ page }) { await page.goto(/pay); await page.fill(#amount, 10.5); await page.click(#submit); const message await page.locator(.error-message).textContent(); expect(message).toContain(金额单位应为分); });这类测试验证的不是“页面能不能打开”而是“规则是否真正落到行为上”。它既检查智能体的实现结果也间接检查文档是否被有效读取。如果文档规则命中Playwright 测试大概率通过如果不命中测试会直接失败。5.2 关键决策点设置中断检查避免错误路径走到底长任务里智能体可能在某个分支选错方向再继续执行会造成更大返工。深度智能体中断interrupt机制对这种场景很有用在关键决策点暂停让模型先说明自己的执行计划再进入实际修改。一个可落地的做法是在任务提示中要求智能体在动手前输出“规则检查报告”执行前请先回答 1. 本任务涉及的规则文件路径是什么 2. 需要遵守的关键约束有哪些 3. 当前计划中可能存在哪些违反约束的风险点在开发环境人可以检查报告后决定是否继续在自动化环境可以把报告内容写入日志由脚本判断关键约束是否被提及。这个验证点应该放在“生成代码之前”而不是“生成之后”因为改代码的成本远大于改计划。5.3 构建小型评估集持续度量“按文档执行”的达标率文档写得好不好不能靠感觉。建议维护一个十个左右任务组成的评估集每个任务包含任务描述、规则文档、期望行为、禁止行为。每次修改文档后把评估集跑一遍记录通过率。下面是一个最简单的评估记录表任务文档是否命中是否遵守金额类型是否遵守错误处理结果新增转账接口是是是通过补充支付测试是是否失败修复退款接口否是是部分通过评估集的价值在于回归。你改了某份文档后可能某个任务变好另一个任务变差。没有评估集很难发现这种此消彼长。这也是“building effective agents”这条实践里最容易被忽略的一环把智能体的行为当成被测系统而不是一次性脚本。6. 常见问题排查文档写了智能体还是乱来6.1 现象一指令冲突时智能体选了错误指令表现系统提示或任务描述说“使用 double”规则文档说“金额用 long”智能体最终选择了 double。原因模型面对冲突指令时往往会优先响应更靠近问题末尾的指令或者更显式的指令。如果规则文档没有声明自身优先级模型无法判断哪条才是权威。检查方式打印进入模型的完整上下文看规则文档是否真的被读取以及它和任务描述之间是否直接矛盾。处理方式在规则文档开头增加优先级声明## 优先级 本文档约束优先于任务描述和系统提示中的通用建议。 当出现冲突时以本文档为准。这是最简单也最有效的补救。毕竟任务描述由人临时编写规则文档才是团队的长期共识。6.2 现象二关键约束被忽略表现文档里明确写了“金额使用 long”智能体生成的代码仍然用 double。原因有两类一是约束文档没有进入检索结果或者进入后排在上下文末尾被截断二是文档中只有一句抽象描述没有配合反例和示例模型没有把这条规则当作“必须遵守的硬约束”。检查方式把智能体执行任务的日志输出打开查看检索返回了哪些块观察上下文是否被截断。处理方式先确认文档块大小把“行为约束”章节拆成独立小文件再对约束块加权重确保检索命中后排在前面最后补充反例让模型看到“不要这样做”和“正确做法”的对比。6.3 现象三文档更新后智能体仍按旧规则执行表现团队把支付金额单位从“元”改成“分”更新了文档但智能体生成的代码仍按旧的“元”逻辑处理。原因智能体工具可能缓存了旧文档或者.agent/context.md这类聚合文件没有重新生成或者检索索引没有随文档更新。检查方式先确认实际注入上下文的文件内容再看检索索引的构建时间。不要只看代码仓库里的文档已经改了要确认“智能体读到的版本”已经改了。处理方式把文档更新纳入持续集成流程。文档变更后自动重建检索索引并触发第 5 章里的评估集任务用自动化方式验证新旧规则是否都生效。6.4 排查链路与检查清单遇到“文档写了智能体还是乱来”这类问题按下面的顺序排查任务描述是否把目标说清楚有没有和文档冲突。文档文件路径是否正确智能体是否真的读取了。检索结果里有没有包含关键规则排序是否符合预期。上下文是否足够长关键规则有没有被截断。规则之间是否存在冲突。聚合文件或检索索引是否已经更新。查看智能体中间日志确认从哪一步开始偏离规则。这套检查顺序适用于多数场景。按照“输入 - 路径 - 检索 - 上下文 - 冲突 - 缓存 - 过程日志”的顺序排查比直接怀疑模型能力更有效率。7. 生产环境中的文档治理写一次多处复用7.1 文档变更触发回归评估学习环境里可以手动更新文档、手动跑评估集。生产环境不行因为一次规则调整可能影响多个智能体任务人工验证成本太高。推荐做法是把文档更新和智能体评估都纳入 CI。简单流程可以是开发者修改docs/下的规则文档。推送后 CI 自动重建检索索引。CI 用修改后的文档触发预定义评估集。查看每个任务的“按文档执行”达标率。未通过任务直接阻塞合并。这个流程把文档当成可测试的代码资产而不是随时可能过期的文本。没有这一步“文档驱动智能体”只能停留在小规模实验无法进入正式发布流程。7.2 文档分级哪些规则只对部分智能体开放生产环境还有一个容易被忽略的点不是所有文档都应该暴露给所有智能体。比如安全敏感规则、内部部署细节、数据库账号约定如果全部注入上下文会带来信息泄露风险。建议按文档敏感度分级级别示例使用方式公开约定命名规范、错误码格式所有智能体可检索内部规则模块边界、数据库字段约定仅相关模块智能体可使用机密信息密钥、生产环境地址禁止写入任何智能体上下文分级后再控制智能体的工具权限可以避免“文档写得很清楚但权限被滥用”的问题。这个设计应该在文档第一版就规划而不是等人数变多后再补。7.3 落地前可复用的检查清单最后整理一份可以直接拿去用的检查清单文档中是否包含行为约束、示例和反例。每条规则是否能用测试或静态检查验证。文档是否按模块拆分块大小是否适合检索。检索索引是否会随文档更新自动重建。规则文档是否声明了相对任务描述的优先级。是否设置了关键决策点的中断检查。是否维护了最小评估集并能度量达标率。文档是否按公开、内部、机密分级。生产流程里文档变更是否触发回归评估。智能体日志是否能还原出“读了哪些文档、在哪一步偏离”。真正的做法不是把所有规则一次性写全而是从最小可用的规则集开始跑通“文档 - 检索 - 执行 - 验证 - 回归”这条链路再逐步补充规则。每一次规则调整都当成一次代码变更来对待智能体的可预测性才会逐步提高。