# /v1 托管工具

> 在对话补全、Messages、Responses 请求中声明平台工具，由平台执行工具循环：三种格式的写法、执行规则、响应形状与计费。

来源：https://www.scirouter.cn/docs/hosted-tools · 更新于 2026-10-28

在 `/v1` 的请求中声明平台的工具后，模型请求这些工具时由平台执行，结果接入上下文后再次调用模型，直到模型作答，最后返回一个完整的响应。调用方无需实现工具循环。

| 接口 | 声明写法 | 响应中的工具调用 |
|---|---|---|
| `POST https://api.scirouter.cn/v1/chat/completions` | `tools` 中一项 `type: "mcp"` | 不出现在 `tool_calls` 中，记录在扩展字段 `scirouter.tool_calls` |
| `POST https://api.scirouter.cn/v1/messages` | `mcp_servers` + `tools` 中的 `mcp_toolset`，带 beta 请求头 | `mcp_tool_use` 与 `mcp_tool_result` 内容块 |
| `POST https://api.scirouter.cn/v1/responses` | `tools` 中一项 `type: "mcp"` | `mcp_list_tools` 与 `mcp_call` 输出项 |

可用的工具、入参与出参、按用户计的限额与数据流向与 MCP 服务端相同，见 [MCP 服务端与工具](https://www.scirouter.cn/docs/mcp)「工具」。任何支持函数调用的模型均可使用托管工具。

## 声明

### 对话补全

```json
{
  "model": "YOUR_MODEL_ID",
  "messages": [{ "role": "user", "content": "近一年钙钛矿封装稳定性有哪些新进展？给出处。" }],
  "tools": [
    { "type": "mcp", "server_label": "scirouter", "allowed_tools": ["web_search", "fetch_page"], "require_approval": "never" }
  ],
  "scirouter": { "max_tool_rounds": 6 }
}
```

`tools` 中加入一项 `type: "mcp"`。该写法沿用 OpenAI Responses 的远程 MCP 工具，官方 SDK 不会拒绝。

| 字段 | 说明 |
|---|---|
| `type` | 固定为 `"mcp"`。`tools` 中只能有一项 `type: "mcp"`；调用方自己的 `function` 工具可同时存在 |
| `server_label` | 只接受 `"scirouter"`，其他取值返回 400 |
| `allowed_tools` | 可省略，省略时为本次部署开放的全部工具。写成字符串数组或 `{ "tool_names": [...] }`。为空数组时返回 400；含不存在的工具名时返回 400，`details.available` 列出可用的工具 |
| `server_url` | 不接受，写了返回 400。外部 MCP 服务器在调用方自己的客户端中连接 |
| `require_approval` | 只能为 `"never"` 或不填 |

### Messages

```http
POST https://api.scirouter.cn/v1/messages
anthropic-beta: mcp-client-2025-11-20
```

```json
{
  "model": "YOUR_MODEL_ID",
  "max_tokens": 4096,
  "messages": [{ "role": "user", "content": "近一年钙钛矿封装稳定性有哪些新进展？给出处。" }],
  "mcp_servers": [{ "type": "url", "url": "https://api.scirouter.cn/mcp", "name": "scirouter" }],
  "tools": [
    {
      "type": "mcp_toolset",
      "mcp_server_name": "scirouter",
      "default_config": { "enabled": false },
      "configs": { "web_search": { "enabled": true }, "fetch_page": { "enabled": true } }
    }
  ],
  "scirouter": { "max_tool_rounds": 6 }
}
```

写法沿用 Anthropic 的 MCP connector。

| 位置 | 规则 |
|---|---|
| 请求头 `anthropic-beta` | 含 `mcp-client-2025-11-20` 或 `mcp-client-2026-09-15`。已弃用的 `mcp-client-2025-04-04` 也接受，此时工具选择写在 `mcp_servers[].tool_configuration`（`enabled`、`allowed_tools`）。都没有时返回 400 |
| `mcp_servers[]` | 只接受一项 `name: "scirouter"`，`type` 为 `"url"`，`url` 为 https 且路径为 `/mcp`（写 `https://api.scirouter.cn/mcp`）。平台只按这些字段识别声明，不比对域名、不连接该地址。`authorization_token` 可省略，写了也不使用 |
| `tools[]` 中的 `mcp_toolset` | 正好一项，`mcp_server_name` 为 `"scirouter"`。每个工具是否开启依次取 `configs.<工具名>.enabled`、`default_config.enabled`，都没写时开启；`configs` 中不存在的工具名不报错 |
| 不支持 | `defer_loading` 与 `mcp_toolset.tools`（固定的工具列表），写了返回 400 |
| 其他 MCP 服务器 | `name` 不是 `scirouter` 的服务器返回 400，`details.reason` 为 `server_tool_unsupported` |

`POST /messages/count_tokens` 可携带同样的声明，见 [Messages](https://www.scirouter.cn/docs/messages)「统计 token 数」。

### Responses

```json
{
  "model": "YOUR_MODEL_ID",
  "input": "近一年钙钛矿封装稳定性有哪些新进展？给出处。",
  "tools": [
    { "type": "mcp", "server_label": "scirouter", "allowed_tools": ["web_search", "fetch_page"], "require_approval": "never" }
  ],
  "scirouter": { "max_tool_rounds": 6 }
}
```

- `tools` 中的 `mcp` 项与[对话补全](#对话补全)的规则相同。`server_label` 不是 `scirouter` 时返回 400，`details.reason` 为 `server_tool_unsupported`。`headers`、`authorization`、`server_description` 不使用。
- `tool_choice` 可写 `{ "type": "mcp", "server_label": "scirouter", "name": "web_search" }`：带 `name` 时要求模型调用该工具，不带时要求模型调用一个工具。`name` 不是本次开启的托管工具时返回 400。响应中的 `tool_choice` 与请求原文相同。
- `max_tool_calls` 对托管工具不起作用。

### 轮数上限

三种格式都接受顶层扩展字段 `scirouter`：

| 字段 | 说明 |
|---|---|
| `scirouter.max_tool_rounds` | 1–30 的整数，缺省 6。带工具调用模型的轮数上限，到达后的处理见[执行流程](#执行流程) |

- 顶层 `scirouter` 只接受 `max_tool_rounds`，写了其他键返回 400。未声明托管工具却写了 `max_tool_rounds`，同样返回 400。
- 使用 OpenAI 官方 Python SDK 时，将 `scirouter` 放入 `extra_body`，SDK 会将其合并到请求体顶层。
- 本次部署未开放托管工具时，声明返回 400。调用方自己的函数（Messages 的自定义工具）与开启的托管工具重名时返回 400，错误信息中写明名称。

## 执行流程

1. 平台把开启的托管工具展开成函数定义，追加在调用方的工具之后，调用模型。工具描述按请求头 `Accept-Language` 选择语言：以 `en` 开头时为英文，其余为中文。
2. 模型只请求托管工具时，平台执行这些工具，结果按模型给出的顺序接入上下文，再调用模型。同一轮最多 4 个工具同时执行。
3. 模型请求调用方自己的函数时，循环结束，该轮返回调用方；调用方执行后发送下一轮，与不使用托管工具时相同。同一轮也请求了托管工具时，托管工具不执行（状态为 `skipped`），调用方回复后模型会重新请求。
4. 执行过一轮托管工具后，要求必须调用工具或指名某个托管工具的 `tool_choice` 改为 `auto`。
5. 达到 `max_tool_rounds` 后，平台再调用一次模型：工具定义照带，`tool_choice` 设为不调用工具，模型根据已有资料作答，因此请求总能得到回答。这一轮模型仍请求托管工具时不执行，状态为 `skipped`。
6. 带托管工具的请求只发往支持工具调用的线路。模型没有支持工具调用的线路时返回 503 `model_unavailable`（同[对话补全](https://www.scirouter.cn/docs/chat-completions)），不退回文本协议。

### 工具失败与限频

下列情况不报错，循环照常继续：原因作为该次工具调用的结果交回模型，状态为 `failed`，由模型决定重试或换用其他方法。

| 情况 | 交回模型的结果 |
|---|---|
| 入参不符合工具的 schema、工具不存在 | 哪个参数不合法、为什么；该次不执行 |
| 被限频（按用户计的核对、找先例次数，见 [MCP 服务端与工具](https://www.scirouter.cn/docs/mcp)「限额与错误码」） | 「调用太频繁，这次没有执行：稍后再试或换个办法」 |
| 收费工具遇到余额不足或达到消费上限 | 原因；该次不执行 |
| 工具已执行但未成功（网站无法打开、被策略拒绝、库中没有该记录） | 原因 |

MCP 服务端按 Key 计的两项限额不作用于托管工具。平台内部出错时，同一轮的其他工具调用被取消，整个请求返回错误。

## 响应

### 对话补全的响应

响应仍为 OpenAI 格式。托管工具的调用不出现在 `choices[].message.tool_calls` 中，顶层 `scirouter` 扩展字段（见[可观测性](https://www.scirouter.cn/docs/observability)）增加 `tool_rounds` 与 `tool_calls`。以下示例省略 `choices` 与 `usage`：

```json
{
  "scirouter": {
    "request_id": "req_…",
    "cost_quota": 23180,
    "tool_rounds": 3,
    "tool_calls": [
      { "round": 0, "id": "call_…", "name": "web_search", "status": "done", "summary": "web 8 条", "ms": 1320 },
      { "round": 1, "id": "call_…", "name": "fetch_page", "status": "failed", "summary": "读网页失败：不允许的地址", "ms": 45 }
    ]
  }
}
```

数值为示例。

| 字段 | 说明 |
|---|---|
| `tool_rounds` | 调用模型的次数 |
| `tool_calls` | 每项为一次托管工具调用。`round` 从 0 起，与请求日志的 `v1_tool_round` 标签口径相同；`status` 为 `done` / `failed` / `skipped`。不含入参与正文 |
| `cost_quota` | 各轮模型调用之和，不含工具费，见[计费](#计费) |
| `request_id` | 每轮使用不同的 `request_id`，此处为最后一轮的 |

`usage` 为各轮之和，`prompt_tokens_details` 等嵌套字段逐项相加。

### Messages 的响应

响应为一条 `message`：每个执行了托管工具的轮次依次给出模型的内容块（托管工具的 `tool_use` 换成 `mcp_tool_use`）与各次调用的 `mcp_tool_result`，最后是最后一轮的内容块。以下示例省略 `usage`：

```json
{
  "id": "msg_…",
  "type": "message",
  "role": "assistant",
  "content": [
    { "type": "mcp_tool_use", "id": "toolu_…", "name": "web_search", "server_name": "scirouter", "input": { "query": "钙钛矿 封装 稳定性" } },
    { "type": "mcp_tool_result", "tool_use_id": "toolu_…", "is_error": false, "content": [{ "type": "text", "text": "网页搜索「钙钛矿 封装 稳定性」，8 条：…" }] },
    { "type": "text", "text": "近一年…" }
  ],
  "stop_reason": "end_turn",
  "scirouter": { "request_id": "req_…", "cost_quota": 23180, "tool_rounds": 2, "tool_calls": [{ "round": 0, "id": "toolu_…", "name": "web_search", "status": "done", "summary": "web 8 条", "ms": 1320 }] }
}
```

数值为示例。

| 字段 | 说明 |
|---|---|
| `id` | 第一轮的 id |
| `mcp_tool_result.is_error` | 为 true 时该次调用的状态为 `failed`，`content[0].text` 为原因 |
| `usage` | 各轮之和；`usage.iterations` 逐轮列出每轮的用量 |
| `scirouter` | 与对话补全的扩展字段相同 |

- 状态为 `skipped` 的托管调用不出现在 `content` 中；去掉后不剩工具调用时，`stop_reason` 为 `end_turn`。
- 继续对话时，将整条助手消息原样放回 `messages`。平台把其中 `server_name` 为 `scirouter` 的 `mcp_tool_use` 与对应的 `mcp_tool_result` 还原成普通的工具调用往返，再发给模型。

### Responses 的响应

`output` 开头一项 `mcp_list_tools`，列出本次给模型的托管工具；每次执行一项 `mcp_call`；最后是最后一轮的输出项。以下示例省略 `usage`：

```json
{
  "id": "resp_…",
  "object": "response",
  "output": [
    { "id": "mcpl_req_…", "type": "mcp_list_tools", "server_label": "scirouter", "tools": [{ "name": "web_search", "description": "…", "input_schema": { "type": "object" }, "annotations": { "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": true } }] },
    { "id": "call_…", "type": "mcp_call", "server_label": "scirouter", "name": "web_search", "arguments": "{\"query\":\"钙钛矿 封装 稳定性\"}", "approval_request_id": null, "status": "completed", "output": "网页搜索「钙钛矿 封装 稳定性」，8 条：…", "error": null },
    { "type": "message", "role": "assistant", "content": [{ "type": "output_text", "text": "近一年…" }] }
  ],
  "scirouter": { "request_id": "req_…", "cost_quota": 23180, "tool_rounds": 2, "tool_calls": [{ "round": 0, "id": "call_…", "name": "web_search", "status": "done", "summary": "web 8 条", "ms": 1320 }] }
}
```

数值为示例。

| 字段 | 说明 |
|---|---|
| `id` | 第一轮的 id |
| `mcp_list_tools` | `tools[]` 含 `name`、`description`、`input_schema` 与 `annotations`。`input` 中已有 `server_label` 为 `scirouter` 的 `mcp_list_tools` 时不再列出 |
| `mcp_call.id` | 模型给出的调用 id（`call_…`） |
| `mcp_call.status` | `completed` 或 `failed`。`failed` 时 `output` 为 null，`error` 见下表 |
| `usage` | 各轮之和 |
| `tools` / `tool_choice` | 与请求原文相同 |
| `scirouter` | 与对话补全的扩展字段相同 |

| `error.type` | 情况 | 内容 |
|---|---|---|
| `mcp_tool_execution_error` | 工具已执行但未成功、余额不足 | `content[0].text` 为原因 |
| `mcp_protocol_error` | 入参不合法、工具不存在 | `code` 为 `-32602`，`message` 为原因 |
| `mcp_protocol_error` | 被限频 | `code` 为 `-32000`，`message` 为原因 |

- 状态为 `skipped` 的托管调用不出现在 `output` 中。
- 继续对话时，将 `output` 原样放入下一次请求的 `input`。平台把 `mcp_call` 还原成 `function_call` 与 `function_call_output`，去掉 `mcp_list_tools`。

### 日志与缓存

- 控制台中每轮记录一条请求日志，标签为 `v1_tool_round`；请求的 `metadata` 已有 8 个键时不添加该标签。
- 带托管工具的请求不进入响应缓存（结果随网页内容变化），`Idempotency-Key` 照常生效。

## 流式响应

### 对话补全的流式响应

- 中间轮的正文照常流出；托管工具的 `tool_calls` 增量不下发。
- 每次工具开始与结束各发送一个 `choices: []` 的分片，`scirouter.tool` 为该次工具调用：开始分片的 `status` 为空、`ms` 为 0，结束分片带 `status` / `summary` / `ms`。该机制与[可观测性](https://www.scirouter.cn/docs/observability)中的扩展分片相同，官方 SDK 会跳过这些分片。
- `[DONE]` 之前的收尾分片带各轮合计；末尾的 usage 分片同样为各轮之和。
- 扩展分片沿用该流第一个上游分片的 `id` / `created` / `model`。

```text
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"tool":{"round":0,"id":"call_…","name":"web_search","status":"","ms":0}}}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"tool":{"round":0,"id":"call_…","name":"web_search","status":"done","summary":"web 8 条","ms":1320}}}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"近一年…"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"request_id":"req_…","tool_rounds":3,"cost_quota":23180,"tool_calls":[…]}}
data: [DONE]
```

### Messages 的流式响应

- 整个循环是一条 message：`message_start` 只发送一次，内容块的 `index` 按下发顺序连续编号。
- 托管工具调用以 `mcp_tool_use` 块下发：`content_block_start`（`input` 为 `{}`）→ `input_json_delta` → `content_block_stop`。每次执行完发送一个 `mcp_tool_result` 块，`content_block_start` 带完整内容。一轮有多个托管调用时，先是各 `mcp_tool_use`，再是各 `mcp_tool_result`，顺序相同。
- 只有最后一轮发送 `message_delta` 与 `message_stop`：`message_delta` 的 `usage` 为各轮之和并带 `iterations`，`message_stop` 带 `scirouter` 字段。

### Responses 的流式响应

- `response.created` 与 `response.in_progress` 只发送一次。随后是 `mcp_list_tools` 项：`response.output_item.added` → `response.mcp_list_tools.in_progress` → `response.mcp_list_tools.completed` → `response.output_item.done`。
- 每次托管调用：`response.output_item.added`（`mcp_call`）→ `response.mcp_call_arguments.delta` → `response.mcp_call_arguments.done` → `response.mcp_call.in_progress` → `response.mcp_call.completed` 或 `response.mcp_call.failed` → `response.output_item.done`（带结果）。
- `output_index` 按下发顺序编号，`sequence_number` 全程递增。
- 只有最后一轮发送终态事件（`response.completed` / `response.incomplete`）：`response.output` 为下发过的全部输出项，`id` 为第一轮的，`usage` 为各轮之和，事件顶层带 `scirouter` 字段。

## 计费

- 每一轮按普通模型调用计费，`cost_quota` 为各轮之和。
- `web_search`、`fetch_page` 读到的 PDF 与 `run_code` 另按平台服务计费，由调用所用 Key 的账户承担，计入该 Key 的月度上限，不计入 `cost_quota`。计费方式见 [MCP 服务端与工具](https://www.scirouter.cn/docs/mcp)「计费」。
- 控制花费：调小 `max_tool_rounds`；用 `allowed_tools`（Messages 用 `configs`）只开启需要的工具。

## 相关

- 工具清单、限额、数据流向与单价：[MCP 服务端与工具](https://www.scirouter.cn/docs/mcp)
- `scirouter` 扩展字段与流式扩展分片：[可观测性](https://www.scirouter.cn/docs/observability)
- 三种接口：[对话补全](https://www.scirouter.cn/docs/chat-completions)、[Messages](https://www.scirouter.cn/docs/messages)、[Responses](https://www.scirouter.cn/docs/responses)
