☰
AI辅助生成Markdown API文档:基于Python AST和大模型的开源工具实践
2026/10/14 4:04:12 网站建设 项目流程

写在最前面:这次开源的是一套用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仓库已经是一个可以继续折腾的起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询