Agent 课程文档

构建使用代码的 Agent

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

Ask a Question Open In Colab

构建使用代码的 Agent

代码 Agent 是 smolagents 中的默认 Agent 类型。它们生成 Python 工具调用来执行操作,从而实现高效、富有表现力且准确的动作表示。

它们简化的方法减少了所需操作的数量,简化了复杂的操作,并实现了现有代码函数的重用。smolagents 提供了一个轻量级框架,用于构建代码 Agent,大约用 1,000 行代码实现。

代码 vs JSON 动作 图表来自论文 可执行代码动作引发更好的 LLM Agent

如果您想了解更多关于代码 Agent 为何有效的信息,请查看 smolagents 文档中的本指南

为什么选择代码 Agent?

在多步骤 Agent 过程中,LLM 编写并执行操作,通常涉及外部工具调用。传统方法使用 JSON 格式将工具名称和参数指定为字符串,系统必须解析这些字符串以确定要执行哪个工具

然而,研究表明,工具调用 LLM 使用代码直接操作时效果更佳。这是 smolagents 的核心原则,如上方图表所示,该图表来自 可执行代码动作引发更好的 LLM Agent

用代码而不是 JSON 编写动作具有以下几个关键优势:

  • 可组合性:轻松组合和重用动作
  • 对象管理:直接处理图像等复杂结构
  • 通用性:表达任何计算上可能的任务
  • 对于 LLM 来说很自然:高质量代码已存在于 LLM 训练数据中

代码 Agent 如何工作?

From https://huggingface.co/docs/smolagents/conceptual_guides/react

上面的图表说明了 CodeAgent.run() 如何运行,它遵循我们在单元 1 中提到的 ReAct 框架。smolagents 中 Agent 的主要抽象是 MultiStepAgent,它是核心构建块。CodeAgent 是一种特殊的 MultiStepAgent,我们将在下面的示例中看到。

CodeAgent 通过一系列步骤循环执行动作,现有变量和知识被整合到 Agent 的上下文中,并保存在执行日志中

  1. 系统提示存储在 SystemPromptStep 中,用户查询记录在 TaskStep 中。

  2. 然后,执行以下 while 循环

    2.1 方法 agent.write_memory_to_messages() 将 Agent 的日志写入 LLM 可读的 聊天消息列表。

    2.2 这些消息被发送到 Model,后者生成一个补全。

    2.3 解析补全以提取动作,在我们的例子中,由于我们正在使用 CodeAgent,因此应为代码片段。

    2.4 执行动作。

    2.5 结果记录到 ActionStep 的内存中。

在每个步骤结束时,如果 Agent 包含任何函数调用(在 agent.step_callback 中),则会执行它们。

让我们看一些例子

您可以按照此 notebook 中的代码进行操作,您可以使用 Google Colab 运行它。

阿尔弗雷德计划在韦恩家族的豪宅举办派对,需要您的帮助以确保一切顺利进行。为了帮助他,我们将应用我们所学的关于多步骤 CodeAgent 如何运作的知识。

Alfred Party

如果您尚未安装 smolagents,可以通过运行以下命令进行安装

pip install smolagents -U

让我们也登录 Hugging Face Hub 以访问 Serverless Inference API。

from huggingface_hub import login

login()

使用 smolagents 为派对选择播放列表

音乐是成功派对的重要组成部分!阿尔弗雷德需要一些帮助来选择播放列表。幸运的是,smolagents 为我们提供了解决方案!我们可以构建一个能够使用 DuckDuckGo 搜索网络的 Agent。为了让 Agent 访问此工具,我们在创建 Agent 时将其包含在工具列表中。

Alfred Playlist

对于模型,我们将依赖 HfApiModel,它提供对 Hugging Face 的 Serverless Inference API 的访问。默认模型是 "Qwen/Qwen2.5-Coder-32B-Instruct",它性能良好且可用于快速推理,但您可以从 Hub 中选择任何兼容的模型。

运行 Agent 非常简单

from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModel

agent = CodeAgent(tools=[DuckDuckGoSearchTool()], model=HfApiModel())

agent.run("Search for the best music recommendations for a party at the Wayne's mansion.")

当您运行此示例时,输出将显示正在执行的工作流程步骤的跟踪。它还将打印相应的 Python 代码以及消息

 ─ Executing parsed code: ──────────────────────────────────────────────────────────────────────────────────────── 
  results = web_search(query="best music for a Batman party")                                                      
  print(results)                                                                                                   
 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────── 

经过几个步骤后,您将看到生成的播放列表,阿尔弗雷德可以将其用于派对!🎵

使用自定义工具准备菜单

Alfred Menu

现在我们已经选择了播放列表,我们需要为客人安排菜单。同样,阿尔弗雷德可以利用 smolagents 来做到这一点。在这里,我们使用 @tool 装饰器来定义一个充当工具的自定义函数。我们稍后将更详细地介绍工具创建,因此现在,我们可以简单地运行代码。

正如您在下面的示例中看到的,我们将使用 @tool 装饰器创建一个工具,并将其包含在 tools 列表中。

from smolagents import CodeAgent, tool, HfApiModel

# Tool to suggest a menu based on the occasion
@tool
def suggest_menu(occasion: str) -> str:
    """
    Suggests a menu based on the occasion.
    Args:
        occasion (str): The type of occasion for the party. Allowed values are:
                        - "casual": Menu for casual party.
                        - "formal": Menu for formal party.
                        - "superhero": Menu for superhero party.
                        - "custom": Custom menu.
    """
    if occasion == "casual":
        return "Pizza, snacks, and drinks."
    elif occasion == "formal":
        return "3-course dinner with wine and dessert."
    elif occasion == "superhero":
        return "Buffet with high-energy and healthy food."
    else:
        return "Custom menu for the butler."

# Alfred, the butler, preparing the menu for the party
agent = CodeAgent(tools=[suggest_menu], model=HfApiModel())

# Preparing the menu for the party
agent.run("Prepare a formal menu for the party.")

Agent 将运行几个步骤,直到找到答案。在文档字符串中精确允许的值有助于引导 Agent 找到存在的 occasion 参数值并限制幻觉。

菜单准备好了!🥗

在 Agent 内部使用 Python 导入

我们已经准备好了播放列表和菜单,但我们需要检查另一个关键细节:准备时间!

阿尔弗雷德需要计算如果他现在开始准备,一切何时就绪,以防他们需要其他超级英雄的帮助。

smolagents 专注于编写和执行 Python 代码片段的 Agent,为安全提供沙盒执行环境。
代码执行具有严格的安全措施 - 默认情况下,预定义安全列表之外的导入被阻止。但是,您可以通过在 additional_authorized_imports 中将它们作为字符串传递来授权其他导入。有关安全代码执行的更多详细信息,请参阅官方指南

在创建 Agent 时,我们将使用 additional_authorized_imports 以允许导入 datetime 模块。

from smolagents import CodeAgent, HfApiModel
import numpy as np
import time
import datetime

agent = CodeAgent(tools=[], model=HfApiModel(), additional_authorized_imports=['datetime'])

agent.run(
    """
    Alfred needs to prepare for the party. Here are the tasks:
    1. Prepare the drinks - 30 minutes
    2. Decorate the mansion - 60 minutes
    3. Set up the menu - 45 minutes
    4. Prepare the music and playlist - 45 minutes

    If we start right now, at what time will the party be ready?
    """
)

这些示例仅仅是您可以使用代码 Agent 完成的工作的开始,我们已经开始看到它们在准备派对方面的实用性。您可以在 smolagents 文档中了解更多关于如何构建代码 Agent 的信息。

总而言之,smolagents 专注于编写和执行 Python 代码片段的 Agent,为安全提供沙盒执行环境。它支持本地和基于 API 的语言模型,使其能够适应各种开发环境。

将我们的自定义派对筹备 Agent 分享到 Hub

与社区分享我们自己的阿尔弗雷德 Agent 岂不是很棒吗?通过这样做,任何人都可以轻松地从 Hub 下载并使用该 Agent,将哥谭的终极派对策划者带到他们的指尖!让我们实现它!🎉

smolagents 库通过允许您与社区分享完整的 Agent 并下载其他 Agent 以供立即使用,从而使这成为可能。它就像以下代码一样简单

# Change to your username and repo name
agent.push_to_hub('sergiopaniego/AlfredAgent')

要再次下载 Agent,请使用以下代码

# Change to your username and repo name
alfred_agent = agent.from_hub('sergiopaniego/AlfredAgent', trust_remote_code=True)

alfred_agent.run("Give me the best playlist for a party at Wayne's mansion. The party idea is a 'villain masquerade' theme")  

同样令人兴奋的是,共享的 Agent 可以直接作为 Hugging Face Spaces 使用,允许您与它们进行实时交互。您可以在此处探索其他 Agent。

例如,AlfredAgent此处可用。您可以直接在下方试用它

您可能想知道——阿尔弗雷德是如何使用 smolagents 构建这样一个 Agent 的?通过集成多个工具,他可以按如下方式生成 Agent。现在不用担心工具,因为我们将在本单元后面的专门章节中详细探讨。

from smolagents import CodeAgent, DuckDuckGoSearchTool, FinalAnswerTool, HfApiModel, Tool, tool, VisitWebpageTool

@tool
def suggest_menu(occasion: str) -> str:
    """
    Suggests a menu based on the occasion.
    Args:
        occasion: The type of occasion for the party.
    """
    if occasion == "casual":
        return "Pizza, snacks, and drinks."
    elif occasion == "formal":
        return "3-course dinner with wine and dessert."
    elif occasion == "superhero":
        return "Buffet with high-energy and healthy food."
    else:
        return "Custom menu for the butler."

@tool
def catering_service_tool(query: str) -> str:
    """
    This tool returns the highest-rated catering service in Gotham City.
    
    Args:
        query: A search term for finding catering services.
    """
    # Example list of catering services and their ratings
    services = {
        "Gotham Catering Co.": 4.9,
        "Wayne Manor Catering": 4.8,
        "Gotham City Events": 4.7,
    }
    
    # Find the highest rated catering service (simulating search query filtering)
    best_service = max(services, key=services.get)
    
    return best_service

class SuperheroPartyThemeTool(Tool):
    name = "superhero_party_theme_generator"
    description = """
    This tool suggests creative superhero-themed party ideas based on a category.
    It returns a unique party theme idea."""
    
    inputs = {
        "category": {
            "type": "string",
            "description": "The type of superhero party (e.g., 'classic heroes', 'villain masquerade', 'futuristic Gotham').",
        }
    }
    
    output_type = "string"

    def forward(self, category: str):
        themes = {
            "classic heroes": "Justice League Gala: Guests come dressed as their favorite DC heroes with themed cocktails like 'The Kryptonite Punch'.",
            "villain masquerade": "Gotham Rogues' Ball: A mysterious masquerade where guests dress as classic Batman villains.",
            "futuristic Gotham": "Neo-Gotham Night: A cyberpunk-style party inspired by Batman Beyond, with neon decorations and futuristic gadgets."
        }
        
        return themes.get(category.lower(), "Themed party idea not found. Try 'classic heroes', 'villain masquerade', or 'futuristic Gotham'.")


# Alfred, the butler, preparing the menu for the party
agent = CodeAgent(
    tools=[
        DuckDuckGoSearchTool(), 
        VisitWebpageTool(),
        suggest_menu,
        catering_service_tool,
        SuperheroPartyThemeTool()
    ], 
    model=HfApiModel(),
    max_steps=10,
    verbosity_level=2
)

agent.run("Give me the best playlist for a party at the Wayne's mansion. The party idea is a 'villain masquerade' theme")

正如您所见,我们创建了一个 CodeAgent,其中包含多个工具,这些工具增强了 Agent 的功能,使其成为可以与社区分享的终极派对策划者!🎉

现在,轮到您了:构建您自己的 Agent,并使用我们刚刚学到的知识与社区分享!🕵️‍♂️💡

如果您想分享您的 Agent 项目,请创建一个 space 并在 Hugging Face Hub 上标记 agents-course。我们很乐意看到您创造了什么!

使用 OpenTelemetry 和 Langfuse 检查我们的派对筹备 Agent 📡

随着阿尔弗雷德微调派对筹备 Agent,他越来越厌倦调试它的运行。Agent 本质上是不可预测的,并且难以检查。但由于他的目标是构建终极派对筹备 Agent 并将其部署到生产环境中,因此他需要强大的可追溯性,以便将来进行监控和分析。

smolagents 再次出手相救!它采用了 OpenTelemetry 标准来检测 Agent 运行,从而实现无缝检查和日志记录。借助 LangfuseSmolagentsInstrumentor,阿尔弗雷德可以轻松跟踪和分析他的 Agent 行为。

设置非常简单!

首先,我们需要安装必要的依赖项

pip install opentelemetry-sdk opentelemetry-exporter-otlp openinference-instrumentation-smolagents

接下来,阿尔弗雷德已经在 Langfuse 上创建了一个帐户,并准备好了他的 API 密钥。如果您尚未这样做,可以在此处注册 Langfuse Cloud,或者探索替代方案

获得 API 密钥后,需要按如下方式正确配置它们

import os
import base64

LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_AUTH=base64.b64encode(f"{LANGFUSE_PUBLIC_KEY}:{LANGFUSE_SECRET_KEY}".encode()).decode()

os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "https://cloud.langfuse.com/api/public/otel" # EU data region
# os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "https://us.cloud.langfuse.com/api/public/otel" # US data region
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {LANGFUSE_AUTH}"

最后,阿尔弗雷德准备好初始化 SmolagentsInstrumentor 并开始跟踪他的 Agent 性能。

from opentelemetry.sdk.trace import TracerProvider

from openinference.instrumentation.smolagents import SmolagentsInstrumentor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

trace_provider = TracerProvider()
trace_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter()))

SmolagentsInstrumentor().instrument(tracer_provider=trace_provider)

阿尔弗雷德现在已连接 🔌!来自 smolagents 的运行正在 Langfuse 中记录,使他可以完全了解 Agent 的行为。通过这种设置,他已准备好重新访问之前的运行并进一步改进他的派对筹备 Agent。

要了解有关跟踪您的 Agent 以及使用收集的数据来评估其性能的更多信息,请查看奖励单元 2
from smolagents import CodeAgent, HfApiModel

agent = CodeAgent(tools=[], model=HfApiModel())
alfred_agent = agent.from_hub('sergiopaniego/AlfredAgent', trust_remote_code=True)
alfred_agent.run("Give me the best playlist for a party at Wayne's mansion. The party idea is a 'villain masquerade' theme")  

阿尔弗雷德现在可以在此处访问这些日志以查看和分析它们。

实际上,执行期间发生了一个小错误。您能在日志中发现它吗?尝试跟踪 Agent 如何处理它并仍然返回有效答案。此处是错误的直接链接,如果您想验证您的答案。当然,错误已在此期间修复,更多详细信息可以在此问题中找到。

与此同时,建议的播放列表为派对准备工作营造了完美氛围。很酷,对吧?🎶


现在我们已经创建了我们的第一个代码 Agent,让我们学习如何创建工具调用 Agent,这是 smolagents 中提供的第二种 Agent 类型。

资源

< > 在 GitHub 上更新