[Bug/错误]: Responses → Chat Completions 将并行 function calls 拆分为无效的连续 assistant 消息
Pre-submission Checklist / 提交前检查
FAQ Check / FAQ 检查
Bug Description / Bug 描述
当发送到 /v1/responses 的请求包含两个连续的 function_call input item,后面跟着对应的两个 function_call_output item 时,AxonHub 会将每个 function call 分别转换为一个独立的 Chat Completions assistant 消息。
合法的 Responses input:
function_call call_a
function_call call_b
function_call_output call_a
function_call_output call_b
AxonHub 当前将其转发到 Chat Completions 渠道时变为:
assistant tool_calls=[call_a]
assistant tool_calls=[call_b]
tool tool_call_id=call_a
tool tool_call_id=call_b
这是非法的 Chat Completions 顺序。第一条 assistant 消息声明了 call_a 后,必须立即跟随对应的 tool 消息,而不是另一条 assistant 消息。严格的上游会拒绝该请求并报告 call_a 缺少响应。
这两个连续的 Responses function_call item 代表同一次 assistant turn 中的并行调用,应合并为一条 Chat Completions assistant 消息。
Steps to Reproduce / 复现步骤
export AXONHUB_BASE_URL='https://your-axonhub.example.com'
export AXONHUB_API_KEY='replace-with-your-api-key'
curl --silent --show-error \
--request POST \
"${AXONHUB_BASE_URL}/v1/responses" \
--header "Authorization: Bearer ${AXONHUB_API_KEY}" \
--header 'Content-Type: application/json' \
--header 'User-Agent: axonhub-consecutive-function-calls-repro/1.0' \
--data-binary @- <<'JSON'
{
"model": "kimi-k3",
"store": false,
"stream": false,
"input": [
{
"role": "user",
"content": "Reply with exactly OK."
},
{
"type": "function_call",
"call_id": "call_a",
"name": "first_tool",
"arguments": "{}"
},
{
"type": "function_call",
"call_id": "call_b",
"name": "second_tool",
"arguments": "{}"
},
{
"type": "function_call_output",
"call_id": "call_a",
"output": "first result"
},
{
"type": "function_call_output",
"call_id": "call_b",
"output": "second result"
},
{
"role": "user",
"content": "Reply with exactly OK."
}
],
"max_output_tokens": 32
}
JSON
kimi官方渠道。
Expected Behavior / 期望行为
AxonHub 应将连续的 function_call item 合并为一条内部 assistant 消息,以保留单次 assistant turn。Chat Completions payload 应具有以下形状:
[
{
"role": "assistant",
"tool_calls": [
{
"id": "call_a",
"type": "function",
"function": { "name": "first_tool", "arguments": "{}" }
},
{
"id": "call_b",
"type": "function",
"function": { "name": "second_tool", "arguments": "{}" }
}
]
},
{
"role": "tool",
"tool_call_id": "call_a",
"content": "first result"
},
{
"role": "tool",
"tool_call_id": "call_b",
"content": "second result"
},
{
"role": "user",
"content": "Reply with exactly OK."
}
]
上游请求应正常完成,而非返回 HTTP 400。
Error Message / 错误消息
HTTP/2 400
{
"error": {
"message": "an assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_a",
"type": "InvalidParameter",
"code": "InvalidParameter"
}
}
Operating System / 操作系统
Docker on Linux.
AxonHub Version / AxonHub 版本
Image: looplj/axonhub:v1.0.0-beta5
OCI version label: v1.0.0-beta5
OCI revision label: d061ac7df6aef0c5ec6cdfa9dc5002546a1c5a57
Usage Scenario / 使用场景
API.
API Format / API 格式
- 入站:OpenAI - Responses(
/v1/responses)。
- 出站渠道:OpenAI - Chat Completions(
/v1/chat/completions)。
Channel Type / 渠道类型
kimi OpenAI-compatible Chat Completions 渠道。
Logs / 日志
保存的入站请求结构正确:
# requests.id = 58800
1 <implicit message> user
2 function_call call_a first_tool
3 function_call call_b second_tool
4 function_call_output call_a
5 function_call_output call_b
6 <implicit message> user
保存的出站执行结构无效:
# request_executions.id = 71643
1 user
2 assistant tool_calls=call_a
3 assistant tool_calls=call_b
4 tool tool_call_id=call_a
5 tool tool_call_id=call_b
6 user
执行记录中包含:
status: failed
response_status_code: 400
format: openai/chat_completions
error: an assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_a
Additional Context / 其他补充信息
可能原因(以下内容由GPT生成)
问题出现在:
llm/transformer/openai/responses/inbound.go
convertInputToMessages() 对 reasoning item 有特殊处理:convertReasoningWithFollowing() 会向前扫描并将后续的 function_call item 合并到一条 assistant 消息中。
但裸 function_call 由 convertItemToMessage() 处理,该方法会为每个调用创建一条只含一个 tool call 的新 assistant 消息。因此,连续的裸 function_call item 会变成连续的 assistant 消息:
convertInputToMessages 约第 329 行
convertReasoningWithFollowing 约第 388 行
convertReasoningWithFollowing/function_call 约第 417 行
convertItemToMessage/function_call 约第 519 行
当前源码参考:
|
func convertInputToMessages(input *Input) ([]llm.Message, error) { |
建议修复
当 convertInputToMessages() 遇到裸 function_call 或 custom_tool_call 时,收集同一 Responses turn 中的所有连续 tool-call item,并追加到一条 llm.Message{Role: "assistant", ToolCalls: [...]} 中。遇到 message、tool output 或其他分隔 turn 的 item 时停止合并。
建议的回归用例:
function_call, function_call 变为一条包含两个 tool calls 的 assistant 消息。
function_call, function_call, function_call_output, function_call_output 保持合法的 Chat Completions 顺序。
- 由 outputs 或 user message 分隔的 tool-call 组不会被跨 turn 合并。
- 对连续的
custom_tool_call item 也应用同等的合并逻辑(在可表示的范围内)。
PR #590 可作为有价值的参考:它针对 Bailian outbound 合并了连续的纯 tool-call assistant 消息,但未修复通用的 Responses inbound 表示——该问题在渠道特定的 outbound 转换之前就产生了无效的消息序列。
[Bug/错误]: Responses → Chat Completions 将并行 function calls 拆分为无效的连续 assistant 消息
Pre-submission Checklist / 提交前检查
FAQ Check / FAQ 检查
Bug Description / Bug 描述
当发送到
/v1/responses的请求包含两个连续的function_callinput item,后面跟着对应的两个function_call_outputitem 时,AxonHub 会将每个 function call 分别转换为一个独立的 Chat Completionsassistant消息。合法的 Responses input:
AxonHub 当前将其转发到 Chat Completions 渠道时变为:
这是非法的 Chat Completions 顺序。第一条 assistant 消息声明了
call_a后,必须立即跟随对应的 tool 消息,而不是另一条 assistant 消息。严格的上游会拒绝该请求并报告call_a缺少响应。这两个连续的 Responses
function_callitem 代表同一次 assistant turn 中的并行调用,应合并为一条 Chat Completions assistant 消息。Steps to Reproduce / 复现步骤
kimi官方渠道。
Expected Behavior / 期望行为
AxonHub 应将连续的
function_callitem 合并为一条内部 assistant 消息,以保留单次 assistant turn。Chat Completions payload 应具有以下形状:[ { "role": "assistant", "tool_calls": [ { "id": "call_a", "type": "function", "function": { "name": "first_tool", "arguments": "{}" } }, { "id": "call_b", "type": "function", "function": { "name": "second_tool", "arguments": "{}" } } ] }, { "role": "tool", "tool_call_id": "call_a", "content": "first result" }, { "role": "tool", "tool_call_id": "call_b", "content": "second result" }, { "role": "user", "content": "Reply with exactly OK." } ]上游请求应正常完成,而非返回 HTTP 400。
Error Message / 错误消息
HTTP/2 400 { "error": { "message": "an assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_a", "type": "InvalidParameter", "code": "InvalidParameter" } }Operating System / 操作系统
Docker on Linux.
AxonHub Version / AxonHub 版本
Usage Scenario / 使用场景
API.
API Format / API 格式
/v1/responses)。/v1/chat/completions)。Channel Type / 渠道类型
kimi OpenAI-compatible Chat Completions 渠道。
Logs / 日志
保存的入站请求结构正确:
保存的出站执行结构无效:
# request_executions.id = 71643 1 user 2 assistant tool_calls=call_a 3 assistant tool_calls=call_b 4 tool tool_call_id=call_a 5 tool tool_call_id=call_b 6 user执行记录中包含:
Additional Context / 其他补充信息
可能原因(以下内容由GPT生成)
问题出现在:
convertInputToMessages()对reasoningitem 有特殊处理:convertReasoningWithFollowing()会向前扫描并将后续的function_callitem 合并到一条 assistant 消息中。但裸
function_call由convertItemToMessage()处理,该方法会为每个调用创建一条只含一个 tool call 的新 assistant 消息。因此,连续的裸function_callitem 会变成连续的 assistant 消息:当前源码参考:
axonhub/llm/transformer/openai/responses/inbound.go
Line 329 in 35a8e5b
建议修复
当
convertInputToMessages()遇到裸function_call或custom_tool_call时,收集同一 Responses turn 中的所有连续 tool-call item,并追加到一条llm.Message{Role: "assistant", ToolCalls: [...]}中。遇到 message、tool output 或其他分隔 turn 的 item 时停止合并。建议的回归用例:
function_call, function_call变为一条包含两个 tool calls 的 assistant 消息。function_call, function_call, function_call_output, function_call_output保持合法的 Chat Completions 顺序。custom_tool_callitem 也应用同等的合并逻辑(在可表示的范围内)。PR #590 可作为有价值的参考:它针对 Bailian outbound 合并了连续的纯 tool-call assistant 消息,但未修复通用的 Responses inbound 表示——该问题在渠道特定的 outbound 转换之前就产生了无效的消息序列。