Skip to content

请求体超过 ~128KB 触发上游 WAF 验证时静默返回空响应(HTTP 200 + completion_tokens: 0,无任何错误) #147

Description

@cheungringo420

现象

当请求体超过约 128 KB 时,上游 chat.qwen.ai 会返回 WAF 反爬验证响应,但 Qwen2API 会静默丢弃它,最终给客户端返回 HTTP 200 + 空内容 + completion_tokens: 0没有任何错误信息(日志里也看不到)。

调用方完全无法区分「模型没话说」和「被 WAF 拦了」。我们排查了很久才定位到,因为表面上看起来像是 token 上限问题,实际上不是。

根因

上游返回的是:

{"ret":["FAIL_SYS_USER_VALIDATE","RGV587_ERROR::SM::哎哟喂,被挤爆啦,请稍后重试"],
 "data":{"url":"https://chat.qwen.ai:443//api/v2/chat/completions/_____tmd_____/punish?x5secdata=...&x5step=2&action=captcha&pureCaptcha="}}

这个 JSON 没有 choices 字段,于是在 src/controllers/chat.js 两处流式解析里被直接跳过(当前 main 866d429):

// 第 260-263 行(流式)
const decodeJson = isJson(dataContent) ? JSON.parse(dataContent) : null
if (decodeJson === null || !decodeJson.choices || decodeJson.choices.length === 0) {
    return          // <-- captcha 响应在这里被丢弃
}

// 第 576-579 行(非流式)同样逻辑
if (decodeJson === null || !decodeJson.choices || decodeJson.choices.length === 0) {
    continue        // <-- 同上
}

丢弃之后,代码继续走到结尾用 tiktoken 自行计算 usage 并输出,所以客户端拿到的是一个「成功」的空响应。

全项目搜索 FAIL_SYS_USER_VALIDATE / RGV587 / punish / x5sec 均无任何处理。

复现

直接对 chat.qwen.ai 发请求,每种情况跑 3 次,结果完全稳定(非随机):

内容 字符数 请求体大小 结果
ASCII 55,000 54 KB ✅ 正常
ASCII 120,000 117 KB ✅ 正常
CJK 40,000 117 KB ✅ 正常
CJK 44,000 129 KB ❌ captcha
ASCII 200,000 196 KB ❌ captcha

注意 ASCII 120,000 字符和 CJK 40,000 字符字符数差 3 倍但字节数相同(都是 117 KB),行为也相同 —— 所以触发条件是请求体字节数,阈值在 128 KB 左右,和 token 数、字符数、模型都无关(qwen3.8-max / qwen3.7-max / qwen3.7-plus / qwen3.6-plus 以及 -thinking / -search / -thinking-search 各变体表现一致)。

顺带一提:gzip 压缩请求体不能绕过。

建议修复

不需要处理 captcha 本身,只要别把它吞掉就够了。在 processSSEPayload(以及非流式那处)里,在 !decodeJson.choices 提前返回之前加一个判断:

// 上游 WAF 反爬验证:没有 choices,但必须让调用方知道
if (decodeJson && Array.isArray(decodeJson.ret) &&
    decodeJson.ret.some(r => typeof r === 'string' && r.includes('FAIL_SYS_USER_VALIDATE'))) {
    logger.error('上游触发 WAF 验证(请求体过大或需要 captcha)', 'CHAT', '', {
        ret: decodeJson.ret,
        punishUrl: decodeJson.data && decodeJson.data.url
    })
    throw new Error('upstream_captcha_required')
}

这样至少:

  1. 日志里能看到真实原因;
  2. 客户端收到明确错误而不是「成功的空响应」,可以据此重试、切账号或缩短 prompt。

如果愿意再进一步,可以考虑在 README 里注明请求体 128 KB 的实际上限 —— 这一点目前完全没有文档说明,而模型元数据里写的是 max_context_length: 1000000,很容易误导(我们一开始就以为是 context window 的问题)。

我可以提 PR,如果你觉得这个方向可以的话。


English summary

When the request body exceeds ~128 KB, upstream chat.qwen.ai returns a WAF anti-bot challenge (FAIL_SYS_USER_VALIDATE / RGV587_ERROR, with a punish?...&action=captcha URL). That JSON has no choices, so src/controllers/chat.js (lines 260-263 and 576-579 on main 866d429) silently discards it and the client receives HTTP 200 with empty content and completion_tokens: 0, with no error logged anywhere.

The trigger is request body bytes, not tokens or characters — ASCII 120,000 chars and CJK 40,000 chars are both 117 KB and both succeed; 129 KB fails. Deterministic across 3 runs and identical for every qwen 3.6/3.7/3.8 model and variant. gzip does not bypass it.

Suggested fix: detect ret containing FAIL_SYS_USER_VALIDATE before the !choices early-return, log it, and surface an error to the caller. Happy to open a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions