Hugging Face 发布 Transformers Agents 2.0
License to Call: Introducing Transformers Agents 2.0
Hugging Face 发布 Transformers Agents 2.0,在原有 agent 类型外新增可基于历史观测迭代的 agent,并将代码与常用属性设计得清晰、模块化,同时加入 agent 分享功能。
官方给出新 Agent 框架的模块设计与 GAIA 榜单实测,读者可据此比较不同 LLM 引擎的 agent 表现。
简而言之
我们发布了 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:这是让你使用工具或实现新工具的类。它主要由一个执行工具操作的可调用 forwardmethod,以及一小组基本属性组成: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 格式或任何其他格式)。
- 执行这些调用。
- 如果智能体被设计为基于之前的输出进行迭代,它会保留一个记忆,其中包含之前的工具调用和观察结果。这个记忆可以根据你希望它有多长期而具有或多或少的细粒度。
关于智能体的更一般背景,你可以阅读 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 是一个小而强大的推理测试,用于评估代理性能。该基准测试已在我们之前的博客文章中使用并详细解释过。
其理念是,你为代理选择的工具会极大地改变某些任务的性能。因此,该基准测试将工具集限制为计算器和基本搜索工具。我们从几个数据集中挑选了仅使用这两种工具就能解决的问题:
- 来自 HotpotQA 的 30 个问题(Yang et al., 2018),用于测试搜索工具的使用。
- 来自 GSM8K 的 40 个问题(Cobbe et al., 2021),用于测试计算器的使用。
- 来自 GAIA 的 20 个问题(Mialon et al., 2023),用于测试两种工具在解决难题时的使用。
这里我们尝试了 3 种不同的引擎:Mixtral-8x7B、Llama-3-70B-Instruct 和 GPT-4 Turbo。
结果如上所示——为了提高精确度,取两次完整运行的平均值。我们还测试了 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 排行榜,然后…… 🥁🥁🥁
⇒ 我们的代理排名第 4:它击败了许多基于 GPT-4 的代理,现在是开源类别中当之无愧的竞争者!
结论
我们将在未来几个月继续改进这个包。我们已经在开发路线图中确定了几个令人兴奋的方向:
- 更多代理分享选项:目前你可以从 Hub 推送或加载工具,我们也将实现推送/加载代理。
- 更好的工具,尤其是图像处理方面。
- 长期记忆管理。
- 多智能体协作。
👉 快去试试 transformers agents 吧!我们期待收到你的反馈和想法。
让我们用更多开源模型来占据排行榜前列吧!🚀
transformers.agents现已升级为独立库 smolagents!这两个库的 API 非常相似,因此切换起来很容易。 请在这里查看smolagents介绍博客 here。
来源:Hugging Face Blog · hf-mirror.com