本文较长,建议点赞收藏。更多AI大模型应用开发学习视频及资料,在智泊AI。
在构建生产环境就绪 的 RAG 系统时,我们需要的远不止简单的"检索-生成"步骤。一个健壮的 RAG 必须具备数据治理、灵活的检索策略、反幻觉 (Anti-Hallucination) 机制、以及自我优化的规划与执行 (Planning and Execution) 能力。

本文将深入探讨如何结合 LangChain、LangGraph 和 RAGAS 这一套强大的工具链,来搭建一个全栈 RAG 系统。我们将模拟真实世界中的挑战,并提供实用的解决方案。
步骤1:理解 RAG 管道 (Pipeline)
在开始编码之前,我们首先需要对这个复杂的 RAG 系统的核心架构 有一个清晰的认识。我们的系统是一个多步骤的、基于 Agent 的 循环流程,它通过不断地规划、执行、反思来逼近最终答案。
1. 消除偏差:问题匿名化 (Anonymize Question)
- 目标: 防止大型语言模型 (LLM) 依赖其预训练知识中的固有偏差 或背景知识 来回答问题,从而强制它仅依赖检索到的上下文。
- 动作: 系统首先调用
anonymize_question。它会将问题中的特定实体(如"哈利·波特"、"伏地魔")替换为中性的占位符(例如,"人物 X"、"反派 Y")。
2. 制定策略:高层规划 (Planner)
- 目标: 为匿名化后的问题制定一个高层次、多步骤的解决策略。
- 示例: 对于"人物 X 如何击败反派 Y?"这样的问题,规划器可能会制定如下计划:
- 识别人物 X 和反派 Y。
- 定位他们最终的对峙场景。
- 分析人物 X 的具体行动。
- 起草一个完整答案。
3. 工具选择与执行:任务处理层 (Task Handler)
这是 Agent 的核心决策点 。task_handler 根据当前任务和已有信息,选择最合适的工具进行上下文检索 或直接回答。可选的工具包括:
| 工具名称 | 作用 | 适用场景 |
|---|---|---|
chosen_tool_is_retrieve_quotes |
检索特定的引用或对话。 | 寻找精确的证据或关键台词。 |
chosen_tool_is_retrieve_chunks |
检索一般性信息和上下文片段。 | 获取背景信息、事件描述。 |
chosen_tool_is_retrieve_summaries |
检索章节/文档的摘要。 | 快速掌握宏观主题和概要。 |
chosen_tool_is_answer |
当上下文充足时,直接合成答案。 | 证据链已完整构建。 |
4. 自我修正:重规划循环 (Replan Cycle)
在使用任一检索工具(retrieve_book_quotes、retrieve_chunks 或 retrieve_summaries)获取新信息后,系统进入反思阶段:
replan: 它接收新的输入,评估当前的进度和预设的目标,然后决定是否需要更新、扩展 或修正原始计划。- 这个
task_handler->tool->replan的循环会不断重复,直到系统判断can_be_answered_already为真。
5. 最终答案合成与评估
get_final_answer: 一旦退出循环,系统将综合所有检索到的证据和中间推理过程,合成一个完整且证据充分的最终响应。eval_using_RAGAS: 最终且关键的一步。我们使用 RAGAS 框架来评估 答案的性能,主要检查准确性 (Accuracy) 和对源文本的忠实度 (Groundedness)。
步骤2:构建关键技术模块
构建一个复杂的、生产级 RAG 系统是一个迭代和多层级 的过程。它从精细的数据处理 开始,通过逻辑分块 优化检索,利用匿名化 来消除 LLM 偏差,然后通过 LangGraph 实现一个具备规划、反思和自我纠正能力 的 Agent 系统,最终用 RAGAS 保证输出的准确性 和可靠性。
构建 RAG 的基础在于数据的质量和结构。
- 数据清洗 (Cleaning Our Data): 这是任何生产系统的第一步。涉及去除噪声、处理缺失值、格式统一等。
- 分块策略 (Breaking our Data): 我们需要测试不同的分块策略,包括:
- 传统分块: 基于固定字符数或句子的简单分割。
- 逻辑分块: 基于文档结构(如标题、段落、章节)的语义化分割,它能更完整地保留上下文的连贯性。
为了解决高噪音 和检索低效问题,我们引入了子图 (Sub Graph) 架构:
- 子图作用: 通过创建专门的检索子图,我们可以将检索过程聚焦到最相关的信息上,有效过滤掉噪音。
- 创建子图解决 Hallucinations: 一个专门的子图可以专注于事实的验证 和证据的蒸馏 (Distillation) ,确保最终答案是"事实扎根 (Grounded on Facts)"的。
我们利用 LangGraph 来实现 Agent 的规划与执行逻辑。LangGraph 提供了一个强大的框架来可视化 和管理 多步、有状态的 Agent 流程。这使得我们能够清晰地看到 task_handler、replan 等节点如何形成一个可回溯的循环 ,极大地提高了复杂流程的可调试性 和可解释性。
一个生产级系统必须能够量化 其性能。我们采用 RAGAS (RAG Assessment) 框架进行评估。
| RAGAS 关键指标 | 关注点 | 目标 |
|---|---|---|
| 忠实度 (Faithfulness) | 衡量生成答案的内容 是否完全基于检索到的上下文。 | 最小化幻觉 (Hallucination)。 |
| 相关性 (Relevancy) | 衡量检索到的上下文是否与原始问题高度相关。 | 优化检索器性能。 |
| 上下文召回率 (Context Recall) | 衡量检索到的上下文是否充分完整地包含了生成答案所需的所有事实。 | 确保检索到的信息是全面且可靠的。 |
步骤3:为生产级 RAG 系统打下环境基础
任何生产级应用的首要任务都是安全地管理敏感信息,主要是 API 密钥。我们通过环境变量来加载这些密钥,避免将其硬编码到代码中。
| 组件类别 | 作用 | 选型考量 |
|---|---|---|
| 大语言模型 (LLM) | 负责理解 问题、规划 步骤、重写 查询、合成最终答案。 | 性能、成本、响应速度 。 |
| 向量编码器 (Embedding) | 负责将文本转化为向量,以进行语义搜索和检索。 | 准确性、模型尺寸、运行效率 。 |
| 向量数据库 (Vector DB) & 检索 | 负责存储 和高效检索向量化的数据块 (Chunks)。 | 检索速度、扩展性 。 |
| 文本全文检索 | 辅助向量检索,用于精确匹配 或关键词过滤。 | 精确性、过滤能力 。 |
步骤4:传统与逻辑分块策略
在对数据进行预处理和清洗 之前,最关键的一步是定义我们的分块策略------即如何将一份长文档切割成可被检索的小单元。

逻辑分块 旨在根据文档本身的结构和语义 进行切割,确保每个块都包含一个完整、有意义的概念。在我们的故事书中, "章节 (Chapters)" 和 "引述 (Quotes)" 是最理想的逻辑断点。
在小说中,引述 往往浓缩了关键对话、人物关系或重要情节,是次于章节的重要信息单元。在金融文档中,这可能对应于表格 或财务报表等关键数据块。
逻辑分块的优势:
- 语义完整性: 保证了每个块(章节或引述)都具有清晰、完整的语义,极大地提高了检索的相关性 (Relevancy)。
- 定制化检索: 允许我们在 RAG 管道中创建专门的检索器,例如,一个专门用于检索关键对话的工具(如在 Agent 系统中)。
步骤5:数据清洗(为 RAG 系统准备纯净的文本)
原始的 PDF 提取文本中往往充斥着各种格式错误,如多余的空格、制表符和不当的换行符。这些"噪音"会显著增加 Token 消耗 、降低 Embedding 质量,并最终影响 LLM 的推理效率和准确性。

尽管我们清除了制表符,但文本中仍存在大量不必要的换行符 (\n) 和多重空格。这些字符不仅增加了 Token 数量,还可能导致单词在不同的行之间被错误地分割。
为什么这个分析很重要?
- LLM 上下文窗口限制: 大语言模型的 Context Window (上下文窗口) 长度是固定的。如果我们的检索单元(章节)过长,超过了 LLM 的处理能力,LLM 就可能无法处理全部信息。
- RAG 系统的处理粒度: 尽管在我们的案例中(章节字数远低于大多数主流 LLM 的上下文限制),这些数据看起来安全。但在处理极长文档(如法律文件或技术手册)时,如果章节字数过高,我们可能需要对章节 进行进一步的分块 (Sub-Chunking) ,或采用摘要 (Summarization) 策略,以确保信息能被 LLM 完全接收和有效推理。
步骤6:数据向量化(构建高效检索的基础)
下一步是将我们的文本数据转化为机器可理解的数学形式------向量 (Vectors) 。这是 RAG (Retrieval-Augmented Generation) 系统的核心,它使得我们可以进行语义相似性搜索,而不是传统的关键词匹配。
在环境设置阶段,我们已经确定使用 ML2 BERT 模型 (m2_bert_80M_32K) 进行向量编码。 这款模型具备 32k 的上下文窗口 (虽然编码器主要关注输入长度,但这个大窗口显示了其处理长文本的能力)和相对高效的性能,非常适合在 RAG 设置中对文档进行密集向量表示。
为了高效地存储和检索这些高维向量,我们选择了 FAISS (Facebook AI Similarity Search) 。 它是 Meta 开源的库,专门用于高效的相似性搜索 和聚类。许多主流云端向量数据库(如 Qdrant、Pinecone)也常常集成或支持 FAISS 的底层技术,以提升大规模数据的检索性能。
将不同类型的分块(传统块、章节摘要、特定引述)存储在独立的向量数据库 中,使我们的 RAG 系统能够实现多策略检索。例如:
- 回答需要精确证据 的问题时,可以使用
quotes_vectorstore。 - 回答需要宏观背景 的问题时,可以使用
chapter_summaries_vectorstore。 - 回答需要详细上下文 的问题时,可以使用
book_splits_vectorstore。
步骤7:多策略上下文检索器
现在进入 RAG 管道的核心环节:构建检索器 (Retriever) 。检索器的目标是从我们多样化的数据集中(传统分块、章节摘要、引述)高效且准确地抓取与用户问题最相关的上下文。
我们的第一个核心 RAG 函数 retrieve_context_per_question,将负责协调这三种检索策略,并整合所有获取到的信息。
python
# 函数的核心逻辑(流程简述)
def retrieve_context_per_question(state):
# 1. 获取问题 question = state["question"]
# 2. 调用 book_chunks_retriever 获取详细 context
# 3. 调用 chapter_summaries_retriever 获取 summary_context (包含章节号)
# 4. 调用 book_quotes_retriever 获取 quotes_context
# 5. 拼接 all_contexts = context + summary_context + quotes_context
# 6. 清洗转义字符
# 7. 返回 {"context": all_contexts, "question": question}
函数返回一个包含整合上下文 和原始问题 的新状态字典,准备传递给 RAG 管道的下一个步骤,例如查询重写 或答案生成。
通过这种多策略的、精细化 Top-K 设置的检索方法,我们确保了 LLM 在生成答案时能获得最多样化 、最相关 且具备高可信度的证据链。
步骤8:使用 LLM 过滤无关信息
即使通过多策略检索器获取了文档,向量相似性搜索仍可能返回一些与核心问题相关度较低 的噪音信息。这些无关内容会占用 LLM 的上下文窗口 (Context Window) ,稀释真正重要的事实,甚至可能分散 LLM 的注意力,导致生成质量下降(即著名的"针在干草堆"问题)。
为了解决这个问题,我们在检索步骤后引入了一个关键的后处理环节:使用 LLM 作为精炼过滤器 (Refinement Filter) ,去除无关信息,只保留对回答问题有价值的内容。
我们首先定义一个指令清晰的 Prompt 模板,来指导 LLM 完成内容过滤任务。这个 Prompt 严格限制了 LLM 的行为:
- 输入: 用户的查询 (
{query}) 和检索到的所有文档内容 ({retrieved_documents})。 - 核心指令: 过滤掉所有与查询不相关的信息。
步骤9:查询重写器(优化检索的"语义桥梁")
在 RAG 管道中,用户最初的提问(Query)往往不够精确 或过于简略。如果直接使用这样的查询进行向量检索,可能会导致:
- 检索不准确: 无法捕获查询的完整语义意图,返回不相关的文档。
- 内容不足: 检索结果无法提供足够的信息来支撑高质量的回答。
与上下文过滤器类似,我们首先使用 Pydantic 定义了 LLM 输出的结构,以确保重写后的结果是稳定、可控的 JSON 格式。
| 字段 | 类型 | 描述 | 目的 |
|---|---|---|---|
rewritten_question |
字符串 | 经过优化的、用于向量检索的改进问题。 | 核心输出 :用于替代原始查询进行检索。 |
explanation |
字符串 | 对重写后问题的解释和理由。 | 可解释性 :便于调试和理解 LLM 的推理过程。 |
步骤10:Chain-of-Thought (CoT) 推理(增强 LLM 的答案生成能力)
CoT 推理的一个挑战是确保 LLM 每次都能以一致的、高质量的逻辑风格 进行推理。我们通过 Few-Shot CoT 方法来解决这个问题:在 Prompt 模板中提供多个推理范例 (Examples),展示所需的思维链条和最终答案结构。
我们提供的范例展示了三种关键推理场景:
- 逻辑比较 (Example 1): 涉及关系推理和排序(Mary > Tom > Jane)。
- 信息聚合 (Example 2): 涉及从上下文中提取和总结多个事实。
- 信息缺失判断 (Example 3): 明确指出当上下文中不包含足够信息时,LLM 应该回答"没有足够的上下文"。
当 LLM 接收到用户的查询 和精炼后的上下文 后,它将被要求先输出一个名为 Reasoning 的内部思考过程,最后再输出最终的 Final Answer。
步骤11:相关性与事实基础检查(RAG 管道的最终校准)
在 RAG 管道中,检索 、过滤 和 CoT 推理 是核心。然而,为了确保系统的高准确度 (High Accuracy) 和可信度 (Trustworthiness) ,我们需要对检索到的信息和生成的答案进行最终的双重验证:
- 传统相关性检查: 检查文档本身是否与查询相关。
- 事实基础检查 (Ground Truth): 检查 LLM 生成的答案是否完全基于提供的上下文,防止幻觉 (Hallucination)。
步骤12:使用 LangGraph 可视化我们的 RAG 管道
我们的 RAG 解决方案是一个有向图 (Directed Graph) ,它定义了一个基于条件逻辑 和循环反馈 的工作流。这个工作流不是线性的,而是具备自适应能力的 Agentic RAG 模式。
我们将之前实现的五个关键功能(retrieve、filter、rewrite、answer)作为图中的节点 (Nodes) 添加到工作流中。每个节点都代表一个单一、独立的处理步骤。
| 节点名称 | 对应功能 | 作用 |
|---|---|---|
retrieve |
retrieve_context_per_question |
从向量存储中获取多源上下文。 |
filter |
keep_only_relevant_content |
使用 LLM 过滤上下文中的无关信息。 |
rewrite |
rewrite_question |
使用 LLM 优化查询,以进行更好的检索。 |
answer |
answer_question_from_context |
使用 CoT 推理生成答案。 |
通过 LangGraph 的 draw_mermaid_png 方法,我们可以将上述逻辑清晰地呈现为图形,直观地展示了 LangGraph 如何创建了一个具备推理、决策和自优化能力的 Agentic RAG 管道。
这个可视化图清晰地体现了流程中的两个核心循环:
- 检索优化循环 (
filter``rewrite``retrieve): 当第一次检索的上下文质量不足时,系统会自动优化查询并进行二次检索。 - 答案自修复循环 (
answer``answer或rewrite): 当生成的答案存在幻觉时,LLM 尝试重新生成;当答案信息不足时,系统会尝试优化查询,从头开始获取更多上下文。
步骤13:子图架构与内容蒸馏基础检查
在复杂的 RAG 应用场景中,尤其是在处理需要多步骤推理或任务分解的查询时,线性的 RAG 流程往往力不从心。为了构建更模块化 (Modular) 、更健壮 (Robust) 和可扩展 (Scalable) 的解决方案,我们引入了 子图架构 (Sub-Graph Approach) 和内容蒸馏 (Distillation Grounding) 机制。
子图方法的核心思想是将一个复杂的工作流分解为多个独立的、功能专注的子图。
- 传统 RAG: 单一的大图包含所有逻辑,维护和调试复杂。
- 子图架构: 每个核心功能(如查询重写、文档过滤、事实核查)都可以被封装为一个独立的 LangGraph 子图 。这些子图拥有自己的输入、输出和内部逻辑,它们像乐高积木一样,可以被主图调用 (Invoke) 或链接 (Chain) 起来。
通过 LangGraph 子图 (Sub-Graph) 方法,我们可以为每个数据源定制一个专注的 、自校验的 检索工作流。这不仅提高了模块化程度,还确保了每个源的输出内容都经过了严格的事实基础 (Grounding) 检查。
我们定义了一个高阶函数 build_retrieval_workflow,它能够为任何给定的检索函数(retrieve_fn)构建一个自循环校验的子图。
这个子图的核心是内容蒸馏 和事实基础检查的循环机制:
- 节点 1:检索 (
node_name) - 获取原始上下文。 - 节点 2:过滤 (
keep_only_relevant_content) - 使用 LLM(如 Llama 3.3)将原始上下文提炼为relevant_context。 - 条件边缘:事实基础检查 (
is_distilled_content_grounded_on_content) - 这一步是关键的质量门 (Quality Gate)。它检查:
- 如果 提炼后的内容完全基于 原始上下文 (
"grounded on the original context") 结束 (END),输出可靠的提炼内容。 - 如果 提炼过程中引入了新的信息 (幻觉) 循环回到过滤节点 (
"keep_only_relevant_content"),要求 LLM 重新进行提炼,直到内容符合事实基础要求。
步骤14:构建抗幻觉子图(提升 RAG 答案可信度)
为了在最终的答案生成阶段引入一个严格的质量控制机制 ,我们创建了一个专门的 LangGraph 子图 ,其核心目标是:确保生成的答案完全基于提供的上下文。

我们构建了一个专用于答案生成的子图。这个图结构非常简洁,但却包含了一个强大的自修复循环:
- 节点:
answer(调用answer_question_from_context函数生成答案)。 - 入口点: 流程始于
answer节点。 - 条件边缘: 答案生成后,立即通过
is_answer_grounded_on_context函数进行校验:
- 如果 校验结果为
"grounded on context"结束 (END),答案可靠。 - 如果 校验结果为
"hallucination"循环回到answer节点 ,要求 LLM 在相同的上下文中重新生成答案。
步骤15:RAGAS最终编译、测试与评估
编译后的 RAG 管道不再是简单的线性流程,而是一个高度复杂的、自适应的图结构:
- 去偏见 (Anonymize): 匿名化用户查询,防止 LLM 规划时产生先验知识偏见。
- 规划 (Plan): 基于匿名查询创建高层执行策略。
- 反匿名化 (De-anonymize): 将实体重新映射回计划,使其具备上下文。
- 分解 (Break Down): 将高层计划细化为可执行的工具任务。
- 执行 (Execute/Task Handler): 根据当前任务选择并调用对应的检索子图(Quotes, Chunks, Summaries)。
- 重规划 (Replanning): 检索后,LLM 评估新获取的上下文,动态调整剩余计划。
- 答案生成 (Answer): 当有足够信息时,调用抗幻觉子图,生成最终答案。
- 结束 (END): 答案通过事实校验后,流程终止。
测试完成后,我们使用 RAGAS (一个用于评估 RAG 系统的先进框架)对管道进行客观量化评估。
我们选择了五个关键指标来全面评估 RAG 系统的质量:
- Answer Correctness (答案正确性): 答案是否事实正确。
- Faithfulness (忠实度): 答案是否只基于提供的上下文(防幻觉)。
- Answer Relevancy (答案相关性): 答案是否精确回答了问题。
- Context Recall (上下文召回率): 有用信息是否都被检索到。
- Answer Similarity (答案相似度): 答案与标准答案的语义相似程度。
学习资源推荐
如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。
本文较长,建议点赞收藏。更多AI大模型应用开发学习视频及资料,在智泊AI。