[Bug] 流式工具调用增量携带空 id/name,导致会话历史永久无法加载:SessionPersistenceCorruptionError: message must have tool source #2169
Replies: 5 comments
|
你发现的链路完全正确,而且这是已知家族的一次关键升级——它把"每回合功能破坏"(#1713/#2090 的显式 null 变体)升级为"会话永久毒化"。我逐一验证,并有一个对现有修复的关键修正。 1. 链路验证(master 47f9438)
2. 关键修正: 我在 #1713/#2090 上验证过的修复是 if (call.id) block.callId = call.id // truthy: 拒绝 null/undefined/"" 三者
if (call.function?.name) block.name = call.function.name(真实的 call id / 工具名必然非空;首帧已捕获真实值,续帧只有携带非空值才应该覆盖。)需要补一个空字符串回归用例: 3. 家族升级:wire 家族获得"永久毒化"放大器
4. 恢复建议 会话日志本身只是被空 callId 的 5. 修复顺序建议:wire 层守卫(truthy 检查)是根修复——毒化永不产生;session 层校验(index.ts:340)是正确的防线,不应放宽(空 callId 的 result 确实是损坏数据)。请把这个空串用例同步到 #1713/#2090 的修复分支上,让一个 fix 覆盖两个 provider 变体。 |
|
同样问题,在 0.1.1-rc.2(2026-08-21 release,Windows 10,Node v24.14.0,源码 zip 构建)上仍然复现。 症状与楼主一致: 按楼主和 argszero 的思路手工修复日志后历史恢复加载:
|
|
同根因。我在 #4365 也报了官方端点的空字符串变体(把失败钉死在最后一个 delta 帧),argszero 已经把这几个串成一个 identity-loss family,并给出了引擎侧修复蓝图。 在引擎 patch 落地之前,已经被毒化、load 不起来的会话不必再手工切帧了——我做了一个恢复插件: dsh-session-repair(https://github.com/Zn-Dk/dsh-session-repair)
它只做「恢复已损坏的历史」,不碰引擎写入路径——根治还是等 argszero 的引擎 patch(L1-L4 + 三态回归)。 |
|
根因与 truthy 守卫的结论都对。但修复建议里的第 2 处( 原因: if (payload === DONE) {
for (const block of order) {
yield { type: 'block-end', index: block.index, block: closeBlock(block) }
}而 case 'tool-call': return {
type: 'tool-call',
id: CallId(block.callId ?? ''),
name: block.name ?? '',
arguments: block.text,
}
private assemble(partial: PartialBlock, index: number): ContentBlock {
if (partial.block) return partial.block所以: 结论:本帖唯一有效的修复点是 顺带确认下游链路(tip 无法通过用户配置规避:没有任何配置项能改变这两个守卫,只能改 adapter 源码,或在 |
|
I implemented and published a tested reference patch for this failure chain on exact upstream base Reference implementation: The patch deliberately treats the DeepSeek root fix and the shared hardening as two different layers:
Deliberate boundary: the strict session loader is unchanged, and this does not silently repair already-corrupted logs. Existing damaged histories still need an explicit offline repair/backup workflow. Verification on the exact published commit:
The Agent Note credits the reporters and the prior analyses/reference patches that informed this implementation, without claiming original discovery or code co-authorship. This is a public reference implementation for review, not a claim of official upstream adoption. |
Uh oh!
There was an error while loading. Please reload this page.
版本 / Version
@deepseek-ai/dsh 0.1.0-rc.5(本地 checkout 构建运行)
复现环境:Linux,Web GUI 打开会话历史(localhost:3080)
现象 / Symptom
某个会话的历史无法加载,接口返回:
GUI 侧显示「历史加载失败」。该会话历史之后每次都触发同一错误(无需任何其他操作)。
复现 / Reproduction
deepseek-official/deepseek-v4-pro)发起一次工具调用(如read)。id/name,后续参数增量携带显式空字符串id: ""/function.name: ""(字段存在,而非省略)。tool-callblock 的id被清空 → agent 以空工具名执行 →ToolNotFoundError: unknown tool ""→ 持久化出tool/result且message.source.callId === ""。会话 JSONL 中的关键证据(seq 165496 → 165508 → 165512 → 165513):
assistant/chunk首个 tool-call-delta:id: "call_edb776167f1347a3bad1011e", name: "read", argumentsDelta: "";tool-call-chunks:id: ""、name: ""(证实后续增量确实带空串,而非省略);{"type":"tool-call","id":"","name":"","arguments":"{…}"};tool/result:source.callId: ""、content[0].toolCallId: ""→ 重放校验拒绝,历史整体无法加载。根因 / Root cause
packages/llm/llm-deepseek/src/translate.ts(约 159–160 行)在每一个 tool_call 增量上无条件覆盖块身份:当后续增量携带
id: ""/name: ""(字段存在但为空)时,真实 id 被空串覆盖。已用真实源码独立复现:同样的流,带空串增量 → 最终 blockid: "";不带 → 正常保留id: "call_edb…", name: "read"。次生隐患:
packages/llm/llm/src/assembler.ts(约 70 行)同样无条件partial.toolCallId = chunk.id,会覆盖首个非空 id。客户端PartialAccumulator已是「首个 id 固定」的语义,BlockAssembler应保持一致。校验侧(
packages/core/session/src/index.ts的assertMessageEventShape)对任何source.callId === ""的tool/result抛错,属防损坏的有意检查;但坏数据已持久化,导致该会话历史永久无法加载。修复建议 / Suggested fix
两处(本地已验证):
修复后同样的流,最终 block 保留
id: "call_edb…"、name: "read"。另外建议从源头缓解:adapter 侧对空 id/name 做规整(不覆盖),或在持久化/校验侧容忍空 callId 并合成兜底 id,避免再次产生「历史无法打开」的会话。已损坏的会话需要手工修复日志(把空
source.callId/toolCallId填回真实 id);注意.jsonl.zstd是多帧格式(首帧为 header 一行、带校验和),修复重压时需保持一致,否则会报corrupt Zstandard session log: first frame is not exactly one header line。影响 / Impact
任何一次「流式工具调用后续增量携带空 id/name」的会话,其
tool/result会以空callId持久化,该会话历史在 GUI 中永久无法打开(agent 侧仍可运行,仅历史展示与续读失败)。遇到同样问题的朋友请给本帖点赞/upvote,方便官方按优先级处理。All reactions