背景我在开发一个项目协同平台的嵌入式 AI 助手。它不是独立的 chatbot而是嵌在业务页面里的——用户可以在首页、项目详情页、任务抽屉等不同位置唤起它用自然语言完成任务查询、创建、删除等操作。和通用对话 AI 不同这个助手有两个硬约束它连接真实数据库。用户说删除这个任务它会真的去删。所以理解错了的代价不是回答不准确而是操作不可逆。它跑在业务页面里。用户的预期是点一下就出结果不是等 3 秒 AI 想一想。核心矛盾AI 助手收到用户输入后第一件事不是回答而是判断这句话要干什么——这就是意图识别Intent Recognition。听起来简单实际上有三个互相矛盾的需求需求含义典型场景快高频操作零延迟响应用户说我的任务不应该等 1 秒 LLM 回来才开始干活准长尾表达不误判用户说帮我把上周那几个没做完的清理一下纯规则覆盖不了稳LLM 挂了系统不白屏凌晨 3 点 LLM 服务商 503用户正在赶 deadline这三个需求无法用单一方案同时满足。纯 LLM 快不了纯规则准不了什么都不做稳不了。所以我做了分层。整体架构用户输入 PageContext | v ----------------------------- | 第一层确定性 Fast-Path | 0ms / 0 成本 / confidence 0.96-0.99 | 模式匹配 页面上下文 | 覆盖 ~60-70% 高频请求 ---------------------------- | 未命中 v ----------------------------- | 第二层LLM Structured | 500-1500ms / 有成本 / confidence 0.5-0.95 | Outputschema 约束输出 | 覆盖剩余 ~25-35% 长尾 ---------------------------- | LLM 调用失败网络/限流/服务端错误 v ----------------------------- | 第三层应急兜底 | 0ms / 0 成本 / confidence 0.1-0.5 | 关键词启发式 追问 | 保底可用不白屏 -----------------------------三层共享同一个输出协议包含四个核心字段capability识别出的业务能力任务管理、项目管理、延期分析等共 8 种null 表示闲聊mode执行模式直接执行、工具循环、固定流水线、多步编排confidence0-1 的置信度决定下游是直接执行还是先追问用户routeType标记结果来自哪一层用于监控统计和消息窗口策略下游节点不关心这个结果是哪一层产出的它只看 capability 和 confidence。三层对外是一个统一的函数签名内部是渐进降级。第一层确定性 Fast-Path为什么需要这一层先算一笔账指标LLM 路由确定性规则延迟500-1500ms 1ms成本~0.005-0.01 元/次0可靠性依赖外部服务100% 本地一个活跃用户每天和 AI 交互 30-50 次其中 60% 是我的任务“创建任务 XXX”删除这个这种高频操作。全走 LLM 意味着每天白白浪费 20-30 次 LLM 调用而且用户每次都要多等半秒。对于我的任务这种请求用户的预期是即时响应。四种 fast-path 模式模式一Quick Action前端按钮直达用户根本没有输入自然语言而是点击了一个预设按钮。比如项目详情页的分析延期风险按钮前端会带上一个 action 标识。路由器维护了一张 action → capability 的映射表大约 18 个预设操作。命中后直接返回 confidence 0.99零歧义。不需要做任何自然语言理解——按钮已经告诉你用户要干什么了。为什么 confidence 给 0.99 而不是 1.0不写 1.0 是有意为之。1.0 意味着绝对不可能错但 action 有可能从不该出现的页面触发前端 bug。0.99 表达的是极高置信但保留审计余地在评测指标统计时不会造成完美命中率的假象。模式二页面感知的模式匹配这是 fast-path 的核心也是嵌入式助手和通用 chatbot 最大的区别。举个例子用户说删除这个。纯文本删除这个——删什么在首页说这话和在任务抽屉里说这话完全不一样。所以判断逻辑不是纯文本匹配而是文本 页面上下文联合判断必须同时满足两个条件——文本匹配操作类模式且当前页面有选中的任务实体。两个条件缺一不可只有文本匹配但没有选中实体不命中无法确定要删哪个只有选中实体但文本不匹配也不命中用户可能在问别的。一个更微妙的例子用户在项目详情页说这个项目有哪些延期任务。这里延期任务同时可以匹配任务管理和延期分析两个 capability。我的处理方式是设置排除规则——当文本命中延期分析的特征时即使当前有选中实体也不走单任务操作路径。因为用户关注的是全局延期分布不是当前选中的那一个。这类互斥规则的设计原则先排除再命中。先判断一定不是什么负面条件再判断可能是什么正面条件。这样新增正面模式时不会意外吞掉已有的排除规则。模式三对话跟进多轮对话中用户的第二句话往往不会重新表述完整意图用户这个项目有哪些任务 AI 共 5 个任务1. 首页重构 2. 接口联调 3. 数据迁移 ... 用户第二个“第二个如果作为独立输入无法判断意图。但结合上一轮的 capability 是任务管理”就知道用户在引用上一轮的查询结果。跟进识别覆盖三种子模式显式跟进语“继续”“展开”“还有吗”、序数跟进“第二个”“第三个”需要上一轮是查询类操作、澄清回复上一轮 AI 在追问请告诉我任务名称这一轮用户直接回了一个标题。但这里有个容易出错的地方用户改主意了怎么办用户我有哪些任务 AI 共 3 个任务 ... 用户生成本周工作周报如果无脑继承上一轮的 capability就会把生成周报当成任务查询的跟进。所以在跟进判断之前先做强意图排除——如果用户的消息包含明确的新业务意图关键词周报、创建、删除等直接判定为独立意图不走跟进路径。原则和页面感知匹配一样先排除再命中。模式四精确短语最简单的一种只拦截完全确定的独立短句——“你好”“谢谢”再见等。严格要求整句匹配必须是句首到句尾你好帮我查一下任务不会被拦截会正常进入后续层级。这类规则的设计原则宁可漏判不可误判。漏判只是多花一次 LLM 调用误判是把业务请求当问候语忽略。Fast-Path 小结子模式匹配条件confidence占比估算Quick Actionaction 标识查表0.99~15%页面感知匹配文本 pageContext0.96-0.97~25%对话跟进文本 上一轮 capability0.88~15%精确短语独立短句匹配0.99~5%合计约60%的请求在这一层就完成路由零 LLM 调用。第二层LLM Structured Output什么时候进这一层第一层没命中说明用户的表达不在预设的高频模式里。比如帮我看看数据分析平台最近有没有卡住的任务 把上周那几个没做完的都归档掉 李四手上现在忙不忙这些表达方式多变、包含隐含语义、省略了主语或宾语规则穷举不了。约束 LLM 的输出空间直接让 LLM 自由回答这是什么意图是危险的——它可能发明一个系统中不存在的 capability 名称或者用自然语言描述意图而不是返回结构化标签。所以 LLM 的输出被 schema 严格约束它只能从预定义的枚举值中选择 capability 和 mode并给出一个 0-1 的置信度分数以及一个三分类的路由判断业务意图 / 闲聊 / 需澄清。LLM 不能越界——框架层面保证返回值严格符合类型定义。LLM 输入不是只有用户消息LLM 收到的不是裸文本而是用户消息加上一段页面上下文的自然语言摘要。比如当前页面项目详情当前已选中任务触发来源任务抽屉。注意这里只传摘要不传原始 ID。LLM 不需要知道 projectId42它只需要知道当前有项目上下文。这是上下文隔离设计的一部分——路由层只拿到裁剪后的上下文摘要不接触执行层数据。三种路由结果的处理LLM 的三分类结果各自有不同的后处理逻辑业务意图直接使用 LLM 给出的 capability 和 mode进入执行层。闲聊capability 设为 null走通用对话模型回复不触发任何业务工具。需澄清这个最微妙。LLM 说我不太确定但要分两种情况——如果 LLM 连 capability 都猜不出来说明这句话大概率不是业务意图比如今天天气真好这时候追问你想操作什么是荒谬的直接降级为闲聊更自然。只有当 LLM 猜了一个 capability 但不确定时才走追问流程并且强制把 confidence 压到 0.5 以下确保下游一定走追问而不是直接执行。重试与容错LLM 调用可能因为网络波动或服务商限流而失败。重试策略是最多尝试 2 次只有瞬时错误超时、429 限流、5xx 服务端错误才重试400 这种客户端错误不重试因为重试也不会变。为什么只重试 1 次路由是用户操作的第一步用户在等着。多等 1-2 秒还能接受多等 5 秒体验就崩了。而且连续两次 503 大概率是服务商全面故障第三次也不会成功。两次都失败就进入第三层。第三层应急兜底什么时候进这一层LLM 两次调用都失败了。这在生产环境中确实会发生——服务商全面故障、网络隔离、账户欠费。系统不能因为 LLM 挂了就白屏。用户正在赶 deadline他说帮我创建一个任务回复AI 服务不可用请稍后再试是不可接受的。兜底逻辑分三档处理乱码/空输入统计输入中有效字符中英文、数字、常见标点的占比不足一半判定为乱码。返回 confidence 0.1当闲聊处理。包含业务关键词任务、项目、待办、周报、延期等猜一个最可能的 capability但 confidence 只给 0.3。其他当闲聊处理confidence 0.5。关键在 confidence 0.3。兜底层知道自己在猜所以刻意给低置信度。下游路由的追问阈值是 0.75——也就是说兜底层的结果永远会触发追问不会直接执行业务操作。系统会对用户说类似我大概理解你想操作任务但不太确定具体要做什么能再说清楚一些吗的话。这比AI 服务不可用好得多——用户知道系统还活着而且只要他说得更明确一点触发 fast-path 的高频模式下一轮就能正常工作。层间协议confidence 和 routeType三层架构能工作的前提是下游不需要知道结果来自哪一层。这靠两个字段实现。confidence控制流变量不是评分来源confidence 范围下游行为fast-path0.96-0.99直接执行LLM 业务意图0.7-0.95 0.75 直接执行 0.75 追问LLM 需澄清 0.5强制压低追问兜底0.1-0.5追问confidence 不是在评价我识别得准不准而是在告诉下游该不该让我直接干。这是一个控制流变量不是一个准确率指标。routeType可观测性 策略适配routeType 标记了结果来自哪一层fast-path / llm-fallback / emergency-fallback / general-chat / conversation-followup。它不影响执行逻辑但影响两件事消息窗口策略不同来源需要的历史上下文量不同。fast-path 已经用规则完成了意图判断执行层只需要当前消息历史消息反而会污染工具调用的判断。而对话跟进必须带最近几轮历史否则执行层不知道第二个指的是什么。闲聊需要更长的历史来维持对话连贯性。监控和调优通过统计 routeType 分布可以发现问题——fast-path 命中率下降说明用户表达模式变了需要补规则emergency-fallback 突增说明 LLM 服务可能有问题llm-fallback 的 confidence 分布偏低说明 prompt 需要调优。页面上下文嵌入式助手的核心差异同一句话不同页面不同路径用户说删除这个 场景 A — 在任务抽屉里有选中实体 → fast-path 命中confidence 0.97直接执行 场景 B — 在首页没有选中实体 → fast-path 全部不命中 → 进入 LLM 层 → LLM 大概率返回需澄清你想删除什么这不是 bug这是设计。删除这个在没有明确上下文时就是有歧义的系统应该追问而不是猜。上下文分层按需裁剪互不越权前端采集的页面上下文是完整的——包含当前页面、路由参数、选中实体、可见列表 ID、筛选条件、搜索关键词、分页信息、触发来源等。但不是所有信息都给所有节点。路由层只拿到裁剪后的子集页面名称、路由参数、选中实体、触发来源。不包含可见列表 ID、筛选条件、分页信息——这些是执行层需要的路由层不需要。为什么要隔离防止信息泄露如果路由层的 LLM prompt 包含实体 ID 列表LLM 可能直接引用这些 ID 绕过正常的权限和解析流程。节省 token一个 20 条任务列表的 ID 数组塞进路由 prompt 纯属浪费。职责清晰路由层的职责是判断要干什么不是拿什么数据去干。真实 Bug为什么纯规则走不远Bug 1中文子串踩踏用户说“这个项目中有几个未完成的任务”状态推断的匹配逻辑按顺序检查先查进行中再查已完成/完成了/完成的/做完的最后查未完成/没完成/没做完。问题出在中文没有空格分词。未完成的这个词在字符串层面包含了完成的这个子串。当正则引擎扫描未完成的时“已完成/完成的那条规则先命中了其中的完成的三个字函数直接返回已完成”。未完成的规则排在后面根本没机会执行。结果用户问未完成的任务有几个系统回答已完成任务共 1 个。答非所问。这不是个别正则写错了而是中文的结构性问题。英文的 incomplete 和 complete 也有类似的包含关系但英文可以用词边界\b解决。中文没有词边界标记。修复否定形式优先匹配——把未完成/没完成/没做完放到已完成/完成的前面。先匹配否定再匹配肯定。更深层的教训这类冲突在规则数量增长后会指数级增加。每新增一条规则都可能与已有的几十条产生子串互踩。人工 review 无法覆盖所有组合。Bug 2文本清洗破坏语义结构用户说“我已完成的任务有几个”系统有一个自我识别机制检测我开头的句子并标记为查询自己的任务。但自我识别只覆盖了我的“我有”“我负责三种组合没覆盖我已”“我未”。“我已完成的”——“我后面跟的是已”不在识别列表里自我识别失败。接下来进入人名提取流程。为了从文本中提取人名需要先清洗掉状态词避免干扰。清洗把已完成的删掉后文本变成了我任务有几个。人名模式匹配了X 有几个提取出人名我任务。系统去数据库找名为我任务的成员——找不到——返回错误。但执行层没有检查错误标识直接用默认值 0 继续格式化输出——“我任务负责的已完成任务共 0 个”。两个 bug 叠加自我识别覆盖不全 执行层把错误静默吞掉。用户看到一个看似合理但完全错误的回答比直接报错更糟糕。这个 bug 说明的问题文本清洗是危险操作。删除文本片段会改变句子结构产生原始文本中不存在的语义拼接。规则系统中预处理步骤越多意外组合越多。为什么不用纯 LLM / 为什么不用纯规则纯 LLM 的问题问题说明延迟每次意图判断 500-1500ms高频操作用户感知明显卡顿成本70% 的高频请求本不需要 LLM全走 LLM 等于 70% 的钱白花可用性LLM 服务商故障时整个系统不可用没有降级方案确定性我的任务这种请求LLM 偶尔会判断成闲聊而不是任务管理纯规则的问题问题说明覆盖率天花板中文表达多样性极高最近有啥活没干完这种说法规则写不出子串冲突规则数量增长后互相踩踏的概率指数增加维护成本每加一条规则都要验证不和已有规则冲突成本随规则数线性增长清洗副作用为提取实体做的文本清洗会破坏语义结构引入难以预见的 bug分层是工程答案不是LLM 好还是规则好的选择题而是什么场景用什么工具的设计题。高频 无歧义 → 规则快、稳、零成本 长尾 有歧义 → LLM准、灵活、有代价 全挂了 → 兜底规则 追问保底可用