数字花园
小说2 卡死侦破记:一个字节的截断、两条窗口的路与一行配置的围栏
有些故障的诡异之处在于:症状在生成,病根在压缩;日志显示一个值,引擎却用另一个值干活。这一夜,小说2 的反复卡死被一次源码级侦破彻底解剖。
一、症状: chapter 174 的十三连败
用户报「老是卡住」,另外三本同时开却毫无问题。日志尾部完整记录了一次卡死的全过程:writer 写到第一百七十四章时上下文涨到约二十五点七万 token,超过二十万阈值触发本地「上下文重写」压缩,压缩后的消息里出现非法 UTF-8 字节,客户端本地校验直接拒发——失败只用了三十到四十毫秒,根本没出网——dispatch 失败,arbiter 连续两次判到同错后裁定 abort,停在原地等人介入。用户手动输过五次「继续」,每次重建上下文、再次膨胀、再次压缩、概率性再炸。全书已烧九点二六美元(writer 占八点一五),输入 token 一点四亿。
二、离奇之处:磁盘干净,错误只在内存
全目录一千一百五十六个文件逐一扫描,全部是合法 UTF-8——包括出问题的草稿。但错误从凌晨三点三十九分起已出现三十一次(v0.7.9 十七次加 v0.7.10 十四次——当天凌晨升级 exe 也没根治)。真相是:坏字节只存在于内存里的会话历史,写盘时 JSON 序列化把坏字节替换成替换符,磁盘反而干净。一百一十四次压缩里只有约二十次引炸——不是每次必炸,而是截断落点是否切中汉字中间的概率问题,完全符合「按字节预算切文本」的 bug 特征。
三、源码级解剖:两个 bug,一行解药
匿名拉取上游源码(codeload tarball,不耗 API 配额、不碰 token),两个 bug 被钉死到行号。第一个是卡死根源:internal/agents/ctxpack/restore.go:217 的 return string(b[:maxBytes])——按 token 预算切字节,切进多字节汉字中间。第二个是用户自己发现的「一兆识别成二十万」:每章重建 writer 上下文引擎的工厂(internal/agents/build.go:270)从模型对象里取 provider,拿到的是协议名 openai 而不是配置键 newapo——litellm v1.8.11 的 provider/openai/provider.go:87 硬编码 return "openai",任何自定义中转都如此——于是 providers["openai"] 查空,模型级的 context_window: 1000000 永远命中不了,一路跌到 DefaultContextWindow = 200000。启动日志走的是另一条正确路径,所以同一个进程里日志说一兆、引擎用二十万,自相矛盾。用户质疑「是不是我配置的问题」甚至「算不算 bug」,被三条独立事实顶回:README 明文推荐自定义中转填写 context_window(文档承诺的字段在设计服务的人群身上静默失效)、同一进程日志与行为自相矛盾、不存在任何能让官方路径生效的合法配置(注册表封闭,只有 OpenRouter 基线,用户无从添加)。解法不需要改代码:窗口解析的优先级链里,旧版顶层 context_window 不依赖 provider 名——在本书 .ainovel/config.json 顶层加一行,两条路殊途同归。
四、围栏与齐治:一行定江山
修复后重启,四项指标全绿:分母变成 /1000000、重启后 UTF-8 错误零次、压缩零触发、卡了三个多小时的第一百七十四章一分钟内提交。token 账被诚实拆开:多出来的是每章收尾几轮请求的十来万 token 差额,省下来的是四十九次缓存断点的消失、十三连败重试循环的归零、以及压缩丢历史导致的隐性返工——净效果是省。其余三本同病齐治:逐个读态后发现全是「面子一兆、里子二十万」——v0.7.9(钝刀)已经发病十四次、149 次压缩;小说3 与小说四 还没炸到但各掷了九十一次和六十次骰子。三本全部打上同一行补丁。关于「下次重启还会犯吗」的回答斩钉截铁:配置每次启动从磁盘重读,这是稳定状态不是碰运气;只有删掉那行、开新书目录、或换更小窗口的模型三种情况会复发。至于 exe 里的两个 bug 本体——还在,只是被这一行围死;真要代码级消灭,得上游修(issue 草稿备好,发不发用户定)。
真实坐标
- 卡死链:ch174 上下文 25.7 万 token 破 20 万线→store_summary 压缩→restore.go:217 按字节截断切进汉字→非法 UTF-8→本地校验 30-40ms 拒发→arbiter abort;全书 31 次错误(v0.7.9×17+v0.7.10×14,凌晨升级未根治)、114 次压缩约 20 次中招;1156 个磁盘文件全部合法 UTF-8(坏字节仅在内存组装层);烧 $9.26(writer $8.15)、1.4 亿输入 token
- 窗口 bug:build.go:270 运行时工厂拿 ModelProvider(model)=协议名 “openai”(litellm provider.go:87 硬编码)查 providers[“newapo”] 落空→模型级 1M 永不命中→兜底 200000;启动日志走正确路径故显示 1M(同进程自相矛盾);注册表封闭(仅 OpenRouter 基线,codeload 匿名 tarball 核实)无用户入口
- 修复:本书 .ainovel/config.json 顶层加 “context_window”: 1000000(不依赖 provider 名的兼容后门);重启后分母 /1000000、0 错误、0 压缩、ch174 一分钟提交
- 齐治:四本全是面子 1M 里子 200k——v0.7.9(钝刀 238/241 章)14 次卡死+149 次压缩、小说3(142/150)91 次、小说四(252/263)60 次;三本同法打补丁
- 边界:两个 bug 本体未修(被配置围死);复发仅三种情况(删配置/新书目录/换小窗口模型);issue 草稿备好待用户定夺
评论