跳到正文
原文
Hugging Face Blog·· 2024-05-13精选AI 评分70

Hugging Face 发布 Transformers Agents 2.0

License to Call: Introducing Transformers Agents 2.0

AI 导读

Hugging Face 发布 Transformers Agents 2.0,在原有 agent 类型外新增可基于历史观测迭代的 agent,并将代码与常用属性设计得清晰、模块化,同时加入 agent 分享功能。

推荐理由

官方给出新 Agent 框架的模块设计与 GAIA 榜单实测,读者可据此比较不同 LLM 引擎的 agent 表现。

正文 · AI 翻译

简而言之

我们发布了 Transformers Agents 2.0!

⇒ 🎁 在现有 agent 类型的基础上,我们引入了两个新 agent,它们能够基于过往观察进行迭代,以解决复杂任务。

⇒ 💡 我们的目标是让代码清晰且模块化,并使最终提示和工具等常见属性保持透明。

⇒ 🤝 我们添加了分享选项,以促进社区 agent。

⇒ 💪 性能极强的新 agent 框架,使 Llama-3-70B-Instruct agent 在 GAIA Leaderboard 上超越基于 GPT-4 的 agent!

🚀 快去试试吧,在 GAIA leaderboard 上攀登得更高!

transformers.agents 现已升级为独立库 smolagents!这两个库的 API 非常相似,因此切换很容易。 请查看 smolagents 介绍博客 这里。

目录

什么是 agent?

大语言模型(LLM)可以处理广泛的任务,但它们往往在逻辑、计算和搜索等特定任务上表现不佳。在这些它们表现不佳的领域中进行提示时,它们经常无法生成正确答案。

克服这一弱点的一种方法是创建一个 agent,它只是一个由 LLM 驱动的程序。agent 通过 工具 获得能力,以帮助其执行操作。当 agent 需要特定技能来解决某个特定问题时,它会依赖其工具箱中合适的工具。

因此,在解决问题的过程中,当 agent 需要特定技能时,它可以直接依赖其工具箱中合适的工具。

实验上,agent 框架通常效果很好,在多个基准测试中达到最先进的性能。例如,看看 HumanEval 的顶级提交:它们都是 agent 系统。

Transformers Agents 的方法

构建 agent 工作流很复杂,我们认为这些系统需要高度的清晰性和模块化。我们一年前推出了 Transformers Agents,现在我们正加倍努力实现核心设计目标。

我们的框架力求做到:

  • 通过简洁实现清晰:我们将抽象降到最低。简单的错误日志和可访问的属性让你可以轻松检查正在发生的事情,并为你提供更多清晰度。
  • Modularity: We prefer to propose building blocks rather than full, complex feature sets. You are free to choose whatever building blocks are best for your project.
    • 例如,由于任何 agent 系统都只是由 LLM 引擎驱动的载体,我们决定在概念上将两者分离,这样你就可以基于任何底层 LLM 创建任何 agent 类型。

在此基础上,我们还拥有分享功能,让你能够站在巨人的肩膀上!

主要元素

  • Tool:这是让你使用工具或实现新工具的类。它主要由一个执行工具操作的可调用 forward method,以及一小组基本属性组成:name、descriptions、inputs 和 output_type。这些属性用于动态生成工具的使用手册,并将其插入 LLM 的提示中。
  • Toolbox:这是一组作为资源提供给智能体以解决特定任务的工具。出于性能原因,工具箱中的工具已经实例化并准备就绪。这是因为有些工具需要时间来初始化,因此通常更好的做法是复用现有的工具箱并只替换一个工具,而不是在每次智能体初始化时从头构建一组工具。
  • CodeAgent:一个非常简单的智能体,它将其动作生成为一大块 Python 代码。它无法基于之前的观察进行迭代。
  • ReactAgent: ReAct agents follow a cycle of Thought ⇒ Action ⇒ Observation until they’ve solve the task. We propose two classes of ReactAgent:
    • ReactCodeAgent 将其动作生成为 Python 代码块。
    • ReactJsonAgent 将其动作生成为 JSON 代码块。

查看文档以了解如何使用每个组件!

智能体在底层是如何工作的?

本质上,智能体所做的就是“让 LLM 使用工具”。智能体有一个关键的 agent.run() 方法,它可以:

  • 以特定的提示词向你的 LLM 提供关于工具使用的信息。这样,LLM 就可以选择要运行的工具来完成任务。
  • 解析来自 LLM 输出的工具调用(可以通过代码、JSON 格式或任何其他格式)。
  • 执行这些调用。
  • 如果智能体被设计为基于之前的输出进行迭代,它会保留一个记忆,其中包含之前的工具调用和观察结果。这个记忆可以根据你希望它有多长期而具有或多或少的细粒度。

graph of agent workflows

关于智能体的更一般背景,你可以阅读 Lilian Weng 的这篇精彩博客文章或我们关于使用 LangChain 构建智能体的早期博客文章。

要深入了解我们的包,请查看智能体文档。

示例用例

为了获得此功能的早期访问权限,请先从它的 main 分支安装 transformers:

pip install "git+https://github.com/huggingface/transformers.git#egg=transformers[agents]"

Agents 2.0 将在 v4.41.0 版本中发布,于 5 月中旬上线。

自我纠正的检索增强生成

快速定义:检索增强生成(RAG)是“使用 LLM 回答用户查询,但将答案建立在从知识库中检索到的信息之上”。与使用普通的或微调过的 LLM 相比,它有许多优势:仅举几例,它允许将答案建立在真实事实之上并减少虚构,它允许为 LLM 提供特定领域的知识,并且它允许对知识库中信息的访问进行细粒度控制。

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

好吧,我们可以通过让我们的智能体访问这些参数来做到这一点!

让我们设置这个系统。

运行下面这行来安装所需的依赖项:

pip install langchain sentence-transformers faiss-cpu

我们首先加载一个我们想在其上执行 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("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)

['blog', 'optimum', 'datasets-server', 'datasets', 'transformers', 'course',
'gradio', 'diffusers', 'evaluate', 'deep-rl-class', 'peft',
'hf-endpoints-documentation', 'pytorch-image-models', 'hub-docs']

我们如何根据用户查询选择相关的来源?

👉 让我们把 RAG 系统构建为一个可以自由选择其来源的 agent!

我们创建一个检索工具,agent 可以用它自己选择的参数来调用:

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": "text",
            "description": "The query to perform. This should be semantically close to your target documents. Use the affirmative form rather than a question.",
        },
        "source": {
            "type": "text", 
            "description": ""
        },
    }
    output_type = "text"
    
    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."
          )

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

        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=3)

        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]
        )

现在,创建一个利用这个工具的 agent 就很简单了!

在初始化时,agent 将需要这些参数:

  • tools:agent 能够调用的工具列表。
  • llm_engine:为 agent 提供支持的 LLM。

我们的 llm_engine 必须是一个可调用对象,它以 messages 列表作为输入并返回文本。它还需要接受一个 stop_sequences 参数,用于指示何时停止生成。为方便起见,我们直接使用包中提供的 HfEngine 类来获得一个调用我们 Inference API 的 LLM 引擎。

from transformers.agents import HfEngine, ReactJsonAgent

llm_engine = HfEngine("meta-llama/Meta-Llama-3-70B-Instruct")

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

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

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

由于我们将 agent 初始化为 ReactJsonAgent,它已自动获得一个默认系统提示,该提示指示 LLM 引擎逐步处理并以 JSON blob 的形式生成工具调用(你可以根据需要将此提示模板替换为你自己的模板)。

然后,当其 .run() 方法启动时,agent 会负责调用 LLM 引擎、解析工具调用 JSON blob 并执行这些工具调用,所有这些都在一个循环中进行,直到提供最终答案才结束。

我们得到以下输出:

Calling tool: retriever with arguments: {'query': 'LORA finetuning script', 'source': "['transformers', 'datasets-server', 'datasets']"}
Calling tool: retriever with arguments: {'query': 'LORA finetuning script'}
Calling tool: retriever with arguments: {'query': 'LORA finetuning script example', 'source': "['transformers', 'datasets-server', 'datasets']"}
Calling tool: retriever with arguments: {'query': 'LORA finetuning script example'}
Calling tool: final_answer with arguments: {'answer': 'Here is an example of a LORA finetuning script: https://github.com/huggingface/diffusers/blob/dd9a5caf61f04d11c0fa9f3947b69ab0010c9a0f/examples/text_to_image/train_text_to_image_lora.py#L371'}

Final output:
Here is an example of a LORA finetuning script: https://github.com/huggingface/diffusers/blob/dd9a5caf61f04d11c0fa9f3947b69ab0010c9a0f/examples/text_to_image/train_text_to_image_lora.py#L371

我们可以看到自我修正正在发挥作用:agent 最初试图限制来源,但由于缺少相应的文档,它最终完全没有限制来源。

我们可以通过检查第 2 步日志中的 llm 输出来验证这一点:print(agent.logs[2]['llm_output'])

Thought: I'll try to retrieve some documents related to LORA finetuning scripts from the entire knowledge base, without any source filtering.

Action:
{
  "action": "retriever",
  "action_input": {"query": "LORA finetuning script"}
}

使用简单的多 agent 设置 🤝 实现高效的网页浏览

在这个例子中,我们想要构建一个 agent,并在 GAIA 基准测试(Mialon et al. 2023)上测试它。GAIA 是一个极其困难的基准测试,大多数问题都需要使用不同工具进行多步推理。一个特别困难的要求是拥有一个强大的网页浏览器,能够导航到具有特定约束的页面:使用网站的内部导航发现页面、按时间选择特定文章……

网页浏览需要深入子页面,并滚动浏览大量对于更高层任务求解来说并不需要的文本 token。我们将网页浏览子任务分配给专门的网页冲浪 agent。我们为它提供一些浏览网页的工具和一个特定的提示(请查看仓库以找到具体实现)。

定义这些工具超出了本文的范围:但你可以查看 该仓库 以找到具体实现。

from transformers.agents import ReactJsonAgent, HfEngine

WEB_TOOLS = [
    SearchInformationTool(),
    NavigationalSearchTool(),
    VisitTool(),
    DownloadTool(),
    PageUpTool(),
    PageDownTool(),
    FinderTool(),
    FindNextTool(),
]

websurfer_llm_engine = HfEngine(
    model="CohereForAI/c4ai-command-r-plus"
)  # We choose Command-R+ for its high context length

websurfer_agent = ReactJsonAgent(
    tools=WEB_TOOLS,
    llm_engine=websurfer_llm_engine,
)

为了让这个 agent 能够被更高层的任务求解 agent 调用,我们可以简单地将其封装在另一个工具中:

class SearchTool(Tool):
    name = "ask_search_agent"
    description = "A search agent that will browse the internet to answer a question. Use it to gather informations, not for problem-solving."

    inputs = {
        "question": {
            "description": "Your question, as a natural language sentence. You are talking to an agent, so provide them with as much context as possible.",
            "type": "text",
        }
    }
    output_type = "text"

    def forward(self, question: str) -> str:
        return websurfer_agent.run(question)

然后我们用这个搜索工具初始化任务求解 agent:

from transformers.agents import ReactCodeAgent

llm_engine = HfEngine(model="meta-llama/Meta-Llama-3-70B-Instruct")
react_agent_hf = ReactCodeAgent(
    tools=[SearchTool()],
    llm_engine=llm_engine,
)

让我们用以下任务运行该 agent:

使用 LibreText 的 Introductory Chemistry 材料中由 Marisa Alviar-Agnew 与 Henry Agnew 根据 CK-12 许可证授权的化学材料中的密度测量值,材料汇编日期为 08/21/2023。 我在 25C 下有一加仑蜂蜜和一加仑蛋黄酱。我从这加仑蜂蜜中一次取出一杯。我需要取出多少次,才能使蜂蜜的重量小于蛋黄酱?假设容器本身的重量相同。

Thought: I will use the 'ask_search_agent' tool to find the density of honey and mayonnaise at 25C.
==== Agent is executing the code below:
density_honey = ask_search_agent(question="What is the density of honey at 25C?")
print("Density of honey:", density_honey)
density_mayo = ask_search_agent(question="What is the density of mayonnaise at 25C?")
print("Density of mayo:", density_mayo)
===
Observation:
Density of honey: The density of honey is around 1.38-1.45kg/L at 20C. Although I couldn't find information specific to 25C, minor temperature differences are unlikely to affect the density that much, so it's likely to remain within this range.
Density of mayo: The density of mayonnaise at 25°C is 0.910 g/cm³.

===== New step =====
Thought: I will convert the density of mayonnaise from g/cm³ to kg/L and then calculate the initial weights of the honey and mayonnaise in a gallon. After that, I will calculate the weight of honey after removing one cup at a time until it weighs less than the mayonnaise.
==== Agent is executing the code below:
density_honey = 1.42 # taking the average of the range
density_mayo = 0.910 # converting g/cm³ to kg/L
density_mayo = density_mayo * 1000 / 1000 # conversion

gallon_to_liters = 3.785 # conversion factor
initial_honey_weight = density_honey * gallon_to_liters
initial_mayo_weight = density_mayo * gallon_to_liters

cup_to_liters = 0.236 # conversion factor
removed_honey_weight = cup_to_liters * density_honey
===
Observation:

===== New step =====
Thought: Now that I have the initial weights of honey and mayonnaise, I'll try to calculate the number of cups to remove from the honey to make it weigh less than the mayonnaise using a simple arithmetic operation.
==== Agent is executing the code below:
cups_removed = int((initial_honey_weight - initial_mayo_weight) / removed_honey_weight) + 1
print("Cups removed:", cups_removed)
final_answer(cups_removed)
===
>>> Final answer: 6

✅ 而且答案是正确的!

测试我们的 agent

让我们试用一下我们的 agent 框架,并用它来对不同的模型进行基准测试!

下面实验的所有代码都可以在这里找到。

LLM 引擎基准测试

agents_reasoning_benchmark 是一个小而强大的推理测试,用于评估代理性能。该基准测试已在我们之前的博客文章中使用并详细解释过。

其理念是,你为代理选择的工具会极大地改变某些任务的性能。因此,该基准测试将工具集限制为计算器和基本搜索工具。我们从几个数据集中挑选了仅使用这两种工具就能解决的问题:

这里我们尝试了 3 种不同的引擎:Mixtral-8x7B、Llama-3-70B-Instruct 和 GPT-4 Turbo。

benchmark of agent performances

结果如上所示——为了提高精确度,取两次完整运行的平均值。我们还测试了 Command-R+ 和 Mixtral-8x22B,但为了清晰起见没有展示。

⇒ Llama-3-70B-Instruct 在开源模型中领先:它与 GPT-4 不相上下,而且在 ReactCodeAgent 中尤为强劲,这得益于 Llama 3 强大的编码性能!

💡 比较基于 JSON 和基于代码的 React 代理很有意思:在使用 Mixtral-8x7B 这类能力较弱的 LLM 引擎时,基于代码的代理表现不如 JSON,因为 LLM 引擎经常无法生成好的代码。但代码版本在作为引擎的更强大模型上确实大放异彩:根据我们的经验,在使用 Llama-3-70B-Instruct 时,代码版本甚至优于 JSON。因此,我们在下一个挑战中使用代码版本:在完整的 GAIA 基准上测试。

用多模态代理攀登 GAIA 排行榜

GAIA(Mialon et al., 2023)是一个极其困难的基准:从上方的 agent_reasoning_benchmark 中可以看到,即使我们精心挑选了可用 2 个基本工具解决的任务,模型的表现也没有超过 50%。

现在我们想在完整集合上获得分数,不再精心挑选问题。因此我们必须覆盖所有模态,这促使我们使用这些特定工具:

  • SearchTool:上文定义的网页浏览器。
  • TextInspectorTool:将文档作为文本文件打开并返回其内容。
  • SpeechToTextTool:将音频文件转录为文本。我们使用基于 distil-whisper 的默认工具。
  • VisualQATool:视觉分析图像。为此我们使用全新的 Idefics2-8b-chatty!

我们首先初始化这些工具(更多细节请查看仓库中的代码)。

然后我们初始化代理:

from transformers.agents import ReactCodeAgent, HfEngine

TASK_SOLVING_TOOLBOX = [
    SearchTool(),
    VisualQATool(),
    SpeechToTextTool(),
    TextInspectorTool(),
]

react_agent_hf = ReactCodeAgent(
    tools=TASK_SOLVING_TOOLBOX,
    llm_engine=HfEngine(model="meta-llama/Meta-Llama-3-70B-Instruct"),
    memory_verbose=True,
)

在完成 165 个问题所需的一段时间后,我们将结果提交到 GAIA 排行榜,然后…… 🥁🥁🥁

GAIA leaderboard

⇒ 我们的代理排名第 4:它击败了许多基于 GPT-4 的代理,现在是开源类别中当之无愧的竞争者!

结论

我们将在未来几个月继续改进这个包。我们已经在开发路线图中确定了几个令人兴奋的方向:

  • 更多代理分享选项:目前你可以从 Hub 推送或加载工具,我们也将实现推送/加载代理。
  • 更好的工具,尤其是图像处理方面。
  • 长期记忆管理。
  • 多智能体协作。

👉 快去试试 transformers agents 吧!我们期待收到你的反馈和想法。

让我们用更多开源模型来占据排行榜前列吧!🚀

transformers.agents 现已升级为独立库 smolagents!这两个库的 API 非常相似,因此切换起来很容易。 请在这里查看 smolagents 介绍博客 here。

来源:Hugging Face Blog · hf-mirror.com