# 协议兼容性与限制

来源: https://platform.lyrouter.com/ai_gateway/docs/compatibility
更新日期: 2026-10-22

LYRouter 提供 Chat Completions、Anthropic Messages、Responses 三种对话接口，而每个服务商通道只使用一种上游协议：OpenAI 兼容或 Anthropic。客户端接口与通道协议不同时，网关自动转换请求、响应、流式事件和错误格式；转换只保留双方都有对应含义的字段，其余字段会被去掉或改写，且不会写进响应头。Responses 请求不做协议转换，只能由提供 Responses 接口的通道服务。依赖特定参数、缓存标记或 `usage` 字段的应用，请按本页确认实际组合下的行为。

## 客户端接口 × 通道协议

| 客户端调用的接口 | OpenAI 兼容通道 | Anthropic 协议通道 |
| --- | --- | --- |
| Chat Completions（`/v1/chat/completions`） | 按原格式发送，网关只调整推理参数，删除缓存标记和历史消息中的 `reasoning_content`（DeepSeek 系列保留当前轮 assistant 消息上的 `reasoning_content`） | 转换为 Messages 请求；响应和流式事件转换回 Chat 格式 |
| Anthropic Messages（`/v1/messages`） | 转换为 Chat Completions 请求；响应和流式事件转换回 Anthropic 格式 | 先转换为内部的 Chat 结构，再转换回 Messages 发送；平台开启原生直通时可以原样发送，见下文 |
| Responses（`/v1/responses`） | 原样发送到通道的 Responses 接口；通道没有该接口时由上游返回错误 | 不可用。模型声明了接口范围且不含 Responses 时，网关直接返回 400 `This model does not support the responses API` |

- **通道由网关选择**：同一个模型可能同时有两种协议的通道，失败重试时也可能换到另一种协议的通道，所以同一个请求在不同时间可能经过不同的转换。要确认某次请求实际发出的格式，在 **日志分析** 的日志详情中查看「服务商」，以及「原始报文」中的「上游请求」。原始报文的正文只在工作空间开启全文日志后保存，失败的请求除外。
- **不需要转换的组合最保真**：Chat Completions 客户端走 OpenAI 兼容通道、Responses 客户端走提供 Responses 接口的通道时，请求基本按原样发送。对字段有严格要求的应用，可以请当前账号管理员在模型详情中只保留同协议的来源，见[应用接入多个服务商](https://platform.lyrouter.com/ai_gateway/docs/multi-provider)。
- **网关拒绝在先**：模型目录声明了接口或输入范围时，不符合的请求（如对话模型调用图像接口、给只收文本的模型发图片）在转换之前就返回 400，见[模型能力与兼容性](https://platform.lyrouter.com/ai_gateway/docs/capabilities#网关拒绝的请求)。

## Chat Completions → Anthropic 协议通道

| 请求字段 | 处理 |
| --- | --- |
| `role` 为 `system`、`developer` 的消息 | 合并为 Anthropic 的 `system`，多条之间以空行连接。**只接受字符串形式的 `content`**，写成内容数组的 system / developer 消息会被丢弃 |
| `user`、`assistant` 消息的 `content` | 字符串原样转换；内容数组中只转换 `text` 和 `image_url`（data URL 转为 base64 图片，http(s) 地址转为 URL 图片），`input_audio`、`file` 等其他类型丢弃 |
| `tool` 消息、`assistant.tool_calls` | 分别转为 `tool_result` 块和 `tool_use` 块；`arguments` 解析为 JSON 对象，解析失败时为空对象 |
| `assistant.reasoning_content` | 不转发 |
| `tools` | 转为 `name`、`description`、`input_schema`（取自 `parameters`） |
| `tool_choice` | `auto` → `auto`，`required` → `any`，指定函数 → `tool`；`none` 不转发，请求带 `tools` 时模型仍可能调用工具 |
| `max_tokens`、`max_completion_tokens` | 转为 `max_tokens`；两者都没有时补 4096 |
| `stop` | 转为 `stop_sequences`，字符串会变成单元素数组 |
| `metadata` | 只保留 `user_id`，其他键丢弃 |
| `enable_thinking`、`thinking_budget` | 转为 `thinking`：有预算时为 `{"type": "enabled", "budget_tokens": N}`，只开启没有预算时为 `{"type": "adaptive"}`，关闭时不发送 |
| `n`、`logprobs`、`top_logprobs`、`response_format`、`frequency_penalty`、`presence_penalty`、`seed`、`parallel_tool_calls`、`logit_bias`、`functions`、`function_call`、`reasoning_effort`、`stream_options` | 丢弃 |
| 其他字段（`temperature`、`top_p`、`top_k`、`stream`、`service_tier` 等） | 原样发送；Anthropic 不认识的字段由上游决定是否接受 |

响应转换回 Chat 格式：文本块合并为 `message.content`，`tool_use` 块转为 `tool_calls`，思考内容放在 `reasoning_content`；`stop_reason` 映射为 `finish_reason`（`end_turn`、`stop_sequence`、`pause_turn`、`refusal` → `stop`，`max_tokens` → `length`，`tool_use` → `tool_calls`）。因为 `n` 被丢弃，`choices` 始终只有一项。

**强制调用工具时关闭思考**：`tool_choice` 为 `required` 或指定函数、且请求没有写推理参数时，网关替你关闭思考，避免上游以「思考模式不支持强制工具调用」拒绝请求。这条规则对任何接口发往 Anthropic 协议通道或阿里云百炼等使用 `enable_thinking` 的通道都生效。

## Anthropic Messages → 通道

`/v1/messages` 请求先转换为内部的 Chat 结构，再按通道协议发出。经过这一步后：

| 请求内容 | 处理 |
| --- | --- |
| `system`（字符串或文本块数组） | 合并为一段文本，多个块以空行连接；多个块上的 `cache_control` 只保留最后一个 |
| 消息中的块 | 转换 `text`、`image`、`tool_use`、`tool_result`、`thinking`、`redacted_thinking`；`document` 等其他类型的块丢弃 |
| 同一条 user 消息中既有 `tool_result` 又有文本或图片 | 只保留 `tool_result`。需要附带的文本请放在下一条 user 消息中 |
| `tools` | 按 `name`、`description`、`input_schema` 转换。只有 `type` 和 `name` 的服务端工具（如 `web_search`）会变成同名的普通函数工具，由客户端自己执行 |
| `tool_choice` | `auto`、`any`、`tool` 转换；`none` 和 `disable_parallel_tool_use` 不转发 |
| `max_tokens`、`stop_sequences`、`stream`、`temperature`、`top_p`、`top_k`、`service_tier` | 保留 |
| `thinking` | `{"type": "disabled"}` 视为关闭，其他类型视为开启，`budget_tokens` 保留为思考预算；发往 Anthropic 协议通道时还原为带预算的 `enabled` 或不带预算的 `adaptive` |
| `metadata`、`context_management`、`output_config` | 发往 Anthropic 协议通道时还原（`metadata` 只保留 `user_id`）；发往 OpenAI 兼容通道时删除 |
| 其他顶层字段 | 不转发 |

发往 **OpenAI 兼容通道** 时，思考开关按服务商的写法传递：阿里云百炼等通道收到 `enable_thinking` / `thinking_budget`；OpenAI、DeepSeek 官方通道不支持这两个字段，会被删除；网关没有适配的服务商原样收到这两个字段，是否生效由上游决定。`output_config` 中的 effort 设置不会换算成 `reasoning_effort`。历史消息中的思考块和全部 `cache_control` 标记也会删除。

响应转换回 Anthropic 格式：

- `stop_reason` 只会是 `end_turn`、`max_tokens`、`tool_use` 三种，上游的 `stop_sequence`、`pause_turn`、`refusal` 都变为 `end_turn`，响应中不出现 `stop_sequence` 字段（流式与非流式相同）。这一点对 Anthropic 协议通道同样适用。
- OpenAI 兼容通道返回的思考内容（`reasoning_content`）在流式响应中转为 `thinking` 事件，不带签名；非流式响应中不包含思考内容。
- 回答只有思考内容（例如在思考阶段就被 `max_tokens` 截断）时，非流式响应的 `content` 目前是空对象 `{}`，而不是空数组 `[]`。按数组遍历 `content` 前请先判断类型。
- 发往 Anthropic 协议通道时，思考块的签名原样往返，可以在下一轮回传。历史消息中有不带签名的思考块（例如上一轮由 OpenAI 兼容通道回答）时，这一轮去掉全部历史思考块，并且不开启思考，避免上游因签名校验失败而拒绝。

## 推理参数

三个推理参数分属不同服务商，网关不在它们之间换算，只按选中的通道删掉对方不认识的字段。除 Anthropic 协议一行外，下表各行指 OpenAI 兼容通道：

| 通道 | `reasoning_effort` | `enable_thinking` | `thinking_budget` |
| --- | --- | --- | --- |
| OpenAI | 保留 | 删除 | 删除 |
| Anthropic 协议 | 删除 | 转为 `thinking` | 转为 `thinking` |
| 阿里云百炼（含通义千问） | 删除 | 保留 | 保留 |
| DeepSeek 官方 | 删除 | 删除 | 删除（思考由模型决定） |
| 其他服务商 | 原样发送 | 原样发送 | 原样发送 |

同一模型的不同通道可能属于不同行。需要稳定的推理行为时，请为每个通道验证，或用[虚拟模型](https://platform.lyrouter.com/ai_gateway/docs/presets)固定推理参数。

## 缓存标记 `cache_control`

| 组合 | 处理 |
| --- | --- |
| `/v1/messages` → Anthropic 协议通道 | 保留 `system`、文本块和工具定义上的标记；多个 `system` 块的标记合并为一个；打在图片块或 `tool_result` 块上的标记不转发 |
| `/v1/chat/completions` → Anthropic 协议通道 | 保留内容数组中文本、图片部分和 `tools` 中各工具上的标记；system 消息的标记写在消息对象上（与 `role`、`content` 同级），`content` 用字符串 |
| 任意接口 → OpenAI 兼容通道 | 全部删除，包括阿里云百炼等支持显式缓存的服务商。这些通道依靠上游的自动前缀缓存 |

- **1 小时缓存**：标记中写 `"ttl": "1h"` 时，网关把 `anthropic-beta` 请求头设为 `extended-cache-ttl-2025-04-11`，替换客户端在该请求头中的原有值。
- **虚拟模型的缓存设置**：虚拟模型开启了缓存且请求本身没有任何标记时，网关只在发往 Anthropic 协议通道时给系统提示词加标记；请求自带标记时以请求为准。
- 标记是否生效、命中了多少，在日志的「显式缓存创建」和「输入（缓存命中）」中查看，优化方法见[降低成本与优化缓存](https://platform.lyrouter.com/ai_gateway/docs/cost-practice)。

## `usage` 字段

响应中的 `usage` 在不同组合下统计方式不同：

| 客户端接口 ← 通道 | 响应中的 `usage` |
| --- | --- |
| Chat Completions ← OpenAI 兼容 | 上游原样返回：`prompt_tokens` 包含缓存命中部分，命中数在 `prompt_tokens_details.cached_tokens`（DeepSeek 为 `prompt_cache_hit_tokens`） |
| Chat Completions ← Anthropic 协议 | `prompt_tokens` 等于上游的 `input_tokens`，**不含**缓存命中和缓存写入；非流式响应另带 `cache_read_input_tokens`、`cache_creation_input_tokens`，流式最后一个 chunk 只有 `prompt_tokens`、`completion_tokens`、`total_tokens` |
| Anthropic Messages ← Anthropic 协议 | `input_tokens` 不含缓存，`cache_read_input_tokens`、`cache_creation_input_tokens` 只在大于 0 时出现 |
| Anthropic Messages ← OpenAI 兼容 | `input_tokens` = 上游 `prompt_tokens` − 缓存命中数，`cache_read_input_tokens` = 缓存命中数，`output_tokens` = 上游 `completion_tokens` |
| Responses | 上游原样返回：`input_tokens` 包含缓存命中部分，命中数在 `input_tokens_details.cached_tokens` |

- 流式 Chat Completions 中，网关总会请求 `usage`，最后一个 chunk 带用量；Responses 和 Anthropic Messages 按各自协议在结束事件中给出用量。
- **请求日志已统一**：日志中的「输入（缓存未命中）」「输入（缓存命中）」「输出」「显式缓存创建」四项按同一方式换算，互不重叠，不受协议组合影响。核对费用、统计命中率时以日志和用量分析为准，不要直接累加不同组合下的 `prompt_tokens`。

## 错误格式与流式结束标记

| 客户端接口 | 错误响应体 | 流式正常结束 | 流式输出开始后上游中断 |
| --- | --- | --- | --- |
| Chat Completions | `{"error": {"message", "type", "code"}}`，`code` 只在部分错误上出现 | `data: [DONE]` | 补一个 `data: {"error": {..., "code": "stream_interrupted"}}`，没有 `data: [DONE]` |
| Anthropic Messages | `{"type": "error", "error": {"type", "message"}}`，没有 `code` | `event: message_stop` | 补一个 `event: error`，没有 `message_stop` |
| Responses | 与 Chat Completions 相同；上游的错误原样转发 | `response.completed` 事件，没有 `[DONE]` | 补一个 `response.failed` 事件（`error.code` 为 `server_error`），没有 `response.completed` |

- `/v1/messages` 上的网关错误和上游错误都转换为 Anthropic 格式，`message` 原文不变，`error.type` 按状态码确定（如 401 → `authentication_error`，429 → `rate_limit_error`，503 → `overloaded_error`）。
- 连接上游失败或等待超时的 500 / 504 返回 HTML 错误页，不是 JSON，任何接口都一样，见[超时与流式中断](https://platform.lyrouter.com/ai_gateway/docs/stream-errors)。
- 对话接口在转换中丢弃的参数不写进 `X-Gateway-Unsupported-Parameters` 响应头；这个响应头和 `X-Gateway-Strict-Parameters` 请求头目前只用于图像、视频接口，见[网关扩展请求头与响应头](https://platform.lyrouter.com/ai_gateway/docs/gateway-headers)。

完整错误码见[错误码与请求标识](https://platform.lyrouter.com/ai_gateway/docs/errors)。

## Responses 的服务端状态

Responses 请求原样转发，`store`、`background`、`previous_response_id` 都由上游处理，响应对象保存在创建它的上游：

| 操作 | 网关的处理 | 限制 |
| --- | --- | --- |
| 带 `previous_response_id` 的新请求 | 用它把请求归入同一会话（日志的「会话」列），然后照常选择通道 | 不保证落到同一个上游。换了通道或凭证时，新的上游找不到上一轮响应，返回上游的错误 |

- **多轮对话**：「最低价」「负载均衡」策略会在 30 分钟内把同一 API Key 对同一模型的请求保持在同一通道，多数接续请求会落回原上游，但失败重试、策略切换或保持期过后仍可能换通道，见[通道选择与路由策略](https://platform.lyrouter.com/ai_gateway/docs/routing)。需要可靠的多轮对话时，在客户端保存历史，每轮把完整上下文放进 `input` 发送。
- **虚拟模型的参数**：通过 Responses 调用虚拟模型时，「推理参数」按 Chat Completions 的字段名合并进请求，例如 `max_tokens` 不会换成 `max_output_tokens`。上游不接受这些字段时会返回 400，供 Responses 客户端使用的虚拟模型请只设置两种接口同名的参数（如 `temperature`、`top_p`）。

## 原生直通

原生直通让 `/v1/messages` 请求在发往 Anthropic 协议通道时跳过上文的转换：请求体除 `model` 换成上游模型名外按客户端发来的内容发送，响应按上游返回的字节原样返回，计费和日志不受影响。平台可以只开启响应原样返回，也可以请求、响应都开启；**默认都关闭**，所有 `/v1/messages` 请求都经过转换。

需要确保请求未被改写的客户端，可以加请求头要求直通，做不到时网关拒绝请求，而不是悄悄转换：

```bash
curl https://api.lyrouter.com/v1/messages \
  -H "x-api-key: $LYROUTER_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "x-gateway-passthrough: require" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "max_tokens": 1024, "messages": [{"role": "user", "content": "你好"}]}'
```

| `x-gateway-passthrough` | 行为 |
| --- | --- |
| 不带，或 `prefer` | 能直通就直通，不能时自动走转换，请求照常完成 |
| `require`（不区分大小写） | 必须直通；不能直通时返回 400 ``native passthrough was required by `x-gateway-passthrough: require` but is not available for this request (<原因>)`` |

括号中的原因说明是哪一项条件不满足：

| 原因 | 含义 | 处理 |
| --- | --- | --- |
| `route_disabled` | 平台没有为该接口开启请求原样发送（默认状态） | 去掉这个请求头，接受转换 |
| `client_protocol` | 请求不是发往 `/v1/messages` 的 | 这个请求头只对 Anthropic Messages 有效 |
| `wire_protocol` | 选中的通道不是 Anthropic 协议 | 换用由 Anthropic 协议通道服务的模型 |
| `endpoint_type` | 不是对话请求 | 去掉这个请求头 |
| `channel_disabled` | 平台对这个通道关闭了直通 | 换用其他模型或去掉请求头 |
| `preset_injection` | 虚拟模型设置了系统提示词、推理参数、锁定参数或缓存，需要改写请求 | 直接调用模型 ID，或去掉虚拟模型中的这些设置 |
| `shape_oai` | 请求体含 OpenAI 专有字段（如 `response_format`、`n`），或消息角色不只是 `user` / `assistant`，或工具是 OpenAI 格式 | 改成纯 Anthropic 格式的请求 |
| `decision_error` | 网关判断时出错 | 稍后重试；持续出现时联系支持 |

直通只作用于请求实际发往的那个通道；失败重试换到 OpenAI 兼容通道时，带 `require` 的请求同样返回 400。

## 常见问题

### 设置了 `response_format`，为什么返回的不是 JSON？

**答：** 请求被路由到 Anthropic 协议通道时，`response_format` 在转换中被丢弃。在日志中确认这次请求的「服务商」；需要稳定的 JSON 输出时，在提示词中写明格式，或只保留 OpenAI 兼容的来源，见[模型能力与兼容性](https://platform.lyrouter.com/ai_gateway/docs/capabilities)。

### 用 Claude Code 调用 Claude 模型，请求会被改写吗？

**答：** 会经过一次转换，但思考块签名、缓存标记、`metadata`、`context_management`、`output_config` 都会还原。需要注意的是 `document` 块、与 `tool_result` 同在一条消息中的文本、服务端工具和 `stop_reason` 的细分值，见上文「Anthropic Messages → 通道」。缓存命中情况在日志的「输入（缓存命中）」中确认。

### 同一个应用同时用 OpenAI SDK 和 Anthropic SDK，用量怎么统一统计？

**答：** 不要直接汇总响应中的 `usage`，两种格式对缓存的计数方式不同。请以请求日志和 **用量分析** 为准，它们已把四项 Token 换算成同一方式，见[通过日志定位调用问题](https://platform.lyrouter.com/ai_gateway/docs/logs)和[用量归因与分析](https://platform.lyrouter.com/ai_gateway/docs/analytics)。
