Skip to content

什么是工具?

📚 本节目标

AI 智能体 的关键能力在于执行行动。正如前文所述,这通过 工具 的使用实现。

本章节主要是学习工具的定义、有效设计方法,以及如何通过系统消息将其集成到智能体中。

通过为智能体配备合适的工具——并清晰描述这些工具的工作原理——可显著提升 AI 的能力边界。让我们开始探讨!

AI 工具的定义

工具 是赋予 LLM 的函数,该函数应实现明确的目标。

以下是 AI 智能体中常用的工具示例:

工具类型描述应用场景
网络搜索允许智能体从互联网获取最新信息实时新闻查询、股价查询、天气信息
图像生成根据文本描述生成图像创意设计、内容创作、可视化展示
信息检索从外部数据源检索信息文档查询、知识库检索、数据库查找
API 接口与外部 API 交互GitHub 操作、数据搜索、音乐播放
计算工具执行数学计算和数据处理复杂计算、统计分析、公式求解
文件操作处理文件的读写和管理文档编辑、数据导入导出、文件转换

💡 重要提醒

以上仅为示例,实际可为 任何用例 创建工具!

工具的价值

优秀工具应能补充 LLM 的核心能力

加载图表中...

具体示例

例如,若需执行数学运算,为 LLM 提供 计算器工具 将比依赖模型原生能力获得更好结果。

此外,LLM 基于训练数据预测提示的补全,意味着其内部知识仅包含 训练截止前的信息。因此,若智能体需要最新数据,必须通过工具获取。

⚠️ 幻觉问题

例如,若直接询问 LLM(无搜索工具)今日天气,LLM 可能会产生随机幻觉:

用户: "今天北京的天气如何?"
无工具的LLM: "今天北京晴朗,气温25°C,微风。" ❌(完全编造)

有搜索工具的智能体: "让我为您查询最新天气信息..." ✅(实时准确)

工具的构成要素

合格工具应包含:

  1. 📝 函数功能的文本描述
  2. 🔧 可调用对象(执行操作的实体)
  3. 📋 带类型声明的参数
  4. 📤 (可选)带类型声明的输出
加载图表中...

工具如何运作?

正如前文所述,LLM 只能接收文本输入并生成文本输出。它们无法自行调用工具。

当我们谈论为智能体提供工具时,本质是:

  1. 教导 LLM 认识工具的存在
  2. 要求模型在需要时生成调用工具的文本

工具调用流程

加载图表中...

详细步骤解释

例如,若我们提供从互联网获取某地天气的工具:

  1. 用户询问:"上海天气如何?"
  2. LLM 识别:该问题适合使用 "天气" 工具
  3. 生成调用LLM 生成代码形式的文本来调用该工具
  4. 智能体执行:解析 LLM 的输出,识别工具调用需求,执行工具调用
  5. 返回结果:工具的输出返回给 LLM
  6. 生成回复LLM 生成最终用户响应

💡 关键理解

工具调用的输出是对话中的另一种消息类型。工具调用步骤通常对用户不可见:智能体检索对话、调用工具、获取输出、将其作为新消息添加,并将更新后的对话再次发送给 LLM。

从用户视角看,仿佛 LLM 直接使用了工具,但实际执行的是我们的应用代码(智能体)。

如何为 LLM 提供工具?

完整答案可能看似复杂,但核心是通过 系统提示(system prompt) 向模型通过文本描述可用工具:

python
system_prompt = """
你是一个智能体, 名字是小马。
拥有以下工具:

1. compare_numbers(a: int, b: int) -> str
   描述: 比较两个数字的大小

2. get_weather(location: str) -> dict
    描述: 获取特定位置的当前天气信息

当需要使用工具时,请使用以下格式响应:
TOOL_CALL: 工具名称(参数=值)
"""

为确保有效性,必须精准描述:

  • 🎯 工具功能
  • 📝 预期输入格式

因此工具描述通常采用 结构化表达方式(如编程语言或 JSON)。虽然不是强制,但任何其他格式均可。

具体示例:计算器工具

若觉抽象,我们通过具体示例理解。

我们将实现简化的计算器工具,仅执行两整数相乘。Python 实现如下:

python
def compare_numbers(a: int, b: int) -> str:
    """比较两个数字的大小"""
    if a > b:
        return f"{a} 大于 {b}"
    elif a < b:
        return f"{a} 小于 {b}"
    else:
        return f"{a} 等于 {b}"

因此我们的工具:

  • 名称compare_numbers
  • 功能:比较两个数字的大小
  • 输入a(int):整数,b(int):整数
  • 输出str:a 与 b 的大小关系

让我们将这些信息整合成 LLM 可理解的工具描述文本:

工具名称:compare_numbers,描述:比较两个数字的大小。参数:a: int, b: int,输出:a 与 b 的大小关系

⚠️ 重要提示

此文本描述是我们希望 LLM 了解的工具体系。

当我们将上述字符串作为输入的一部分传递给 LLM 时,模型将识别其为工具,并知晓需要传递的输入参数及预期输出。

若需提供更多工具,必须保持格式一致性。此过程可能较为脆弱,容易遗漏某些细节。

那么,是否有更好的方法?

自动化工具描述生成

我们的工具采用 Python 实现,其代码已包含所需全部信息:

  • 功能描述性名称compare_numbers
  • 详细说明:通过函数文档字符串实现 比较两个数字的大小
  • 输入参数及类型:函数明确要求两个int类型参数
  • 输出类型-> str

这正是人们使用编程语言的原因:表达力强、简洁且精确

虽然可以将 Python 源代码作为工具规范提供给 LLM,但具体实现方式并不重要。关键在于工具名称、功能描述、输入参数和输出类型。

Python 自省特性

我们将利用 Python自省特性,通过源代码自动构建工具描述。只需确保工具实现满足:

  1. 使用类型注解(Type Hints)
  2. 编写文档字符串(Doc Strings)
  3. 采用合理的函数命名

完成这些之后,我们只需使用一个 Python 装饰器 来指示 compare_numbers 函数是一个工具:

python
@tool
def compare_numbers(a: int, b: int) -> str:
    """比较两个数字的大小"""
    if a > b:
        return f"{a} 大于 {b}"
    elif a < b:
        return f"{a} 小于 {b}"
    else:
        return f"{a} 等于 {b}"

print(compare_numbers.to_string())

注意函数定义前的 @tool 装饰器。

通过我们即将看到的实现,可以利用装饰器提供的 to_string() 方法从源代码自动提取以下文本:

工具名称:compare_numbers,描述:比较两个数字的大小。参数:a: int, b: int,输出:a 与 b 的大小关系

正如你所见,这与我们之前手动编写的内容 完全一致 ! 🎉 🎉

通用工具类实现

我们创建通用Tool类,可在需要时重复使用:

📝 说明

此示例实现为虚构代码,但模拟了主流工具库的实际实现方式。

python
class Tool:
    """
    一个工具的类,用于包装函数并提供工具的文本描述
    
    属性:
        name (str): 工具的名称
        description (str): 工具功能的文本描述
        func (callable): 该工具包装的函数
        arguments (list): 参数列表
        outputs (str or list): 被包装函数的返回类型
    """
    def __init__(self, 
                 name: str,           # 工具名称
                 description: str,    # 工具描述
                 func: callable,      # 可调用的函数对象
                 arguments: list,     # 参数列表
                 outputs: str):       # 输出类型
        """
        初始化工具对象
        """
        self.name = name                # 设置工具名称
        self.description = description  # 设置工具描述
        self.func = func                # 设置被包装的函数
        self.arguments = arguments      # 设置参数列表
        self.outputs = outputs          # 设置输出类型

    def to_string(self) -> str:
        """
        返回工具的字符串表示形式,
        包括工具名称、描述、参数和输出类型
        """
        # 将参数列表转换为字符串格式:参数名: 参数类型
        args_str = ", ".join([
            f"{arg_name}: {arg_type}" 
            for arg_name, arg_type 
            in self.arguments
        ])
        
        # 返回格式化的工具信息字符串
        return (
            f"工具名称: {self.name},"          # 工具名称
            f" 描述: {self.description}," # 工具描述
            f" 参数: {args_str},"          # 参数信息
            f" 输出: {self.outputs}"         # 输出类型
        )

    def __call__(self, *args, **kwargs):
        """
        使工具对象可以像函数一样被调用
        将提供的参数传递给底层函数并执行
        """
        # 调用被包装的函数并返回结果
        return self.func(*args, **kwargs)

类结构解析

虽然看似复杂,但逐步解析即可理解其工作机制。我们定义的Tool类包含以下核心要素:

属性类型说明
namestr工具名称
descriptionstr工具功能简述
functioncallable工具执行的函数
argumentslist预期输入参数列表
outputsstr/list工具预期输出
方法说明
__call__()调用工具实例时执行函数
to_string()将工具属性转换为文本描述

手动创建工具实例

可通过如下代码创建工具实例:

python
compare_numbers_tool = Tool(
    "compare_numbers",                # 工具名称
    "比较两个数字的大小",                # 工具描述
    compare_numbers,                  # 可调用的函数对象
    [("a", "int"), ("b", "int")],     # 参数列表
    "str",                            # 输出类型
)

但我们可以利用 Python 的inspect模块自动提取这些信息!这正是@tool装饰器的实现原理。

🔍 装饰器实现代码

python
import inspect
from typing import get_type_hints

def tool(func):
    """
    装饰器:将函数自动转换为 Tool 实例
    """
    # 1. 获取函数签名信息(参数名、默认值等)
    sig = inspect.signature(func)
    
    # 2. 获取函数的类型提示信息
    type_hints = get_type_hints(func)
    
    # 3. 提取参数信息
    arguments = []
    for param_name, param in sig.parameters.items():
        # 从类型提示中获取参数类型,如果没有则默认为 'any'
        param_type = type_hints.get(param_name, 'any')
        
        # 处理类型名称的显示
        if hasattr(param_type, '__name__'):
            type_str = param_type.__name__  # 如:int, str, float
        else:
            type_str = str(param_type)      # 如:List[int], Optional[str]
        
        # 添加到参数列表:(参数名, 参数类型)
        arguments.append((param_name, type_str))
    
    # 4. 提取返回类型信息
    return_type = type_hints.get('return', 'any')
    if hasattr(return_type, '__name__'):
        output_str = return_type.__name__
    else:
        output_str = str(return_type)
    
    # 5. 提取函数的文档字符串作为描述
    description = func.__doc__.strip() if func.__doc__ else "没有提供描述"
    
    # 6. 创建 Tool 实例
    tool_instance = Tool(
        name=func.__name__,         # 函数名作为工具名
        description=description,     # 文档字符串作为描述
        func=func,                  # 原始函数
        arguments=arguments,        # 自动提取的参数信息
        outputs=output_str          # 自动提取的返回类型
    )
    
    # 7. 将 Tool 的方法添加到原函数对象上
    func.to_string = tool_instance.to_string  # 添加信息展示方法
    func.__call__ = tool_instance.__call__    # 保持可调用性
    
    # 8. 返回增强后的函数
    return func

使用装饰器

在应用此装饰器后,我们可以按如下方式实现工具:

python
@tool
def compare_numbers(a: int, b: int) -> str:
    """比较两个数字的大小"""
    if a > b:
        return f"{a} 大于 {b}"
    elif a < b:
        return f"{a} 小于 {b}"
    else:
        return f"{a} 等于 {b}"

print(compare_numbers.to_string())

输出:

工具名称:compare_numbers,描述:比较两个数字的大小。参数:a: int, b: int,输出:a 与 b 的大小关系

我们可以使用 Tool 类的 to_string 方法自动生成适合 LLM 使用的工具描述文本。

该描述将被注入系统提示。以本节初始示例为例,替换 tools_description 后的系统提示如下:

python
system_prompt = f"""
你是一个智能体, 名字是小马。
拥有以下工具:

{compare_numbers.to_string()}

当需要使用工具时,请使用以下格式响应:
TOOL_CALL: 工具名称(参数=值)
"""

在 Actions 章节,我们将探讨智能体如何调用刚创建的这个工具。

模型上下文协议(MCP):统一的工具接口

模型上下文协议(MCP) 是一种开放式协议,它规范了应用程序向 LLM 提供工具的方式。

加载图表中...

MCP 生态系统

MCP 工具类型示例用途
文件系统文件读写、目录操作文档处理、数据管理
数据库连接MySQL、PostgreSQL、SQLite数据查询、统计分析
API 集成GitHub、Notion外部服务交互
开发工具Git 操作、代码执行软件开发、版本控制

🔗 了解更多

访问 Model Context Protocol 了解更多关于 MCP 的信息和可用工具。

📚 总结与下一步

工具在增强 AI 智能体能力方面至关重要。

本节要点总结

  • 🔧 工具定义: 通过提供清晰的文本描述、输入参数、输出结果及可调用函数
  • ⚡ 工具本质: 赋予LLM额外能力的函数(如执行计算或访问外部数据)
  • 🎯 工具必要性: 帮助智能体突破静态模型训练的局限,处理实时任务并执行专业操作
  • 🤖 自动化生成: 利用Python装饰器和自省特性自动生成工具描述
  • 🌐 统一标准: MCP协议提供统一的工具接口规范

关键理解

加载图表中...