AI Agent驱动代码文档自动化生成与验证的工程实践
这次我们来看一个来自 Hacker News“Show HN”思路的代码文档项目Get agents to do what I want with code documentation。标题翻译过来很直白——让 AI Agent 按照我的预期去写代码文档。很多人一开始觉得这种项目无非就是“把代码丢给大模型让它生成 README”但真正做起来才发现文档生成的难点从来不在“能不能写”而在“写出来的东西是不是我要的”。命名是否一致、接口是否有真实示例、依赖变更有没有同步到安装说明、废弃函数是否有标注这些细节才是文档质量的分水岭。这篇文章会把这个项目思路展开成一整套可落地的工程方案。我会先给你一个核心能力速览然后按“需求结构化 - 环境准备 - Agent 核心流程 - 测试验证 - 接口化与批量任务 - 资源占用 - 常见问题”的顺序拆开讲。你可以直接照着一套最小可运行例子搭起来把它接进自己的仓库里。内容偏工程实践适合已经在用 AI 编程工具、准备把文档流程自动化或者正在做内部 AI Agent 工具链的开发者。1. 核心能力速览能力项说明项目类型AI Agent 代码文档生成与维护核心目标让 Agent 按预设规范生成、校验、更新代码文档典型工作方式读取仓库 - 分析 diff - 生成文档 - 校验 - 提交输入源码目录、git diff、已有文档、文档规范文件输出README、模块说明、API 文档、CHANGELOG 草稿、架构说明运行平台Linux / macOS / Windows 都可以启动方式命令行脚本、FastAPI 包装、定时任务硬件要求取决于你使用的基础模型API 调用不需要本地显卡用本地模型需按实际模型测试是否支持 API可以封装成 HTTP 服务是否支持批量任务支持按目录 / 模块 / 文件批量处理适合读者开源维护者、内部项目组、技术文档负责人这个方案本质上不是单个工具而是一个“文档 Agent 工作流模板”。你可以理解成先定义一个“文档规范文件”再把仓库变更历史和源码片段塞给 Agent让它按规范产出文档产出之后不是直接采用而是跑一遍链接检查、命令检查、格式检查最后再决定是否合入。这套结构里真正要花心思的是规范文件和验证层Agent 本身只需要一个稳定的对话接口。2. 适用场景与使用边界这个项目思路适合解决三类问题。第一类是仓库文档常年滞后代码改了注释没改注释改了 README 没改第二类是文档风格不统一有人写得很细有人一句话带过混在一起阅读成本很高第三类是文档里的代码示例没有经过验证用户复制出来根本跑不通。把这三件事交给 Agent 自动做可以省掉大量重复劳动。但它也有明确的不适用场景。如果你的项目处于架构频繁调整的早期阶段文档还在快速探索期这时候让 Agent 自动化生成反而会放大混乱。另外它不应该替代人工审核尤其是涉及安全边界、加密算法、权限模型这一类文档必须由熟悉系统的人确认之后才能发布。合规方面也要注意几件事。接入外部大模型 API 时不要把包含内部密钥、客户数据、未公开商业信息的文件丢进上下文代码仓库本身如果有特殊许可证Agent 生成的文档也属于仓库内容发布前要确认合规如果以后扩展成“自动提交 PR”的模式一定要有权限控制不能让它绕过 Code Review 直接推到主干分支。3. 让 Agent 理解“我要什么”需求结构化很多 Agent 项目跑偏原因不是模型能力不够而是你根本没有把一个可执行的“标准”告诉它。让 Agent 写文档至少要在 prompt 或规范文件里明确几个维度。第一是目标读者。README 面对的是使用者模块文档面对的是二次开发者API 文档面对的是调用方。读者不同详略和用词完全不同。第二是风格约束。比如代码块必须带语言标注、函数说明必须包含参数类型和返回值、标题层级不允许跳级。第三是示例要求。文档里出现的命令必须真正可执行接口示例必须和当前代码签名一致。第四是变更范围。一次任务只处理当前 diff 涉及到的文件不要顺手把全部文档重写一遍。为了达成这些约束我建议在仓库根目录维护一个文档规范文件例如docs/SPEC.mdAgent 每次生成前都要读取它。# 文档规范 ## 目标读者 - README项目使用者第一次接触项目的人 - docs/api.md接口调用方 ## 风格要求 - 所有代码块必须标注语言 - 函数说明格式作用 / 参数 / 返回值 / 示例 - 不写空泛的形容词比如“非常强大”“易于使用” ## 示例要求 - 代码示例必须与当前代码签名一致 - bash 命令必须可执行 - 禁止使用未定义的变量名 ## 变更范围 - 只处理本次 git diff 涉及的文件 - 不重写与本次变更无关的章节把规范文件放在仓库里还有一个好处每次变更都能沉淀经验。你发现 Agent 写的文档哪类问题多就直接往 SPEC 里加一条规则下一轮生成就会收敛很多。这个迭代过程比频繁改 prompt 更可控。除了规范文件还需要把“什么是这次任务的目标”写清楚。可以定义一个简单 JSON 任务描述{ task: update_api_doc, repo: ./my-project, change_scope: [src/auth/login.py, src/auth/token.py], target_doc: docs/api.md, max_output_tokens: 3000 }这样每次调用 Agent 之前只需要更新这个任务文件。脚本读它、拼上下文、调模型、写回文档人负责审核。整个过程保持单一职责Agent 不需要猜测你要干什么你也不需要反复在 prompt 里描述同一件事。4. 环境准备与最小可运行架构这一步我们先不管 Agent 本身有多聪明先把能跑起来的环境准备好。如果按最小依赖来搭你需要Python 3.10 以上Git一个可用的 LLM API 客户端环境OpenAI 兼容接口或本地推理服务目标代码仓库一份克隆到本地requests、python-dotenv两个 Python 依赖目录结构建议这样组织doc-agent/ ├── agent.py # Agent 核心逻辑 ├── spec_reader.py # 读取文档规范 ├── diff_tool.py # git diff 采集 ├── validator.py # 文档验证 ├── server.py # FastAPI 包装层可选 ├── tasks/ │ └── task.json # 当前任务定义 ├── outputs/ # 生成的文档 └── .env # 环境变量API Key、模型名环境准备阶段最值得注意的不是 Python 版本而是 API 地址和模型名的配置方式。不要把 Key 硬编码在脚本里建议用环境变量管理。# .env 示例 LLM_API_URLhttps://your-endpoint.example/v1/chat/completions LLM_API_KEYyour_key_here LLM_MODELyour-model-name如果你用的是 OpenAI 兼容接口可以直接把这个 URL 换成自己的服务地址如果你用本地部署的模型也只需要改 URL 和模型名。整个 Agent 代码里不要写死任何厂商相关的逻辑只认“messages 进、文本出”这个标准行为。这样后面换模型成本几乎为零。运行之前检查一下 git 仓库是否干净避免把生成的文档和手头的修改混在一起cd my-project git status --short如果输出为空再开始跑 Agent。这一步很重要因为后面要做 diff 采集如果工作区本身有未提交修改Agent 生成的“变更分析”就会包含你的临时改动文档内容会被污染。5. 实现一个最小文档 Agent 核心流程Agent 核心流程可以拆成五步采集变更、组装上下文、生成文档、写回文件、验证输出。每一步都单独封装函数方便以后加日志、缓存和失败重试。先写一个简单的 LLM 调用函数。这里用 OpenAI 兼容接口但实际调用地址需要按你自己的服务配置替换。import os import requests API_URL os.environ.get(LLM_API_URL, ).rstrip(/) API_KEY os.environ.get(LLM_API_KEY, ) MODEL os.environ.get(LLM_MODEL, ) def call_llm(messages: list, temperature: float 0.2) - str: if not API_URL or not API_KEY or not MODEL: raise RuntimeError(请先配置 LLM_API_URL / LLM_API_KEY / LLM_MODEL) resp requests.post( f{API_URL}/chat/completions, headers{Authorization: fBearer {API_KEY}}, json{ model: MODEL, messages: messages, temperature: temperature, }, timeout180, ) resp.raise_for_status() return resp.json()[choices][0][message][content]注意我把temperature默认设成了 0.2。文档生成不是创意写作温度越高越容易跑偏低温度更适合保持一致性和严谨性。接下来是采集 git diff。只关心本次变更涉及的文件名以及对应的代码变更内容。这里有一个经验不要一次性把整个仓库源码都塞给模型而是先拿 diff。diff 比源码更聚焦token 成本也更低。import subprocess import json def get_changed_files(base: str HEAD) - list: result subprocess.run( [git, diff, --name-only, base], capture_outputTrue, textTrue, ) files [line.strip() for line in result.stdout.splitlines() if line.strip()] return files def get_file_diff(file_path: str, base: str HEAD) - str: result subprocess.run( [git, diff, base, --, file_path], capture_outputTrue, textTrue, ) return result.stdout再写一个最简的 spec 读取函数。这个函数只需要把规范文件和任务文件读出来拼成 Agent 的 system prompt。from pathlib import Path def load_text(path: str) - str: return Path(path).read_text(encodingutf-8) def build_messages(task: dict) - list: spec load_text(docs/SPEC.md) changed task.get(change_scope, []) context_blocks [] for f in changed: context_blocks.append(f## {f}\n\n{get_file_diff(f)}) context_text \n\n.join(context_blocks) return [ { role: system, content: ( 你是一个代码文档维护助手。请严格遵循文档规范 只处理任务指定的变更范围不要重写无关内容。\n\n f文档规范\n{spec} ), }, { role: user, content: ( f需要更新的文档{task.get(target_doc)}\n\n f本次变更文件\n{changed}\n\n f相关 diff\n{context_text}\n\n 请根据 diff 更新目标文档保持原有风格。 ), }, ]实际生成时你会希望把 target_doc 的旧内容也一起作为上下文发过去。这样 Agent 不是从零写而是基于旧文档做增量修改风格延续性会好很多。上面的示例只是为了展示核心链路真实使用时可以把这个迭代过程改成读旧文档 - 拼 diff - 生成新文档 - 写回。最后写回文件。这里推荐先写到一个临时文件验证通过后再覆盖原文件避免 Agent 输出一半导致文档损坏。def write_doc(path: str, content: str) - None: Path(path).write_text(content, encodingutf-8)如果你希望更稳妥一点可以先把生成内容输出到outputs/目录人在本地 diff 确认之后再用。让生成和合入解耦这是文档 Agent 项目里非常关键的设计。6. 用测试验证文档“真的能跑”文档生成之后最容易被忽略的一步是验证。Agent 写出了 README但里面的命令是编的链接是失效的示例参数和真实函数签名对不上。不验证的话这份文档还不如不生成。这里可以参考当前 Agent 圈子里流行的“test agents”思路让另一个独立流程去验证文档产物而不是在同一个生成 prompt 里让模型自我检查。模型自我检查天然有盲区它容易把“看起来完整的例子”当成“能运行的内容”。最简单的验证项目有三个。第一个是代码块语言标注检查。确保每个 Markdown 代码块都有语言标注防止出现没有语法高亮的裸代码块。import re def check_code_fences(md_text: str): fences re.findall(r(\w*), md_text) return [f for f in fences if f ]这个函数返回所有没有语言标注的代码块位置。如果列表不为空就说明文档不合规。第二个是 bash 命令可执行性检查。从文档里提取 bash 代码块逐个做 dry-run。import re import subprocess def extract_bash_blocks(md_text: str) - list: return re.findall(rbash\n(.*?), md_text, re.S) def dry_run_commands(md_text: str): for block in extract_bash_blocks(md_text): for line in block.splitlines(): line line.strip() if not line or line.startswith(#): continue print(f[dry-run] {line}) # 注意不要直接执行未知命令。这里只做语法检查或白名单检查。这里要特别说明一下安全限制文档里的命令可能包含删除、覆盖、联网下载等操作绝对不能直接用subprocess.run(shellTrue)去执行。更安全的做法是维护一个命令白名单比如pip install、python -m pytest、docker build这些常见命令可以拆成参数结构去检查其他命令一律标记为“需人工确认”。第三个是链接检查。Markdown 里的相对链接和绝对链接都可能是死链。网络请求可能不稳定可以先只检查锚点和本地文件路径。from pathlib import Path import re def check_local_links(md_text: str, base_dir: Path) - list: links re.findall(r\[.*?\]\((.*?)\), md_text) broken [] for link in links: if link.startswith((http://, https://, #)): continue target (base_dir / link).resolve() if not target.exists(): broken.append(link) return broken这一套验证代码是独立于生成逻辑的。你可以把它接在 CI 里也可以作为 Agent 生成后的自动检查环节。我的建议是直接做成一个validator.py每次生成完先跑一遍失败就重新生成最多重试两次。如果两次还失败说明当前上下文信息不足应该停下来让人介入而不是无限循环。7. 接口 API 化与批量任务处理命令行跑通之后下一步就是接口化和批量处理。文档 Agent 做成 HTTP 服务的好处是可以接入 CI/CD、聊天机器人、内部工具平台让团队成员不用本地配环境就能调用。用 FastAPI 做一个轻量包装是非常简单的。这里不要求项目本身必须提供 API而是给你一个通用的封装模板拿到自己的 Agent 逻辑上就能用。from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI() class DocTask(BaseModel): repo_path: str change_scope: list[str] target_doc: str max_retry: int 2 app.post(/generate_doc) def generate_doc(task: DocTask): try: messages build_messages(task.dict()) for attempt in range(task.max_retry): content call_llm(messages) broken_links check_local_links(content, repo_path) if not broken_links: return {status: ok, content: content} return {status: needs_review, content: content, warning: 验证未通过请人工处理} except Exception as exc: raise HTTPException(status_code500, detailstr(exc))这个接口的输入是 repo_path、change_scope、target_doc输出是生成结果和验证状态。注意一点如果服务部署在服务器上repo_path这个参数意味着调用者可以指定任意本地路径风险很大。实际使用时必须做路径白名单校验只允许访问预设的仓库根目录防止路径穿越。批量任务可以按目录或按模块组织。一个简单的批量队列可以做成这样# batch_tasks.json [ { repo_path: ./project-a, change_scope: [src/auth/, docs/auth.md], target_doc: docs/auth.md }, { repo_path: ./project-a, change_scope: [src/payment/, docs/payment.md], target_doc: docs/payment.md } ]批量执行时要重点控制并发。不要一个任务结束立刻上十个任务否则 API 限流会把整个队列打崩。建议用ThreadPoolExecutor(max_workers1)先单线程跑一轮确认稳定后再慢慢加并发。每个任务记录开始时间、结束时间、token 消耗、验证结果写成一个 JSON 日志文件。失败任务单独进入 retry 队列超过重试次数就标记为failed等人处理。8. 资源占用与成本观察做文档 Agent 和做图像、视频生成不同它的主要资源瓶颈不是显存而是 token 消耗。一次文档生成会同时消耗输入 token 和输出 token。输入 token 包括 spec 文件、旧文档、diff、源码片段输出 token 就是新文档本身。如果你用的是按 token 计费的 API很快就能感受到上下文越长成本越高。我在前面反复强调“收集 diff 而不是整个源码”就是为了降低输入成本。一个 diff 通常只有几百到几千字符而整个仓库可能是几十万字符差了一个数量级。如果想进一步控制成本可以做三层优化。第一层是缓存。同一个文件的 diff 如果没变过上一次生成的结果可以直接复用。你可以在本地维护一个cache.jsonkey 是“文件路径 git diff 的 hash”。{ src/auth/login.py:::a1b2c3d4...: { content: 生成的文档内容, timestamp: 2025-01-01T12:00:00 } }第二层是分块。如果变更涉及大量文件不要一次性全丢给 Agent。按模块拆成多个小任务每个任务只处理一个模块。这样每个请求的上下文长度更稳定Agent 跑偏的概率也低。第三层是选择更小的模型。先拿一个小模型做“文档草稿”再用大模型做“审校修正”。很多情况下小模型产出的草稿已经能用大模型只做摘要、补示例、纠正格式。这个分层机制能显著降低总成本而且质量不一定比单次大模型生成差。如果选择本地模型显存占用就看模型大小了。这里我不能替你估算一个固定数字因为不同量化等级、不同上下文长度、不同并发数都会影响显存。更稳妥的判断是先从 API 方式跑通流程等确认这套方案对你有价值再考虑本地模型。本地模型的好处是隐私可控代价是部署和调优成本更高。9. 常见问题与排查方法文档 Agent 在实际运行中会遇到的问题很多和普通 AI 应用是共同的。我整理了一个排查表可以直接按表格定位。问题现象可能原因排查方式解决方案生成的文档与代码完全无关上下文里放错了文件或 diff 采集为空检查 git status 和 diff 输出确保工作区干净确认 change_scope 路径真实存在生成的文档风格和旧文档不一致没有把旧文档内容作为上下文传入检查 messages 里是否包含 target_doc 原文把旧文档内容加入 user prompt代码块没有语言标注规范文件没生效或模型忽略了规范检查 SPEC.md 是否被读取在 system prompt 中增加硬性要求并在验证层拦截文档里的命令无法执行模型生成的命令是虚构的检查 bash 代码块和 dry-run 结果加强验证层命令必须白名单匹配API 请求超时上下文太长或模型推理速度慢查看请求耗时检查上下文 token 数减少 diff 范围分模块处理调大 timeout批量任务跑到一半卡住某个任务上下文异常或 API 限流查看任务日志定位卡住的任务增加每任务超时时间失败自动重试并记录token 消耗突然升高引入无关文件或 Agent 重写了整个文档检查请求日志和 token 用量在 prompt 里明确“只处理 diff 涉及部分”Agent 被中断后状态丢失没有保存中间步骤状态检查是否有任务状态文件每个任务写一个 state.json记录当前进度模型反复推荐删除已废弃接口模型对项目背景不了解检查 prompt 是否包含足够背景在上下文中加入接口的弃用声明或历史原因其中“任务中断后状态丢失”是很容易被忽略的问题。Agent 可能已经生成了三份文档突然遇到超时或网络抖动整个任务从头再来。建议在每一份文档写盘之后立即更新 state.json标记完成状态。这样重启后可以跳过已完成文件只续跑未完成的部分。10. 最佳实践与延伸把文档 Agent 接入团队工作流之后有几条工程建议可以让你少踩坑。第一第一次跑的时候把参数调小。只选一个模块、一次 diff、一个小模型先看流程是否走通不要一上来就整个仓库全量生成。全量生成的失败排查成本会高很多。第二保留一套最小可运行配置。把.env.example、tasks/task.example.json、SPEC.md都放进 git 仓库新机器上克隆下来改一下 API Key 就能跑。这样不管是换电脑还是新手加入团队五分钟就能复现环境。第三目录分层管理。原始文档、Agent 草稿、验证报告、人工确认后的文档分别放在不同目录。不要让 Agent 直接覆盖正式文档至少要先经过 diff 审核。第四批量任务必须加日志和失败重试。日志里至少要包含每个任务的输入文件列表、输出 token、耗时、验证结果。没有日志你无法判断一次批量生成是成功还是“表面成功但内容全错”。第五接口服务要限制访问范围。暴露在网络上的文档生成 API一定要做好鉴权、路径白名单、请求体大小限制。否则别人可以传一个repo_path/etc来探测你的服务器文件。第六涉及内部代码时要注意数据合规。发送给外部大模型 API 的代码片段如果包含未公开的业务逻辑建议先用本地化部署模型或脱敏工具处理。从“让 Agent 按预期生成代码文档”这个 Show HN 思路出发我们能延伸出来的方向其实不少。比如把验证层做深接入 Playwright 之类的自动化测试让文档中的前端示例代码真的跑一遍浏览器测试也可以把“interrupt”机制加入 Agent 执行流程在人工发现生成方向错误时允许随时打断并向 Agent 追加修订意见还可以把多个文档 Agent 合并成一个流水线生成文档、写 Changelog、整理 commit message一套链路全部自动完成。最有价值的第一步是先在你的一个真实仓库里跑通“采集 diff - 生成文档 - 验证链接和命令”这个最小闭环。跑通之后你才会真正理解哪些地方需要 Agent 更聪明哪些地方其实是工程化就能解决的问题。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →