Skip to content

Tools 工具的使用

分类:LangChain
标签:LangChain
创建时间:2026年10月25日 17:24:17

工具的概述 ​

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

工具是构建智能体的核心要素之一!

工具调用 ​

在LangChain中,工具(Tools)实际上是指明确定义了输入和输出的 可调用函数 。因此, 工具调用 (Tool Calling) 也被称为 函数调用(Function Calling) 。

具体有两种调用方式:直接调用和绑定到模型

python
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息"""
    return f"{city} 天气非常好"

print(get_weather.invoke({"city": "北京"}))
python
import os

from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool

load_dotenv(verbose=True)

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息"""
    return f"{city} 天气非常好"

# 创建模型并绑定工具
model = init_chat_model(
    model="deepseek-flash",
    api_key = os.getenv("DEEPSEEK_API_KEY"),
    base_url = os.getenv("DEEPSEEK_API_BASE"),
).bind_tools([get_weather])

res = model.invoke("北京的天气怎么样?")

if res.tool_calls:
    print(f"AI 需要调用工具,待调用列表:{res.tool_calls}")
else:
    print(f"AI 不需要调用工具,输出结果:{res.content}")

工具调用的整体流程 ​

工具定义:不使用 @tool ​

python
from langchain.chat_models import init_chat_model

def get_weather(city:str):
    return f"{city}天气很好"

model = init_chat_model(
    model="ollama:qwen3.5"
).bind_tools([get_weather])

res = model.invoke("北京天气怎么样?")
print(res)

convert_to_openai_tool ​

执行 bind_tools([get_weather]) ,底层最终会调用 convert_to_openai_tool 生成工具描述。

python
from langchain_core.utils.function_calling import convert_to_openai_tool

def get_weather(city:str):
    return f"{city}天气很好"

print(convert_to_openai_tool(get_weather))
''' 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'get_weather',
        'description': '',
        'parameters': {'properties': {'city': {'type': 'string'}}, 'required': ['city'], 'type': 'object'}
    }
}
'''

parameters 参数解析:

  1. properties:用于定义JSON 对象(Object)中可以包含哪些属性(键),以及每个属性对应的值类型和说明。
  2. required:当 type 为 "object" 时使用,是一个数组,列出了对象中必须存在的属性名。
  3. type:定义当前数据节点必须是什么数据类型。常见类型有 string, number, integer, boolean, object, array, null。object即是json对象。

面试题:为什么不使用@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。

description 说明 ​

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

一个成熟的工具必须具备 docstring(文档字符串)

python
from langchain_core.utils.function_calling import convert_to_openai_tool

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

    Args:
        dt: 日期
        city: 城市名
    """
    return f"{dt}:{city}天气很好"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'get_weather',
        'description': '查询天气的工具',
        'parameters': {
            'properties': {
                'dt': {'description': '日期', 'type': 'string'},
                'city': {'default': '北京', 'description': '城市名', 'type': 'string'}
            },
            'required': ['dt'],
            'type': 'object'
        }
    }
}
"""

使用 Args: 、 Returns: 、 Raises: 等关键字,这种方式可读性强。

工具定义:使用 @tool ​

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

自定义工具描述:description ​

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

python
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print

@tool
def get_weather(dt:str, city:str="北京"):
    return f"{dt}:{city}天气很好"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
ValueError: Function must have a docstring if description not provided.
"""

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

python
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print

@tool(description="天气查询工具")
def get_weather(dt:str, city:str="北京"):
    """工具1"""
    return f"{dt}:{city}天气很好"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'get_weather',
        'description': '天气查询工具',
        'parameters': {
            'properties': {
                'dt': {'type': 'string'},
                'city': {'default': '北京', 'type': 'string'}
            },
            'required': ['dt'],
            'type': 'object'
        }
    }
}
"""

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

python
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print

@tool
def get_weather(dt:str, city:str="北京"):
    """工具1

    Args:
        dt: 具体日期
        city: 城市
    """
    return f"{dt}:{city}天气很好"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'get_weather',
        'description': '工具1\n\nArgs:\n    dt: 具体日期\n    city: 城市',
        'parameters': {
            'properties': {
                'dt': {'type': 'string'},
                'city': {'default': '北京', 'type': 'string'}
            },
            'required': ['dt'],
            'type': 'object'
        }
    }
}
"""

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

python
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print

@tool(parse_docstring=True)
def get_weather(dt:str, city:str="北京"):
    """工具1

    Args:
        dt: 具体日期
        city: 城市
    """
    return f"{dt}:{city}天气很好"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'get_weather',
        'description': '工具1',
        'parameters': {
            'properties': {
                'dt': {'description': '具体日期', 'type': 'string'},
                'city': {
                    'default': '北京',
                    'description': '城市',
                    'type': 'string'
                }
            },
            'required': ['dt'],
            'type': 'object'
        }
    }
}
"""

更改工具名称:name_or_callable ​

默认情况,使用函数名作为工具名称,但可以向 @tool 传参 name_or_callable ,以更改工具名称。

但通常不这么写,工具名称等于函数名。

python
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print

@tool(name_or_callable='getWeatherPlus')
def get_weather(dt:str, city:str="北京"):
    """工具1"""
    return f"{dt}:{city}天气很好"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'getWeatherPlus',
        'description': '工具1',
        'parameters': {
            'properties': {
                'dt': {'type': 'string'},
                'city': {'default': '北京', 'type': 'string'}
            },
            'required': ['dt'],
            'type': 'object'
        }
    }
}
"""

自定义 args_schema ​

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

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

方式1:使用 Pydantic 模型定义 ​
  1. 创建参数说明类 weatherInput 此类必须继承 BaseModel。
  2. Field() :用来定制字段的函数,可用于设置默认值、描述等。
  3. 可以使用 Literal 类型限定参数为固定选项。
  4. 通过 @tool(args_schema=weatherInput) 将这个 Pydantic 模型与工具函数关联。
python
from typing import Literal
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from pydantic import BaseModel, Field
from rich import print

class weatherInput(BaseModel):
    city: str = Field(description="城市")
    unit: Literal["摄氏度", "华氏度"] = Field(
        description="温度单位",
        default="摄氏度"
    )

@tool(description="查询天气的工具", args_schema=weatherInput)
def get_weather(city, unit):
    return f"{city}天气很好,温度20{unit}"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'get_weather',
        'description': '查询天气的工具',
        'parameters': {
            'properties': {
                'city': {'description': '城市', 'type': 'string'},
                'unit': {
                    'default': '摄氏度',
                    'description': '温度单位',
                    'enum': ['摄氏度', '华氏度'],
                    'type': 'string'
                }
            },
            'required': ['city'],
            'type': 'object'
        }
    }
}
"""
方式2:使用 Json Schema 定义 ​
python
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print

args_json = {
    'properties': {
        'city': {'description': '城市', 'type': 'string'},
        'unit': {
            'default': '摄氏度',
            'description': '温度单位',
            'enum': ['摄氏度', '华氏度'],
            'type': 'string'
        }
    },
    'required': ['city'],
    'type': 'object'
}

@tool(description="查询天气的工具", args_schema=args_json)
def get_weather(city, unit):
    return f"{city}天气很好,温度20{unit}"

print(convert_to_openai_tool(get_weather))
""" 输出结果:
{
    'type': 'function',
    'function': {
        'name': 'get_weather',
        'description': '查询天气的工具',
        'parameters': {
            'properties': {
                'city': {'description': '城市', 'type': 'string'},
                'unit': {
                    'default': '摄氏度',
                    'description': '温度单位',
                    'enum': ['摄氏度', '华氏度'],
                    'type': 'string'
                }
            },
            'required': ['city'],
            'type': 'object'
        }
    }
}
"""

单工具调用 ​

python
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class ArgsWeather(BaseModel):
    city: str = Field(
        description="城市信息",
        default="北京"
    )
    next_day: bool = Field(
        description="是否显示次日信息",
        default=False
    )

@tool(description="天气查询工具", args_schema=ArgsWeather)
def get_weather(city, next_day):
    res = f"{city}今天天气很好"
    if next_day:
        res += "明天下雨"
    return res

model = init_chat_model(
    model="ollama:qwen3.5"
).bind_tools([get_weather])

messages = [HumanMessage("北京的天气怎么样?明天的天气怎么样")]

res = model.invoke(messages)
messages.append(res)

if res.tool_calls:
    for tool in res.tool_calls:
        if tool["name"] == "get_weather":
            messages.append(get_weather.invoke(tool))
res = model.invoke(messages)
messages.append(res)

for msg in messages:
    msg.pretty_print()
    
""" 输出结果:
================================ Human Message =================================

北京的天气怎么样?明天的天气怎么样
================================== Ai Message ==================================
Tool Calls:
  get_weather (dd4faa84-6738-40f4-82bb-3595ae7fa845)
 Call ID: dd4faa84-6738-40f4-82bb-3595ae7fa845
  Args:
    city: 北京
    next_day: True
================================= Tool Message =================================
Name: get_weather

北京今天天气很好明天下雨
================================== Ai Message ==================================

北京今天天气不错,很晴朗;不过明天会下雨,出门记得带伞哦。
"""

多工具调用 ​

python
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class ArgWeather(BaseModel):
    city: str = Field(
        description="城市",
        default="北京"
    )

@tool(description="天气查询工具", args_schema=ArgWeather)
def get_weather(city):
    return f"{city} 天气晴朗"

@tool(description="新闻查询工具", args_schema=ArgWeather)
def get_news(city):
    news = {
        "北京": ["北京新闻1", "北京新闻2", "北京新闻3"],
        "上海": ["上海新闻4", "上海新闻5", "上海新闻6"]
    }
    return news.get(city, [])

model = init_chat_model(
    model="ollama:qwen3.5"
).bind_tools([get_weather, get_news])

messages = [HumanMessage("北京今天的天气怎么样?近期有没有什么新闻?")]

while True:
    res = model.invoke(messages)
    messages.append(res)

    if not res.tool_calls:
        break

    for tool in res.tool_calls:
        if tool["name"] == "get_weather":
            messages.append(get_weather.invoke(tool))
        elif tool["name"] == "get_news":
            messages.append(get_news.invoke(tool))

for msg in messages:
    msg.pretty_print()
    
""" 输出结果:
================================ Human Message =================================

北京今天的天气怎么样?近期有没有什么新闻?
================================== Ai Message ==================================
Tool Calls:
  get_weather (36926a53-4d57-4941-bfae-27af987260ea)
 Call ID: 36926a53-4d57-4941-bfae-27af987260ea
  Args:
    city: 北京
  get_news (6e79651b-76d9-4d07-bb13-934d6eeb5a72)
 Call ID: 6e79651b-76d9-4d07-bb13-934d6eeb5a72
  Args:
    city: 北京
================================= Tool Message =================================
Name: get_weather

北京 天气晴朗
================================= Tool Message =================================
Name: get_news

['北京新闻1', '北京新闻2', '北京新闻3']
================================== Ai Message ==================================

今天的北京天气晴朗。最近有一些新闻,包括“北京新闻1”、“北京新闻2”和“北京新闻3”。如果您需要更具体的信息,可以告诉我!
"""

强制使用工具 ​

bind_tools 可以传递参数 tool_choice ,用于控制是否强制使用工具。

该字段最终会作为 payload 的 tool_choice 字段传递给模型,OpenAI和Deepseek的官方API服务对于 tool_choice 的取值做了相同的规定。

tool_choice 的取值:

  1. none:模型不会调用任何工具。
  2. auto:默认值,模型可以自主决定不调用或调用任意数量的工具。
  3. required:模型必须调用工具,数量不限。

此外, tool_choice 还支持传递 any ,等价于 required 。

当 tool_choice 值为工具名时表示使用特定工具。

python
import os

from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field
from rich import print as rprint

load_dotenv(verbose=True)

class ArgWeather(BaseModel):
    city: str = Field(
        description="城市",
        default="北京"
    )

@tool(description="天气查询工具1", args_schema=ArgWeather)
def get_weather1(city):
    return f"{city} 天气晴朗"

@tool(description="天气查询工具", args_schema=ArgWeather)
def get_weather2(city):
    return f"{city} 会下雨"

model = init_chat_model(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url=os.getenv("DEEPSEEK_API_BASE_URL"),
).bind_tools([get_weather1, get_weather2], tool_choice="get_weather2")
rprint(model.invoke("北京今天天气怎么样?"))

""" 输出结果:
AIMessage(
    ...
    tool_calls=[
        {
            'name': 'get_weather2',
            'args': {'city': '北京'},
            'id': 'call_00_KAYBpRVJwXTd5WdePiCI8078',
            'type': 'tool_call'
        }
    ],
    ...
)
"""

基于 MIT 许可发布