智能体课程文档

什么是工具?

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

什么是工具?

Unit 1 planning

AI Agent 的一个重要方面是它们执行行动的能力。正如我们所见,这是通过使用工具来实现的。

在本节中,我们将学习什么是工具、如何有效设计它们以及如何通过系统消息将它们集成到您的 Agent 中。

通过为您的 Agent 提供正确的工具——并清楚地描述这些工具的工作原理——您可以大大提高您的 AI 所能完成的任务。让我们深入探讨吧!

什么是 AI 工具?

工具是赋予 LLM 的一个函数。此函数应实现一个明确的目标

以下是 AI Agent 中一些常用的工具:

工具 描述
网页搜索 允许 Agent 从互联网获取最新信息。
图像生成 根据文本描述创建图像。
检索 从外部来源检索信息。
API 接口 与外部 API(GitHub、YouTube、Spotify 等)交互。

这些只是示例,因为您实际上可以为任何用例创建工具!

一个好的工具应该能够补充 LLM 的能力

例如,如果您需要执行算术运算,为您的 LLM 提供一个计算器工具将比依赖模型本身的本机能力提供更好的结果。

此外,LLM 根据其训练数据预测提示的完成,这意味着它们的内部知识只包括训练之前发生的事件。因此,如果您的 Agent 需要最新数据,您必须通过某种工具提供它。

例如,如果您直接询问 LLM(没有搜索工具)今天的天气,LLM 可能会虚构随机的天气。

Weather
  • 一个工具应该包含:

    • 函数功能的文本描述
    • 一个可调用对象(用于执行操作)。
    • 带有类型标注的参数
    • (可选)带有类型标注的输出。

工具如何工作?

LLM,正如我们所见,只能接收文本输入并生成文本输出。它们无法自行调用工具。当我们谈论向 Agent 提供工具时,我们指的是向 LLM 教授这些工具的存在,并指示它在需要时生成基于文本的调用。

例如,如果我们提供一个工具来从互联网检查某个位置的天气,然后询问 LLM 巴黎的天气,LLM 将识别出这是使用“天气”工具的机会。LLM 不会自己检索天气数据,而是生成表示工具调用的文本,例如 call weather_tool(‘Paris’)。

然后,Agent 读取此响应,识别出需要工具调用,代表 LLM 执行该工具,并检索实际的天气数据。

工具调用步骤通常不向用户显示:Agent 在将更新的对话再次传递给 LLM 之前,将其作为新消息附加。然后,LLM 处理此附加上下文并为用户生成自然听起来的响应。从用户的角度来看,似乎 LLM 直接与工具交互,但实际上,是 Agent 在后台处理了整个执行过程。

我们将在未来的课程中详细讨论这个过程。

我们如何向 LLM 提供工具?

完整的答案可能看起来令人不知所措,但我们基本上使用系统提示来向模型提供可用工具的文本描述。

System prompt for tools

为了使其正常工作,我们必须非常精确地说明:

  1. 工具的作用
  2. 它期望的精确输入

这就是为什么工具描述通常使用表达性但精确的结构(例如计算机语言或 JSON)提供的原因。这并不是必需的,任何精确且连贯的格式都可以。

如果这看起来过于理论化,让我们通过一个具体的例子来理解它。

我们将实现一个简化的计算器工具,它只将两个整数相乘。这可能是我们的 Python 实现:

def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

因此,我们的工具名为`calculator`,它将两个整数相乘,并且需要以下输入:

  • `a`int):一个整数。
  • `b`int):一个整数。

该工具的输出是另一个整数,我们可以这样描述:

  • int):`a` 和 `b` 的乘积。

所有这些细节都很重要。让我们将它们组合成一个文本字符串,用于描述我们的工具,以便 LLM 理解。

Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int

提醒:这个文本描述是我们希望 LLM 了解工具的内容

当我们把前面的字符串作为输入的一部分传递给 LLM 时,模型会把它识别为一个工具,并且知道需要传递什么作为输入以及从输出中期望什么。

如果我们想要提供额外的工具,我们必须保持一致并始终使用相同的格式。这个过程可能很脆弱,我们可能会不小心忽略一些细节。

有没有更好的方法?

工具部分自动格式化

我们的工具是用 Python 编写的,其实现已经提供了我们所需的一切:

  • 描述其功能的名称:`calculator`
  • 函数文档字符串提供的更长描述:`Multiply two integers.`
  • 输入及其类型:函数明确期望两个 `int` 类型。
  • 输出的类型。

人们使用编程语言是有原因的:它们表达力强、简洁、精确。

我们可以提供 Python 源代码作为 LLM 的工具规范,但工具的实现方式并不重要。重要的是它的名称、功能、期望的输入和提供的输出。

我们将利用 Python 的自省特性,利用源代码为我们自动构建工具描述。我们所需要做的就是工具实现使用类型提示、文档字符串和合理的函数名称。我们将编写一些代码来从源代码中提取相关部分。

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

@tool
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

print(calculator.to_string())

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

通过我们接下来将看到的实现,我们将能够通过装饰器提供的 `to_string()` 函数从源代码中自动检索到以下文本:

Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int

正如你所看到的,这和我们之前手动写的一模一样!

通用工具实现

我们创建了一个通用的 `Tool` 类,以便在需要使用工具时进行复用。

免责声明:此示例实现是虚构的,但与大多数库中的真实实现非常相似。

from typing import Callable


class Tool:
    """
    A class representing a reusable piece of code (Tool).

    Attributes:
        name (str): Name of the tool.
        description (str): A textual description of what the tool does.
        func (callable): The function this tool wraps.
        arguments (list): A list of arguments.
        outputs (str or list): The return type(s) of the wrapped function.
    """
    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:
        """
        Return a string representation of the tool,
        including its name, description, arguments, and outputs.
        """
        args_str = ", ".join([
            f"{arg_name}: {arg_type}" for arg_name, arg_type in self.arguments
        ])

        return (
            f"Tool Name: {self.name},"
            f" Description: {self.description},"
            f" Arguments: {args_str},"
            f" Outputs: {self.outputs}"
        )

    def __call__(self, *args, **kwargs):
        """
        Invoke the underlying function (callable) with provided arguments.
        """
        return self.func(*args, **kwargs)

它可能看起来很复杂,但如果我们慢慢地阅读它,我们就能明白它的作用。我们定义了一个`Tool`类,其中包括:

  • `name` (str):工具的名称。
  • `description` (str):工具功能的简要描述。
  • `function` (callable):工具执行的函数。
  • `arguments` (list):预期的输入参数。
  • `outputs` (strlist):工具的预期输出。
  • `__call__()`:当调用工具实例时执行函数。
  • `to_string()`:将工具的属性转换为文本表示。

我们可以使用如下代码创建此类的工具:

calculator_tool = Tool(
    "calculator",                   # name
    "Multiply two integers.",       # description
    calculator,                     # function to call
    [("a", "int"), ("b", "int")],   # inputs (names and types)
    "int",                          # output
)

但我们也可以使用 Python 的 `inspect` 模块为我们检索所有信息!这就是 `@tool` 装饰器所做的。

如果您感兴趣,可以展开以下部分查看装饰器实现。

装饰器代码
import inspect

def tool(func):
    """
    A decorator that creates a Tool instance from the given function.
    """
    # Get the function signature
    signature = inspect.signature(func)

    # Extract (param_name, param_annotation) pairs for inputs
    arguments = []
    for param in signature.parameters.values():
        annotation_name = (
            param.annotation.__name__
            if hasattr(param.annotation, '__name__')
            else str(param.annotation)
        )
        arguments.append((param.name, annotation_name))

    # Determine the return annotation
    return_annotation = signature.return_annotation
    if return_annotation is inspect._empty:
        outputs = "No return annotation"
    else:
        outputs = (
            return_annotation.__name__
            if hasattr(return_annotation, '__name__')
            else str(return_annotation)
        )

    # Use the function's docstring as the description (default if None)
    description = func.__doc__ or "No description provided."

    # The function name becomes the Tool name
    name = func.__name__

    # Return a new Tool instance
    return Tool(
        name=name,
        description=description,
        func=func,
        arguments=arguments,
        outputs=outputs
    )

重申一下,有了这个装饰器,我们可以这样实现我们的工具:

@tool
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

print(calculator.to_string())

我们还可以使用 `Tool` 的 `to_string` 方法自动检索适合作为 LLM 工具描述的文本:

Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int

该描述被注入到系统提示中。以我们本节开头讨论的示例为例,替换 `tools_description` 后它将是这样的:

System prompt for tools

行动部分,我们将详细了解 Agent 如何调用我们刚刚创建的这个工具。

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

模型上下文协议 (MCP) 是一个开放协议,它规范了应用程序如何向 LLM 提供工具。MCP 提供:

  • 不断增长的预构建集成列表,您的 LLM 可以直接接入
  • 在 LLM 提供商和供应商之间切换的灵活性
  • 在您的基础设施内保护数据的最佳实践

这意味着任何实现 MCP 的框架都可以利用协议中定义的工具,从而无需为每个框架重新实现相同的工具接口。

如果您想深入了解 MCP,可以查看我们的免费 MCP 课程


工具在增强 AI Agent 能力方面发挥着至关重要的作用。

总而言之,我们学习了:

  • 什么是工具:赋予 LLM 额外能力的函数,例如执行计算或访问外部数据。

  • 如何定义工具:通过提供清晰的文本描述、输入、输出和可调用函数。

  • 为什么工具必不可少:它们使 Agent 能够克服静态模型训练的局限性,处理实时任务,并执行专业操作。

现在,我们可以继续学习Agent 工作流,在那里您将看到 Agent 如何观察、思考和行动。这将我们迄今为止所涵盖的一切结合在一起,并为创建您自己的功能齐全的 AI Agent 奠定了基础。

但首先,是时候进行另一个简短的测验了!

< > 在 GitHub 上更新