使用LangGraph和Qdrant的代理RAG
传统的检索增强生成(RAG)系统遵循一条直接的路径:查询 → 检索 → 生成。当然,这在许多场景下效果很好。但让我们面对现实——当你处理需要多个步骤或整合不同类型信息的复杂查询时,这种线性方法往往会遇到困难。
代理式 RAG 通过引入AI代理,将事情提升到一个新的水平,这些代理可以协调多个检索步骤,并智能地决定如何收集和使用你所需的信息。可以这样理解:在Agentic RAG的工作流程中,RAG只是更大、更多功能工具包中的一个强大工具。
通过将LangGraph的强大状态管理与Qdrant的前沿向量搜索相结合,我们将构建一个不仅能回答问题,还能优雅处理复杂、多步骤信息检索任务的系统。
我们将构建什么
我们正在构建一个AI代理,使用LangGraph来回答关于Hugging Face和Transformers文档的问题。我们的AI代理的核心是LangGraph,它就像管弦乐队中的指挥。它指导各个组件之间的流程——决定何时检索信息,何时执行网络搜索,以及何时生成响应。
组件包括:两个Qdrant向量存储和Brave网络搜索引擎。然而,我们的代理并不只是盲目地遵循一条路径。相反,它会评估每个查询,并决定是访问第一个向量存储、第二个向量存储,还是搜索网络。
这种选择性方法使您的系统能够灵活选择最适合任务的数据源,而不是像传统的RAG那样每次都被锁定在相同的检索过程中。虽然我们不会在本教程中深入探讨查询优化,但您在这里学到的概念为将来添加该功能奠定了坚实的基础。
工作流程

| 步骤 | 描述 |
|---|---|
| 1. 用户输入 | 您首先通过界面(如聊天机器人或网页表单)输入查询或请求。此查询直接发送到AI代理,即操作的核心。 |
| 2. AI代理处理查询 | AI代理分析您的查询,弄清楚您在问什么,以及哪些工具或数据源最能回答您的问题。 |
| 3. 工具选择 | 基于其分析,AI代理选择适合的工具。您的数据分布在两个向量数据库中,根据查询的不同,它会选择合适的一个。对于需要实时或外部网络数据的查询,代理会使用由BraveSearchAPI驱动的网络搜索工具。 |
| 4. 查询执行 | AI 代理随后将其选择的工具投入使用: - RAG 工具 1 查询向量数据库 1。 - RAG 工具 2 查询向量数据库 2。 - 网络搜索工具 使用搜索 API 深入互联网。 |
| 5. 数据检索 | 结果如下: - 向量数据库1和2返回与您的查询最相关的文档。 - 网络搜索工具提供最新的或外部信息。 |
| 6. 响应生成 | 使用文本生成模型(如GPT),AI代理根据您的查询生成详细且准确的响应。 |
| 7. 用户响应 | 经过打磨的响应通过接口发送回给您,准备使用。 |
堆栈
该架构利用尖端工具来支持高效的Agentic RAG工作流程。以下是其组件和所需技术的简要概述:
- AI 代理: 系统的核心,该代理解析您的查询,选择正确的工具,并整合响应。我们将使用 OpenAI 的 gpt-4o 作为推理引擎,由 LangGraph 无缝管理。
- 嵌入: 查询使用OpenAI的text-embedding-3-small模型转换为向量嵌入。
- 向量数据库:嵌入被存储并用于相似性搜索,Qdrant 作为我们选择的数据库。
- LLM: 响应是使用OpenAI的gpt-4o生成的,确保答案准确且基于上下文。
- 搜索工具: 为了扩展RAG的功能,我们添加了一个由BraveSearchAPI驱动的网络搜索组件,非常适合实时和外部数据检索。
- 工作流管理:整个编排和决策流程都是使用LangGraph构建的,提供了处理复杂工作流所需的灵活性和智能性。
准备好从头开始构建这个系统了吗?让我们开始吧!
实现
在我们深入构建我们的代理之前,让我们先完成所有设置。
导入
以下是所需的关键导入列表:
import os
import json
from typing import Annotated, TypedDict
from dotenv import load_dotenv
from langchain.embeddings import OpenAIEmbeddings
from langgraph import StateGraph, tool, ToolNode, ToolMessage
from langchain.document_loaders import HuggingFaceDatasetLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.llms import ChatOpenAI
from qdrant_client import QdrantClient
from qdrant_client.http.models import VectorParams
from brave_search import BraveSearch
Qdrant 向量数据库设置
我们将使用Qdrant Cloud作为我们的文档嵌入向量存储。以下是设置方法:
| 步骤 | 描述 |
|---|---|
| 1. 创建一个账户 | 如果您还没有账户,请前往Qdrant Cloud并注册。 |
| 2. 设置集群 | 登录您的账户,在仪表板上找到创建新集群按钮。按照提示进行配置: - 选择您偏好的区域。 - 选择免费层进行测试。 |
| 3. 保护您的详细信息 | 一旦您的集群准备就绪,请记录以下详细信息: - 集群URL (例如,https://xxx-xxx-xxx.aws.cloud.qdrant.io) - API密钥 |
请安全保存这些信息以备将来使用!
OpenAI API 配置
您的OpenAI API密钥将用于嵌入生成和语言模型交互。访问OpenAI的平台并注册一个账户。在仪表板的API部分,创建一个新的API密钥。我们将使用text-embedding-3-small模型进行嵌入,并使用GPT-4作为语言模型。
勇敢搜索
为了增强搜索能力,我们将集成Brave搜索。访问勇敢的API并完成他们的API访问请求流程以获取API密钥。此密钥将启用我们代理的网页搜索功能。
为了增加安全性,请将所有API密钥存储在.env文件中。
OPENAI_API_KEY = <your-openai-api-key>
QDRANT_KEY = <your-qdrant-api-key>
QDRANT_URL = <your-qdrant-url>
BRAVE_API_KEY = <your-brave-api-key>
然后加载环境变量:
load_dotenv()
qdrant_key = os.getenv("QDRANT_KEY")
qdrant_url = os.getenv("QDRANT_URL")
brave_key = os.getenv("BRAVE_API_KEY")
文档处理
在我们创建代理之前,我们需要处理和存储文档。我们将使用来自Hugging Face的两个数据集:他们的通用文档和特定于Transformers的文档。
这是我们的文档预处理函数:
def preprocess_dataset(docs_list):
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=700,
chunk_overlap=50,
disallowed_special=()
)
doc_splits = text_splitter.split_documents(docs_list)
return doc_splits
此函数通过将我们的文档分割成可管理的块来处理它们,确保在块边界处通过重叠保留重要的上下文。我们将使用HuggingFaceDatasetLoader将数据集加载到Hugging Face文档中。
hugging_face_doc = HuggingFaceDatasetLoader("m-ric/huggingface_doc","text")
transformers_doc = HuggingFaceDatasetLoader("m-ric/transformers_documentation_en","text")
在这个演示中,我们从数据集中选择了前50个文档,并将它们传递给处理函数。
hf_splits = preprocess_dataset(hugging_face_doc.load()[:number_of_docs])
transformer_splits = preprocess_dataset(transformers_doc.load()[:number_of_docs])
我们的分割已经准备好了。让我们在Qdrant中创建一个集合来存储它们。
定义状态
在LangGraph中,状态指的是在过程或一系列操作执行期间存储在特定点的数据或信息。状态捕获系统需要跟踪的中间或最终结果,以管理和控制任务的流程,
LangGraph 使用基于状态的系统。我们这样定义我们的状态:
class State(TypedDict):
messages: Annotated[list, add_messages]
让我们构建我们的工具。
构建工具
我们的代理配备了三个强大的工具:
- Hugging Face Documentation Retriever
- Transformers Documentation Retriever
- Web Search Tool
让我们首先定义一个检索器,它接收文档和集合名称,然后返回一个检索器。查询使用OpenAIEmbeddings转换为向量。
def create_retriever(collection_name, doc_splits):
vectorstore = QdrantVectorStore.from_documents(
doc_splits,
OpenAIEmbeddings(model="text-embedding-3-small"),
url=qdrant_url,
api_key=qdrant_key,
collection_name=collection_name,
)
return vectorstore.as_retriever()
Hugging Face 文档检索器和 Transformers 文档检索器都使用相同的函数。通过这种设置,为每个工具创建独立的工具变得非常简单。
hf_retriever_tool = create_retriever_tool(
hf_retriever,
"retriever_hugging_face_documentation",
"Search and return information about hugging face documentation, it includes the guide and Python code.",
)
transformer_retriever_tool = create_retriever_tool(
transformer_retriever,
"retriever_transformer",
"Search and return information specifically about transformers library",
)
对于网页搜索,我们使用Brave Search创建了一个简单但有效的工具:
@tool("web_search_tool")
def search_tool(query):
search = BraveSearch.from_api_key(api_key=brave_key, search_kwargs={"count": 3})
return search.run(query)
search_tool 函数利用 BraveSearch API 执行搜索。它接收一个查询,使用 API 密钥检索前 3 个搜索结果,并返回结果。
接下来,我们将设置并将我们的工具与语言模型集成:
tools = [hf_retriever_tool, transformer_retriever_tool, search_tool]
tool_node = ToolNode(tools=tools)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
llm_with_tools = llm.bind_tools(tools)
在这里,ToolNode 类负责处理和协调我们的工具:
class ToolNode:
def __init__(self, tools: list) -> None:
self.tools_by_name = {tool.name: tool for tool in tools}
def __call__(self, inputs: dict):
if messages := inputs.get("messages", []):
message = messages[-1]
else:
raise ValueError("No message found in input")
outputs = []
for tool_call in message.tool_calls:
tool_result = self.tools_by_name[tool_call["name"]].invoke(
tool_call["args"]
)
outputs.append(
ToolMessage(
content=json.dumps(tool_result),
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
)
return {"messages": outputs}
ToolNode 类通过初始化工具列表并将工具名称映射到其对应的函数来处理工具执行。它处理输入字典,提取最后一条消息,并检查来自 LLM 工具调用能力提供者(如 Anthropic、OpenAI 等)的 tool_calls。
路由与决策制定
我们的代理需要确定何时使用工具以及何时结束循环。这个决策由路由函数管理:
def route(state: State):
if isinstance(state, list):
ai_message = state[-1]
elif messages := state.get("messages", []):
ai_message = messages[-1]
else:
raise ValueError(f"No messages found in input state to tool_edge: {state}")
if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
return "tools"
return END
将所有内容整合在一起:图表
最后,我们将构建将所有内容联系在一起的图表:
graph_builder = StateGraph(State)
graph_builder.add_node("agent", agent)
graph_builder.add_node("tools", tool_node)
graph_builder.add_conditional_edges(
"agent",
route,
{"tools": "tools", END: END},
)
graph_builder.add_edge("tools", "agent")
graph_builder.add_edge(START, "agent")
这是图表的样子:

图3:使用LangGraph的代理RAG
运行代理
一切设置完成后,我们可以使用一个简单的函数来运行我们的代理:
def run_agent(user_input: str):
for event in graph.stream({"messages": [("user", user_input)]}):
for value in event.values():
print("Assistant:", value["messages"][-1].content)
现在,您可以开始询问有关Hugging Face和Transformers的问题了!我们的代理将在需要时智能地结合文档中的信息和网络搜索结果。
例如,你可以问:
In the Transformers library, are there any multilingual models?
代理将深入研究Transformers文档,提取有关多语言模型的相关细节,并提供清晰、全面的答案。
以下是响应可能的样子:
Yes, the Transformers library includes several multilingual models. Here are some examples:
BERT Multilingual:
Models like `bert-base-multilingual-uncased` can be used just like monolingual models.
XLM (Cross-lingual Language Model):
Models like `xlm-mlm-ende-1024` (English-German), `xlm-mlm-enfr-1024` (English-French), and others use language embeddings to specify the language used at inference.
M2M100:
Models like `facebook/m2m100_418M` and `facebook/m2m100_1.2B` are used for multilingual translation.
MBart:
Models like `facebook/mbart-large-50-one-to-many-mmt` and `facebook/mbart-large-50-many-to-many-mmt` are used for multilingual machine translation across 50 languages.
These models are designed to handle multiple languages and can be used for tasks like translation, classification, and more.
结论
我们已经成功实现了Agentic RAG。但这仅仅是个开始——你还可以探索更多内容,将你的系统提升到下一个层次。
Agentic RAG 正在改变企业如何将数据源与 AI 连接,从而实现更智能和更动态的交互。在本教程中,您已经学习了如何构建一个 Agentic RAG 系统,该系统将 LangGraph、Qdrant 和网络搜索的强大功能结合到一个无缝的工作流程中。
该系统不仅仅停留在从Hugging Face和Transformers文档中检索相关信息。它还会在需要时智能地回退到网络搜索,确保每个查询都能得到解答。使用Qdrant作为向量数据库的骨干,您可以获得快速、可扩展的语义搜索,即使在处理海量数据集时也能精确检索信息。
要真正掌握这种方法的潜力,为什么不将这些概念应用到您自己的项目中呢?定制我们分享的模板以适应您独特的用例,并为您的业务需求释放Agentic RAG的全部潜力。可能性是无限的。
