Repository navigation
feat(tools): add OpenAPI toolset #343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # OpenAPI Tools | ||
|
|
||
| `OpenAPIToolSet` converts operations in an OpenAPI 3.x document into standard tRPC-Agent `BaseTool` instances. It loads a Python mapping or local JSON/YAML file, derives each tool name from `operationId`, exposes JSON input schemas to the model, and executes calls asynchronously with `httpx`. | ||
|
|
||
| ## Use cases | ||
|
|
||
| OpenAPI Tools are useful when a system already exposes REST APIs and maintains | ||
| an OpenAPI document, but implementing a separate `FunctionTool` for every | ||
| operation would duplicate existing contracts. Typical examples include: | ||
|
|
||
| - CRM customer search and activity records; | ||
| - ticket lookup, creation, and status updates; | ||
| - product, inventory, and order services; | ||
| - internal approval, CMDB, and operations platforms; | ||
| - SaaS products that publish OpenAPI specifications. | ||
|
|
||
| The feature reuses the existing API contract: | ||
|
|
||
| ```text | ||
| OpenAPI document | ||
| → operationId and parameter schemas | ||
| → Agent-visible Tools | ||
| → model selects a Tool and generates arguments | ||
| → asynchronous HTTP request | ||
| → Tool Result returned to the model | ||
| ``` | ||
|
|
||
| It does not replace the Agent. `OpenAPIToolSet` adapts APIs into Tools, while | ||
| `LlmAgent` still interprets user intent, chooses operations, prepares arguments, | ||
| and explains responses. | ||
|
|
||
| ## Basic usage | ||
|
|
||
| ```python | ||
| from trpc_agent_sdk.tools import OpenAPIToolSet | ||
|
|
||
| toolset = OpenAPIToolSet( | ||
| "openapi.yaml", | ||
| base_url="https://api.example.com/v1", | ||
| operation_ids=["get_pet", "create_pet"], | ||
| static_headers={"Authorization": "Bearer secret"}, | ||
| allowed_hosts=["api.example.com"], | ||
| timeout=20.0, | ||
| ) | ||
|
|
||
| tools = await toolset.get_tools() | ||
| ``` | ||
|
|
||
| Pass `toolset` directly in an agent's `tools` list. Call `await toolset.close()` if no runner owns its lifecycle. An existing `httpx.AsyncClient` can be supplied with `client`; when supplied, the caller retains ownership. `transport` is also available for an owned custom transport such as `httpx.MockTransport`. | ||
|
|
||
| `auth` accepts an `httpx.Auth` implementation. `static_headers` are applied after operation arguments, so trusted application headers cannot be replaced by model-generated header parameters. | ||
|
|
||
| ## Conversion logic | ||
|
|
||
| For every HTTP operation: | ||
|
|
||
| 1. `operationId` becomes the tool name. Missing, duplicate, or unsafe IDs are rejected. | ||
| 2. Path-level and operation-level parameters are combined. Operation-level entries override matching `(name, location)` entries. | ||
| 3. Path, query, header, and cookie parameters become top-level tool arguments. | ||
| 4. an `application/json` request body becomes the `request_body` argument. | ||
| 5. Required parameters and request bodies populate the generated JSON Schema `required` list. | ||
| 6. Local `$ref` JSON Pointers are resolved before schema generation. | ||
| 7. The base URL is chosen from `base_url`, operation servers, path servers, then document servers. | ||
|
|
||
| Successful calls return: | ||
|
|
||
| ```python | ||
| { | ||
| "status_code": 200, | ||
| "content_type": "application/json", | ||
| "content": {"id": "pet-1"}, | ||
| } | ||
| ``` | ||
|
|
||
| JSON media types, including `+json`, are decoded. Other responses are returned as text and empty responses as `None`. HTTP 4xx/5xx responses raise `OpenAPIHTTPError`; transport, argument, and conversion failures use clear `OpenAPIToolError` or `OpenAPISpecError` exceptions. | ||
|
|
||
| ## Selection and filtering | ||
|
|
||
| Use `operation_ids` to select a fixed subset while converting. Unknown requested IDs are rejected. The standard `tool_filter` and `is_include_all_tools` arguments remain available for context-time filtering through the normal toolset interface. | ||
|
|
||
| ## Security and SSRF | ||
|
|
||
| Treat OpenAPI documents as executable configuration: | ||
|
|
||
| - Prefer checked-in local specifications. Remote specification URL loading is intentionally not supported; fetch and validate a trusted document in application code before passing a mapping. | ||
| - Set `base_url` from trusted configuration instead of accepting it from users. | ||
| - Set `allowed_hosts` to constrain the final request host. Redirect behavior remains controlled by the supplied `httpx.AsyncClient`; the default client does not follow redirects. | ||
| - Inject credentials with `auth` or `static_headers`, and do not place secrets in the OpenAPI file. | ||
| - Review operations before exposing them to a model, especially destructive methods. | ||
| - Apply network egress controls as the final SSRF boundary. Host allowlists do not replace DNS and network-layer controls. | ||
|
|
||
| Base URLs must be absolute HTTP(S) URLs and cannot contain embedded credentials or fragments. | ||
|
|
||
| ## Supported subset and limitations | ||
|
|
||
| Supported: | ||
|
|
||
| - OpenAPI 3.x documents from Python mappings and local JSON/YAML files | ||
| - HTTP operations with safe, unique `operationId` values | ||
| - Path, query, header, and cookie parameters | ||
| - JSON request bodies | ||
| - Local, non-circular `$ref` references | ||
| - server variable defaults | ||
| - JSON, text, and empty response bodies | ||
|
|
||
| Not currently supported: | ||
|
|
||
| - Swagger/OpenAPI 2.x | ||
| - remote or external `$ref` references and remote specification loading | ||
| - circular schemas | ||
| - non-JSON request bodies, multipart uploads, callbacks, links, or webhooks | ||
| - full OpenAPI parameter serialization styles such as `deepObject` | ||
| - automatic interpretation of OpenAPI `securitySchemes` | ||
| - response-schema validation | ||
|
|
||
| ## Complete Agent example | ||
|
|
||
| The [OpenAPI tools example](../../../examples/openapi_tools/README.md) follows | ||
| the same complete Agent structure as Quickstart: | ||
|
|
||
| ```text | ||
| root_agent (LlmAgent) | ||
| └── OpenAPIToolSet | ||
| ├── get_pet | ||
| └── create_pet | ||
| ``` | ||
|
|
||
| A real model decides whether to call `get_pet` or `create_pet`, and the example | ||
| prints generated arguments, Tool Results, and final Agent responses. The Pet | ||
| API itself uses an in-process `httpx.MockTransport`, so no external business | ||
| service is contacted. | ||
|
|
||
| Copy and configure the environment before running: | ||
|
|
||
| ```bash | ||
| cd examples/openapi_tools | ||
| cp .env.example .env | ||
| python run_agent.py | ||
| ``` | ||
|
|
||
| For production, replace the Mock Transport with the real HTTP API and use | ||
| `operation_ids` to expose only reviewed operations. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| # OpenAPI 工具 | ||
|
|
||
| `OpenAPIToolSet` 可将 OpenAPI 3.x 文档中的操作转换为标准 tRPC-Agent `BaseTool`。它支持 Python 字典或本地 JSON/YAML 文件,以 `operationId` 生成工具名,向模型暴露 JSON 输入 Schema,并通过 `httpx` 异步发起请求。 | ||
|
|
||
| ## 使用场景 | ||
|
|
||
| OpenAPI Tools 适合已经有 REST API 和 OpenAPI 文档,但不希望逐个开发 | ||
| `FunctionTool` 的系统,例如: | ||
|
|
||
| - CRM 客户查询和跟进记录; | ||
| - 工单查询、创建和状态更新; | ||
| - 商品、库存和订单服务; | ||
| - 企业内部审批、CMDB 或运维平台; | ||
| - 由第三方提供 OpenAPI 规范的 SaaS 服务。 | ||
|
|
||
| 其核心价值是复用现有 API 契约: | ||
|
|
||
| ```text | ||
| OpenAPI 文档 | ||
| → operationId 和参数 Schema | ||
| → Agent 可见的 Tool | ||
| → 模型选择 Tool 并生成参数 | ||
| → 异步 HTTP 请求 | ||
| → Tool Result 返回模型 | ||
| ``` | ||
|
|
||
| 它不会替代 Agent。`OpenAPIToolSet` 负责 API 到 Tool 的适配,`LlmAgent` | ||
| 仍负责理解用户意图、选择 API、组合参数并解释响应。 | ||
|
|
||
| ## 基本用法 | ||
|
|
||
| ```python | ||
| from trpc_agent_sdk.tools import OpenAPIToolSet | ||
|
|
||
| toolset = OpenAPIToolSet( | ||
| "openapi.yaml", | ||
| base_url="https://api.example.com/v1", | ||
| operation_ids=["get_pet", "create_pet"], | ||
| static_headers={"Authorization": "Bearer secret"}, | ||
| allowed_hosts=["api.example.com"], | ||
| timeout=20.0, | ||
| ) | ||
|
|
||
| tools = await toolset.get_tools() | ||
| ``` | ||
|
|
||
| 可将 `toolset` 直接放入 Agent 的 `tools` 列表。如果没有 Runner 管理生命周期,请调用 `await toolset.close()`。也可传入已有的 `httpx.AsyncClient`;此时客户端仍由调用方关闭。`transport` 可用于让 ToolSet 管理自定义传输,例如 `httpx.MockTransport`。 | ||
|
|
||
| `auth` 接受 `httpx.Auth` 实现。`static_headers` 在操作参数之后写入,因此模型生成的 Header 参数不能覆盖应用注入的可信 Header。 | ||
|
|
||
| ## 转换逻辑 | ||
|
|
||
| 每个 HTTP 操作按以下规则转换: | ||
|
|
||
| 1. `operationId` 成为工具名;缺失、重复或不安全的 ID 会立即报错。 | ||
| 2. 合并 Path Item 与 Operation 参数;相同 `(name, location)` 的 Operation 参数优先。 | ||
| 3. path、query、header、cookie 参数成为顶层工具参数。 | ||
| 4. `application/json` 请求体成为 `request_body` 参数。 | ||
| 5. 必填参数和必填请求体进入生成 JSON Schema 的 `required`。 | ||
| 6. 生成 Schema 前解析本地 `$ref` JSON Pointer。 | ||
| 7. Base URL 按 `base_url`、Operation servers、Path servers、文档 servers 的顺序选择。 | ||
|
|
||
| 成功调用返回: | ||
|
|
||
| ```python | ||
| { | ||
| "status_code": 200, | ||
| "content_type": "application/json", | ||
| "content": {"id": "pet-1"}, | ||
| } | ||
| ``` | ||
|
|
||
| JSON 和 `+json` 媒体类型会被解析;其他响应返回文本,空响应返回 `None`。HTTP 4xx/5xx 抛出 `OpenAPIHTTPError`;传输、参数和转换错误会使用清晰的 `OpenAPIToolError` 或 `OpenAPISpecError`。 | ||
|
|
||
| ## 操作选择与过滤 | ||
|
|
||
| 使用 `operation_ids` 在转换时选择固定操作子集。指定不存在的 ID 会报错。标准 `tool_filter` 与 `is_include_all_tools` 参数仍可通过普通 ToolSet 接口实现运行时过滤。 | ||
|
|
||
| ## 安全与 SSRF | ||
|
|
||
| 应将 OpenAPI 文档视为可执行配置: | ||
|
|
||
| - 优先使用代码仓库中受控的本地规范。为降低 SSRF 风险,本功能不支持直接从 URL 加载规范;如有需要,应在应用层获取并校验可信文档,再传入字典。 | ||
| - 使用可信配置设置 `base_url`,不要接受用户输入。 | ||
| - 使用 `allowed_hosts` 限制最终请求主机。重定向行为由传入的 `httpx.AsyncClient` 控制;默认客户端不跟随重定向。 | ||
| - 使用 `auth` 或 `static_headers` 注入凭证,不要把密钥写入 OpenAPI 文件。 | ||
| - 向模型暴露前审查操作,尤其是具有修改或删除效果的操作。 | ||
| - 使用网络出口策略作为最终 SSRF 防线;Host 白名单不能替代 DNS 与网络层控制。 | ||
|
|
||
| Base URL 必须是绝对 HTTP(S) URL,且不能包含内嵌凭证或 Fragment。 | ||
|
|
||
| ## 支持范围与限制 | ||
|
|
||
| 支持: | ||
|
|
||
| - Python 字典和本地 JSON/YAML 格式的 OpenAPI 3.x | ||
| - 具有安全且唯一 `operationId` 的 HTTP 操作 | ||
| - path、query、header、cookie 参数 | ||
| - JSON 请求体 | ||
| - 本地、非循环 `$ref` | ||
| - Server 变量默认值 | ||
| - JSON、文本和空响应体 | ||
|
|
||
| 暂不支持: | ||
|
|
||
| - Swagger/OpenAPI 2.x | ||
| - 远程或外部 `$ref`,以及远程规范 URL 加载 | ||
| - 循环 Schema | ||
| - 非 JSON 请求体、multipart 上传、callback、link 或 webhook | ||
| - `deepObject` 等完整 OpenAPI 参数序列化样式 | ||
| - 自动解析 OpenAPI `securitySchemes` | ||
| - 响应 Schema 校验 | ||
|
|
||
| ## 完整 Agent 示例 | ||
|
|
||
| [OpenAPI 工具示例](../../../examples/openapi_tools/README.md)采用与 Quickstart | ||
| 一致的完整 Agent 结构: | ||
|
|
||
| ```text | ||
| root_agent (LlmAgent) | ||
| └── OpenAPIToolSet | ||
| ├── get_pet | ||
| └── create_pet | ||
| ``` | ||
|
|
||
| 示例使用真实模型判断应调用 `get_pet` 还是 `create_pet`,并打印模型生成的 | ||
| 参数、Tool Result 和最终回答。Pet API 通过 `httpx.MockTransport` 在进程内 | ||
| 模拟,因此不会访问外部业务服务。 | ||
|
|
||
| 运行前复制并配置环境变量: | ||
|
|
||
| ```bash | ||
| cd examples/openapi_tools | ||
| cp .env.example .env | ||
| python run_agent.py | ||
| ``` | ||
|
|
||
| 生产环境可将 Mock Transport 替换为真实 HTTP API,并通过 `operation_ids` | ||
| 只暴露经过审查的操作。 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| TRPC_AGENT_API_KEY= | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 问题: 文档 触发条件: 用户按文档操作时 实际影响: 示例无法按文档引导运行,新用户上手即遇到失败,示例可复现性差。 修正方向: 提交 |
||
| TRPC_AGENT_BASE_URL= | ||
| TRPC_AGENT_MODEL_NAME= | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
问题: 本文件(第 134 行)与英文版
docs/mkdocs/en/openapi_tools.md(第 137 行)都指导先执行cp .env.example .env,但本次提交只包含examples/openapi_tools/.env,没有.env.example(git ls-files确认仓库内不存在该文件)。触发条件: 用户按文档快速上手时按文档中的安装流程执行命令,
cp报「文件不存在」,首次体验即中断。实际影响: 文档开箱体验失败;用户不得不手动发现需要自行创建
.env,或误以为示例不可用,影响新特性推广。修正方向: 新增
examples/openapi_tools/.env.example(字段与现有.env一致,值为空占位符),或把文档和示例 README 中的命令改为直接编辑examples/openapi_tools/.env。