一、简介
LangChain 工具调用(Tool Calling) 是让大模型(LLM)连接外部世界、执行实际操作(搜索、计算、读写文件、调用 API 等)的核心机制,也是构建智能 Agent 的基础能力。LangChain 1.0 对工具调用做了标准化重构,API 更简洁、兼容性更强、稳定性更高。模型不直接执行代码,而是输出一个结构化的调用请求(包含函数名和参数),由应用程序负责执行并将结果返回给模型。
1.1 核心概念
- 工具(Tool):封装了特定功能的可调用对象(函数 / API),包含名称、描述、参数 Schema、执行逻辑。
- 工具调用(Tool Calling):模型根据用户意图,自主选择工具、生成符合 Schema 的参数,并由框架执行、返回结果的流程。
- 等价术语:函数调用(Function Calling)、结构化工具调用。
1.2 核心价值
LangChain 的工具调用系统提供了:
- 统一抽象:通过 @tool 装饰器定义,bind_tools() 绑定
- 扩展能力:让 LLM 突破 "文本生成",能操作外部系统
- 多提供商支持:OpenAI、Anthropic、Gemini、本地模型等
- 自动 Schema 生成:从函数签名和文档字符串自动生成 JSON Schema
- 并行调用:支持单次响应多个工具调用
- 流式支持:逐步返回工具调用参数
- 深度集成:与 LangGraph 无缝配合,构建复杂 Agent
二、工具调用与函数调用的区别
在 LangChain 中,工具调用(Tool Calling) 和 函数调用(Function Calling) 是两个密切相关但又有本质区别的概念。简单来说:函数调用是工具调用的底层技术基础,而工具调用是 LangChain 对函数调用的高级封装。
2.1 核心定义与关系
-
函数调用 (Function Calling)
- 起源:源自 OpenAI 于 2023 年推出的原生 API 功能(functions/tools 参数),是 LLM 厂商定义的模型能力。
- 本质:让模型输出结构化的 JSON 指令(包含函数名、参数),而非自然语言。模型只负责 "决定调用" 和 "生成参数",不负责执行。
- 狭义:特指单个函数的结构化调用协议。
-
工具调用 (Tool Calling)
- 起源:LangChain 对 "函数调用" 能力的框架级封装与扩展。
- 本质:LangChain 中的 Tool = 一个带元数据的函数。它包含:
- 执行函数(Python 代码、API 调用等)
- Schema 元数据(名称、描述、参数 JSON Schema)
- 广义:LangChain 统一了各大厂商(OpenAI, Anthropic, Gemini 等)的函数调用接口,不仅支持函数,还支持多工具、并行调用、Agent 智能路由等复杂场景。
2.2 核心区别对比表
| 维度 | 函数调用 (Function Calling) | 工具调用 (Tool Calling) |
|---|---|---|
| 概念层级 | 模型层能力 (LLM 输出格式) | 框架层能力 (LangChain 执行层) |
| 核心对象 | 纯函数 / API 接口 | Tool 对象(函数 + Schema + 生命周期) |
| 定义方式 | 手动构造 JSON Schema | @tool 装饰器 / 继承 BaseTool 类 |
| 模型支持 | 仅限支持 Function Calling 的模型 (GPT-3.5+/Claude 3+) | 统一兼容所有厂商的工具调用协议 |
| 执行主体 | 开发者手动解析、执行 | LangChain 自动解析、调度、执行、回传 |
| 能力边界 | 单函数、基础结构化输出 | 多工具、并行调用、工具组合、Agent 决策、错误处理 |
| 在 LangChain 中的角色 | 底层通信协议 | 上层业务接口(开发者主要使用) |
| 开发者关注点 | 如何让模型输出正确格式 | 如何定义工具、绑定工具、处理执行结果 |
2.3 函数调用(Function Calling)详解
函数调用是 LLM 提供商(如 OpenAI、Anthropic、Google)原生提供的能力:模型在生成文本的同时,可以决定调用一个函数并返回结构化参数。
原生用法示例:
python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
# 定义函数 schema
functions = [{
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}]
# 绑定函数
llm_with_functions = llm.bind_functions(functions)
response = llm_with_functions.invoke("北京天气怎么样?")
print(response.additional_kwargs["function_call"])
# 输出: {'name': 'get_weather', 'arguments': '{"city": "北京"}'}
关键特点:
- 返回值嵌入在 AIMessage.additional_kwargs["function_call"] 中
- 参数是 JSON 字符串,需要手动 json.loads() 解析
- 需要手动调用实际函数并处理结果
2.4 工具调用(Tool Calling)详解
工具调用是 LangChain 在函数调用基础上构建的统一抽象:将函数、API、RAG 检索器等包装为"工具",让 LLM 可以标准化的方式请求执行。
核心用法示例:
python
from langchain.tools import tool
from langchain_openai import ChatOpenAI
# 定义工具(自动生成 schema)
@tool
def get_weather(city: str, unit: str = "celsius") -> str:
"""获取指定城市的天气"""
return f"{city} 当前25度"
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools([get_weather])
response = llm_with_tools.invoke("北京天气怎么样?")
print(response.tool_calls)
# 输出: [{'name': 'get_weather', 'args': {'city': '北京'}, 'id': 'call_xxx'}]
自动执行工具调用:
python
from langgraph.prebuilt import create_react_agent
# 创建可以自动执行工具的 Agent
agent = create_react_agent(llm, [get_weather])
result = agent.invoke({"messages": [("user", "北京天气怎么样?")]})
# Agent 自动调用 get_weather 并将结果返回给 LLM 生成最终答案
关键特点:
- 统一的 tool_calls 属性,跨模型一致
- 自动处理参数解析(args 已是 dict)
- 支持并行调用多个工具
- 可与 LangGraph 的 ToolNode 无缝集成
2.5 关键概念澄清
-
模型不执行代码,只输出指令
无论是 Function Calling 还是 Tool Calling,LLM 都不直接执行函数。
- Step 1 (模型):生成 {"name": "get_weather", "parameters": {"city": "北京"}}
- Step 2 (LangChain):解析指令 → 调用 get_weather("北京") → 获取结果
- Step 3 (模型):将结果喂给模型,生成最终自然语言回答
-
@tool 装饰器的作用
这是 LangChain 最常用的工具定义方式,它做了 3 件事:
- 自动生成 Schema:从函数名、文档字符串、类型注解中提取信息,生成供模型理解的 JSON Schema。
- 标准化接口:将普通函数包装成统一的 Tool 对象,拥有 invoke() / ainvoke() 方法。
- 集成生态:可直接被 bind_tools() 绑定到模型,可被 Agent 自动调用。
-
为什么 LangChain 要区分?
- 历史兼容:早期 LangChain 使用自定义的 Agent 提示词(ReAct 框架)让模型调用工具,没有依赖原生 Function Calling。
- 生态统一:不同厂商(OpenAI, Anthropic)的 Function Calling 格式略有差异。LangChain 的 Tool Calling 提供了统一抽象,切换模型无需改代码。
- 能力增强:原生 Function Calling 较简单,LangChain Tool 增加了异步执行、参数校验、状态管理、回调钩子等生产级特性。
三、工具调用完整工作流
bash
1. 定义工具 → 2. 绑定工具到模型 → 3. 模型生成ToolCall →
4. 解析并执行工具 → 5. 返回ToolMessage → 6. 模型整理结果 → 7. 输出
3.1 定义工具
LangChain 提供了多种定义工具的方式。
3.1.1 使用 @tool 装饰器(推荐)
最简单的方式,从函数签名自动生成工具模式。
python
from langchain.tools import tool
@tool
def calculate(expression: str) -> float:
"""计算数学表达式的结果。
Args:
expression: 有效的数学表达式,如 "2 + 3 * 4"
"""
return eval(expression)
# 工具对象包含名称、描述、参数模式
print(calculate.name) # "calculate"
print(calculate.description) # "计算数学表达式的结果。..."
print(calculate.args) # 自动生成的 JSON Schema
3.1.2 BaseTool 子类(高度自定义)
适合复杂工具(异步、状态、错误处理)。
python
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
class WeatherInput(BaseModel):
city: str = Field(..., description="城市名(中文/英文)")
country: str = Field(default="CN", description="国家代码")
class GetWeatherTool(BaseTool):
name: str = "get_weather"
description: str = "获取指定城市实时天气"
args_schema: type[BaseModel] = WeatherInput
def _run(self, city: str, country: str="CN") -> str:
"""同步执行"""
return f"{city}({country}):晴天,25℃"
async def _arun(self, city: str, country: str="CN") -> str:
"""异步执行"""
return self._run(city, country)
weather_tool = GetWeatherTool()
3.1.3 内置工具(开箱即用)
LangChain 社区提供大量预定义工具:
python
# 搜索
from langchain_community.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()
# 计算
from langchain_community.tools import CalculatorTool
calc = CalculatorTool()
# 维基百科/ArXiv
from langchain_community.tools import WikipediaQueryRun, ArxivQueryRun
# 文件操作
from langchain_community.tools import ReadFileTool, WriteFileTool
3.2 绑定工具到模型
使用 bind_tools() 方法将工具列表传给模型,让模型感知工具能力。其工作原理:
- bind_tools() 在幕后将工具 schema 转换为模型 API 支持的格式(如 OpenAI 的 tools 参数)
- 模型在生成回复时,会决定是否需要调用工具
python
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o")
# 绑定单个工具
model_with_tools = model.bind_tools([calculate])
# 绑定多个工具
tools = [calculate, get_weather, web_search]
model_with_tools = model.bind_tools(tools)
# 强制调用特定工具(使用 tool_choice)
model_with_tool_choice = model.bind_tools(
tools,
tool_choice={"type": "function", "function": {"name": "calculate"}}
)
3.3 模型调用与 ToolCall 解析
模型响应包含 tool_calls 字段(结构化调用指令)。
python
# 用户输入
user_input = "计算3*4,并搜索'AI大模型最新进展'"
# 调用模型
response = llm_with_tools.invoke(user_input)
# 查看响应
print("模型响应:", response.content) # 文本内容(空或提示)
print("工具调用:", response.tool_calls) # 结构化调用指令
ToolCall 结构:
python
[
{
"name": "multiply",
"args": {"a": 3, "b": 4},
"id": "call_xxx" # 唯一ID
},
{
"name": "search_web",
"args": {"query": "AI大模型最新进展", "num_results": 5},
"id": "call_yyy"
}
]
3.4 执行工具并返回 ToolMessage
用 invoke() 执行工具,结果包装为 ToolMessage 传回模型LangChain。
python
from langchain_core.messages import ToolMessage
# 执行所有工具调用
tool_messages = []
for tool_call in response.tool_calls:
# 匹配工具
tool_map = {t.name: t for t in tools}
tool = tool_map[tool_call["name"]]
# 执行
result = tool.invoke(tool_call["args"])
# 生成ToolMessage(必须带call_id)
tool_messages.append(
ToolMessage(content=str(result), tool_call_id=tool_call["id"])
)
print("工具执行结果:", tool_messages)
3.5 多轮对话:模型整理最终结果
将 用户消息 + AI响应 + ToolMessage 传回模型,生成自然语言答案。
python
# 构建完整消息历史
messages = [
("user", user_input),
response, # AI的ToolCall响应
*tool_messages # 工具结果
]
# 模型生成最终回答
final_response = llm_with_tools.invoke(messages)
print("最终答案:", final_response.content)
四、LangChain 1.0 高级特性
4.1 create_agent:自动工具调用(推荐)
1.0 新 API,内置完整工具调用循环,无需手动解析 / 执行。
python
from langchain.agents import create_agent
from langchain_core.prompts import ChatPromptTemplate
# 1. 定义提示词
prompt = ChatPromptTemplate.from_messages([
("system", "你是专业助手,可调用工具完成任务。"),
("user", "{input}"),
("agent", "{agent_scratchpad}") # 工具调用历史占位
])
# 2. 创建Agent(自动处理工具调用)
agent = create_agent(
model=llm,
tools=tools,
prompt=prompt
)
# 3. 直接调用(自动执行:调用→执行→总结)
result = agent.invoke({
"input": "北京天气如何?计算123*456,再搜索'2026世界杯'"
})
print("最终结果:", result["output"])
4.2 并行工具调用
模型同时调用多个工具,框架并行执行(提升效率)。
python
# 模型可能返回多个 tool_calls
response.tool_calls = [
{"name": "get_weather", "args": {"city": "Beijing"}, "id": "call_1"},
{"name": "get_weather", "args": {"city": "Shanghai"}, "id": "call_2"},
{"name": "search", "args": {"query": "news"}, "id": "call_3"}
]
# 并行执行所有工具
import asyncio
async def execute_tool(tool_call):
tool = tool_map[tool_call["name"]]
return ToolMessage(
content=tool(**tool_call["args"]),
tool_call_id=tool_call["id"]
)
results = await asyncio.gather(*[execute_tool(tc) for tc in response.tool_calls])
4.3 工具参数校验(Pydantic)
自动校验参数类型 / 范围,避免无效调用。
python
from pydantic import Field, ValidationError
@tool
def get_weather(city: str=Field(..., min_length=2)) -> str:
"""城市名至少2字符"""
return f"{city}:晴天"
# 错误调用会抛出ValidationError
try:
get_weather.invoke({"city": "B"})
except ValidationError as e:
print("参数错误:", e)
4.4 工具调用策略(ToolStrategy)
控制模型如何选择 / 调用工具:
python
# 强制调用某工具
llm_forced = llm.bind_tools(tools, tool_choice={"type": "tool", "name": "search_web"})
# 禁止调用工具
llm_no_tool = llm.bind_tools(tools, tool_choice="none")
# 自动选择(默认)
llm_auto = llm.bind_tools(tools, tool_choice="auto")
4.5 异步工具调用(高性能)
python
# 异步定义工具
@tool
async def async_search(query: str) -> str:
"""异步搜索"""
return f"异步结果:{query}"
# 异步调用Agent
import asyncio
async def main():
result = await agent.ainvoke({"input": "异步搜索AI"})
print(result["output"])
asyncio.run(main())
4.6 工具元数据与运行时(ToolRuntime)
1.0 新特性:工具访问上下文、状态、配置。
python
from langchain_core.tools import ToolRuntime
@tool
def user_info(runtime: ToolRuntime) -> str:
"""访问用户上下文(runtime对模型不可见)"""
user_id = runtime.context.get("user_id", "unknown")
return f"用户ID:{user_id}"
# 调用时传入上下文
result = user_info.invoke({}, runtime=ToolRuntime(context={"user_id": "123"}))
4.7 错误处理与重试
python
from langchain_core.tools import ToolException
@tool
def fragile_tool(param: str) -> str:
"""可能失败的复杂操作"""
try:
# 可能抛出异常的操作
result = dangerous_operation(param)
return result
except Exception as e:
# 抛出 ToolException,模型可以理解错误信息
raise ToolException(f"工具执行失败: {str(e)}")
# 在调用时捕获异常
try:
result = fragile_tool.invoke({"param": "value"})
except ToolException as e:
# 将错误信息返回给模型,模型可能会尝试修正参数
error_message = ToolMessage(content=str(e), tool_call_id=tool_call_id)
4.8 超时控制
python
import asyncio
from functools import wraps
def with_timeout(seconds: int):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
try:
return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
except asyncio.TimeoutError:
return f"工具执行超时({seconds}秒)"
return wrapper
return decorator
@tool
@with_timeout(5)
async def slow_api_call(param: str) -> str:
await asyncio.sleep(10) # 模拟慢调用
return "结果"
4.9 流式工具调用
python
from langchain_core.tools import BaseTool
from typing import AsyncIterator
class StreamingTool(BaseTool):
name = "streaming_calculator"
description = "逐步显示计算过程"
async def _arun(self, expression: str) -> AsyncIterator[str]:
# 流式返回中间结果
steps = expression.split('+')
total = 0
for step in steps:
value = int(step.strip())
total += value
yield f"加上 {value},当前结果: {total}\n"
yield f"最终结果: {total}"
# 流式调用时,工具调用信息在最后才完整出现
async for chunk in model_with_tools.astream(messages):
# chunk 可能是文本增量或工具调用增量
if chunk.tool_call_chunks:
# 工具调用正在构建中
print(f"工具调用片段: {chunk.tool_call_chunks}")
elif chunk.content:
print(chunk.content, end="")
五、最佳实践
5.1 工具设计原则
- 描述精准:docstring 清晰告诉模型 "工具能做什么"
- 参数简单:避免复杂嵌套结构,模型易理解
- 单一职责:一个工具只做一件事(如 "查天气"≠"查天气 + 发邮件")
- 错误处理:捕获异常、返回友好错误信息
- 幂等性:工具调用应可重复执行(尤其写操作)
- 超时控制:为外部调用设置合理超时
- 权限控制:敏感工具(删数据、转账)加中间件审核
5.2 必选优化配置
python
# 1. 温度设为0(工具调用需精准)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 2. 工具调用超时
from langchain_core.tools import tool
import time
@tool
def search_with_timeout(query: str) -> str:
"""搜索(5秒超时)"""
import threading
result = [None]
def task():
result[0] = f"搜索:{query}"
t = threading.Thread(target=task)
t.start()
t.join(5)
return result[0] if result[0] else "搜索超时"
# 3. 工具结果截断(避免Token超限)
@tool
def search_short(query: str) -> str:
result = f"长文本结果..."
return result[:1000] + "..." # 截断
5.3 中间件增强(安全 / 监控)
结合 Middleware 实现工具调用管控:
python
from langchain.agents.middleware import (
HumanInTheLoopMiddleware, # 敏感工具人工审核
ToolRetryMiddleware, # 失败重试
TokenCounterMiddleware # 调用统计
)
agent = create_agent(
model=llm,
tools=tools,
prompt=prompt,
middleware=[
HumanInTheLoopMiddleware(tool_names=["delete_data", "transfer_money"]),
ToolRetryMiddleware(max_retries=2),
TokenCounterMiddleware()
]
)
5.4 提示词中的工具指导
python
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个助手,可以使用以下工具:
- calculate: 计算数学表达式,参数 expression
- get_weather: 获取天气,参数 city
当用户需要计算时使用 calculate,需要天气时使用 get_weather。
如果同时需要多个信息,可以并行调用多个工具。
"""),
("human", "{input}")
])
chain = prompt | model_with_tools
5.5 限制工具调用轮数
python
class LimitedToolAgent:
def __init__(self, model, tools, max_turns=3):
self.model = model.bind_tools(tools)
self.tools = {t.name: t for t in tools}
self.max_turns = max_turns
def invoke(self, input_text):
messages = [HumanMessage(content=input_text)]
for _ in range(self.max_turns):
response = self.model.invoke(messages)
if not response.tool_calls:
return response.content
# 执行工具
for tool_call in response.tool_calls:
result = self.tools[tool_call["name"]].invoke(tool_call["args"])
messages.append(response)
messages.append(ToolMessage(content=str(result), tool_call_id=tool_call["id"]))
return "已达到最大工具调用次数,无法完成请求"
5.6 工具调用追踪
python
from langchain.callbacks import BaseCallbackHandler
class ToolCallTracer(BaseCallbackHandler):
def on_tool_start(self, serialized, input_str, **kwargs):
print(f"[工具开始] {serialized['name']} - 参数: {input_str}")
def on_tool_end(self, output, **kwargs):
print(f"[工具结束] 输出: {output[:100]}...")
model = ChatOpenAI(callbacks=[ToolCallTracer()])
六、常见问题与解决方案
6.1 模型不调用工具
原因:描述不清晰或用户输入与工具不匹配。
解决:优化工具描述,或在系统提示中明确指导何时使用。
python
@tool
def get_current_time() -> str:
"""获取当前日期和时间。当用户询问时间、日期、现在几点时使用此工具。"""
return datetime.now().isoformat()
6.2 模型调用错误参数
解决:使用 Pydantic 模型进行严格验证,并提供清晰的参数描述。
python
class SearchInput(BaseModel):
query: str = Field(description="搜索关键词,如'Python教程'")
max_results: int = Field(default=5, ge=1, le=10, description="结果数量,1-10之间")
6.3 工具结果太长超过上下文
解决:截断或总结工具结果。
python
def truncate_result(result: str, max_tokens: int = 2000) -> str:
if len(result) > max_tokens:
return result[:max_tokens] + "...(结果已截断)"
return result
# 在返回 ToolMessage 前截断
ToolMessage(content=truncate_result(long_result), tool_call_id=id)
6.4 工具循环(模型反复调用同一工具)
解决:在状态中记录已调用过的工具,或限制单轮调用次数。
python
# 使用 LangGraph 时设置递归限制
app = graph.compile(checkpointer=memory, recursion_limit=10)
七、完整示例
python
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain_core.prompts import ChatPromptTemplate
# 1. 定义工具
@tool
def add(a: int, b: int) -> int:
"""加法运算"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""乘法运算"""
return a * b
@tool
def get_weather(city: str) -> str:
"""获取城市天气"""
return f"{city}:多云,18-25℃"
# 2. 初始化模型
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [add, multiply, get_weather]
# 3. 创建Agent
prompt = ChatPromptTemplate.from_messages([
("system", "你是全能助手,可调用工具精准完成任务。"),
("user", "{input}"),
("agent", "{agent_scratchpad}")
])
agent = create_agent(model=llm, tools=tools, prompt=prompt)
# 4. 执行复杂任务
result = agent.invoke({
"input": "先算10+20,再乘3,最后查北京天气"
})
print("最终输出:", result["output"])
# 输出:10+20=30,30×3=90;北京天气:多云,18-25℃
八、总结
- Tool Calling = LLM + 外部能力,是 Agent 核心基础。
- 三步核心:定义工具 → 绑定模型 → 自动 / 手动执行。
- LangChain 1.0 优势:create_agent 简化循环、标准化 API、稳定可靠。
- 生产关键:精准描述、参数校验、错误处理、权限管控、中间件增强。