Commit 777820c1 authored by sakurawztlt's avatar sakurawztlt Committed by 陈曦
Browse files

fix: Anthropic 非流式路径在上游终态事件 output 为空时从 delta 事件重建响应内容

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),
本次复用相同累加器无需新增测试。
parent a04ae28a
...@@ -270,6 +270,7 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse( ...@@ -270,6 +270,7 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse(
var finalResponse *apicompat.ResponsesResponse var finalResponse *apicompat.ResponsesResponse
var usage OpenAIUsage var usage OpenAIUsage
acc := apicompat.NewBufferedResponseAccumulator()
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
...@@ -288,8 +289,12 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse( ...@@ -288,8 +289,12 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse(
continue continue
} }
// Accumulate delta content for fallback when terminal output is empty.
acc.ProcessEvent(&event)
// Terminal events carry the complete ResponsesResponse with output + usage. // 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 { event.Response != nil {
finalResponse = event.Response finalResponse = event.Response
if event.Response.Usage != nil { if event.Response.Usage != nil {
...@@ -318,6 +323,10 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse( ...@@ -318,6 +323,10 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse(
return nil, fmt.Errorf("upstream stream ended without terminal event") 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) anthropicResp := apicompat.ResponsesToAnthropic(finalResponse, originalModel)
if s.responseHeaderFilter != nil { if s.responseHeaderFilter != nil {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment