AI 专题 DEC 18, 2025

意图识别:从关键词匹配到语义路由

#意图识别#语义路由#AI 应用#NLU

意图识别:从关键词匹配到语义路由

本文是【AI 专题精讲】系列第 07 篇。 上一篇:RAG 检索优化:混合搜索、Reranking、多路召回 | 下一篇:结构化输出:让 AI 稳定返回 JSON


这篇文章你会得到什么

前六篇聚焦 RAG 链路,今天跳出来聊一个更通用的问题:用户说了一句话,系统怎么知道他想干什么?

这就是意图识别——AI 应用的”路由层”。

用户输入”帮我查一下退货流程”,系统应该去查知识库。用户输入”今天天气怎么样”,系统应该调天气 API。用户输入”你好啊”,系统应该回一句闲聊。

没有意图识别,所有输入都往 LLM 扔,结果就是:该查知识库的没查,该调工具的没调,该拒绝的没拒绝,用户体验一塌糊涂。

今天给你三个层级的方案:

  • Level 1:关键词 / 正则匹配——简单粗暴,5 分钟搞定
  • Level 2:LLM 分类——用 Prompt 让 AI 判断意图
  • Level 3:Embedding 语义路由——最灵活,无需修改 Prompt

再加上多意图处理、与 Function Calling 的关系,给你一套完整的意图路由框架。


意图识别在 AI 应用中的位置

用户输入

┌─────────────┐
│  意图识别层  │  ← 判断用户想干什么
└──────┬──────┘

┌──────┴──────────────────────────┐
│                                  │
▼            ▼            ▼        ▼
知识库查询   工具调用     闲聊      拒绝/引导
(RAG)      (API/Agent)  (Chat)   (兜底)

意图识别是入口层,决定后续走哪条处理链路。它的准确率直接影响整个系统的表现。

常见意图分类

以企业知识库助手为例:

意图示例处理方式
knowledge_query”退货流程是什么”RAG 检索
data_query”上个月销售额多少”调数据 API
task_execute”帮我创建一个工单”调业务 API
chitchat”你好” “谢谢”直接回复
out_of_scope”帮我写个小说”引导回正题

Level 1:关键词 / 正则匹配

最原始但最可控的方案。适合意图边界清晰、表达方式固定的场景。

import re
from dataclasses import dataclass


@dataclass
class IntentResult:
    intent: str
    confidence: float
    matched_rule: str = ""


class KeywordIntentClassifier:
    def __init__(self):
        self.rules = [
            {
                "intent": "knowledge_query",
                "keywords": ["怎么", "如何", "什么是", "流程", "规定", "政策", "制度"],
                "patterns": [r".*(?:||).*(?:知识|文档|手册)"],
            },
            {
                "intent": "data_query",
                "keywords": ["多少", "数据", "统计", "报表", "销售额", "营收"],
                "patterns": [r".*(?:上个?|今年|Q\d).*(?:多少|数据)"],
            },
            {
                "intent": "task_execute",
                "keywords": ["创建", "提交", "发起", "申请", "审批"],
                "patterns": [r"帮我(?:创建|提交|发起)"],
            },
            {
                "intent": "chitchat",
                "keywords": ["你好", "谢谢", "再见", "哈哈", "嗯"],
                "patterns": [r"^(?:你好|hi|hello|谢谢|好的)$"],
            },
        ]

    def classify(self, text: str) -> IntentResult:
        text_lower = text.lower().strip()

        for rule in self.rules:
            # 正则匹配(优先)
            for pattern in rule.get("patterns", []):
                if re.search(pattern, text_lower):
                    return IntentResult(
                        intent=rule["intent"],
                        confidence=0.9,
                        matched_rule=f"pattern: {pattern}",
                    )

            # 关键词匹配
            for keyword in rule.get("keywords", []):
                if keyword in text_lower:
                    return IntentResult(
                        intent=rule["intent"],
                        confidence=0.7,
                        matched_rule=f"keyword: {keyword}",
                    )

        return IntentResult(intent="unknown", confidence=0.0)

优缺点

优点缺点
零延迟,零成本覆盖不了所有表达方式
100% 可控可调试维护规则越来越多会变成噩梦
不依赖外部服务同义词、口语化表达处理不好

适用场景:意图类型少(< 5 种)、表达方式固定(内部系统)、对延迟极度敏感。


Level 2:LLM 分类

用大模型理解自然语言,通过 Prompt 让 AI 判断意图。覆盖面和准确率远超关键词。

基础实现

from openai import OpenAI

class LLMIntentClassifier:
    def __init__(self, client: OpenAI, model: str = "gpt-4o-mini"):
        self.client = client
        self.model = model

    def classify(self, text: str, intents: list[dict]) -> IntentResult:
        intent_desc = "\n".join(
            f"- {i['name']}: {i['description']}"
            for i in intents
        )

        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {
                    "role": "system",
                    "content": f"""你是一个意图分类器。根据用户输入判断意图类别。

可选意图:
{intent_desc}

返回 JSON 格式:
{{"intent": "意图名称", "confidence": 0.0-1.0, "reason": "简短理由"}}

只返回 JSON,不要其他内容。""",
                },
                {"role": "user", "content": text},
            ],
            temperature=0,
            response_format={"type": "json_object"},
        )

        import json
        result = json.loads(response.choices[0].message.content)

        return IntentResult(
            intent=result["intent"],
            confidence=result.get("confidence", 0.8),
        )

使用方式

client = OpenAI()
classifier = LLMIntentClassifier(client)

intents = [
    {"name": "knowledge_query", "description": "用户想查询知识库中的信息,如流程、规定、政策等"},
    {"name": "data_query", "description": "用户想查询数据报表,如销售额、统计数字等"},
    {"name": "task_execute", "description": "用户想执行某个操作,如创建工单、提交申请等"},
    {"name": "chitchat", "description": "闲聊、打招呼、感谢等社交性对话"},
    {"name": "out_of_scope", "description": "超出系统能力范围的请求"},
]

result = classifier.classify("帮我看看上个季度的退货率", intents)
# IntentResult(intent='data_query', confidence=0.95)

带上下文的意图识别

有时候单看一句话判断不了,需要结合对话历史:

def classify_with_context(
    self,
    text: str,
    history: list[dict],
    intents: list[dict],
) -> IntentResult:
    recent_history = history[-4:]
    history_text = "\n".join(
        f"{'用户' if m['role'] == 'user' else '助手'}: {m['content']}"
        for m in recent_history
    )

    response = self.client.chat.completions.create(
        model=self.model,
        messages=[
            {
                "role": "system",
                "content": f"""你是一个意图分类器。结合对话上下文判断用户最新一句话的意图。

最近对话:
{history_text}

可选意图:
{chr(10).join(f"- {i['name']}: {i['description']}" for i in intents)}

返回 JSON:{{"intent": "意图名称", "confidence": 0.0-1.0}}""",
            },
            {"role": "user", "content": text},
        ],
        temperature=0,
        response_format={"type": "json_object"},
    )

    import json
    result = json.loads(response.choices[0].message.content)
    return IntentResult(intent=result["intent"], confidence=result.get("confidence", 0.8))

举例:

用户: 退货流程是什么?      → knowledge_query
助手: 退货需在7天内...
用户: 那换货呢?            → knowledge_query(结合上下文才能判断)

“那换货呢”单独看不知道在问什么,有了上下文就清楚是在问换货流程。

优缺点

优点缺点
理解自然语言,覆盖面广每次分类都要调 API,有延迟和成本
新增意图只需改 Prompt偶尔会分错(非确定性)
能结合上下文判断依赖外部服务

适用场景:意图类型多、表达方式多样、对延迟有一定容忍度(200~500ms)。


Level 3:Embedding 语义路由

把每个意图用几个示例句描述,转成向量。用户输入也转成向量,看和哪个意图最近。

实现

import numpy as np

class SemanticRouter:
    def __init__(self, embedding_provider):
        self.embedding_provider = embedding_provider
        self.routes = []
        self.route_embeddings = []

    def add_route(self, name: str, examples: list[str], handler: str = ""):
        result = self.embedding_provider.embed(examples)
        avg_embedding = np.mean(result.embeddings, axis=0).tolist()

        self.routes.append({
            "name": name,
            "examples": examples,
            "handler": handler,
        })
        self.route_embeddings.append(avg_embedding)

    def classify(self, text: str, threshold: float = 0.5) -> IntentResult:
        result = self.embedding_provider.embed([text])
        query_vec = np.array(result.embeddings[0])

        route_vecs = np.array(self.route_embeddings)

        # 归一化
        query_vec = query_vec / np.linalg.norm(query_vec)
        norms = np.linalg.norm(route_vecs, axis=1, keepdims=True)
        route_vecs = route_vecs / norms

        # 余弦相似度
        scores = route_vecs @ query_vec
        best_idx = int(np.argmax(scores))
        best_score = float(scores[best_idx])

        if best_score < threshold:
            return IntentResult(intent="unknown", confidence=best_score)

        return IntentResult(
            intent=self.routes[best_idx]["name"],
            confidence=best_score,
        )

配置路由

router = SemanticRouter(embedding_provider=create_embedding_provider("openai"))

router.add_route(
    name="knowledge_query",
    examples=[
        "退货流程是什么",
        "怎么申请年假",
        "公司的报销制度",
        "出差审批流程",
        "新员工入职需要什么材料",
    ],
)

router.add_route(
    name="data_query",
    examples=[
        "上个月销售额多少",
        "今年Q3的营收数据",
        "退货率是多少",
        "本周订单量统计",
        "各部门人数",
    ],
)

router.add_route(
    name="task_execute",
    examples=[
        "帮我创建一个工单",
        "提交请假申请",
        "发起报销",
        "帮我预约会议室",
        "创建一个新项目",
    ],
)

router.add_route(
    name="chitchat",
    examples=[
        "你好", "谢谢", "再见", "哈哈", "你是谁",
        "今天心情不错", "辛苦了",
    ],
)

# 使用
result = router.classify("假期还剩几天")
# IntentResult(intent='knowledge_query', confidence=0.82)

动态更新路由

语义路由最大的优势:新增意图只需添加几个示例句,不需要改 Prompt 或重新训练

router.add_route(
    name="meeting_summary",
    examples=[
        "总结一下刚才的会议",
        "帮我整理会议纪要",
        "把讨论的要点列出来",
    ],
)

优缺点

优点缺点
语义理解,泛化能力强需要 Embedding 调用(可缓存)
新增意图只需加示例意图之间语义太近时容易混淆
可离线预计算示例句质量影响很大

适用场景:意图经常变化、需要快速迭代、对灵活性要求高。


组合方案:三层融合

实际项目中我推荐三层方案组合使用:

class HybridIntentClassifier:
    def __init__(
        self,
        keyword_classifier: KeywordIntentClassifier,
        semantic_router: SemanticRouter,
        llm_classifier: LLMIntentClassifier,
        intents: list[dict],
    ):
        self.keyword = keyword_classifier
        self.semantic = semantic_router
        self.llm = llm_classifier
        self.intents = intents

    def classify(self, text: str, use_llm_fallback: bool = True) -> IntentResult:
        # Layer 1: 关键词快速匹配(0ms)
        result = self.keyword.classify(text)
        if result.confidence >= 0.9:
            return result

        # Layer 2: 语义路由(~50ms)
        result = self.semantic.classify(text, threshold=0.75)
        if result.intent != "unknown":
            return result

        # Layer 3: LLM 兜底(~300ms)
        if use_llm_fallback:
            return self.llm.classify(text, self.intents)

        return IntentResult(intent="unknown", confidence=0.0)

策略

  • 关键词能确定的,直接走,零延迟
  • 关键词不确定的,语义路由补上
  • 都不确定的,LLM 兜底

这样 80% 的请求在 50ms 内完成意图识别,只有 20% 的边缘 case 需要走 LLM。


意图路由分发

识别完意图,下一步是分发到对应的处理链路:

class IntentRouter:
    def __init__(self):
        self.handlers = {}

    def register(self, intent: str, handler):
        self.handlers[intent] = handler

    async def route(self, text: str, intent_result: IntentResult, context: dict = None) -> str:
        handler = self.handlers.get(intent_result.intent)

        if not handler:
            handler = self.handlers.get("fallback")

        if not handler:
            return "抱歉,我不太理解你的意思,可以换个说法吗?"

        return await handler(text, context or {})


# 注册处理器
router = IntentRouter()

router.register("knowledge_query", rag_handler)
router.register("data_query", data_api_handler)
router.register("task_execute", task_handler)
router.register("chitchat", chitchat_handler)
router.register("out_of_scope", scope_handler)
router.register("fallback", fallback_handler)


# 处理器示例
async def rag_handler(text: str, context: dict) -> str:
    results = await retriever.retrieve(text, top_k=5)
    # ... 把检索结果塞进 Prompt 让 LLM 回答 ...
    return answer

async def chitchat_handler(text: str, context: dict) -> str:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "你是一个友好的助手,简短回复即可。"},
            {"role": "user", "content": text},
        ],
    )
    return response.choices[0].message.content

async def scope_handler(text: str, context: dict) -> str:
    return "这个问题超出了我的能力范围,我主要负责公司知识库查询和数据查询,换个问题试试?"

多意图和模糊意图

多意图

用户一句话可能包含多个意图:

"帮我查一下退货流程,顺便看看上个月的退货率"
→ knowledge_query + data_query
class MultiIntentClassifier:
    def __init__(self, client: OpenAI):
        self.client = client

    def classify(self, text: str, intents: list[dict]) -> list[IntentResult]:
        intent_desc = "\n".join(f"- {i['name']}: {i['description']}" for i in intents)

        response = self.client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {
                    "role": "system",
                    "content": f"""分析用户输入中包含的所有意图(可能有多个)。

可选意图:
{intent_desc}

返回 JSON 数组:
[{{"intent": "名称", "confidence": 0.0-1.0, "sub_query": "对应的子查询"}}]""",
                },
                {"role": "user", "content": text},
            ],
            temperature=0,
            response_format={"type": "json_object"},
        )

        import json
        data = json.loads(response.choices[0].message.content)
        items = data if isinstance(data, list) else data.get("intents", [data])

        return [
            IntentResult(intent=item["intent"], confidence=item.get("confidence", 0.8))
            for item in items
        ]

模糊意图

用户的表达可能在两个意图之间模棱两可:

"退货率太高了怎么办"
→ data_query(查退货率数据)?还是 knowledge_query(查降低退货率的方法)?

处理策略:

def handle_ambiguous(results: list[IntentResult], threshold: float = 0.15) -> str:
    if len(results) < 2:
        return None

    top_two = sorted(results, key=lambda r: r.confidence, reverse=True)[:2]
    gap = top_two[0].confidence - top_two[1].confidence

    if gap < threshold:
        return f"你是想{describe_intent(top_two[0].intent)},还是{describe_intent(top_two[1].intent)}?"

    return None

当两个意图的置信度差距很小时,主动追问用户而不是猜。


与 Function Calling 的关系

OpenAI 的 Function Calling(工具调用)本质上也是意图识别——模型根据用户输入判断该调哪个函数。

tools = [
    {
        "type": "function",
        "function": {
            "name": "search_knowledge_base",
            "description": "搜索公司知识库获取流程、制度等信息",
            "parameters": {"type": "object", "properties": {"query": {"type": "string"}}},
        },
    },
    {
        "type": "function",
        "function": {
            "name": "query_sales_data",
            "description": "查询销售数据和统计报表",
            "parameters": {"type": "object", "properties": {"metric": {"type": "string"}, "period": {"type": "string"}}},
        },
    },
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "上个月销售额多少"}],
    tools=tools,
)

什么时候用 Function Calling 代替自己做意图识别?

维度自研意图识别Function Calling
延迟可做到 < 50ms300ms+(必须调 LLM)
成本语义路由几乎免费每次都消耗 Token
灵活度完全自控受限于 LLM 能力
适用场景高性能路由层工具调用 + 参数提取一步到位

我的建议:用自研意图识别做第一层路由(快、便宜),需要参数提取时再用 Function Calling


总结

  1. 意图识别是 AI 应用的路由层——没有它,所有请求都混在一起处理,效果和效率都差。
  2. 三个层级递进:关键词(快)→ 语义路由(准)→ LLM 分类(全),组合使用效果最佳。
  3. 语义路由是甜蜜点——新增意图只需加示例句,无需改代码或 Prompt,50ms 内完成。
  4. 多意图和模糊意图要处理——用户一句话可能包含多个意图,置信度接近时应主动追问。
  5. Function Calling 不是万能的——延迟高、成本高,适合需要参数提取的场景,不适合做轻量路由。

下一篇回到工程实战:结构化输出。AI 返回的是自由文本,前端怎么稳定解析?JSON 格式经常出错怎么办?


下一篇预告08 | 结构化输出:让 AI 稳定返回 JSON


讨论话题:你的 AI 应用有做意图识别吗?用的什么方案?遇到过用户输入太离谱导致意图判断错误的情况吗?评论区聊聊。

评论