[Bug] Configuring third-party OpenAI-compatible gateways: STREAM_CLOSED error message is too vague to root-cause #2374
Replies: 1 comment
|
两个陷阱都成立,而且陷阱 1 的机制你写得很准。补三件:一个能立刻自查的办法、一个可能更合适的配置路径、以及这个报错属于哪一类。 一、两个陷阱各自最快的自查陷阱 1(baseURL 少了 curl -i -X POST "https://example.com/chat/completions" \
-H 'content-type: application/json' -H 'authorization: Bearer <key>' \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"stream":true}'看第一行状态码和 陷阱 2(模型 id 大小写)——先看网关自己认什么: curl -s "https://example.com/v1/models" -H 'authorization: Bearer <key>'二、如果你想确认 DSH 到底发了什么,有个现成的透传录制器你的陷阱 2 里有个环节是推测的——"DSH 直接把配置的 model ID 原样发送给 API"。这件事可以直接看到,不用推。 我们仓库里有个独立的小脚本( PROXY_UPSTREAM=https://example.com PROXY_PORT=4599 node recording-proxy.mjs
# 然后把 baseURL 指到 http://127.0.0.1:4599/v1,正常跑一轮然后 (我们做这个是因为自己有条规矩:端到端不许用 mock,假端点回一句固定话就"通过",证明的只是假端点会说话。但它对排查自建网关同样好用,所以就这么放着了——你不需要装我们任何东西,把那个 .mjs 拷走就能跑。) 三、一个可能更合适的配置路径你引的根因文件是 DSH 还有另一个官方适配器 llm-pi-ai:
providers:
my-gateway:
displayName: My Gateway
api: openai-completions
baseURL: https://example.com/v1
apiKeyEnv: MY_GATEWAY_KEY
models:
- id: deepseek-v4-flash
contextWindow: 131072走这条的好处:它有一整套面向第三方网关的 compat 开关( 边界说清:我不知道换到 四、这个报错属于一个反复出现的类
同样的形状最近在这个社区出现过好几次:
共同点:解析/投影环节遇到不该出现的输入时,产出了一个"看起来正常的空值",而不是一个说得清的错误。 于是错误信息指向了下游的症状(没有 你的修复 1 正好是这一类的标准解法——把实际收到的东西(content-type + body 前 200 字节)放进错误里。我建议在提案里把这一点写成通则而不是个案:任何解析器在"零产出"时,都应该把它实际看到的东西报出来,因为零产出恰恰是最没有信息量的失败形态。 边界与利益相关我们不修 DSH 自家组件—— 利益相关:我维护 pi2dsh(Pi 生态兼容层),第二节那个录制器是它的验收装置之一。这条不推销:那个脚本是独立的一个文件,拷走直接 |
Uh oh!
There was an error while loading. Please reload this page.
环境
api.deepseek.com)问题
DSH 在配置第三方 OpenAI 兼容 API 网关时存在两个配置陷阱,导致所有请求必然失败并抛出
LlmError("SSE stream ended without [DONE]", "STREAM_CLOSED"),用户体验是「一对话就报错,完全不可用」。陷阱 1:
baseURL路径不完整时无任何报错提示现象: 当用户配置
baseURL为https://example.com/(缺少/v1后缀)时,DSH 的DeepSeekAdapter.request()方法会拼接/chat/completions,最终请求到https://example.com/chat/completions。很多第三方 API 网关的正确端点是
https://example.com/v1/chat/completions。缺少/v1的请求会被网关返回一个 HTML 首页(200 OK,Content-Type: text/html),而不是 SSE 流。parseSse()函数尝试用EventSourceParserStream解析这个 HTML 响应,当然不会产生任何合法的 SSE event,循环结束后没有收到[DONE],于是抛出STREAM_CLOSED。误导性: 这个错误信息
"SSE stream ended without [DONE]"完全没有提示用户可能是 baseURL 配错了。用户会以为是网络问题、模型问题或者 DSH 本身的 bug,很难定位根因。建议改进:
request()方法中,检查 HTTP 响应的Content-Type。如果不是text/event-stream,应该抛出更明确的错误,例如:/models端点),提前发现配置错误。陷阱 2: 模型 ID 大小写敏感,配置不匹配时报错信息模糊
现象: 用户在
settings.yaml中配置模型 ID 为deepseek-V4-flash(大写 V),但 API 网关上注册的模型 ID 是deepseek-v4-flash(小写 v)。DSH 直接把配置的 model ID 原样发送给 API,API 网关返回
model_not_found。然而 DSH 的 retry policy 可能会重试这个请求,最终用户看到的还是STREAM_CLOSED或其他模糊错误。建议改进:
/v1/models端点拉取可用模型列表,与用户配置的 models 做交叉校验,对大小写不一致的情况给出 warning。model_not_found错误时,提示用户检查模型 ID 的拼写和大小写,并附带 API 返回的完整错误信息。根因代码位置
文件:
@deepseek-ai/dsh-llm-deepseek/lib/index.js修复建议
修复 1: 在
request()方法中增加 Content-Type 校验在
fetch()返回后、调用parseSse()之前,增加:修复 2: 在模型解析阶段增加大小写校验
在
resolveModel()或首次请求时,可以提示用户检查模型 ID:临时解决方案
对于遇到同样问题的用户,手动修改
~/.dsh/settings.yaml:可以用 curl 验证:
curl -s https://your-api-gateway.com/v1/models -H "Authorization: Bearer $KEY"总结
这两个问题本质上都是「配置错误 → 模糊报错 → 用户无法定位」。DSH 作为一个面向开发者的工具,应该在错误提示上做得更好。
STREAM_CLOSED这个错误码太笼统了,它背后可能隐藏着完全不同的根因(HTML 响应、模型不存在、网络中断等),应该被区分开。All reactions