SYSTEM OK | ACTION CONTRACTS 247 DOMAINS 35 DEMO ENVIRONMENT 无需申请 | 服务热线 18926139835
首页/洞察/技术系列/意图识别的三层策略

意图识别的三层策略

一句「3号机没劲了」进入系统后,需要在 100ms 内被判定为故障上报,并解析出目标设备。 本文说明 EAMX IntentAgent 的三层策略:别名解析、本地匹配(静态关键词规则与本地意图模式)、LLM 兜底分类, 以及各层的分工、优先级、实测耗时与置信度阈值。

作者 EAMX 产品团队 发布 2026-09-28 阅读 约 11 分钟 专题 技术系列

01意图识别不宜从大模型起步

常见的对话机器人采用 LLM 优先的实现方式:每一轮对话都调用一次大模型完成意图分类。 这一方式的成本与延迟由模型决定,并且与输入本身的难易程度无关——一条「报修」与一条含义模糊的长句,付出的代价相同。

工业现场的输入分布并不均匀。报修、查询设备、查看工单这几类输入占据了绝大多数调用量, 其表达方式高度固定、可预判。对这类输入而言,静态规则匹配即可达到 95% 的准确率, 大模型并不带来可测量的额外收益,只带来延迟与调用成本。

EAMX IntentAgent 的设计原则为渐进式策略:能用静态规则解决的,不访问数据库;能用已缓存的模式解决的,不调用大模型。 每一层遵循同一条规则——命中即返回,未命中才下沉至下一层。层的顺序由代价决定,确定性最高的手段排在前面。

这一顺序同时是一种取舍。别名解析与静态规则的结果可复现,耗时在毫秒级且与输入难易无关; 本地意图模式依赖历史数据,命中质量随使用量逐步上升;大模型层的能力上限最高, 单次调用的代价也最高。三层策略因此把最确定的判断放在最前,把最不确定的判断留到最后, 分工的依据是每一层的可复现程度。

本文的核心判断

意图识别的关键变量是判定顺序:把结果可复现的判定放在前面,把依赖模型能力的判定留到最后。 顺序安排得当,绝大多数请求在整个过程中不产生模型调用; 顺序安排不当,则每一次输入都要付出与长尾输入相同的代价。

02三层策略的总体结构

三层策略的划分与源文的步骤编号一一对应:第一层为别名解析(Step 1), 第二层为本地匹配(Step 2,内含 2a 静态关键词规则与 2b 本地意图模式两个分支),第三层为 LLM 兜底分类(Step 3)。 层内两个分支沿用源文的 2a 与 2b 标号,其顺序即为执行顺序。

意图识别主流程 · preprocessIntent(message, tenantId)
Step 1 别名解析(lookupAliases)
  entity_aliases 查询 →「3号机」→「3号注塑机」
  同时扫描 equipments.aliases 这一 JSONB 字段
Step 2a 静态关键词规则(matchStaticRule)
  12 条规则,纯内存匹配,实测 0ms
  「处于故障的设备」→ list_equipments + operationalStatus: fault
[未命中] Step 2b 本地意图模式(lookupPattern)
  intent_patterns 查询,关键词重叠度评分,阈值 0.80
  「3号机没劲了」→ 匹配历史模式 → create_fault_report
[仍为低置信度] Step 3 LLM 兜底(classifyWithLLM)
  AI_FAST_MODEL,整条管线的最后保障
输出 IntentPreprocessResult
  primaryIntent / confidence / contextBlock / needsConfirmation

整条流程设有 100ms 超时保护,用于避免 LLM 调用阻塞主对话管道。

第一层与第二层的共同前提是:系统已经知道用户在说哪台设备。 别名解析因此位于最前——它输出的实体上下文会作为参数进入后续两层,并随置信度一并写入结果对象。 结果对象中的 confidence 字段决定交互方式(见第 07 节),needsConfirmation 为确认交互的开关。

每一层都有两个出口:命中即返回,未命中或置信度不足以支撑决策则交给下一层。 为保证这一顺序可判断,各层在返回意图名称的同时给出置信度, 置信度因此成为贯穿三层的统一量纲,也是第三层与第二层之间的合流依据。

依据:agents/intent.agent.ts 中 preprocessIntent 的主流程与 Step 2a/2b 的分支判断。

03第一层:别名解析

现场称呼与台账名称普遍不一致。「3号机」这类简称需要先映射为台账中的规范名称「3号注塑机」, 后续的意图匹配与动作参数才能落到具体设备上。该层的数据来源是两张表:别名字典 entity_aliases 与设备表的 aliases 字段。

表 1 · 别名字典 entity_aliases 的字段设计
字段类型说明
idUUID PRIMARY KEY别名记录主键
tenant_idUUID NOT NULL租户隔离键,别名按租户独立
aliasTEXT NOT NULL现场称呼,例如「3号机」
entity_typeTEXT NOT NULL实体类别:equipment/spare_part/location
entity_idUUID NOT NULL指向实体记录
canonical_nameTEXT NOT NULL规范名称,例如「3号注塑机」
usage_countINT DEFAULT 0命中次数,决定该别名是否留在高频集合内
created_at / updated_atTIMESTAMPTZ创建与更新时间

查找逻辑分三步执行。第一步按 usageCount 降序取出该租户下用量最高的 100 条别名; 第二步对消息做字符串包含匹配,命中后异步将 usageCount 加一; 第三步在该表未命中时降级扫描 equipments.aliases 这一 JSONB 字段,覆盖设备录入时由人工手工填写的别名。

该层的输出为四项:原始称呼、实体类别、实体主键与规范名称。 它们随结果对象一并向下传递,因此第二层与第三层处理的是已经完成映射的消息与实体, 无需再次处理称呼的不一致问题。

实现摘录 · lookupAliases
async function lookupAliases(message, tenantId) {
 // 1. 取该租户用量最高的 100 条别名
 const storedAliases = await db
  .select().from(entityAliasesTable)
  .where(eq(entityAliasesTable.tenantId, tenantId))
  .orderBy(desc(entityAliasesTable.usageCount))
  .limit(100);

 // 2. 字符串包含匹配,命中后异步 usageCount 加一
 for (const alias of storedAliases) {
  if (message.includes(alias.alias)) results.push({ original, entityType, entityId, canonicalName });
 }

 // 3. 降级扫描:entity_aliases 未命中时查询 equipments.aliases
}

该层有三处设计决定其稳定运行:

  • 计数自增形成筛选机制。高频别名因 usageCount 持续增长而长期留在前 100 条,低质量别名则逐步退出集合。
  • 降级扫描保留人工录入的别名。别名未写入字典表时,仍可在设备表的 aliases 字段中被找到。
  • 计数更新为异步写入。别名命中的返回不受计数更新影响,主流程不被写操作阻塞。

口径:该层耗时为 5–15ms(含一次数据库查询往返),在高频场景中承担约 60% 的请求解析。

04第二层 2a:静态关键词规则

静态关键词规则是三层中唯一不访问数据库的一层。规则以常量数组的形式随进程加载, 匹配方式为子串包含,命中任意一个关键词即返回对应规则及其置信度。

表 2 · 静态关键词规则中的四条示例,规则集中共 12 条
关键词(任一命中即匹配)目标意图携带实体置信度
故障状态/处于故障/故障的设备/故障设备列表list_equipmentsoperationalStatus: fault0.95
报修/上报故障/提交故障create_fault_report—0.93
我的工作/我的任务/我的工单/我的维修list_my_work—0.95
备件库存/库存查询/查询库存/库存情况query_spare_part_stock—0.93
实现摘录 · matchStaticRule
const STATIC_RULES: StaticRule[] = [
 { keywords: ["故障状态", "处于故障", "故障的设备", "故障设备列表"],
  intent: "list_equipments",
  entities: { operationalStatus: "fault" },
  confidence: 0.95 },
 // …… 共 12 条规则
];

function matchStaticRule(message) {
 const lower = message.toLowerCase();
 for (const rule of STATIC_RULES) {
  if (rule.keywords.some(kw => lower.includes(kw.toLowerCase()))) return rule;
 }
 return null;
}

12 条规则覆盖设备故障查询、故障上报、工单查看、库存查询、技术标准推荐与资产概览六类最高频意图。 匹配复杂度为 O(n×m):n 为规则条数(12),m 为关键词条数(均小于 10)。 这一规模下的子串包含耗时接近 0ms,引入更复杂的分类器不会带来可测量的收益。 未落入这 12 条规则的输入,交由 2b 与第三层继续处理。

口径:0ms 为内存匹配的实测取值,未计入消息预处理(toLowerCase)的耗时。该层在高频意图中承担约 25% 的请求解析。

05第二层 2b:本地意图模式库

未被静态规则覆盖的输入进入本地意图模式库。该层从 intent_patterns 表读取该租户已确认的模式 (条件为 confirmed_count ≥ 1),按确认次数降序取前 50 条,再对当前消息逐条评分。

表 3 · 本地意图模式的两种评分策略与命中阈值
策略触发条件得分计算
策略 1 · 字符串包含 历史表达与当前消息互为子串 score = minLength / maxLength,属最强信号
策略 2 · 关键词重叠 两者公共词不少于 2 个 score = overlap / maxWords × 0.7
命中判定 两种策略均被计算 取二者最高分,达到 0.80 即判为命中
本地模式的评分与命中条件
score = max( minLength / maxLength , overlap / maxWords × 0.7 )  ≥  0.80

字符串包含用于识别表达形式接近的历史模式;关键词重叠用于承接语序不同、用词部分重合的表述。 两种策略取最高分,0.80 的阈值用于控制误匹配。

模式库的来源是使用过程本身。每轮 AI 对话结束后,编排器异步记录一条意图模式: rawExpression 取用户消息的前 200 个字符,resolvedIntent 取实际执行的工具名,置信度记为 0.85。 写入为 fire-and-forget,失败不影响对话结果。

实现摘录 · orchestrator.ts 的 Phase 4 Learning
recordIntentPattern({
 tenantId,
 rawExpression: userMessage.slice(0, 200),
 resolvedIntent: toolName,
 confidence: 0.85,
}).catch(() => {}); // fire-and-forget

同一表达反复出现时 confirmedCount 递增;新表达则新建记录。 高频确认的模式会自然上升到前 50 条,从而在本层被优先匹配。 这一机制使模式库随实际使用自我调整,无需人工维护关键词表。

该层的读写路径分开:读取发生在对话进行中,用于匹配当前消息;写入发生在对话结束之后,属于离线积累。 两者不共享事务,因此写入失败不会改变本次对话的结果,也不会使已确认的模式失效。 其代价是模式库存在冷启动阶段——初始记录较少时,长尾表述仍会落到第三层。

口径:该层耗时为 3–8ms(含一次数据库查询往返),在高频场景中承担约 10% 的请求解析。

06第三层:LLM 兜底分类

当前两层均未命中,或其置信度不足以支撑决策时,进入第三层。 该层的提示词由四段构成:系统角色(EAMX 工业设备管理系统的意图分类专家)、 可用操作列表(ACTION_CATALOG,26 个 action 的完整清单)、 用户输入(原始消息与已解析的别名上下文)、任务说明 (返回包含 intent、confidence、entities、reasoning 四个字段的 JSON)。

实现摘录 · classifyWithLLM 的调用参数
const resp = await openai.chat.completions.create({
 model: AI_FAST_MODEL,    // DeepSeek-V3.2
 ...FAST_OPTS,       // enable_thinking: false
 max_completion_tokens: 300,
 temperature: 0,
});

可用操作列表随上下文注入,因此本层输出的意图名称即为可执行的 action 名称,无需在后续环节再做一次映射。

该层同时承担与第二层的合并职责。本地模式命中而置信度偏低(低于 0.85)时同样调用本层; 若大模型结果与本地一致,按置信度加权合并;若不一致,以大模型结果为准。 合并后的置信度进入第 07 节的分档判断。

调用参数中有两项值得说明。可用操作列表随提示词一次性注入,使本层输出直接落在动作名称上, 后续环节不再需要一层名称映射;temperature 取 0,用于让相同输入得到稳定输出—— 本层的输出会直接触发动作,输出波动即执行错误。

口径:AI_FAST_MODEL 当前取 DeepSeek-V3.2,调用参数为 enable_thinking: false、 max_completion_tokens: 300、temperature: 0;实测耗时约 200ms,在高频场景中承担约 5% 的请求解析。

07置信度分档与确认交互

三层输出的 confidence 决定系统采取何种交互方式。阈值分为三档, 分档结果写入 needsConfirmation 与 clarificationQuestion 两个字段。

表 4 · 置信度分档与对应的系统行为
置信度区间系统行为用户侧感知
≥ 0.85直接执行,不询问直接返回执行结果
0.60 – 0.85执行前确认,提示语取意图标签,形如「您是要执行『故障上报』吗」单次确认
< 0.60询问澄清,提示用户说明要执行的操作需要补充说明

分档的意义在于把人工确认集中在中间区间:高置信度结果不打扰使用者,低置信度结果不静默执行。 与静态规则的 95% 准确率、本地模式的 0.80 阈值相配合,三层策略在此处收口, 未命中与低置信度两类出口均由确认或澄清环节承接。

依据:preprocessIntent 中 needsConfirmation 与 clarificationQuestion 的赋值逻辑。

08性能数据与口径

四步的实测耗时与高频场景命中占比如下表。样本取自高频场景,即报修、查询设备、查看工单三类输入。

表 5 · 三层四步的分层耗时与高频场景命中占比
层级耗时高频场景命中占比
Step 1 别名解析5–15ms(数据库)60%
Step 2a 静态关键词0ms(内存)25%(高频意图中)
Step 2b 本地意图模式3–8ms(数据库)10%
Step 3 LLM 兜底约 200ms5%

高频场景中约 95% 的请求在第一层与第二层内完成,不进入大模型调用,整体延迟控制在 15ms 以内。 大模型在此处承担长尾职责,其 200ms 的耗时只作用于剩余的少数请求。

分层带来的差别可以按同一组数字对照:若全部请求都交由第三层处理,单次延迟即为 200ms 量级; 前两层承接其中约 95% 之后,整体延迟降至 15ms 以内。代价分布由此与输入分布对齐—— 固定的高频表达以毫秒级代价处理,不固定的长尾表达才承担模型调用。

口径:耗时为单次请求的分层实测值,数据库项含一次查询往返,LLM 项约为 200ms; 命中占比按高频场景样本统计,不代表全量输入分布。

09实现位置与边界

三层策略的实现集中在两个文件与两张数据表,索引如下。

核心文件
agents/intent.agent.ts(611 行)— 三层策略的全部逻辑
学习入口
ai/orchestrator.ts L149–156 — Phase 4 意图学习的触发点
数据表
entity_aliases — 别名字典;intent_patterns — 意图模式库
超时保护
整条流程设有 100ms 超时保护,避免 LLM 调用阻塞主对话管道

该实现有三处边界需要一并说明。别名字典的高频集合由用量决定, 新录入的别名需要经过数次使用才能进入前 100 条;模式库的匹配质量由确认次数决定, 初始阶段可用的模式较少,长尾输入仍会落到第三层;静态规则的 12 条覆盖范围决定了后两层的负载, 规则之外的表述越多,模式库与大模型层的调用量越大。

10作者与依据

作者
EAMX 产品团队 · 技术系列
原文
EAMX 技术系列 02《意图识别的三层策略》(作者 白杨,EAMX 产品负责人)
依据
agents/intent.agent.ts、ai/orchestrator.ts(L149–156)、entity_aliases 与 intent_patterns 两张表的实现记录
数据来源
分层耗时与命中占比来自高频场景实测记录;模型标识与调用参数取自当前运行配置
口径
性能数字均标注测试条件;无实测数据的指标不予列示,不承诺识别准确率 100%
更新日期
2026-09-28
01相关产品能力

本文所述的实现,对应哪一层产品能力

02相关文章

同专题与跨专题的延伸

本文的实现细节可以逐条核对

12 条静态关键词规则、0.80 的模式匹配阈值、0.85 与 0.60 两档确认边界、5–15ms 与约 200ms 的分层耗时—— 上述各项对应工具清单与业务域的可见范围,可由演示环境核验;耗时口径见第 08 节。

信息中心 / 技术评估
关心接入细节与调用链路
执行层 / 业务部门
关心现场输入如何落到动作