最近搞了个文档问答系统,核心是 RAG(检索增强生成)技术。很多人觉得 RAG 很玄乎,其实理解了原理,代码实现很直接。
这篇文章聚焦 RAG 的实现细节,把关键代码都贴出来。
unsetunset一、RAG 是什么unsetunset
简单说:先检索,再生成。
传统 LLM 问答容易"瞎编",RAG 的做法是:
从文档库检索相关内容
把检索结果作为上下文喂给 LLM
LLM 基于上下文生成答案
这样答案有依据,不会乱说。
unsetunset二、完整流程拆解unsetunset
RAG 的完整流程分 5 步:
接下来逐步拆解每一步的实现。
unsetunset三、查询重写unsetunset
为什么要重写?
用户问题往往太简洁,比如"如何部署",直接搜可能找不全。重写成多个变体能提高召回率。
在构建问答系统或知识库时,如何将海量的非结构化文档转化为机器可理解的知识,是决定检索精度的第一步。咱们的 Python 代码完整展示了这一流程:
第一步:智能分块 (Chunking)。
咱们并非简单地按字符切分,而是利用 RecursiveCharacterTextSplitter 递归地寻找段落、句号和空格。
这种带重叠区 (Overlap) 的切分方式,能够有效防止语义在断句处被割裂,确保每个文本块都保留了足够的上下文信息。
第二步:语义向量化 (Embedding)。
代码调用了轻量级且高效的 all-MiniLM-L6-v2 模型。它能将一段文字映射为空间中的高维向量。
https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2
这意味着,即便用户搜索的关键词与原文不完全一致,只要语义相近,系统就能精准捕捉。
第三步:工程化写入 (Bulk Indexing)。
针对大规模数据,代码采用了 Elasticsearch 的 Bulk API 进行批量操作。
这种方式极大地减少了网络往返开销,将文本内容、空间向量以及元数据(如 chunk_id)一次性存入索引,为后续的高并发语义检索打下了坚实的性能基础。
效果对比:
原查询:"部署系统" 重写后:
"部署系统"
"部署系统 详细步骤"
"部署系统 说明文档"
"什么是 部署系统"
"如何 部署系统"
本质一句话,让问题更加具体,以方便答案的准确性。
unsetunset四、混合检索unsetunset
核心思路:BM25 + 向量检索,优势互补。
BM25:关键词匹配,速度快,适合精确查询
向量检索:语义匹配,能找到同义表达
4.1 向量化准备
先把文档切块并向量化:关键点:
分块大小 500 字符:太大不利于检索,太小语义不完整
重叠 50 字符:避免边界信息丢失
向量维度 384:
all-MiniLM-L6-v2输出的维度
4.2 Elasticsearch 索引配置
# 创建索引 mapping = { 'mappings': { 'properties': { 'content': { 'type': 'text', 'analyzer': 'ik_max_word'# 中文分词 }, 'embedding': { 'type': 'dense_vector', 'dims': 384, 'index': True, # 开启向量索引 'similarity': 'cosine' # 余弦相似度 }, 'file_path': {'type': 'keyword'}, 'chunk_id': {'type': 'integer'} } } }注意:index: True必须设置,否则向量检索会很慢。
4.3 混合检索实现
BM25 (关键词检索)
“找得准” 传统的文本匹配。
它擅长处理专业术语、人名、缩写等精确词汇。比如搜索“iPhone 15 Pro”,它能确保结果中一定包含这些关键词。
kNN (向量检索)
“懂得多” 基于深度学习的语义匹配。
它将文本转化为数学向量,擅长处理“意思相近但用词不同”的情况。
比如搜索“苹果最新的旗舰手机”,即使文档里没出现这几个字,向量检索也能定位到“iPhone”相关的文档。
RRF (结果融合)
“平权大师” 由于 BM25 的得分(通常是几十分)和向量检索的得分(通常是 0 到 1 之间)不在一个维度上,无法直接相加。
RRF (Reciprocal Rank Fusion) 算法通过考虑文档在两个列表中的排名位置来计算最终得分,从而实现公平、有效的排序。
参数说明:
num_candidates:kNN 先找 100 个候选,再返回最相似的 k 个boost:调整 BM25 权重,默认 1.0
unsetunset五、RRF 融合算法unsetunset
RRF(Reciprocal Rank Fusion)是什么?
单纯靠关键词容易漏掉语义相关的结果,单纯靠向量检索有时会忽略特定的关键词。
这种“混合模式”是目前 RAG(检索增强生成)和企业级搜索系统的工业标准做法。
一种简单有效的结果融合算法,公式:
RRF_score(doc) = Σ 1 / (k + rank_i)rank_i:文档在第 i 个结果列表中的排名k:融合参数(默认 60)
为什么用 RRF?
简单:无需训练,直接用
有效:同时出现在两个列表的文档得分更高
鲁棒:对不同检索器的分数尺度不敏感
unsetunset六、构建 RAG Promptunsetunset
检索到相关文档后,构建提示词喂给 LLM。
Prompt 模板:
PROMPT_TEMPLATE = """你是一个专业的问答助手。请严格根据以下上下文回答问题。 【重要规则】 1. 只能使用提供的上下文信息,不能编造 2. 如果上下文中没有相关信息,明确回答"无法从文档中找到相关信息" 3. 引用信息时标注来源,格式:[块1] [块2] 4. 答案要详细、准确、逻辑清晰 【上下文】 {context} 【问题】 {question} 【回答】 """Prompt 设计要点:
明确约束:强调只用上下文,不编造
引用机制:要求标注信息来源
处理无答案:告诉 LLM 没有信息时怎么回答
格式化上下文:加编号和来源,便于追溯
unsetunset七、调用 LLM 生成答案unsetunset
支持多种 LLM,视频中以 DeepSeek 为例, 当然也可以以其他的大语言模型为例子。
参数调优:
temperature=0.3:降低随机性,让答案更稳定、准确max_tokens=1000:限制答案长度,避免太啰嗦
unsetunset八、实战案例unsetunset
跑个例子看看效果。
测试问题:"如何配置 Elasticsearch 的分词器?"
执行流程:
result = rag_query("如何配置 Elasticsearch 的分词器")输出:
用户问题:如何配置 Elasticsearch 的分词器 查询变体:['如何配置 Elasticsearch 的分词器', '如何配置 Elasticsearch 的分词器 详细步骤', ...] 检索到 10 个相关文档块 Prompt 长度:2456 字符 答案: **效果很好:** - 答案准确,有步骤有代码 - 标注了信息来源 - 没有编造内容unsetunset总结unsetunset
RAG 的核心就是:检索 + 生成。
实现流程很清晰:
查询重写 → 2. 混合检索 → 3. RRF 融合 → 4. 构建 Prompt → 5. LLM 生成
关键是要理解每一步的原理,根据实际场景调优参数。代码实现不复杂,难的是工程化和细节打磨。
实现高效 RAG 的秘诀在于通过‘合理的文档分块’与‘关键词+向量混合检索’夯实底层数据召回,配合‘多维度查询重写’拓宽信息来源,并最终利用‘Temperature 参数’与‘强约束 Prompt’确保大模型在精准的上下文土壤中结出确定性的答案。
有问题欢迎留言交流。
打造你的企业级智能文档问答系统——Everything plus RAG 实战指南