消息位置
OpenAI Chat ↔ Anthropic Messages 消息位置
Section titled “OpenAI Chat ↔ Anthropic Messages 消息位置”OpenAI Chat Completions 和 Anthropic Messages 都把对话表示为有序 turn,但 system 指令、工具调用和工具结果所在的位置不同。转换代码应显式保留这些位置规则。
总体位置对照
Section titled “总体位置对照”| 概念 | OpenAI Chat Completions | Anthropic Messages |
|---|---|---|
| System 指令 | messages[] 中 role: "system" 的 item | 顶层 system 字段 |
| Developer 指令 | messages[] 中 role: "developer" 的 item | 没有专门 role;折叠进顶层 system |
| 用户内容 | messages[] 中 role: "user" 的 item | messages[] 中 role: "user" 的 item |
| Assistant 文本 | messages[] 中 role: "assistant" 且带 content | messages[] 中 role: "assistant" 且包含 text content blocks |
| 工具调用请求 | assistant message 的 tool_calls[] | assistant message content block,type: "tool_use" |
| 工具调用结果 | 独立的 messages[] item,role: "tool",带 tool_call_id | user message content block,type: "tool_result" |
| 旧版 function 结果 | 独立的 messages[] item,role: "function" | 不支持;没有 tool_call_id 时无法可靠映射到 tool_result |
System 与 developer 指令
Section titled “System 与 developer 指令”Chat 把 system-like 指令放在有序 messages[] 数组中:
{"role": "system", "content": "You are concise."}{"role": "developer", "content": "Prefer exact answers."}Anthropic 没有 developer role,也不会把 system 指令放进 messages[]。将 Chat system 和 developer content 转为 Anthropic 顶层 system 字段。只有一个非空文本片段时使用 string 形态;多个片段时使用 block 形态保留边界:
{ "system": [ {"type": "text", "text": "You are concise."}, {"type": "text", "text": "Prefer exact answers."} ]}Chat role: "user" content 不包含工具结果。它包含普通用户输入的 content parts,例如文本、图片、音频或文件:
{ "role": "user", "content": [ {"type": "text", "text": "Summarize this."}, {"type": "image_url", "image_url": {"url": "https://example.test/a.png"}} ]}当目标协议能表达来源内容时,将它们转为 Anthropic user content 中的 text/image/document blocks。不支持的 user part 应返回 TranslationError::InvalidPayload,不要静默丢弃。
工具调用请求
Section titled “工具调用请求”在 Chat Completions 中,模型通过 assistant message 的 tool_calls[] 请求执行工具:
{ "role": "assistant", "content": "I will look that up.", "tool_calls": [ { "id": "call_1", "type": "function", "function": { "name": "lookup", "arguments": "{\"query\":\"proxai\"}" } } ]}在 Anthropic Messages 中,相同请求是 assistant content block:
{ "role": "assistant", "content": [ {"type": "text", "text": "I will look that up."}, { "type": "tool_use", "id": "call_1", "name": "lookup", "input": {"query": "proxai"} } ]}Chat function tool arguments 是 JSON 字符串。转换为 Anthropic tool_use.input 时应解析为 JSON;如果无效,应让转换失败,不要用 {} 替代。
Chat function tools 可以映射到 Anthropic custom tools,因为二者都携带具名 JSON-schema 输入约束。Chat custom tools 不同:它们的输入是 freeform text 或 grammar-constrained text,不是由 input_schema 描述的 JSON object。转换到 Anthropic Messages 时应拒绝 Chat custom tool 定义、custom tool choice 和 custom tool call,而不是假装它们是空 object schema 的 JSON 工具。
工具调用结果
Section titled “工具调用结果”在 Chat Completions 中,工具执行输出不是 assistant message 的一部分,而是独立的 role: "tool" message:
{ "role": "tool", "tool_call_id": "call_1", "content": "found"}在 Anthropic Messages 中,工具结果是 user 侧 content block,并引用前面的 tool_use.id:
{ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": "call_1", "content": "found", "is_error": false } ]}因此 Chat role: "tool" message 会转成 Anthropic role: "user" message,其中包含 tool_result block。不要把 Chat 工具结果放进 Chat user content,也不要放进 Anthropic assistant content。
tool_result.is_error 的映射
Section titled “tool_result.is_error 的映射”Anthropic tool_result 携带可选的 is_error: bool,用来标记本地工具执行失败。所有支持的目标协议在工具结果输出上都没有专门的失败标志位,因此该转换按设计就是 lossy 的,遵循以下规则:
- Anthropic -> OpenAI Responses (
FunctionCallOutputResource.status):始终为Completed。Responses 的FunctionCallOutputStatusEnum只有InProgress/Completed/Incomplete,其中Incomplete专门表示 “输出被中途截断”——与 “工具执行失败” 的语义不同。如果用Incomplete表示失败,会误导客户端对结果生命周期的判断。错误上下文仍然保留在output负载里:Anthropic 客户端通常在is_error = true时把错误描述填进tool_result.content,该文本会被原样传递。这与 OpenAI 客户端和模型通常用来区分成功/失败工具执行的方式一致。 - Anthropic -> OpenAI Chat Completions:Chat 的
role: "tool"message 根本没有 status 或 error 字段,只有content和tool_call_id。is_error标志在翻译时被丢弃,content里的错误文本原样转发。这与 Responses 路径对称:错误信号在负载文本里,不在协议元数据里。 - OpenAI Responses / Chat -> Anthropic (
FunctionCallOutput/role: "tool"->tool_result):proxai 目前不会从入方向的任何启发式规则合成is_error = true,因为 OpenAI 客户端没有统一的方式标记工具调用失败。如果未来出现约定(例如 SDK 关于错误负载的约定),再重新考虑该方向。
旧版 function message
Section titled “旧版 function message”Chat 除了现代 tool_calls,还有旧版 function-calling shape。转换到 Anthropic Messages 时应拒绝 role: "function" message。旧版 function 结果只有 function name,没有稳定的 tool_call_id;而 Anthropic tool_result 必须引用前面的 tool_use.id。不要发明 id,也不要把结果降级成普通 user text。
响应 choices 与候选回复
Section titled “响应 choices 与候选回复”Chat Completions 响应中的 choices[] 是一组可替代的候选 assistant 回复,通常由请求参数 n 产生。它不是 content block 列表,也不是并行工具调用的表达方式。
{ "choices": [ {"index": 0, "message": {"role": "assistant", "content": "方案 A"}}, {"index": 1, "message": {"role": "assistant", "content": "方案 B"}} ]}并行工具调用位于单个候选 assistant message 内部,即
choices[i].message.tool_calls[];这些工具调用可以映射为同一个 Anthropic
assistant message 中的多个 tool_use block。
Anthropic Messages 没有等价的顶层候选列表响应结构。非流式 Anthropic 响应是一个
Message,其中包含一条 content[] 序列,而不是多个可替代 assistant
message 的列表。OpenAI Responses API 也没有 Chat 风格的 choices[] 等价结构:
它的 output[] 是输出 item 序列(message、function call、reasoning item 等),
不是候选答案集合。
不要把多个 Chat choices 合并进一个 Anthropic content[] 数组,也不要静默只保留第一个 choice。这两种做法都会丢失协议语义:每个 choice 的 index、独立的 finish_reason,以及这些 choices 是互斥候选而不是同一个 assistant turn 的事实。将 Chat response 转为 Anthropic Messages 或 OpenAI Responses 时,应要求恰好一个 choice,并拒绝 multi-choice response。
Chat -> Responses output 位置
Section titled “Chat -> Responses output 位置”OpenAI Responses 使用 response.output[] 表示模型返回的工作结果,它是 typed output item 序列。Chat assistant message 转换到这层时按层级拆分:
| Chat response 字段 | Responses 位置 | 顺序 |
|---|---|---|
| `choices[0].message.content` | OutputItem::Message(OutputMessage { content: [...] }),其中 content part 是 OutputMessageContent::OutputText | 非空时先放。 |
| `choices[0].message.refusal` | 同一个 OutputMessage.content[],使用 OutputMessageContent::Refusal | 和 message content 一起放;Responses 没有 message-level refusal 字段。 |
| `choices[0].message.tool_calls[]` | 独立的 function_call / custom_tool_call output items | 放在 assistant message content item 之后。 |
不要把 tool calls 塞进 OutputMessage.content[]:Responses 把 tool call 建模为同级 output item,而 OutputMessage.content[] 只承载 text/refusal 等 message content parts。如果 Chat response 只有 tool calls,转换后的 Responses output 也只包含这些 tool-call items。
Chat -> Anthropic 响应与流式语义
Section titled “Chat -> Anthropic 响应与流式语义”Chat -> Anthropic 非流式响应转换规则:
- 将
choices[0].message.content映射为 Anthropictextblocks; - 将 function
tool_calls[]映射为 Anthropictool_useblocks,并把 Chat functionarguments解析为 JSON 后写入tool_use.input; - 当存在
message.refusal时,将可见拒答文字保留为textblock,同时设置stop_reason: "refusal"和stop_details.explanation;Chat 没有 refusal category,因此不发明 category; - 要求恰好一个 Chat choice,并拒绝没有可表示 text、refusal 或 function tool calls 的响应。
Chat -> Anthropic 流式转换应保持显式 lifecycle:
- 等到第一个 assistant choice chunk 后再发 Anthropic
message_start; - 将 Chat
delta.content/delta.refusal转为 Anthropic text block;第一段 text 可以放在content_block_start中,后续片段使用text_delta; - 将 Chat function tool-call start 转为
tool_useblock start,并使用空 objectinput,因为 Chat streaming 的function.arguments是 partial JSON 字符串; 这些参数片段通过input_json_delta发送; - 当 Chat
finish_reason到达时,关闭所有打开的 content blocks,并保存包含 finish reason 和 refusal 文字的 pending terminal state; - 当后续到达
choices: []usage-only chunk,或[DONE]/ EOF 结束 stream 且没有 final usage 时,输出 Anthropicmessage_delta/message_stop。
OpenAI 在设置 stream_options: {"include_usage": true} 后,最终 streaming usage
由最后一个 choices: [] chunk 表达。转换时应把这个 usage-only chunk 作为 final
usage 来源。不要把非空 choices chunk 上的 usage 视为 final usage,也不要用它来
停止 Anthropic stream。一些 OpenAI-compatible 服务会在普通 chunk 上暴露
continuous/intermediate usage stats;这些值不能替代最终 usage-only chunk,本转换会忽略它们。
Chat stream 中的 choices: [] chunk 只有在已经收到 terminal finish_reason 后,
作为 usage-only chunk 才是合法的。应拒绝出现在任何 assistant message 之前、
terminal finish reason 之前、或 Anthropic message stopped 之后的 usage-only chunk。
对 Chat stream logprobs、非 assistant delta role、multi-choice chunks,也应报错,
不要静默丢弃 Anthropic Messages 无法表示的信息。
流式终止:Chat [DONE] 与 Responses terminal events
Section titled “流式终止:Chat [DONE] 与 Responses terminal events”不要把所有 SSE stream 的终止方式混为一谈;应按协议分别处理。
OpenAI Chat Completions streaming 是 data-only SSE,并用非 JSON sentinel frame 终止:
data: [DONE]OpenAI/async-openai schema 明确记录 Chat streaming 以 data: [DONE] 结束;
stream_options.include_usage 的最终 usage-only chunk 也出现在该 sentinel 之前。
因此,凡是输出 Chat Completions stream 的转换器,都应在 terminal finish/usage
chunks 之后追加 [DONE];凡是消费 Chat Completions stream 的转换器,都应在已收到
terminal finish_reason 后将 [DONE] 视为 stream-end marker。
OpenAI Responses streaming 则建模为类型化 SSE events(ResponseStreamEvent)。
终止状态由以下事件表达:
response.completedresponse.incompleteresponse.failed
Responses schema 没有把 [DONE] 建模为这些事件的必需终止帧。因此,输出 Responses
stream 的转换器应以合适的类型化 terminal event 结束,不应额外添加 Chat 风格的
[DONE] sentinel,除非 Responses wire model 明确变更为需要它。