[2026-11-09 23:50] │ entry #5447

它宣告了下一步,却没伸手:给中文 Agent 补上防停住的闸门

2026 年 11 月 9 日 │ LLM与AI │ 0 评论 │ 阅读 0

周末跑《道陨苍穹》第一章的分镜任务,会话跑着跑着又停了。没有报错,没有被中断,日志里干干净净一个 Turn ended: reason=text_response(finish_reason=stop)。模型最后一句话是:「接线细节要按 v0.3.1 源码确认,先从 zip 里读 Load/Save 的实际行为:」——然后就没了。它宣告了下一步,但没动手。我又得回去敲一个”继续”。

这种”说到一半停住”我之前遇到过几回,一直以为是玄学,这次决定查个明白。Hermes 的会话都存 state.db(SQLite),消息流、每次 API 调用的 token 构成、思维链全文都在里面,配合 agent.log 基本可以完整还原案发现场。查出来很有意思:那次调用的思维链是完整的,最后一句规划是”让我从 zip 里提取 nodes.py 的 Load/Save 实现,这是必要的先决检查”——想清楚了,可见正文只吐出那句宣告,工具调用一次都没发。轮次就这么带着”complete”收场了。

差点冤枉了 thinking budget

第一反应是老问题:推理模型的思维链把预算顶满了,只来得及吐一句收尾文本。之前 32k 预算时代确实栽过,指纹很明确——输出 token 数逼近预算整数值(32,830 = 32,768+62 这种)。但这次实测服务端 GET /config,strata 的 reasoning_budget_tokens 早就调到 131072,而那一次调用总共才输出 1215 个 token,离预算差两个数量级。不是想不完,是想完了没伸手。

顺着这个思路翻 Hermes 源码,才发现框架其实早就防了这一手:有个 stall guard,轮次以纯文本收尾、但话尾是在宣告下一步动作时,会自动塞一条 nudge 逼模型继续干活。问题出在它的检测正则上——

r"(?:\blet me now\b|\bi(?:['’])?ll now\b|\bi will now\b
 |\bnow i(?:['’]ll| will)\b|\bnext[,:] i\b)..."

全英文。let me now、I'll now、next, I,一个中文词都没有。「先从 zip 里读……:」「接下来我核对一下。」这种中文宣告,正则看都不看一眼,直接放行。换句话说:这个防呆机制对说中文的 agent 基本失效。我拿自己会话里真实停住的那几句原文喂进检测函数,全部判 False,实锤。

打补丁:三层判断,一个闸门

改法不复杂,在 agent_runtime_helpers.py 里给 stall guard 补两个中文友好的判据,但里面有两个坑值得说。

一个是字符类的坑。中文句子的”句子结束”是。!?,我第一版把 ASCII 句点也排除了,结果”先看 v0.3.1 的源码”里 v0 后面那个点把匹配拦腰截断,直接漏判。中文版字符类里必须放掉 ASCII 句点。

另一个更要紧:中文触发词太容易误伤。”接下来””让我”这种词,正经回答也会用——”接下来我们看三种方案。”这是在回答问题,不是在宣告动手,催它一把纯属浪费。英文的 let me now 形状窄,误伤面小,中文不行。所以最后的设计分三层:

  • 英文尾、裸冒号尾(回复最后一个字是”:”,任何语言都是话没说完)——无条件触发;
  • 中文触发词尾——加闸门:本轮已经真正调用过工具才触发。纯问答零工具调用,不打扰;任务中途的停住,才值得催。

这个”本轮已干过活”的闸门是另一个 AI 复盘我的补丁时点出来的,确实是四条意见里最值钱的一条。它还纠了我两处:正则不该排除 ASCII 句点(同上)、”我先”太宽(”我先说一下结论”是答案不是宣告,得收窄成后接动作动词才算)。我原本只跑了十一组临时用例验证,它要求固化进仓库测试文件——也对,不然下次升级一重构就悄悄回归了。这轮复盘让我想起之前查 Strata 推理循环那次:模型自己看不见自己的盲区,但换一个模型逐条对证据审,命中率很高。

几条带得走的经验

一、先拿数值指纹,再下结论。“停住了”有三个嫌疑人:预算顶满、被中断、模型没动手。三者在 state.db 里指纹完全不同——out 逼近预算整数是前者,日志有 Interrupt 是中者,思维链完整收尾+零工具调用是后者。这次要是凭”之前就是预算问题”的印象直接去调服务端,方向就错了。

二、agent 框架里的行为检测默认是英语写的。不只是 Hermes:意图分类、中间宣告识别、”看起来像没说完”这类启发式,正则和关键词表大多按英文形状设计。中文 agent 用久了遇到的”玄学”,相当一部分是这些英文中心的检测器在静默失效。修法是通病,不是一家之毛病——这也是我愿意给上游提 PR 的原因。

三、给模型”催活”的机制要有闸门。宁可漏催一轮,不要错催纯问答。”本轮已有工具活动”是个便宜且可靠的信号:真 stall 必然发生在任务中途,纯聊天永远零工具。

四、本机补丁要留重放路径。/usr/local/lib/hermes-agent 恰好是 git 仓库,改完 commit 两个、git format-patch 存到硬盘、提示词和 diff 推上 Gitea——升级覆盖后 git am 一把重放加跑一遍测试就回来。另一台机器不是 git 仓库也不怕,照提示词直接打 diff。

现在新会话里它要是再敢”宣告完就站着”,日志里会冒出一条 Stall guard: ... re-prompting (1/2),然后自己把活干完。这个周末,我不用再当那个喊”继续”的人了。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。