From a1e299a3552f055f08905fa27451e578b8650536 Mon Sep 17 00:00:00 2001 From: sakurawztlt <154661250+sakurawztlt@users.noreply.github.com> Date: Mon, 13 Apr 2026 18:17:08 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Anthropic=20=E9=9D=9E=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=9C=A8=E4=B8=8A=E6=B8=B8=E7=BB=88=E6=80=81?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=20output=20=E4=B8=BA=E7=A9=BA=E6=97=B6?= =?UTF-8?q?=E4=BB=8E=20delta=20=E4=BA=8B=E4=BB=B6=E9=87=8D=E5=BB=BA?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit b2e379cf 引入的 BufferedResponseAccumulator 已修复了 chat_completions 非流式路径和 responses OAuth 非流式路径,但遗漏了 Anthropic /v1/messages 非流式路径 (handleAnthropicBufferedStreamingResponse)。 当客户端请求 stream=false 且模型开启思考时,上游 response.completed 终态事件的 output 字段可能为空,实际 message 内容通过 response.output_text.delta 增量事件下发。旧代码只读终态事件的 Response, 导致客户端收到的 content 字段为空 ([{"type":"text"}])。 本 commit 将 b2e379cf 的相同修复模式镜像到 Anthropic 路径:在 SSE 扫描 过程中用 BufferedResponseAccumulator 累积 delta 内容,终态 output 为空 时通过 SupplementResponseOutput 补充重建。 同时修复 handleAnthropicBufferedStreamingResponse 遗漏 response.done 事件类型的问题,与 chat completions 路径保持一致,避免上游发送 response.done 时 handler 认不出终态事件、最终返回 502 的潜在问题。 BufferedResponseAccumulator 已在 chatcompletions_responses_test.go 有 完整单元测试覆盖(TextOnly/ToolCalls/Reasoning/Mixed/SupplementEmpty/ NoSupplementWhenOutputExists/EmptyDeltas/IgnoresNonFunctionCallItems), 本次复用相同累加器无需新增测试。 --- backend/internal/service/openai_gateway_messages.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/internal/service/openai_gateway_messages.go b/backend/internal/service/openai_gateway_messages.go index 7a4862d3..a72b9bbf 100644 --- a/backend/internal/service/openai_gateway_messages.go +++ b/backend/internal/service/openai_gateway_messages.go @@ -270,6 +270,7 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse( var finalResponse *apicompat.ResponsesResponse var usage OpenAIUsage + acc := apicompat.NewBufferedResponseAccumulator() for scanner.Scan() { line := scanner.Text() @@ -288,8 +289,12 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse( continue } + // Accumulate delta content for fallback when terminal output is empty. + acc.ProcessEvent(&event) + // Terminal events carry the complete ResponsesResponse with output + usage. - if (event.Type == "response.completed" || event.Type == "response.incomplete" || event.Type == "response.failed") && + if (event.Type == "response.completed" || event.Type == "response.done" || + event.Type == "response.incomplete" || event.Type == "response.failed") && event.Response != nil { finalResponse = event.Response if event.Response.Usage != nil { @@ -318,6 +323,10 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse( return nil, fmt.Errorf("upstream stream ended without terminal event") } + // When the terminal event has an empty output array, reconstruct from + // accumulated delta events so the client receives the full content. + acc.SupplementResponseOutput(finalResponse) + anthropicResp := apicompat.ResponsesToAnthropic(finalResponse, originalModel) if s.responseHeaderFilter != nil {