写在最前面:这次开源的是一套用AI辅助开发方式做出来的文档工具示例代码,仓库已经放到Gitee上,整个项目从构思到跑通大概花了一周。先说清楚这套东西是干什么的——它能读取你项目里的Python源码结构,配合注释和一份目录模板,调用大模型接口自动生成结构化的Markdown文档,涵盖接口说明、参数表、返回值、使用示例这些常规文档必备的内容。适合谁用?自己维护开源项目但懒得写README的人、小团队里被文档任务追着跑的人,以及想看看AI辅助开发到底怎么落地的学习者。这篇文章我不打算只贴仓库链接,我会把设计思路、核心实现、踩坑过程全部摊开,内容会有点长,但保证都是实际动手得来的经验。
1. 为什么专门做个工具来"治"文档拖延
1.1 写文档这件事,比写代码更容易让人想逃
代码写完了,功能跑通了,测试也绿了,然后呢?打开一个空白的Markdown文件,开始写接口文档。那一刻我通常会陷入一种奇怪的抗拒状态——脑子里明明清楚每个函数在做什么,但要把这些逻辑组织成一段别人能看懂的描述,就是迟迟下不了笔。更别提字段变更之后,文档里的表格忘了同步更新,你甚至能在自己的项目里看到注释和实际代码互相矛盾的场景。
这个问题在我维护自己的模拟项目X时被放到了最大。项目不大,但类和方法不少,一个新加入的成员光是理解模块边界和调用关系就花了两三天。我算了一笔账:代码本身大概写了八十个小时,文档陆陆续续补了两个星期,而且补出来的东西还不一定准确。正是在这种情况下,我开始认真考虑用AI辅助开发的方式造一个文档工具,让AI先把初稿吐出来,我来负责审校和修正。
1.2 从"一句话生成全部文档"到"只做结构化整理"
一开始我的想法很简单粗暴:把整个项目的源码丢给大模型,让它写全量文档。实测下来这个思路基本走不通。项目一大,上下文窗口根本塞不下;强行塞进去,模型会把前面的内容忘掉,生成出来的文档结构混乱,甚至出现连项目里都不存在的函数名。
经过几轮调整,我把工具的定位改成了"AI辅助的结构化文档生成器"——核心思路不是让AI自由发挥写一部小说,而是让AI充当一个非常熟练的文档整理员:
- 解析器先读取代码的AST结构,把类、方法、参数、返回值、装饰器全部提取出来;
- 语义层再把源码中的注释和AST元信息合并成结构化的"素材卡";
- 生成层拿着素材卡和目录模板,要求大模型按照指定章节输出;
- 输出层做Markdown渲染和结构校验,缺了必填小节就自动触发补写。
这样设计的好处是,AI不需要真的"理解"整个项目,它只需要理解单个类、单个方法对应的代码块,就能在上下文中生成高质量的描述。人的工作量从"写全部"降级为"审校初稿",速度完全不是一个量级。
2. 整体架构:文档生成流水线的四个环节
2.1 解析、语义、生成、输出,各司其职
工具整体的运行链路是严格的流水线结构,上游的输出直接作为下游的输入。我一开始也想过做成一个"黑盒"——源码进去,文档出来,但是遇到问题根本没法排查。后来改成显式分层的设计,每个中间结果都可以单独查看,调试成本一下子降了下来。
分层之后的核心优势是每一个环节都可以单独替换。比如今天用的是通用大模型接口,明天想换成部署在本地的开源模型,只需要改生成层;想从只支持Python扩展到支持JavaScript,只需要在解析层加一个tree-sitter的解析器。这种插件化的思路,我认为比在单个文件里堆逻辑要健康得多。
2.2 输入约定:注释和目录模板是质量的上限
让AI生成文档有一个很反直觉的事实:给的信息越多,生成结果越可靠,而最容易被忽略的信息恰恰是"你想要什么样的文档结构"。如果只丢给AI一段代码,它当然能写,但写出来的东西大概率是"这段代码定义了一个名为X的类,其中包含方法Y……"这样的流水账。
所以我引入了两个输入约定:
- 每个类、函数上方建议写块注释,描述职责和关键逻辑;参数和返回值的说明写进docstring。
- 提供一个目录模板,告诉工具最终文档应该包含哪些章节,比如"概述、快速开始、API参考、常见问题"。
这两个约定不是硬性约束,而是通过提示词转达给大模型的。模板控制了文档的骨架,注释控制了每个API的语义核心。代码里的注释写得越好,最终文档质量越高。换句话说,这个工具不会帮你把烂注释变成好文档,但它能把你脑中的结构信息高效地转化为排版规范的文档初稿。
2.3 技术选型:为什么是Python+AST+通用大模型接口
选型这件事我在动手前就想得很清楚,最终确定的技术栈如下:
| 组件 | 选型 | 理由 |
|---|---|---|
| 解析层 | Python标准库ast模块 | 零依赖,能稳定提取类、函数、参数、装饰器、行号 |
| 注释处理 | docstring解析 + 正则提取行尾注释 | 兼容项目里的历史代码,不强制全部使用规范docstring |
| 生成层 | HTTP方式调用通用大模型接口 | 接口标准统一,可以灵活切换服务商或本地部署 |
| 输出层 | Markdown渲染 + 字符串校验 | 文档本来就是Markdown,不需要额外渲染框架 |
使用Python标准库的ast模块是最没有悬念的选择。它对Python源码的解析非常可靠,能够拿到精确到行号的语法节点,甚至能区分位置参数、关键字参数、可变参数。相比之下,正则表达式看似简单,但遇到嵌套括号、多行签名就会出现各种边界问题。
生成层用HTTP调用而非依赖特定SDK,理由也很简单——不需要绑定任何厂商,配置文件里换一个endpoint就能切换后端模型。我在代码里给了一个通用的接口封装,结构类似于市面上大多数兼容接口的格式,你只需要把api_base换成自己的服务地址就能跑通。
3. 核心实现:怎么让AI产出能用的文档
3.1 AST提取与注释关联:让代码结构变成结构化数据
整个工具的第一个关键环节是把源码转成结构化数据。AST的遍历逻辑非常直观,核心代码如下:
import ast from dataclasses import dataclass, field @dataclass class FunctionInfo: name: str lineno: int args: list returns: str docstring: str body_preview: str @dataclass class ClassInfo: name: str lineno: int docstring: str methods: list = field(default_factory=list) def extract_functions(tree: ast.AST): results = [] for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): args = [a.arg for a in node.args.args] returns = ast.unparse(node.returns) if node.returns else "None" docstring = ast.get_docstring(node) or "" # 截取函数体前若干行作为预览,用于给模型补充上下文 body_preview = "\n".join( ast.unparse(child) for child in node.body[:3] ) if node.body else "" results.append(FunctionInfo( name=node.name, lineno=node.lineno, args=args, returns=returns, docstring=docstring, body_preview=body_preview )) return results这里有个容易被忽略的小设计:body_preview。只给函数签名和docstring,模型往往不理解这个方法的实际行为,生成的示例代码会想当然。截取函数体前三行,让模型看一眼真实的赋值、调用、返回逻辑,它能写出更贴近实际用法的示例。这是我在多轮实测后加上的字段,效果提升很明显。
注释关联的逻辑也不复杂。通常情况下,一个函数上方的注释就是对这个函数的说明。我按AST节点的lineno对源码行做索引,取函数定义前紧邻的连续注释块合并进docstring的上下文。如果项目里既有docstring又有行注释,两者会被合并之后一起发给模型。
3.2 提示词模板:RIO结构的工程化实践
AI生成文档的质量,很大程度上取决于提示词的结构化程度。我在工具里使用了一个简化的RIO模板,也就是角色、输入、输出三要素。完整模板大致长这样:
SYSTEM_PROMPT = """你是一名资深技术文档工程师。你的任务是基于给定的代码上下文编写技术文档章节。 要求: 1. 内容直接、准确,禁止空话套话。 2. 每个API必须包含:功能简述、参数表(名称/类型/说明)、返回值、典型用法示例。 3. 参数表中的内容必须来自给定的代码上下文,不得编造不存在的参数。 4. 示例代码必须是可运行的,变量名与函数签名保持一致。 5. 只输出规范的Markdown内容,不要输出任何解释性前缀或后缀。""" USER_PROMPT_TEMPLATE = """请为以下代码元素编写文档章节。 【代码元素类型】{element_type} 【名称】{name} 【源码位置】第 {lineno} 行 【函数签名】{signature} 【docstring】{docstring} 【函数体预览】 {body_preview} 【文件路径】{file_path} 请输出包含 {required_sections} 的文档章节。"""写这个模板的时候我踩过一个认知误区:一开始我把所有约束都堆在用户提示词里,系统提示词只有一句话。后来发现,风格类的约束放在系统提示词里最稳定,而针对单个代码元素的约束放在用户模板里更精确,两者职责一拆,生成结果质量明显提升。
另外,模板最后一句话"只输出规范的Markdown内容,不要输出解释性前缀"也不是白写的。大模型在没有明确约束时经常输出"好的,以下是为您生成的文档"这类废话,加了这句之后,解析环节能省很多事。
3.3 上下文窗口管理:大文件拆成"可消化"的片段
大模型API对上下文窗口有明确限制,而项目里的大文件往往远超这个限制。我的处理策略是:以"类"为颗粒度,一个类生成一节,一个独立的顶层函数单独生成一小节。
这样拆分之后,每个生成任务需要传入的代码上下文被控制在一个很小的范围内。工具内部维护一个"待生成队列",逐个元素调用生成层,而不是把整个文件一次吞进去。实测下来,一个两百行的核心模块,拆成6到8次调用的耗时大概在1到2分钟,完全在可接受范围内。
这个过程中我加了一个缓存机制:每个已处理的文件会生成一个哈希值,如果文件内容没有变化,第二次运行直接读取上次的文档片段,不会重复调用模型。否则改一个注释就要全量重新生成一遍,token消耗会非常快。
3.4 输出校验:缺项自动补写与格式兜底
模型生成结果不可能每次都完美,所以我在输出层加了校验逻辑。工具会检查生成结果中是否包含必需的Markdown小节,比如接口文档是否包含"参数"和"示例"两个子标题。如果缺失,工具会带着缺失项清单发起一次补写请求。
补写请求的提示词大概是这样:你之前的回答缺少"参数"小节,请只补充输出该小节,不要重复输出其他内容。这个针对缺失项的小步补全,比要求模型"重新生成完整内容"成功率高出很多,因为它的输出目标更明确,重新生成反而容易引入新的遗漏。
除了校验小节,还有一层兜底:如果连续两次补写仍然缺失必填项,工具会把这条记录写入"待人工处理日志",而不是产出残缺文档。宁可少一段,也不能让一条残缺文档混进正式发布内容里。这是我在几次被不完整文档坑过之后才加上的底线规则。
4. 跑通一次完整生成:从一个示例项目到成品文档
4.1 准备示例项目:刻意设计了三种注释风格
为了验证工具的适应性,我造了一个模拟项目X,里面包含三个模块,每个模块的注释风格刻意保持不同:一个类使用完整docstring,一个类只有零散的行注释,还有一个类甚至存在部分过时注释。这样做是为了看工具在"不理想输入"下能到什么水平。
运行前需要先编辑配置文件config.yaml:
llm: api_base: "https://your-llm-endpoint.example/v1/chat/completions" api_key_env: "LLM_API_KEY" model: "demo-model" temperature: 0.2 max_tokens: 2000 source: root_dir: "./demo_project" include_patterns: - "**/*.py" output: doc_path: "./docs/api-reference.md" template_path: "./docs-template.md"这里特别说明一下temperature: 0.2。文档生成任务不需要创造性,温度越低输出越稳定。我测试过0.7和0.2两组参数,0.7生成的示例代码偶尔会用不同的风格写同一功能,0.2则稳定得多。api_key_env使用环境变量读取密钥,避免把密钥写进配置文件后不小心提交到仓库里。
4.2 执行生成与中间产物观察
配置完成后运行命令:
pip install -r requirements.txt export LLM_API_KEY="你的密钥" python -m docgen generate --config config.yaml工具会逐条打印处理日志,当时记录的关键日志行大致如下:
[解析] 模块 demo_project/user.py,发现 1 个类、5 个方法 [生成] 处理 UserManager.generate_token() 完成,耗时 8.3s [校验] generate_token() 缺少"参数"小节,发起补写 [补写] generate_token() 补写成功,耗时 5.1s [输出] 已写入 docs/api-reference.md,共 3 个章节、42 个段落从日志里能看到整个流程是有反馈循环的:解析器报告结构信息,生成器逐个处理,校验器发现缺项后自动补写。这也体现出分层设计的一个价值——不需要黑盒猜测是哪一步出了问题,日志会明确告诉你卡在哪个环节。
4.3 生成效果与人工修订的时间账
这是生成文档中一个方法片段的实际效果,经过轻度人工修订后的版本:
### generate_token(user_id, expire_hours=24) -> str 为指定用户生成一个新的访问令牌。 参数: | 参数 | 类型 | 说明 | | --- | --- | --- | | user_id | int | 用户唯一标识 | | expire_hours | int | 令牌有效时长,默认24小时 | 返回: JWT格式的字符串令牌。 使用示例: from demo_project.user import UserManager manager = UserManager() token = manager.generate_token(user_id=1001, expire_hours=12)这里我想说一个真实体会:人工修订不是逐字审阅,而是重点检查三个点。一是示例代码是否真的能跑通,因为模型可能写出基于假想的调用方式;二是参数表是否跟代码签名完全一致,漏参数是常见问题;三是术语是否和项目里一贯的说法一致。这三个点过一遍,一篇初稿变成可发布文档,快的话二十分钟就够了。比起从零写两三个小时,效率提升还是相当可观的。
5. 实测中的三个大坑:从"AI味很重"到"可直接发布"
5.1 坑一:AI科普腔泛滥,文档看起来像营销软文
用第一版提示词生成出来的文档,里面充斥着"此功能具有重要的实际意义""该接口能够有效地提升系统性能"这类废话。你说它错也不算错,但技术文档里全是这种话,读者根本找不到关键信息,恨不得每个API前面都得跳过两行才知道参数是什么。
排查之后认定是提示词缺少文风约束,修复方法是在系统提示词里加了两条硬性规定:禁止使用任何价值判断式表述;每个小节必须以参数、返回值或示例直接开头。类似"该功能支持用户登录"这种句式可以,但"该功能具有重大意义"不可以。加了约束之后,生成的文本风格明显收紧,和项目原有的技术文档风格基本一致了。
5.2 坑二:类有12个方法,文档只生成了8个,而且是"薛定谔式"复现
这是最让我头疼的一个问题。有个业务类的文档有时完整有时候缺,缺的不是同一批方法。我复现了整整三次,发现只要这个类生成总耗时超过某条线,后续方法就会被静默跳过。最后定位到根因是:因为生成单个方法消耗的时间累计过长,触发了外部队列的超时丢弃逻辑。文档引擎本身没有报错,但调用方已经丢弃了这部分请求。
完整的排查链路梳理一遍:
- 现象:同一个类生成结果不稳定,某些方法偶发缺失。
- 初步怀疑:是不是提示词长度限制导致信息被截断?检查后发现每个方法的上下文都远低于限制,排除。
- 进一步怀疑:是不是AST遍历漏了方法?单独写了一个测试脚本遍历该类,结果是12个方法全部被提取,排除。
- 终于定位:翻阅运行日志,发现缺方法的那次运行,总体耗时比完整运行多了大约40秒,超时丢弃正好发生在这个区间。
- 修复:调整了两个参数——单次请求超时时间放宽,同时把单方法生成并发数降低到1,改为串行处理。另外在调和层增加了结果数量校验:当发现已生成方法数少于AST提取数时,自动触发补齐任务。
修复之后,连续跑了五次完整生成测试,方法缺失问题没有再出现。这个坑让我重新认识了一件事:AI工具的问题不一定是AI本身的问题,周边基础设施的并发、超时、队列策略同样会造成看起来很玄学的故障。
5.3 坑三:模型偶尔返回Markdown之外的内容
大模型不是严格的状态机,尽管提示词要求"只输出Markdown",偶尔还是会返回夹杂解释性文字的响应,甚至在Markdown代码块里藏着额外的说明。为此输出层加了解析兜底逻辑。
最有效的方案是让输出结构"契约化":在提示词中要求模型用特定标记包裹文档本体,解析时优先提取标记内的内容;提取失败时再用正则剔除常见的干扰前缀,比如"好的""以下是""没问题"这类词。判断规则很简单:找到第一个以#开头的行为文档起点,最后一个代码块结束标记为终点,中间全部保留。这套兜底在实际测试中救回了不少看似"坏掉"的响应。
5.4 给AI工具加一道安全网:失败率监控、抽检与文档版本化
这几个坑踩完之后,我专门给工具加了几道安全措施,分享出来供大家参考。
- 失败率监控:工具统计每轮生成的失败次数,连续失败超过3次会中止流程并输出错误摘要,在长任务批量执行时非常有用。
- 结果抽检:设置一个"抽检比例"参数,随机抽取生成结果的10%打印到日志里,方便快速浏览是否有明显的胡言乱语。
- 全量版本化:所有生成的Markdown最终通过git提交,AI生成初稿后人工修订再提交一次。反复修改也不会丢失中间状态,回溯起来非常方便。
这类措施的底层层逻辑是用工程手段弥补概率模型的不确定性。AI能力再强,它的输出也需要经过和人类协作者一样的质量门槛。
6. 开源仓库内容和使用指引
6.1 仓库目录结构
仓库结构尽量保持简单,方便直接fork后二次开发。目录大致如下:
ai-docgen-demo/ ├── docgen/ │ ├── __init__.py │ ├── cli.py # 命令行入口 │ ├── parser.py # AST解析与注释提取 │ ├── generator.py # LLM调用与补写逻辑 │ ├── validator.py # 输出结构校验 │ └── config.py # 配置加载与校验 ├── config.yaml.example # 配置示例 ├── requirements.txt ├── docs-template.md # 文档目录模板 └── README.md # 项目说明核心文件就是这几个,我刻意没有做特别复杂的模块隔离,因为作为示例代码,太重的工程抽象反而会影响阅读。每个文件的行数控制在两三百行以内,打开就能看懂主流程。
6.2 五分钟本地跑起来
如果你现在就想试,照着下面几步操作即可。整个流程不涉及复杂的构建步骤,只要机器上有Python 3.10以上版本。
git clone https://gitee.com/yourname/ai-docgen-demo.git cd ai-docgen-demo pip install -r requirements.txt cp config.yaml.example config.yaml # 编辑config.yaml,填入你的模型接口地址 export LLM_API_KEY="你的密钥" python -m docgen generate --config config.yaml第一次建议用仓库里的demo_project试跑,这个示例项目体积小、接口边界清晰,能快速看到完整流程。确认没问题之后再替换成你自己的项目。另外我建议第一次运行的时候打开生成的日志,观察解析、生成、校验三个环节分别打印了什么,好对这个工具的内部流程产生直观感受。
6.3 配置参数速查
| 参数 | 作用 | 建议值 |
|---|---|---|
| llm.temperature | 生成随机性 | 0.2 |
| llm.max_tokens | 单次输出的最大长度 | 2000 |
| source.include_patterns | 源码文件匹配规则 | ["**/*.py"] |
| output.template_path | 文档结构模板 | ./docs-template.md |
| validator.required_sections | 每个API必须包含的小节 | ["参数", "返回", "使用示例"] |
| pipeline.failure_threshold | 连续失败中止阈值 | 3 |
除了表格里的参数,还有一个需要在环境变量里设置的LLM_API_KEY。不用配置文件管理密钥,是为了降低密钥泄露风险。如果你的模型接口不需要密钥,环境变量不设置也没关系。
6.4 后续可以扩展的三个方向
就我个人使用下来的感受,这个骨架可以很容易向三个方向继续演进。
第一个是支持更多语言。Python用ast,其他语言可以考虑接入tree-sitter,解析层的输出格式保持统一的话,生成层完全不用动。
第二个是增量更新。现在工具是全量生成,对于大型项目来说,每次改动都全量生成有点浪费。可以考虑基于git diff识别变更文件,只重新生成受影响的文档章节。
第三个是接入RAG,把项目的历史文档和issue讨论纳入检索范围,让模型在生成时能引用过去的决策记录。这个扩展会让文档内容的历史一致性变得更好,不再只是单次代码快照的描述。
从长远看,文档生成只是一步,更完整的目标是在一个项目里形成"代码、注释、文档、测试"四者的联动维护闭环。当代码发生变化时,相关文档自动进入更新候选列表,由AI生成初稿,由人决定是否合入。那才是AI辅助开发真正发挥长期价值的状态。不过这个目标不是一次开源就能完全到达的,至少目前这个Gitee仓库已经是一个可以继续折腾的起点。