开源 AI 食谱文档

使用 Transformers Agents 构建拥有工具调用超能力的代理 🦸

Hugging Face's logo
加入 Hugging Face 社区

并获得增强型文档体验

开始使用

Open In Colab

使用 Transformers Agents 构建拥有工具调用超能力的代理 🦸

作者:Aymeric Roucher

此笔记本展示了如何使用 Transformers Agents 构建很棒的代理

什么是代理?代理是基于 LLM 的系统,通过精心设计的提示和输出解析,使 LLM 能够使用特定的工具来解决问题。

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

什么是Transformers Agents?它是我们 transformers 库的扩展,提供了构建自己的代理的构建块!从文档中了解有关它的更多信息。

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

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

!pip install "transformers[agents]" datasets langchain sentence-transformers faiss-cpu duckduckgo-search openai langchain-community --upgrade -q

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

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

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

  • 对于图像生成,我们从 Hub 加载一个工具,该工具使用 HF 推理 API(无服务器)通过 Stable Diffusion 生成图像。
  • 对于网页搜索,我们使用内置工具。
from transformers import load_tool, ReactCodeAgent, HfApiEngine

# Import tool from Hub
image_generation_tool = load_tool("m-ric/text-to-image", cache=False)

# Import tool from LangChain
from transformers.agents.search import DuckDuckGoSearchTool

search_tool = DuckDuckGoSearchTool()

llm_engine = HfApiEngine("Qwen/Qwen2.5-72B-Instruct")
# Initialize the agent with both tools
agent = ReactCodeAgent(tools=[image_generation_tool, search_tool], llm_engine=llm_engine)

# Run it!
result = agent.run(
    "Generate me a photo of the car that James bond drove in the latest movie.",
)
result

Image of an Aston Martin DB5

2. 📚💬 RAG 结合迭代查询细化和来源选择

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

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

  • 现在假设我们要执行 RAG,但带有额外的约束条件,即某些参数必须动态生成。例如,根据用户查询,我们可能希望将搜索限制在知识库的特定子集中,或者我们可能希望调整检索到的文档数量。难点在于:如何根据用户查询动态调整这些参数?

  • RAG 的一个常见失败情况是,基于用户查询的检索没有返回任何相关的支持文档。有没有办法通过在之前的结果不相关时使用修改后的查询重新调用检索器来进行迭代?

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

➡️ 让我们展示如何做到这一点。我们首先加载一个我们想要在其上执行 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,以便我们的代理可以利用它从知识库中检索信息。

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

import json
from transformers.agents 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])

可选:将你的检索器工具分享到 Hub

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

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

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 transformers.agents import load_tool

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

运行代理!

from transformers.agents import HfApiEngine, ReactJsonAgent

llm_engine = HfApiEngine("Qwen/Qwen2.5-72B-Instruct")

retriever_tool = RetrieverTool(vectordb=vectordb, all_sources=all_sources)
agent = ReactJsonAgent(tools=[retriever_tool], llm_engine=llm_engine, verbose=0)

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

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

发生了什么?首先,代理启动了检索器,并指定了一些特定的来源(['transformers', 'blog'])。

但这次检索没有产生足够的结果 ⇒ 没问题!代理可以迭代之前的结果,所以它只是用更宽松的搜索参数重新运行了检索。因此,研究成功了!

注意,**使用一个调用检索器的LLM代理**,它可以动态修改查询和其他检索参数,**是RAG更通用的公式**,它也涵盖了许多RAG改进技术,如迭代式查询细化。

3. 💻 调试 Python 代码

由于ReactCodeAgent内置了Python代码解释器,我们可以用它来调试有问题的Python脚本!

from transformers import ReactCodeAgent

agent = ReactCodeAgent(tools=[], llm_engine=HfApiEngine("Qwen/Qwen2.5-72B-Instruct"))

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

for i in range(4):
    print(list(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",
    code=code,
)

如你所见,代理尝试了给定的代码,得到了一个错误,分析了错误,修正了代码,并在验证其工作后将其返回!

最终的代码是修正后的代码

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

for i in range(4):
    if i < len(my_list):
        print(my_list[i])
    else:
        print("Index out of range")

4. 创建你自己的LLM引擎(OpenAI)

设置你自己的LLM引擎非常容易:它只需要一个__call__方法,满足以下条件

  1. ChatML 格式 的消息列表作为输入,并输出答案。
  2. 接受一个stop_sequences参数,用于传递生成停止的序列。
  3. 根据你的LLM接受的消息角色类型,你可能还需要转换一些消息角色。
import os
from openai import OpenAI
from transformers.agents.llm_engine import MessageRole, get_clean_message_list

openai_role_conversions = {
    MessageRole.TOOL_RESPONSE: "user",
}


class OpenAIEngine:
    def __init__(self, model_name="gpt-4o-2024-05-13"):
        self.model_name = model_name
        self.client = OpenAI(
            api_key=os.getenv("OPENAI_API_KEY"),
        )

    def __call__(self, messages, stop_sequences=[]):
        # Get clean message list
        messages = get_clean_message_list(messages, role_conversions=openai_role_conversions)

        # Get LLM output
        response = self.client.chat.completions.create(
            model=self.model_name,
            messages=messages,
            stop=stop_sequences,
        )
        return response.choices[0].message.content


openai_engine = OpenAIEngine()
agent = ReactCodeAgent(llm_engine=openai_engine, tools=[])

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

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

final_answer = agent.run(
    "I have some code that creates a bug: please debug it and return the final code",
    code=code,
)
>>> print(final_answer)
my_list = [0, 1, 2]  # Renamed the list to avoid using the built-in name

for i in range(len(my_list)):  # Changed the range to be within the length of the list
    print(my_list[i])  # Corrected the list access syntax

➡️ 结论

上面的用例应该让你了解我们代理框架的可能性!

要了解更多高级用法,请阅读文档,以及这个实验,它让我们能够基于Llama-3-70B构建我们自己的代理,在非常困难的GAIA排行榜上击败许多GPT-4代理!

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

< > 更新 在GitHub上