跳转到内容

消息位置

OpenAI Chat ↔ Anthropic Messages 消息位置

Section titled “OpenAI Chat ↔ Anthropic Messages 消息位置”

OpenAI Chat Completions 和 Anthropic Messages 都把对话表示为有序 turn,但 system 指令、工具调用和工具结果所在的位置不同。转换代码应显式保留这些位置规则。

概念OpenAI Chat CompletionsAnthropic Messages
System 指令messages[]role: "system" 的 item顶层 system 字段
Developer 指令messages[]role: "developer" 的 item没有专门 role;折叠进顶层 system
用户内容messages[]role: "user" 的 itemmessages[]role: "user" 的 item
Assistant 文本messages[]role: "assistant" 且带 contentmessages[]role: "assistant" 且包含 text content blocks
工具调用请求assistant message 的 tool_calls[]assistant message content block,type: "tool_use"
工具调用结果独立的 messages[] item,role: "tool",带 tool_call_iduser message content block,type: "tool_result"
旧版 function 结果独立的 messages[] item,role: "function"不支持;没有 tool_call_id 时无法可靠映射到 tool_result

Chat 把 system-like 指令放在有序 messages[] 数组中:

{"role": "system", "content": "You are concise."}
{"role": "developer", "content": "Prefer exact answers."}

Anthropic 没有 developer role,也不会把 system 指令放进 messages[]。将 Chat systemdeveloper 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,不要静默丢弃。

在 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 工具。

在 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。

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 字段,只有 contenttool_call_idis_error 标志在翻译时被丢弃,content 里的错误文本原样转发。这与 Responses 路径对称:错误信号在负载文本里,不在协议元数据里。
  • OpenAI Responses / Chat -> Anthropic (FunctionCallOutput / role: "tool" -> tool_result):proxai 目前不会从入方向的任何启发式规则合成 is_error = true,因为 OpenAI 客户端没有统一的方式标记工具调用失败。如果未来出现约定(例如 SDK 关于错误负载的约定),再重新考虑该方向。

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。

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。

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 非流式响应转换规则:

  • choices[0].message.content 映射为 Anthropic text blocks;
  • 将 function tool_calls[] 映射为 Anthropic tool_use blocks,并把 Chat function arguments 解析为 JSON 后写入 tool_use.input
  • 当存在 message.refusal 时,将可见拒答文字保留为 text block,同时设置 stop_reason: "refusal"stop_details.explanation;Chat 没有 refusal category,因此不发明 category;
  • 要求恰好一个 Chat choice,并拒绝没有可表示 text、refusal 或 function tool calls 的响应。

Chat -> Anthropic 流式转换应保持显式 lifecycle:

  1. 等到第一个 assistant choice chunk 后再发 Anthropic message_start
  2. 将 Chat delta.content / delta.refusal 转为 Anthropic text block;第一段 text 可以放在 content_block_start 中,后续片段使用 text_delta
  3. 将 Chat function tool-call start 转为 tool_use block start,并使用空 object input,因为 Chat streaming 的 function.arguments 是 partial JSON 字符串; 这些参数片段通过 input_json_delta 发送;
  4. 当 Chat finish_reason 到达时,关闭所有打开的 content blocks,并保存包含 finish reason 和 refusal 文字的 pending terminal state;
  5. 当后续到达 choices: [] usage-only chunk,或 [DONE] / EOF 结束 stream 且没有 final usage 时,输出 Anthropic message_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.completed
  • response.incomplete
  • response.failed

Responses schema 没有把 [DONE] 建模为这些事件的必需终止帧。因此,输出 Responses stream 的转换器应以合适的类型化 terminal event 结束,不应额外添加 Chat 风格的 [DONE] sentinel,除非 Responses wire model 明确变更为需要它。