一、先明确:LangChain 中 “工具” 的核心作用

在 LangChain 里,“工具” 是大模型能主动调用的外部功能模块(比如计算、查询、数据处理等)。

@tool装饰器的作用是:把普通 Python 函数,转换成 LangChain 标准的 “工具对象”—— 自动生成工具的名称、描述、参数约束(Schema),让大模型能理解 “这个工具是做什么的、需要传什么参数”。

二、三种工具定义方式

LangChain 支持 3 种@tool的使用方式,对应代码里的 “方式一、方式二、方式三”,各有适用场景:

方式 1:基础用法(@tool + 普通函数 + docstring

@tool
def add(a: int, b: int) -> int:
    """两数相加

    Args:
        a: 第一个整数
        b: 第二个整数
    """
    return a + b
核心逻辑:
  • @tool装饰器会自动读取函数的3 类信息(对应 “函数名、字符串文档和类型提示”都需要定义”):
    1. 函数名add:作为工具的名称(add.name会返回 "add");
    2. docstring 第一行 “两数相加”:作为工具的描述(告诉大模型 “这个工具是做什么的”);
    3. 参数类型提示(a: int)+ docstring 里的Args:作为工具的参数约束(告诉大模型 “需要传什么类型的参数、参数是什么意思”)。
  • 适用场景:简单工具,参数少、逻辑简单。

方式 2:进阶用法(@tool + Pydantic BaseModel

class AddInput(BaseModel):
    """两数相加"""
    a: int = Field(..., description="第一个整数")
    b: int = Field(..., description="第二个整数")

@tool(args_schema=AddInput)
def add(a: int, b: int) -> int:
    return a + b
核心逻辑:
  • Pydantic的BaseModel(这里是AddInput)定义工具的参数 Schema,替代方式 1 里的 docstring + 类型提示;
  • Field(..., description="xxx")用来定义参数的 “必填性”(...表示必填)和描述;
  • @tool(args_schema=AddInput)指定工具的参数 Schema 由这个 BaseModel 生成。
  • 适用场景:复杂工具(参数多、需要参数校验(比如Field(gt=0)限制正数)、默认值等)。

方式 3:简洁用法(@tool + Annotated类型提示

@tool
def add(
        a : Annotated[int, ..., "第一个整数"],
        b : Annotated[int, ..., "第二个整数"],
) -> int:
    """两数相加

    Args:
        a: 第一个整数
        b: 第二个整数
    """
    return a + b
核心逻辑:
  • Annotated是 Python 3.9 + 支持的类型提示(需导入from typing_extensions import Annotated),格式是Annotated[参数类型, 元数据1, 元数据2...]
  • 这里Annotated[int, ..., "第一个整数"]中:
    • int是参数类型;
    • ...表示参数必填;
    • "第一个整数"是参数的描述;
  • @tool会自动读取Annotated里的 “类型 + 描述” 作为参数约束,替代方式 1 里的Args部分(更简洁)。
  • 适用场景:希望代码更紧凑,同时保留参数描述的场景(注意:你的写法里...是冗余的,标准写法是Annotated[int, "第一个整数"])。

三、你当前代码的输出解释

运行代码后,4 个print的输出及意义:

  1. add.invoke({"a": 2, "b": 3})→ 输出5:调用工具,传入参数字典,执行add函数的逻辑(两数相加)。
  2. add.name→ 输出"add":工具的名称(默认是函数名)。
  3. add.description→ 输出"两数相加\n\nArgs:\n a: 第一个整数\n b: 第二个整数":工具的描述(来自函数的 docstring),大模型会读这段文字判断是否调用这个工具。
  4. add.args→ 输出类似{"a": {"type": "integer", "description": "第一个整数"}, "b": {"type": "integer", "description": "第二个整数"}}:工具的参数 Schema(对应 “JSON Schema”),大模型会根据这个 Schema 生成正确的参数格式。

四、为什么需要 “函数名、文档、类型提示”?

原因: “这些信息都是传递给工具 schema 的”,核心是:大模型本身不会直接 “理解 Python 函数”,但它能读懂JSON Schema(一种描述数据格式的标准)。

@tool装饰器的作用,就是把 “函数名、docstring(字符串文档)、类型提示” 自动转换成大模型能理解的 JSON Schema—— 告诉大模型:

  • 这个工具叫什么(name);
  • 这个工具能做什么(description);
  • 调用这个工具需要传什么参数、参数是什么类型(args 对应的 JSON Schema)。

总结

这 3 种方式的核心目标都是生成 LangChain 标准的工具对象,区别是参数约束的定义方式:

  • 方式 1:简单直接,依赖 docstring;
  • 方式 2:灵活强大,依赖 Pydantic BaseModel;
  • 方式 3:简洁紧凑,依赖 Annotated 类型提示。

完整代码:

from langchain_core.tools import tool
from pydantic import BaseModel, Field
from typing_extensions import Annotated


# 定义工具
# 方式一:
# @tool
# def add(a: int, b: int) -> int:
#     """两数相加
#
#     Args:
#         a: 第一个整数
#         b: 第二个整数
#     """
#     return a + b

# 方式二
# class AddInput(BaseModel):
#     """两数相加"""
#
#     a: int = Field(..., description="第一个整数")
#     b: int = Field(..., description="第二个整数")
#
#
# @tool(args_schema=AddInput)
# def add(a: int, b: int) -> int:
#     return a + b

@tool
def add(
        a : Annotated[int, ..., "第一个整数"],
        b : Annotated[int, ..., "第二个整数"],
) -> int:
    """两数相加

    Args:
        a: 第一个整数
        b: 第二个整数
    """
    return a + b



print(add.invoke({"a": 2, "b": 3}))
print(add.name)
print(add.description)
print(add.args)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐