QuantumNous / QuantumNous/new-api
[Bug] OpenAI Chat 流已有部分输出后超时仍补 usage + [DONE],调用方无法感知异常
Nobody has claimed this yet.
- 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=timeoutnormal=false
但下游实际收到的却是:
- 最后一个可能不完整的缓存 chunk;
- New API 合成的 usage chunk;
data: [DONE]。
处理函数最终返回 nil,调用方因此会把本次请求识别为正常完成,无法知道工具调用参数其实是不完整的。
本 issue 不要求在已有部分输出后自动切换渠道或重试,也不要求在 SSE 已开始后修改 HTTP 状态码。问题仅针对:异常结束为什么会被转换成正常 [DONE],以及为什么没有任何错误信号传给调用方。
从当前代码看:
relay/helper/stream_scanner.go会将超时记录为异常结束;relay/channel/openai/relay-openai.go在 scanner 返回后没有检查StreamStatus.IsNormalEnd();- 随后仍调用最终响应处理逻辑;
- 最终响应逻辑会补 usage 并发送
[DONE]。
想确认:
- 当
EndReason=timeout/scanner_error/eof时仍发送[DONE],这是预期设计吗? - 如果是预期设计,普通 API 调用方应通过什么字段判断这次流并未正常完成?
- 如果不是预期设计,能否在已有部分输出时发送协议兼容的流内错误;如果无法发送错误事件,至少在异常断开时不要发送
[DONE],并将请求记录为 partial/error?
复现步骤
- 启动一个假的 OpenAI Compatible 上游。
- 上游接受请求并返回 HTTP 200。
- 返回一个或多个 SSE chunk,其中包含部分文本或未完成的
tool_calls.function.arguments。 - Flush 响应后保持连接,但不再发送数据,也不发送
[DONE]。 - 等待时间超过 New API 配置的
STREAMING_TIMEOUT。 - 通过非透传 OpenAI Compatible 渠道,以
stream=true调用该上游。 - 检查 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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