毫秒级响应的意图识别:两阶段混合分类架构实战

6 阅读7分钟

告别 500ms 延迟:两阶段意图识别让 Agent 既快又准

启发式规则优先、LLM 按需介入 —— 一种兼顾速度与准确性的意图识别架构

一、问题背景

Agent 系统面临一个经典难题:用户输入是闲聊、询问能力,还是真实操作请求?

识别不清会导致两类问题:

  • 过度暴露:用户只是说了句"你好",系统却打开了所有工具权限,既浪费 LLM 调用,又容易产生误操作
  • 遗漏意图:用户说"帮我读一下这个 PDF",系统没识别出文件操作意图,导致无法执行

常见解决方案有两种:

方案原理优点缺点
纯规则关键词匹配极速,< 1ms覆盖不全,难以处理歧义
纯 LLM大模型判断准确,可处理复杂语义慢,每次 200-500ms
两阶段混合规则快筛 + LLM 兜底兼具两者优点架构稍复杂

本文介绍一种两阶段混合意图识别的实现原理。


二、整体架构

                    ┌─────────────────────┐
                          用户输入         
                    └──────────┬──────────┘
                               
                               
                    ┌─────────────────────┐
                      Stage 1: 启发式规则   
                          毫秒级检测        
                    └──────────┬──────────┘
                               
                               
                    ┌─────────────────────┐
                      单意图 + 高置信度     
                       (置信度  0.85)    │───Yes──▶ 直接返回结果
                    └──────────┬──────────┘
                                No
                               
                    ┌─────────────────────┐
                     Stage 2: LLM 精细分类  
                      仅在模糊/多意图时触发  
                    └──────────┬──────────┘
                               
                               
                    ┌─────────────────────┐
                        两阶段结果合并      
                    └──────────┬──────────┘
                               
                               
                    ┌─────────────────────┐
                        工具暴露决策        
                    └──────────┬──────────┘
                               
                               
                    ┌─────────────────────┐
                       按需暴露工具列表     
                    └─────────────────────┘

核心思路:

  1. Stage 1(启发式):毫秒级初筛,简单意图直接命中
  2. Stage 2(LLM):仅在复杂/模糊场景介入,作为精细化校正
  3. 结果合并:按置信度动态选择,规则与 LLM 互补

三、意图类型体系

设计意图识别前,首先要定义清楚需要识别哪些意图类型:

class IntentType(Enum):
    # 社交类
    CASUAL_CHAT          # 闲聊问候(你好、bye、谢谢)
    CAPABILITY_QUESTION  # 询问能力(能做什么?)

    # 文件操作类
    FILE_READ            # 读取文件/目录
    FILE_EDIT            # 修改/编辑文件
    FILE_CREATE          # 创建新文件

    # 执行类
    COMMAND_EXEC         # 命令行执行
    WEB_SEARCH           # 联网搜索
    WEB_FETCH            # 获取网页内容

    # 委托类
    SPAWN_SUBAGENT       # 启动子 Agent
    RUN_SKILL            # 运行技能

    # 调度类
    CRON_SCHEDULE        # 定时任务
    MESSAGE_SEND         # 发送消息

    # 分析类
    PROJECT_INSPECTION    # 项目代码分析
    UNKNOWN              # 未知意图

分类时每条意图附带一个 confidence(置信度),取值 0.0 ~ 1.0,表示对该判断的确信程度。


四、Stage 1:启发式规则分类

4.1 设计思路

启发式规则的核心是用关键词组合快速排除明显场景,典型模式:

def _looks_like_casual_chat(text: str) -> bool:
    short_smalltalk = {"你好", "hello", "hi", "thanks", "bye"}
    lowered = text.strip().lower()

    # 精确匹配
    if lowered in short_smalltalk:
        return True

    # 短句 + 问候词(防止"帮我读一下"误判为闲聊)
    if len(lowered) <= 12 and any(token in lowered for token in ("你好", "hi")):
        return True
    return False

4.2 关键词组设计

每种意图对应一组关键词,高效覆盖常见表达:

# 文件读取意图
LOOKS_LIKE_FILE_READ_KEYWORDS = (
    "读取", "查看", "分析文件", "看下", "目录",
    ".py", ".md", ".json", "read ", "open ", "inspect",
)

# 命令执行意图
LOOKS_LIKE_EXEC_KEYWORDS = (
    "执行", "运行", "命令", "终端", "shell",
    "bash", "cmd", "python ", "exec", "run ",
)

# Web 搜索意图
LOOKS_LIKE_WEB_KEYWORDS = (
    "搜索", "联网", "网页", "网站", "最新",
    "web", "search", "fetch", "browse",
)

def _contains_any(text: str, keywords: tuple[str, ...]) -> bool:
    return any(kw in text for kw in keywords)

4.3 防误触设计

一个难点:"能不能读取文件" 既像能力询问,又像文件操作。

解决方案:引入排除列表

def _looks_like_capability_question(text: str) -> bool:
    capability_markers = ("能不能", "能否", "can you", "do you support")
    explicit_execution_markers = (
        "帮我读", "请读取", ".pdf", ".md",
        "这个文件", "read this", "open this",
    )

    has_question_tone = _contains_any(text, question_markers)
    has_capability_tone = _contains_any(text, capability_markers)

    if not has_capability_tone:
        return False

    # 有能力询问特征,但同时有执行意图 → 不算纯询问
    if _contains_any(text, explicit_execution_markers):
        return False

    return has_question_tone or text.startswith(("能", "可以", "是否"))

4.4 分类决策

def heuristic_classify(text: str) -> IntentResult:
    lowered = text.strip().lower()
    detected_intents: list[IntentDecision] = []

    # 闲聊/能力询问 → 无需工具
    if _looks_like_casual_chat(lowered):
        return IntentResult.casual_chat()

    if _looks_like_capability_question(lowered):
        return IntentResult.capability_question()

    # 工具类意图检测
    if _contains_any(lowered, LOOKS_LIKE_FILE_READ_KEYWORDS):
        detected_intents.append(IntentDecision(IntentType.FILE_READ, confidence=0.85))

    if _contains_any(lowered, LOOKS_LIKE_EXEC_KEYWORDS):
        detected_intents.append(IntentDecision(IntentType.COMMAND_EXEC, confidence=0.90))

    if _contains_any(lowered, LOOKS_LIKE_WEB_KEYWORDS):
        detected_intents.append(IntentDecision(IntentType.WEB_SEARCH, confidence=0.85))
    # ... 其他意图检测

    # 单意图 + 高置信 → 直接返回,不走 LLM
    if len(detected_intents) == 1 and detected_intents[0].confidence >= 0.85:
        return IntentResult(
            primary_intent=detected_intents[0],
            should_enable_tools=True,
            requires_llm=False,  # 关键标志:无需 Stage 2
        )

    # 多意图或低置信 → 需要 LLM 介入
    return IntentResult(
        primary_intent=detected_intents[0],
        secondary_intents=tuple(detected_intents[1:]),
        should_enable_tools=True,
        requires_llm=True,
    )

五、Stage 2:LLM 精细分类

5.1 何时触发

以下情况会触发 LLM 分类:

  • 多个意图同时命中(用户说"帮我读这个文件然后执行命令")
  • 单意图但置信度 < 0.85(输入模糊)
  • Stage 1 无法匹配的未知输入

5.2 提示词设计

LLM 的提示词需要简洁、输出格式固定:

LLM_SYSTEM_PROMPT = """你是一个意图识别专家。根据用户输入判断其意图。

意图类型:CASUAL_CHAT, CAPABILITY_QUESTION, FILE_READ, FILE_EDIT,
           COMMAND_EXEC, WEB_SEARCH, SPAWN_SUBAGENT, CRON_SCHEDULE, ...

输出 JSON(不要输出其他内容):
{
  "intent": "意图类型",
  "confidence": 0.0-1.0,
  "reasoning": "简短推理",
  "recommended_tools": ["tool1"]  // 可选
}

confidence 评分标准:
- 0.95+: 非常确定
- 0.80-0.94: 较确定
- 0.60-0.79: 中等,需要参考上下文
- <0.60: 不确定
"""

5.3 带超时的异步调用

import asyncio

async def llm_classify_async(text: str, llm, timeout: float = 10.0) -> IntentResult:
    messages = [
        SystemMessage(content=LLM_SYSTEM_PROMPT),
        HumanMessage(content=f"用户输入:\n{text}")
    ]

    try:
        # 带超时保护,防止 LLM 响应慢导致阻塞
        response = await asyncio.wait_for(llm.ainvoke(messages), timeout=timeout)
        parsed = json.loads(response.content)

        return IntentResult(
            primary_intent=IntentDecision(
                intent=IntentType[parsed["intent"]],
                confidence=parsed["confidence"],
                reasoning=parsed["reasoning"],
            ),
            recommended_tools=tuple(
                ToolRecommendation(tool, 1.0) for tool in parsed.get("recommended_tools", [])
            ),
            metadata={"source": "llm"},
        )

    except asyncio.TimeoutError:
        # LLM 超时 → 回退到启发式结果
        return IntentResult.default_unknown()

六、两阶段结果合并

当两阶段都完成后,需要合并结果。核心策略:置信度高者胜出

CONFIDENCE_THRESHOLD_LLM = 0.80
CONFIDENCE_THRESHOLD_HEURISTIC = 0.85

def merge_results(heuristic: IntentResult, llm: IntentResult) -> IntentResult:
    # LLM 置信度高 → 信任 LLM
    if llm.primary_intent.confidence >= CONFIDENCE_THRESHOLD_LLM:
        return llm

    # 启发式置信度高 → 信任启发式
    if heuristic.primary_intent.confidence >= CONFIDENCE_THRESHOLD_HEURISTIC:
        return heuristic

    # 都不确定 → 以 LLM 为主,保留启发式的候选意图
    return IntentResult(
        primary_intent=llm.primary_intent,
        secondary_intents=heuristic.secondary_intents,
        should_enable_tools=llm.should_enable_tools or heuristic.should_enable_tools,
        metadata={"source": "merged"},
    )

合并的三种情况:

场景处理方式
LLM 高置信(≥ 0.80)直接采用 LLM 结果
启发式高置信(≥ 0.85)采用启发式结果
都不高LLM 为主 + 保留启发式候选意图

七、工具暴露控制

意图识别的最终目的是按需暴露工具,不是一次性全开。

def select_tools_for_intent(result: IntentResult) -> tuple[str, ...]:
    # 闲聊/能力询问 → 不暴露任何工具
    if not result.should_enable_tools:
        return ()

    if not result.recommended_tools:
        return ALL_TOOLS  # 没有推荐时保守返回全部

    # 按 exposure_level 排序,选取最相关的 N 个
    sorted_tools = sorted(result.recommended_tools, key=lambda t: t.exposure_level, reverse=True)
    selected = [t.tool_name for t in sorted_tools if t.exposure_level >= 0.3]

    return tuple(selected[:MAX_TOOLS_PER_INTENT])

意图与工具的典型映射:

意图推荐工具exposure_level
FILE_READfilesystem1.0
FILE_EDITfilesystem1.0
COMMAND_EXECshell1.0
WEB_SEARCHweb1.0
SPAWN_SUBAGENTspawn1.0
PROJECT_INSPECTIONfilesystem + shell0.8, 0.3

八、完整流程图

                        ┌──────────────────┐
                             用户输入       
                        └────────┬─────────┘
                                 
                                 
                    ┌────────────────────────┐
                      闲聊 / 能力询问?        
                    └────────┬────────────────┘
                      Yes        No
                                 
        ┌──────────────────┐  ┌────────────────────┐
         should_enable_       启发式多意图检测    
         tools = False     └─────────┬──────────┘
         无需 LLM                    
        └────────┬────────┘            
                            ┌────────────────────┐
                              单意图 + 置信度     
                               0.85?          
                            └─────────┬──────────┘
                              Yes        No
                                         
                            ┌──────┐  ┌────────────┐
                            │采用     调用 LLM   
                            │启发式│   精细分类   
                            └──┬───┘  └─────┬──────┘
                                          
                                          
                                    ┌────────────┐
                                     LLM 超时?  
                                    └─────┬──────┘
                                     Yes     No
                                             
                                    ┌──┐   ┌────┐
                                    │回│   │合并│
                                    │退│   │结果│
                                    └──┘   └─┬──┘
                                             
                 └──────────┴────┴────┴───────┴───┘
                                             
                                             
                   ┌─────────────────────────────┐
                         按置信度选择工具        
                   └─────────────┬───────────────┘
                                 
                                 
                   ┌─────────────────────────────┐
                       暴露选中的工具列表        
                   └─────────────────────────────┘

九、性能对比

方案响应时间准确性LLM 调用成本
纯 LLM200-500ms每次都调用
纯启发式< 1ms
两阶段混合1ms(命中高速路径)仅模糊场景调用

实测数据:约 70-80% 的用户输入可在 Stage 1 高速路径完成,LLM 调用减少显著。


十、核心设计思想总结

  1. 规则做快筛:简单明确的场景用规则快速处理,避免无意义的 LLM 调用
  2. LLM 做兜底:复杂模糊的场景交给 LLM,确保准确性
  3. 置信度驱动:所有决策点都有置信度阈值,避免非此即彼的硬判断
  4. 工具按需暴露:意图识别结果直接决定工具权限,实现最小权限原则
  5. 超时保护:LLM 调用设置 10 秒超时,防止外部服务延迟影响主流程

这套两阶段混合架构在生产级 Agent 系统中具有良好的参考价值,可根据具体业务场景裁剪意图类型和规则集合。