[Bug] 会话历史包含孤立 UTF-16 代理码元后,所有后续请求持续返回 HTTP 400 INVALID_REQUEST
#436
Unanswered
bailong-Hakuryu
asked this question in
Q&A
Replies: 2 comments
|
官方加载器拒读之后,磁盘上的 https://github.com/xiaoshenming/dsh-session-surgeon 默认 dry-run; git clone https://github.com/xiaoshenming/dsh-session-surgeon.git
cd dsh-session-surgeon
dsh plugin --profile web add link:"$(pwd)"重启 |
0 replies
|
谢谢您的来信,我会及时处理。
|
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.
问题摘要
DeepSeek Harness 的一次工具结果中写入了一个孤立的 UTF-16 高代理码元
U+D83C。该内容被持久化到会话日志后,会在后续请求中进入messages[40].content。DeepSeek API 随即持续返回 HTTP 400;由于 Harness 每次继续会话时都会重放同一段历史,用户重复点击“继续”或切换模型都不能恢复这个会话。本次诊断通过单变量对照确认:原始请求返回 HTTP 400;只把这个孤立代理码元替换为 Unicode 替换字符
U+FFFD,其余请求字段和内容保持不变,请求返回 HTTP 200。此外,API 的错误响应正文是纯文本,
Content-Type为application/octet-stream。当前适配器只尝试调用response.json(),解析失败后丢弃正文,因此界面仅显示笼统的DeepSeek API error (HTTP 400)和INVALID_REQUEST,没有显示服务端已经提供的具体原因。用户可见现象
在同一个任务中发送“继续”后,界面连续出现:
再次发送“继续”仍然得到相同错误。将模型从
deepseek-v4-pro切换到deepseek-v4-flash后,问题仍然存在。环境
v24.19.0pnpm dsh webhttp://127.0.0.1:3080session-3e91e9ab-242d-48ff-8d50-6fd368b833c4API Key、请求头中的授权值和完整请求正文均未包含在本报告中。
复现步骤
目前已经从故障会话中得到确定性复现请求:
session-3e91e9ab-242d-48ff-8d50-6fd368b833c4。seq: 75402的tool/result文本,其中索引1552是孤立高代理码元U+D83C。messages[40].content。U+D83C替换为U+FFFD,不修改模型、消息顺序、工具调用、推理内容或其他请求字段。触发异常的会话事件信息:
实际结果
原始请求返回:
Harness 界面只显示:
预期结果
Harness 在发送请求之前应保证所有模型可见文本都是 well-formed Unicode。工具输出或持久化事件中即使含有孤立 UTF-16 代理码元,也不应导致整个会话永久无法继续。
如果服务端返回非 JSON 错误正文,Harness 应在安全长度限制内显示或记录该正文,使错误信息能够指出具体消息字段和解析位置。
对于已经包含不可发送历史的会话,产品应提供可发现的恢复方式,例如从上一成功步骤分叉,或在明确告知用户后规范化异常文本并重试。
确定性对照结果
U+D83C替换为U+FFFD原始请求的相关统计:
工具调用 ID、工具参数 JSON 以及
tool-call/tool-result关联均正常,没有发现孤立或未匹配的工具调用。为什么重复“继续”无法恢复
异常字符已经写入持久化会话事件。后续每轮请求都会重新序列化并发送完整相关历史,因此每次重试都会再次携带同一个
U+D83C。这不是一次性的网络错误,单纯重试不会改变请求内容。切换模型也不会修复历史消息,因此
deepseek-v4-pro和deepseek-v4-flash都表现为 HTTP 400。可行的临时恢复方式是从异常事件之前的成功步骤分叉,避免重放该事件。用于验证服务、账户和凭据正常的成功分叉会话:
已排除的原因
根据原始请求检查和对照实验,已经排除:
deepseek-v4-pro模型名无效;deepseek-v4-flash后仍然返回相同错误;tool-call与tool-resultID 不匹配;相关实现位置
1. 请求序列化没有规范化非 well-formed UTF-16 字符串
packages/llm/llm-deepseek/src/serialize.ts中的flattenText()直接拼接文本,serializeAssistant()也直接传递文本。随后JSON.stringify()生成请求体,但发送前没有检查或规范化孤立代理码元。相关代码:
serialize.ts中的flattenText()adapter.ts中的请求序列化与发送建议同时调查异常码元的最初来源,特别是所有按 UTF-16 代码单元截断工具结果、日志内容或上下文的路径。若截断点落在代理对中间,原本合法的补充平面字符也会变成孤立代理码元。
2. 非 JSON 错误正文被丢弃
packages/llm/llm-deepseek/src/adapter.ts在response.ok === false时只调用response.json()。本次服务端返回application/octet-stream纯文本,因此 JSON 解析进入空catch,服务端的具体错误正文没有传递给用户。相关代码:
adapter.ts的非成功响应处理3. 持久化坏历史缺少恢复入口
一旦异常文本进入会话日志,用户在原会话内无法通过重试修复。界面也没有提示用户从上一成功步骤分叉,导致表面上相同的
INVALID_REQUEST可以无限重复。修复建议
String.prototype.isWellFormed()和String.prototype.toWellFormed();也可以实现等价的、经过测试的替换逻辑,将孤立高/低代理码元替换为U+FFFD。Content-Type尝试解析 JSON;无法解析时保留经过长度限制和敏感信息处理的纯文本错误。建议的回归测试
reasoning_content包含孤立代理码元;text/plain或application/octet-stream正文;影响
任何工具输出、外部文件内容或截断逻辑只要产生一个孤立 UTF-16 代理码元,就可能使当前会话的所有后续请求失败。由于错误会随会话历史持久化,用户无法通过普通重试恢复;同时,当前错误处理隐藏了服务端正文,使问题容易被误判为模型、网络、API Key、上下文长度或工具调用关联故障。
隐私说明
本报告没有包含 API Key、Authorization 请求头或完整会话正文。若维护者需要原始请求进行复现,建议通过私密渠道传输,并在分享前移除凭据、个人路径和无关会话内容。
All reactions