LLM工具误选实战:用pre_gateway_dispatch钩子纠正语音备忘技能

通过网关层钩子拦截并注入硬编码指令,彻底解决Qwen模型将语音备忘误判为内置memory工具的问题。
本文记录了一次真实的AI Agent调试过程:在基于Hermes Agent与Qwen 3.6-35B模型的语音备忘系统中,以韩语"메모"开头的语音指令始终被模型路由至内置`memory`工具,而非专用`voice-memo`技能,技能激活率为0/3。根因是网关仅向模型传递纯文本,缺乏语音来源标记,导致模型无法区分随口提及与正式命令,只能凭字面语义猜测。修改激活条件与优化Whisper提示词均告失效,作者转而编写`pre_gateway_dispatch`钩子,在消息进入模型前进行轻量预转写与关键词检测,匹配成功则注入硬编码约束指令,明确禁止调用memory工具。修复后技能激活率达3/3、memory工具零调用。核心结论是:当提示工程反复失效时,在数据流层面施加强约束往往比继续调教模型更可靠。
在构建语音驱动的自动化系统时,一个看似简单的问题常常让开发者抓狂:模型明明收到了正确的指令,却偏偏调用了错误的工具。这篇来自真实工程实践的调试记录,展示了如何在Hermes Agent上解决语音备忘(voice-memo)技能被误判为内置记忆工具的问题——核心手段是绕过提示工程,直接在网关层拦截并改写消息。
问题场景:转写成功,技能却从不触发
整套系统运行在NVIDIA DGX Spark上,搭载Hermes Agent(v0.20.6)与Qwen 3.6-35B模型。预期流程是:用户通过Telegram发送语音备忘,由本地Whisper服务转写,再由Agent将文本整理成结构化文档,保存到/sandbox/notes/memos/目录。
问题出在技能激活环节。第二轮测试中,尽管语音转写全部成功,voice-memo技能的激活率却是0/3。转写文本正确地以韩语单词메모(意为"备忘")开头,但Qwen模型始终选择默认的memory工具来存储信息,而不是调用专门的voice-memo技能。结果所有笔记都被写进了通用的MEMORY.md文件,根本没有被加工成目标目录下的结构化Markdown文档。

两次失败的尝试
作者首先尝试了最直觉的办法——修改技能激活条件,让转写文本以메모开头时触发。结果失败了:模型依旧优先选择memory工具,无视了激活条件。
第二个尝试针对转写质量,给Whisper STT引擎增加了技术术语提示词以减少转写错误。这一步部分有效,在特定测试用例中把错误从6个降到了3个。但这只是提升了输入质量,并没有触及工具选择的核心矛盾。
两次尝试的共同启示是:当问题根源在模型的语义理解层面时,调优输入或微调条件往往治标不治本。
根因:模型层面的语义误解
真正的病灶是模型层面的语义混淆。当Agent收到以메모开头的消息时,Qwen模型把意图理解为"记住这件事",于是调用了内置的memory工具。
关键在于,Hermes v0.20.6的网关只是把转写后的文本作为一个普通的引号字符串传给模型,没有附带任何语音标记或来源信息。这意味着模型无从区分两种情况:一种是对话中随口提到"备忘"这个词,另一种是触发voice-memo技能的正式命令。缺少上下文信号,模型只能凭字面语义猜测,而猜测的结果恰好是错的。
这类问题在工具调用(tool calling)场景中相当典型:功能相近的工具之间,模型的选择高度依赖输入信号的明确性,而不是开发者的"善意期望"。
工具调用(Tool Calling / Function Calling)是当前大语言模型Agent架构的核心机制:模型在生成回复时,可以输出一段结构化的"调用意图",由框架层实际执行对应函数后再将结果返回给模型。这一机制的副作用是工具选择高度依赖模型对输入语义的理解,而非硬编码的规则路由。当系统中存在语义相近的工具时——例如本文中"记录一条备忘"与"调用voice-memo技能"在字面含义上几乎重叠——模型会在推理阶段根据上下文概率分布"猜"出最合适的工具。如果输入信号不够显著,模型往往选择它在预训练语料中见过更多次的通用工具(如memory),而非针对特定场景设计的专有技能。这种选择偏差本质上是一个概率问题,无法单靠调整激活条件或优化上游输入来彻底消除。
解决方案:pre_gateway_dispatch钩子
作者的做法是编写一个自定义Hermes插件,使用pre_gateway_dispatch钩子。这个钩子会在消息到达模型之前进行拦截:
- 对于音频消息,先做一次轻量的预转写(约0.7秒),检查首词是否为
메모; - 对于文本消息,检查是否以
메모:开头。
一旦匹配成功,插件就改写消息,注入一段硬编码指令,明确告诉模型:这是作者的备忘,不要使用memory工具,而要调用名为voice-memo的技能并严格执行。核心逻辑如下:
SPOKEN = re.compile(r"^\W*메모(?=[\s,.:!?~…]|$)")
TYPED = re.compile(r"^\s*메모\s*[::]")
INSTRUCTION = ("[voice-memo] This message is the author's memo (it starts with the word 메모). "
"Do not use the memory tool and do not continue earlier topics. "
"Call skill_view with name \"voice-memo\" and follow it exactly...")
def on_dispatch(event=None, **_):
try:
kind = getattr(getattr(event, "message_type", None), "value", "")
text = (getattr(event, "text", "") or "").strip()
if kind == "text" and TYPED.match(text):
return {"action": "rewrite", "text": f"{INSTRUCTION}\n\n{text}"}
media = list(getattr(event, "media_urls", None) or [])
if kind in ("voice", "audio") and media and SPOKEN.match(_transcribe(media[0]).strip()):
return {"action": "rewrite", "text": f"{text}\n\n{INSTRUCTION}" if text else INSTRUCTION}
except Exception as e:
logger.warning("voice_memo: 판단 실패 %s", e)
return None
钩子的注册也很简洁:
def register(ctx):
ctx.register_hook("pre_gateway_dispatch", on_dispatch)
值得关注的是异常处理设计——即便钩子判断失败,消息仍按原样放行,保证了对正常对话流程零干扰。这种"失败即降级"的工程防御是生产级插件的基本功。
钩子(Hook)机制是中间件与插件架构中的经典模式:框架在处理流程的关键节点预留"插槽",允许外部代码在不修改核心逻辑的前提下注入自定义行为。pre_gateway_dispatch 即"网关分发前"钩子,其触发时机位于消息进入大模型推理之前、完成来源校验之后,恰好处于可以安全改写消息内容的窗口期。相比在提示词(System Prompt)中添加软性指令,网关层的硬编码注入具有决定性优势:它是同步且确定的,不依赖模型对提示词的遵从程度。这也体现了"防御性工程"(Defensive Engineering)的思路——在数据流的物理路径上施加约束,而非依赖模型的"理解意愿"。类似的拦截机制在内容过滤、权限校验、请求改写等场景中被广泛采用,是生产级Agent系统的常见架构手段。
结果:3/3成功,memory工具零调用
第三轮测试中,修复完全奏效:文档保存成功率达到3/3,memory工具被调用了0次,所有笔记都正确进入目标目录并被整理成结构化文档。
从0/3到3/3的提升,验证了一个判断:当提示工程反复失效时,在数据流层面注入强约束往往比继续调教模型更可靠。
经验总结与遗留问题
作者提炼了两条可复用的经验:
- 当LLM持续把某条命令误判为另一个工具时,不要单纯依赖提示词,而应使用预分发钩子注入明确约束;
- 对于语音驱动的自动化,在网关层做一次轻量预转写,可以提供引导模型行为所需的关键上下文。
系统仍有未解决的问题。其一是中断处理:当Agent正忙时若有新语音消息到达(busy_input_mode: interrupt),当前轮次会被打断。其二是转写幻觉——Whisper在静默段末尾偶尔会凭空添加"Thank you"之类的内容。
这些遗留问题提醒我们,语音自动化链路上的每一环(STT、网关、模型、并发调度)都可能成为误差来源,端到端的稳定性需要在多个层面协同治理。
注:原文由作者的本地Gemma模型根据提交日志与实验笔记撰写,并由本地Qwen3.6模型对照核实,未经人工编辑。这一元信息本身也折射出本地LLM辅助工程记录的新实践。
相关推荐

Claude Code+Codex多智能体系统实测:缓存率98%背后的真问题
开发者公布Claude Code+Codex多智能体系统7天实测:缓存率98%、输入输出比354:1,作者却卡在组织调度与输出比例,并追问行业基准与成本度量方法。

Cursor SDK 新功能:实时操控运行中的 AI 代理
Cursor SDK 新增 run.steer() 功能,允许开发者在 AI 代理运行中实时注入消息,调整执行方向。子代理遇 steer 时自动转入后台继续任务,为 AI 编程工作流带来更灵活的运行时干预能力。

如何评估AI生成的研究摘要?三个关键维度
AI生成的研究摘要读起来流畅,但事实未必可靠。本文基于Reddit讨论,梳理评估AI研究摘要系统的三个核心维度:来源可追溯性、事实一致性与不确定性处理,并探讨其对深度学习从业者的实践意义。