在这里插入图片描述

前言

在构建强大 AI 应用的征程中,仅仅依赖大语言模型(LLM)的文本生成能力,如同纸上谈兵。真正的突破,在于让模型能够与真实世界互动,从“认识世界”迈向“改变世界”。工具(Tools) 正是为模型插上这双翅膀的关键,它将模型的智慧延伸至无限可能。

从本篇博文开始的两篇博文,我们将浅层的讲解 Tools 的定义、构建方法以及使用策略,提供一些基本的示例,详细内容将在下一个篇章 —— Agent,即真正应用工具实现场景互动的应用范式中详细提供,请期待。


什么是 Tools?为什么它如此重要?

简单来说,Tools 是让 LLM 能够调用外部世界能力的接口。它将 LLM 从一个封闭的“文本大脑”变成了一个可以实际操作的“行动派”。无论是执行一次网络搜索、查看当前时间戳、进行一次数学计算、查询数据库,还是运行一段代码解释器,都离不开 Tools 的支持。

核心特点

  • 功能增强: 让 LLM 突破纯文本的局限,执行搜索、计算、API 调用等实际操作。
  • 智能决策: 在 Agent 工作流中,LLM 能根据用户意图,动态地选择最合适的工具来完成任务。
  • 模块化设计: 每个 Tool 专注一个功能,易于复用和组合,例如:搜索工具 + 计算工具 + 天气查询工具,可以协同完成复杂任务。

剖析 Tool 的五大核心要素

要让 LLM 精准地理解并使用我们创建的工具,就必须按照约定来定义它。一个标准的 Tool 通常包含以下五个核心要素:

要素类型功能说明
namestring工具的唯一标识符。 一个简洁、明确的名称是 LLM 识别工具的第一步
descriptionstring工具的灵魂所在。 这是你写给 LLM 的“使用说明书”,必须清晰、准确地描述工具的功能、使用场景以及它能解决什么样的问题。LLM 完全依赖此描述来判断何时调用该工具
orgs_schemaPydantic BaseModel工具的输入参数规范。 它定义了工具需要哪些输入,以及每个输入的类型和格式。这能确保 LLM 以正确的格式提供参数
funccallable工具要执行的函数。 这是工具背后真正的逻辑,是实际执行任务的代码
return_directboolean是否将结果直接返回给最终用户。 如果为 True,则工具的执行结果将作为最终答案,工作流结束。如果为 False(默认),结果将返回给 LLM,由 LLM 决定下一步动作

工作流程简述

  1. 提供上下文: 我们将定义好的工具列表(包含 name, description, args_schema)提供给 LLM。
  2. LLM 推理决策: LLM 根据用户的指令和它所理解的工具描述,判断是否需要调用工具。如果需要,它会生成一个包含工具名称 (name) 和具体调用参数 (args) 的指令。
  3. 应用执行回调: 我们的应用程序接收到 LLM 的指令后,调用相应的 func 并传入参数,执行工具,然后将结果返回给 LLM 或用户。

自定义工具的两种实战方式

LangChain 为我们提供了两种便捷的方式来创建自定义工具:

  1. @tool 装饰器
  2. StructuredTool.from_function 类方法

方式一:@tool 装饰器(快速、便捷)

这是最简单、最 Pythonic 的方式。它能将任何一个 Python 函数快速封装成一个 Tool。

  • 高价值说明:
    默认情况下,@tool 会自动使用函数名作为工具的 name,并抽取 函数的文档功能注释 作为工具的 description。因此,编写清晰的函数文档描述至关重要!

1. 基础定义

我们定义一个简单的加法工具,并查看其自动生成的属性。

from langchain_core.tools import tool

@tool
def add_number(a: int, b: int) -> int:
    """计算并返回两个整数的和。"""
    return a + b

# 查看工具属性
print(f"名称 (name) = {add_number.name}")
print(f"描述 (description) = {add_number.description}")
print(f"参数 (args) = {add_number.args}")

输出结果:

名称 (name) = add_number
描述 (description) = 计算并返回两个整数的和。
参数 (args) = {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}

2. 自定义参数描述

默认的参数描述(如 {'title': 'A'})过于简单,LLM 可能无法理解其含义。我们可以通过 Pydantic 的 BaseModelField 来提供更丰富的参数描述,帮助 LLM 更好地理解如何传递参数。

  • 重点功能:使用 args_schema 精准控制输入
    通过 pydantic.Fielddescription 字段,我们可以为每个参数添加详细的说明,这是提升 LLM 调用准确率的关键技巧。
from langchain_core.tools import tool
from pydantic import BaseModel, Field

# 1. 定义一个符合 args_schema 规范的类
class CalculatorInput(BaseModel):
    a: int = Field(description="进行加法运算的第一个整数")
    b: int = Field(description="进行加法运算的第二个整数")

# 2. 将其传递给 @tool 装饰器
@tool(args_schema=CalculatorInput)
def add_number(a: int, b: int) -> int:
    """计算并返回两个整数的和。"""
    return a + b

# 查看现在更丰富的参数定义
print(f"名称 (name) = {add_number.name}")
print(f"描述 (description) = {add_number.description}")
print(f"参数 (args) = {add_number.args}")

输出结果,可以看到 description 已经被成功注入:

名称 (name) = add_number
描述 (description) = 计算并返回两个整数的和。
参数 (args) = {'a': {'title': 'A', 'description': '进行加法运算的第一个整数', 'type': 'integer'}, 'b': {'title': 'B', 'description': '进行加法运算的第二个整数', 'type': 'integer'}}

方式二:StructuredTool.from_function() (灵活、可配置)

当你的函数已经定义好,或者你需要更灵活地配置工具属性时,StructuredTool.from_function() 是一个绝佳的选择。

  • 高价值说明:
    这种方式将函数定义工具封装彻底分离,代码结构更清晰,特别适合于将项目中已有的函数库封装为 AI 工具的场景。它提供了比装饰器更多的配置选项。

1. 基础封装

将一个已有的 search_google 函数封装成 Tool。

from langchain_core.tools import StructuredTool

# 假设这是一个已存在的函数
def search_google(query: str) -> str:
    """一个模拟的谷歌搜索函数。"""
    print(f"正在搜索: {query}")
    return f"关于“{query}”的搜索结果..."

# 使用 from_function 方法进行封装
google_search_tool = StructuredTool.from_function(
    func=search_google,
    name="GoogleSearch",
    description="当需要回答关于实时信息、事件或需要网络查询才能知道的问题时,使用此工具。"
)

# 查看工具属性
print(f"名称 (name) = {google_search_tool.name}")
print(f"描述 (description) = {google_search_tool.description}")

# 调用工具
google_search_tool.invoke({"query": "AI领域的最新进展"})

重点: 在这里,namedescription 是在封装时明确指定的,而不是从函数本身推断。这给了我们极大的灵活性。

2. 配置参数与返回方式

@tool 类似,我们也可以使用 args_schema 来美化参数,并可以设置 return_direct=True

from pydantic import BaseModel, Field

class SearchInput(BaseModel):
    query: str = Field(description="需要谷歌搜索的关键词或问题")

google_search_tool_pro = StructuredTool.from_function(
    func=search_google,
    name="DirectGoogleSearch",
    description="用于网络搜索,并将结果直接返回给用户。",
    args_schema=SearchInput,
    return_direct=True  # 结果直接输出,不再返回给LLM
)

print(f"名称 (name) = {google_search_tool_pro.name}")
print(f"参数 (args) = {google_search_tool_pro.args}")
print(f"直接返回 (return_direct) = {google_search_tool_pro.return_direct}")

总结 & 如何选择?

  • 使用 @tool 装饰器: 当你正在编写新函数,并希望快速将其转换为工具时。它代码紧凑,非常适合快速原型开发和简单场景。
  • 使用 StructuredTool.from_function() 当你需要将一个已存在的函数封装成工具,或者需要更精细、更明确地控制工具的各项属性(如 name, description)时。它提供了更好的代码分离和配置灵活性。

无论选择哪种方式,请始终记住:一个高质量的 description 是让 LLM 能否在正确时机、以正确方式调用你工具的决定性因素。 在描述上投入的时间,将会在 AI 应用的性能上得到百倍的回报。


2025.10.13 金融街

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐