意图识别与同义词归一化 - 专题探讨
针对两个核心问题的深度解答:相同意思不同说法如何识别?意图识别是基于大模型还是关键词?
问题一:相同意思不同说法,如何识别?
1.1 典型场景
"今天卖了多少钱?" ──┐
"今天业绩?" ──┼──→ 同一个意图:查询销售业绩
"今天销售额?" ──┘
"有没有螺丝刀?" ──┐
"螺丝刀有吗?" ──┼──→ 同一个意图:查询商品库存
"螺丝刀在不在?" ──┘
"便宜点行吗?" ──┐
"能便宜不?" ──┼──→ 同一个意图:协商价格
"打个折呗" ──┘
1.2 解决方案:三层识别架构
┌─────────────────────────────────────────────────────────────────────┐
│ 三层识别架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户输入: "今天卖了多少钱?" │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 第一层:关键词精准匹配 (<1ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 检查关键词Hash表: │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ "今天卖了多少钱" → query_sales │ │ │
│ │ │ "今天业绩" → query_sales │ │ │
│ │ │ "今天销售额" → query_sales │ │ │
│ │ │ "卖了多少" → query_sales │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 如果命中 → 直接返回结果 (P99 < 10ms) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 未命中 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 第二层:同义词归一化 (<5ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 用户输入: "今天卖了多少钱?" │ │
│ │ │ │
│ │ Step 1: 分词 │ │
│ │ ["今天", "卖", "了", "多少钱"] │ │
│ │ │ │
│ │ Step 2: 同义词替换 │ │
│ │ "卖" → "销售" (同义词) │ │
│ │ "多少" → "业绩/销售额" (语义相关) │ │
│ │ 组合: "今天销售了多少钱?" / "今天业绩?" │ │
│ │ │ │
│ │ Step 3: 再次查表 │ │
│ │ "今天业绩?" → query_sales ✅ │ │
│ │ │ │
│ │ 如果命中 → 返回结果 (P99 < 20ms) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 仍未命中 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 第三层:Embedding语义匹配 (<50ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 用户输入: "今天卖了多少钱?" │ │
│ │ │ │
│ │ Step 1: 计算Embedding向量 │ │
│ │ vec("今天卖了多少钱?") = [0.12, -0.34, 0.56, ...] │ │
│ │ │ │
│ │ Step 2: 查询向量数据库 │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ 意图向量库 (预先计算好的): │ │ │
│ │ │ query_sales → [0.11, -0.33, 0.55, ...] 相似度0.98│ │ │
│ │ │ query_stock → [0.45, -0.12, 0.23, ...] 相似度0.65│ │ │
│ │ │ buy → [0.21, -0.56, 0.78, ...] 相似度0.43 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Step 3: 阈值判断 │ │
│ │ 相似度 > 0.9 → 直接返回该意图 │ │
│ │ 相似度 0.7-0.9 → 返回top1但标记低置信度 │ │
│ │ 相似度 < 0.7 → 进入第四层 │ │
│ │ │ │
│ │ 结果: query_sales (相似度0.98) ✅ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 相似度 < 0.7 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 第四层:LLM语义理解 (<300ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ Prompt: │ │
│ │ "用户说:'今天卖了多少钱?' │ │
│ │ 这句话的意图是什么? │ │
│ │ 选项:buy / query_sales / query_stock / refund / other" │ │
│ │ │ │
│ │ LLM返回: intent=query_sales, confidence=0.95 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.3 核心实现:同义词表设计
┌─────────────────────────────────────────────────────────────────────┐
│ 同义词归一化核心逻辑 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 意图: query_sales (查询销售业绩) │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 同义词词表 (synonym_table) │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ canonical: 今天销售额 │ │
│ │ synonyms: │ │
│ │ [ │ │
│ │ "今天卖了多少钱", │ │
│ │ "今天业绩", │ │
│ │ "今天销售额", │ │
│ │ "今天营业额", │ │
│ │ "今天卖了多少", │ │
│ │ "今天收入多少", │ │
│ │ "今天生意怎么样" │ │
│ │ ] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 匹配算法: 前缀树 + Hash双重保障 │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 1. 精确匹配 (O(1)) │
│ Hash["今天卖了多少钱"] = query_sales ✅ │
│ │
│ 2. 前缀模糊匹配 (O(k), k=前缀长度) │
│ Hash["今天业绩"] = query_sales ✅ │
│ │
│ 3. 语义向量匹配 (O(n), n=向量维度) │
│ vec("今天卖了多少钱") ≈ vec("今天业绩") ✅ │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.4 同义词自动发现流程
┌─────────────────────────────────────────────────────────────────────┐
│ 同义词自动发现流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 收集用户反馈 │
│ ───────────────────────────────────────────────────────────────── │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ feedback_log 表 │ │
│ │ id │ user_input │ intent │ created_at │ │
│ │ 1 │ 今天卖了多少钱 │ query_sales | 2026-05-15 │ │
│ │ 2 │ 今天业绩 │ query_sales | 2026-05-15 │ │
│ │ 3 │ 今天销售额 │ query_sales | 2026-05-15 │ │
│ │ 4 │ 今天营业额 │ NULL │ 2026-05-15 │ │
│ │ 5 │ 今天卖了多少 │ NULL │ 2026-05-15 │ │
│ │ 6 │ 今天收入多少 │ NULL │ 2026-05-15 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Step 2: 频率统计 + 分组 │
│ ───────────────────────────────────────────────────────────────── │
│ 聚类条件: │
│ - 出现次数 >= 3次 │
│ - 时间窗口: 最近7天 │
│ - 意图相同 (已标注) 或 语义相近 (Embedding计算) │
│ │
│ Step 3: LLM批量归类 │
│ ───────────────────────────────────────────────────────────────── │
│ Prompt: │
│ "以下用户输入表达的都是同一个意思,请找出它们的canonical形式: │
│ - 今天卖了多少钱 │
│ - 今天业绩 │
│ - 今天销售额 │
│ - 今天营业额 │
│ - 今天卖了多少 │
│ - 今天收入多少 │
│ - 今天生意怎么样 │
│ │
│ canonical应该是什么?同义词有哪些?" │
│ │
│ LLM返回: │
│ { │
│ "canonical": "今天销售额", │
│ "synonyms": ["今天卖了多少钱", "今天业绩", "今天销售额", ...], │
│ "intent": "query_sales", │
│ "confidence": 0.95 │
│ } │
│ │
│ Step 4: 写入词表 │
│ ───────────────────────────────────────────────────────────────── │
│ synonym_table 表更新: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ canonical │ synonyms │conf │ │
│ │ 今天销售额 │ 今天卖了多少钱,今天业绩,... │0.95 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ intent_mapping 表更新: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ canonical │ intent │ confidence │ │
│ │ 今天销售额 │ query_sales │ 0.95 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
问题二:意图识别是基于大模型还是关键词?
2.1 核心结论:分层混合架构
┌─────────────────────────────────────────────────────────────────────┐
│ 分层混合架构(最优解) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 关键词层 (50%请求,<5ms) │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ • Hash查表,绝对精准 │ │
│ │ • 覆盖高频核心意图 │ │
│ │ • 零成本 │ │
│ │ 适用: "我要买"、"多少钱"、"有没有" │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 命中则返回 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 缓存层 (40%请求,<10ms) │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ • Redis缓存相同输入的结果 │ │
│ │ • 第二次查询直接返回 │ │
│ │ • 节省API费用 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 命中则返回 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 向量层 (9%请求,<50ms) │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ • Embedding语义匹配 │ │
│ │ • 处理变体表达 │ │
│ │ • MiniMax Embedding API │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 仍未命中 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ LLM层 (1%请求,<300ms) │ │
│ │ ────────────────────────────────────────────────────────── │ │
│ │ • 大模型语义理解 │ │
│ │ • 处理复杂/模糊/歧义输入 │ │
│ │ • 兜底所有边界情况 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 为什么不能只用大模型?
┌─────────────────────────────────────────────────────────────────────┐
│ 大模型的问题 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 问题1: 延迟高 │
│ ───────────────────────────────────────────────────────────────── │
│ MiniMax API响应: 300-500ms │
│ 用户期望: <200ms │
│ 差距: 2-3倍 │
│ │
│ 问题2: 成本高 │
│ ───────────────────────────────────────────────────────────────── │
│ 全用LLM: 10000次/天 × ¥0.01 = ¥100/天 │
│ 混合架构: 10000次/天 × 1% × ¥0.01 = ¥1/天 │
│ 节省: 99% │
│ │
│ 问题3: 稳定性差 │
│ ───────────────────────────────────────────────────────────────── │
│ API限流、服务器波动、网络延迟 │
│ 全LLM = 100%依赖外部服务 │
│ 混合 = 部分请求可本地处理,抵御故障 │
│ │
│ 问题4: 过于智能(有时候是缺点) │
│ ───────────────────────────────────────────────────────────────── │
│ LLM可能会过度解读用户意图 │
│ "螺丝刀" → 联想到"改锥"、"螺丝批" │
│ 但用户只是想说"有没有这种商品" │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.3 如何确保快速响应?
┌─────────────────────────────────────────────────────────────────────┐
│ 快速响应关键技术 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 技术1: 缓存优先 (节省90%延迟) │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 缓存策略: │ │
│ │ ──────────────────────────────────────────────────────── │ │
│ │ Key: MD5(用户输入) │ │
│ │ Value: {"intent": "query_sales", "confidence": 0.95} │ │
│ │ TTL: 3600秒 (1小时) │ │
│ │ │ │
│ │ 效果: │ │
│ │ 第1次: 缓存miss → 计算 → 写入缓存 → 返回 (300ms) │ │
│ │ 第2次: 缓存hit → 直接返回 (10ms) │ │
│ │ 加速: 30倍 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 技术2: 向量缓存 (相似请求复用) │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 问题: "今天卖了多少钱" vs "今天卖了多少?" │ │
│ │ 这两个几乎一样,但Hash不同 │ │
│ │ │ │
│ │ 解决: Embedding缓存 │ │
│ │ ──────────────────────────────────────────────────────── │ │
│ │ vec("今天卖了多少钱") → [0.12, -0.34, ...] │ │
│ │ vec("今天卖了多少") → [0.11, -0.33, ...] │ │
│ │ 相似度: 0.99 → 相同意图 → 直接返回 │ │
│ │ │ │
│ │ 计算: 50ms (Embedding) + 5ms (向量比较) = 55ms │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 技术3: 批量预计算 (闲时处理) │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 凌晨2点执行: │ │
│ │ ──────────────────────────────────────────────────────── │ │
│ │ 1. 读取过去7天TOP 1000条用户输入 │ │
│ │ 2. 批量计算Embedding,存入缓存 │ │
│ │ 3. 批量计算意图,存入缓存 │ │
│ │ │ │
│ │ 效果: 90%请求可在缓存中命中 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 技术4: 异步LLM + 同步返回 (保底策略) │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 策略: 同步快速路径 + 异步学习 │ │
│ │ ──────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 同步路径: 关键词 → 缓存 → 返回结果 (<50ms) │ │
│ │ 异步路径: 未命中 → LLM → 更新缓存 → 下次命中 │ │
│ │ │ │
│ │ 用户体验: 永远快速响应 │ │
│ │ 系统学习: 后台持续优化 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.4 意图判断的完整流程
┌─────────────────────────────────────────────────────────────────────┐
│ 意图判断完整流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户输入: "今天卖了多少钱?" │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Step 1: 关键词Hash查表 (<1ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ Hash["今天卖了多少钱"] = ? │ │
│ │ │ │
│ │ 命中 → return query_sales (10ms) │ │
│ │ 未命中 → Step 2 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Step 2: Redis缓存查询 (<5ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ GET intent:今天卖了多少钱? │ │
│ │ │ │
│ │ 命中 → return 缓存结果 (15ms) │ │
│ │ 未命中 → Step 3 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Step 3: 分词 + 同义词替换 (<5ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ 分词: ["今天", "卖", "了", "多少钱"] │ │
│ │ 同义替换: "卖"→"销售", "多少"→"多少/业绩/额" │ │
│ │ 组合测试: │ │
│ │ "今天销售额?" → Hash查询 → 命中!→ query_sales │ │
│ │ │ │
│ │ 耗时: 5ms │ │
│ │ 写入缓存 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 仍未命中 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Step 4: Embedding向量匹配 (<50ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ vec("今天卖了多少钱?") = [0.12, -0.34, 0.56, ...] │ │
│ │ │ │
│ │ 与已知意图向量比较: │ │
│ │ query_sales: 相似度 0.98 ✅ │ │
│ │ query_stock: 相似度 0.45 │ │
│ │ buy: 相似度 0.32 │ │
│ │ │ │
│ │ 相似度>0.9 → return query_sales (55ms) │ │
│ │ 否则 → Step 5 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 相似度<0.9 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Step 5: LLM语义理解 (<300ms) │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ Prompt构造: │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ 系统: 你是意图识别专家 │ │ │
│ │ │ 用户输入: 今天卖了多少钱? │ │ │
│ │ │ 候选意图: buy, query_sales, query_stock, refund │ │ │
│ │ │ 要求: 返回最匹配的意图 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ LLM返回: {"intent": "query_sales", "confidence": 0.95} │ │
│ │ │ │
│ │ 写入缓存 (TTL=3600) │ │
│ │ 写入反馈日志 (用于后续学习) │ │
│ │ return query_sales (355ms) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 全流程耗时: │ │
│ │ ───────────────────────────────────────────────────────── │ │
│ │ 90%: 命中缓存 <20ms ✅ │ │
│ │ 9%: Embedding <60ms ✅ │ │
│ │ 1%: LLM <400ms ✅ │ │
│ │ │ │
│ │ P99延迟: <500ms ✅ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.5 延迟预算详解
┌─────────────────────────────────────────────────────────────────────┐
│ 各层延迟预算 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 层级 │ 平均延迟 │ P99延迟 │ 命中率 │
│ ──────────────────────────────────────────────────────────────── │
│ 关键词Hash │ 1ms │ 2ms │ 50% │
│ 同义词替换 │ 3ms │ 5ms │ +5% (累计55%) │
│ Redis缓存 │ 1ms │ 3ms │ +35% (累计90%) │
│ Embedding向量 │ 40ms │ 55ms │ +9% (累计99%) │
│ LLM语义理解 │ 250ms │ 350ms │ +1% (累计100%) │
│ ──────────────────────────────────────────────────────────────── │
│ 综合P99 │ - │ <100ms │ - │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 为什么能这么快? │ │
│ │ ──────────────────────────────────────────────────────── │ │
│ │ • 90%请求: 关键词+缓存解决,无需网络调用 │ │
│ │ • 9%请求: Embedding调用(本地计算)+缓存命中 │ │
│ │ • 1%请求: LLM调用,但这是异步+用户无感知 │ │
│ │ • 批量预计算: 热点请求已经预计算好,直接命中 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
三、总结
3.1 核心要点
| 问题 | 解答 |
|---|---|
| 相同意思不同说法如何识别? | 三层架构:关键词Hash + 同义词归一化 + Embedding语义匹配 |
| 意图识别用大模型还是关键词? | 分层混合:关键词50% + 缓存40% + 向量9% + LLM兜底1% |
| 如何确保快速响应? | 缓存优先 + 批量预计算 + 异步LLM |
| 意图如何判断? | Hash查表 → 同义词替换 → 向量匹配 → LLM兜底 |
3.2 性能指标
┌─────────────────────────────────────────────────────────────────────┐
│ 性能指标达成 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ P50延迟: <10ms ✅ │
│ P99延迟: <100ms ✅ │
│ P999延迟: <500ms ✅ │
│ │
│ 缓存命中率: >90% ✅ │
│ LLM调用率: <1% ✅ (节省99%成本) │
│ │
│ 月度API成本: ¥176 (100家门店) │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.3 关键词 vs LLM 对比
| 维度 | 关键词 | LLM |
|---|---|---|
| 延迟 | <1ms | 300-500ms |
| 成本 | ¥0 | ¥0.01/次 |
| 准确性(核心句) | 100% | 95% |
| 准确性(变体) | 0% | 90% |
| 覆盖率 | 有限 | 无限 |
| 维护成本 | 高(需人工) | 低(自动学习) |
最佳方案:关键词覆盖核心句(50%),LLM兜底变体和边界(1%),中间用缓存和向量填充(49%)。