[Bug] 内容审核按"单个字形"拦截 → 会话永久 400:最小复现仅 2 码位、跨端点/跨客户端一致(附可落地的三处修复) #7879
overFloweee
started this conversation in
General
Replies: 1 comment
"按单个字形拦截"这条如果成立,真正的 DSH 侧问题不是拦截本身,而是"整会话砖化"1. 先划边界内容审核是服务端行为(由 DeepSeek API 决定"什么该拦"),所以"按单个字形判定"这条在本仓改不了。但它导致的两个客户端/宿主侧后果是 DSH 该负责的:
2. 所以建议把诉求写成这两条(而不是"请放宽审核")
3. 你那条"最小复现仅 2 码位"很有价值——建议保留并写清它把问题从"内容问题"变成了"判定粒度过细",同时跨端点、跨客户端一致这一点排除了"某个客户端的问题"。请把那两个码位本身(可用 4. 一条相关线索(建议引用但标明是"同类")同期另有两份报告指向同族的可诊断性问题:网关 403 被显示成"API 密钥无效"(#7799)、纯文本 5xx 被归成不可重试的兜底码(#7691)。同类在于:真实原因没有被呈现给用户。 5. 版本提醒
一条边界"审核按单个字形拦截"这一结论我无法从本仓核实(那是服务端规则);我确认与主张的是**"一次命中导致该会话此后每轮失败"这个后果应当被修**,以及错误里必须能看出触发点。你的三处修复建议请保留原样——它们比我这里更贴近你的实测。 |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
EN TL;DR — Content moderation rejects a request when the payload contains one specific regional-indicator glyph pair (2 code points). Minimal reproduction is a 2-character request body — no long page, no binary search needed. Semantically equivalent text (the region's Chinese name, its Latin two-letter code), a lone regional indicator, ordinary emoji, and 26/26 non-country-code pairs all return 200. Once that glyph reaches a session surface, every later request replays it and gets the same 400 — the session is permanently unusable, and there is no supported way to locate or remove the rejected node. Reproduced first-hand against
api.deepseek.com, and independently by a third-party client on a different gateway. This report is not a request to bypass moderation; it asks for legibility and a supported recovery path.0. 这条与已有帖子的关系(先说清,不是重复)
.slice()切断 emoji 代理对)1. 最小复现:请求体 2 个码位
单变量对照矩阵(同一端点、同一时间窗,每个请求体 1–2 个字符,只改内容这一个变量):
TW)DE/JP)TW(纯文本)⇒ 判定粒度是具体字形簇,不是"讨论某地区就是风险",也不是"所有旗帜/所有 emoji 都拦"。确定性:同一字形连打 3 次全 400;间隔 2s/15s/60s 全 400;三把不同 key 全 400;官方端点与自建网关同分钟交替打结果一致。
独立第三方复现:
anomalyco/opencode#51348(2026-09-25,OPEN)在另一网关、另一客户端上得到一致对照表(该字形 400×10;DE/HK/US/JP 200;孤立码位 200;普通 emoji 200;纯中文 200),并记录其客户端因 fail-closed 阻断整个会话的写操作。2. 机制:为什么"一次误伤"会变成"会话作废"
INVALID_REQUEST([Bug] 一次内容审核 400 会永久废掉整个会话 —— 需要「撤销被拒内容并继续」的回滚能力 #7310 已定位到@deepseek-ai/dsh-llm-deepseek/lib/index.js的httpErrorCode(),400 分支),下游无法对"内容审核拒绝"分支;dsh-llm-retry按 provider + code 重试 —— 重试INVALID_REQUEST等于原样重发同一份表面,必然复现同一条拒绝;实测补充:服务端不做会话级封禁 —— 400 之后紧接着发一个干净请求会返回 200,纯粹是"历史里带着它"。
3. 新数据点:报告本身是毒源
把同族帖子 #5445 的正文(含附件样本)作为请求体单发 → 稳定 400。也就是说任何 agent 都无法读取那条帖子来参与排查;我这边是在本地做了"隔离被拒节点"才没有把会话搭进去。
这给"至少要在错误里给出被判定内容的位置"增加了一条独立理由:否则官方的 bug 报告区对 agent 是不可读的,而排查这类问题恰恰需要 agent 去读。
4. 建议修法(按依赖排序,三处都在既有接缝上)
① 可分支的错误码。 在
httpErrorCode()里把"内容审核拒绝"从通用 400 分出来(如CONTENT_REJECTED)。这是另外两条的前提 —— 在此之前插件/下游拿不到这个信号。② 定位被拒节点。 对"外部内容被拒"而言,实用的启发式是"最近新增 / 最大的外部内容块";本次实测还给了一个更省事的路径:把审核接口当探针 + 二分,26 次 2 字符级探测就能把触发窗口收敛到 4 个字符(与 #6476 的最小单元一致)。⇒ 服务端若在响应里给出 message index / 码位区间,调用方可以直接定位,不必二分。诊断不要回显被拒内容本身(只要工具名 + 时间 + 长度/哈希指纹)。
③ 受支持的表面替换入口。 会话 surface 已经支持替换语义(
isReplaceOp/startSeq/endSeq),@deepseek-ai/dsh-compaction-tool-result-pruner也已在改写模型可见表面 ⇒ 不是在要求新架构,而是补一个分支 + 一个用户入口。语义上建议:只重试一次、替换成占位标记、并把"哪条内容被移除、为什么"明确告知模型(避免它再去抓同一来源)。④ 顺带:既然这类触发物至少一族是可枚举的(区域指示符对的一个子集),那么在请求边界做字符类归一化(把区域指示符对折叠成文字标签)是一个成本极低的预防层 —— 它同时消灭"报告本身是毒源"这个副作用。它不改变上游策略:策略仍然在拦,只是拦不到我们自己的历史。
5. 我没验的
tools/system/ 图片字段与多模态里的同一字形。assistant历史消息里的字形与user/tool角色是否同等对待。max_tokens ≤ 4。6. 相关
All reactions