DeepSeek Harness 报 MALFORMED_RESPONSE:refusal 停因为何被判解析失败
一条 24 个汉字的运营指令——「今天行情日K数据已经更新完成了,可以将预约的任务提前执行」——流正常产出了 reasoning,模型甚至在复述任务,然后流以 stop_reason: refusal 结束,界面报「本轮运行失败 / MALFORMED_RESPONSE」。 这个错误码很容易被读成「响应被截断」或「模型拒绝了你」,但从语义上拆开,它只说明一件事:客户端不认识这个停止原因(#11337)。
DeepSeek Harness 第一层:MALFORMED_RESPONSE 说的是「我不认识」
它是客户端对响应流的分类结果,不是传输层错误,也不是内容审核结论。
- 看完整文案:
DeepSeek Messages stream: unsupported stop reason refusal,code 是MALFORMED_RESPONSE。 - 看流的内容:先有一个 reasoning 块,模型正确复述了任务并规划要跑哪一条预约项。
- 紧接着一个 finish 块,
reason.kind是 error,failure.message就是上面那句。 - 也就是说:内容生产是成功的,失败发生在收尾阶段。
- 所以「malformed」在这里的含义是「这个结束原因我没有对应处理」,而不是「报文坏了」。
DSH插件 第二层:stopReason() 只认四个值
适配器只处理 end_turn、stop_sequence、tool_use、max_tokens,其余一律走 malformed(),refusal 正好落在其余里。
- 看到达路径:
message_delta把stop_reason交给stopReason()。 - 看分支:四个已知值各有映射,其他值统一进
malformed()。 - 社区在隔离环境里跑过这个函数,输入
refusal就产出你看到的那个错误。 - 结论:客户端行为是可预期的、确定的,不需要猜。
- 这也是本文唯一能给出确定结论的部分。
DeepSeek Harness 第三层:refusal 到底是谁的语义
它是上游给的停止原因;客户端把它归类为错误,但无法解释上游为什么这么给。
- Anthropic 的文档把
refusal列为 Messages 的合法 stop reason,但那份文档描述的是 Claude。 - 它不能推出 DeepSeek 的契约,也不能证明这次拒绝是否合理。
- 报告者的输入不含暴力、自残、违法、色情、个人数据或政治内容,是一条调度指令。
- 报告者的假设是「事后安全分类把『提前执行预约任务』识别成外部动作」——这是假设,不是结论。
- 到这一步就该停:能写进文档的只有客户端映射这一半。
DeepSeek插件 能确定与不能确定的分界
把两类结论分开写,能避免把客户端缺陷说成模型问题,也避免替提供方下判断。
| 事项 | 是否确定 | 依据 |
|---|---|---|
| 流已完整结束、reasoning 已产出 | 确定 | 事件序列可见 |
| 客户端只映射四个 stop reason | 确定 | 源码与隔离运行 |
refusal 会落到 malformed 分支 | 确定 | 同上 |
| 提供方为何返回 refusal | 不确定 | 无官方说明 |
| 是否属于安全分类误判 | 不确定 | 报告者假设 |
DeepSeek Harness 该怎么处置这类错误
客户端侧该做的是「保留上游原因并映射成独立状态」,而不是把所有未知值都压成同一个错误码。
- 在规范化结果里保留上游的
stop_reason原值,别丢掉。 - 如果
refusal是该提供方的合法取值,映射成独立的「模型拒绝」状态,而不是MALFORMED_RESPONSE。 - 给用户一句可读解释,但不要暴露内部推理内容。
- 不要对同一条被拒请求做无限自动重试。
- 补一条回归测试:流里先有合法 reasoning 块,再以
stop_reason: "refusal"结束。 - 排查提供方行为是另一件事,两者可以并行,不该互相阻塞。
DSH插件 复现时收集什么
信息够用就好,别把凭据和业务数据带进去。
- 脱敏后的
message_delta事件,包含stop_details(如果有)。 - 客户端版本与提供方版本。
- 同一输入的第二次结果,用来判断是否稳定复现。
- 事件发生时间与是否重试过。
- 明确排除凭据、私有提示词、会话标识。
一个可复用的判断框架
遇到「未知取值被当成错误」这类报告,先问三句:值是谁给的、客户端怎么映射、文档承诺了什么。
- 值是谁给的:来自上游响应字段,还是客户端自己推断的。本例是上游
stop_reason。 - 客户端怎么处理:有没有显式分支,未知值落到哪里。本例落到
malformed()。 - 文档承诺了什么:该提供方是否公开列出这个取值。本例只有 Claude 的文档提到。
- 三问之后,仍无法回答「上游为什么这么给」,就不要在下结论时越界。
- 这条框架同样适用于其它
unsupported ...类错误:它们大多表示映射缺口,而非数据损坏。
注意事项
- 别把 MALFORMED_RESPONSE 当成网络问题。它出现在流收尾,不在传输。
- 别据此判断内容违规。客户端没有解释权。
- 别反复重试同一条。先确认是否稳定复现。
- 别把 Anthropic 文档当 DeepSeek 契约。同名字段在不同提供方语义可能不同。
- 报告时区分两半。「客户端映射缺口」与「提供方拒绝原因」要分开写,否则维护者无法定位。
如果你的错误文案里不是 unsupported stop reason,那属于另一类收尾问题,先去查 finish 块的完整结构。
如果确实是 unsupported stop reason 但取值不是 refusal,逻辑相同:先映射缺口,再谈上游行为。相关排查见 DeepSeek Harness 只思考不输出:空 stop 被判成功与 EMPTY_RESPONSE 重试排查。
常见问题
不是,推理块已经正常产出,流是完整结束的,DeepSeek Harness 只是不认识这个 stop reason。问题在客户端只认 end_turn、stop_sequence、tool_use、max_tokens 四个 stop reason,其余值统一走 malformed(),于是 refusal 被报成解析失败。
不能,DeepSeek Harness 的客户端映射错误只能证明它不认识这个值,无法证明提供方为什么发出它。社区明确指出:refusal 是上游给的 stop reason,客户端没有解释权。
那份文档描述的是 Claude 的行为,不能推出 DeepSeek Harness 的契约。DSH 的 DeepSeek Messages 适配器目前只映射四个值,refusal 不在其中,属于客户端映射缺口。
先别自动重试,DeepSeek Harness 这里需要先确认它是不是稳定复现。社区建议先对比第二次同样输入是否复现,判断是偶发还是稳定;即使稳定,也应该把 refusal 映射成独立的拒绝状态,而不是 MALFORMED_RESPONSE,更不该无限重试。
把脱敏后的 message_delta 事件(含 stop_details)和客户端与提供方版本号交给 DeepSeek Harness 维护者。不要带凭据、私有提示词或会话标识。
相关术语
- MALFORMED_RESPONSE
- MALFORMED_RESPONSE 是 DeepSeek Harness 在响应流无法按预期解析时返回的错误码。它表示客户端没认出某种响应形态,不等于传输失败或内容被截断。— https://github.com/deepseek-ai/deepseek-harness/discussions/11337
- stop_reason
- stop_reason 是流式响应结束的原因标识,取值如 end_turn、stop_sequence、tool_use、max_tokens。客户端按这个值决定如何收尾,未知取值通常会被归为异常。— https://github.com/deepseek-ai/deepseek-harness/discussions/11337
- refusal
- refusal 是表示模型拒绝完成请求的停止原因。Anthropic 文档把它列为 Messages 的合法取值,但它在各提供方的契约是否一致需要单独确认。— https://github.com/deepseek-ai/deepseek-harness/discussions/11337
- malformed()
- malformed() 是 DeepSeek Messages 适配器里把未知停止原因转成错误的函数。当前除四个已知取值外,其余全部落入这个分支。— https://github.com/deepseek-ai/deepseek-harness/discussions/11337
来源
- #11337 — Benign Chinese ops prompt → stop reason refusal → client reports MALFORMED_RESPONSE· deepseek-ai(GitHub Discussions)
- deepseek-ai/deepseek-harness(源码仓库)· GitHub