大模型 RAG 经验面
整理自:AiGC面试宝典 | 来源:知识星球 整理日期:2024年8月11日
一、RAG 基础面
1.1 为什么大模型需要外挂向量知识库?
- 克服遗忘问题:大模型在训练后无法动态更新知识,外挂知识库可以实时补充新信息
- 提升回答的准确性、权威性、时效性:避免模型生成过时或不准确的内容
- 解决垂直领域知识不足:通用模型对小众领域涉猎有限,外挂知识库可以针对性补充
- 提高可控性和可解释性:答案可追溯到具体来源,提高可信度和安全性
📝通俗解释:就像一个记忆力很好的学生,但不可能记住所有知识。考试时让他带一本专业参考资料(外挂知识库),既不会忘,又能准确回答各种问题。
1.2 RAG 思路是怎样的?
完整流程:
文档处理流程(知识库构建)
- 加载本地文档(txt/doc/pdf等)
- 读取文本内容
- 文本分割(切分成小块)
- 文本向量化(embedding)
- 存入向量数据库
查询与生成流程(用户提问)
- 用户输入 query(问题)
- 问题向量化
- 在向量数据库中检索相似内容(Top K)
- 匹配到的文档作为上下文
- 组装 prompt 提交给 LLM
- LLM 生成回答
流程图示:
用户 Query → 向量化 → 向量相似度匹配 ← 文档向量库
↓
Top K 相关文档
↓
[上下文 Context] + [问题] → Prompt 模板
↓
大语言模型 LLM
↓
最终答案 Answer📝通俗解释:RAG就像在图书馆找书。先把书籍分类整理(知识库构建),有人问问题时,根据问题关键词找到最相关的几本书(检索),然后翻阅这些书的相关章节来回答问题(生成)。
1.3 RAG 核心技术是什么?
- 核心技术:Embedding(向量化)
- 核心思路:
- 将用户知识库内容经过 embedding 存入向量知识库
- 用户提问同样经过 embedding
- 利用向量相关性算法(如余弦相似度)找到最匹配的文档片段
- 将这些片段作为上下文,与问题一起提交给 LLM 回答
📝通俗解释:Embedding就是给文字"拍照",把文字转换成计算机能理解的数字向量。比如"猫"和"狗"的向量很相似(都是动物),而"猫"和"汽车"的向量差别很大。这样通过比较向量的"距离"就能找到相关内容。
1.4 RAG Prompt 模板如何构建?
已知信息:
{context}
根据上述已知信息,简洁且专业地回答用户的问题。如果无法从中得到答案,请说"根据已知信息无法回答该问题"或"没有提供足够的相关信息",不允许在答案中添加编造成分,答案请使用中文。
问题是:{question}📝通俗解释:这个prompt模板就像给LLM一张"答题卡":先告诉它看哪些资料(已知信息),再问具体问题(question),最后还要提醒它不知道就说不知道,别瞎编。
1.5 如何评价 RAG 项目效果的好坏?
检索环节评估指标:
| 指标 | 说明 |
|---|---|
| MMR 平均倒排率 | 查询结果中正确信息的位置倒数 |
| Hits Rate 命中率 | Top K 结果中包含正确信息的比例 |
| NDCG | 标准化折损累计增益,综合考虑相关性和排名 |
生成环节评估指标:
- 非量化指标:完整性、正确性、相关性
- 量化指标:Rouge-L(评估生成文本与参考文本的重叠程度)
📝通俗解释:检索就像考试找资料,要看找得全不全(命中率)、找得准不准(排名)。生成就像写答案,要看答案是否完整、正确、有没有答非所问。
1.6 RAG 的检索阶段,常见的向量检索模型有哪些?
ANN(近似最近邻)算法:
- 乘积量化(Product Quantization)
- 暴力搜索(Brute Force)
- HNSW(Hierarchical Navigable Small World)
其他方法:
- KD 树(K-Dimensional Tree)
📝通俗解释:想象在图书馆找书。暴力搜索就是一本一本翻(慢但准确);HNSW就像问图书管理员,他凭经验直接带你去大致区域(快但可能不是最优);KD树把书架分成区域管理,帮你快速定位。
1.7 针对通用的 RAG,还有哪些改进点?
- Query 侧优化:做 query 的纠错、改写、规范化扩展
- 向量数据库优化:构建层次索引,提高检索效率和精度
- LLM 微调:针对垂直领域引入知识库,提升回答的专业性、时效性和正确性
- 后处理优化:对最终输出做处理,降低不合理答案的出现概率
📝通俗解释:改进RAG就像升级一个问答系统:问题输入要纠错(用户打错字能自动纠正);检索要更聪明(更快更准找到资料);回答要更专业(针对特定领域微调模型);最后还要把关(防止输出奇怪的内容)。
二、RAG 优化面
痛点1:文档切分粒度不好把控,既担心噪声太多又担心语义信息丢失
问题描述
问题1:如何让 LLM 简要、准确回答细粒度知识?
- 举例:用户问"2023年我国上半年的国内生产总值是多少?"
- 期望回答:"593034亿元"
- 实际可能:模型啰嗦半天或者编造数字
问题2:如何让 LLM 回答出全面的粗粒度(跨段落)知识?
- 举例:用户问"征信中心有几点声明?"
- 期望回答:完整列出三点声明内容
- 实际可能:只回答了两点,因为召回的文档不完整
需求分析
- 需要实现语义级别的分割,而不是简单基于 html 或 pdf 的换行符分割
- 痛点:文档分割不够准确,导致召回结果残缺
- 切分粒度太大 → 噪声多,答案不精准
- 切分粒度太小 → 语义丢失,回答不全面
解决方案
核心思想:基于 LLM 的文档对话架构 = 先检索,后推理
检索部分要满足三点:
- 尽可能提高召回率
- 尽可能减少无关信息
- 速度快
二级索引架构:
第一级索引:关键信息(短摘要)→ 做embedding,参与相似度计算
第二级索引:原始文本(完整内容)→ 映射关系,交给LLM架构图示:
文章A 文章B
├── 段落A ├── 段落D
│ ├── 句子a → 关键信息3 │ ├── 句子g → 关键信息9
│ └── 句子b → 关键信息4 │ └── 句子h → 关键信息10
├── 段落B └── 段落E
│ ├── 句子c → 关键信息5 └── 句子i → 关键信息11
│ └── 句子d → 关键信息6
└── 段落C
├── 句子e → 关键信息7
└── 句子f → 关键信息8
检索示例:
问题1答案在句子c和句子h → 检索到关键信息5和10 → 返回原始句子
问题2答案在段落A、B、E → 检索到关键信息1、14、9 → 返回完整段落如何构建关键信息?
方法1:NLP 篇章分析(discourse parsing)
- 利用篇章分析工具提取段落间关系(如主从关系)
- 把包含主从关系的段落合并,保证每段说同一件事
方法2:BERT 语义分割
- 利用 BERT 的 NSP(Next Sentence Prediction)任务
- 判断相邻段落是否有语义衔接关系
- 设置相似度阈值,决定是否合并
def is_nextsent(sent, next_sent):
encoding = tokenizer(sent, next_sent, return_tensors="pt", truncation=True, padding=False)
with torch.no_grad():
outputs = model(**encoding, labels=torch.LongTensor([1]))
logits = outputs.logits
probs = torch.softmax(logits/TEMPERATURE, dim=1)
next_sentence_prob = probs[:, 0].item()
return next_sentence_prob > MERGE_RATIO方法3:关键信息抽取
- 成分句法分析:提取核心名词短语、动词短语
- 命名实体识别(NER):提取重要实体(人名、地名、机构名等)
- 语义角色标注:提取"谁对谁做了什么"的信息
方法4:关键词提取工具
- HanLP(中文效果好,但付费)
- KeyBERT(英文效果好,中文效果一般)
- 垂直领域建议:训练专门的关键词生成模型(如 ChatLaw 的 KeyLLM)
常见问题
Q:句子、语义段之间召回不会有包含关系吗,是否会造成冗余?
A:会造成冗余,但试验表明回答效果很好,无论是细粒度还是粗粒度知识准确度都比 LangChain 粗分效果好很多。可以优化但非必要。
📝通俗解释:文档切分就像切蛋糕——切太小了,每块都说不清完整的故事(语义丢失);切太大了,一块蛋糕里混了太多奶油和水果(噪声太多)。二级索引的思路是:先看每块的"简介"(关键信息)来快速匹配,找到相关的再给完整内容。这样既快又准。
痛点2:在垂直领域表现不佳
问题描述:通用 RAG 在垂直领域(如医疗、法律、金融)回答效果差
解决方法:
- Embedding 模型微调:基于垂直领域数据微调向量化模型
- LLM 微调:基于垂直领域语料微调大语言模型
📝通俗解释:就像让一个通才医生做心脏手术——基础能力有,但不够专业。解决方案就是让他去专门进修(微调),变成专科医生。
痛点3:LangChain 内置问答分句效果不佳
问题描述:LangChain 默认的分句方式不够智能
解决方法:
- 更好的文档拆分:使用语义识别模型(如达摩院的模型)进行拆分
- 改进填充方式:判断中心句上下文的相关性,仅添加相关度高的句子
- 分段总结:对每段分别进行总结,基于总结内容进行匹配
📝通俗解释:LangChain默认就像用尺子切文章——按固定长度切,但不管内容是否连贯。改进方法是要么用更智能的"语义切刀",要么切完后再"筛选一遍"去掉无关内容。
痛点4:如何尽可能召回与 Query 相关的 Document
问题描述:如何让与问题相关的文档尽可能多地被召回
解决方法:
平衡 Document 长度、embedding 质量和召回数量:
- 文本较短时,embedding 质量可能更高
- 各段落语义关联不宜过强
优化检索策略:
- 基于本地知识微调文本向量化工具
- 结合 ES 搜索与 Faiss 结果
📝通俗解释:召回就像在大海里捞针。要么用更密的网(更多召回),要么让网更准(提高相关性)。短文本的embedding更准确,所以切分时不要贪多。
痛点5:如何让 LLM 基于 Query 和 Context 得到高质量的 Response
问题描述:如何让 LLM 生成高质量答案
解决方法:
- Prompt 优化:尝试多个 prompt 模板,选择效果最好的
- LLM 微调:用本地知识问答相关的语料对 LLM 进行微调
📝通俗解释:同一个问题,问法不同,答案质量可能差很多。比如问"根据这段话,告诉我答案"和"请仔细阅读以下内容,然后用简洁准确的语言回答问题",效果可能完全不同。这就是prompt的学问。
痛点6:Embedding 模型在表示 Text Chunks 时偏差太大
问题描述:
- 开源 embedding 模型效果一般,text chunk 很大时很难准确表示
- 多语言问题:文档是英文,问题是中文,跨语言检索困难
解决方法:
用更小的 text chunk + 更大的 topk:
- smaller chunk → noise 更小,embedding 表示更准确
- larger topk → 组合更丰富的 context
选择多语言 embedding 模型:使用支持多语言的 embedding 模型
📝通俗解释:Embedding就像给文章画"重点"。文章太长,重点就画不准(偏差大)。解决方案是:要么把文章写短一点(切分小一点),要么多找几本书一起看(topk大一点)。多语言问题就像用中文查英文书,需要找会双语的书架管理员。
痛点7:不同的 Prompt 可能产生完全不同的效果
问题描述:Prompt 是个神奇的东西,不同的提法可能产生完全不同的效果。尤其是希望生成特定格式时,需要反复尝试。
解决方法:
- 不断尝试和优化 prompt 设计
- 针对特定格式要求进行专项调试
📝通俗解释:Prompt就像跟不同的人说话。有的人吃软不吃硬(喜欢鼓励式指令),有的人就喜欢直接命令(喜欢指令式)。LLM也是这样,不同的prompt写法效果天差地别,需要反复"调教"。
痛点8:LLM 生成效果问题
问题描述:LLM 本质上是"接茬"机器,各家 LLM 在理解 context 和生成方面相差较大。
实践经验:
- 付费 GPT 代理:生成内容可读性强,完全按格式生成
- 本地开源模型(如 Chinese-LLaMA2-Alpaca、Baichuan2):
- 量化后(Q6_K)性能接近 fp16
- 在格式理解上不如 GPT 灵活
- 生成时有点"轴"(过于遵循指令模板)
解决思路:选择开源模型,自己构造领域数据集进行微调,让 LLM 更听指挥
📝通俗解释:LLM就像一个写作助手。付费的GPT就像经验丰富的老编辑,你说什么他都懂;开源模型像刚毕业的新手,你得慢慢教他规矩(微调),才能写出你想要的内容。
痛点9:如何更高质量地召回 Context 喂给 LLM
问题描述:初期使用 LangChain 默认配置时,召回的内容和 query 根本不相关
解决思路:
- 更细颗粒度地做 recall
- 学术内容优化:
- 使用学术相关的 embedding 模型
- 准备高质量的指令数据
- 更细致的 PDF 解析
📝通俗解释:就像考试时找参考资料。如果随便翻几页就答题,肯定答不好。要更细致地"翻书"(细粒度召回),找真正相关的章节,才能写出好答案。
三、RAG 工程示例面
3.1 本地知识库问答系统(LangChain-ChatGLM)
3.1.1 避坑记录
问题1:解决持续网页 loading 的问题
- 原因:高版本 gradio 检查 google 字体导致卡住
- 解决:降低 gradio 版本
pip install gradio==3.21.0问题2:解决无法安装 detectron2
- 解决:0517 后使用 paddleocr 代替
# 1. 下载依赖包
git clone https://github.com/facebookresearch/detectron2
cd detectron2
# 2. 安装(注意 torch==1.8.0)
pip install -e .
# 3. 升级 torch,降至 protobuf 版本
pip install torch==2.0.0
pip install protobuf==3.20.0问题3:解决 PDF 无法加载
# 1. 更新 apt
sudo apt update
# 2. 安装依赖
sudo apt install libmagic-dev poppler-utils tesseract-ocr
# 3. 检查版本
tesseract --version
tesseract --list-langs
# 4. 如需中文识别,下载中文包放入:
# /usr/share/tesseract-ocr/4.00/tessdata/问题4:解决错误 file0
# 1. 下载 punkt.zip
# 解压放到 nltk_data/tokenizers 路径下
# 2. 下载 averaged_perceptron_tagger.zip
# 解压放到 nltk_data/taggers 路径下
# 查看 nltk_data 路径
python -c "import nltk; print(nltk.data.path)"问题5:使用 PaddleOCR 出现错误
- 错误:
ModuleNotFoundError: No module named 'tools.infer' - 原因:import 路径问题
- 解决:修改 paddleocr 文件中的 import 语句
# 原代码
from tools.infer import ...
# 修改为
from paddleocr.tools.infer import ...问题6:解决 MOSS 模型加载错误
- 错误:
get_class_from_dynamic_module() missing 2 required positional arguments - 解决:修改
langchain-ChatGLM/models/moss_llm.py第33行
# 原代码
cls = get_class_from_dynamic_module(
class_reference="fnlp/moss-moon-003-sft--modeling_moss.MossForCausalLM",
pretrained_model_name_or_path=llm_model_dict['moss']
)
# 修改为
cls = get_class_from_dynamic_module(
pretrained_model_name_or_path="fnlp/moss-moon-003-sft",
module_file="modeling_moss.py",
class_name="MossForCausalLM"
)
# 模型文件放置路径:langchain-ChatGLM/fnlp/moss-moon-003-sft/问题7:解决 MOSS 提问出现错误
- 错误:
RuntimeError: probability tensor contains either inf, nan or element < 0 - 解决:移除
do_sample=True参数
📝通俗解释:这些坑都是实战中踩出来的。高版本gradio有bug要降级;detectron2安装麻烦不如用paddleocr;PDF加载需要系统级依赖;nltk数据要手动下载;模型加载参数要填对。这些都是"学费"啊!
总结
RAG(检索增强生成)是大模型应用的重要技术方向,核心在于:
- 构建高质量知识库:合理的文档切分、关键信息提取
- 优化检索效率:向量检索、层次索引、多路召回
- 提升生成质量:Prompt 工程、模型微调
- 工程实践:处理各种依赖和环境问题
📝通俗解释:RAG就像给大模型配了一个"知识外挂"。它让AI回答问题时有据可查,而不是凭空编造。做好RAG,关键在于:找资料要快(检索)、找得要准(相关性)、答案要好(生成)。这是一套系统工程,需要不断优化和实践。