【AI智能体】Langchain 工具调用实战操作详解

目录

一、前言

[二、Tools 工具介绍](#二、Tools 工具介绍)

[2.1 Tools 是什么](#2.1 Tools 是什么)

[2.1.1 Tool 组成结构](#2.1.1 Tool 组成结构)

[2.1.2 Tool 的重要性](#2.1.2 Tool 的重要性)

[2.2 Tools 有什么作用](#2.2 Tools 有什么作用)

[2.3 Tool 的特点](#2.3 Tool 的特点)

[2.4 Tool 使用场景](#2.4 Tool 使用场景)

三、工具的调用方式

[3.1 工具的两种调用](#3.1 工具的两种调用)

[3.1.1 直接调用](#3.1.1 直接调用)

[3.1.2 绑定到模型(主流)调用](#3.1.2 绑定到模型(主流)调用)

[3.1.3 工具调用完整流程](#3.1.3 工具调用完整流程)

[3.2 从Message流转看工具的调用](#3.2 从Message流转看工具的调用)

[3.3 工具描述的各部分参数详解](#3.3 工具描述的各部分参数详解)

[3.3.1 convert_to_openai_to](#3.3.1 convert_to_openai_to)

[3.3.2 description 说明](#3.3.2 description 说明)

[3.3.3 参数说明](#3.3.3 参数说明)

[3.3.4 参数类型说明](#3.3.4 参数类型说明)

[3.3.5 参数默认值](#3.3.5 参数默认值)

[3.4 使用@tool装饰器定义工具](#3.4 使用@tool装饰器定义工具)

[3.4.1 自定义工具描述:description](#3.4.1 自定义工具描述:description)

[3.4.2 添加工具描述:description](#3.4.2 添加工具描述:description)

[3.4.3 更改工具名称](#3.4.3 更改工具名称)

[3.5 自定义args_schema](#3.5 自定义args_schema)

[3.5.1 pydantic类型的定义](#3.5.1 pydantic类型的定义)

[3.5.2 限定参数取值范围](#3.5.2 限定参数取值范围)

[3.6 使用Json Schema定义](#3.6 使用Json Schema定义)

四、写在最后


一、前言

在使用Langchain 进行智能体开发中,会涉及到一个非常重要的组件调用,就是工具(Tool),工具作为AI应用与大模型的中间层,承载着非常重要的角色,可以说搞清楚了Tool在其中的作用,对AI智能体调用的完整过程就有了一个深刻全面的理解,本篇将详细介绍下Langchain 中的工具使用。

二、Tools 工具介绍

2.1 Tools 是什么

LangChain 工具(Tool)是赋予大模型调用外部能力的标准化接口。

大模型本身只能生成文本,无法主动联网、查数据库、执行代码。工具就是把这些外部能力封装成 LLM 能理解、能调用的函数,让模型从"只会说"变成"能动手做"。

2.1.1 Tool 组成结构

一个工具通常包含三部分:

  • 名称(name):唯一标识

  • 描述(description):LLM 据此判断何时调用,最关键

  • 功能(function):实际执行的代码逻辑

工具包(Toolkit)则是一组相关工具的集合,比如 SQLDatabaseToolkit 把列表、查结构、执行查询等工具打包在一起。

2.1.2 Tool 的重要性

要构建更强大的AI工程应用,只有生成文本这样的" 纸上谈兵 "能力自然是不够的。

工具是赋予大语言模型 与外部世界交互能力 的关键组件,从而能让智能体执行搜索、计算、数据库查询、邮件发送或调用第三方API等,进而构建功能强大的AI应用。借助工具,大模型才能从" 认识世界 "走向" 改变世界 "。

从图中可以看到,工具是构建智能体的核心要素之一,具有非常重要的地位

2.2 Tools 有什么作用

Tool的核心作用是扩展大模型的能力边界,具体包括:

  1. 突破知识时效性:通过搜索工具获取实时信息,弥补训练数据截止的局限。

  2. 突破能力限制:通过计算器、代码执行工具完成精确计算和复杂逻辑。

  3. 连接私有数据:通过数据库、文件系统工具访问企业或个人私有数据。

  4. 对接外部系统:通过 API 工具调用第三方服务,完成真实业务操作。

  5. 让 Agent 自主决策:工具是 Agent 的"手脚",Agent 根据任务自主选择并调用合适的工具。

2.3 Tool 的特点

Tool 主要有如下特点:

  1. 描述驱动:LLM 完全依赖工具的 description 来决定是否调用、如何调用,描述写得越清楚,调用越准确。

  2. 标准化接口:所有工具都遵循统一调用方式(如 .invoke()),便于组合和替换。

  3. 可自定义:用 @tool 装饰器就能把普通 Python 函数变成工具,扩展成本低。

  4. 可组合:多个工具可同时提供给 Agent,由 Agent 按需调度。

  5. 安全可控:工具的执行逻辑由开发者定义,可以对输入输出做校验和限制。

2.4 Tool 使用场景

|---------|-----------------------|----------------|
| 场景 | 典型工具 | 解决的问题 |
| 实时信息查询 | DuckDuckGo、SerpAPI | 获取最新新闻、股价、天气 |
| 知识问答 | WikipediaQueryRun | 补充百科类知识 |
| 数据分析/计算 | PythonREPLTool | 精确计算、数据处理 |
| 数据库交互 | SQLDatabaseToolkit | 自然语言查数据库 |
| 文件操作 | FileManagementToolkit | 读写、管理本地文件 |
| 业务系统对接 | OpenAPIToolkit | 按规范调用 REST API |
| 多工具协作 | Agent + 多工具 | 复杂任务自动拆解执行 |

一句话总结:

LangChain 工具是让大模型"能做事"的桥梁,通过标准化封装和描述驱动机制,使 LLM 能自主调用外部能力,适用于一切需要模型与真实世界交互的场景。

三、工具的调用方式

在LangChain中,工具(Tools)实际上是指明确定义了输入和输出的 可调用函数 。因此, 工具调用(Tool Calling) 也被称为 函数调用(Function Calling) 。具体来说主要有2种调用方式,下面分别介绍。

3.1 工具的两种调用

3.1.1 直接调用

直接调用这种方式,适合测试时使用,下面是一个基础的工具调用案例代码

python 复制代码
from langchain_core.tools import Tool, tool

@tool
def get_weather(city: str) -> str:
    """
    获取指定城市的天气信息

    参数:
        city: 城市名称,如"北京"、"上海"

    返回:
        天气信息字符串
    """
    # 你的实现
    return city + "晴天,温度 15°C"

# 使用 .invoke() 方法
result = get_weather.invoke({"city": "北京"})
print(result)

运行一下效果如下

3.1.2 绑定到模型(主流)调用

这种方式,让AI来调用,开发中使用最多的方式

python 复制代码
from dotenv import load_dotenv
import os

from langchain_openrouter import ChatOpenRouter

# 1、加载配置文件
load_dotenv(override=True)

OPENROUTER_API_KEY = os.getenv ("OPENROUTER_API_KEY")
OPENROUTER_API_BASE = os.getenv ("OPENROUTER_API_BASE")

# 2、模型初始化
model = ChatOpenRouter(
    model="deepseek/deepseek-v3.2",
    api_key=OPENROUTER_API_KEY,
    base_url=OPENROUTER_API_BASE,
)

from langchain_core.tools import tool

# 定义工具
@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气"""
    # 你的实现
    return "晴天,温度 15°C"

# 绑定工具
model_with_tools = model.bind_tools([get_weather])

# AI 可以决定是否调用工具
response = model_with_tools.invoke("北京天气如何?")

# response = model_with_tools.invoke("2 + 3 = ?")
# 检查 AI 是否要调用工具
if response.tool_calls:
    print("AI 想调用工具:", response.tool_calls)
else:
    print("AI 直接回答:", response.content)

调用一下,看到下面的效果,在真实的业务场景下,可能会在tool的方法内部对接外部厂商的API ,以获取天气为例,就可以拿到真实的天气信息数据

3.1.3 工具调用完整流程

大模型能根据对话上下文决定何时调用工具以及传递哪些参数。

上面编写的LangChain应用正好对应图中的AI助手或应用

经典流程如下:

3.2 从Message流转看工具的调用

接下来深入了解一下工具内部在代码中是怎么调用的,前提:代码中配置模型初始化

python 复制代码
from dotenv import load_dotenv
from langchain.messages import HumanMessage, ToolMessage
import os

from langchain_openrouter import ChatOpenRouter

# 1、加载配置文件
load_dotenv(override=True)

OPENROUTER_API_KEY = os.getenv ("OPENROUTER_API_KEY")
OPENROUTER_API_BASE = os.getenv ("OPENROUTER_API_BASE")

# 2、模型初始化
model = ChatOpenRouter(
    model="deepseek/deepseek-v3.2",
    api_key=OPENROUTER_API_KEY,
    base_url=OPENROUTER_API_BASE,
)

1、不使用@tool修饰

通过定义普通的方法形式使用,完整代码如下

python 复制代码
from dotenv import load_dotenv
from langchain.messages import HumanMessage, ToolMessage
import os

from langchain_openrouter import ChatOpenRouter

# 1、加载配置文件
load_dotenv(override=True)

OPENROUTER_API_KEY = os.getenv ("OPENROUTER_API_KEY")
OPENROUTER_API_BASE = os.getenv ("OPENROUTER_API_BASE")

# 2、模型初始化
model = ChatOpenRouter(
    model="deepseek/deepseek-v3.2",
    api_key=OPENROUTER_API_KEY,
    base_url=OPENROUTER_API_BASE,
)

from langchain_core.tools import tool

def get_weather(city: str):
    """获取天气的工具"""
    return f"{city}天气晴朗"

# 模型与工具绑定
model_with_tools = model.bind_tools([get_weather])

messages = [
    HumanMessage("今天北京天气如何")
]

# 模型生成调用工具请求
response = model_with_tools.invoke(messages)

# 添加AIMessage
messages.append(response)
tool_calls = response.tool_calls

for tool_call in tool_calls:
    if tool_call["name"] == "get_weather":
        # 拼接出ToolMessage实例
        tool_response = ToolMessage(
            content=get_weather(**tool_call["args"]),
            tool_call_id=tool_call["id"],
            name=tool_call["name"]
        )
        messages.append(tool_response)

print("=====================> messages <=====================")
for msg in messages:
    msg.pretty_print()
print("=====================> messages <=====================")

final_response = model_with_tools.invoke(messages)
print(f"final_response: \n{final_response}")

上述代码对应的完整调用流程如下图

2、使用@tool修饰

使用@tool修饰之后,完整的调用代码如下

python 复制代码
from langchain_core.tools import tool

@tool
def get_weather(city: str):
    """获取天气的工具"""
    return f"{city}天气晴朗"

# 模型与工具绑定
model_with_tools = model.bind_tools([get_weather])

messages = [
    HumanMessage("今天北京天气如何")
]

# 模型生成调用工具请求
response = model_with_tools.invoke(messages)

# 添加AIMessage
messages.append(response)
tool_calls = response.tool_calls

for tool_call in tool_calls:
    if tool_call["name"] == "get_weather":
        tool_response = get_weather.invoke(tool_call)
        print(type(tool_response))
        messages.append(tool_response)

print("=====================> messages <=====================")
for msg in messages:
    msg.pretty_print()
print("=====================> messages <=====================")

final_response = model_with_tools.invoke(messages)
print(f"final_response: \n{final_response}")

说明:

被 @tool 修饰的函数可以调用 invoke 接收模型返回的入参信息执行函数,并返回ToolMessage 实例,我们不再需要手动拼接 ToolMessage 。

工具流程调用总结

如果真正要大模型根据工具调用结果进行回复,完整的调用流程包括如下四个步骤:

  • 步骤1:模型绑定工具 :通过model.bind_tools(...)绑定一个或者多个工具

  • 步骤2:模型生成工具调用请求 :用户输入问题,调用模型(比如invoke())。如果需要调用工具,模型返回包含工具调用信息(如工具名称和参数)的AIMessage

  • 步骤3:开发者手动执行工具 :用户从响应中提取工具调用信息并手动调用对应的工具(比如工具.invoke())

  • 步骤4:将工具执行结果ToolMessage传递给模型生成最终结果 :将之前用户提问内容和手动执行工具结果ToolMessage返回模型,模型最终生成回复

特别注意:大模型调用工具是单次推理,直接响应,需要开发者手动执行工具并管理循环,适合简

单、确定的任务。

3.3 工具描述的各部分参数详解

3.3.1 convert_to_openai_to

在上面的代码中,执行 model.bind_tools(get_weather) ,其底层最终会调用 convert_to_openai_tool 生成工具描述。所以可以直接调用后者查看解析后的工具描述。

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint

def get_weather(city: str):
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

输入结果如下

上面的结果 字段 说明:

  • type:定义当前数据节点必须是什么数据类型。

    • 常见类型有 string, number, integer, boolean, object, array, null。object即是json对象
  • properties:

    • 用于定义JSON 对象(Object)中可以包含哪些属性(键),以及每个属性对应的值类型和说明
  • required:

    • 当 type为 "object"时使用,是一个数组,列出了对象中必须存在的属性名

到这里有同学有疑问了,为什么不使用@tool装饰器修饰的函数,也可以理解为工具呢?

查看 convert_to_openai_tool 底层源码:

python 复制代码
elif isinstance(function, langchain_core.tools.base.BaseTool): 
    oai_function = cast("dict", _format_tool_to_openai_function(function)) 
elif callable(function): 
    oai_function = cast( 
        "dict", _convert_python_function_to_openai_function(function) 
    )

相当于加了@tool修饰的函数走上面的分支,没有加@tool修饰的函数走下面的分支,后者会基于函数定义和docstring生成pydantic模式的描述,然后转换为规范的tool_schema。

3.3.2 description 说明

convert_to_openai_tool 会从 docstring(文档字符串) 加载工具的描述信息,上面的案例中,docstring 为空,所以抽取的 description 为空。

  • docstring,文档字符串,使用三个双引号表示开始和结束
python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint

def get_weather(city: str):
    """
    天气查询工具
    """
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

再次运行,此时在输出结果的描述字段中就可以看到有信息了

3.3.3 参数说明

convert_to_openai_tool 会从 docstring 加载参数说明,这里的 docstring 必须遵循 Google 风格 。

Google 风格 docstring 说明:styleguide | Style guides for Google-originated open-source projectsGoogle 风格 docstring 示例:新建标签页

Python docstring 通用约定:PEP 257 -- Docstring Conventions | peps.python.org

基础用法不必完整阅读规范,只需要按照下面的示例仿写即可。

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint

def get_weather(city: str):
    """
    天气查询工具

    Args:
        city: 城市名称
    """
    return f"{city}天气晴朗"


rprint(convert_to_openai_tool(get_weather))

在输出结果中可以看到,参数的详细信息都输出了

使用 Args: 、 Returns: 、 Raises: 等关键字,这种方式可读性强。Agent通过工具的这些注释来理解工具的用途和调用时机,因此清晰、准确的文档字符串是工具能被正确调用的前提。AI 依赖 docstring 来理解工具,下面就是一个好的示例:

python 复制代码
@tool
def search_products(query: str) -> str:
    """
        在产品数据库中搜索产品

    Args:
        query: 搜索关键词,如"笔记本电脑"、"手机"

    Returns:
        产品列表的 JSON 字符串
    """
...

3.3.4 参数类型说明

参数类型来源于函数的类型注解,比如下面的代码

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint

def get_weather(city):
    """
    天气查询工具
    """
    return f"{city}天气晴朗"


rprint(convert_to_openai_tool(get_weather))

删除了参数类型注解,则工具描述中也不包含参数类型说明

注意:如果docstring中包含参数说明,则对应的参数必须有类型注解,否则报错,比如下面的代码

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint

def get_weather(city):
    """
    天气查询工具

    Args:
        city: 城市名称
    """
    return "天气晴朗"

rprint(convert_to_openai_tool(get_weather))

运行直接报下面的错误

3.3.5 参数默认值

如果参数没有默认值,则会包含 在required对应的列表 中。

反之,则参数的描述信息会包含 default 字段,并且 不会出现在required列表 中。

下面的代码中,给入参增加一个默认值

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint

def get_weather(city : str = "北京"):
    """
    天气查询工具

    Args:
        city: 城市名称
    """
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

在输出的结果中在default 这里就能看到这个参数默认值了

3.4 使用@tool装饰器定义工具

使用 @tool 装饰器修饰,可以自动将普通 Python 函数转化为智能体可调用的工具。

此方式 最直接 ,代码量极少,非常适合快速验证想法或创建参数简单的工具。

3.4.1 自定义工具描述:description

在下面的代码中,方法使用@tool 修饰,但是没有描述(docstring)信息,运行的时候将会报错

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool

@tool
def get_weather(city):
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

添加上去之后就正常了

小结:

  • 在bind_tools()调用时,先将函数封装为 BaseTool 类型的对象,再传递给 convert_to_openai_tool 函数,生成工具的描述。

  • @tool 会从 docstring 生成描述信息,同样要求遵循 Google docstring 规范 。如果没有 docstring 则报错,如下。

Traceback...

ValueError: Function must have a docstring if description not provided.

3.4.2 添加工具描述:description

@tool 的参数 description 可以更改工具描述,优先级高于 docstring 的函数说明

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool

@tool(description="获取具体城市的天气信息")
def get_weather(city):
    """
    获取城市天气信息

    Args:
        city: 城市名称
    """
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

运行一下可以看到@tool后面的描述信息被输出了

解析docstring:parse_docstring

当我们没有向 @tool 传递 description 参数时,默认情况下, tool 会将 docstring 整体视为 description ,

  • 通过将 parse_docstring 设置为True,docstring会被解析,填充到相应的字段描述中

  • 不使用 @tool 装饰器时,docstring不合法会被视为普通文本,作为 description ,但如果使用了 @tool 时 docstring 不合法,将会抛出异常

python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool

@tool(description="获取具体城市的天气信息",parse_docstring=True)
def get_weather(city):
    """
    获取城市天气信息
    Args:
        city: 城市名称
    """
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

下面这样被认定为不合法的话会抛出异常

3.4.3 更改工具名称

默认情况,使用函数名作为工具名称,但可以向@tool 传参 name_or_callable ,以更改工具名称,如下代码,通过name_or_callable 这个参数重新赋值给工具名称

  • **注意:**虽然有这种方式能对工具重命名,但在开发中,习惯使用函数名作为工具名称,不推荐自定义工具名称
python 复制代码
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool

@tool(description="获取具体城市的天气信息",name_or_callable="weather_tool")
def get_weather(city):
    """
    获取城市天气信息

    Args:
        city: 城市名称
    """
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

此时工具的名称已经变了

3.5 自定义args_schema

当工具的参数变得复杂,需要 枚举值 、 范围限制 或 更复杂的业务逻辑验证 时,Pydantic 模型是理想的选择,提供强大的类型检查和数据验证。

使用Pydantic 的主要优势在于能够精确控制工具参数的格式和验证规则,让大模型更准确地理解如何调用工具。

3.5.1 pydantic类型的定义

通过继承核心基类 BaseModel 定义数据模型,从而声明字段结构、类型约束、默认值以0及校验规则。

python 复制代码
from pydantic import BaseModel
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool

class WeatherInput(BaseModel):
    city: str

@tool(args_schema=WeatherInput)
def get_weather(city):
    """
    获取城市天气信息
    """
    return f"{city}天气晴朗"

rprint(convert_to_openai_tool(get_weather))

WeatherInput 对象中还可以补充更多的信息,如下,增加描述和默认值

python 复制代码
class WeatherInput(BaseModel):
    city: str = Field(
        description="具体的城市",
        default="北京"
    )

3.5.2 限定参数取值范围

某些情况下为了安全起见,对参数的取值范围做限定,可以使用 Literal类型限定参数为固定选项

  • Literal :表示字段不能是任意某种类型的值,而只能是几个固定字面量之一
python 复制代码
class WeatherInput(BaseModel):
    city: str = Field(
        description="具体的城市",
        default="北京"
    ),
    unit: Literal["celsius", "fahrenheit"]

3.6 使用 Json Schema定义

在 LangChain 中,还可以直接使用 JSON Schema 字典 来定义工具的参数模式。这种方式提供了极大的灵活性。因为工具参数模式可以基于数据库配置或用户输入在 运行时动态生成 ,所以这种方式特别适合参数结构需要动态生成的场景。

如下完整的代码

python 复制代码
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint

json_schema = {
    'parameters': {
        'properties': {
            'city': {'type': 'string'},
            'unit': {'enum': ['celsius', 'fahrenheit'], 'type': 'string'}
        },
        'required': ['unit'],
        'type': 'object'
    }
}

@tool(args_schema=json_schema)
def get_weather(city):
    """
    获取城市天气信息
    """
    return f"{city}天气晴朗"


rprint(convert_to_openai_tool(get_weather))

运行一下,效果与上面的输出效果一致

四、写在最后

本文详细介绍了Langchain 中工具的调用,并通过 案例的方式详细演示了内部的参数使用,希望对看到的同学有用,本篇到此结束,感谢观看。

相关推荐
染指111015 小时前
141.Agent-多Agent框架-编写并使用Skills
人工智能·语言模型·langchain·skill·agents
小盆女神节奶粉19 小时前
LangChain 中间件
langchain·agent
浪子明X21 小时前
LangChain 接入 Embedding 之前:先画清文本、向量与检索器的契约
人工智能·langchain·embedding
大连好光景1 天前
LangChain是干啥的?有哪些核心组件?
langchain·ai应用
不会就选b1 天前
Langchain基础(二)
langchain
JWASX1 天前
【agent 开发】agent 开发学习 - LangChain(3)
学习·langchain
范中勤1 天前
LLM 动态加载与多用户缓存架构技术文档
redis·langchain·llm·缓存架构·多用户隔离
一木 之林1 天前
Dify学习笔记 00 · 总览:56集四模块学习地图(从低代码平台到检索底座)
人工智能·计算机视觉·langchain
JWASX1 天前
【agent 开发】agent 开发学习 - LangChain(2)
python·学习·langchain