# 跨 Provider 映射表 本文件把三家的字段与语义放在一起对照,标注**哪些能无损映射、哪些必然有损**。数据来源见 [openai.md](./openai.md)、[anthropic.md](./anthropic.md)、[deepseek.md](./deepseek.md) 各自的来源列表。 --- ## 1. 端点与鉴权 | | OpenAI Chat | OpenAI Responses | Anthropic Messages | DeepSeek | | --- | --- | --- | --- | --- | | 路径 | `/v1/chat/completions` | `/v1/responses` | `/v1/messages` | `/chat/completions` | | 鉴权 | `Authorization: Bearer` | 同左 | **`x-api-key`** | `Authorization: Bearer` | | 版本 header | 无 | 无 | **`anthropic-version: 2023-06-01` 必需** | 无 | --- ## 2. 请求参数 | 语义 | OpenAI Chat | OpenAI Responses | Anthropic | DeepSeek | | --- | --- | --- | --- | --- | | 消息列表 | `messages` | `input` | `messages` | `messages` | | 系统提示 | `messages[role=system\|developer]`(可多条、可在任意位置) | `instructions`(顶层) | **`system`(顶层,只有一条)** | `messages[role=system]` | | 输出上限 | `max_completion_tokens`(`max_tokens` 已废弃) | `max_output_tokens` | **`max_tokens`(必填)** | `max_tokens` | | 温度 | `temperature` 0~2 | 同左 | **`temperature` 0~1** | `temperature` 0~2 | | top-p | `top_p` | `top_p` | `top_p` | `top_p` | | top-k | ✗ | ✗ | `top_k` | ✗ | | 停止序列 | `stop`(≤4) | — | `stop_sequences` | `stop`(≤16) | | 频率惩罚 | `frequency_penalty` | — | ✗ | **已废弃** | | 存在惩罚 | `presence_penalty` | — | ✗ | **已废弃** | | 多候选 | `n`(1~128) | ✗ | ✗ | ✗ | | JSON 模式 | `response_format` | `text.format` | `output_config.format` | `response_format`(仅 `json_object`) | | 工具 | `tools[].function.parameters` | `tools[].parameters` | **`tools[].input_schema`** | 同 OpenAI Chat | | 工具选择 | `tool_choice`: `auto`/`required`/`none`/具体 | 同左 | `tool_choice`: `auto`/**`any`**/`none`/`tool` | 同 OpenAI Chat | | 禁并行工具 | `parallel_tool_calls: false` | 同左 | `tool_choice.disable_parallel_tool_use: true` | `parallel_tool_calls` | | 推理开关 | `reasoning_effort` | `reasoning.{effort,summary}` | `thinking` + `output_config.effort` | `thinking.{type,reasoning_effort}` | | 缓存控制 | `prompt_cache_key` / `prompt_cache_options.ttl` | 同左 | `cache_control` 断点 | **无(全自动)** | | 用户标识 | `safety_identifier`(`user` 已废弃) | 同左 | `metadata.user_id` | `user_id`(≤512,限字符集) | ### 必然有损的转换 - **多条 system 消息 → Anthropic**:只能合并。Anthropic 官方兼容层的做法是把所有 system/developer 消息用**单个换行 `\n`** 连接成一条,放在最前。 - **`n > 1` → 任何非 OpenAI 上游**:无法实现,只能拒绝或退化为 `n=1`。 - **`frequency_penalty` / `presence_penalty` / `logit_bias` / `seed` → Anthropic 或 DeepSeek**:无对应,只能忽略。 - **`temperature` OpenAI(0~2) → Anthropic(0~1)**:需 clamp。官方兼容层的选择是 **>1 一律截为 1**(而不是线性缩放)。 - **`top_k` → OpenAI**:无对应,只能丢弃。 --- ## 3. 消息与内容块 | 语义 | OpenAI Chat | Anthropic | | --- | --- | --- | | 纯文本 | `content: "..."` 或 `[{type:"text", text}]` | `content: "..."` 或 `[{type:"text", text}]` | | 图片 | `{type:"image_url", image_url:{url, detail}}` | `{type:"image", source:{type:"base64"\|"url", media_type, data\|url}}` | | 图片 detail | `detail: low/high/auto` | **无对应,丢弃** | | PDF/文档 | `{type:"file"}` | `{type:"document", source:{...}, citations}` | | 音频输入 | `{type:"input_audio"}` | **不支持** | | 助手工具调用 | `assistant.tool_calls[]` | `content` 里的 `tool_use` block | | 工具结果 | **独立 `{role:"tool", tool_call_id, content}` 消息** | **user 消息 content 里的 `tool_result` block** | | 工具错误 | 靠 content 文本表达 | `tool_result.is_error: true` | | 推理内容 | `reasoning` item(Responses)| `thinking` / `redacted_thinking` block | | 引用 | `annotations` | `citations`(5 种 location 类型) | ### 工具 ID 三套命名 | | 调用侧 | 结果侧 | | --- | --- | --- | | OpenAI Chat | `tool_calls[].id` | `tool.tool_call_id` | | OpenAI Responses | `function_call.call_id` | `function_call_output.call_id` | | Anthropic | `tool_use.id`(`toolu_` 前缀) | `tool_result.tool_use_id` | 中转站需要维护 ID 映射表,不能假设上游会接受下游生成的 ID 格式。 --- ## 4. 停止原因 | 语义 | OpenAI `finish_reason` | Anthropic `stop_reason` | DeepSeek `finish_reason` | | --- | --- | --- | --- | | 自然结束 | `stop` | `end_turn` | `stop` | | 达到上限 | `length` | `max_tokens` | `length` | | 命中停止序列 | `stop` | `stop_sequence` | `stop` | | 调用工具 | `tool_calls` | `tool_use` | `tool_calls` | | 内容过滤 | `content_filter` | `refusal`(+ `stop_details.category`) | `content_filter` | | 上下文溢出 | 无(表现为 400) | `model_context_window_exceeded` | 无 | | 服务端工具暂停 | 无 | **`pause_turn`** | 无 | | 资源不足 | 无 | 无 | **`insufficient_system_resource`** | **注意**:`stop` 与 `stop_sequence` 在 OpenAI 侧合并成一个值,Anthropic → OpenAI 方向可无损降级;反向则丢失信息。`pause_turn` 在 OpenAI 格式下**无法表达**,中转站必须在网关内部自行完成续跑循环,不能透传给客户端。 --- ## 5. Usage 统计 | 语义 | OpenAI | Anthropic | DeepSeek | | --- | --- | --- | --- | | 输入 token | `usage.prompt_tokens` | `usage.input_tokens` **(仅未缓存部分)** | `usage.prompt_tokens`(总数) | | 输出 token | `usage.completion_tokens` | `usage.output_tokens` | `usage.completion_tokens` | | 总计 | `usage.total_tokens` | 需自行相加 | `usage.total_tokens` | | 缓存命中 | `prompt_tokens_details.cached_tokens` | `cache_read_input_tokens` | `prompt_cache_hit_tokens` | | 缓存写入 | 无单独计数 | `cache_creation_input_tokens` | 无(`prompt_cache_miss_tokens` 是未命中数) | | 推理 token | `completion_tokens_details.reasoning_tokens` | `output_tokens_details.thinking_tokens` | `completion_tokens_details.reasoning_tokens` | **关键陷阱**:Anthropic → OpenAI 时, ``` prompt_tokens = input_tokens + cache_read_input_tokens + cache_creation_input_tokens ``` 直接用 `input_tokens` 会严重低报(缓存命中时可能低报 99%)。 --- ## 6. 流式协议映射 | | OpenAI | Anthropic | | --- | --- | --- | | 结构 | 无状态 chunk 序列 | **有状态**的 block 事件机 | | 事件名 | 无(都是匿名 `data:`) | 有(`event: message_start` 等) | | 文本增量 | `choices[0].delta.content` | `content_block_delta` + `text_delta` | | 工具增量 | `delta.tool_calls[].function.arguments`(按 `index` 累加) | `content_block_delta` + `input_json_delta.partial_json` | | 推理增量 | Responses 有专门事件 | `thinking_delta` + `signature_delta` | | usage | 最后一个 chunk(需 `include_usage`),`choices` 为空数组 | `message_delta.usage`,**累计值** | | 结束标记 | `data: [DONE]` | `message_stop` | | 保活 | 无标准(实践中发注释行) | `ping` 事件 | | 中途错误 | 无标准 | `error` 事件(200 之后仍可能发) | ### Anthropic → OpenAI 流式转换要点 1. `message_start` → 发一个只含 `role: "assistant"` 的首 chunk。 2. `content_block_start(text)` → 无对应,吞掉。 3. `text_delta` → `delta.content`。 4. `content_block_start(tool_use)` → 发一个带 `id`/`name`、`arguments: ""` 的 `tool_calls[index]` chunk。 5. `input_json_delta` → `tool_calls[index].function.arguments` 追加。 6. `thinking_delta` → 无标准落点;常见做法是映射到 `delta.reasoning_content`(DeepSeek 风格,事实标准)。**`signature_delta` 无处可放,会丢失**——这意味着经过 OpenAI 格式往返后的对话**无法再发回 Anthropic 上游**。 7. `message_delta.stop_reason` → 最后一个 chunk 的 `finish_reason`。 8. `message_stop` → `data: [DONE]`。 9. `ping` → 丢弃或转成 SSE 注释行保活。 ### OpenAI → Anthropic 流式转换要点 必须自行维护 block index 状态机:文本与工具调用要分配不同的 `index`,并在切换时补发 `content_block_stop` / `content_block_start`。OpenAI 的 chunk 里没有"块结束"信号,只能靠 `tool_calls[].index` 变化和 `finish_reason` 推断。 --- ## 7. 推理内容三方对照(最不可互通的部分) | | OpenAI Responses | Anthropic | DeepSeek | | --- | --- | --- | --- | | 载体 | `reasoning` item | `thinking` / `redacted_thinking` block | `reasoning_content` 字符串 | | 可见文本是什么 | `summary`(需显式开启) | **摘要,永远不是原始 CoT** | 思考内容字符串 | | 真实推理载体 | `encrypted_content` | **`signature`(完整思考的密文)** | 无,就是明文字段 | | 密文是否必需 | ZDR / `store:false` 时必需 | **始终存在**,服务端解密它重建思考 | 不适用 | | 可否修改 | 加密内容需原样回传 | **不可修改,改了直接 400** | 可自由处理 | | 多轮是否必回传 | 有函数调用时必须 | **工具轮次内必须**;其他情况可省略但不可改 | 无工具调用时**不必**;有工具调用时**必须** | | 关闭方式 | `reasoning.effort: "none"` / `"minimal"` | 部分模型**根本无法关闭** | `thinking.type: "disabled"` | | 跨模型可移植 | — | **否**,绑定生成模型,换模型必须剥离 | 是 | | 跨平台可移植 | — | **是**,API/Bedrock/Vertex 三方通用 | — | ### 为什么 Anthropic 侧无法靠字段翻译打通 Anthropic 的 `thinking` 文本字段是**摘要**,真正的推理内容加密在 `signature` 里,服务端回传时**解密 `signature` 来重建思考,并不读你传的文本**(详见 [anthropic.md](./anthropic.md) §5)。 所以这条常见的转换路径是**死路**: ``` Anthropic thinking block → reasoning_content 字符串 → 存下来 → 转回 thinking block ``` 转回去的东西只有摘要文本、没有 signature,Anthropic 侧要么 400,要么丢失全部推理连续性。 ### 可行做法 **把 signature / encrypted_content / redacted_thinking.data 当成 opaque blob 原样搬运。** 三家的密文字段都明确声明为不可解析,中转站不需要理解它们: 1. 网关按会话 ID 存储上游返回的**原始 content block 数组**(含密文字段)。 2. 下游客户端只看到脱敏后的摘要文本(映射到 `reasoning_content` 或 Responses 的 reasoning item)。 3. 下一轮请求时,用存储的原始块重建上游请求,而不是从下游回传的文本反向构造。 需要额外注意的两点: - **模型路由会破坏它**:Anthropic thinking block 绑定生成它的模型,换模型时必须剥离。其他模型**静默忽略而不报错,但照样计输入 token**——做故障转移时不清理,用户会为无用内容付费且毫无提示。 - **signature 很长**(Claude 4+ 显著变长)。存储层的列宽、日志截断、header 大小限制都可能悄悄截断它,而截断后的 signature 会在下一轮触发 400。