Auto Ontology OpenAPI 契约生成指南:FastAPI 路由如何零手写产出完整 API 文档

原创2026-11-07 23:38:391,900 阅读
文章标签:AI 应用MCP 服务数据治理知识图谱数据目录数据建模后端前端大模型人工智能

Auto Ontology OpenAPI 契约生成指南:FastAPI 路由如何零手写产出完整 API 文档

Auto Ontology 是一套 Python 框架,用于在数据之上构建语义化、基于图的可推理层,支持确定性的查询生成、更精准的上下文检索,以及更可解释的 LLM/Agent 工作流。它有一套巧妙的 OpenAPI 契约生成机制:FastAPI 路由定义完接口后,文档自动产出、永不手写,CI 还会自动检查契约是否"漂移"。本文将拆解这套机制。

为什么要自动生成 OpenAPI 契约?

手写 API 文档的痛苦人人都懂:接口改了、文档忘了改,消费者按旧文档调用就炸。

Auto Ontology 的做法是**"文档即构建产物"**——三份 OpenAPI 规范全部由代码生成并提交进仓库:

文件 描述对象 生成方式
auto-ontology-api.json 对外公开 API(前端消费者用的就是它) 前端执行 pnpm openapi
backend.json 内部 FastAPI 主服务(59 条路径) uv run python -m dev_tools.generate_backend_openapi
ingestion.json 内部 Ingestion Service 同上

这三份文件都是"生成物",仓库中专门的 README明确要求不要手改——CI 会重新生成并 diff,你提交的内容如果过期就会构建失败。

Auto Ontology 系统架构:Agent Harness 经 Frontend 与 Backend 交互,Ingestion Service 负责源数据库摄取

上图展示了 Auto Ontology 的整体架构:Python 服务(Backend、Ingestion Service)部署为 ClusterIP-only,外部流量全部经过 Next.js 前端代理。这个架构特点是理解契约生成的关键。

第一步:FastAPI 路由零成本导出内部 spec

FastAPI 的原生能力是这套机制的地基。开发者只需按惯例写路由——装饰器声明路径、Pydantic 模型声明请求/响应结构:

# auto_ontology/server/terms/router.py
@router.get("/terms", response_model=TermsPageResponse)
def list_terms(query: str | None = Query(...), skip: int = ..., limit: int | None = ...):
    """Return Term nodes. ..."""

response_model 里的 Pydantic 模型、Query 的描述文本、函数 docstring,FastAPI 都会自动翻译成 OpenAPI 结构。

关键设计在 create_app():它把 14 个业务路由(datasources、chat、terms、tags、search 等)挂到同一个 FastAPI 实例上,并且与应用启动完全解耦——

def create_app() -> FastAPI:
    """Build the API app without serving it.

    Kept separate from ``main`` so tooling — the OpenAPI spec generator,
    tests — can construct the app and read ``app.openapi()`` without
    starting uvicorn.
    """

注意 lifespan 里的注释:数据库 schema 校验被刻意放在 lifespan 里而非 create_app 中,因为契约生成器构建应用时根本没有数据库。也就是说,生成一份完整的 API 文档,不需要启动服务器、不需要连数据库——纯内存中 create_app().openapi() 一步到位。

导出脚本 dev_tools/generate_backend_openapi.py 极简:

return {
    "backend.json": create_app().openapi(),
    "ingestion.json": ingestion_app.openapi(),
}

写盘时用了 sort_keys=True,保证每次生成的字节内容稳定——CI 的 drift 检查只对"真实变化"报警,而不是被 JSON 键序抖动误伤。

第二步:前端生成器合并出公开 spec

backend.json 描述的是"内部表面",集群外没人能直接调用它。真正对外的是 Next.js 的 route handler。于是 frontend/scripts/generate-openapi.ts 承担第二棒,它用 ts-morph 做静态 AST 分析,遍历 frontend/app/api/**/route.ts:

  • 路径推导:文件式路由天然确定,app/api/terms/[term_id]/route.ts → /api/terms/{term_id};
  • 权限提取:从每个路由的 withPermission({...}) 包裹器读出所需权限,withPublic 则标记为公开,并据此决定 security 字段和 401/403 响应;
  • 代理识别:调用 proxyToBackend 的 handler 原样转发路径,于是直接按 path + method 从 backend.json 合并 FastAPI 的完整请求/响应 schema。

一个典型的代理路由只有 3 行代码,文档却自动完整:

// frontend/app/api/terms/route.ts
export const GET = withPermission({ catalog: ['read'] })((req) => proxyToBackend(req));

前端独有的路由(Prisma 直接支撑、无 FastAPI 对应)以及自己拼上游请求的 chat 路由,则通过同目录的 openapi.ts 声明模块用 zod 补充形状,契约定义见 frontend/types/openapi.ts:

export const openapi: OpenApiRoute = {
  get: { responses: { 200: { description: '…', schema: z.array(acronym) } } },
};

之所以放在 route.ts 旁边的独立文件,是因为生成器是普通 Node 脚本,无法 import 带 next/headers、getPrisma() 的路由模块,而 openapi.ts 只依赖 zod,可以干净导入。

生成器最终组装出 OpenAPI 3.1 文档,并打上几个自定义扩展字段(详见 docs/openapi/README.md):

  • x-auto-ontology-permissions —— 该操作需要的权限;
  • x-auto-ontology-source —— 提供该操作的 route.ts 路径;
  • x-auto-ontology-proxied / x-auto-ontology-upstream —— 是否代理、代理到哪条后端路径。

生成时还会做一系列硬校验:读不懂 withPermission 参数直接报错、识别不出任何 HTTP 方法导出的路由文件报错、路径参数与后端不一致报错——宁可构建失败,也不发布一份"看起来完整但缺了一角"的契约。

第三步:一条命令 + CI 防漂移

再生成顺序有讲究(公开 spec 会读取已提交的 backend.json):

uv run python -m dev_tools.generate_backend_openapi   # 产出 backend.json / ingestion.json
cd frontend && pnpm openapi                           # 产出 auto-ontology-api.json

生成的公开 spec 会先过 Prettier 统一格式,再同步拷贝一份进 MCP 包(mcp/auto_ontology_mcp/auto-ontology-api.json),因为 MCP 服务从 pip/uvx 直接安装运行时没有 docs/ 目录可读,两份副本 CI 都会 diff。

Auto Ontology 语义图谱:Terms、Column Attribute、SQL Attribute 等语义对象之间的关联网络

上图是契约所描述的对象世界——Terms、表格、列、SQL 属性等语义对象组成的图谱。公开 spec 里 81 条路径、155 个组件 schema,覆盖的正是操作这张图谱的全部能力。

契约的下游消费者:不止给人看

自动生成的契约在这里不只是"漂亮的文档",它是机器的一等公民:

  1. MCP 工具面:mcp/auto_ontology_mcp/tools.py 从公开 spec 中挑出一组 allow-list 操作,转成 Agent 可调用的工具。参数、请求体、字段描述全部直接取自 spec——注释里说得很直白:"参数保持正确,是因为 spec 一旦与路由漂移 CI 就会失败。"
  2. 文档站点:公开 spec 已部署到静态页面供查阅,也可以粘贴到 Swagger 编辑器里浏览。
  3. CI 门禁:两份生成物都提交进仓库,PR diff 即评审界面;过期契约直接让构建变红。

这套机制值得借鉴的 5 个设计点

  • 生成器零运行时依赖:create_app() 与 main() 分离,文档生成不碰数据库、不起服务器;
  • 静态分析 + 合并双通道:FastAPI 路由的 schema 自动合并,前端独有逻辑用 zod 旁路声明,各取所长;
  • 排序写入保证 diff 稳定:sort_keys=True 让 drift 检查只报真变化;
  • 失败优于沉默:解析不了权限、匹配不上路径、参数错位,全部抛错终止构建;
  • 命名规范写进约定:API 全线 snake_case,动态路由目录 [term_id] 与后端参数名一一对应,使跨语言合并(Python ↔ TypeScript)可以逐字匹配。

相关文件索引

💡 想动手试试?克隆仓库后按上文两条命令顺序执行,几秒内即可得到全新的三份 JSON 契约——这正是"文档零手写"的含义。

登录后查看全文
auto-ontology