LangChain 意图识别与 Function Call:让 LLM 学会“选工具“
前言
在企业知识库问答系统中,不是所有问题都需要查知识库。用户说"你好"时应该闲聊,问"请假流程是什么"时需要检索。如何让系统自动判断?本文介绍基于 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:
-
可用助手列表:让 LLM 知道哪些知识域可查
-
上一轮意图:帮助判断跟进问题
-
判断规则:处理歧义场景的优先级
第三步:绑定工具并调用
# 创建 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
小结
本文介绍的意图识别方案核心思路:
-
Tool Calling 做分类:
bind_tools让 LLM 在两条路径间二选一 -
上下文注入:助手列表 + 上一轮意图 + 对话历史,三个维度辅助判断
-
降级兜底:LLM 返回格式异常或调用失败时,默认走闲聊
-
意图历史:解决跟进问题和代词指代的歧义
下一篇将介绍查询增强模块——如何把"他是谁"改写为"张三(公司CTO)是谁",提升 RAG 检索命中率。
更多推荐

所有评论(0)