开源 AI 食谱 文档

使用 smolagents 构建具有工具调用超能力的 Agent 🦸

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

Open In Colab

使用 smolagents 构建具有工具调用超能力的 Agent 🦸

作者: Aymeric Roucher

本笔记本演示了如何使用 smolagents 构建强大的 Agent

什么是 Agent?Agent 是由 LLM 驱动的系统,使 LLM(通过仔细的提示和输出解析)能够使用特定的工具来解决问题。

这些工具基本上是 LLM 自身无法很好执行的功能:例如,对于像 Llama-3-70B 这样的文本生成 LLM,这可以是一个图像生成工具、一个网页搜索工具、一个计算器……

什么是 smolagents?它是一个库,提供构建模块来构建您自己的 Agent!在 文档中了解更多信息。

让我们看看如何使用它,以及它可以解决哪些用例。

运行以下行以安装所需的依赖项

!pip install smolagents datasets langchain sentence-transformers faiss-cpu duckduckgo-search openai langchain-community --upgrade -q

让我们登录以便调用 HF Inference API

from huggingface_hub import notebook_login

notebook_login()

1. 🏞️ 多模态 + 🌐 网页浏览助手

对于此用例,我们想展示一个可以浏览网页并能够生成图像的 Agent。

为了构建它,我们只需要准备好两个工具:图像生成和网页搜索。

  • 对于图像生成,我们从 Hub 加载一个工具,该工具使用 HF Inference API (无服务器) 来使用 Stable Diffusion 生成图像。
  • 对于网页搜索,我们使用内置工具。
>>> from smolagents import load_tool, CodeAgent, HfApiModel, DuckDuckGoSearchTool

>>> # Import tool from Hub
>>> image_generation_tool = load_tool("m-ric/text-to-image", trust_remote_code=True)


>>> search_tool = DuckDuckGoSearchTool()

>>> model = HfApiModel("Qwen/Qwen2.5-72B-Instruct")
>>> # Initialize the agent with both tools
>>> agent = CodeAgent(tools=[image_generation_tool, search_tool], model=model)

>>> # Run it!
>>> result = agent.run(
...     "Generate me a photo of the car that James bond drove in the latest movie.",
... )
>>> result
TOOLCODE:
 from smolagents import Tool
from huggingface_hub import InferenceClient


class TextToImageTool(Tool):
    description = "This tool creates an image according to a prompt, which is a text description."
    name = "image_generator"
    inputs = {"prompt": {"type": "string", "description": "The image generator prompt. Don't hesitate to add details in the prompt to make the image look better, like 'high-res, photorealistic', etc."}}
    output_type = "image"
    model_sdxl = "black-forest-labs/FLUX.1-schnell"
    client = InferenceClient(model_sdxl)


    def forward(self, prompt):
        return self.client.text_to_image(prompt)

Image of an Aston Martin DB5

2. 📚💬 RAG 与迭代查询优化 & 来源选择

快速定义:检索增强生成 (RAG) 是“使用 LLM 回答用户查询,但答案基于从知识库检索的信息”

与使用原始或微调的 LLM 相比,此方法有很多优点:仅举几例,它允许将答案建立在真实事实上并减少虚构,它允许为 LLM 提供特定领域的知识,并且它允许对从知识库访问信息进行细粒度控制。

  • 现在假设我们想执行 RAG,但附加的约束是一些参数必须动态生成。例如,根据用户查询,我们可能希望将搜索限制在知识库的特定子集,或者我们可能希望调整检索到的文档数量。 困难在于:如何根据用户查询动态调整这些参数?

  • RAG 的一个常见失败案例是,当基于用户查询的检索没有返回任何相关的支持文档时。 是否有一种方法可以通过使用修改后的查询重新调用检索器来迭代,以防之前的检索结果不相关?

🔧 嗯,我们可以用一种简单的方式解决上述问题:我们将让我们的 Agent 控制检索器的参数!

➡️ 让我们展示如何做到这一点。我们首先加载一个知识库,我们想在其上执行 RAG:此数据集是许多 huggingface 包的文档页面的汇编,以 markdown 格式存储。

import datasets

knowledge_base = datasets.load_dataset("m-ric/huggingface_doc", split="train")

现在我们通过处理数据集并将其存储到向量数据库中来准备知识库,以供检索器使用。我们将使用 LangChain,因为它具有用于向量数据库的出色实用程序。

from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings

source_docs = [
    Document(page_content=doc["text"], metadata={"source": doc["source"].split("/")[1]}) for doc in knowledge_base
]

docs_processed = RecursiveCharacterTextSplitter(chunk_size=500).split_documents(source_docs)[:1000]

embedding_model = HuggingFaceEmbeddings(model_name="thenlper/gte-small")
vectordb = FAISS.from_documents(documents=docs_processed, embedding=embedding_model)

现在我们已经准备好数据库,让我们构建一个基于它回答用户查询的 RAG 系统!

我们希望我们的系统仅根据查询从最相关的信息来源中进行选择。

我们的文档页面来自以下来源

>>> all_sources = list(set([doc.metadata["source"] for doc in docs_processed]))
>>> print(all_sources)
['datasets-server', 'datasets', 'optimum', 'gradio', 'blog', 'course', 'hub-docs', 'pytorch-image-models', 'peft', 'evaluate', 'diffusers', 'hf-endpoints-documentation', 'deep-rl-class', 'transformers']

👉 现在让我们构建一个 RetrieverTool,我们的 Agent 可以利用它从知识库中检索信息。

由于我们需要将 vectordb 添加为工具的属性,我们不能简单地使用带有 @tool 装饰器的简单工具构造函数:因此我们将遵循高级 Agent 文档中突出显示的高级设置。

import json
from smolagents import Tool
from langchain_core.vectorstores import VectorStore


class RetrieverTool(Tool):
    name = "retriever"
    description = (
        "Retrieves some documents from the knowledge base that have the closest embeddings to the input query."
    )
    inputs = {
        "query": {
            "type": "string",
            "description": "The query to perform. This should be semantically close to your target documents. Use the affirmative form rather than a question.",
        },
        "source": {"type": "string", "description": ""},
        "number_of_documents": {
            "type": "string",
            "description": "the number of documents to retrieve. Stay under 10 to avoid drowning in docs",
        },
    }
    output_type = "string"

    def __init__(self, vectordb: VectorStore, all_sources: str, **kwargs):
        super().__init__(**kwargs)
        self.vectordb = vectordb
        self.inputs["source"]["description"] = (
            f"The source of the documents to search, as a str representation of a list. Possible values in the list are: {all_sources}. If this argument is not provided, all sources will be searched.".replace(
                "'", "`"
            )
        )

    def forward(self, query: str, source: str = None, number_of_documents=7) -> str:
        assert isinstance(query, str), "Your search query must be a string"
        number_of_documents = int(number_of_documents)

        if source:
            if isinstance(source, str) and "[" not in str(source):  # if the source is not representing a list
                source = [source]
            source = json.loads(str(source).replace("'", '"'))

        docs = self.vectordb.similarity_search(
            query,
            filter=({"source": source} if source else None),
            k=number_of_documents,
        )

        if len(docs) == 0:
            return "No documents found with this filtering. Try removing the source filter."
        return "Retrieved documents:\n\n" + "\n===Document===\n".join([doc.page_content for doc in docs])

可选:将您的 Retriever 工具分享到 Hub

要将您的工具分享到 Hub,首先将 RetrieverTool 定义单元格中的代码复制粘贴到一个新文件中,例如命名为 retriever.py

当工具从单独的文件加载时,您可以使用以下代码将其推送到 Hub(确保使用写入访问令牌登录)。

share_to_hub = True

if share_to_hub:
    from huggingface_hub import login
    from retriever import RetrieverTool

    login("your_token")

    tool = RetrieverTool(vectordb, all_sources)

    tool.push_to_hub(repo_id="m-ric/retriever-tool")

    # Loading the tool
    from smolagents import load_tool

    retriever_tool = load_tool("m-ric/retriever-tool", vectordb=vectordb, all_sources=all_sources)

运行 Agent!

from smolagents import HfApiModel, ToolCallingAgent

model = HfApiModel("Qwen/Qwen2.5-72B-Instruct")

retriever_tool = RetrieverTool(vectordb=vectordb, all_sources=all_sources)
agent = ToolCallingAgent(tools=[retriever_tool], model=model, verbose=0)

agent_output = agent.run("Please show me a LORA finetuning script")

print("Final output:")
print(agent_output)

这里发生了什么?首先,Agent 启动了检索器,并考虑了特定的来源(['transformers', 'blog'])。

但是这次检索没有产生足够的结果 ⇒ 没问题!Agent 可以迭代之前的检索结果,因此它只是使用限制性较小的搜索参数重新运行了检索。 因此,研究成功了!

请注意,使用 LLM Agent 调用检索器作为工具,并且可以动态修改查询和其他检索参数,是 RAG 的更通用表述,它也涵盖了许多 RAG 改进技术,例如迭代查询优化。

3. 💻 调试 Python 代码

由于 CodeAgent 具有内置的 Python 代码解释器,我们可以使用它来调试我们有故障的 Python 脚本!

from smolagents import CodeAgent

agent = CodeAgent(tools=[], model=HfApiModel("Qwen/Qwen2.5-72B-Instruct"))

code = """
numbers=[0, 1, 2]

for i in range(4):
    print(numbers(i))
"""

final_answer = agent.run(
    "I have some code that creates a bug: please debug it, then run it to make sure it works and return the final code",
    additional_args=dict(code=code),
)

如您所见,Agent 尝试了给定的代码,收到错误,分析错误,更正代码,并在验证它可以工作后返回它!

最终代码是更正后的代码

>>> print(final_answer)
numbers=[0, 1, 2]

for i in range(len(numbers)):
    print(numbers[i])

➡️ 结论

上述用例应该让您对我们的 Agent 框架的可能性有所了解!

有关更高级的用法,请阅读 文档

欢迎所有反馈,这将帮助我们改进框架!🚀

< > 在 GitHub 上更新