代码文档这件事听起来简单做起来却很容易让开发者崩溃。尤其是当你尝试让 AI Agent 帮你自动更新文档、生成 API 说明、补充注释时经常会出现“它以为自己懂了实际上完全跑偏”的情况。最近在技术社区看到不少关于 Building Effective Agents 和 Deep Agents 的讨论核心矛盾其实都指向同一个问题如何让 Agent 真正按照你的意图完成任务而不是自由发挥。本文将从一个非常典型的场景——让 Agent 自动维护代码文档——出发完整拆解一套可落地的方案。内容包括 Agent 的工作机制、为什么文档类任务最容易失控、如何通过上下文约束和工具设计让 Agent 准确执行以及一套可以复制到本地运行的实战示例。无论你是在研究 AI 编程助手还是想在项目中接入 Agent 自动化流程这篇文章都有参考价值。1. 背景与核心概念1.1 什么是 Agent为什么与代码文档有关AI Agent智能体可以理解为具备“感知 → 决策 → 行动”闭环的 AI 程序。它不只是回答你一句话而是能够根据你设定的目标自行拆解任务、调用工具、浏览代码、读取文件最终产出结果。近一年里Agent 在代码生成、代码审查、自动化测试等领域发展非常快但“自动维护代码文档”却一直是个尴尬的领域。原因在于代码文档任务有一个独特性质模糊性极高。你让 Agent“写文档”它不知道你希望文档面向谁、用什么粒度、是否包含示例、是否要解释为什么而不是只解释是什么。代码本身是确定的而文档是高度主观的产物。代码是事实文档是观点。 Agent 擅长提取事实但很难猜出你的观点。1.2 为什么“让 Agent 做我想做的事”这么难很多开发者第一次尝试时都以为只需把代码丢给 Agent再说一句“帮我写文档”就行了。但实际上你会发现 Agent 经常只写了函数签名注释完全没有调用示例把内部实现细节全都写进去了读者却看不懂对外接口语言风格跟你的项目文档完全不统一文档结构混乱没有遵循现有文档的章节组织更麻烦的是它可能生成一些看似合理、实际上与代码行为不符的内容。这不是 Agent 不聪明而是 Agent 缺少足够的约束信息。要让 Agent 真正做到“做你想做的事”关键不在于换更强大的模型而在于把任务定义从模糊变为明确。1.3 Agent 处理代码文档的常见工作模式根据当前社区的主流实现方式可以让 Agent 做代码文档任务的三种工作模式工作模式描述适用场景单次生成一次性把代码文件发给 Agent让其生成完整文档零散文件、一次性标准化增量更新针对代码变更 diff让 Agent 只更新受影响部分日常迭代、持续维护自主探索Agent 自行遍历项目、调用工具链、定位入口后生成文档大型项目、模块梳理第三种模式最接近“Deep Agent”概念也就是 Agent 不止依赖一次模型的输入输出而是通过多轮工具调用来逐步理解系统。不过对于代码文档任务来说第三种模式并不一定是唯一正确答案我们优先要解决的是意图对齐的问题。2. 让 Agent 准确理解意图原理与拆解2.1 意图对齐的三个核心要素从大量实践来看让 Agent 准确执行代码文档任务需要同时满足三个条件第一任务说明要像需求文档一样精确。不能只说“给这个类写文档”要说清楚文档用途、目标读者、必须包含哪些部分、不写哪些内容。第二Agent 需要有足够大的上下文窗口来感知代码全貌。只给它一个文件它无法理解模块之间的关系。只给它一个目录树它又看不到实现细节。所以 Agent 需要主动去读取相关文件这就是工具调用存在的原因。第三Agent 需要能验证自己的输出。对于文档任务来说验证意味着检查代码符号是否与文档一致、示例是否可运行、链接是否指向正确的目标。这三个要素中第一点最容易被人忽略。很多人以为 Agent 无所不能其实它的能力边界很大程度上取决于你的指令质量。2.2 提示词从模糊到精确的转变先来看一个反例。假设你有下面这样一个 Python 函数# 文件路径order_service.py def calculate_total_price(order_items, discount_rate0.0, tax_rate0.06): total 0.0 for item in order_items: total item[price] * item[quantity] total * (1 - discount_rate) total * (1 tax_rate) return round(total, 2)如果你只说“帮我生成文档”Agent 可能输出## calculate_total_price 计算订单总价。 参数 - order_items订单项 - discount_rate折扣率 - tax_rate税率 返回总价这个文档对吗技术上没错但信息量极低。它既没有说明order_items里每个元素需要包含哪些 key也没有说明折扣率和税率的计算公式与顺序更没有一个可以直接复制运行的示例。而如果你给出精确的任务说明效果会完全不同。来看后面实战部分如何写 Agent 指令模板。2.3 上下文工程给 Agent 看什么文件除了指令本身Agent 能看到的上下文范围同样重要。一个常见的错误是“把整个仓库塞给模型”这样不仅 token 消耗巨大而且大量无关文件会干扰模型判断。更合理的做法是为 Agent 提供代码上下文索引项目目录结构相关模块的入口文件被调用函数的定义同目录下已存在的文档文件作为风格参考变量命名与类型注解信息。这些信息可以帮助 Agent 在生成文档时保持与现有代码风格一致。为了做到这一点我们的 Agent 需要有读取文件和搜索符号的能力这两项能力可以通过工具函数来实现。3. 环境准备与基础配置3.1 技术选型说明为了演示完整流程本文选择 Python 作为运行环境来实现一个文档生成 Agent。这样做的原因有两个第一Python 在处理 AI Agent 工具链时生态成熟第二Python 代码的 AST 解析能力非常方便可以帮我们自动化提取函数签名和类型信息。技术栈说明Python 3.10 及以上版本OpenAI 兼容的 Chat API或本地部署的模型推理服务标准库ast用于解析 Python 代码得到函数签名pathlib用于跨平台路径操作可选pydantic或jsonschema用于校验 Agent 的输出结构。具体的依赖版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。3.2 项目结构规划我们先定义一个最小但完整的项目结构doc-agent/ ├── agent.py # Agent 主逻辑负责调用模型与工具 ├── tools.py # 工具函数读取文件、解析代码、搜索符号 ├── instructions.py # 文档任务指令模板 ├── sample_project/ │ ├── order_service.py # 示例业务代码 │ └── README.md # 已有文档作为风格参考 └── run.py # 入口脚本这个项目不大但已经可以覆盖“读取代码 → 提取结构 → 生成文档 → 输出文件”的完整链路。3.3 安装依赖先在项目目录下创建虚拟环境并激活cd doc-agent python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate如果你使用在线模型 API安装 OpenAI Python SDKpip install openai如果你希望调用本地模型通过 Ollama、vLLM 或 LM Studio 提供的 OpenAI 兼容接口则不需要额外安装 SDK直接使用requests也可以完成调用。pip install requests建议将 API Key 和模型名称写到.env文件中避免硬编码在代码里。下面是一个.env的示例OPENAI_API_KEYsk-xxxxxxxxxxxxxxxx OPENAI_BASE_URLhttps://api.openai.com/v1 MODEL_NAMEgpt-4o-mini如果你使用兼容 OpenAI 协议的本地服务第三行可以写成类似OPENAI_BASE_URLhttp://localhost:11434/v1 MODEL_NAMEqwen2.5:14b注意不同模型的指令遵循能力差异很大文档生成任务建议使用指令遵循能力较强的模型不要使用太小的量化模型。4. 核心代码实现4.1 定义工具函数从代码到结构化信息要让 Agent 不瞎猜我们首先需要把代码变成结构化信息。Python 的ast模块可以帮我们完成这件事。# 文件路径tools.py import ast from pathlib import Path from typing import Any, Dict, List def get_project_tree(root: str, max_depth: int 3) - str: 生成项目目录树文本方便 Agent 理解整体结构。 root_path Path(root) lines [] lines.append(f{root_path.name}/) def walk(current: Path, prefix: str, depth: int): if depth max_depth: return entries sorted(current.iterdir(), keylambda p: (p.is_file(), p.name)) for entry in entries: if entry.name.startswith((., __pycache__, venv)): continue if entry.is_dir(): lines.append(f{prefix}├── {entry.name}/) walk(entry, prefix │ , depth 1) else: lines.append(f{prefix}├── {entry.name}) walk(root_path, , 0) return \n.join(lines) def parse_python_module(file_path: str) - Dict[str, Any]: 解析 Python 文件提取类与函数的签名信息。 source Path(file_path).read_text(encodingutf-8) tree ast.parse(source) result { functions: [], classes: [] } for node in tree.body: if isinstance(node, ast.FunctionDef): result[functions].append({ name: node.name, args: [arg.arg for arg in node.args.args], docstring: ast.get_docstring(node), }) elif isinstance(node, ast.ClassDef): methods [] for item in node.body: if isinstance(item, ast.FunctionDef): methods.append({ name: item.name, args: [arg.arg for arg in item.args.args], docstring: ast.get_docstring(item), }) result[classes].append({ name: node.name, methods: methods, docstring: ast.get_docstring(node), }) return result这里需要注意的是ast只能提取静态信息无法理解函数内部的业务逻辑。但静态签名信息已经足够作为 Agent 的骨架真正的业务逻辑由模型结合源码来理解。4.2 实现文件读取工具Agent 在执行过程中需要按需读取更多文件。我们把它封装成一个工具函数并加上路径安全检查避免 Agent 读到项目目录之外的文件。# 文件路径tools.py继续追加 PROJECT_ROOT Path(__file__).resolve().parent def read_file(file_path: str) - str: 读取文件内容限制在项目目录内。 full_path (PROJECT_ROOT / file_path).resolve() if not full_path.is_relative_to(PROJECT_ROOT): return 错误不允许访问项目目录之外的文件。 if not full_path.exists(): return f错误文件不存在 {file_path} return full_path.read_text(encodingutf-8)4.3 定义文档生成指令模板这部分是整个方案的核心。指令模板决定了 Agent 最终输出的文档质量。我们需要把模糊的“帮我写文档”替换成明确的要求。# 文件路径instructions.py DOC_GENERATION_PROMPT 你是一个资深技术文档工程师。请根据如下信息为一个 Python 模块编写使用文档。 ## 项目结构 {project_tree} ## 目标文件路径 {file_path} ## 目标文件源码 python {source_code}已解析的代码结构{parsed_structure}已有文档风格参考{style_reference}文档要求输出 Markdown 格式使用中文。文档必须包含以下部分模块简介说明该模块解决什么问题安装或依赖说明如果存在外部依赖核心 API 说明每个函数或方法需要包含参数说明、返回值说明、一个可直接运行的调用示例常见使用场景示例注意事项与边界条件。不要在文档中编造源码中不存在的参数或行为。调用示例必须与函数签名一致。如果已有文档存在风格需尽量保持一致。输出只包含文档正文不要输出解释性文字。 ### 4.4 Agent 主逻辑工具循环 现在我们实现 Agent 主循环。它的工作方式是 1. 接收用户给出的目标文件路径 2. 调用 tools 获取项目结构、源码和解析结果 3. 将信息拼装进指令模板调用模型接口 4. 校验输出写回目标文档文件。 python # 文件路径agent.py import json import os from pathlib import Path from openai import OpenAI import tools from instructions import DOC_GENERATION_PROMPT class DocumentationAgent: def __init__(self, api_key: str | None None, base_url: str | None None, model: str gpt-4o-mini): self.client OpenAI( api_keyapi_key or os.getenv(OPENAI_API_KEY), base_urlbase_url or os.getenv(OPENAI_BASE_URL), ) self.model model def generate_documentation(self, file_path: str, output_path: str | None None) - str: source_code tools.read_file(file_path) if source_code.startswith(错误): return source_code parsed_structure tools.parse_python_module(file_path) project_tree tools.get_project_tree(sample_project) style_reference self._load_style_reference(file_path) prompt DOC_GENERATION_PROMPT.format( project_treeproject_tree, file_pathfile_path, source_codesource_code, parsed_structurejson.dumps(parsed_structure, ensure_asciiFalse, indent2), style_referencestyle_reference, ) response self.client.chat.completions.create( modelself.model, messages[ {role: system, content: 你是一个严格遵循指令的文档生成助手。}, {role: user, content: prompt}, ], temperature0.3, ) doc_content response.choices[0].message.content final_output output_path or str(Path(file_path).with_suffix(.md)) Path(final_output).write_text(doc_content, encodingutf-8) return f文档已生成{final_output} def _load_style_reference(self, file_path: str) - str: 查找同目录下已有的 README 或文档文件作为风格参考。 target_dir Path(file_path).parent for candidate in target_dir.glob(README*): return candidate.read_text(encodingutf-8)[:2000] return 当前目录没有已有文档。4.5 入口脚本最后是运行入口# 文件路径run.py import os from dotenv import load_dotenv from agent import DocumentationAgent load_dotenv() if __name__ __main__: agent DocumentationAgent( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), modelos.getenv(MODEL_NAME, gpt-4o-mini), ) result agent.generate_documentation( file_pathsample_project/order_service.py, output_pathsample_project/order_service.md, ) print(result)执行方式python run.py如果你的模型接口兼容 OpenAI 格式这段代码可以原样运行如果使用本地模型只需要修改.env中的OPENAI_BASE_URL和MODEL_NAME。5. 运行示例与输出效果5.1 示例业务代码为了验证效果我们在sample_project/order_service.py中放入一个相对完整的订单模块# 文件路径sample_project/order_service.py from typing import List, Dict class OrderItem: def __init__(self, product_id: str, name: str, price: float, quantity: int): self.product_id product_id self.name name self.price price self.quantity quantity def subtotal(self) - float: return self.price * self.quantity class OrderService: 订单服务负责创建订单、计算总价与折扣。 def __init__(self): self._orders: Dict[str, List[OrderItem]] {} def create_order(self, order_id: str, items: List[OrderItem]) - str: 创建订单并保存到内存。 self._orders[order_id] items return order_id def calculate_total(self, order_id: str, discount_rate: float 0.0, tax_rate: float 0.06) - float: 计算订单总价。 计算顺序先应用折扣再计算税额。 items self._orders.get(order_id, []) subtotal sum(item.subtotal() for item in items) subtotal * (1 - discount_rate) subtotal * (1 tax_rate) return round(subtotal, 2)5.2 预期输出文档运行/run.py之后Agent 生成的order_service.md大致会包含以下内容实际会因模型不同有所差异# OrderService 模块使用文档 ## 模块简介 该模块实现了订单的创建与金额计算能力。核心类 OrderService 负责保存订单和计算订单总价OrderItem 表示订单中的单个商品条目。 ## 核心 API 说明 ### OrderItem | 方法 | 说明 | | --- | --- | | __init__(product_id, name, price, quantity) | 创建订单条目 | | subtotal() | 计算条目的小计金额 | #### 调用示例 python item OrderItem( product_idA1001, name机械键盘, price199.0, quantity2, ) print(item.subtotal()) # 398.0OrderServicecreate_order(order_id, items)创建订单并保存到内存数据中。参数类型说明order_idstr订单唯一标识itemsList[OrderItem]商品条目列表calculate_total(order_id, discount_rate, tax_rate)计算订单总价先应用折扣再计算税额。调用示例service OrderService() service.create_order(1001, [item]) total service.calculate_total(1001, discount_rate0.1, tax_rate0.06) print(total) # 总价注意事项订单数据保存在内存中服务重启后数据丢失discount_rate建议取值范围为 0 ~ 1如果没有找到订单calculate_total会返回 0.0。这个输出最大的特点是**所有示例都与真实代码签名一致**没有编造参数也没有臆想不存在的功能。 ### 5.3 验证文档是否与代码一致 在上面的输出中calculate_discount 方法被完整地列入了文档。如果 Agent 输出的文档中出现了源码中不存在的函数说明它在幻觉。为了避免这种情况我们可以在 Agent 流程中加入“符号一致性检查” python # 文件路径verify.py import ast import re def extract_documented_symbols(markdown_text: str) - set: 粗提取 Markdown 文档中的函数/方法名。 symbols set() for line in markdown_text.splitlines(): # 捕捉形如 xxx() 或 xxx(yyy) 的文本 matches re.findall(r([a-zA-Z_][a-zA-Z0-9_]*)\([^)]*\), line) symbols.update(matches) return symbols def extract_code_symbols(source_code: str) - set: tree ast.parse(source_code) symbols set() for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): symbols.add(node.name) return symbols # 使用方式 # 如果 doc_symbols - code_symbols 为空说明文档中的函数都真实存在。这个校验步骤看似简单但非常有效。真实的工程项目中文档与代码不一致往往是导致线上事故的隐藏原因之一。6. 常见问题与排查思路6.1 Agent 生成的文档里出现了代码中不存在的参数问题现象常见原因解决思路文档函数签名与源码不一致模型上下文太长遗漏了关键代码片段将源码分段传入提取函数签名后一并放入 prompt文档中出现未定义的参数模型依赖既有经验进行了补全在指令中明确说“不要编造参数”并增加一致性校验生成的示例运行报错模型对可选参数理解错误在 prompt 中提供示例输入与预期输出排查步骤先检查文档中的函数名是否都在源码中存在再对照源码检查每个参数的类型最后把示例代码复制出来实际运行能跑通再写入文档。6.2 Agent 一次读取的代码太多生成质量下降如果整个项目有几百个文件不建议一次性把全部源码发给模型。较好的思路是让 Agent 先读取初始化文件和入口模块再按依赖关系逐步读取。这其实就是“Deep Agent”模式的核心思路不追求一次到位而是分步探索。实现上可以把generate_documentation中的单次模型调用改成多轮循环第一步读取目录结构确定入口文件。 第二步读取入口文件提取类与函数。 第三步针对每个函数读取其依赖模块。 第四步生成文档草稿。 第五步根据一致性检查结果修正文档。6.3 输出格式不稳定有时不返回 Markdown模型偶尔会输出大段解释性文字而不是直接返回文档。解决方式有两种一种是在 prompt 里重复强调“只输出文档正文”另一种是在代码里做后处理只截取第一个#标题之后的内容。推荐前者后处理容易把有效内容截断。6.4 本地小模型生成效果差如果你使用 7B 或 13B 量级的本地模型指令遵循能力往往不足以支撑多段格式要求。这时可以降低同时出现的约束数量把文档要求拆成多次对话改用结构化输出要求模型返回 JSON再由代码格式化为 Markdown或者干脆使用 32B 以上参数量的模型。7. 最佳实践与工程建议7.1 把指令模板当作独立资产维护不要在代码里散落写 prompt。将指令模板放在单独的模块或 YAML 配置文件中方便后续反复调整和复用。文档生成任务的特点是prompt 的收益高于模型升级你精调一次模板效果可能比换一个大模型更明显。7.2 引入代码结构解析减少模型负担能够用规则代码解决的问题就不要让模型去猜。函数签名、参数列表、类型注解、类的继承关系这些都应该用ast、tree-sitter或ctags之类的工具提前解析出来再提供给模型。模型只负责“撰写说明文字”而不是“猜测代码结构”。7.3 文档生成必须和代码审查联动不要运行一次 Agent 就完事。文档是会被用户直接阅读的错误信息会直接误导调用方。建议在 CI 中接入一个简单的检查任务当代码变更时自动检查对应文档中的函数签名是否和源码一致。不一致就直接阻断合并。7.4 安全性控制 Agent 的文件访问范围当 Agent 拥有“读取文件”和“写入文件”的能力时必须限制其操作范围。在上面的示例中read_file函数使用了is_relative_to做路径校验。如果你的 Agent 还具备写文件能力更要用白名单机制约束避免 Agent 修改到业务配置文件。7.5 考虑不同文档粒度的混合策略大型项目通常需要两级文档模块级文档说明模块职责、主要类关系、典型调用链路函数级文档每个函数的参数、返回值、异常与示例。不建议让 Agent 在一次任务中同时产出两级文档。更可靠的策略是先让 Agent 阅读模块并生成大纲确认大纲无误后再逐函数生成详细说明。这种方式虽然调用次数变多但可控性明显更高。7.6 关注 token 成本与缓存代码文档任务通常会消耗大量 token因为代码本身很长。实际项目中可以使用语义缓存如果代码文件最近一次修改时间没有变化就直接使用上次生成的文档不重复调用模型。也可以在 prompt 中只保留 diff 部分的代码而不是整个文件。8. 总结与下一步方向本文围绕“如何让 Agent 按你的意图写代码文档”这一主题拆解了意图对齐的三个核心要素精确的指令、足够的结构化上下文、以及可验证的输出。同时给出了一个基于 Python 和 OpenAI 兼容接口的完整实战示例覆盖了从代码解析、指令拼接到文档生成与一致性校验的完整链路。如果你现在正准备在自己的项目里接入文档 Agent建议优先做好两件事把代码结构解析工具做扎实这决定了 Agent 的下限把指令模板写具体这决定了 Agent 的上限。下一步可以继续深入的方向包括让 Agent 根据 git diff 自动更新文档、将生成的文档接入现有文档站构建流程、加入 API 调用示例的自动验证逻辑等。另外也可以关注社区中关于 Efficient Agents 的设计思路尝试用更少的模型调用完成同等的文档维护任务。如果你在实际运行中遇到了文档跑偏、格式不稳定或者模型幻觉的问题欢迎把具体的报错或 prompt 留在评论区我们一起讨论更优的解法。