从 OpenAPI 到 AI Agent 工具:免去手写封装的烦恼

别再手写 AI Agent 的工具定义了!本文详解如何将 OpenAPI 规范自动生成 Agent 工具,实现数据模型同步,彻底解决接口漂移和调用错误,大幅提升智能体开发效率。

用 Apifox,节省研发团队的每一分钟

从 OpenAPI 到 AI Agent 工具:免去手写封装的烦恼

免费使用 Apifox

相关推荐

最新文章

API

一体化协作平台

API 设计

API 文档

API 调试

自动化测试

API Mock

API Hub

立即体验 Apifox
目录

大多数 Agent 代码库中都包含一个没人愿意维护的文件。它保存着四十个工具定义,每一个都是手写的 JSON Schema,用于描述一个在别处早已定义好数据模型的接口。一旦 API 团队发布了一个新的必填字段,规范更新了,文档更新了,但 Agent 依然在发送旧的数据 payload,直到有人注意到 400 错误。

实际上,你已经拥有了每个接口的机器可读描述,那就是 OpenAPI 文档。现在的任务是将这些文档转换为模型可以调用的工具定义,并让两者保持自动同步,而不是依靠人工记忆。

本指南将介绍 OpenAPI 操作如何映射到工具数据模型、生成器在此过程中需要修复哪些问题、如何将包含 200 个接口的规范精简到模型可以推理的合理范围,以及如何测试生成的工具是否能正常工作。如果你处于技术栈的更上游,我们关于“当 Agent 编写代码时是否仍需要 API 工具”的文章提供了更广泛的背景信息。

Apifox 在这里至关重要,因为只有规范本身正确,基于它生成的内容才可能正确。工具定义会继承源文档中的每一个缺陷。

AI Coding 交流群

如果你也在用 AI 写代码,或者正在研究 Cursor、Claude Code 这些工具,欢迎加入以下交流群。群里平时会聊一些 AI 编程的实际用法、开发工作流,还有各种新工具和新玩法。

手写工具定义的代价

当只有五个接口时,手写工具定义感觉还行。但当数量达到二十个左右时,由于以下三个原因,这种方式就难以为继了。

定义漂移。 规范是由代码生成的,或者由 API 团队维护。而工具文件则由构建 Agent 的人维护。由于两者之间没有任何关联,它们会悄无声息地产生偏差,而最先出现的症状就是 Agent “突然”停止工作。

描述变得简陋。 当一个人手动编写四十个数据模型时,最后二十个往往只配有一行简短的描述。模型是通过阅读这些描述来选择工具的,因此简陋的文本会直接降低工具选择的准确性。我们关于 Agent 工具数据模型设计的文章深入探讨了为什么措辞如此重要。

错误在运行时才会暴露。 如果手写的数据模型将某个字段声明为字符串,而 API 实际上需要一个整数,那么在生产环境中,当 Agent 第一次尝试执行真实任务时,就会产生 422 错误。

从规范中自动生成可以一次性解决这三个问题。这样就有了单一的事实来源,描述使用的是与文档相同的文本,而类型则来自于服务端进行校验所依据的相同数据模型。

OpenAPI 操作如何转换为工具

这种映射关系比看起来更加直接。以单个操作为例:

paths:
  /orders/{orderId}/refund:
    post:
      operationId: refundOrder
      summary: Refund an order
      description: >
        Issues a full or partial refund against a completed order.
        Refunds are irreversible. Partial refunds require an amount
        no greater than the remaining refundable balance.
      parameters:
        - name: orderId
          in: path
          required: true
          schema: { type: string }
          description: The order to refund.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                amount:
                  type: integer
                  description: Amount in cents. Omit for a full refund.
                reason:
                  type: string
                  enum: [duplicate, fraudulent, requested_by_customer]

由此生成的工具定义如下:

{
  "name": "refundOrder",
  "description": "Issues a full or partial refund against a completed order. Refunds are irreversible. Partial refunds require an amount no greater than the remaining refundable balance.",
  "input_schema": {
    "type": "object",
    "required": ["orderId", "reason"],
    "properties": {
      "orderId": { "type": "string", "description": "The order to refund." },
      "amount": { "type": "integer", "description": "Amount in cents. Omit for a full refund." },
      "reason": { "type": "string", "enum": ["duplicate", "fraudulent", "requested_by_customer"] }
    }
  }
}

以下四个规则完成了大部分工作:

  1. operationId 成为工具名称。如果某个操作没有 operationId,则根据方法加路径生成一个稳定的名称,然后将其添加到接口规范中。
  2. Path、query 和 body parameter 扁平化为一个 properties 对象。模型并不关心值在网络传输中处于什么位置,但你的执行器关心,因此需要维护一个辅助表来记录每个 parameter 的具体位置。
  3. summary 加上 description 成为工具描述。两者拼接在一起。仅凭 summary 通常过于简短,不足以指导工具的选择。
  4. 合并 required 数组。必填的 path 参数和必填的 body 字段都会归入同一个 required 列表中。

执行器是另一半工作,且非常简短:

def execute(tool_name, args, spec_index, http):
    op = spec_index[tool_name]          # method, path template, param locations
    path = op.path
    query, body = {}, {}

    for name, value in args.items():
        location = op.locations[name]   # "path" | "query" | "header" | "body"
        if location == "path":
            path = path.replace("{" + name + "}", str(value))
        elif location == "query":
            query[name] = value
        elif location == "body":
            body[name] = value

    return http.request(op.method, path, params=query, json=body or None)

这就是完整的桥接。其余的工作只是过程中的清理。

生成器需要解决的问题

将接口规范直接粗暴地导出为工具数据模型,会导致模型难以处理这些工具。以下五个调整至关重要。

解析 $ref 指针。 大多数工具调用 API 仅支持 JSON Schema 的子集,并且不会跟踪指向组件库(components)部分的引用。因此需要将它们内联。注意防范递归数据模型,因为内联会导致无限展开;请在固定深度截断递归,并用自然语言描述更深层的结构。

丢弃不支持的关键字。 oneOfallOfdiscriminatornullable 在接口规范中很常见,但工具数据模型对其支持较差。可以通过合并属性来消除 allOf。对于 oneOf,要么选择最主要的变体,要么将操作拆分为两个工具(每个工具对应一种结构)。无论如何,第二种方案通常能实现更好的工具选择效果。

扁平化深层嵌套。 超过三层嵌套的 body 很难被模型正确填充。如果你的创建订单 payload 中嵌套了 customer.address.postal_code,可以考虑设计一个更扁平的工具接口,并在执行器中重新组装嵌套的结构。

精简响应数据模型。 工具定义描述的是输入。完整的响应数据模型不应该包含在定义中,包含它会浪费上下文空间。只有当结果返回时,响应的结构才重要。这是一个独立的问题,我们在关于如何将 API 响应保持在 Agent 上下文窗口内的文章中对此进行了探讨。

保留安全标记。 应当对写操作进行标记,以便执行器可以将其路由到审批流程中。如果你的接口规范使用了类似 x-agent-requires-approval 的扩展属性,请读取并遵守它。这可以与我们 AI Agent 安全护栏指南中的模式配合使用。

不要把所有 200 个接口都交给模型

实际上面临的最大问题不是转换,而是数量。一个成熟的 API 拥有数百个操作,如果把它们全部粘贴到工具列表中,会同时导致两个失败:在任务开始之前,上下文就已经被各种数据模型填满了;同时,由于模型要在近乎相同的选项中进行选择,工具选择的准确度也会下降。

以下是减少接口数量的三种方法,大致按其效果排序:

按标签过滤。 OpenAPI 操作带有标签,而标签通常会映射到不同的业务领域。一个处理退款的 Agent 需要 orderspayments 标签,而不需要 adminanalytics。这只是一个单行过滤规则,通常能过滤掉大部分接口。

维护允许调用的白名单。 根据 operationId 列出允许该 Agent 调用的操作,并仅生成这些操作。这同时也起到安全控制的作用,因为如果 Agent 没有某个接口对应的工具,就无法意外调用它。我们在关于防止 Agent 搞垮你的 API 的文章中,极力推荐采用这种精简接口的设计。

按需检索工具。 对于非常庞大的 API,可以对操作进行索引,并在每次对话中根据任务选择少数几个。这增加了一个检索步骤,并且会引入其特有的失败模式,因此建议仅在过滤和白名单维护不足以解决问题时才使用此方法。

此外,还有协议路线。Model Context Protocol 规范了服务端向客户端公开工具的方式,而基于 OpenAPI 文档的 MCP 服务端为您提供了一个统一的集成点,无需为每个框架分别进行集成。我们关于 MCP 的科普文章介绍了该模型,而使用 Apifox 构建 MCP 服务端则涵盖了具体的构建过程。

首先要保证接口定义/规范的正确性

工具生成将质量问题转移到了上游。OpenAPI 文档中模糊的描述会变成模糊的工具描述,导致模型选择错误的接口。服务端实际上必填的 optional(可选)字段,会导致 Agent 在首次尝试调用工具时就发生错误。

因此,在生成任何内容之前,请站在 Agent 的角度审计接口定义/规范:

  • 每个操作都有一个 operationId,且读起来像“动词 + 名词”。
  • 每个操作都有一个描述,说明它的作用、它会改变什么以及何时不该使用它。仅写 “Deletes a user” 是不够的,而 “Permanently deletes a user and all their sessions. Cannot be undone. Use deactivateUser to disable access temporarily.” 才是合格的描述。
  • 每个 parameter 都有包含单位和格式的描述。amount 是模糊的,而 “Amount in cents, minimum 50” 则很清晰。
  • 枚举(Enums)应当被声明式地定义,而不是用文字描述,这样模型就能获得一个闭集,而不是去猜测。
  • Required(必填项)标注要准确。接口定义/规范在演进过程中往往容易将所有内容都标记为 optional,这会将校验失败的风险推迟到运行时。

这是基本的接口定义/规范规范化工作,并且能带来双重收益,因为相同的文本也会用于生成你发布的文档。在 Apifox 中,接口定义/规范、文档、mock 服务端和测试都来自同一个项目,因此完善一个描述就能同时改进所有这些内容。我们关于在 Apifox 中管理 API 版本的指南则涵盖了另一半内容——如何在长期演进中确保生成的工具保持准确。

共享工具集,而不是复制它

生成的工具集本质上是配置,而仅存在于单个开发人员本地检出分支中的配置,就像手写的数据模型一样容易产生偏差。过滤列表、白名单和固定的接口定义/规范版本应当是共享的产出物,与它们所源自的接口定义/规范并存并进行版本管理。

一些平台将此作为默认单元。在 HiFox 中,Agent 是保存的工作配置,而不是一次性的提示词:它的指令、Runtime、Skills、代码仓库和运行设置都会随之移动,并可以在空间(Space)中共享。因此,一个行之有效的工具配置会成为团队复用的资产,而不是每个人都去重新构建。底层的 runtime 仍然是 Claude Code、Codex 或你已经在运行的其他工具。改变的是,围绕它的配置不再局限于本地。

测试生成的工具

生成的工具会以手写工具所不具备的方式发生故障,因此既要测试工具的调用,也要测试其生成过程。

首先进行数据模型往返校验。对于每个生成的工具,根据数据模型构建一个有效的示例并发送。任何返回 400422 的响应都意味着工具数据模型与服务端不一致,而这正是需要修复接口规范的地方。

然后测试选择机制。编写一小组已知正确工具的任务提示词,运行它们,并记录模型选择了哪个工具。这是一个低成本的回归测试套件,可以及时发现有人重命名操作或缩短描述的情况。由于输出是非确定性的,因此应该对工具名称进行断言,而不是对具体的参数进行精确断言,这与我们关于测试非确定性 Agent 的指南一致。

最后,在正式上线前,在 mock 上运行 Agent。根据同一接口规范生成的 mock 服务端可以提供真实的响应且没有副作用,并且它允许你注入重试逻辑应该处理的 500 错误和超时。

总结与展望

接口规范是契约,工具列表应该是它的映射,而不是手动维护的平行副本。自动生成工具,严格筛选它们,保持描述真实,并对数据结构和选择机制进行测试。

首先导出你的 OpenAPI 文档,并统计没有描述的操作数量。这个数字代表了你与构建出值得信赖的 Agent 工具之间还有多少工作要做。如果你希望在修复过程中将接口规范、mock 和测试整合在一处,可以下载 Apifox

常见问题解答

我能从 Swagger 2.0 文档生成工具吗? 可以,但首先应将其转换为 OpenAPI 3.x。2.0 的 body 模型差异较大,以至于生成器对其处理不一致,而 3.x 是目前工具链主要支持的版本。OpenAPI 规范仓库记录了这些差异。

一个模型可以同时处理多少个工具? 在达到技术上限之前,准确率就已经开始下降了,实际的上限通常是几十个。如果工具列表超过了这个数量,应将其视为需要通过标签过滤或整理白名单的信号,而不是用来测试极限的阈值。

工具名称应该与 operationId 完全一致吗? 是的,前提是 operationId 具有可读性。这样你可以直接从工具调用追溯到接口规范操作,从而让追踪和调试变得更加容易。如果名称不好,请在接口规范中重命名,而不是在生成器中修改。

GraphQL API 呢? 同样的思路也适用,只是数据源不同:通过内省数据模型,并为每个查询(query)或变更(mutation)生成一个工具。由于 GraphQL 数据模型暴露了更多的表层,数量问题会更加严重,因此过滤就显得尤为重要。

我还需要手动编写工具吗? 少数情况下需要。例如将多个调用串联成一个动作的复合工具,以及封装非 HTTP 协议的工具,仍然需要手动编写。关键在于,那些常规的单接口封装器不再需要手工编写了。

如何在测试期间阻止 Agent 调用写入接口? 通过过滤 HTTP 方法为测试运行生成一个只读工具集,并将 Agent 指向针对任何写入操作的 mock。我们关于为什么 Agent 应该调用 mock 而非生产环境的博文介绍了具体配置。

开发必备:API 全流程管理神器 Apifox

介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。

如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用

Apifox

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案

获取专属报价与部署方案

icon 详细的私有化部署系统架构与安全白皮书
icon 针对您公司规模的专属报价单
icon 免费的 1v1 专属产品演示 (Demo) 机会
获取部署方案
* 提交后,我们的客户经理将在 1 个工作日内与您联系
林俊锋 企业微信
@Apifox 专属顾问
扫码备注: 私有化 + 公司名