QuantumNous / QuantumNous/new-api

[Bug] OpenAI Chat 流已有部分输出后超时仍补 usage + [DONE],调用方无法感知异常

Open
#6,239 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Go
Stars
48.5k
Forks
11.6k
Avg merge
1d 13h
Merged PRs (30d)
58

Description

提交前必读(请勿删除本节)

  • 文档:https://docs.newapi.ai/
  • 使用问题先看或先问:https://deepwiki.com/QuantumNous/new-api
  • 开启透传后的转发相关反馈不接受 issue;透传模式会直接转发请求,请自行确认上游行为。
  • 不接受 coding plan、逆向渠道等技术支持类 issue。
  • 警告:删除本模板、删除小节标题或随意清空内容的 issue,可能会被直接关闭;重复恶意提交者可能会被 block。

您当前的 newapi 版本

v1.0.0-rc.21-5-ga63364d1(main commit a63364d1

提交确认

  • 非重复 issue: 我已搜索现有 Issues,确认目前没有完全相同的 issue。
  • 提交前必读: 我已完整阅读上方“提交前必读”,并确认这不是使用、配置或接入类问题。
  • 模板完整: 我未删除此模板中的任何引导内容或小节标题,并会按要求完整填写。
  • 维护成本: 我理解项目维护者精力有限,不遵循模板要求的 issue 可能会被无视或直接关闭。

问题描述

使用非透传的 OpenAI Compatible 渠道调用 /v1/chat/completions 流式接口。

上游已返回部分 SSE chunk,例如部分 tool_calls.function.arguments,随后超过 STREAMING_TIMEOUT 没有返回任何新数据,也没有返回 finish_reason[DONE]

此时 New API 内部的 StreamStatus 会记录:

  • end_reason=timeout
  • normal=false

但下游实际收到的却是:

  1. 最后一个可能不完整的缓存 chunk;
  2. New API 合成的 usage chunk;
  3. data: [DONE]

处理函数最终返回 nil,调用方因此会把本次请求识别为正常完成,无法知道工具调用参数其实是不完整的。

本 issue 不要求在已有部分输出后自动切换渠道或重试,也不要求在 SSE 已开始后修改 HTTP 状态码。问题仅针对:异常结束为什么会被转换成正常 [DONE],以及为什么没有任何错误信号传给调用方。

从当前代码看:

  • relay/helper/stream_scanner.go 会将超时记录为异常结束;
  • relay/channel/openai/relay-openai.go 在 scanner 返回后没有检查 StreamStatus.IsNormalEnd()
  • 随后仍调用最终响应处理逻辑;
  • 最终响应逻辑会补 usage 并发送 [DONE]

想确认:

  1. EndReason=timeout/scanner_error/eof 时仍发送 [DONE],这是预期设计吗?
  2. 如果是预期设计,普通 API 调用方应通过什么字段判断这次流并未正常完成?
  3. 如果不是预期设计,能否在已有部分输出时发送协议兼容的流内错误;如果无法发送错误事件,至少在异常断开时不要发送 [DONE],并将请求记录为 partial/error?

复现步骤

  1. 启动一个假的 OpenAI Compatible 上游。
  2. 上游接受请求并返回 HTTP 200。
  3. 返回一个或多个 SSE chunk,其中包含部分文本或未完成的 tool_calls.function.arguments
  4. Flush 响应后保持连接,但不再发送数据,也不发送 [DONE]
  5. 等待时间超过 New API 配置的 STREAMING_TIMEOUT
  6. 通过非透传 OpenAI Compatible 渠道,以 stream=true 调用该上游。
  7. 检查 New API 返回给调用方的完整 SSE。

实际结果:

  • New API 内部记录 end_reason=timeout
  • 下游仍收到 usage 和 [DONE]
  • 调用方无法区分正常完成和超时中断。

相关讨论:

  • #2989:空流不重试
  • #4139:HTTP 200 流内业务错误
  • #5222:流式错误分类
  • #3142:首个下游事件前的错误状态
  • PR #4060:仅处理零输出时的超时重试

本问题与上述讨论的区别是:上游已经产生部分输出,不能安全重试,但 New API 不应把异常结束伪装成正常 [DONE]

预期结果

建议区分以下情况:

  • 尚未向下游发送真实模型数据:可以返回错误或按现有重试策略处理。
  • 已向下游发送部分数据:不要求重试,但不应发送代表正常完成的 [DONE];应发送流内错误,或者异常关闭 SSE,并在日志中记录为 partial/error。
  • 只有上游明确正常结束时,才补最终 usage 和 [DONE]

计费、退款和部分输出后的重试策略不在本 issue 的讨论范围内。

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with relay/helper/stream_scanner.go and relay/channel/openai/relay-openai.go, tracing StreamStatus and the final response path after a timeout or scanner error. Confirm how partial output is handled, then make the completion behavior distinguish abnormal endings from normal upstream completion; done means partial failures are not reported with usage and [DONE], with the chosen error or closure recorded consistently.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend, stream-processing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.