开源 AI 食谱 文档
使用 smolagents 构建具有工具调用超能力的 Agent 🦸
并获得增强的文档体验
开始使用
使用 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)
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 上更新