11 KiB
跨 Provider 映射表
本文件把三家的字段与语义放在一起对照,标注哪些能无损映射、哪些必然有损。数据来源见 openai.md、anthropic.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:无对应,只能忽略。temperatureOpenAI(02) → Anthropic(01):需 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 流式转换要点
message_start→ 发一个只含role: "assistant"的首 chunk。content_block_start(text)→ 无对应,吞掉。text_delta→delta.content。content_block_start(tool_use)→ 发一个带id/name、arguments: ""的tool_calls[index]chunk。input_json_delta→tool_calls[index].function.arguments追加。thinking_delta→ 无标准落点;常见做法是映射到delta.reasoning_content(DeepSeek 风格,事实标准)。signature_delta无处可放,会丢失——这意味着经过 OpenAI 格式往返后的对话无法再发回 Anthropic 上游。message_delta.stop_reason→ 最后一个 chunk 的finish_reason。message_stop→data: [DONE]。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 §5)。
所以这条常见的转换路径是死路:
Anthropic thinking block → reasoning_content 字符串 → 存下来 → 转回 thinking block
转回去的东西只有摘要文本、没有 signature,Anthropic 侧要么 400,要么丢失全部推理连续性。
可行做法
把 signature / encrypted_content / redacted_thinking.data 当成 opaque blob 原样搬运。 三家的密文字段都明确声明为不可解析,中转站不需要理解它们:
- 网关按会话 ID 存储上游返回的原始 content block 数组(含密文字段)。
- 下游客户端只看到脱敏后的摘要文本(映射到
reasoning_content或 Responses 的 reasoning item)。 - 下一轮请求时,用存储的原始块重建上游请求,而不是从下游回传的文本反向构造。
需要额外注意的两点:
- 模型路由会破坏它:Anthropic thinking block 绑定生成它的模型,换模型时必须剥离。其他模型静默忽略而不报错,但照样计输入 token——做故障转移时不清理,用户会为无用内容付费且毫无提示。
- signature 很长(Claude 4+ 显著变长)。存储层的列宽、日志截断、header 大小限制都可能悄悄截断它,而截断后的 signature 会在下一轮触发 400。