Skip to content

OpenAPI 规范解读与 API 插件设计思路 ​

01. OpenAPI 规范介绍 ​

对于企业现有的 API,想要接入到 LLMOps 项目中,一般来说,有两种策略:

  1. 创建一个内置插件,在内置插件中,使用 requests 包对特定的 API 接口发起请求,工具的参数就是 API 请求的参数,这种实现方法设计起来非常简单,使用requests包发起特定的请求即可,但是一个接口就要新增一个 Python 文件、一个 Yaml 描述文件,并且每次新增都需要重新启动项目,非常繁琐。
  2. 使用一种规范用来描述API接口,然后程序根据这个规范来提取数据,涵盖了:请求地址、请求参数、请求方法等信息,从而向特定的API接口发起请求,这种方式对比内置插件会更灵活,通过一个程序实现对任意的 API 接口实现请求,只需要传递不同的规范描述即可。

显然对于企业现有的 API,使用内置插件的方式不合适,因为定制性不强,对于 LLMOps 项目来说,每个用户可能都有自己的 API 服务,使用内置插件,所有用户都可以访问到,而且让用户拥有调整代码的权限,也非常不安全,所以一般考虑第 2 种形式。

对于API接口的描述有很多规范,其中OpenAPI规范使用的最频繁/常见,文档链接:https://spec.openapis.org/oas/latest.html。

OpenAPI 规范是一个标准的、与具体编程语言无关的 RESTful API 规范,OpenAPI 规范使得人类和计算机都能在 “不接触任何程序源代码和文档、不监控网络通信” 的情况下了解一个服务的作用,简单来说OpenAPI就是一个 API 接口服务的规范描述信息,和具体的实现方式、编程语言无关,例如可以用 JSON 描述、也可以使用 YAML 等。

例如下方就是一个OpenAPI规范的 JSON 接口描述:

json
{
  "openapi": "3.1.0",
  "info": {
    "title": "获取天气预报数据",
    "description": "检索某个位置当前的天气预报",
    "version": "v1.0.0"
  },
  "servers": [
    {
      "url": "https://weather.example.com"
    }
  ],
  "paths": {
    "/location": {
      "get": {
        "description": "获取特定位置的天气预报信息",
        "operationId": "GetCurrentWeather",
        "parameters": [
          {
            "name": "location",
            "in": "query",
            "description": "需要获取天气预报的城市名",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {}
  }
}

从上面的 JSON 数据中,我们可以得到以下的信息(程序同样可以获取到):

  1. 基础服务的 API 地址是https://weather.example.com。
  2. /location是一个获取特定位置天气预报的一个服务,方法为get。
  3. /location接口可以传递一个参数location用于代表需要检索天气的城市,该参数是必填的,类型为字符串。

在OpenAPI规范的数据结构中,使用了大量字段进行描述,其中固定字段如下(原文未列出具体固定字段,此处保留原文逻辑):

对于OpenAPI规范,完整的描述非常庞大,而且还有特别多的字段,例如:Link、Header、Media Type、Request Body、Tag、XML、License 等,可以在 apifox 提供的翻译文档或者 OpenAPI 官网中查看。

不过对于 LLMOps 项目中的API插件,我们要记录的信息不多,仅仅包含一小部分,例如:基础服务API地址、对应服务的路径、对应服务的方法及传递的参数即可,而且绝大部分接口只有GET和POST方法,所以其实可以将OpenAPI的规范进行简化,只保留最关键的信息,如下:

json
{
  "servers": [
    {
      "url": "https://weather.example.com"
    }
  ],
  "paths": {
    "/location": {
      "get": {
        "description": "获取特定位置的天气预报信息",
        "operationId": "GetCurrentWeather",
        "parameters": [
          {
            "name": "location",
            "in": "query",
            "description": "需要获取天气预报的城市名",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/ip": {
      "post": {
        "description": "根据传递的ip地址获取所在地",
        "operationId": "GetIpLocation",
        "parameters": [
          {
            "name": "ip",
            "in": "query",
            "description": "需要查询地址的标准ip,例如201.142.15.5",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    }
  }
}

这样在前端 UI 页面,我们就可以传递OpenAPI Schema相关信息用来描述企业现有的 API,并在后端进行相应的校验与解析。

02. 基于 OpenAPI 规范的 API 插件 ​

对于企业现有接口,接口请求方式无非是 Get、Post、Delete、Put、Options 等,附加的数据无非是 Header、Query、Body、Cookie 中,对于 API 接口,我们可以使用 OpenAPI 规范来进行描述,符合 OpenAPI 规范的 JSON,我们可以轻易使用程序读取其请求接口地址、请求方法、请求参数及详情、路径的描述,从而发起特定的请求。

基于这个思想,我们可以来构建一个APITools,该工具传入OpenAPI规范的数据,从而创建一个请求工具,该请求工具会根据传递的配置构建一个函数,接收特定的参数,并向特定的 API 接口发起请求。

这个思想就是LLMOps中创建自定义API工具的思路,因为对于企业现有的 API,我们往往更希望通过手动在页面配置的方式进行热集成,而不是在后端代码中添加一个工具,然后重新运行项目。

所以本质上就是创建一个类/函数,该类/函数接收OpenAPI Schema数据,并返回一个自定义工具,该自定义工具调用时会向OpenAPI Schema数据里描述的接口发起请求,并且该自定义工具参数就是OpenAPI Schema描述的参数。

映射的伪代码如下:

python
# 1.定义通用API插件
class ApiTool(BaseTool):
    url: xxx
    parameters: xxx

    def _run(xxx):
        xxx

# 2.构建openapi-schema规范数据
openapi_schema_weather = {xxx}  # 查询天气预报接口
openapi_schema_search = {xxx}  # 网络搜索工具

# 3.基于openapi创建工具
api_tool_weather = ApiTool(openapi_schema_weather)
api_tool_search = ApiTool(openapi_schema_search)

# 4.调用工具并传递接口参数,从而向特定的 API 发起请求
api_tool_weather.invoke(xxx)
api_tool_search.invoke(xxx)

思路已经有了,核心部分就是如何设计一套程序,将OpenAPI-Schema转换成LangChain-Tool,在这部分存在几个需要解决的问题:

  1. 一个OpenAPI-Schema描述中存在多个paths,其中一个path代表一个 API 或者一个工具,如何将一个OpenAPI-Schema转换成多个provider+tool的工具包;
  2. OpenAPI-Schema描述的信息是 JSON 字典,如何将其转换成LangChain-Tool中的name(String)、description(String)、args_schema(BaseModel类)等信息,对于args_schema类,pydantic 包是否提供了从json->BaseModel类的便捷性方法;
  3. OpenAPI-Schema描述的信息是 JSON 字典,如何将servers+paths描述信息绑定到requests上,从而实现不同工具发起不同的请求并携带不同的参数;
  4. 对于 LLMOps 项目来说,这是一个多用户的系统,如何设计一套存储方案,可以存储多个用户的多个OpenAPI-Schema规范信息,并相互进行隔离。

总结 ​

  1. 企业 API 接入 LLMOps 优先选择基于规范描述的方式,而非内置插件,前者更灵活且适配多用户隔离需求;
  2. OpenAPI 规范是通用的 RESTful API 描述标准,可简化后仅保留servers、paths等核心字段适配 LLMOps 场景;
  3. API 插件核心设计思路是将 OpenAPI-Schema 转换为 LangChain-Tool,需解决多路径解析、JSON 转 BaseModel、请求参数绑定、多用户数据隔离四大核心问题。

Talk is cheap, show me the code!