解锁大模型的无限可能:LangChain 工具调用完全指南
本文系统介绍了LangChain工具调用的核心技术,包括工具创建、绑定和执行全流程。工具调用突破了大语言模型的封闭性,使其能执行计算、查询实时数据、调用API等外部交互。文章详细讲解了三种工具创建方式(@tool装饰器、Pydantic验证、StructuredTool),以及如何通过bind_tools()将工具绑定到模型。重点剖析了工具调用的完整工作流:模型决策、工具执行和结果整合,并提供了不
目录
2.1.3 进阶:依赖 Annotated(简洁元数据传递)
2.2 模式二:使用 StructuredTool(精细控制)
2.2.2 高级:配置 response_format(优化复杂结果处理)
3.2 bind_tools() 核心参数(控制模型调用行为)
大语言模型(LLM)虽然强大,但本质上是一个“只读”的知识库,无法直接与外部世界交互。而工具调用(Tool Calling)正是打破这一壁垒的关键技术。它让 LLM 不再局限于训练数据,能够执行计算、查询数据库、调用 API,甚至生成图表,从而真正成为一个强大的自动化助手。
本文将整合工具调用的基础认知、创建方法、绑定使用、执行回传及实战技巧,从零到一、由浅入深,帮助你完整掌握 LangChain 工具调用技术,轻松构建可交互、能落地的 AI 应用。
一、为什么需要工具调用?
LLM 本身是一个封闭的系统,其能力受限于训练数据的时效性和内在的文本生成逻辑。工具调用的作用体现在以下四个核心方面,也是其成为 LLM 生产力升级关键的原因:
-
扩展能力边界:让模型完成自身无法完成的任务,如执行复杂数学计算、实时查询天气、操作数据库或调用任何外部 API,打破“文本生成”的单一局限。
-
保证信息实时性:通过搜索引擎或数据库获取最新信息,避免 LLM 因训练数据滞后而“一本正经地胡说八道”,提升回答的准确性和实用性。
-
处理复杂任务:将复杂的用户请求(如“分析我上个月的消费趋势并生成图表”)分解为多个步骤,依次调用不同的工具协同完成,实现复杂工作流的自动化。
-
连接现有系统:将企业内部的系统、API 和数据库封装成工具,让 LLM 用自然语言驱动,无需额外开发交互界面,极大提升自动化和系统集成能力。
在 LangChain 中,工具调用的过程非常直观。例如,当你问“夏威夷的天气怎么样?”时,模型会生成一个结构化的工具调用请求,然后由系统执行并返回结果,最终整合为自然语言回答。
AIMessage(
tool_calls=[{
"name": "get_weather",
"args": {"location": "Hawaii"},
"id": "call_abc123",
"type": "tool_call"
}]
)
二、创建工具:从简单到专业(3种核心模式)
在 LangChain 中,创建工具的核心是将 Python 函数(或外部能力)封装成模型可理解的接口(包含名称、描述、参数规范)。根据需求复杂度,主要有三种创建模式,从简单到进阶逐步升级。
2.1 模式一:使用 @tool 装饰器(最简单入门)
@tool 装饰器是定义自定义工具的最简方式,它会自动将函数名、文档字符串和类型提示,转换为模型理解工具所需的 Schema(名称、描述、参数规范),无需额外配置。
2.1.1 基础用法
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two integers.
Args:
a: First integer
b: Second integer
"""
return a * b
# 调用工具(测试)
print(multiply.invoke({"a": 2, "b": 3})) # 输出: 6
print(multiply.name) # 输出: multiply(工具名称,自动取函数名)
print(multiply.description) # 输出: Multiply two integers...(工具描述,自动取文档字符串)
print(multiply.args) # 输出: {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}(参数Schema)
2.1.2 进阶:依赖 Pydantic(复杂参数验证)
当工具参数较多、需要更严格的输入验证,或需要更详细的参数描述时,可以结合 Pydantic 定义输入模型,让工具的 Schema 更清晰、更规范。
from langchain_core.tools import tool
from pydantic import BaseModel, Field
# 定义输入参数的 Pydantic 模型(包含参数描述和验证)
class AdderInput(BaseModel):
"""Add two integers.""" # 输入模型描述,辅助模型理解
a: int = Field(..., description="First integer") # ... 表示必填
b: int = Field(..., description="Second integer")
# 绑定输入模型,定义工具
@tool(args_schema=AdderInput)
def add(a: int, b: int) -> int:
"""Add two integers.""" # 工具功能描述
return a + b
2.1.3 进阶:依赖 Annotated(简洁元数据传递)
如果不需要复杂的输入验证,仅需给参数添加描述,也可以使用 typing_extensions.Annotated 传递元数据,语法更简洁。
from langchain_core.tools import tool
from typing_extensions import Annotated
@tool
def add(a: Annotated[int, "First integer"],
b: Annotated[int, "Second integer"]) -> int:
"""Add two integers."""
return a + b
2.2 模式二:使用 StructuredTool(精细控制)
当你需要对工具进行更精细的控制(如自定义工具名称、描述,或处理复杂的返回结果),或者从现有函数快速创建工具时,StructuredTool.from_function() 是更灵活的选择。
2.2.1 常规用法
from langchain_core.tools import StructuredTool
# 定义普通 Python 函数
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
# 从函数创建工具,自定义工具名称和描述
calculator_tool = StructuredTool.from_function(
func=multiply,
name="Calculator", # 自定义工具名称(替代函数名)
description="Multiply two numbers" # 自定义工具描述
)
2.2.2 高级:配置 response_format(优化复杂结果处理)
在处理包含大量数据(如图表、表格、长文本)的工具时,直接返回原始数据会消耗大量 Token,降低效率。通过 response_format="content_and_artifact" 可将结果分为两部分,优化处理效率:
-
content:给模型看的简洁摘要,节省 Token。
-
artifact:存储原始的、结构化的复杂数据,供后续步骤(如保存、二次处理)使用。
from pydantic import BaseModel, Field
from langchain_core.tools import StructuredTool
# 定义输入模型
class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
# 定义返回 content 和 artifact 的函数
def multiply(a: int, b: int) -> tuple[str, list[int]]:
"""Multiply two numbers and return a summary and the full result."""
result = a * b
# content: 给模型的简洁描述
content = f"The result of {a} * {b} is {result}"
# artifact: 存储的复杂数据结构(此处为示例,可替换为表格、图表等)
artifact = [a, b, result]
return content, artifact
# 创建工具,配置返回格式
calculator_tool = StructuredTool.from_function(
func=multiply,
name="Calculator",
description="Multiply two numbers",
args_schema=CalculatorInput,
response_format="content_and_artifact" # 关键配置
)
三、绑定工具:让模型“看见”自身能力
创建好工具后,下一步是将工具“绑定”到 LLM 上——这相当于告诉模型:“你现在可以调用这些工具了,它们的功能和参数是这样的”。绑定后,模型会自动分析用户需求,决定是否调用工具、调用哪个工具。
在 LangChain 中,绑定工具通过 `bind_tools()` 方法实现,它返回一个新的 Runnable 实例,封装了模型和工具的组合,无需修改模型本身的配置。
3.1 bind_tools() 基础用法
from langchain_openai import ChatOpenAI
# 1. 初始化大语言模型(以 OpenAI 模型为例)
model = ChatOpenAI(model="gpt-4o-mini")
# 2. 准备已创建的工具(沿用之前的 add, multiply 工具)
tools = [add, multiply]
# 3. 将工具绑定到模型上
model_with_tools = model.bind_tools(tools)
3.2 bind_tools() 核心参数(控制模型调用行为)
bind_tools() 提供了多个关键参数,可灵活控制模型的工具调用逻辑,适配不同场景需求,具体如下表所示:
|
参数名 |
核心作用 |
|---|---|
|
tools |
接收可绑定的工具类型列表,支持函数、Pydantic 类或 BaseModel(即我们前面创建的各类工具)。 |
|
tool_choice |
控制模型是否/如何调用工具(核心参数):- "auto" (默认): 自动判断,可调用或不调用;- "none": 强制不调用任何工具;- "any": 强制调用至少一个工具;- {"name": "<tool_name>"}: 强制调用指定的工具。 |
|
strict |
如果为 True,则模型输出严格遵循工具的 JSON Schema,拒绝任何参数偏差,避免调用失败。 |
|
parallel_tool_calls |
控制是否允许模型并行调用多个工具(如同时调用天气查询和时间查询),默认为 True,提升效率。 |
四、工具调用:完整工作流详解(从决策到执行)
绑定工具后,一个标准的工具调用流程包含 5 个关键步骤,形成完整的交互闭环: 1. 用户提问 → 2. 模型决策(是否调用工具、调用哪个)→ 3. 执行工具 → 4. 回传工具结果 → 5. 模型生成最终回答。
4.1 第一步:模型决策(核心环节)
当我们向绑定了工具的模型提问时,模型会首先分析问题的性质,自动决策: - 若问题可直接通过自身知识回答(如“Hello world!”),则不调用工具,直接返回自然语言; - 若问题需要外部能力(如计算、查实时数据),则生成结构化的 tool_calls 指令,指定要调用的工具和参数。
场景一:需要调用工具(如数学计算)
# 向绑定工具的模型提问
result = model_with_tools.invoke("9乘以5等于多少?")
print(result)
输出结果(AIMessage):模型返回空内容,仅生成 tool_calls 指令,告知系统需要调用 multiply 工具。
{
"content": "",
"additional_kwargs": {
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "multiply",
"arguments": {"a": 9, "b": 5}
}
}
]
}
}
场景二:不需要调用工具(如简单问候)
# 提问无需工具的问题
result = model_with_tools.invoke("Hello world!")
print(result)
输出结果(AIMessage):模型不生成 tool_calls,直接返回自然语言回答。
{
"content": "Hello! How can I assist you today?",
"additional_kwargs": {"refusal": None}
}
场景三:强制调用工具(指定 tool_choice)
有时我们希望模型必须使用工具(如测试工具可用性),即使问题可直接回答,此时可通过 tool_choice 参数强制调用。
# 强制模型调用至少一个工具
model_with_tools_forced = model.bind_tools(tools, tool_choice="any")
# 即使是简单问候,模型也会强制生成工具调用
result = model_with_tools_forced.invoke("Hello world!")
print(result.tool_calls)
4.2 第二步:执行工具(将指令转为结果)
模型生成的 tool_calls 只是“指令”,无法直接产生结果,需要我们手动(或通过系统)提取指令、执行对应的工具,并获取执行结果。
from langchain_core.messages import HumanMessage, ToolMessage
# 1. 构建对话历史(包含用户提问)
messages = [HumanMessage(content="9乘以5等于多少?5加3等于多少?")]
# 2. 模型生成工具调用指令(多个工具并行调用)
ai_msg = model_with_tools.invoke(messages)
messages.append(ai_msg) # 将工具调用指令加入对话历史,便于后续回传
# 3. 遍历所有 tool_calls,执行对应的工具
for tool_call in ai_msg.tool_calls:
# 根据工具名称,匹配对应的工具函数(此处可扩展为工具池查询)
selected_tool = {"add": add, "multiply": multiply}[tool_call["name"]]
# 执行工具:传入工具参数,获取执行结果
tool_result = selected_tool.invoke(tool_call["args"])
# 将结果包装成 ToolMessage(关联工具调用ID,便于模型对应),加入对话历史
messages.append(ToolMessage(tool_result, tool_call_id=tool_call["id"]))
4.3 第三步:回传结果,生成最终回答
将包含工具执行结果的 ToolMessage 再次发送给模型,模型会自动关联工具调用指令和执行结果,整合信息后生成自然、流畅的最终回答,完成整个交互闭环。
# 模型根据对话历史(用户提问 + 工具调用 + 工具结果),生成最终回答
final_response = model.invoke(messages)
print(final_response.content)
输出结果:9乘以5等于45,5加3等于8。
关键说明:整个流程中,我们实际上调用了两次 LLM: 1. 第一次调用:仅让模型分析需求,生成工具调用指令(不直接回答); 2. 第二次调用:让模型结合工具执行结果,整合信息并生成最终回答。
五、总结与展望:从“有工具”到“用好工具”
工具调用不是简单的“函数调用”,而是 LLM 与外部世界交互的完整心智模型,也是 LLM 应用从“玩具”走向“生产力工具”的关键一步。结合本文内容,我们可梳理出 LangChain 工具调用的核心逻辑:
-
定义工具:通过 @tool 装饰器、StructuredTool 等方式,将外部能力(函数、API、数据库等)封装成模型可理解的接口,核心是明确工具的名称、描述和参数 Schema。
-
绑定工具:通过 bind_tools() 方法将工具与 LLM 关联,配置 tool_choice 等参数,控制模型的调用行为,让模型“知道”自己拥有哪些能力。
-
智能决策:模型接收用户输入后,自动分析需求,决定是否调用工具、调用哪个工具,生成结构化的调用指令。
-
执行调用:提取工具调用指令,执行对应的工具,将结果包装成 ToolMessage,与对话历史关联。
-
整合回答:模型结合用户提问、工具调用指令和执行结果,生成整合后的自然语言回答,完成闭环。
掌握了这套完整流程,你就可以灵活扩展 LLM 的能力边界——无论是实时查询、数据计算,还是系统集成、复杂工作流自动化,都能通过 LangChain 工具调用轻松实现。
未来,随着 Agent 智能体技术的发展,工具调用将不再局限于“单个任务执行”,而是成为复杂工作流的自动化编排中心:模型可自主规划步骤、选择工具、处理异常,真正实现“输入需求,输出结果”的全自动化 AI 助手。
更多推荐

所有评论(0)