适用读者:要接 AI Agent / 低代码平台 / 内部工具市场的人;要设计"可被程序自动调用"的 API 的人。
前置:S02-B06(HTTP)、S02-B09(HTTP 与 RPC)。
实测环境:Python 3.12(内置http.server+urllib);端到端 demo 为 2026-11-05 实测。
覆盖旧文:A-647(OpenAPI Schema:把 HTTP API 变成可执行插件工具,199 点)、A-649(从 toolId 到真实 HTTP 请求,56 点)= 255 点。
覆盖扩展:E-S02-019(OpenAPI 3.1 与 JSON Schema)、E-S02-020(function calling / MCP)。
上一篇:S02-B09 · HTTP 与 RPC:为什么 RPC 更快,又为什么仍要 HTTP
下一篇:S02-X01 · 面试问答速查(基础层附录)。
1. 一句话结论
一个 HTTP API 要变成"可被程序调用的工具",只需要三样东西:可机读的描述(schema)、可预测的地址(URL 模板)、可判定的结果(状态码 + 结构)。
OpenAPI 文档 ──抽取──► 工具描述(name / description / parameters)
│ 模型按描述生成参数 JSON
▼
参数校验 → URL 拼接 → 发起请求 → 结果归一化
这四步里,最容易出事的是第一步和第四步:描述不严 → 模型乱填参数;结果不统一 → 上层无法判断成败。
2. 实测:从 spec 到真实请求
2.1 一份最小 OpenAPI 3.1 描述
{
"openapi": "3.1.0",
"info": { "title": "Demo Items API", "version": "1.0.0" },
"servers": [{ "url": "http://127.0.0.1:19030" }],
"paths": {
"/items/{id}": {
"get": {
"operationId": "getItem",
"summary": "按 ID 取条目",
"parameters": [
{ "name": "id", "in": "path", "required": true,
"schema": { "type": "string", "pattern": "^[0-9]{1,6}$" } },
{ "name": "verbose", "in": "query", "required": false,
"schema": { "type": "boolean", "default": false } }
],
"responses": { "200": { "description": "OK" } }
}
}
}
}
2.2 按描述生成并发出真实请求
args = {"id": "42", "verbose": True}
for p in op["parameters"]:
if p["required"] and p["name"] not in args:
raise SystemExit(f"缺必填参数 {p['name']}")
url = base + "/items/{id}".replace("{id}", args["id"]) + ("?verbose=true" if args.get("verbose") else "")
实测输出:
工具描述 -> 实际 URL: http://127.0.0.1:19030/items/42?verbose=true
响应: {"id": "42", "name": "示例条目", "verbose": true}
这条链路就是"插件化"的全部本质:描述 → 参数 → URL → 结果。没有魔法。
3. 描述里的五个字段,缺一个就不可靠
| 字段 | 作用 | 缺了会怎样 |
|---|---|---|
operationId |
工具唯一 ID(toolId) | 无法稳定引用;重名/改名引用的调用全断 |
summary / description |
给模型看的"什么时候该用它" | 模型不知道何时调用,或乱调用 |
parameters[].required |
必填校验 | 模型漏参数 → 请求构造失败 |
parameters[].schema.type/pattern |
类型与格式约束 | 模型传 "abc" 给 ID → 后端 500 |
responses |
结果结构 | 无法判断成败与提取数据 |
3.1 toolId 到请求的映射(旧文 A-649 的核心)
| toolId | HTTP | 映射规则 |
|---|---|---|
getItem |
GET /items/{id} |
path 参数替换、query 参数拼接 |
createOrder |
POST /orders |
body 由 requestBody schema 生成,Content-Type 来自 spec |
listOrders |
GET /orders?status= |
可选参数缺省值由 default 决定 |
工程实践:toolId 一旦发布就不要改。要改就新增一个 toolId 并把旧的标记 deprecated——这是"工具契约"的版本兼容问题(A02 里讲的 API 治理同理)。
4. 四道安全闸门(旧文没讲,但必须有)
| 闸门 | 风险 | 做法 |
|---|---|---|
| 鉴权 | 工具被滥用 | 凭据由平台注入(不让模型看见 token) |
| 参数校验 | 注入/越权 | 按 schema 校验(类型、范围、枚举、正则) |
| 目标限制 | SSRF:模型让你请求内网地址 | 白名单域名 + 禁止 169.254.169.254、127.0.0.1、私网段 |
| 副作用控制 | 误删/误扣款 | 写操作需二次确认;幂等键;限流 |
SSRF 那条尤其重要:如果工具描述里的 servers.url 或参数能被外部影响,攻击者可以让你的服务去访问云元数据接口(http://169.254.169.254/latest/meta-data/)拿到凭据。
5. 与 function calling / MCP 的关系(E-S02-020)
| 概念 | 是什么 | 与 OpenAPI 的关系 |
|---|---|---|
| function calling | 模型输出"我要调用 X,参数是 {...}" | 工具的 schema 来自 OpenAPI 的精简版(name/description/parameters) |
| MCP(Model Context Protocol) | 标准化的"模型 ↔ 工具/资源"协议 | 一个 MCP server 可以把多个 OpenAPI 接口包装成工具 |
| OpenAPI | 描述 HTTP 接口的行业标准 | 描述源头;工具市场多由它导入 |
实践结论:
- 先有干净的 OpenAPI,再有可靠的工具——描述质量决定工具质量。
- 不要直接把整个 OpenAPI 丢给模型:接口几十个时要做"工具筛选/分组",否则模型选择困难。
- 写操作必须走确认:读接口可以自动调,写接口(下单、删除、转账)必须人工确认或强幂等。
6. 一个"不合格的工具描述"长什么样
{ "name": "do", "description": "执行操作", "parameters": { "type": "object" } }
| 问题 | 后果 |
|---|---|
| 名字无意义 | 模型无法判断用途 |
| 描述模糊 | 该用的时候不用,不该用时乱用 |
| 参数无 schema | 传什么都能构造请求,后端承担全部风险 |
| 无返回值定义 | 上层无法判断成功/失败 |
反面判据:如果一个描述不能让人(不看代码)正确调用它,模型也做不到。
7. 五个误区
| 误区 | 真相 |
|---|---|
| "有了 OpenAPI 就能自动生成可靠工具" | 描述质量决定一切;自动生成只解决"形式" |
| "工具越多越好" | 选择成本上升,误调用率上升 |
| "鉴权交给模型传 token" | 凭据绝不应进入模型上下文 |
| "写接口和读接口一样处理" | 写接口必须幂等 + 确认 |
| "MCP 会取代 OpenAPI" | MCP 是"如何暴露给模型",OpenAPI 是"如何描述 HTTP";互补 |
8. 自检
- 工具描述里缺
required会导致什么具体故障? - 为什么
operationId不能随意改? - SSRF 在这个链路里的具体入口在哪?
- 读接口与写接口的处理差异是什么?为什么?
- 什么样的描述算"人能不看代码就正确调用"?
9. 下一篇
S02-X01 · 面试问答速查:操作系统 / 计算机网络 / 网络编程
基础层最后一篇是可查的附录:把 4 张大卡(2,232 个点位)压成"题干 + 一句话答案 + 正文小节链接",只做索引不重复正文。之后进入进阶层(TCP 排障、TLS 深水区、连接数工程)。
本篇属于系列《S02 · 计算机与网络基础》。旧文来源:A-647、A-649;扩展点 E-S02-019/020。OpenAPI → 请求端到端为 2026-11-05 实测。