Chapter 51
创建宾客信息检索生成(RAG)工具
创建宾客信息检索生成(RAG)工具
您信赖的智能体 Alfred 正在筹备本世纪最盛大的晚会。为确保活动顺利进行,Alfred 需要快速获取最新宾客信息。让我们通过定制化的检索增强生成(RAG)工具(基于专属数据集)为 Alfred 赋能。
为何选择 RAG 技术?
试想 Alfred 在宾客间穿梭时需即时调取详细信息,传统大语言模型(LLM)可能面临以下挑战:
- 宾客名单属于特定活动数据,不在模型训练范围内
- 宾客信息可能频繁变更或更新
- 需精确检索电子邮件地址等细节信息
这正是检索增强生成(RAG)技术的优势所在!通过结合检索系统与 LLM,Alfred 可按需获取准确、实时的宾客信息。
[!TIP] 您可以选择课程涵盖的任何框架实现本用例,请通过代码标签页选择偏好方案。
应用搭建
我们将以 Hugging Face Space 为开发环境,采用结构化 Python 项目构建智能体。这种架构通过功能模块化实现代码整洁,更贴近实际部署场景。
项目结构
tools.py– 为智能体提供辅助工具retriever.py– 实现支持知识访问的检索功能app.py– 整合所有组件形成完整功能智能体(将在本单元最后完成)
实操参考:Hugging Face Space 示例,该空间已部署本单元开发的智能体增强 RAG 系统,欢迎克隆体验!
可直接测试下方智能体:
数据集概览
数据集 agents-course/unit3-invitees 包含以下字段:
- Name: 宾客全名
- Relation: 与主办方关系
- Description: 简要传记或趣闻
- Email Address: 邀请函发送及跟进联系方式
数据集预览:
[!TIP] 实际场景可扩展数据集字段,包含饮食偏好、礼品兴趣、禁忌话题等对主持人有用的详细信息。
构建宾客信息工具
我们将创建 Alfred 在晚会期间快速检索宾客信息的定制工具,分三步实现:
- 加载并预处理数据集
- 创建检索工具
- 工具与 Alfred 集成
步骤一:加载并预处理数据集
首先将原始宾客数据转换为适合检索的格式:
from langchain_core.documents import Document
# 加载数据集
guest_dataset = datasets.load_dataset("agents-course/unit3-invitees", split="train")
# 转换为 Document 对象
docs = [
Document(
page_content="\n".join([
f"Name: {guest['name']}",
f"Relation: {guest['relation']}",
f"Description: {guest['description']}",
f"Email: {guest['email']}"
]),
metadata={"name": guest["name"]}
)
for guest in guest_dataset
]我们将使用 Hugging Face datasets 库来加载数据集并将其转换为来自 llama_index.core.schema 模块的 Document 对象列表。
from llama_index.core.schema import Document
# 加载数据集
guest_dataset = datasets.load_dataset("agents-course/unit3-invitees", split="train")
# 转换为 Document 对象
docs = [
Document(
text="\n".join([
f"Name: {guest_dataset['name'][i]}",
f"Relation: {guest_dataset['relation'][i]}",
f"Description: {guest_dataset['description'][i]}",
f"Email: {guest_dataset['email'][i]}"
]),
metadata={"name": guest_dataset['name'][i]}
)
for i in range(len(guest_dataset))
]我们将使用 Hugging Face datasets 库来加载数据集并将其转换为来自 langchain.docstore.document 模块的 Document 对象列表。
from langchain_core.documents import Document
# 加载数据集
guest_dataset = datasets.load_dataset("agents-course/unit3-invitees", split="train")
# 转换为 Document 对象
docs = [
Document(
page_content="\n".join([
f"Name: {guest['name']}",
f"Relation: {guest['relation']}",
f"Description: {guest['description']}",
f"Email: {guest['email']}"
]),
metadata={"name": guest["name"]}
)
for guest in guest_dataset
]在上面的代码中,我们:
- 加载数据集
- 将每条房客记录转换为包含格式化内容的 “Document” 对象
- 将 “Document” 对象存储在列表中
这意味着我们已经准备好所有数据,可以开始配置检索功能了。
步骤 2:创建检索工具
现在,让我们创建一个自定义工具,Alfred 可以使用它来搜索房客信息。
我们将使用“langchain_community.retrievers”模块中的“BM25Retriever”来创建一个检索工具。
[!TIP] BM25Retriever 是检索的一个很好的起点,但对于更高级的语义搜索,您可以考虑使用基于嵌入的检索器,例如来自 sentence-transformers 的检索器。
from smolagents import Tool
from langchain_community.retrievers import BM25Retriever
class GuestInfoRetrieverTool(Tool):
name = "guest_info_retriever"
description = "Retrieves detailed information about gala guests based on their name or relation."
inputs = {
"query": {
"type": "string",
"description": "The name or relation of the guest you want information about."
}
}
output_type = "string"
def __init__(self, docs):
self.is_initialized = False
self.retriever = BM25Retriever.from_documents(docs)
def forward(self, query: str):
results = self.retriever.get_relevant_documents(query)
if results:
return "\n\n".join([doc.page_content for doc in results[:3]])
else:
return "No matching guest information found."
# 初始化工具
guest_info_tool = GuestInfoRetrieverTool(docs)让我们逐步了解这个工具:
name和description帮助智能体了解何时以及如何使用此工具inputs定义工具所需的参数(在本例中为搜索查询)- 我们使用
BM25Retriever,这是一种强大的文本检索算法,不需要嵌入 forward方法处理查询并返回最相关的客人信息
我们将使用 llama_index.retrievers.bm25 模块中的 BM25Retriever 来创建一个检索工具。
[!TIP] BM25Retriever 是一个很好的检索起点,但对于更高级的语义搜索,您可以考虑使用基于嵌入的检索器,例如 sentence-transformers 中的检索器。
from llama_index.core.tools import FunctionTool
from llama_index.retrievers.bm25 import BM25Retriever
bm25_retriever = BM25Retriever.from_defaults(nodes=docs)
def get_guest_info_retriever(query: str) -> str:
"""Retrieves detailed information about gala guests based on their name or relation."""
results = bm25_retriever.retrieve(query)
if results:
return "\n\n".join([doc.text for doc in results[:3]])
else:
return "No matching guest information found."
# 初始化工具
guest_info_tool = FunctionTool.from_defaults(get_guest_info_retriever)让我们逐步了解这个工具。
- 文档字符串帮助智能体了解何时以及如何使用此工具
- 类型装饰器定义了工具所需的参数(在本例中为搜索查询)
- 我们使用
BM25Retriever,这是一种强大的文本检索算法,不需要嵌入 - 该方法处理查询并返回最相关的客人信息
我们将使用 langchain_community.retrievers 模块中的 BM25Retriever 来创建一个检索工具。
[!TIP] BM25Retriever 是一个很好的检索起点,但对于更高级的语义搜索,您可以考虑使用基于嵌入的检索器,例如 sentence-transformers 中的检索器。
from langchain_community.retrievers import BM25Retriever
from langchain_core.tools import Tool
bm25_retriever = BM25Retriever.from_documents(docs)
def extract_text(query: str) -> str:
"""Retrieves detailed information about gala guests based on their name or relation."""
results = bm25_retriever.invoke(query)
if results:
return "\n\n".join([doc.page_content for doc in results[:3]])
else:
return "No matching guest information found."
guest_info_tool = Tool(
name="guest_info_retriever",
func=extract_text,
description="Retrieves detailed information about gala guests based on their name or relation."
)让我们逐步了解这个工具。
name和description帮助智能体了解何时以及如何使用此工具。- 类型装饰器定义了工具所需的参数(在本例中为搜索查询)。
- 我们使用
BM25Retriever,这是一种强大的文本检索算法,无需嵌入。 - 该方法处理查询并返回最相关的客人信息。
步骤 3:将工具与 Alfred 集成
最后,让我们创建智能体并为其配备自定义工具,将所有内容整合在一起:
from smolagents import CodeAgent, InferenceClientModel
# 初始化 Hugging Face 模型
model = InferenceClientModel()
# 使用宾客信息工具创建我们的晚会智能体 Alfred
alfred = CodeAgent(tools=[guest_info_tool], model=model)
# Example query Alfred might receive during the gala
response = alfred.run("Tell me about our guest named 'Lady Ada Lovelace'.")
print("🎩 Alfred's Response:")
print(response)预期输出:
🎩 Alfred's Response:
根据我检索到的信息,艾达·洛夫莱斯夫人是一位受人尊敬的数学家和朋友。她因其在数学和计算机领域的开创性工作而闻名,并因其在查尔斯·巴贝奇的分析机方面的贡献而被誉为第一位计算机程序员。她的电子邮件地址是 ada.lovelace@example.com。这最后一步具体做了什么:
- 我们使用
InferenceClientModel类初始化 Hugging Face 模型 - 我们将智能体 (Alfred) 创建为
CodeAgent,它可以执行 Python 代码来解决问题 - 我们让 Alfred 检索一位名叫“Lady Ada Lovelace”的客人的信息
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
# 初始化 Hugging Face 模型
llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")
# 使用宾客信息工具创建我们的晚会智能体 Alfred
alfred = AgentWorkflow.from_tools_or_functions(
[guest_info_tool],
llm=llm,
)
# Example query Alfred might receive during the gala
response = await alfred.run("Tell me about our guest named 'Lady Ada Lovelace'.")
print("🎩 Alfred's Response:")
print(response)预期输出:
🎩 Alfred's Response:
Lady Ada Lovelace 是一位受人尊敬的数学家和朋友,因其在数学和计算领域的开创性工作而闻名。她因参与查尔斯·巴贝奇的分析机研究而被誉为第一位计算机程序员。她的邮箱是 ada.lovelace@example.com。这最后一步具体做了什么:
- 我们使用
HuggingFaceInferenceAPI类初始化 Hugging Face 模型 - 我们将智能体 (Alfred) 创建为
AgentWorkflow,其中包含我们刚刚创建的工具 - 我们请求 Alfred 检索名为“Lady Ada Lovelace”的客人的信息
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage, HumanMessage, AIMessage
from langgraph.prebuilt import ToolNode
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
# 生成聊天界面,包括工具
llm = HuggingFaceEndpoint(
repo_id="Qwen/Qwen2.5-Coder-32B-Instruct",
huggingfacehub_api_token=HUGGINGFACEHUB_API_TOKEN,
)
chat = ChatHuggingFace(llm=llm, verbose=True)
tools = [guest_info_tool]
chat_with_tools = chat.bind_tools(tools)
# 生成 AgentState 和 Agent 图
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
def assistant(state: AgentState):
return {
"messages": [chat_with_tools.invoke(state["messages"])],
}
## 构建流程图
builder = StateGraph(AgentState)
# 定义节点:这些节点完成工作
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))
# 定义边:这些决定了控制流如何移动
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
"assistant",
# If the latest message requires a tool, route to tools
# Otherwise, provide a direct response
tools_condition,
)
builder.add_edge("tools", "assistant")
alfred = builder.compile()
messages = [HumanMessage(content="Tell me about our guest named 'Lady Ada Lovelace'.")]
response = alfred.invoke({"messages": messages})
print("🎩 Alfred's Response:")
print(response['messages'][-1].content)预期输出:
🎩 Alfred's Response:
Lady Ada Lovelace 是一位受人尊敬的数学家和计算机领域的先驱,由于她在查尔斯·巴贝奇的分析机上所做的工作,她经常被誉为第一位计算机程序员。这最后一步具体做了什么:
- 我们使用
HuggingFaceEndpoint类初始化 Hugging Face 模型。我们还生成了一个聊天界面并附加了工具。 - 我们将智能体 (Alfred) 创建为一个
StateGraph,它使用一条边连接两个节点(Assistant和tools)。 - 我们让 Alfred 检索一位名叫“Lady Ada Lovelace”的客人的信息。
互动示例
在晚会上,对话可能像这样进行:
你:“Alfred,那位正在和大使说话的先生是谁?”
Alfred 快速搜索嘉宾数据库 “先生,那是尼古拉·特斯拉博士。他是你大学时代的老朋友。他最近申请了一种新的无线能量传输系统的专利,很乐意和你讨论一下。不过别忘了,他对鸽子很感兴趣,所以这或许会成为一次很好的闲聊。”
{
"name": "Dr. Nikola Tesla",
"relation": "old friend from university days",
"description": "Dr. Nikola Tesla is an old friend from your university days. He's recently patented a new wireless energy transmission system and would be delighted to discuss it with you. Just remember he's passionate about pigeons, so that might make for good small talk.",
"email": "nikola.tesla@gmail.com"
}更进一步
既然 Alfred 可以检索宾客信息,不妨考虑如何增强这个系统:
- 改进检索器,使用更复杂的算法,例如 sentence-transformers
- 实现对话记忆,让 Alfred 记住之前的互动
- 结合网页搜索,获取陌生宾客的最新信息
- 整合多个索引,从经过验证的来源获取更完整的信息
现在,Alfred 已经完全有能力轻松处理宾客的咨询,确保您的晚会成为本世纪最精致、最令人愉悦的盛事!
<提示> 尝试扩展检索工具,使其能够根据每位宾客的兴趣或背景返回对话开场白。您将如何修改该工具来实现这一点?
完成后,在 retriever.py 文件中实现您的宾客检索工具。 </提示>
