为什么需要 Tool

虽然大模型具备强大的语言理解和生成能力,但它本质上是静态的、不可交互的。比如:

  • 不具备访问数据库、调用 API 的能力

  • 不能执行代码或文件操作

  • 无法实时访问互联网或动态数据等
    通过 Tool(工具)机制,可以让模型具备“调用外部函数”的能力,使其能够与外部系统、API 或自定义函数交互,从而完成仅靠文本生成无法实现的任务。例如:

  • 实时访问外部世界(如天气、股票、网页等)

  • 调用计算函数(数学、单位换算)

  • 查询数据库或搜索文档

  • 实现“多轮决策”流程(如规划任务、搜索后总结)

一个完整的Agent至少要包含两个关键的部分:

  • 模型:是Agent的大脑,负责推理、分析,规划任务步骤
  • 工具:是Agent的手脚,负责执行任务,与外界交互

在这里插入图片描述

Tool 工作原理

工具的工作流程如下:

  • 定义工具:指定工具的名称、描述和执行逻辑(函数或类)。
  • 注册工具:将工具提供给代理或链,代理根据任务描述选择工具。
  • 调用工具:代理生成工具调用的指令(包括输入参数),工具执行并返回结果。
  • 处理结果:代理或链将工具输出整合到工作流中,生成最终响应。

工具的核心依赖:

  • 工具描述:帮助代理理解工具的功能和适用场景。
  • 输入解析:确保工具能正确处理代理提供的输入。
  • 输出格式:工具返回的结果应与代理或链的期望兼容。

Tool 常用属性

属性 类型 描述
name str 必选,在提供给LLM或Agent的工具集中必须是唯一的。
description str 可选但建议,描述工具的功能。LLM或Agent将使用此描述作为上下文,使用它确定工具的使用
args_schema Pydantic BaseModel 可选但建议,可用于提供更多信息(例如,few-shot示例)或验证预期参数。
return_direct boolean 仅对Agent相关。当为True时,在调用给定工具后,Agent将停止并将结果直接返回给用户。

基本用法

我们先通过一个案例快速回顾Agent定义的步骤,以及Agent的工作原理。
定义一个带有工具的Agent分为两步:

  • 定义工具
  • 定义Agent,绑定工具

首先,使用tool装饰器定义工具:

# 1.使用tool装饰器定义工具
from langchain.tools import tool

@tool
def get_weather(location: str) -> str:
    """
    Get the weather in a given location.
    Args:
        location: city name or coordinates
    """
    return f"Current weather in {location} is sunny"

接着,定义Agent,绑定工具:

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage

# 2.创建智能体,并绑定工具
agent = create_agent(
    model="deepseek-chat",
    tools=[get_weather]
)

# 3.调用Agent
response = agent.invoke(
    {"messages": [HumanMessage(content="杭州今天天气如何?")]},
)

for message in response['messages']:
    message.pretty_print()

执行结果如下:

================================ Human Message =================================

杭州今天天气如何?
================================== Ai Message ==================================

我来帮您查询杭州今天的天气情况。
Tool Calls:
  get_weather (call_00_FETE4MIR9p1Gr6uszgjcko6m)
 Call ID: call_00_FETE4MIR9p1Gr6uszgjcko6m
  Args:
    location: 杭州
================================= Tool Message =================================
Name: get_weather

Current weather in 杭州 is sunny
================================== Ai Message ==================================

根据查询结果,杭州今天的天气是晴朗的。天气很好,适合外出活动!

流程图:
在这里插入图片描述

由此可见,所谓的工具,本质就是一个可调用的函数,要想让Agent知道有哪些工具可调用,该如何调用这些工具,就必须把这个函数的详细信息发送给模型。包括:

  • 函数名
  • 函数的作用
  • 函数的参数和返回值信息

所以,定义工具的时候,关键就是把这些信息描述清楚即可。

大模型会自动分析用户需求,判断是否需要调用指定工具。

如果模型认为需要调用工具(如 MoveFileTool ),返回的 message 会包含

  • content : 通常为空(因为模型选择调用工具,而非生成自然语言回复)。
  • additional_kwargs : 包含工具调用的详细信息:
    如果模型认为无需调用工具(例如用户输入与工具无关),返回的 message 会是普通文本回复

自定义工具

在LangChain中,定义工具的过程被大大简化,与定义普通函数几乎没什么差别,只是在一些细节上需要注意。

首先,定义工具需要在函数上添加@tool装饰器。

例如,我们定义一个计算平方根的数学工具:

# 定义工具
from langchain.tools import tool

@tool
def square_root(x: float) -> float:
    """计算指定数字的平方根"""
    return x ** 0.5

智能体在工作时,需要将函数的名称、输入、作用传递给大模型,默认情况下这些信息的来源是:

  • 工具名称:函数名
  • 工具输入:函数入参
  • 工具作用:函数的注释

当然,我们可以通过tool装饰器来覆盖上述信息:

  • 通过装饰器定义工具名称
@tool("square_root")
def tool1(x: float) -> float:
    """Calculate the square root of a number"""
    return x ** 0.5
  • 通过装饰器定义工具作用描述
@tool("square_root", description="Calculate the square root of a number")
def tool1(x: float) -> float:
    return x ** 0.5
  • 通过装饰器定义工具入参约束
    如果要覆盖工具的入参信息则会复杂很多,我们要借助于Pydantic或JSON约束。

例如,我们需要定义个查询天气的tool,借助于Pydantic来约束入参。
我们定义一个入参的模型,在模型中添加入参描述信息:

# 例如一个查询天气的tool
class WeatherInput(BaseModel):
    """查询天气的输入参数."""
    location: str = Field(description="City name or coordinates")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="Temperature unit preference"
    )
    include_forecast: bool = Field(
        default=False,
        description="Include 5-day forecast"
    )

定义工具,使用定义的模型来约束入参:

# 定义一个查询天气的tool
@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """Get current weather and optional forecast."""
    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

工具定义好之后,调用方式与普通函数类似:

# 调用数学工具
tool1.invoke({"x": 467})

# 调用查询天气工具
get_weather.invoke({"location": "杭州", "include_forecast": True})

注意: 在LangChain中,作为工具的函数有两个保留的参数名,你的自定义参数不能与之重复!他们是:

  • config:用来传递运行时配置
  • runtime:用来传递运行时上下文

当我们创建智能体时,可以把定义好的工具传递给智能体,将来模型就能得到工具信息,并根据情况判断是否需要调用工具,需要调用哪个工具了。

from langchain.agents import create_agent

# 创建智能体,并添加工具
agent = create_agent(
    model="deepseek-chat",
    tools=[tool1, get_weather],
    system_prompt="你是一个智能助手,你使用工具来解决用户问题。"
)

接下来,调用智能体,向其提问,模型会自动根据用户问题判断:

  • 是否需要调用工具?
  • 该调用哪个工具?
  • 该传递那些参数?
    并且在调用工具之后,根据工具执行结果给用户生成响应。
# 调用智能体
for token, metadata in agent.stream(
    {"messages": [HumanMessage(content="467的平方根是多少?")]},
    stream_mode="messages"
):
    print(token.content, end="", flush=True)
    

for token, metadata in agent.stream(
    {"messages": [HumanMessage(content="北京和杭州接下来几天天气如何?")]},
    stream_mode="messages"
):
    print(token.content, end="", flush=True)

如果采用stream模式的updates模式,可以看到工具调用的具体步骤:

for chunk in agent.stream(
    {"messages": [HumanMessage(content="467、529的平方根是多少?")]},
    stream_mode="updates"
):
    for step, data in chunk.items():
        print(f"step: {step}")
        print(f"content: {data['messages'][-1].content_blocks}")
        print()

输出如下:

step: model
content: [{'type': 'text', 'text': '我来帮你计算这两个数的平方根。'}, {'type': 'tool_call', 'id': 'call_00_oWChR8Xgo21mmWKW0SP9uOS9', 'name': 'square_root', 'args': {'x': 467}}, {'type': 'tool_call', 'id': 'call_01_UqzhGeRNcoSoidItA0gScaoY', 'name': 'square_root', 'args': {'x': 529}}]

step: tools
content: [{'type': 'text', 'text': '21.61018278497431'}]

step: tools
content: [{'type': 'text', 'text': '23.0'}]

step: model
content: [{'type': 'text', 'text': '计算结果如下:\n\n- **467的平方根** ≈ 21.6102\n- **529的平方根** = 23.0(因为23 × 23 = 529,所以529是完全平方数)\n\n所以:\n- √467 ≈ 21.6102\n- √529 = 23'}]

工作流程如图:
在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐