只用关键词,用户换个说法就答不上;只用语义,容易答非所问还慢半拍。实用的做法是两段式——关键词先拦高置信度的,语义兜底,两段都没命中才走通用回复或转人工。

一、关键词层:三种匹配各管一类

关键词不是简单的一句 in 判断。精确匹配——枚举"退货运费""发票抬头"这类标准问法,命中即答。模糊匹配——去掉标点空格后做子串包含,覆盖"发票 抬头""怎么开发票"这类变体。正则匹配——处理订单号、日期、金额这类结构化输入。

执行顺序是精确 > 正则 > 模糊。精确命中直接返回,不再往下走,避免被模糊规则抢答。

二、语义层:阈值决定它敢不敢答

语义匹配用向量相似度,核心是阈值标定。太高,大部分问题落到兜底;太低,答非所问。做法是拿历史问答做离线评估,取准确率 90% 对应的那个阈值,别凭感觉设。

相似度落在 0.75-0.9 之间时,不要直接回答,改成一句"你是想问 XX 吗"让用户确认。这比重答一遍再道歉便宜得多。

三、兜底与迭代:没命中的去哪

两段都没命中的问题进"未命中池",按周聚类看高频问题,人工补进知识库或关键词表。让机器人变聪明的路径不是换更大的模型,是让漏掉的问题一个个被补上。

未命中池要带原始问法和命中失败的层级,补规则时才知道该补哪一层。

匹配层对照

匹配层

方法

置信度

未命中处理

精确

枚举标准问法

高

进入下一层

正则

订单号/日期等结构

中高

进入下一层

模糊

去噪后子串包含

中

进入语义层

语义

向量相似度+阈值

可调

确认问句或兜底

两段式匹配实现

import re

class ReplyMatcher:
    FUZZY_HIT = 0.8                     # 模糊层相似度下限
    SEMANTIC_HIT = 0.90                 # 语义直接回答阈值
    SEMANTIC_ASK = 0.75                 # 超过则反问确认

    def __init__(self, kb, embedder):
        self.kb = kb
        self.embedder = embedder
        self.exact = kb.exact_table()    # {标准问法: 答案}
        self.regex_rules = kb.regex_rules()   # [(pattern, answer, name)]

    def match(self, text):
        # 1. 精确匹配
        norm = self._normalize(text)
        if norm in self.exact:
            return {"answer": self.exact[norm], "layer": "exact"}

        # 2. 正则匹配
        for pattern, answer, name in self.regex_rules:
            if re.search(pattern, text):
                return {"answer": answer, "layer": "regex",
                        "rule": name}

        # 3. 模糊匹配
        best, best_score = None, 0.0
        for q, answer in self.exact.items():
            score = self._similar(norm, self._normalize(q))
            if score > best_score:
                best, best_score = answer, score
        if best_score >= self.FUZZY_HIT:
            return {"answer": best, "layer": "fuzzy",
                    "score": round(best_score, 3)}

        # 4. 语义匹配
        vec = self.embedder.encode(norm)
        hits = self.kb.vector_search(vec, top_k=3)
        if not hits:
            return {"layer": "miss"}
        top = hits[0]
        if top.score >= self.SEMANTIC_HIT:
            return {"answer": top.answer, "layer": "semantic",
                    "score": round(top.score, 3)}
        if top.score >= self.SEMANTIC_ASK:
            return {"layer": "confirm", "candidate": top.question,
                    "answer": f"你是想问「{top.question}」吗?"}
        return {"layer": "miss"}

    def _normalize(self, text):
        # 去标点、空格、统一大小写
        return re.sub(r"[\s,。!?、,.!?~]", "", text).lower()

    def _similar(self, a, b):
        # 字符级相似度,够用且无需模型
        sa, sb = set(a), set(b)
        return len(sa & sb) / len(sa | sb) if sa and sb else 0.0

    def log_miss(self, text, layer):
        self.kb.add_miss(text, layer, ts=None)

落地建议

关键词层按精确、正则、模糊三级顺序执行,精确命中就不往下走,避免被模糊规则抢答。语义层阈值用历史问答离线标定,取准确率 90% 对应的值,别凭感觉设。相似度在中间区间时改为反问确认,比答错再道歉成本低。未命中的问题进池按周聚类,补规则时记录失败在哪一层,才知道该补关键词还是补知识库。微信侧的消息收发由 Eyun 这类个人微信API平台 提供,匹配与知识库逻辑在自建服务实现,接口字段以平台开发文档为准。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐