用可执行检查划定 AI 文档生成边界
文章以脚本和分节责任台账管理模型撰写文档:可生成内容必须来自程序提取的事实,文中数字须有对应来源,且不能夹带未解决的判断。迁移建议、安全保证和性能预期等内容交由具名作者负责,并将检查接入 CI。
AI 深度解读
AI 写文档最麻烦的错误,未必是凭空编造,而是接口已经改了,说明还停在上一版,却没人负责更新。作者给出的办法是:按「能否被命令验证」划分章节,机器事实交给模型起草,迁移建议、安全承诺等判断交给具名的人执笔。
这套方案用一份章节台账和两个 Python 脚本,把分工接进 CI。它值得借鉴的地方,是把文档责任变成了可检查的规则;但检查通过,离内容正确还有距离。
哪些文档可以交给模型写
作者给模型起草设置了三个同时成立的条件:
- 事实能从程序提取,例如 CLI 帮助、配置 schema、HTTP 响应头和退出码。
- 正文出现的每个数字,都能在提取出的事实包中找到。
- 没有尚待人判断的主张,例如「应该优先选择 X,因为 Y」。
这条分界线关心的是一句话如何得到证据。程序能告诉你某个参数的默认值,却不能单凭这个值证明它适合生产环境。
因此,迁移建议、安全保证、废弃原因、性能预期,以及涉及设计意图的表述,都被划入人工章节。作者要求人从头执笔,其他人再校对,而不是先让模型写出一段像样的解释,等审核者签字。
能够核对字段,不等于能够替用户做决策。
作者认为,过期的参数表可能让读者浪费一个下午,错误的迁移建议可能耽误一周。这是风险轻重的经验表达,原文没有提供事故统计或对照实验。
先登记责任,再开始生成
作者建议在 docs 目录旁创建 docplan.tsv,每个章节占一行。原文称它有五列,但表头实际有六列:
section facts_class probe owner draft handwritten_claims
cli-flags machine tests/probe_cli.py::test_help_text @docs-cli model 0
config-keys machine tests/probe_config.py::test_schema_dump @docs-cli model 0
exit-codes machine tests/probe_exit.py::test_exit_matrix @docs-cli model 0
migration-guide judgment - @avery-lin human 4
security-model judgment - @avery-lin human 6
要重点看 handwritten_claims:它记录已知需要人工判断的主张数。迁移章节登记了 4 条,安全章节登记了 6 条,因而由人执笔。实际文件必须使用制表符分隔,不能照着展示效果用空格对齐。
ownership_gate.py 检查台账是否自洽:章节名不能重复,facts_class 只能是 machine 或 judgment,draft 只能是 model 或 human,负责人必须以 @ 开头。
模型章节还必须满足 facts_class=machine、handwritten_claims=0,并指向一个存在的探针文件。执行 python3 ownership_gate.py docplan.tsv,示例输出为 sections=5 model=3 human=2,退出码是 0;探针改名后,检查返回 1,并指出行号和缺失路径。
这里有个边界:脚本只取 probe.split("::")[0] 检查文件存在,因此不能证明 test_help_text 等测试函数存在,更没有运行它们。它也不会阅读正文、自动发现新增的判断句;必须有人更新计数,门禁才会拒绝不合规的模型章节。
数字有出处,只是第一层检查
起草前,探针先生成 JSON 事实包。原文通过 mkdir -p .docfacts 创建目录,再依次执行 tests/probe_cli.py、tests/probe_config.py、tests/probe_exit.py,把输出写入 .docfacts/${name}.json,最后用 ls -1 .docfacts 查看结果。
事实包采用纯文本,方便审查差异。例如参数列表少了两个条目,审核者可以直接看到输入发生了什么变化。模型起草时,数字只能来自对应章节的事实包。
下面这段原始脚本负责在起草后检查:正文是否出现事实包没有提供的数字。
#!/usr/bin/env python3
"""fact_audit.py - every numeral in a drafted section must exist in its fact bundle."""
import re, sys
from pathlib import Path
NUM = re.compile(r"(?<![\w.])-?\d+(?:\.\d+)?(?![\w.])")
def numbers(text):
return {m.group(0) for m in NUM.finditer(text)}
def main(facts_path, draft_path):
facts = numbers(Path(facts_path).read_text(encoding="utf-8"))
draft = numbers(Path(draft_path).read_text(encoding="utf-8"))
unbacked = sorted(draft - facts)
for n in unbacked:
print(f"UNBACKED {n} in {draft_path}", file=sys.stderr)
print(f"draft_numbers={len(draft)} backed={len(draft & facts)} unbacked={len(unbacked)}")
return 1 if unbacked else 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1], sys.argv[2]))
最需要看的是 unbacked = sorted(draft - facts):它比较数字集合,不检查数字与字段的对应关系,也不检查单位或上下文。
按章节运行 python3 fact_audit.py .docfacts/probe_cli.json docs/cli-flags.md,发现无依据数字就返回 1。即使输出 unbacked=0,模型仍可能把一个接口的超时值写到另一个接口上,或者把默认值解释成最大值。
数字在事实包里出现过,只能证明有来源,不能证明用对了。
人工章节则应该暂时留空。作者设想生成 stubs/migration-guide.md,把登记的四条判断写成待回答问题,并附上负责人;文件缺失时让 CI 失败。不过,文中两个脚本既没有实现占位文件生成,也没有检查它是否存在,台账还只记录数量,没有记录问题内容。这部分仍需团队自行补齐。
我的判断
我赞同先划清责任,再决定如何生成。对于参数、配置和退出码文档,模型适合把结构化事实整理成易读说明;迁移决策则需要有人解释适用条件、取舍和后果,挂一个审核名字远远不够。
但原文交付的是一个轻量骨架。探针文件存在,不代表探针有效;空事实包也可能通过现有检查。负责人以 @ 开头,只能证明格式符合要求,不能证明账号有效或本人认可。人工章节还被强制要求至少声明一条判断,这会排除「内容全是事实,但团队选择手写」的合理情况。
对于稳定的参数表,直接从 schema 或 CLI 输出生成模板,通常更容易保证字段对应关系。模型的价值更适合放在措辞整理上。原文没有比较两种方案的维护成本,也没有给出错误率变化,因此这仍是一套经验方法。
另外,文章披露它属于 MonkeyCode 产品推广。免费模型访问和免费服务器是运营方提供的可用性说法,不是本文验证过的能力。作者也提醒,免费额度不宜成为发布链路的长期前提,应保留本地模板回退。
如果仓库以教程、产品叙事为主,或者只有一两个人维护,台账可能比问题本身还重。此时 README 中一段 OWNERS 说明,可能已经够用。
能拿来做什么
- 给 Node CLI 或配置文档挑五个现有章节试分类,先判断哪些事实确实能从
--help、schema 和退出码提取,再决定模型参与范围。 - 将事实包作为可审查的生成输入保留。参数或配置发生变化时,先看事实差异,再看模型是否准确表达了变化。
- 对迁移指南和安全说明指定执笔人,列出必须回答的问题;同时补上占位文件检查,避免台账登记完成、正文仍无人处理。
- 为参数表保留本地模板生成路径,让模型不可用时仍能输出基本文档;只有解释性文字需要润色时,再调用模型。