AI实践(5)检索增强(RAG)
Author: Once Day Date: 2026年3月2日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦...
漫漫长路,有人对你微笑过嘛...
全系列文章可参考专栏: AI实践成长_Once-Day的博客-CSDN博客
参考文章:
- Prompt Engineering Guide
- Documentation - Claude API Docs
- OpenAI for developers
- 检索增强生成 (RAG) | Prompt Engineering Guide
- Build a RAG agent with LangChain - Docs by LangChain
- 一文读懂:大模型RAG(检索增强生成)含高级方法
- 2026 年 RAG 技术最新进展与落地实践指南 - 个人文章 - SegmentFault 思否
- RAG 是什么?一文带你看懂 AI 的"外挂知识库"-阿里云开发者社区
- Llama4本地RAG知识库搭建2026:让大模型读懂你的文档
- LlamaIndex - LlamaIndex 框架
文章目录
- AI实践(5)检索增强(RAG)
-
-
-
- [1. RAG 介绍](#1. RAG 介绍)
- [2. Embedding 介绍](#2. Embedding 介绍)
- [3. LangChain 和 LlamaIndex 介绍](#3. LangChain 和 LlamaIndex 介绍)
-
- [3.1 LangChain](#3.1 LangChain)
- [3.2 LlamaIndex](#3.2 LlamaIndex)
- [4. RAG 流程介绍](#4. RAG 流程介绍)
-
- [4.1 索引阶段](#4.1 索引阶段)
- [4.2 检索阶段](#4.2 检索阶段)
- [4.3 生成阶段](#4.3 生成阶段)
- [5. LlamaIndex RAG 实践](#5. LlamaIndex RAG 实践)
-
- [5.1 环境准备](#5.1 环境准备)
- [5.2 文档加载](#5.2 文档加载)
- [5.3 索引构建](#5.3 索引构建)
- [5.4 查询引擎](#5.4 查询引擎)
- [6. LangChain RAG 实践](#6. LangChain RAG 实践)
-
- [6.1 环境准备](#6.1 环境准备)
- [6.2 文档加载与切分](#6.2 文档加载与切分)
- [6.3 向量索引构建](#6.3 向量索引构建)
- [6.4 检索与生成](#6.4 检索与生成)
-
-
1. RAG 介绍
RAG(Retrieval Augmented Generation,检索增强生成)最早由 Meta AI 的研究团队于 2020 年提出,旨在解决大语言模型在知识密集型任务上的不足。其核心思路是将信息检索组件与文本生成模型相结合------在模型生成回答之前,先从外部知识库中检索出与问题相关的文档片段,再将这些片段作为上下文连同用户的原始提问一起送入 LLM,由模型综合这些信息产出最终的回答。
这一设计带来的直接好处是,当事实信息发生变化时,只需更新外部知识库中的文档,而无需对模型本身进行重新训练或微调。众所周知,LLM 的参数化知识在训练完成后便处于静态冻结状态,其训练数据存在截止日期,无法感知此后发生的新事件。RAG 通过引入检索环节,使语言模型能够在推理时动态获取最新信息,从而显著缓解了知识过时的问题。
除了时效性之外,RAG 还针对性地解决了 LLM 的另外两个痛点。
- 一是幻觉(
Hallucination),即模型在缺乏足够依据时倾向于自行编造看似合理但实际错误的内容; - 二是缺乏领域私有知识,企业内部文档、专业数据库等内容从未出现在模型的训练语料中,模型自然无法回答相关问题。
RAG 通过将真实文档作为生成依据注入上下文,既约束了模型的输出范围,也使其具备了回答私域问题的能力。同时,由于检索到的文档可以作为引用来源一并返回给用户,回答的可追溯性和可信度也得到了提升。
自 2020 年首次提出以来,RAG 的技术形态经历了持续演进。早期的 Naive RAG 采用较为简单的"检索-拼接-生成"流水线,随后社区发展出 Advanced RAG,在查询改写、检索重排序、上下文压缩等环节做了大量优化。到 2025 年前后,更多新范式开始涌现:Graph-RAG 引入知识图谱来增强文档间的关系推理能力;Agentic RAG 则借助 Agent 架构,让模型能够自主决定何时检索、检索什么、是否需要多轮检索,使整个流程更加灵活和智能。下图展示了 RAG 的基本工作原理:
相似度匹配
用户提问
检索器 Retriever
外部知识库
相关文档片段
Prompt 组装
LLM 生成
最终回答 + 引用来源
从应用场景来看,RAG 已广泛落地于智能客服、企业知识问答、法律文书检索、医疗辅助诊断等领域。相比纯粹依赖模型参数记忆的方案,RAG 在准确性、可控性和部署成本之间取得了较好的平衡,成为当前 LLM 应用工程中最主流的架构模式之一。
2. Embedding 介绍
在 RAG 的检索环节中,系统需要从大量文档片段里快速找出与用户提问语义最相近的内容,这一过程的基础便是 Embedding(向量嵌入)技术。简单来说,Embedding 是一种将文字序列------无论是单个词、一句话还是一整段文档------转换为固定维度数值向量的方法。经过这一转换后,原本只能被人类理解的自然语言文本就变成了计算机可以直接运算的数学对象。
Embedding 模型的训练目标可以概括为一个核心原则:语义相近的文本在向量空间中应当彼此靠近,语义无关的文本则应当彼此远离。例如"如何重置密码"和"忘记密码怎么办"这两个句子虽然措辞不同,但经过良好训练的 Embedding 模型会将它们映射到向量空间中非常接近的位置;而"今天天气不错"则会被映射到一个距离较远的区域。这种特性使得通过计算向量间的距离(常用余弦相似度或欧氏距离)就能量化文本之间的语义关联程度,从而实现高效的语义检索。
目前主流的 Embedding 模型大多基于 Transformer 架构。根据应用场景的不同,可以将它们大致分为以下几类:
| 类别 | 代表模型 | 输出维度 | 特点 |
|---|---|---|---|
| 通用开源模型 | bge-large、e5-large、GTE |
768~1024 | 免费部署,社区活跃 |
| 商业 API | OpenAI text-embedding-3-small/large |
1536~3072 | 开箱即用,按调用量计费 |
| 多语言模型 | multilingual-e5、m3e |
768 | 支持中文等多语种场景 |
在实际工程中,Embedding 维度的选择需要在精度与性能之间做出权衡。维度越高,向量能够承载的语义信息越丰富,检索精度通常也越好;但相应地,存储开销和检索计算量都会随之增大。对于大多数中小规模的 RAG 应用,768 或 1024 维的模型已经能够提供足够的效果。
值得注意的是,Embedding 模型的质量直接决定了 RAG 系统的检索上限------如果检索阶段未能召回正确的文档片段,后续的 LLM 生成环节再强也无法给出准确答案。因此在构建 RAG 系统时,选择一个与目标语言和领域匹配的 Embedding 模型,并在必要时使用领域数据对其进行微调,往往是提升整体效果最直接的手段。生成的向量通常会被存入专门的向量数据库(如 Milvus、FAISS、Chroma 等)中进行索引管理,以支撑后续的高效近似最近邻检索。
3. LangChain 和 LlamaIndex 介绍
在 RAG 的工程实践中,从零搭建整套流水线涉及文档解析、文本切分、向量化、存储、检索、Prompt 组装、模型调用等诸多环节。为了降低开发门槛并提升可复用性,社区涌现出多个开源框架,其中最具代表性的当属 LangChain 和 LlamaIndex。两者的设计哲学和侧重点有所不同,理解它们各自的定位有助于在实际项目中做出合理选择。
3.1 LangChain
LangChain 是一个通用的、模块化的 LLM 应用开发框架,RAG 只是其众多能力之一。它的设计理念更接近于提供一套构建各类 LLM 应用所需的"积木",开发者可以根据需求自由组合。其核心模块包括:
Model I/O:统一封装了对OpenAI、Anthropic、本地模型等各类LLM和Embedding模型的调用接口,屏蔽了不同供应商之间的 API 差异。Chains(链):将多个处理步骤串联为一条流水线,例如"检索 → 拼装 Prompt → 调用 LLM → 解析输出"这样的典型RAG链路。Agents(智能体):赋予LLM自主决策能力,使其能够根据任务需要动态选择调用搜索引擎、计算器、外部 API 等工具。Memory(记忆):为对话系统提供短期和长期记忆管理,使多轮对话具备上下文连贯性。Retrieval(检索):提供文档加载器、文本切分器、向量存储和检索器等模块,支持完整的RAG管道构建。
由于覆盖面广,LangChain 尤其适合那些不仅限于问答检索、还需要结合 Agent、工具调用、多链编排等复杂逻辑的应用场景。不过也正因其通用性,在纯 RAG 场景下的配置项和抽象层次相对较多,初学者可能需要一定的学习成本来熟悉其组件体系。
3.2 LlamaIndex
LlamaIndex(原名 GPT Index)的定位则更为聚焦------它是一个专注于数据索引与检索的框架,核心使命是让 LLM 更好地连接和利用外部数据,因此与 RAG 的关系最为紧密。其核心能力包括:
Data Connectors(数据连接器):内置了对PDF、数据库、API、Notion、Slack等上百种数据源的加载支持,大幅简化了数据接入流程。Index(索引):提供向量索引、树索引、关键词索引、知识图谱索引等多种数据组织结构,可以针对不同查询模式选用最合适的索引策略。Query Engine(查询引擎):封装了从检索到生成的完整RAG管道,并支持子问题分解、递归检索、混合检索等多种高级检索策略。- 高级
RAG优化:内置了重排序(Reranking)、查询改写、父文档检索、句子窗口检索等进阶技术,开箱即可使用。
相较于 LangChain 的广度优先,LlamaIndex 在 RAG 这一垂直方向上做得更深。对于以文档问答和知识检索为主要目标的项目,LlamaIndex 往往能够以更少的代码量和更简洁的 API 完成搭建。以下表格对两个框架的关键差异做了简要对比:
| 维度 | LangChain |
LlamaIndex |
|---|---|---|
| 定位 | 通用 LLM 应用框架 | 数据索引与检索专用框架 |
| RAG 支持 | 作为子模块提供 | 核心功能,深度优化 |
| Agent 能力 | 完善,支持多种 Agent 范式 | 有基础支持,非主要方向 |
| 索引类型 | 主要依赖向量索引 | 向量、树、图谱等多种索引 |
| 上手难度 | 概念较多,灵活但学习曲线稍陡 | 聚焦 RAG,API 简洁直观 |
| 适用场景 | 复杂 LLM 应用、多工具编排 | 文档问答、知识库检索 |
在实际项目中,两个框架并非互斥关系。不少团队会将 LlamaIndex 作为数据索引和检索层,同时使用 LangChain 来编排上层的 Agent 逻辑和多链调用,两者配合使用可以兼顾检索深度与应用灵活度。
4. RAG 流程介绍
RAG 的完整工作流程可以划分为三个核心阶段:索引(Indexing) 、检索(Retrieval) 和生成(Generation)。索引阶段属于离线预处理,通常只需执行一次(或在知识库更新时增量执行);检索与生成阶段则在每次用户提问时实时触发。三个阶段环环相扣,任何一个环节的质量短板都会直接影响最终回答的准确性。
4.1 索引阶段
文档索引是整个 RAG 流程的基础环节,其目标是将原始的非结构化文档转化为可被高效检索的向量表示。这一阶段通常包含以下三个步骤:
原始文档
PDF/Word/Markdown/Excel
文档解析与加载
文本切分
Chunking
Embedding 模型
向量化
向量数据库
存储与索引
首先是文档加载与解析。原始文档的格式多种多样,包括 PDF、Word、Excel、Markdown、网页 HTML 等,需要借助对应的解析工具将其内容提取为纯文本。这一步看似简单,实际上常常是工程中的难点所在------PDF 中的表格、图片、多栏排版等复杂布局都可能导致解析结果出现错位或信息丢失。
接下来是文本切分(Chunking)。由于 Embedding 模型和 LLM 的上下文窗口都存在长度限制,且过长的文本段落在语义表达上容易模糊焦点,因此需要将完整文档按照一定策略切分为较小的文本块(chunk)。常见的切分策略包括按固定字符数切分、按段落或句子边界切分、按文档的逻辑结构(标题层级)切分等。切分时通常还会设置一定的重叠窗口(overlap),确保相邻 chunk 之间的语义连贯性不被截断。chunk 大小的选择需要权衡:过小会丢失上下文信息,过大则会引入过多噪声并降低检索精度,实践中 256~1024 个 token 是较常见的取值 范围。
最后,将每个 chunk 送入 Embedding 模型转换为固定维度的向量,并将向量连同原始文本一起写入向量数据库(如 Chroma、FAISS、Milvus 等)。向量数据库会对这些向量建立索引结构(如 HNSW、IVF 等),以支撑后续的高效近似最近邻搜索。
4.2 检索阶段
当用户发起提问时,系统首先使用与索引阶段相同的 Embedding 模型将用户问题转换为查询向量。这里必须保证查询端和索引端使用同一个模型,否则两者的向量空间不一致,相似度计算将失去意义。
随后,向量数据库通过高效的数学运算(如余弦相似度、欧氏距离或内积)在已索引的全部 chunk 向量中搜索与查询向量最接近的若干条结果,即 Top-K 检索。K 值的大小决定了召回的文档片段数量:K 太小可能遗漏关键信息,K 太大则会引入不相关内容并增加后续 LLM 处理的 token 消耗。在进阶实践中,还可以在初步召回之后引入重排序 (Reranking)模型,对候选结果进行二次精排,以进一步提升检索质量。
4.3 生成阶段
检索完成后,系统将检索到的文档片段与用户的原始问题一起组装成结构化的 Prompt,送入 LLM 进行生成。一个典型的 Prompt 模板结构如下:
markdown
你是一个知识助手,请根据以下参考资料回答用户的问题。
如果参考资料中没有相关信息,请如实告知。
【参考资料】
{检索到的文档片段1}
{检索到的文档片段2}
...
【用户问题】
{用户的原始提问}
LLM 在接收到这样的 Prompt 后,会基于提供的参考资料进行理解和推理,生成一段有据可依的回答。由于回答的生成被约束在检索结果的范围之内,相比直接向 LLM 提问,幻觉现象得到了显著抑制。完整的检索-生成流程可以总结如下:

图片引用自:RAG 是什么?一文带你看懂 AI 的"外挂知识库"-阿里云开发者社区
需要指出的是,以上描述的是最基础的单轮 RAG 流程。在实际生产环境中,往往还需要在各环节加入额外的优化措施,例如在检索前对用户查询进行改写或扩展以提升召回率,对检索结果进行上下文压缩以减少冗余 token,或者引入多轮迭代检索来应对复杂的多跳推理问题。这些进阶策略是从基础 RAG 迈向生产级 RAG 系统的关键所在。
5. LlamaIndex RAG 实践
5.1 环境准备
首先安装所需的 Python 依赖包。LlamaIndex 从 0.10 版本开始采用了模块化拆分的包结构,核心包与各集成组件分别独立发布:
bash
pip install llama-index
pip install llama-index-embeddings-huggingface
LlamaIndex 支持使用免费开源的 Embedding 模型,既省钱又能保证数据隐私,这里选择从 Hugging Face 下载 BAAI/bge-small-zh-v1.5,它比较小,只有 95 MB。
bash
pip install huggingface_hub
huggingface-cli download BAAI/bge-small-zh-v1.5 --local-dir ./bge-small-zh-v1.5
安装完成后,需要配置第三方 API 的密钥和接入地址。由于使用的是兼容 OpenAI 协议的第三方服务,除了 API Key 之外还需要指定自定义的 base_url。可以通过环境变量进行全局设置:
bash
export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxx"
export OPENAI_API_BASE="https://your-third-party-provider.com/v1"
也可以在代码中显式传入,这种方式在多环境部署时更为灵活。以下代码完成 LLM 和 Embedding 模型的初始化,并将其注册为 LlamaIndex 的全局默认配置:
python
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
API_KEY = "sk-xxxxxxxxxxxxxxxx"
API_BASE = "https://your-third-party-provider.com/v1"
llm = OpenAI(
model="gpt-5.4",
api_key=API_KEY,
api_base=API_BASE,
temperature=0.1,
)
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-zh-v1.5")
# 注册为全局默认,后续无需重复传入
Settings.llm = llm
Settings.embed_model = embed_model
Settings 是 LlamaIndex 0.10+ 引入的全局配置对象,替代了早期版本中的 ServiceContext。通过在入口处统一设置 llm 和 embed_model,后续所有的索引构建和查询操作都会自动使用这两个模型实例。
5.2 文档加载
LlamaIndex 提供了丰富的文档加载器。对于本地文件,最常用的是 SimpleDirectoryReader,它能够自动识别指定目录下的 txt、md、pdf 等常见格式并完成解析:
python
from llama_index.core import SimpleDirectoryReader
# 加载 ./data 目录下的所有文档
documents = SimpleDirectoryReader(input_dir="./data").load_data()
print(f"共加载 {len(documents)} 个文档")
for doc in documents:
print(f" - {doc.metadata.get('file_name', 'unknown')}, "
f"长度: {len(doc.text)} 字符")
假设 ./data 目录下存放了若干份 Markdown 格式的技术文档,load_data() 方法会将每个文件解析为一个 Document 对象,其中 text 属性保存了提取的纯文本内容,metadata 字典则记录了文件名、文件路径等元信息。如果只需要加载单个文件,也可以通过 input_files 参数指定:
python
documents = SimpleDirectoryReader(
input_files=["./data/rag_intro.md", "./data/llm_guide.md"]
).load_data()
5.3 索引构建
文档加载完成后,下一步是构建向量索引。VectorStoreIndex 是 LlamaIndex 中最核心的索引类型,它会自动完成文本切分、Embedding 向量化以及索引存储这三个步骤:
python
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_documents(documents)
这一行代码背后实际执行了完整的索引流水线:首先使用内置的 SentenceSplitter 将文档按句子边界切分为 chunk(默认大小为 1024 个 token,重叠 200 个 token);然后对每个 chunk 调用前面配置的 Embedding 模型生成向量;最后将向量存入内存中的默认向量存储。如果需要自定义切分参数,可以通过 Settings 进行调整:
python
from llama_index.core.node_parser import SentenceSplitter
Settings.text_splitter = SentenceSplitter(
chunk_size=512,
chunk_overlap=64,
)
# 使用自定义切分参数构建索引
index = VectorStoreIndex.from_documents(documents)
在实际应用中,如果文档规模较大或需要在服务重启后复用已有索引,可以将索引持久化到磁盘,避免每次启动都重新进行向量化计算:
python
# 持久化存储
index.storage_context.persist(persist_dir="./storage")
# 后续启动时从磁盘加载,无需重新 Embedding
from llama_index.core import StorageContext, load_index_from_storage
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
5.4 查询引擎
索引构建完毕后,通过 as_query_engine() 方法即可获得一个开箱即用的查询引擎,它封装了从检索到生成的完整 RAG 管道:
python
query_engine = index.as_query_engine(
similarity_top_k=3, # 检索最相关的 3 个文档片段
)
response = query_engine.query("什么是 RAG?它解决了哪些问题?")
print(response)
similarity_top_k 参数控制检索阶段返回的文档片段数量。查询引擎在收到问题后,会依次完成以下操作:将问题向量化 → 从索引中检索 Top-K 相似片段 → 将片段与问题组装为 Prompt → 调用 LLM 生成回答。返回的 response 对象不仅包含最终的回答文本,还可以通过 source_nodes 属性查看被引用的原始文档片段及其相似度分数:
python
# 查看检索到的原始文档片段
for node in response.source_nodes:
print(f"相似度: {node.score:.4f}")
print(f"来源: {node.metadata.get('file_name', 'unknown')}")
print(f"内容: {node.text[:200]}...")
print("---")
如果希望自定义 Prompt 模板,可以通过 PromptTemplate 进行覆盖,从而控制 LLM 的回答风格和行为约束:
python
from llama_index.core import PromptTemplate
custom_template = PromptTemplate(
"你是一个专业的技术助手。请仅根据以下参考资料回答问题,"
"如果资料中没有相关信息,请回答'抱歉,我没有找到相关信息'。\n\n"
"【参考资料】\n{context_str}\n\n"
"【问题】\n{query_str}\n\n"
"【回答】\n"
)
query_engine = index.as_query_engine(
similarity_top_k=3,
text_qa_template=custom_template,
)
6. LangChain RAG 实践
6.1 环境准备
LangChain 同样采用了模块化的包管理方式,核心包与各集成组件独立发布。本示例使用 Chroma 作为向量数据库:
bash
pip install langchain
pip install langchain-openai
pip install langchain-chroma
pip install langchain-text-splitters
pip install langchain-community
与 LlamaIndex 通过全局 Settings 统一配置不同,LangChain 中的模型实例需要在使用时显式传入。首先完成 LLM 和 Embedding 模型的初始化:
python
import os
from langchain_openai import ChatOpenAI
from langchain_huggingface import HuggingFaceEmbeddings
API_KEY = os.getenv("OPENAI_API_KEY")
API_BASE = os.getenv("OPENAI_API_BASE")
llm = ChatOpenAI(
model="gpt-5.3",
api_key=API_KEY,
base_url=API_BASE,
temperature=0.1,
)
embed_model = HuggingFaceEmbeddings(model_name="bge-small-zh-v1.5")
ChatOpenAI 是 LangChain 对兼容 OpenAI Chat 协议模型的封装,base_url 参数用于指向第三方服务地址。这里需要注意参数命名与 LlamaIndex 略有不同------LlamaIndex 中使用 api_base,而 LangChain 使用 base_url。
6.2 文档加载与切分
LangChain 的文档加载和文本切分是两个独立的步骤,需要分别调用不同的组件。这种设计使得每个环节都可以单独替换或定制:
python
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 加载 ./data 目录下的所有 txt/md 文件
loader = DirectoryLoader(
path="./data",
glob="**/*.md",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"},
)
documents = loader.load()
print(f"共加载 {len(documents)} 个文档")
DirectoryLoader 通过 glob 参数筛选目标文件,并使用指定的 loader_cls 进行解析。对于 PDF 文件,可以替换为 PyPDFLoader;对于 HTML 文件,可以使用 BSHTMLLoader 等,各加载器的接口保持一致。
文档加载完成后,使用文本切分器将长文档拆分为较小的 chunk。RecursiveCharacterTextSplitter 是 LangChain 中最常用的切分器,它会按照段落、换行、句子、空格等分隔符的优先级递归切分,尽量保持语义完整性:
python
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=64,
separators=["\n\n", "\n", "。", ".", " ", ""],
)
chunks = text_splitter.split_documents(documents)
print(f"切分为 {len(chunks)} 个文本块")
split_documents 方法会保留每个 chunk 对应的原始文档元信息(文件路径、文件名等),这对后续的引用溯源非常重要。相比 LlamaIndex 将切分逻辑封装在 VectorStoreIndex.from_documents() 内部自动完成,LangChain 要求开发者显式控制切分过程,虽然代码量稍多,但在调试和优化时可以更精细地观察每个 chunk 的内容。
6.3 向量索引构建
切分完成后,将所有 chunk 向量化并存入 Chroma 向量数据库。LangChain 对这一步提供了便捷的封装:
python
from langchain_chroma import Chroma
PERSIST_DIR = "./chroma_storage"
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embed_model,
persist_directory=PERSIST_DIR,
)
from_documents 方法会依次完成向量化和存储两个操作,persist_directory 参数指定了持久化路径。后续再次启动时,可以直接从磁盘加载已有索引:
python
import os
if os.path.exists(PERSIST_DIR):
vectorstore = Chroma(
persist_directory=PERSIST_DIR,
embedding_function=embed_model,
)
else:
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embed_model,
persist_directory=PERSIST_DIR,
)
从 vectorstore 中可以方便地获取检索器对象,用于后续的 RAG 链路组装:
python
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3},
)
6.4 检索与生成
LangChain 中组装 RAG 链路的方式有多种,最推荐的是使用 LCEL(LangChain Expression Language)进行声明式编排。首先定义 Prompt 模板:
python
from langchain_core.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate.from_template(
"你是一个专业的技术助手。请仅根据以下参考资料回答问题,"
"如果资料中没有相关信息,请回答'抱歉,我没有找到相关信息'。\n\n"
"【参考资料】\n{context}\n\n"
"【问题】\n{question}\n\n"
"【回答】\n"
)
然后使用 LCEL 的管道语法将检索器、Prompt 模板、LLM 和输出解析器串联为一条完整的处理链:
python
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
def format_docs(docs):
"""将检索到的文档列表格式化为单一文本"""
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{
"context": retriever | format_docs,
"question": RunnablePassthrough(),
}
| prompt_template
| llm
| StrOutputParser()
)
这条链的执行逻辑是:用户输入的问题同时传递给 retriever(经检索后格式化为上下文文本)和 question 字段,两者共同填充 Prompt 模板,再送入 LLM 生成回答,最后由 StrOutputParser 提取纯文本结果。调用方式非常简洁:
python
response = rag_chain.invoke("什么是 RAG?它解决了哪些问题?")
print(response)
如果需要同时获取检索到的原始文档片段以实现引用溯源,可以单独调用 retriever:
python
docs = retriever.invoke("什么是 RAG?")
for i, doc in enumerate(docs, 1):
print(f" 引用{i}: {doc.metadata.get('source', 'unknown')}")
print(f" 内容: {doc.page_content[:200]}...")

Once Day
也信美人终作土,不堪幽梦太匆匆......
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注!
(。◕‿◕。)感谢您的阅读与支持~~~