前言

在企业知识库问答系统中,不是所有问题都需要查知识库。用户说"你好"时应该闲聊,问"请假流程是什么"时需要检索。如何让系统自动判断?本文介绍基于 LangChain Tool Calling(Function Call)的意图识别方案。

为什么不用正则或关键词?

简单的意图识别可以用关键词匹配:包含"查询"、"检索"、"规章制度"就是知识库意图,否则是闲聊。

但这种方式遇到以下场景就失效了:

  • 跟进问题:"还有吗?"(需要结合上下文判断)

  • 代词指代:"他是谁?"(前文提到了知识库中的人物)

  • 隐含需求:"我想了解一下"(了解什么?前文可能提到过知识库相关内容)

因此需要 LLM 结合对话历史和可用助手信息来做语义级判断。

整体方案

用户问题 + 对话历史 + 可用助手列表
           ↓
     LangChain bind_tools
   绑定 knowledge_base / general_chat 两个工具
           ↓
       LLM 决定调用哪个工具
           ↓
  knowledge_base  → 知识库链
  general_chat    → 通用对话链

第一步:定义工具

LangChain 的 @tool 装饰器可以方便地定义 Function Call 工具:

# Langchain_utils/intent.py
​
from langchain_core.tools import tool
​
@tool("knowledge_base")
def knowledge_base_tool(question: str, chat_history: str = "") -> str:
    """当用户问题需要查询知识库时调用此工具。适用于涉及公司信息、规章制度、专业领域知识、特定人物或事件等需要查询知识库的内容。"""
    return "knowledge_base"
​
@tool("general_chat")
def general_chat_tool(question: str, chat_history: str = "") -> str:
    """当用户问题是日常问候、常识性问题、数学计算、个人意见等无需特殊知识库时调用此工具。"""
    return "general_chat"

关键设计:文档字符串(docstring)才是真正的 Prompt。LLM 根据 docstring 描述来判断何时调用工具,所以描述要清晰具体,列出适用和不适用场景。

第二步:注入上下文

光有工具定义不够,LLM 需要知道有哪些知识库可用、以及上一轮的意图是什么,才能做好上下文相关的判断:

def detect_intent(question: str, chat_history: str = "") -> str:
    # 获取助手列表信息
    assistants_info = get_assistants()
​
    # 获取上一个意图(用于跟进问题判断)
    last_intent = get_last_intent()
    last_intent_info = f"上一个问题的意图是: {last_intent}" if last_intent else "这是对话的第一个问题"
​
    # 构建系统提示词
    system_prompt = """分析用户问题,判断是否需要查询专业知识库来回答。
​
    以下是当前系统中所有可用的知识助手及其掌握的知识内容:
    {assistants_info}
​
    {last_intent_info}
​
    根据以上信息判断:
    1. 问题涉及助手掌握的知识 → knowledge_base
    2. 日常问候、常识、计算 → general_chat
    3. 特别注意:
       - 跟进问题("他是谁?"、"还有呢?")沿用前一个意图
       - 代词(他、她、它、这个)指代的知识库信息 → knowledge_base
       - "查询"、"找找"、"检索"等词 → 强烈知识库信号
    """.format(assistants_info=assistants_info, last_intent_info=last_intent_info)

三个关键信息被注入 System Prompt:

  1. 可用助手列表:让 LLM 知道哪些知识域可查

  2. 上一轮意图:帮助判断跟进问题

  3. 判断规则:处理歧义场景的优先级

第三步:绑定工具并调用

    # 创建 LLM 实例
    llm = ChatOpenAI(
        model=model,
        api_key=api_key,
        base_url=base_url
    )
​
    # 绑定工具 — 让 LLM 在回答前先选择工具
    llm_with_tools = llm.bind_tools([knowledge_base_tool, general_chat_tool])
​
    # 构建消息
    messages = [
        {"role": "system", "content": system_prompt}
    ]
    if chat_history:
        messages.append({"role": "system", "content": f"对话历史:\n{chat_history}"})
    messages.append({"role": "user", "content": question})
​
    # 调用
    response = llm_with_tools.invoke(messages)
​
    # 解析结果
    if hasattr(response, 'tool_calls') and response.tool_calls:
        tool_call = response.tool_calls[0]  # 取第一个工具调用
        tool_name = tool_call["name"]        # "knowledge_base" 或 "general_chat"
        intent = tool_name  # 工具名即意图名
    else:
        intent = "general_chat"  # 默认闲聊
​
    return intent

bind_tools() 是 LangChain 的核心 API,它把工具定义转换为 OpenAI 兼容的 Function Call schema 并注入到请求中。LLM 返回的不是文本,而是 tool_calls 数组,包含调用的工具名和参数。

数据流:

ChatOpenAI.bind_tools([kb_tool, chat_tool])
         ↓
LLM 请求带 functions 参数
         ↓
LLM 返回: { "tool_calls": [{"name": "knowledge_base", "args": {...}}] }
         ↓
提取 tool_calls[0]["name"] 作为意图

第四步:意图历史追踪

跟进问题("为什么?"、"然后呢?")的判断高度依赖上下文。因此需要记录意图历史:

_intent_history = []  # 最多保留 5 条
​
def get_last_intent() -> Optional[str]:
    if _intent_history:
        return _intent_history[-1]
    return None
​
# 每次识别后更新
_intent_history.append(intent)
if len(_intent_history) > 5:
    _intent_history = _intent_history[-5:]

上一轮的意图被注入到 System Prompt 中,帮助 LLM 理解:"上一个问题查了知识库,当前很可能是跟进问题"。

异常处理

LLM 调用不可靠,需要降级策略:

try:
    response = llm_with_tools.invoke(messages)
    # ... 解析
except Exception as e:
    logging.error(f"意图分类错误: {str(e)}")
    _intent_history.append("general_chat")
    return "general_chat"  # 出错时默认闲聊,保证可用性

失败降级为 general_chat(纯 LLM 对话),虽然可能丢失检索增强,但至少不会中断对话。

完整调用示例

# 测试 1:明确的知识库查询
question = "请帮我查询一下公司的请假制度"
result = detect_intent(question, "")  # → knowledge_base
​
# 测试 2:一般性对话
question = "今天天气真不错啊"
result = detect_intent(question, "")  # → general_chat
​
# 测试 3:代词指代 — 需要结合历史
question = "他有什么成就?"
history = "用户: 张三是谁?\n助手: 张三是公司CTO。"
result = detect_intent(question, history)  # → knowledge_base

小结

本文介绍的意图识别方案核心思路:

  1. Tool Calling 做分类bind_tools 让 LLM 在两条路径间二选一

  2. 上下文注入:助手列表 + 上一轮意图 + 对话历史,三个维度辅助判断

  3. 降级兜底:LLM 返回格式异常或调用失败时,默认走闲聊

  4. 意图历史:解决跟进问题和代词指代的歧义

下一篇将介绍查询增强模块——如何把"他是谁"改写为"张三(公司CTO)是谁",提升 RAG 检索命中率。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐