Repository navigation
【Bug】达到最大输出长度时会话被永久写坏:后续每轮都报 invalid pi-ai replay state: block count does not match assistant content #1244
Replies: 1 comment 1 reply
|
你和 #1263 是同一个 bug 的两份独立报告,而且证据正好互补:你这边有持久化产物的实测数据(content 2 块 vs replayState 4 块、stopReason length),那边有源码级链路( 你多给出的一条事实值得单独强调:从坏会话 fork 出来的新会话也是坏的。也就是说 fork 不是逃生口——这把"一次截断报废一条会话"升级成"报废一整条血缘",严重程度比单条会话高。 能做的1. 已经坏掉的救不回来。 2. 预防:把 触发条件是"输出被 max-tokens 截断,且截断点正好落在工具调用上"。自定义 provider 没显式声明 llm-pi-ai:
providers:
你的catpaw路由:
models:
- id: glm-5.3
contextWindow: <真实上下文窗口>
maxTokens: <该模型真实的最大输出>这不修 bug,只是让截断少发生。对"后果是会话报废"这种量级,降低触发频率是有意义的。 一条我不建议你走的路有人会想到"换一条不经过 (这条正好指向我自己维护的东西,所以更该说清楚。) 边界我们不修 利益相关:我维护 pi2dsh;上面第 2 点是 DSH 官方配置,跟我的包无关。 |
Uh oh!
There was an error while loading. Please reload this page.
【Bug】达到最大输出长度时会话被永久写坏:后续每轮都报
invalid pi-ai replay state: block count does not match assistant content环境
0.1.0-rc.6(当前 npm 最新发布版),macOSanthropic-messages协议接入现象
根因分析
截断那一轮的持久化
assistant/message实测数据(来自session.jsonl.zstd):链路如下:
stop_reason: "length"→ finish chunk 的reason映射为{ kind: 'max-tokens' }packages/llm/llm/src/assembler.ts的BlockAssembler.blocks():当finish.kind === 'max-tokens'时会过滤掉所有 tool-call 块(截断的工具调用不能安全执行)——这个行为本身是对的replayStategetter 原样返回 finish chunk 附带的 adapter replay state,其blocks仍描述上游完整块列表(含被过滤的 tool-call)packages/core/agent-loop/src/agent.ts组装assistant/message处)把「过滤后的 content + 未过滤的 replayState」一起持久化,产生了自相矛盾的消息packages/llm/llm-pi-ai/src/replay.ts的replayedAssistant()逐块校验state.blocks.length === message.content.length(4 ≠ 2),抛出INVALID_REPLAY_STATE建议修复
在 assembler 侧:当 max-tokens 过滤真的丢弃了 tool-call 块时,同步丢弃 replayState。该消息随后会回退为 foreign assistant 回放(
foreignAssistant路径),永远合法,只是损失签名信息:注意只读
blocks()也能判断,但blocks()对未知未闭合块类型会抛错,getter 里用 partials 判断可以避免引入新的抛错路径;纯文本截断(无 tool-call)时 replayState 应保留,避免不必要的签名损失。两点补充建议(可选)
replayedAssistant()遇到块数不匹配时,是否可以考虑降级为 foreign 回放 + warning,而不是抛错把会话永久 brick 掉?保持校验严格没问题,但失败最好可恢复。replayState.blocks(去掉多余的 tool-call 条目,保留前缀对齐的 reasoning/text 签名)修复了自己的会话,验证了该修复方向有效——修好后「继续」能正常续跑。感谢!
All reactions