学习资料参考:开源组织Datawhale all-in-rag项目
一、RAG简介
1.1 定义
RAG(Retrieval-Augmented Generation)检索增强生成。
- 检索指的是从外部知识库中查找与用户查询相关的信息
- 增强指的是将检索到的这些有效信息作为补充输入
- 生成指的是在补充信息的支撑下,生成符合用户需求的回答
所以 RAG 相当于AI的查资料工具,让它回答问题时 "有据可依",不再靠 "脑补"。
1.2 作用
| 没有RAG存在的问题 | RAG的解决方案 |
|---|---|
| 静态知识局限 | 实时检索外部知识库,支持动态更新 |
| 幻觉 | 基于检索内容生成,错误率降低 |
| 领域专业性不足 | 引入领域特定知识库(如医疗/法律) |
| 数据隐私风险 | 本地化部署知识库,避免敏感数据泄露 |
1.3 流程
提问前:分片、索引
提问后:召回、重排、生成
| 操作 | 解释 |
|---|---|
| 分片 | 把海量长文档切成短小的文本片段 |
| 索引 | 给这些片段打上标签、建立检索目录 |
| 召回 | 根据用户问题,从索引里找出一批最相关的文本片段 |
| 重排 | 给这些片段按匹配度打分排序,筛选出最核心的内容 |
| 生成 | 模型结合排序后的优质片段和问题,输出准确有依据的回答 |
数据源 → 分片 → 索引 → 召回 → 重排 → 生成
二、快速上手 RAG
基于 LangChain 框架的 RAG 实现(详细注释)
python
# 导入操作系统相关模块,用于读取环境变量
import os
# 导入dotenv库,用于加载.env文件中的环境变量(如API密钥)
from dotenv import load_dotenv
# 导入LangChain的Markdown文档加载器,用于读取本地markdown文件
from langchain_community.document_loaders import UnstructuredMarkdownLoader
# 导入递归字符文本分割器,是RAG中「分片」环节的核心工具
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 导入HuggingFace嵌入模型封装,用于将文本转为向量(索引/召回的基础)
from langchain_huggingface import HuggingFaceEmbeddings
# 导入内存向量存储,用于临时存储文本向量(RAG中「索引」环节的简易实现)
from langchain_core.vectorstores import InMemoryVectorStore
# 导入聊天提示词模板,用于规范向大模型提问的格式
from langchain_core.prompts import ChatPromptTemplate
# 导入深度求索大模型的封装,用于最终的「生成」环节
from langchain_deepseek import ChatDeepSeek
# 加载.env文件中的环境变量(比如DeepSeek的API密钥),避免硬编码敏感信息
load_dotenv()
# 定义本地markdown文件的路径,替换为你自己的文件路径
# 这是RAG的数据源,即外部知识库
markdown_path = "../../data/C1/markdown/easy-rl-chapter1.md"
# ====================== RAG 提问前:分片 + 索引 环节 ======================
# 1. 加载本地markdown文件
# 使用UnstructuredMarkdownLoader读取markdown文件内容,返回文档对象列表
# 文档对象包含文本内容、元数据(如文件名、路径)等信息
loader = UnstructuredMarkdownLoader(markdown_path)
docs = loader.load() # docs是加载后的文档列表,每个元素是一个Document对象
# 2. 文本分块(RAG的「分片」核心步骤)
# 原因:大模型有上下文长度限制,长文本直接处理效率低、召回不准
# RecursiveCharacterTextSplitter:按字符递归分割,优先按段落/句子分割,避免切断语义
# 默认参数:chunk_size=1000(每个块最大字符数),chunk_overlap=200(块之间重叠字符数,保证语义连贯)
text_splitter = RecursiveCharacterTextSplitter()
chunks = text_splitter.split_documents(docs) # 将加载的文档切分为小文本块
# 3. 初始化中文嵌入模型(将文本转为向量的核心工具)
# BAAI/bge-small-zh-v1.5:轻量级中文向量模型,适合新手,效果好且速度快
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-small-zh-v1.5", # 指定预训练的中文向量模型
model_kwargs={'device': 'cpu'}, # 运行设备:cpu(无GPU也能跑),有GPU可改为'cuda'
encode_kwargs={'normalize_embeddings': True} # 归一化向量,提升相似度计算准确性
)
# 4. 构建向量存储(RAG的「索引」核心步骤)
# InMemoryVectorStore:内存级向量库,适合测试(重启程序后数据丢失)
# 生产环境可替换为Chroma、FAISS、Milvus等持久化向量库
vectorstore = InMemoryVectorStore(embeddings)
vectorstore.add_documents(chunks) # 将切分后的文本块转为向量,存入向量库
# ====================== RAG 提问后:召回 + 重排 + 生成 环节 ======================
# 5. 定义提示词模板(Prompt Engineering)
# 作用:规范大模型的回答逻辑,强制其仅基于检索到的上下文回答,避免幻觉
# {context}和{question}是占位符,后续会替换为实际的检索内容和用户问题
prompt = ChatPromptTemplate.from_template("""请根据下面提供的上下文信息来回答问题。
请确保你的回答完全基于这些上下文。
如果上下文中没有足够的信息来回答问题,请直接告知:"抱歉,我无法根据提供的上下文找到相关信息来回答此问题。"
上下文:
{context}
问题: {question}
回答:"""
)
# 6. 配置大语言模型(RAG「生成」环节的核心工具)
# ChatDeepSeek:深度求索大模型的LangChain封装
llm = ChatDeepSeek(
model="deepseek-chat", # 指定深度求索的对话模型
temperature=0.7, # 生成温度:0-1,越高回答越灵活,越低越严谨
max_tokens=4096, # 单次生成的最大令牌数(控制回答长度)
api_key=os.getenv("DEEPSEEK_API_KEY") # 从环境变量读取API密钥,避免硬编码
)
# 7. 定义用户的查询问题(可替换为任意你想提问的内容)
question = "文中举了哪些例子?"
# 8. 向量召回(RAG的「召回」核心步骤)
# similarity_search:基于向量相似度检索相关文本块
# 参数k=3:召回最相似的3个文本块(可调整,k越大召回越多但可能引入无关信息)
# 注:这段代码未实现「重排」,生产环境可在召回后加CrossEncoder等重排模型,提升精准度
retrieved_docs = vectorstore.similarity_search(question, k=3)
# 将召回的3个文本块拼接成完整的上下文,方便传入提示词
docs_content = "\n\n".join(doc.page_content for doc in retrieved_docs)
# 9. 生成回答(RAG的「生成」核心步骤)
# 1. prompt.format:将上下文和问题填入提示词模板,生成完整的提问文本
# 2. llm.invoke:调用深度求索模型,基于填充后的提示词生成回答
answer = llm.invoke(prompt.format(question=question, context=docs_content))
# 打印最终回答结果
print(answer)
三、总结
-
RAG(检索增强生成)是为AI模型补充外部知识库的技术方案,核心是让模型基于检索到的真实信息生成回答,而非单纯依赖自身训练数据"脑补"。
-
RAG的完整流程分为两大阶段:
- 提问前(预处理):对海量数据源进行「分片」(切割为短小文本片段)和「索引」(将片段转为向量并建立检索目录),为后续检索做准备;
- 提问后(生成):根据用户问题「召回」相关文本片段,经「重排」筛选核心内容后,结合问题输入大模型完成最终「生成」。
-
基于LangChain框架可快速实现简易RAG流程,核心步骤包括:
- 加载本地知识库文件作为数据源;
- 用文本分割器完成分片,解决大模型上下文长度限制问题;
- 借助中文嵌入模型将文本转为向量,存入内存向量库完成索引;
- 基于用户问题从向量库召回相似文本片段;
- 通过提示词模板规范输入格式,调用大模型结合检索内容生成回答。