fix: skip previous_response_id recovery when payload has function_call_output

When a preflight ping fails or previous_response_not_found is returned,
sub2api drops previous_response_id and retries. But if the payload
contains function_call_output (tool results), the upstream API loses
the response chain context needed to match tool_result to tool_use,
causing 400: "No tool call found for function call output".

Add hasFunctionCallOutput checks to both recovery paths:
- Preflight ping failure recovery (forcePreferredConn path)
- recoverIngressPrevResponseNotFound function
This commit is contained in:
Jiahao Luo
2026-05-05 15:13:46 +08:00
parent 4de28fec8c
commit e71b55ec69

View File

@@ -3104,6 +3104,12 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
if turnPrevRecoveryTried || !s.openAIWSIngressPreviousResponseRecoveryEnabled() {
return false
}
// 携带 function_call_output 的请求不能丢弃 previous_response_id
// 上游 API 需要 response chain 来匹配 tool_result 与之前的 tool_use
// 丢弃后会导致 "No tool call found for function call output" 400 错误。
if gjson.GetBytes(currentPayload, `input.#(type=="function_call_output")`).Exists() {
return false
}
if isStrictAffinityTurn(currentPayload) {
// Layer 2严格亲和链路命中 previous_response_not_found 时,降级为“去掉 previous_response_id 后重放一次”。
// 该错误说明续链锚点已失效,继续 strict fail-close 只会直接中断本轮请求。
@@ -3370,7 +3376,11 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
truncateOpenAIWSLogValue(pingErr.Error(), openAIWSLogValueMaxLen),
)
if forcePreferredConn {
if !turnPrevRecoveryTried && currentPreviousResponseID != "" {
// 携带 function_call_output 的请求不能丢弃 previous_response_id
// 上游 API 需要 response chain 来匹配 tool_result 与之前的 tool_use
// 丢弃后会导致 "No tool call found for function call output" 400 错误。
hasFCOutput := gjson.GetBytes(currentPayload, `input.#(type=="function_call_output")`).Exists()
if !turnPrevRecoveryTried && currentPreviousResponseID != "" && !hasFCOutput {
updatedPayload, removed, dropErr := dropPreviousResponseIDFromRawPayload(currentPayload)
if dropErr != nil || !removed {
reason := "not_removed"