要点
- 文本来自客户端,因此使用 curl 重现调用以查看真实的错误正文和状态。
- 繁忙账户上最常见的原因是 HTTP 429 配合 rate_limited 或 concurrency 代码,且流会一直占用槽位直到结束。
- 首先检查基础 URL、密钥、模型 ID uncensored、正文大小(8 MB)和总 token 数(100,000)。
- 仅重试 429 和网络断开,等待 Retry-After,并保持并发数低于密钥限制。
该消息实际告诉你的内容
如果你搜索到 unknown error from llm stream learn more,你很可能正在查看聊天前端、编辑器插件或代理框架中的提示框或红色错误行。该措辞来自客户端。它表示应用请求了流式输出,但在流式传输之前或期间发生了错误,而应用不知道如何描述该错误。API 本身通过 HTTP 状态码和 JSON 错误响应体报告问题,而客户端丢弃了这些详细信息。
这是好消息,因为这意味着你可以找回详细信息。在客户端之外发送相同的请求,读取状态行和正文,原因通常在一两次尝试内就会显而易见。本页面的其余部分按该 API 上倾向于发生的顺序介绍原因,然后提供一个小型流式输出客户端,它会打印真实错误而不是隐藏它。
从一开始就要记住的一点是:流式请求有两个故障点。它可以在第一个字节之前失败,当服务器拒绝请求时(密钥错误、URL 错误、达到限制)。或者它可以在中间失败,当连接在部分文本到达后断开。修复方法不同,因此请注意你看到的是哪一种。如果回答从未开始,请参阅第二至四节。如果回答开始后停止,请参阅代理和超时部分。
首先检查 Base URL、密钥和模型
新设置中的大多数流式输出错误源于三个小错误。排除它们只需一分钟。
- Base URL。 它必须是
https://api.uncensoredllmhub.com/v1,包含/v1且不带有尾部/chat/completions。客户端会自行追加路径。如果你将完整的接口粘贴到期望 Base URL 的字段中,客户端会请求一个不存在的路径并报告模糊的失败。 - 密钥。请求头为
Authorization: Bearer YOUR_KEY。多余的空格、随密钥复制的换行符,或来自其他服务的密钥都会以相同方式失败。如果不确定,请从 API 密钥页面重新创建或复制密钥。 - 模型 ID。 唯一的模型是
uncensored。提供下拉菜单的客户端通常会预填充来自其他提供商的名称。手动输入 ID,并调用GET /v1/models以确认服务器列出的内容。
测试这三项的快速方法是 models 调用。它不流式输出,因此可以将连接或密钥问题与流式输出问题区分开:
curl -i https://api.uncensoredllmhub.com/v1/models -H "Authorization: Bearer $API_KEY"
如果返回正常列表,说明 URL 和密钥均无误,你可以停止怀疑它们。如果失败,请先修复该问题,然后再处理流式输出相关事项。同时检查你的账户是否有额度:API 会在响应的 X-Balance-USD 头中发送你的剩余余额,当密钥在大量使用后突然失效时这很有用。定价页面 解释了充值方式,新账户可以在密钥页面领取 $0.50 的试用额度。
速率限制和占用槽位的流式输出
下一个最常见的原因是 HTTP 429。有三种类型,你的客户端会将它们全部折叠为同一个无用的消息。
| 信号 | 含义 | 该做什么 |
|---|---|---|
429,代码 rate_limited | 密钥每分钟请求过多 | 放慢速度;完整密钥允许每分钟 300 次,试用密钥允许 60 次 |
429,代码 concurrency | 并发请求过多 | 对它们排队;完整密钥允许同时 8 个,试用密钥允许同时 2 个 |
429 且 Retry-After: 5 | 服务端的临时过载 | 等待 5 秒,然后重试 |
并发限制是令人惊讶的一点,因为流式输出的工作方式。流式请求占用其槽位直到流结束,而不仅仅是直到第一个 token 到达。如果你的应用为每条消息打开一个流且从不关闭旧流,例如因为用户导航离开且 UI 忘记取消请求,这些槽位会保持占用。在限制为 2 的试用密钥上,两个被遗弃的流就足以使第三条消息失败。
症状是一致的。前几条消息正常,然后开始报错,短暂等待后一切恢复正常。如果符合该情况,请中止不再需要的流式连接(浏览器中使用 AbortController,服务器中关闭响应),并在你自己的代码中限制并发请求的数量。代理通常是罪魁祸首:一个一次性触发十个工具调用的规划器会立即达到试用密钥的限制。
响应头可帮助你了解当前状态。X-RateLimit-Limit-Requests 和 X-RateLimit-Limit-Concurrency 显示适用于你所用密钥的限制,X-Request-Id 用于标识调用,如果你需要就此事联系 支持 时很有用。
请求大小和上下文长度
请求也可能因过大而被拒绝。有两个数字很重要。正文最多 8 MB,提示词加上补全必须适合 100,000 token 的上下文窗口。在每次轮次中重新发送整个对话的聊天前端会缓慢触及这些限制,然后一次性全部失败:聊天工作一小时,然后一条长消息或粘贴的文件使其超出限制,后续所有轮次都会失败,因为历史记录仍在请求中。
如果错误在长会话后开始,请使用短的新对话进行测试。如果有效,则历史记录是原因。通过丢弃旧轮次、将它们总结为一条消息或开始新的聊天来修剪它。大型粘贴文档是另一个触发因素。几百千字节文本文件没问题,但 base64 块或非常大的日志可能比你预期的更快达到 8 MB。
你不必设置 max_tokens,因为补全可以使用窗口中剩余的任何内容。如果你确实设置了它,请确保提示词 token 加上 max_tokens 保持在 100,000 以下。在长历史记录之上硬编码大型 max_tokens 的客户端可能会使总和超过窗口,即使单独提示词看起来没问题。
代理、缓冲和超时
流式输出使用服务器发送事件:一个长寿命的 HTTP 响应,随着生成的内容交付小块。你和 API 之间的任何等待整个响应再转发的内容都会破坏这一点。结果要么是长时间沉默后的一阵突发,要么是超时和通用错误。
在你自己的设置中查找这些:
- 反向代理。 如果你通过 nginx 或类似代理路由 API,请关闭该位置的响应缓冲,并将读取超时提高到超过你预期的最长回答。来自 100,000 token 窗口的长回答可能需要一段时间。
- 企业或防病毒代理。 某些代理会检查 HTTPS 流量并保留正文。请通过手机热点进行测试以排除此问题。
- 无服务器函数。 许多平台限制执行时间或缓冲响应,除非你选择加入流式输出。如果流在每次相同秒数时死亡,平台时间限制可能是原因。
- 浏览器扩展和 VPN。 罕见,但它们可以切断空闲连接。
指向这里的线索是回答中间的错误,每次尝试都有相同的截断时间。速率限制会在开始时拒绝请求,因此总是在例如 30 秒时发生的流式传输中途切断几乎从来不是限制。
显示真实错误的客户端
当你想要真相而不是友好的消息时使用此方法。它捕获 OpenAI SDK 异常,打印状态和错误正文,并在存在时读取重试提示。
import time
from openai import OpenAI, APIConnectionError, APIStatusError, RateLimitError
client = OpenAI(
base_url="https://api.uncensoredllmhub.com/v1",
api_key="YOUR_API_KEY",
)
def stream_once(messages):
stream = client.chat.completions.create(
model="uncensored",
messages=messages,
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if getattr(chunk, "usage", None):
print("\n[usage]", chunk.usage)
messages = [{"role": "user", "content": "Write two sentences about harbours."}]
for attempt in range(4):
try:
stream_once(messages)
break
except RateLimitError as e:
wait = int(e.response.headers.get("retry-after", 5))
print("429:", e.body, "- waiting", wait, "s")
time.sleep(wait)
except APIStatusError as e:
print("status", e.status_code, e.body)
print("request id:", e.response.headers.get("x-request-id"))
break
except APIConnectionError as e:
print("connection problem:", e)
time.sleep(2)
Node 中的相同想法,使用中止信号以便被遗弃的流释放其槽位:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.uncensoredllmhub.com/v1",
apiKey: process.env.API_KEY,
});
const controller = new AbortController();
try {
const stream = await client.chat.completions.create(
{
model: "uncensored",
messages: [{ role: "user", content: "Write two sentences about harbours." }],
stream: true,
},
{ signal: controller.signal }
);
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
} catch (err) {
console.error("status:", err.status, "body:", err.error);
}
// call controller.abort() when the user leaves the page
以及原始 curl 调用,它从画面中移除每个客户端。-N 关闭 curl 自己的缓冲,-i 打印状态行和头,包括 X-Request-Id:
curl -N -i https://api.uncensoredllmhub.com/v1/chat/completions -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{"model":"uncensored","stream":true,"messages":[{"role":"user","content":"Say hello."}]}'
如果 curl 流式传输文本正常而你的应用不行,则 API 健康且问题位于应用、其代理或其设置中。如果 curl 显示 429 或其他状态,你现在有了确切的原因。
不会使情况恶化的重试规则
重试有助于解决瞬态问题,但对永久性问题有害。简短的策略可以让你避免麻烦。
- 重试 429 和连接中断。当存在
Retry-After时等待它,否则退后几秒并添加一些随机抖动,以便并行工作者不会同步重试。 - 不要重试身份验证失败或请求过大的情况。结果不会改变,每次重试只会增加噪音。
- 将尝试次数限制为三到四次,然后向用户显示真实错误文本。
- 将你自己的并发请求数控制在密钥限制以下。完整密钥下使用 6 的信号量,可以为使用同一密钥的第二个工具调用留出空间。
- 如果流式连接在半途断开,请保留部分文本。将该文本作为助手消息重新发送,并请求模型继续生成,而不是从头重新生成。
失败或被拒绝的请求不会收费,因此使用重复测试调用进行调试不会消耗你的额度。成功的请求按每 100 万输入 token $0.15、每 100 万输出 token $0.50 计费,而正常完成的部分流式响应只是一个简短的答案。关于请求格式的更多详细信息请参阅 API 文档。
如果你已完成上述所有操作但仍看到该消息,请将失败调用的 X-Request-Id 发送至 支持页面,并附上尝试的时间以及失败是发生在第一个 token 之前还是之后。这些信息足以追踪发生的情况。
