LangChain Tools:工具使用完全指南

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)自定义工具描述

通过@tooldescription参数手动指定描述,优先级高于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 模型的核心价值
    1. 参数校验 :自动校验输入参数的类型、枚举值(比如units只能传celsius/fahrenheit),避免无效参数;

    2. 清晰的参数描述 :通过Field为每个参数添加描述,模型能精准理解参数含义;

    3. 默认值配置:支持为参数设置默认值,简化工具调用;

    4. 复杂参数扩展:可定义嵌套模型、列表、字典等复杂参数,满足各类业务场景。

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.toolslangchain.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)。

五、核心开发要点总结 📌

  1. 类型注解必选:工具函数的输入/输出必须添加类型注解,LangChain 基于注解生成Schema;

  2. docstring清晰:工具的描述(docstring/自定义description)是模型判断是否调用的核心,需明确功能和使用场景;

  3. 避免保留参数 :不要使用config/runtime作为工具参数,避免运行时错误;

  4. 上下文分层使用:短期数据用State、不可变配置用Context、长期数据用Store;

  5. 生产环境持久化 :Store 务必使用PostgresStore等持久化实现,避免InMemoryStore的数据丢失;

  6. ToolNode 用于自定义工作流 :需要细粒度控制工具执行时,使用ToolNode替代create_agent

  7. 错误处理不可少 :生产环境中为ToolNode配置自定义错误处理,提升系统鲁棒性;

  8. 优先使用预构建工具:常见任务直接使用LangChain的预构建工具,减少重复开发。

相关推荐
SQL必知必会2 小时前
SQL 计算百分位数和中位数
数据库·sql
亓才孓2 小时前
[SpringBoot]UnableToConnectException : Public Key Retrieval is not allowed
java·数据库·spring boot
好学且牛逼的马2 小时前
从“混沌初开”到“有序统一”:Java集合框架发展历程与核心知识点详解
前端·数据库·python
wuqingshun3141592 小时前
什么是浅拷贝,什么是深拷贝,如何实现深拷贝?
java·开发语言·jvm
REDcker4 小时前
HDR Vivid 技术介绍
数据库·算法·视频·sdr·屏幕·显示技术·dhr
冰暮流星4 小时前
sql语句之union语句
数据库·sql
2401_876381924 小时前
程序人生-Hello’s P2P
数据库·程序人生·p2p
VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue养老院管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
湘-枫叶情缘5 小时前
从数据库写作到情绪工程:网络文学工程化转向的理论综述
数据库·人工智能