LangChain Tools:工具使用完全指南
LangChain 中的工具(Tools) 是扩展智能体(Agent)能力的核心组件,能让智能体实现实时数据获取、代码执行、外部数据库查询、现实世界操作等功能。底层来看,工具是具有明确输入输出的可调用函数,会被传递给大语言模型(Chat Model),由模型根据对话上下文决定何时调用、传入哪些参数。
本文将从工具创建、自定义配置、运行时上下文访问、ToolNode 工作流、预构建工具等维度,全面讲解 LangChain 工具的使用。
一、创建工具 🛠️
LangChain 提供了极简的工具创建方式,核心是@tool装饰器,同时支持基础定义、自定义属性、高级模式三种创建层级,满足不同场景需求。
1.1 基础工具定义
这是创建工具的最简方式,通过@tool装饰普通Python函数即可,类型注解是必选的(用于定义工具的输入模式),函数的文档字符串(docstring)会自动作为工具的描述,帮助大语言模型理解工具的用途。
代码示例+详解
from langchain.tools import tool # 导入工具装饰器
@tool # 装饰器将普通函数转为LangChain工具
def search_database(query: str, limit: int = 10) -> str:
"""
搜索客户数据库中匹配查询条件的记录
Args:
query: 搜索关键词
limit: 返回结果的最大数量,默认10条
"""
# 实际场景中可替换为真实的数据库查询逻辑
return f"Found {limit} results for '{query}'"
- 关键要点 :
-
类型注解(
query: str/limit: int/-> str):LangChain 会根据注解生成工具的输入/输出Schema,模型会依据Schema传递参数; -
docstring:必须清晰、简洁,明确工具的功能和参数含义,这是模型判断是否调用该工具的核心依据;
-
函数返回值:建议为字符串类型,方便模型直接解析使用。
-
1.2 自定义工具属性
默认情况下,工具的名称为函数名、描述为docstring,LangChain 支持手动覆盖这两个属性,让工具的定义更贴合业务场景。
(1)自定义工具名称
通过@tool("自定义名称")的方式指定,适用于函数名过于简洁、无法体现工具功能的场景。
@tool("web_search") # 自定义工具名称为web_search,替代默认的search
def search(query: str) -> str:
"""搜索网络上的信息"""
return f"Results for: {query}"
print(search.name) # 输出:web_search,验证自定义名称生效
(2)自定义工具描述
通过@tool的description参数手动指定描述,优先级高于docstring,可让模型更精准地理解工具用途(比如明确「何时使用该工具」)。
# 自定义名称为calculator,描述为「执行算术计算,所有数学问题都用这个工具」
@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
"""解析数学表达式"""
return str(eval(expression)) # 执行表达式计算,实际场景建议增加安全校验
核心作用 :自定义描述可以直接给模型下达「使用指令」,比如上述示例中明确Use this for any math problems,模型会在遇到数学问题时主动调用该计算器工具。
1.3 高级模式定义(复杂输入)
当工具需要复杂的输入参数 (比如多参数、可选参数、枚举值、参数校验)时,可通过Pydantic 模型 或JSON Schema 定义输入模式,再通过args_schema参数传递给@tool装饰器,是生产环境的推荐方式。
代码示例+详解(Pydantic 模型,最常用)
from pydantic import BaseModel, Field # Pydantic核心库,用于定义数据模型
from typing import Literal # 用于定义枚举类型参数
# 第一步:定义Pydantic模型,描述工具的输入参数
class WeatherInput(BaseModel):
"""天气查询工具的输入参数模型"""
# 城市名/经纬度,Field描述参数含义,模型会自动校验参数类型
location: str = Field(description="City name or coordinates")
# 枚举值:仅支持celsius(摄氏度)/fahrenheit(华氏度),默认摄氏度
units: Literal["celsius", "fahrenheit"] = Field(
default="celsius",
description="Temperature unit preference"
)
# 是否包含5天预报,布尔值,默认不包含
include_forecast: bool = Field(
default=False,
description="Include 5-day forecast"
)
# 第二步:将模型传入args_schema,创建工具
@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
"""获取当前天气,可选返回5天预报"""
# 模拟天气查询逻辑,实际场景替换为真实的天气API调用
temp = 22 if units == "celsius" else 72
result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
# 如果需要预报,追加预报信息
if include_forecast:
result += "\nNext 5 days: Sunny"
return result
- Pydantic 模型的核心价值 :
-
参数校验 :自动校验输入参数的类型、枚举值(比如
units只能传celsius/fahrenheit),避免无效参数; -
清晰的参数描述 :通过
Field为每个参数添加描述,模型能精准理解参数含义; -
默认值配置:支持为参数设置默认值,简化工具调用;
-
复杂参数扩展:可定义嵌套模型、列表、字典等复杂参数,满足各类业务场景。
-
1.4 保留参数名称 ⚠️
LangChain 规定了两个保留参数名 ,不能作为工具的自定义参数,否则会触发运行时错误,这是开发中需要避免的坑。
| 保留参数名 | 用途 |
|---|---|
config |
内部用于传递RunnableConfig配置,包含回调、标签、元数据等 |
runtime |
用于传递ToolRuntime对象,实现状态、上下文、持久化存储的访问 |
注意 :如果需要访问运行时信息(如对话状态、用户上下文),直接使用ToolRuntime参数即可,无需手动定义config/runtime。
二、访问运行时上下文 📝
工具的核心能力之一是访问运行时的上下文信息(比如对话历史、用户ID、持久化偏好),让工具能根据不同的场景和用户返回个性化结果。
LangChain 提供ToolRuntime参数实现上下文访问,该参数会自动注入 到工具中,且对大语言模型隐藏(不会出现在工具的输入Schema中),只需在工具函数中声明即可使用。
ToolRuntime 包含6大核心组件,覆盖短/长期记忆、实时流、配置等所有运行时信息,如下表所示:
| 组件 | 类型 | 描述 | 典型使用场景 |
|---|---|---|---|
| State | 短期记忆 | 当前对话的可变数据,仅在本次对话有效 | 访问对话历史、统计工具调用次数、存储临时用户偏好 |
| Context | 不可变配置 | 调用工具时传入的固定配置,本次对话全程不变 | 基于用户ID获取个人信息、会话ID关联业务数据 |
| Store | 长期记忆 | 跨对话的持久化存储,数据在未来会话中仍有效 | 保存用户长期偏好、维护知识库、存储历史操作记录 |
| Stream Writer | 实时流输出 | 工具执行过程中发送实时更新 | 为长耗时操作提供进度反馈(如「正在查询数据」「数据查询完成」) |
| Config | 执行配置 | 工具的执行配置信息 | 访问回调函数、标签、元数据,实现日志追踪 |
| Tool Call ID | 唯一标识 | 本次工具调用的唯一ID | 日志关联、多工具调用的溯源、模型调用与工具调用的关联 |
2.1 短期记忆(State)
State 是当前对话的临时内存,生命周期与对话一致,对话结束后数据销毁,包含对话消息历史、自定义字段(如计数器、临时偏好)等。
(1)访问State(读取对话信息)
通过runtime.state访问,核心是读取对话消息历史和自定义字段,代码示例附带详细解析:
from langchain.tools import tool, ToolRuntime # 导入ToolRuntime
from langchain.messages import HumanMessage # 导入人类消息类型,用于筛选
@tool
def get_last_user_message(runtime: ToolRuntime) -> str:
"""获取用户的最新一条消息"""
# 从runtime.state中读取对话消息列表,key固定为"messages"
messages = runtime.state["messages"]
# 倒序遍历消息,找到最后一条人类消息(排除模型/工具消息)
for message in reversed(messages):
if isinstance(message, HumanMessage):
return message.content
# 无人类消息时的兜底返回
return "No user messages found"
# 访问自定义State字段(如用户临时偏好)
@tool
def get_user_preference(
pref_name: str, # 模型可见的参数:要获取的偏好名称
runtime: ToolRuntime # 模型隐藏的参数:运行时上下文
) -> str:
"""获取用户的临时偏好值"""
# 从state中读取自定义字段user_preferences,不存在则返回空字典
preferences = runtime.state.get("user_preferences", {})
# 返回指定偏好值,不存在则返回"Not set"
return preferences.get(pref_name, "Not set")
- 关键要点 :
-
runtime.state是一个字典,内置messages字段存储对话历史,也可自定义任意字段; -
ToolRuntime参数对模型隐藏,上述示例中模型仅能看到pref_name参数,无需传递runtime; -
消息类型区分:LangChain 会将消息分为
HumanMessage(人类)、AIMessage(模型)、ToolMessage(工具)等,可通过类型筛选指定来源的消息。
-
(2)更新State(修改对话信息)
通过langgraph.types.Command对象更新State,返回Command(update={字段: 新值})即可实现自定义字段的修改,适用于工具需要向对话中写入临时数据的场景。
from langgraph.types import Command # 导入Command对象
from langchain.tools import tool
@tool
def set_user_name(new_name: str) -> Command:
"""将用户名写入对话的短期记忆(State)"""
# 返回Command对象,update参数指定要修改的State字段和新值
return Command(update={"user_name": new_name})
- 并发冲突处理 :如果多个工具并行调用 且修改同一个State字段,建议为字段定义Reducer(归约器),用于解决冲突(比如取最新值、合并值)。
2.2 不可变上下文(Context)
Context 是调用工具时传入的固定配置 ,具有不可变性 (本次对话中无法修改),适用于存储用户ID、会话ID、应用级配置等无需变动的信息,通过runtime.context访问。
代码示例+详解(用户账户信息查询)
from dataclasses import dataclass # 用于定义简单的上下文模型
from langchain_openai import ChatOpenAI # 导入OpenAI大模型
from langchain.agents import create_agent # 导入智能体创建函数
from langchain.tools import tool, ToolRuntime
# 模拟用户数据库,实际场景替换为真实的数据库/缓存
USER_DATABASE = {
"user123": {"name": "Alice Johnson", "account_type": "Premium", "balance": 5000, "email": "alice@example.com"},
"user456": {"name": "Bob Smith", "account_type": "Standard", "balance": 1200, "email": "bob@example.com"}
}
# 第一步:定义上下文模型,使用dataclass简化定义(也可使用Pydantic模型)
@dataclass
class UserContext:
user_id: str # 上下文仅包含用户ID,不可变
# 第二步:创建工具,通过ToolRuntime[UserContext]指定上下文类型
@tool
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
"""获取当前用户的账户信息"""
# 从runtime.context中读取用户ID
user_id = runtime.context.user_id
# 根据用户ID查询数据库
if user_id in USER_DATABASE:
user = USER_DATABASE[user_id]
return f"账户持有人:{user['name']}\n账户类型:{user['account_type']}\n账户余额:${user['balance']}"
return "用户不存在"
# 第三步:创建智能体,指定上下文模型
model = ChatOpenAI(model="gpt-4.1") # 初始化大模型
agent = create_agent(
model=model,
tools=[get_account_info], # 绑定工具
context_schema=UserContext, # 指定上下文模型
system_prompt="你是一个金融助理,负责查询用户的账户信息"
)
# 第四步:调用智能体,传入具体的上下文(用户ID=user123)
result = agent.invoke(
{"messages": [{"role": "user", "content": "我的账户余额是多少?"}]},
context=UserContext(user_id="user123") # 传入不可变上下文
)
- 核心价值 :Context 实现了用户信息与工具的解耦,工具无需硬编码用户ID,而是通过上下文获取,让工具具有通用性,可复用在不同用户的对话中。
2.3 长期记忆(Store)
Store 是跨对话的持久化存储 ,基于BaseStore实现,数据不会随对话结束而销毁,适用于保存用户长期偏好、知识库、历史操作记录等,通过runtime.store访问。
LangChain 提供了多种Store实现:
-
开发环境 :
InMemoryStore(内存存储,重启后数据丢失); -
生产环境 :
PostgresStore(PostgreSQL)、RedisStore(Redis)等持久化存储(推荐)。
Store 采用**「命名空间/键」** 的双层模式组织数据,避免键冲突,格式为store.get((命名空间,), 键)/store.put((命名空间,), 键, 值)。
代码示例+详解(用户信息的持久化增查)
from typing import Any # 用于定义任意类型的参数
from langgraph.store.memory import InMemoryStore # 导入内存存储(开发用)
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
# 1. 读取持久化的用户信息
@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
"""根据用户ID查询持久化的用户信息"""
# 从runtime中获取store实例
store = runtime.store
# 从命名空间"users"中读取指定user_id的信息,格式:get((命名空间,), 键)
user_info = store.get(("users",), user_id)
# 存在则返回值,否则返回"未知用户"
return str(user_info.value) if user_info else "Unknown user"
# 2. 保存用户信息到持久化存储
@tool
def save_user_info(user_id: str, user_info: dict[str, Any], runtime: ToolRuntime) -> str:
"""将用户信息保存到持久化存储"""
store = runtime.store
# 向命名空间"users"中写入键值对,格式:put((命名空间,), 键, 值)
store.put(("users",), user_id, user_info)
return "用户信息保存成功"
# 3. 初始化Store(开发用InMemoryStore,生产替换为PostgresStore)
store = InMemoryStore()
# 4. 创建智能体,绑定Store和工具
agent = create_agent(
model=model, # 需提前初始化大模型(如ChatOpenAI)
tools=[get_user_info, save_user_info],
store=store # 传入持久化存储实例
)
# 5. 第一次会话:保存用户信息(跨会话有效)
agent.invoke({
"messages": [{"role": "user", "content": "保存用户信息:userid=abc123,name=Foo,age=25,email=foo@langchain.dev"}]
})
# 6. 第二次会话:查询已保存的用户信息(即使重启程序,持久化存储仍能读取)
agent.invoke({
"messages": [{"role": "user", "content": "查询用户ID为abc123的信息"}]
})
-
输出结果 :
以下是用户ID为abc123的信息: - 姓名:Foo - 年龄:25 - 邮箱:foo@langchain.dev -
生产环境注意 :务必替换
InMemoryStore为持久化存储(如PostgresStore),否则重启应用后数据会全部丢失。
2.4 实时流输出(Stream Writer)
Stream Writer 用于在工具执行过程中发送实时更新 ,适用于长耗时的工具操作 (如大数据查询、文件生成、API调用),为用户提供进度反馈,提升交互体验,通过runtime.stream_writer使用。
代码示例+详解
from langchain.tools import tool, ToolRuntime
@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
"""查询指定城市的天气"""
# 获取流写入器实例
writer = runtime.stream_writer
# 发送实时进度更新,可多次调用
writer(f"正在查询{city}的天气数据...")
writer(f"已获取{city}的天气原始数据,正在解析...")
# 模拟天气查询逻辑
return f"{city}的天气:晴,25℃,微风"
- 注意 :使用
runtime.stream_writer的工具,必须在LangGraph的执行上下文中调用,否则会触发错误。
三、ToolNode:LangGraph 工作流中的工具执行 📈
ToolNode是LangGraph提供的预构建节点 ,专门用于在工作流中执行工具,自动处理并行工具执行、错误处理、状态注入等复杂逻辑,是实现自定义工作流的核心组件。
如果需要对工具执行进行细粒度的控制 (如自定义执行顺序、条件路由),建议使用ToolNode替代create_agent,它是智能体工具执行的底层构建块。
3.1 基础使用
核心步骤:创建工具 → 初始化ToolNode → 将ToolNode添加到LangGraph的状态图中。
代码示例+详解
from langchain.tools import tool
from langgraph.prebuilt import ToolNode # 导入ToolNode
from langgraph.graph import StateGraph, MessagesState, START, END # 导入状态图相关组件
# 1. 创建两个基础工具
@tool
def search(query: str) -> str:
"""搜索网络信息"""
return f"搜索结果:{query}"
@tool
def calculator(expression: str) -> str:
"""执行数学计算"""
return str(eval(expression))
# 2. 初始化ToolNode,传入需要执行的工具列表
tool_node = ToolNode([search, calculator])
# 3. 创建LangGraph状态图,指定状态类型为MessagesState(消息状态)
builder = StateGraph(MessagesState)
# 4. 将ToolNode添加到状态图中,节点名称为"tools"
builder.add_node("tools", tool_node)
# 5. 后续可添加其他节点(如LLM节点)、定义节点间的边(执行顺序)
# ... add other nodes and edges
- MessagesState :LangGraph的内置状态类型,核心字段为
messages,用于存储对话历史,是最常用的状态类型。
3.2 错误处理
ToolNode支持灵活的错误处理配置,通过handle_tool_errors参数实现,可设置为布尔值、自定义错误信息、自定义错误处理函数、指定异常类型,满足不同的错误处理需求。
代码示例(所有错误处理方式)
from langgraph.prebuilt import ToolNode
# 方式1:默认行为 → 捕获工具调用错误,重新抛出工具执行错误
tool_node = ToolNode(tools)
# 方式2:捕获所有错误,向LLM返回默认错误信息
tool_node = ToolNode(tools, handle_tool_errors=True)
# 方式3:捕获所有错误,返回自定义错误信息
tool_node = ToolNode(tools, handle_tool_errors="工具执行失败,请稍后重试!")
# 方式4:自定义错误处理函数,针对不同异常返回个性化信息
def handle_error(e: ValueError) -> str:
"""自定义错误处理函数,参数为异常对象,返回字符串"""
return f"输入参数无效:{e}"
tool_node = ToolNode(tools, handle_tool_errors=handle_error)
# 方式5:仅捕获指定类型的异常(如ValueError、TypeError)
tool_node = ToolNode(tools, handle_tool_errors=(ValueError, TypeError))
3.3 条件路由(tools_condition)
通过tools_condition可实现基于LLM是否调用工具的条件路由,让工作流根据模型的决策自动选择「执行工具」或「结束对话」,是实现智能体「思考-执行」循环的核心。
代码示例+详解
from langgraph.prebuilt import ToolNode, tools_condition # 导入tools_condition路由函数
from langgraph.graph import StateGraph, MessagesState, START, END
# 假设已定义call_llm(LLM节点)和tools(工具列表)
tool_node = ToolNode(tools)
builder = StateGraph(MessagesState)
# 1. 添加LLM节点和ToolNode节点
builder.add_node("llm", call_llm) # llm节点:模型思考,决定是否调用工具
builder.add_node("tools", tool_node) # tools节点:执行工具
# 2. 定义执行流:开始 → LLM节点
builder.add_edge(START, "llm")
# 3. 条件路由:LLM节点执行后,根据是否调用工具决定下一步
# - 模型决定调用工具 → 跳转到tools节点
# - 模型无需调用工具 → 结束对话(END)
builder.add_conditional_edges("llm", tools_condition)
# 4. 工具执行后,回到LLM节点,继续思考(形成「思考-执行」循环)
builder.add_edge("tools", "llm")
# 5. 编译状态图,生成可执行的工作流
graph = builder.compile()
- 核心逻辑 :实现了智能体的经典循环 → LLM思考 → 判断是否调用工具 → 工具执行 → 回到LLM继续思考(直到无需调用工具,结束对话)。
3.4 状态注入
ToolNode会自动将LangGraph的全局状态 注入到工具的ToolRuntime中,工具可通过runtime.state直接访问工作流的全局状态,无需额外配置,实现了工具与工作流状态的无缝对接。
代码示例
from langchain.tools import tool, ToolRuntime
from langgraph.prebuilt import ToolNode
# 创建工具,访问工作流的全局消息状态
@tool
def get_message_count(runtime: ToolRuntime) -> str:
"""获取对话中的消息总数"""
# 直接访问LangGraph的全局状态messages
messages = runtime.state["messages"]
return f"当前对话共有{len(messages)}条消息"
# 初始化ToolNode,工具自动获取全局状态
tool_node = ToolNode([get_message_count])
四、预构建工具与服务端工具 📦
LangChain 提供了丰富的预构建工具 和服务端工具,无需手动开发即可直接使用,大幅提升开发效率。
4.1 预构建工具(Prebuilt tools)
LangChain 内置了大量针对常见任务的预构建工具/工具集(Toolkits),覆盖网络搜索、代码解释、数据库访问、文件操作、API调用等场景,可直接集成到智能体中,无需编写自定义逻辑。
典型预构建工具分类:
-
搜索类:Bing Search、Google Search、DuckDuckGo Search;
-
代码类:Code Interpreter(代码解释器)、Python REPL;
-
数据库类:SQL Database Toolkit(MySQL/PostgreSQL/SQLite);
-
文件类:CSV Tool、PDF Tool、Excel Tool;
-
其他:Email Tool、Slack Tool、GitHub Tool。
使用方式 :直接从langchain.tools或langchain.agents.toolkits导入,绑定到智能体即可,示例:
from langchain.tools import DuckDuckGoSearchRun
from langchain.agents import create_agent
# 初始化预构建的搜索工具
search_tool = DuckDuckGoSearchRun()
# 绑定到智能体
agent = create_agent(model, tools=[search_tool], system_prompt="你是一个智能搜索助手")
4.2 服务端工具(Server-side tool use)
部分大语言模型(如GPT-4、Claude)内置了服务端工具 (由模型提供商部署和执行),如网络搜索、代码解释器、函数调用等,无需开发者定义或托管工具逻辑,只需在调用模型时启用工具调用功能即可。
核心特点:
-
无需开发工具逻辑,模型直接在服务端执行;
-
支持实时数据获取(如GPT-4的网络搜索);
-
需遵循模型提供商的工具调用规范。
使用方式 :参考对应模型的LangChain集成文档,启用工具调用功能即可(如OpenAI的enable_function_calling)。
五、核心开发要点总结 📌
-
类型注解必选:工具函数的输入/输出必须添加类型注解,LangChain 基于注解生成Schema;
-
docstring清晰:工具的描述(docstring/自定义description)是模型判断是否调用的核心,需明确功能和使用场景;
-
避免保留参数 :不要使用
config/runtime作为工具参数,避免运行时错误; -
上下文分层使用:短期数据用State、不可变配置用Context、长期数据用Store;
-
生产环境持久化 :Store 务必使用
PostgresStore等持久化实现,避免InMemoryStore的数据丢失; -
ToolNode 用于自定义工作流 :需要细粒度控制工具执行时,使用
ToolNode替代create_agent; -
错误处理不可少 :生产环境中为
ToolNode配置自定义错误处理,提升系统鲁棒性; -
优先使用预构建工具:常见任务直接使用LangChain的预构建工具,减少重复开发。