规划输出被截断,半截内部协议直接显示给用户¶
用户在聊天窗口里看到一段被截断的动作协议 JSON,形如 {"action":"answer","answer":"...——内部协议的开头加半个答案,然后断掉。对应调用记录里 completion_tokens=240,finish_reason=length。当时的实现后来已重构,这里按重构前的行为记录。
排查¶
用户层直接出现内部 JSON,先怀疑规划和生成两步的职责没拆开。链路里这两步是两次模型调用,各有一条用量记录,确认这个怀疑要回答两个问题:这段文本产自哪次调用,它为什么断。
第一步看返回文本的形态:
规划器的输出就是动作 JSON,正式回答链不产出这种格式。开头命中协议前缀,说明用户拿到的是规划器的输出,排查范围从整条链路收窄到规划阶段。
第二步看用量记录:
finish_reason=length 表示输出撞到 token 上限被切断,模型本身还没写完。截断停在 240,说明这次调用的输出上限就在这个量级,是按短 JSON 设的预算,放不下完整正文。规划和生成各有一次调用,同样的 length 可能来自其中任何一次,修法不同,所以要先归因到具体调用。核对记录后,截断发生在规划阶段:规划器这次被当成回答生成器用,正文写了一半,先撞上自己的小预算。
根因¶
两层原因叠加:
- 预算和用途不匹配。规划器负责选动作,输出预算是按"输出短动作 JSON"设的小 token 上限。早期实现允许规划器在选
answer动作时直接把答案写进动作 JSON,等于拿小预算跑长正文,JSON 写到字符串中间就到上限。 - 截断的输出没人拦。当时的链路不检查规划输出是否完整、JSON 是否合法,未闭合的半截协议就这样原样传到了用户层。
修复与验证¶
修复把职责拆开:规划器只输出动作 JSON;选择回答动作时由正式生成链输出正文;规划输出截断、JSON 非法、返回未知动作时统一回退到正式回答链;规划预算与用户可见正文预算分开。协议输出偶尔偏离格式时,当时允许有限次数的格式修复;持续非法则结束执行,无法解析的规划文本不进入回答。
原文没有附这一节的独立测试记录,验证手段是日志特征检查:返回文本开头是否还是协议格式、finish_reason=length 出现在哪个阶段。后续规则里,规划类模型只负责决策,内部协议不再直接当作用户可见输出。