使用 Transformers Agents 构建具有工具调用超能力的智能体 🦸
作者: Aymeric Roucher
这个 notebook 展示了如何使用 Transformers Agents 来构建出色的智能体!
什么是智能体?智能体是由大型语言模型(LLM)驱动的系统,它们使得 LLM(通过精心设计的提示和输出解析)能够使用特定的工具来解决问题。
这些工具基本上是 LLM 自身无法很好执行的功能:例如,对于像 Llama-3-70B 这样的文本生成 LLM,这可能是一个图像生成工具、网络搜索工具、计算器…
什么是 Transformers Agents ?它是我们 transformers
库的一个扩展,提供了构建自己的智能体的构建块!在文档中了解更多信息。
让我们看看如何使用它,以及它能解决哪些用例。
我们从源代码安装 transformers agents ,你可以使用 pip install transformers[agents]
轻松安装。
!pip install "git+https://github.com/huggingface/transformers.git#egg=transformers[agents]"
!pip install datasets huggingface_hub langchain sentence-transformers faiss-cpu serpapi google-search-results openai -q
1. 🏞️ 多模态 + 🌐 网络浏览助手
对于这个用例,我们想要展示一个能够浏览网络并能够生成图像的智能体。
为了构建它,我们只需要准备两个工具:图像生成和网络搜索。
- 对于图像生成,我们从 Hub 加载一个工具,该工具使用 HF 推理 API(无服务器)使用 Stable Diffusion 生成图像。
- 对于网络搜索,我们加载一个 LangChain 工具。
from transformers import Tool, load_tool, ReactCodeAgent, HfEngine
# Import tool from Hub
image_generation_tool = load_tool("m-ric/text-to-image")
# Import tool from LangChain
from langchain.agents import load_tools
search_tool = Tool.from_langchain(load_tools(["serpapi"])[0])
llm_engine = HfEngine("meta-llama/Meta-Llama-3-70B-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
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)
['evaluate', 'course', 'deep-rl-class', 'peft', 'hf-endpoints-documentation', 'blog', 'gradio', 'datasets', 'datasets-server', 'transformers', 'optimum', 'hub-docs', 'pytorch-image-models', 'diffusers']
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": ""},
"number_of_documents": {
"type": "text",
"description": "the number of documents to retrieve. Stay under 10 to avoid drowning in docs",
},
}
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, 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(确保使用具有写入
访问权限的 token 登录)
share_to_hub = False
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")
运行智能体!
>>> from transformers.agents import HfEngine, ReactJsonAgent, load_tool
>>> llm_engine = HfEngine("meta-llama/Meta-Llama-3-70B-Instruct")
>>> retriever_tool = load_tool("m-ric/retriever-tool", 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)
Final output: https://github.com/huggingface/diffusers/blob/main/examples/text_to_image/train_text_to_image_lora.py
发生了什么?首先,智能体启动了检索器,并考虑了特定的来源(['transformers', 'blog']
)。
但是这次检索没有产生足够的结果 ⇒ 没关系!智能体可以迭代之前的结果,因此它只是用不那么严格的搜索参数重新运行了它的检索。
因此,研究成功了!
请注意,使用调用检索器作为工具并可以动态修改查询和其他检索参数的 LLM 智能体是 RAG 的更一般的表述,这也涵盖了像迭代查询优化这样的许多 RAG 改进技术。
3. 💻 调试 Python 代码
from transformers import ReactCodeAgent
agent = ReactCodeAgent(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)
list=[0, 1, 2] for i in range(4): print(list(i))
4. 创建你自己的 LLM 引擎(OpenAI)
设置你自己的 LLM 引擎真的非常简单:
它只需要一个具有以下标准的__call__
方法:
- 接受ChatML 格式的消息列表作为输入并输出答案。
- 接受一个
stop_sequences
参数,以传递生成停止的序列。 - 根据你的 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 智能体!
欢迎所有反馈,这将帮助我们改进框架! 🚀
< > Update on GitHub