结构化比较 OpenAPI,生成确定性变更日志
作者通过规范化解析、结构比较和引用处理,避免把 OpenAPI 文件的键顺序变化误报为接口变更。针对递归引用,使用已访问的引用与路径组合终止循环,并输出 JSON 和 Markdown 供自动化流程使用。
AI 深度解读
让 LLM 写 API 更新日志,最麻烦的不是措辞不够漂亮,而是它可能编出一次根本不存在的破坏性变更。作者因此做了一个 OpenAPI 变更生成器:输入新旧两份规范,用确定性比较输出差异,同样的输入始终得到同样的结果。
这篇短文的价值,在于讲清了两个容易踩的坑:字段排序会制造噪声,递归引用会让解析器失控。对于准备把 API 检查接入自动化流程的程序员,这比「让模型读完再总结」更有参考意义。
为什么比较规范不能直接用文本 diff
工具接收两份 OpenAPI 规范,分别作为 base 和 new,比较接口操作、schema、参数以及响应码,最后返回结构化差异。
困难从文件顺序开始。YAML/JSON 对象的键顺序不代表接口语义发生变化,但普通文本 diff 会把位置变化也算进去。作者遇到过两份语义完全相同的规范,却生成了上千行「更新日志」。
文件变了,不等于接口变了。
作者的处理是先解析规范,再转成统一形式:对键排序,把 $ref 指针解析到目标路径,并按 schema 的结构比较,而不是逐行比较文本。可以把它理解为:先认清每个字段指向什么、结构是什么,再判断两份规范有什么不同。
作者称,经过这些处理,在包含 200 对规范的测试集中,误报降到了零。这是文章最具体的实测结果,但原文没有交代测试集构成,也没有报告漏报情况。
为什么处理 $ref 还得防止无限递归
统一格式还不够,引用解析本身也有坑。
例如,树节点包含 children,而 children 中的元素又是同一种树节点。这种递归模型很正常,但朴素的 $ref 解析器可能不断展开同一个结构,最终导致栈溢出。
作者采用的办法,是在遍历时记录已经访问过的 (ref, path) 对,让循环引用能够终止,而不是一直递归下去。
这里的重点不只是「加一个去重集合」,而是访问记录同时包含引用和路径。不过,原文没有给出实现代码,也没有解释 path 如何定义、何时判定重复,因此还不足以直接复刻这套循环检测逻辑。
「不使用 LLM」为什么能成为优点
作者希望输出能直接进入后续流程,因此提供了两种格式:机器可读的 JSON 供自动化和 Agent 消费,人类可读的 Markdown 用于发布说明。
这套设计把更新日志的事实来源收紧到了规范差异:有变化才输出,没有变化就不提。同样的输入,每次产生同样的结果,也更方便复查问题。
确定性比较适合回答「改了什么」,但不能自动证明「会不会破坏兼容」。
作者反感 LLM 编造破坏性变更,这个出发点成立。不过,原文只明确描述了差异检测,没有展示完整的兼容性判定规则。检测到参数或响应码变化,与确认调用方一定受影响,仍然是两件事。
我的判断
我认同把事实提取交给确定性程序。OpenAPI 已经是结构化输入,键、引用和 schema 都有明确的比较对象。让 LLM 直接承担这一步,会让结果的复现和核查变得更困难。
但「200 对规范零误报」只能支持作者在这批样本上的结果,不能推出工具对所有规范都可靠。文章没有给出与其他 OpenAPI diff 工具的对照,也没有展示复杂组合 schema、不同规范版本等情况的覆盖范围。工具整体效果仍需更多证据。
和普通文本 diff 相比,结构化比较解决的是语义噪声;和 LLM 总结相比,它提供的是稳定的事实清单。如果后续需要更易读的说明,可以再让模型根据清单组织语言,但这属于可选的组合方式,作者没有提供相关对照实验。
能拿来做什么
- 在 Node 项目的 API 发布流程中,可以先保存 base 和 new 两份 OpenAPI 规范,再生成结构化差异,让发布说明有可核查的来源。
- 如果正在开发规范比较工具,可以先加入「只调整键顺序」和「包含递归
$ref」的样本,分别检查误报与遍历终止问题。 - 给 AI 编程 Agent 接入 API 变更信息时,可以优先提供 JSON 差异;如果还要判定破坏性变更,应单独明确兼容性规则,不能把所有变化都算作破坏。