WeClaw_91|双引擎意图识别:一次「配置开了却没生效」的排查,揭开的链路真相
系列文章第 91 篇 - 双引擎架构 · 规则引擎兜底 · 链路分工 · 子开关灰度 · 首 token 延迟
📚 专栏信息
《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏
专栏定位:面向开发者和技术决策者的实战专栏,用真实案例和完整代码带你理解如何构建生产级 AI 应用
本文记录一次认知反转。配置文件里明明写着 intent_mode = "llm",我们却一度以为「意图识别已经用上大模型了」——直到一轮对话追问「现在的对话应该都是 chat_stream 吧?」,顺藤摸瓜才发现:日常每一次交互走的流式链路,意图识别仍然是纯规则引擎,LLM 增强只惠及两个低频后台场景。这篇讲清楚双引擎架构的设计逻辑、chat 与 chat_stream 的真实分工,以及为什么「开了配置却没生效」恰恰是这个架构做对了的证据。
👨💻 作者与项目
作者简介:翁勇刚 WENG YONGGANG 新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者 理念:"再复杂的技术,也能用代码讲清楚"
- 💻 项目地址:https://github.com/wyg5208/weclaw.git
- 🌐 官网地址:https://weclaw.link
- 📝 作者 CSDN:https://blog.csdn.net/yweng18
- ⭐ 欢迎 Star⭐、Fork🍴、贡献代码🤝
📝 摘要
本文结构概览:
0.1ms 与 1.5s:一次基准测试揭示的四个数量级鸿沟(数据先行)→ 为什么双引擎不是「过渡方案」而是终局架构(设计哲学)→ 一次排查:intent_mode="llm" 为什么没有改变日常体验(链路真相)→ chat 与 chat_stream 的分工地图 → 子开关灰度:把增强能力先放进配置里,等数据说话(渐进放开)。
核心结论:
- 规则引擎与 LLM 的延迟差距是四个数量级(0.1ms vs 1.5s),这个差距大到足以重塑架构——不是选哪个引擎的问题,而是两个引擎各守一段链路;
- 「配置开了却没生效」不是 bug,而是链路门控的设计结果:配置项只决定「引擎可用性」,调用链决定「引擎是否被允许上场」;
- 理解一个配置的真实作用域,必须追到每一个调用点的 allow_llm 参数——配置文件的语义是由调用链共同定义的。
一、一组让架构自己现形的数字
在给流式链路接入 LLM 意图识别之前,我们先跑了一组基准(n=30):
| 引擎 | 单次意图识别耗时 | 依赖 |
|---|---|---|
| 规则引擎 | 0.1ms(均值=最大值) | 无,纯内存 |
| LLM 分类(正常) | 1.51s | 网络 + 模型服务 |
15,000 倍。这不是「快一点」,是两个完全不同的时间物种。
这组数字直接否决了一个诱人的想法——「干脆全链路切 LLM,规则引擎退役」。因为意图识别在流式对话中位于首 token 之前串行执行:它的耗时原封不动地加在用户等待第一个字的时间内。全量切换意味着每条消息凭空多等 1.5 秒——而规则引擎在 90% 的常见意图上已经足够准。
于是架构选择变得清晰:规则引擎是地基,LLM 是可选的精装修。双引擎不是 LLM 成熟前的过渡态,而是终局形态——因为两者的成本曲线永远不会收敛。
二、双引擎的分工契约
用户输入
│
▼
_route_intent_detection(user_input, allow_llm=?)
│
├─ allow_llm=True 且 intent_mode=="llm" 且熔断器放行
│ │
│ ▼
│ LLM 分类(6s 超时 / 1 次快失败重试 / 3 次熔断)
│ │
│ ├─ 成功 ──────────────► 返回 LLM 意图(更准)
│ └─ 失败/超时/熔断 ─┐
│ ▼
└──────────────► 规则引擎(0.1ms,永远在场)──► 返回规则意图
契约只有三条,但每条都是硬约束:
- LLM 永远只是「增强」:它的产出可以被整体放弃,放弃后系统行为等价于没接 LLM;
- 规则引擎永远在场:任何降级路径的终点都是它,不存在「两个引擎都失败」的状态;
- 门控由调用点声明:
allow_llm是每个调用点自己决定的,不是全局状态——这一点是后文排查的关键。
三、排查实录:配置开了,为什么日常体验没变
3.1 困惑的起点
配置文件里白纸黑字:
intent_mode = "llm"
intent_llm_model = "deepseek-v4-flash"
intent_llm_timeout = 6.0
intent_llm_in_stream = false
按直觉,intent_mode = "llm" 应该让所有意图识别走 LLM。但日志里日常对话的意图识别耗时全是亚毫秒级,LLM 调用记录寥寥无几。配置生效了吗?
最初的回答差点出错:「chat 链路已开 LLM 意图识别」——这句话技术上没错,但隐含了「日常对话受益了」的错误推论。一句追问戳破了它:
「我有点迷惑,现在的对话应该都是 chat_stream 吧,chat(普通对话)在哪个环节会发生?」
3.2 追调用点
顺着两个入口函数搜调用方:
Agent.chat_stream(流式) —— GUI 主窗口的每一条消息都走这里:打字输入、语音识别后的转写文本,全部经由 GuiAgent.chat(UI 包装层)最终落到 Agent.chat_stream。它的门控是:
intent_result = await self._route_intent_detection(
user_input, allow_llm=self._intent_llm_in_stream, # ← 子开关,默认 False
)
intent_llm_in_stream = false ——日常交互的意图识别就是纯规则引擎,与 intent_mode 无关。
Agent.chat(非流式) —— allow_llm=True,确实享受 LLM 增强。但它的调用方只有两处:
| 调用点 | 场景 | 频率 |
|---|---|---|
src/tools/cron.py | 定时任务执行(让 Agent 跑一条预设指令) | 每天几次 |
src/remote_client/client.py | 远程 PWA 流式失败时的降级兜底 | 极少数 |
3.3 真相拼图
配置 intent_mode = "llm" → 决定「LLM 引擎是否可用」
调用点 allow_llm → 决定「这条链路是否允许使用」
实际行为 = 两者取交集
日常交互(chat_stream):intent_llm_in_stream=false ∩ llm可用 = 规则引擎
定时任务(chat): allow_llm=True ∩ llm可用 = LLM 增强
远程降级(chat): allow_llm=True ∩ llm可用 = LLM 增强
intent_mode="llm" 确实生效了——只是它惠及的是两个低频后台场景。配置没有撒谎,是我们误读了配置的作用域。
3.4 为什么没有「chat/stream 切换开关」
排查中自然产生的下一个问题:能不能配置让日常对话走 chat(从而用上 LLM 意图)?答案是:没必要,也不应该。chat 与 stream 的分工由场景天然决定——UI 交互必须流式(用户要看着字逐个蹦出来),后台任务只要最终结果(没有人在盯着屏幕)。把「用哪个引擎」耦合进「用哪条链路」,会让两个正交的关注点互相绑架。正确的解耦方式就是现在的样子:链路选链路,引擎选引擎,用子开关在交叉点做决策。
四、子开关灰度:这套架构里最新的一块拼图
既然日常交互要不要上 LLM 是个开放问题,我们给它配了专用的门:intent_llm_in_stream(默认 false)。设计上有三个刻意的细节:
1. 默认关闭,收益要用数据换。 首 token 延迟实测基准写进了交付记录:开关闭 = 0.1ms 基线不变,G4 行为基线无需重建;打开 = +1.5s 量级。要不要付出这个代价,留给真实使用数据决定,而不是架构讨论拍脑袋。
2. 独立于 intent_mode,语义单一。 intent_mode 管「引擎可用性」,子开关管「流式链路是否放行」。两个配置正交,互不覆盖——将来任何链路想单独试验 LLM,只需要自己的开关,不用动全局。
3. 配置模板即文档。 config/default.toml.example 中子开关旁边就是注释说明延迟代价。配置文件是给六个月后的自己和接手的人读的,每个布尔值都应该自带「翻转它会发生什么」的说明书。
五、可复用的方法论
- 延迟差超过两个数量级,就值得改变架构。0.1ms vs 1.5s 不是选快慢的问题,是「谁守在线路径、谁守在离线路径」的分工问题。
- 兜底引擎必须永远在场。双引擎的价值不在 LLM 多准,而在 LLM 挂了、慢了、熔断了的时候,系统行为等价于从未接过 LLM。
- 配置的语义由调用链定义。读懂一个配置,要追完所有消费它的调用点;只看配置文件会得出「全链路生效」的错误直觉。
- 「没生效」可能是设计在保护你。本次排查的结局不是修 bug,而是确认门控工作正常——默认关闭的增强项,就该在显式打开前保持静默。
- 链路和引擎正交解耦。场景决定链路(UI 流式/后台非流式),策略决定引擎(规则/LLM),交叉点用独立子开关——任何一方的演进都不牵连另一方。
六、总结
这次迭代最终交付的意图识别体系,是一张三层的决策网:底层是 0.1ms 的规则引擎(永不停机),中层是带全套保险的 LLM 增强(6 秒超时、熔断、纯函数解析),顶层是逐链路声明的门控策略(chat 开、stream 配置化、deferred 永久关)。intent_mode="llm" 打开了中层的能力,但上层门控决定能力落在哪些链路上——这次排查最大的收获,是把这张决策网画进了团队的共同认知里。
架构的正确性有时不以「功能生效」来证明。一个默认关闭、按需放开的开关,在打开之前安安静静地保持静默——这正是它被设计出来的样子。
版权声明:本文为 CSDN 博主「翁勇刚」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。