# 对话补全

> POST /v1/chat/completions 的字段处理、流式响应、思考过程、工具调用、超时与中途断开的计费。

来源：https://www.scirouter.cn/docs/chat-completions · 更新于 2026-10-24

```http
POST https://api.scirouter.cn/v1/chat/completions
```

请求与响应格式与 OpenAI Chat Completions 一致。字段语义以 OpenAI 文档为准，下文只列平台的处理方式。

## 请求

```json
{
  "model": "YOUR_MODEL_ID",
  "messages": [
    { "role": "system", "content": "你是一名结构生物学助手。" },
    { "role": "user", "content": "帮我判断这段序列的二级结构倾向：MKTAYIAKQR…" }
  ],
  "max_tokens": 1024,
  "temperature": 0.3,
  "stream": false
}
```

| 字段 | 必填 | 平台的处理 |
|---|---|---|
| `model` | 是 | 模型 id，见[模型列表](https://www.scirouter.cn/docs/models)。模型不可调用时的错误见[错误](#错误) |
| `messages` | 是 | 不能为空数组，原样转给上游。助手消息上可带上一轮响应中的 `thinking_blocks`，平台原样接收，见[带签名的思考块](#带签名的思考块) |
| `max_tokens` | 否 | 正整数。超限、未填写与上下文窗口的处理见 [max_tokens](#maxtokens) |
| `max_completion_tokens` | 否 | `max_tokens` 的新名称。两者都提供时，平台的校验与预扣以 `max_tokens` 为准，两个字段均原样转给上游；超限报错时 `param` 为 `max_tokens` |
| `temperature` / `top_p` | 否 | 按模型的请求规则处理：超出范围的值夹到范围内，模型不接受采样参数时去掉，均在响应头 `X-SciRouter-Param-Actions` 中留痕；其他情况原样转给上游 |
| `stop` | 否 | 字符串或字符串数组，其他类型返回 400 |
| `stream` | 否 | 见[流式响应](#流式响应) |
| `stream_options.include_usage` | 否 | 流式时末尾多一个带 `usage` 的分片（`choices` 通常为空）。未设置或为 `false` 时平台去掉该分片 |
| `tools` / `tool_choice` | 否 | 见[工具调用](#工具调用)。`type: "mcp"`、`server_label: "scirouter"` 的项是平台托管工具，由平台执行，见 [MCP 与托管工具](https://www.scirouter.cn/docs/mcp) |
| `metadata` / `user` | 否 | 调用方的归因标签，不转给上游，见[可观测性](https://www.scirouter.cn/docs/observability) |
| `scirouter` | 否 | 顶层扩展字段，只接受 `max_tool_rounds`（托管工具的轮数，1–30），其他键返回 400。不转给上游 |
| 其他字段 | 否 | 按[参数如何被处理](https://www.scirouter.cn/docs/params)的登记表分类处理 |

其他字段的处理：

| 类别 | 示例 | 处理 |
|---|---|---|
| 提示类 | `seed`、惩罚项、`reasoning_effort` 等 | 无法带到线路上时去掉并留痕 |
| 契约类 | `response_format`、`n`、`logprobs` 等 | 依次尝试翻译、模拟、换线路，均不可行时返回 400 `unsupported_param` |
| 计价类 | `web_search_options` | 返回 400 |
| 不在登记表中的专有参数 | — | 走 OpenAI 兼容入口时原样带上，走其他入口时去掉并留痕，见[多厂商差异](https://www.scirouter.cn/docs/vendors) |

请求体上限默认为 4 MB，超过时返回 413 `payload_too_large`。

### max_tokens

| 情况 | 平台的处理 |
|---|---|
| 超过模型或套餐的单次上限 | 返回 400，`details.limit` 为上限。平台不自动截短 |
| 未填写，套餐有单次上限 | 平台填入模型与套餐上限中较小的值，发给上游 |
| 未填写，套餐无单次上限 | 使用上游的默认值 |
| 输入估算本身超过套餐的每分钟 token 数 | 400 `validation_failed`，`details.reason` 为 `plan_tpm_exceeded`，`message` 写明输入估算与套餐每分钟上限，见[错误码](https://www.scirouter.cn/docs/errors)。`max_tokens` 不参与这一判定：每分钟 token 数只按输出预留计入，见[错误码](https://www.scirouter.cn/docs/errors)「HTTP 429 的来源」 |
| 输入加 `max_tokens` 超过模型的上下文窗口 | 400 `context_window_exceeded`，带 `details.limit` / `details.estimated`，见[参数如何被处理](https://www.scirouter.cn/docs/params)「上下文窗口」 |

预扣按「输入估算 + 输出上限」计算，未填写时按上表确定的上限；账户的充值余额大于 0 时，输出只按输出上限与平台设定值中的较小者估算。结束后按实际用量结算，实际超出的部分照扣，见[认证与 API Key](https://www.scirouter.cn/docs/authentication)「预扣」。

## 非流式响应

```json
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "created": 1758585600,
  "model": "YOUR_MODEL_ID",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "这段序列……" },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 42, "completion_tokens": 318, "total_tokens": 360 },
  "scirouter": { "request_id": "req_…", "attempts": 1, "cost_quota": 18240, "…": "…" }
}
```

数值为示例。

- `scirouter` 字段与 `X-SciRouter-*` 响应头见[可观测性](https://www.scirouter.cn/docs/observability)。
- 单次调用扣除的配额以 `scirouter.cost_quota` 为准，不根据 `usage` 自行计算。
- `finish_reason` 为 `length` 表示回答被 `max_tokens` 截断。结构化数据或代码被截断时解析可能不报错，内容仍不完整；批处理按 `finish_reason` 判断，不按内容长度判断。

## 流式响应

请求带 `"stream": true` 时，响应为 `text/event-stream`：

```text
: ping

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""}}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"这段序列"}}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":42,"completion_tokens":318,"total_tokens":360}}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"request_id":"req_…","attempts":1,"cost_quota":18240}}

data: [DONE]
```

读取流时处理以下四点：

1. 以 `:` 开头的行是注释，跳过。排队或上游长时间无输出时，网关约每 10 秒发送一行 `: ping` 心跳。官方 SDK 自动跳过。
2. `choices` 可能为空数组。末尾的用量分片（`include_usage` 为 `true` 时才有）、`scirouter` 扩展分片与托管工具的进度分片均如此，直接读取 `choices[0]` 会越界。
3. **收到 `data: [DONE]` 才表示正常结束。** 出错时流中出现一个 `{"error": {...}}` 分片，随后断开，不发送 `[DONE]`。未收到 `[DONE]` 即结束的流按失败处理，错误分片的格式见[错误码](https://www.scirouter.cn/docs/errors)。
4. 错误分片可能出现在第一个内容分片之前。心跳发出后 HTTP 状态码已是 200，此后的错误（如排队超时）只能以错误分片返回。只检查状态码会将这些错误当作成功。

故障转移只发生在第一个字节之前。上游开始输出后，中途失败不换线路重试，返回错误分片。

## 思考过程

推理模型的思考过程同时写在 `reasoning_content` 与 `reasoning` 两个字段中，内容相同，读取任一字段即可：非流式在 `choices[].message` 上，流式在 `choices[].delta` 上。

思考消耗的 token 在 `usage.completion_tokens_details.reasoning_tokens` 中（上游返回时才有），已包含在 `completion_tokens` 内。

### 带签名的思考块

部分模型对思考过程签名。多轮工具调用时，须将上一轮的思考块连同签名一起带回，模型才能接续；只带回 `reasoning_content` 的文本会丢失签名。

这类模型的助手消息上另有扩展字段 `thinking_blocks`：非流式在 `choices[].message` 上，流式在 `choices[].delta` 上，签名在最后一个分片中。数组中每项为以下两种之一：

```json
[
  { "type": "thinking", "thinking": "…", "signature": "…" },
  { "type": "redacted_thinking", "data": "…" }
]
```

- 下一轮将助手消息原样放回 `messages`，包括 `thinking_blocks`，内容不做修改。平台原样接收，并优先将请求发回签发该签名的线路。
- 不签名的模型没有该字段。
- 字段名为上述 `thinking` / `signature` 与 `redacted_thinking` / `data`，已按此格式实现的客户端无需修改。

### 签名被拒时的重试

请求落到其他线路时，签名可能无效并被厂商拒绝。线路走 Anthropic Messages 入口时，平台不返回该 400，在同一条线路上重试一次：

| 情况 | 重试方式 | 响应扩展块 `param_actions` 的 `reshaped` |
|---|---|---|
| 厂商判定这些思考块属于其他对话（此前的历史有改动），且该线路支持 | 请厂商只丢掉对不上的块及其后的思考块，其余照常使用。此后发往该线路的请求均如此发送 | 厂商给出的原因，如 `thinking(prefix_binding_mismatch)` |
| 其他情况 | 去掉请求历史中的思考块后重试 | `thinking(signature_rejected)` |

- 第一种方式重试后仍因签名被拒，或线路不支持该选项时，按第二种方式再重试一次；线路不支持的，此后不再按第一种方式发送。
- 第二种方式下，平台记录该线路拒绝过的思考块。此后发往该线路的请求，去掉历史中最后一条带有被拒块的消息及其之前的思考块，之后新增的思考块照常带上。
- 重试仍失败时按普通失败处理。

线路走其他入口时，厂商的 400 照旧原样交回。Anthropic 格式（`/v1/messages`）的调用不做上述重试：签名被拒的 400 照旧原样交回，由客户端去掉思考块后重发，见 [Messages](https://www.scirouter.cn/docs/messages)。

## 工具调用

`tools` 与 `tool_choice` 按 OpenAI 格式编写，模型返回的 `tool_calls` 原样交给调用方。需要平台代为执行工具（如搜索网页、读取文献）时，使用托管工具，见 [MCP 与托管工具](https://www.scirouter.cn/docs/mcp)。

模型未声明工具调用能力（`GET /models` 的 `features`）时，带 `tools` 的请求在选择线路前返回 400 `unsupported_param`，`details.cause` 为 `feature_missing`。

### 线路筛选

选择线路时按以下条件过滤：

| 请求条件 | 跳过的线路 |
|---|---|
| 带 `tools`（模型已声明工具调用能力） | 不支持工具调用的线路 |
| 流式 | 不支持流式的线路 |
| 预计 token 数（输入 + `max_tokens`）超过线路的单次上限 | 该线路 |

没有满足条件的线路时返回 503 `model_unavailable`，`details.reason` 指明缺少的条件：

| `details.reason` | 含义 |
|---|---|
| `tools_unsupported` | 没有支持工具调用的线路 |
| `streaming_unsupported` | 没有支持流式的线路 |
| `input_too_large` | 预计 token 数超过线路的单次上限，常见于 `max_tokens` 过大，先调小 |
| `no_capable_endpoint` | 同时缺少以上多项 |

以上几种不带 `Retry-After`，原样重试无效，去掉对应参数或更换模型。带 `Retry-After` 的 503（`endpoints_busy` / `model_busy`）表示暂时繁忙。见[错误码](https://www.scirouter.cn/docs/errors)「HTTP 503 的 details.reason」。

## 超时

| 情况 | 结果 |
|---|---|
| 流式：上游开始响应后连续约 60 秒无任何数据（各线路可单独配置） | 504 `upstream_timeout`，`details.timeout` 为 `idle`。发生在第一个内容分片之前时换线路重试，之后返回错误分片 |
| 连接超时（默认约 10 秒，按线路） | 504 `upstream_timeout`，`details.timeout` 为 `connect` |
| 总时长上限（默认约 10 分钟，按线路） | 504 `upstream_timeout`，`details.timeout` 为 `total` |
| 上游自身报告超时 | 504，`details.timeout` 为 `upstream` |
| 上游的流未正常结束即断开 | 502 `upstream_error`，`details.provider_error.code` 为 `stream_interrupted` |
| 非流式：整个响应超过约 270 秒 | 504 `upstream_timeout`，`details.timeout` 为 `response`，不计费 |

**非流式请求最长等待约 270 秒。** 非流式请求须等完整生成后才有第一个字节，平台不按开始响应的时间判定，最多等待整个响应约 270 秒。该时长短于接入层（CDN）等待源站应答的 300 秒，超时时返回 JSON 错误体，不出现 CDN 的 524。

回答可能超过数分钟（推理模型、长输出）时使用流式。流式约每 10 秒发送一行心跳，不受 270 秒限制，受上表的空闲超时与总时长上限约束。

## 中途断开与计费

| 情况 | 计费 |
|---|---|
| 非流式请求失败 | 不计费 |
| 流式请求输出一部分后失败 | 按已产生的用量计费：上游返回用量时按上游用量，未返回时估算（输入按估算，输出按已收到的正文、思考过程与工具调用参数） |
| 调用方主动断开，流式 | 网关同时取消上游，结果不保存；按断开前已产生的部分计费，口径同上 |
| 调用方主动断开，非流式 | 网关同时取消上游，结果不保存；无法获得用量，不计费 |

**调用方主动断开后结果不保存，带 `Idempotency-Key` 重试时会重新调用并照常计费**，见[缓存与幂等](https://www.scirouter.cn/docs/caching)。

## 错误

| 状态码 | code | 触发条件 |
|---|---|---|
| 404 | `model_not_found` | 模型不存在 |
| 503 | `model_unavailable` | 模型维护中、已停用或尚未定价；无满足条件的线路见[线路筛选](#线路筛选) |
| 403 | `model_not_allowed` | 模型不在 Key、项目或套餐的白名单内，`details.scope` 为 `key` / `project` / `plan` |
| 403 | `model_restricted` | 模型只对获授权的团队开放 |
| 400 | `context_window_exceeded` | 输入加 `max_tokens` 超过模型的上下文窗口 |
| 400 | `unsupported_param` | 契约类参数无法处理，或模型未声明工具调用能力时带 `tools` |
| 413 | `payload_too_large` | 请求体超过上限（默认 4 MB） |

所有错误码与重试策略见[错误码](https://www.scirouter.cn/docs/errors)。

## 相关文档

- 错误码与可重试的错误：[错误码](https://www.scirouter.cn/docs/errors)
- 重复请求命中缓存、重试不重复扣费：[缓存与幂等](https://www.scirouter.cn/docs/caching)
- 单次调用路由到的线路与耗时：[可观测性](https://www.scirouter.cn/docs/observability)
