RAG是什么?——让大模型有记忆的知识库
你有没有问过ChatGPT"昨天发生了什么新闻"?它会回答:"抱歉,我的训练数据截止到某个日期,我不知道最新信息。"
这就是大模型最大的局限:知识截止在训练日期。GPT-4训练数据截止到2023年12月,对之后发生的事情一无所知。而且对所有用户的问题都给出同样的答案,无法处理私有文档。
RAG(Retrieval-Augmented Generation,检索增强生成)就是解决这个问题的方法。它的核心思想很简单:
让LLM在回答问题前,先去知识库找到相关资料,然后基于资料回答。
这个过程就像开卷考试和闭卷考试的区别:
普通LLM = 闭卷考试——全靠记在脑子里的东西
RAG = 开卷考试——你可以翻书找答案再作答
RAG的好处:
- 准确回答关于私有文档的问题(公司内部资料、产品手册等)
- 减少幻觉——因为回答基于检索到的文档而非模型凭空想象
- 实时更新——只需更新知识库,不用重新训练模型
- 可溯源——可以告诉用户"这个答案来自文档X的第Y页"
RAG的完整流程:
- 用户提问:"我们公司去年的营收是多少?"
- 向量检索:在公司的文档库中搜索最相关的几段文本
- 找到相关文档后,将【检索到的文档 + 用户问题】一起发给LLM
- LLM基于检索到的信息生成回答
Embedding与向量数据库:把文字变成数学
搜索引擎怎么知道"苹果手机"和"iPhone"是同一回事?Google的搜索引擎用了很多技术,但RAG用的是向量嵌入(Embedding)。
Embedding是什么?
把一段文字转换成一串数字(向量),这串数字编码了文字的语义(含义)。语义相近的文字,它们的向量在空间中距离很近。
举个例子(这些数字只是演示,真实向量是几百到几千维):
"今天天气很好" → [0.2, 0.5, 0.1]
"今日天气不错" → [0.3, 0.4, 0.2] (距离很近——意思差不多)
"机器学习算法" → [-0.8, 0.1, 0.9] (距离很远——意思完全不同)
向量空间中的"距离"就是语义相似度。距离越近=意思越接近。
常用Embedding模型:
- OpenAI text-embedding-3-small:1536维向量,效果好,但要付费
- BGE系列(BAAI):开源中文最好的Embedding之一,bge-large-zh-v1.5
- M3E:MokaAI开发的中文Embedding,开源免费,部署方便
- Jina Embeddings:支持多语言,特别适合长文本
向量数据库专门存储和检索这些向量。普通数据库用"WHERE name = '张三'"精确匹配,向量数据库用"找最近似的Top K"——不要求一模一样,但要求最相似。
就像图书馆里,传统的书号检索让你找"书号=12345的书"。向量检索让你找"和这本最相关的几本书"。
LangChain实现完整RAG系统
检索策略与优化
基础RAG用简单的向量相似度检索。但真实场景中可能遇到很多问题:"我想找北京的公司"——关键词检索可能更好。或问题很复杂需要分步推理。
1. 混合检索(Hybrid Search)
向量检索(语义相似)+ 关键词检索(BM25精确匹配)的结合。
score = α × 向量相似度 + (1-α) × BM25得分
α=0.5~0.7时效果最好。向量的语义能力弥补关键词的"只看字面",关键词的精确性弥补向量的"有时候跑偏"。
2. 重排序(Re-Ranking)
先用向量检索快速召回Top 50个候选,再用Cross-Encoder精挑出Top 5。
为什么?向量检索快但精度有限,Cross-Encoder更准但太慢。两步走:第一阶段扫一遍(快),第二阶段细选(准)。
3. 查询分解(Query Decomposition)
复杂问题拆解成多个子问题,分别检索再合并。
"比较Transformer和RNN的优缺点"→
子问题1:"Transformer有什么优点和缺点?"
子问题2:"RNN有什么优点和缺点?"
分别检索每个子问题,找到的文档更精确。
4. 上下文压缩(Context Compression)
检索到的文档可能太长,Token不够用。用LLM压缩/摘要后再回答问题。Extract-then-Answer策略。
RAG实战:构建公司产品知识库问答系统
场景:一家电商公司有100份产品文档(功能说明、保修政策、使用指南等),要构建一个内部客服系统。
设计思路:
文档选择:将PDF、Word都转成txt格式放入docs目录
切割策略:chunk_size=800(中文产品文档段落较长),chunk_overlap=100
Embedding:用BGE-large-zh做中文向量化
向量库:ChromaDB(轻量级,本地运行)
LLM:通过API调用GPT-3.5-turbo
完整代码结构:
# 1. 文档预处理
from langchain.document_loaders import PyPDFLoader, Docx2txtLoader
# 不同格式用不同loader,统一转成Document对象
# 2. 嵌入
from langchain.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5",
model_kwargs={'device': 'cuda'},
encode_kwargs={'normalize_embeddings': True}
)
# 3. 向量库
vectordb = Chroma(
embedding_function=embeddings,
persist_directory="./product_kb"
)
# 4. Prompt模板
from langchain.prompts import PromptTemplate
template = "你是产品专家。基于以下信息回答用户问题:\n\n{context}\n\n问题:{question}\n回答:"
prompt = PromptTemplate(template=template, input_variables=["context", "question"])
# 5. 使用
result = qa_chain({"query": "手机屏幕碎了怎么处理?"})
# 回答:"根据保修政策,非人为损坏的屏幕在购买7天内可以免费更换..."
练习题
Q1:RAG的全称是什么?它解决了LLM的什么核心问题?
Q2:文本切割时chunk_size=300和chunk_size=1000分别有什么影响?
Q3:向量检索和关键词检索各有什么优点?为什么要混合使用?
Q4:RAG流程中k=3是什么意思?设成k=10会怎样?
Q5:设计一个RAG系统需要哪几个核心组件?
查看答案
A1:Retrieval-Augmented Generation。解决了LLM的知识截止问题(无法回答训练截止日期之后的事)和无法处理私有文档的问题。让LLM在回答前先检索知识库,基于资料回答,减少幻觉。
A2:chunk_size=300:块太小,可能切碎完整段落,丢失上下文。但检索时更精确匹配用户问题。chunk_size=1000:块太大,保留更多上下文,但可能包含与问题无关的内容,引入噪声。
A3:向量检索理解语义("苹果"≈"iPhone"),但可能跑偏。关键词检索精确匹配字面(只找有"苹果"二字的),但不懂同义词。混合检索取长补短,score=α×向量+ (1-α)×关键词,α通常0.5-0.7。
A4:k=3表示检索最相似的3个文本块。k=10时返回10块,信息更全面但也引入了更多可能不相关的噪声,而且更消耗Token。需要根据文档质量和问题复杂度调整。
A5:五个核心组件:1) 文档加载器(读取PDF/Word/txt);2) 文本切割器(把长文档切成块);3) Embedding模型(把文本转成向量);4) 向量数据库(存储和检索向量);5) LLM(基于检索到的文档生成答案)。