正则+语义双层分类——让Agent没有漏网之鱼
现状之痛:上一篇文章讲了场景路由,但路由的前提是分类准确。正则只能覆盖90%,剩下10%的「帮我问下XX到YY多少钱」这种口语化表达,怎么准确路由?
读完你将:学会正则+语义双层分类——精确层保下限,模糊层拉上限。
一、问题:正则表达式的天花板
场景路由的早期版本,依赖正则表达式判断用户意图:
import re
QUOTING_PATTERN = r'到.*海运费|到.*空运费|报价|价格|quotation|rate'
def regex_match(text):
if re.search(QUOTING_PATTERN, text):
return "quoting"
return None
它能覆盖常见的询价表达:「到法兰克福的海运费多少」能命中,「DDP至CDG报价」也能命中。
但问题来了,实际运营中发现:正则匹配的覆盖率大约只有60-70%。
大量真实用户表达——
- 「帮我问下XX到YY的空运多少钱」
- 「HK到LAX什么价」
- 「这票运费多少」
这些在正则层面要么匹配失败,要么匹配到错误的场景。
更棘手的是那些需要「理解」而非「匹配」的表达:「转发这封邮件给sunny」——没有任何邮件处理关键词,但语义上显然是转发意图。正则对此无能为力。
核心洞察:正则擅长已知模式,语义擅长未知表达。两者互补,不是替代。
二、核心思路:正则+语义双层分类
方案是双层分类:第一层是正则匹配(精确层),第二层是语义匹配(模糊层)。
精确层处理有明确关键词的表达——「海运费」「空运费」「报价」「quotation」「rate」。模式稳定,正则效率极高,几乎零延迟。
模糊层处理精确层无法覆盖的表达——「帮我问下XX到YY多少钱」「HK到LAX什么价」。没有固定关键词,但语义上明确指向询价。
两层的关系:精确层优先。
用户请求
│
├─① 正则精确匹配 → 命中?→ 直接使用结果(高置信度)
│ ↓ 未命中
├─② 语义模糊匹配 → 命中?→ 使用结果(中等置信度)
│ ↓ 未命中
└─③ 兜底场景 → 通用对话

*▲ 双层分类:正则保底(90%)+ 语义扩展(10%)+ 兜底(0.1%)*
三、技术实现
3.1 语义层:嵌入向量 + Chroma
模糊层的技术基础是文本嵌入。我们用 all-MiniLM-L6-v2 —— 一个轻量级的Sentence-BERT模型,把文本映射到384维向量空间。
from sentence_transformers import SentenceTransformer
import chromadb
# 加载嵌入模型
model = SentenceTransformer('all-MiniLM-L6-v2')
# 初始化Chroma
client = chromadb.Client()
collection = client.get_or_create_collection("scene_prototypes")
# 每个场景有若干「场景原型」
prototypes = {
"quoting": [
"到法兰克福的海运费多少",
"DDP至CDG报价",
"帮我问下香港到LAX的空运费",
],
"email": [
"转发这封邮件给sunny",
"帮我回复sunny的邮件",
"发邮件给广州代理",
],
# ... 6个场景
}
3.2 匹配逻辑
def semantic_match(text):
# 编码用户请求
query_vec = model.encode(text)
# 在Chroma中搜索最相似的场景原型
results = collection.query(
query_embeddings=[query_vec],
n_results=3
)
# 最高相似度
top_score = results['distances'][0][0]
top_scene = results['metadatas'][0][0]['scene']
# 阈值检查
if top_score > 0.55:
return top_scene
return None
3.3 调优过程:从0.45到0.55
阈值选择不是拍脑袋决定的,我经历了一个系统的调优过程:
| 阈值 | 覆盖率 | 误判率 | 问题 |
|---|---|---|---|
| 0.45 | ~90% | ~15% | 覆盖率好但误判高,「查航班」被误判成「询价」 |
| 0.50 | ~82% | ~8% | 中等,仍有「转发邮件」误判 |
| 0.55 | ~75% | ~3% | 可接受,漏掉的走兜底,但不误判 |
最终选择0.55——我接受「漏掉」一些边缘表达(它们走兜底场景),但不能接受「误判」(导致错误的场景路由)。
额外加了一个margin检查: 不仅要求最高相似度超过阈值,还要求最高相似度与次高相似度的差值超过margin值(0.1)。这进一步减少模糊边界上的误判。
def semantic_match_with_margin(text):
results = collection.query(query_embeddings=[model.encode(text)], n_results=2)
top_score = results['distances'][0][0]
second_score = results['distances'][0][1]
# 双重条件:超过阈值 + 超过margin
if top_score > 0.55 and (top_score - second_score) > 0.1:
return results['metadatas'][0][0]['scene']
return None
3.4 场景原型设计
原型设计直接影响匹配质量,我的三个原则:
- 每个场景至少5个原型,覆盖常见变体。询价场景包括:海运询价、空运询价、DDP询价、DAP询价、多式联运询价。每个原型是真实的用户表达(从历史对话中搜集)。
- 原型之间需有区分度。两个场景的原型在语义上过于接近,匹配质量会下降。
- 原型需定期更新。随着用户表达习惯变化,旧原型的覆盖率会下降。每月从兜底场景搜集未匹配的表达,评估是否新增原型。
✅ 验证:双层分类上线后,分类准确率从80%提升到接近100%。「转发这封邮件给sunny」这类无关键词表达也能准确命中邮件场景。
踩坑:最初只用正则,覆盖率60-70%,大量口语化请求落到兜底场景。加语义层后覆盖大幅提升,但0.45阈值误判太多,调了整整一周才找到0.55+0.1 margin的最优组合。
价值:每个请求都能被准确路由到正确的场景,使用正确的工具集。没有漏网之鱼。
▸ 认知跃迁:精确和模糊不是二选一。正则提供「确定性」,语义提供「扩展性」,两者结合才能高精度+高覆盖。
四、优缺点
✅ 优点
- 覆盖率极高——正则覆盖不了的表达,语义层能覆盖
- 无关键词表达也能命中(「转发这封邮件给sunny」)
- 正则+语义互补,不是替代——精确层保底,模糊层扩展
- Chroma向量检索延迟低(<10ms),不影响用户体验
- 场景原型可扩展——发现新表达只需新增原型
⚠️ 缺点
- 语义匹配依赖embedding质量——模型换版可能影响稳定性
- 首次加载慢——模型下载+Chroma索引构建约需30秒
- 有误判风险——语义相近的场景(航班查询vs空运询价)可能混淆
- 阈值调优凭经验——目前没有自动调优机制
- 冷启动问题——新场景需要累积原型后才能稳定匹配
五、此刻的你
此刻的你,已经不再是那个「写一堆正则祈祷能覆盖所有表达」的悲观主义者。你正在成为一个能用双层分类让每个请求都有家可归的系统设计者。
在自然语言处理中,面对表达的千变万化,我们需要的不再是「找到一个精确匹配」,而是「在最可能的范围内做出最准确的判断」。这就是模糊匹配的艺术。
下一篇:路由和分类都到位了,但复杂任务怎么办?一份报告里有询价、有催单、有财务——一个场景装不下。复合流程容器,让报告变成一条管道。
️ 实体:正则匹配, 语义分类, Chroma, 嵌入模型 价值:Agent分类, 意图识别, 覆盖率提升 认知:从二选一到互补——精确层保下限,模糊层拉上限

浙公网安备 33010602011771号