Skip to content

[Bug] codex Responses 请求转发失败:ResponsesInputItem.Arguments/.Output 字段类型过窄导致 502 与静默丢数据 #2913

@hanyunsushi

Description

@hanyunsushi

问题描述

通过 /responses(及 /v1/responses/backend-api/codex/responses)接入 codex 等 Responses API 客户端时,在多轮工具调用场景下,请求在 apicompat 协议转换阶段失败:

  • 转发到 Anthropic 上游:返回 502 Bad Gateway,客户端报 unexpected status 502 Bad Gateway: Upstream request failed
  • 转发到 OpenAI-compat 上游(只支持 /v1/chat/completions 的第三方中转):不报错,但工具调用结果被静默丢弃,表现为模型"思考完没有输出"。

后台账号"测试连接"显示正常(测试连接走自构造的简单请求,不经过 Responses input 解析),掩盖了该问题。

复现

curl -N -X POST http://<host>/responses \
  -H "Authorization: Bearer <KEY>" -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4-x",
    "stream": true,
    "input": [
      {"role": "user", "content": [{"type": "input_text", "text": "hi"}]},
      {"type": "function_call", "call_id": "c1", "name": "foo", "arguments": {"x": 1}},
      {"type": "function_call_output", "call_id": "c1",
       "output": [{"type": "output_text", "text": "result"}]}
    ]
  }'

实测日志:

ERROR handler/gateway_handler_responses.go  gateway.responses.forward_failed
  platform=anthropic
  error="convert responses to anthropic: parse responses input:
         json: cannot unmarshal array into Go struct field
         ResponsesInputItem.output of type string"
→ HTTP 502

arguments 为对象时同理:cannot unmarshal object into Go struct field ResponsesInputItem.arguments of type string

根因

backend/internal/pkg/apicompat/types.goResponsesInputItem 的两个字段定义为 string:

type ResponsesInputItem struct {
    // ...
    Content   json.RawMessage `json:"content,omitempty"` // 已正确用 RawMessage
    Arguments string          `json:"arguments,omitempty"` // BUG:codex 可能发对象
    // ...
    Output    string          `json:"output,omitempty"`    // BUG:codex 新版发数组
}

而新版 OpenAI Responses API / codex 实际发送:

  • function_call.arguments:可能是 stringified JSON,也可能是 JSON 对象
  • function_call_output.output:新版是数组 [{"type":"output_text","text":"..."}],旧版才是纯字符串。

强类型 string 字段遇到对象/数组,json.Unmarshal 直接报错。

两条转换路径表现不同(同一根因)

路径 入口 文件 遇到对象/数组
Responses→Anthropic convertResponsesInputToAnthropic responses_to_anthropic_request.go 502 崩溃
Responses→ChatCompletions responsesInputToChatMessages chatcompletions_responses_bridge.go 不崩,但 rawString() 对非字符串返回 "",工具结果静默丢失

建议修复

ResponsesInputItem.Arguments.Output 改为 json.RawMessage,并在取值处归一化:

  • arguments:object / stringified JSON / 空 → JSON 对象(供 Anthropic tool_use.input);→ stringified JSON(供 chat/completions function.arguments)。
  • output:string / array(提取 output_text/text 文本)/ 空 → 文本(供 tool_result.content / tool message)。
  • 反向构造 ResponsesInputItem 的位置(anthropic_to_responses.gochatcompletions_to_responses.go)需把原 string 包成 JSON-string RawMessage,保持 wire 格式不变。

json.RawMessage 对旧的纯字符串输入仍向后兼容。

版本:基于 v0.1.133 / upstream main,核实字段定义未变。本地按上述方案修复并实测后,codex → /responses → Anthropic 上游恢复正常 200,返回完整 Responses SSE 流。如果方案 OK 我可以提 PR。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions