当面试官问到你对于 RAG 的理解,脑子里一片空白怎么办?四个流程,一文读懂
前言
随着ai时代的浪潮,我们注意到,面试官不单单只考察八股了,其对于AI知识的考察,你真的了解了吗?
RAG简介
RAG全称(Retrieval-Augmented Generation),名为检索增强生成。
助于理解,举个例子:
传统的LLM:像一个鹦鹉,它很聪明,能流利地说话。
但问题是:
- 如果你问它昨天的新闻,它不知道(知识过时)
- 如果你问它你公司的内部规定,它也不知道(没见过的知识)
- 有时候它还会"胡编乱造",一本正经地瞎说(幻觉问题)
RAG的出现就是帮助LLM来解决这些问题的
RAG的解决方案: 给这只鹦鹉配一个助手,助手手里拿着一堆资料。每次你提问时:
- 助手先翻资料,找到相关内容
- 把资料递给鹦鹉
- 鹦鹉看着资料回答问题
总结:
RAG是一种技术,通过将检索机制与生成模型结合,使LLM能够从外部知识源获取相关文档并基于这些文档生成答案,从而解决了传统LLM的知识过时、缺乏领域知识和幻觉等问题。
RAG的设计架构
- 数据准备阶段:把你的文档、数据库等各种知识源准备好
- 索引构建阶段:把这些知识"消化"成机器能快速查找的格式
- 检索阶段:用户问问题时,快速找到相关的知识
- 生成阶段:结合找到的知识和问题,生成靠谱的答案
这就像你在图书馆找资料写论文:
- 数据准备 = 图书馆的藏书
- 索引构建 = 图书分类和目录卡
- 检索 = 你根据关键词找书
- 生成 = 你看着书写论文
一张表区分LLM和RAG
| 对比维度 | 传统 LLM(Large Language Model) | RAG(Retrieval-Augmented Generation) |
|---|---|---|
| 基本定义 | 只依赖模型内部参数进行回答 | 在回答前先检索外部知识,再让模型生成答案 |
| 知识来源 | 模型训练时学到的知识 | 模型 + 外部知识库(文档、数据库、向量库等) |
| 是否实时更新 | 基本不能实时更新,需要重新训练模型 | 可以实时更新知识库,无需重新训练模型 |
| 回答依据 | 模型“记忆”中的知识 | 检索到的文档 + 模型推理 |
| 准确性 | 容易出现 幻觉(Hallucination) | 因为有真实文档支撑,准确性更高 |
| 数据范围 | 受训练数据限制 | 只要知识库有数据,就可以回答 |
| 典型流程 | 用户问题 → LLM生成回答 | 用户问题 → 向量检索 → 找到相关文档 → LLM生成回答 |
| 适合场景 | 通用聊天、写作、翻译、代码生成 | 企业知识库问答、文档问答、客服系统 |
| 系统复杂度 | 简单,只需要模型 | 较复杂,需要:Embedding + 向量数据库 + 检索系统 |
| 示例 | 直接使用 GPT 进行问答 | GPT + 向量数据库(Milvus / FAISS) + 企业文档 |
简单来说:
| 模式 | 类比 |
|---|---|
| 传统 LLM | 一个只靠记忆回答问题的人 |
| RAG | 一个会先查资料再回答问题的人 |
举个实际例子:
假设你在做一个医疗问答系统: 用户问:“阿司匹林的常见副作用是什么?”
传统LLM可能回答: “阿司匹林可能导致胃部不适、恶心等。(但不确定是否完整或最新)”
RAG系统的工作流程:
- 检索系统在医学数据库中搜索"阿司匹林 副作用"
- 找到3篇相关医学文献
- 提取关键信息:胃肠道反应、出血风险、过敏反应等
- LLM基于这些文献生成答案: "根据医学文献,阿司匹林的常见副作用包括:
- 胃肠道反应(胃痛、恶心、消化不良)
- 出血风险增加
- 过敏反应(如荨麻疹) 参考来源:[文献1]、[文献2]"
索引构建简介
- 数据获取
从网络,书籍,PDF,WORD,Wiki中收集信息 - 文档预处理
将数据内容进行处理
#示例:文档预处理代码
def preprocess_document(doc):
# 1. 移除多余的空格和换行
doc = re.sub(r'\s+', ' ', doc)
# 2. 提取纯文本(从PDF、HTML等)
if doc_type == 'pdf':
text = extract_text_from_pdf(doc)
# 3. 规范化格式
text = text.strip().lower()
# 4. 去除无用信息(页眉、页脚等)
text = remove_headers_footers(text)
return text
- 文档分块(关键)
举例:
你问林黛玉是谁,我不可能将正本红楼梦全部读完,我会翻到含有林黛玉的信息的那几页
把大文档切成小段,每段包含一个相对完整的语义单元(模块化)
常见的分块策略:
#每300个字符一块
chunk_size = 300
chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
#按句号、问号、感叹号分割
import nltk
sentences = nltk.sent_tokenize(text)
段落分块(保留逻辑结构)
#按换行符或段落标记分割
chunks = text.split('\n\n')
滑动窗口分块(带重叠,避免信息丢失)
chunk_size = 300
overlap = 50 # 重叠50字符
chunks = []
for i in range(0, len(text), chunk_size - overlap):
chunks.append(text[i:i+chunk_size])
分块的关键参数:
- 块大小(Chunk Size)
- :太小 → 语义不完整;太大 → 检索不精准
- 推荐:200-500 tokens(约150-400个汉字)
- 重叠(Overlap)
- :避免关键信息被切断
- 推荐:10-20% 的块大小
举例:
原文:
阿司匹林是一种常用的解热镇痛药。它的主要作用包括:
解热:降低发烧体温
镇痛:缓解轻到中度疼痛
抗血小板:预防血栓形成
但是,阿司匹林也有副作用。常见的副作用包括:
胃肠道反应:胃痛、恶心
出血风险:特别是长期服用
分块后:
Chunk 1: “阿司匹林是一种常用的解热镇痛药。它的主要作用包括:
1. 解热:降低发烧体温
2. 镇痛:缓解轻到中度疼痛
3. 抗血小板:预防血栓形成”
Chunk 2: “阿司匹林的主要作用包括预防血栓形成。但是,阿司匹林也有副作用。
常见的副作用包括:
1. 胃肠道反应:胃痛、恶心
2. 出血风险:特别是长期服用”
- 向量化(Embedding)
分块后的文本还是人类语言,机器不懂。我们需要把它转换成向量(一串数字)。
什么是Embedding?
把文字转换成数字向量,相似的文字会得到相似的向量。
#使用OpenAI的Embedding模型
from openai import OpenAI
client = OpenAI()
text = "阿司匹林是一种解热镇痛药"
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
vector = response.data[0].embedding
print(f"向量维度: {len(vector)}") # 输出: 1536
print(f"前5个值: {vector[:5]}") # 输出: [0.023, -0.014, 0.089, ...]
为什么需要向量化?
想象你在找"感冒药",但文档里写的是"抗感冒药物"、"治疗感冒的药品"等各种说法。如果只靠关键词匹配,就找不到了。
但是!如果用向量化:
"感冒药" → [0.1, 0.3, 0.5, ...]
"抗感冒药物" → [0.12, 0.29, 0.51, ...] # 向量很接近!
"汽车" → [0.8, 0.1, 0.2, ...] # 向量差很远
向量之间可以计算相似度(余弦相似度),数值越接近1越相似:
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
vec1 = np.array([0.1, 0.3, 0.5])
vec2 = np.array([0.12, 0.29, 0.51])
similarity = cosine_similarity([vec1], [vec2])[0][0]
print(f"相似度: {similarity:.3f}") # 输出: 0.999(非常相似)
- 存储到向量数据库
我们将这些向量,存储到专门的向量数据库(Vector Database)中。
为什么不用普通数据库?
普通数据库(MySQL、MongoDB)擅长精确查询:“找ID=123的记录”。但向量搜索是相似性查询:“找和[0.1, 0.3, 0.5]最相似的10个向量”。
向量数据库用了特殊的索引算法(如HNSW、IVF),能在百万、千万级向量中毫秒级找到最相似的。
常用向量数据库:
- Pinecone(云服务,简单好用)
- Milvus(开源,功能强大)
- FAISS(Facebook开源,本地使用)
- Weaviate(支持混合搜索)
检索阶段(快速找到相关知识)
检索阶段的任务是:从海量知识中快速找出最相关的那几条。
- 查询处理
就像我们问ai一样,我们给ai的提示词也不会特别精准。所以第一步,就会对我们喂给他的提示词进行加工处理
用户原始问题:“我吃了阿司匹林后胃疼怎么办?”
需要优化为:
“阿司匹林 胃痛 副作用”(关键词提取)
“阿司匹林导致的胃部不适如何处理”(问题改写)
- 向量检索
把用户问题也转换为向量,然后在向量数据库中进行搜索:
#用户问题
question = "阿司匹林有哪些副作用?"
#问题向量化
question_embedding = embeddings.embed_query(question)
#向量检索(找最相似的3个)
results = vectorstore.similarity_search_by_vector(
embedding=question_embedding,
k=3 # 返回top3
)
for i, doc in enumerate(results):
print(f"结果{i+1}:")
print(doc.page_content)
print(f"相似度: {doc.metadata['score']}")
print("-" * 50)
结果1:
阿司匹林的常见副作用包括:1. 胃肠道反应:胃痛、恶心、消化不良…
相似度: 0.89
结果2:
长期服用阿司匹林可能增加出血风险,特别是胃肠道出血…
相似度: 0.85
结果3:
少数患者对阿司匹林过敏,可能出现荨麻疹、呼吸困难等症状…
相似度: 0.81
- 检索策略
检索系统通常采用混合搜索方法,结合向量搜索(找语义相似的文档)和关键词搜索(精确匹配),然后对结果进行排序和过滤。
单纯向量搜索:
优点:能理解语义
- 缺点:对专有名词、数字等不敏感
混合搜索:
- 向量搜索 + 关键词搜索
综合排序,取最优结果
#混合搜索示例
def hybrid_search(query, alpha=0.5):
# alpha: 向量搜索权重(0-1)
# 向量搜索结果
vector_results = vectorstore.similarity_search(query, k=10)
# 关键词搜索结果(BM25算法)
keyword_results = bm25_search(query, k=10)
# 融合排序
final_results = merge_results(vector_results, keyword_results, alpha)
return final_results[:3] # 返回top3
重排序:
初步检索后,用更精细的模型重新排序,提高精度。
from sentence_transformers import CrossEncoder
#加载重排序模型
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
#对检索结果重新打分
query = "阿司匹林副作用"
candidate_docs = ["文档1内容", "文档2内容", "文档3内容"]
scores = reranker.predict([(query, doc) for doc in candidate_docs])
#按分数排序
ranked_docs = [doc for _, doc in sorted(zip(scores, candidate_docs), reverse=True)]
- 检索效果评估指标
| 指标 | 含义 | 公式 | 目标 |
|---|---|---|---|
| Recall@K | 前K个结果中,系统找回了多少相关文档 | 检索到的相关文档数 / 总相关文档数 | 越高越好 |
| Precision@K | 前K个结果中,有多少是相关文档 | 相关文档数 / K | 越高越好 |
| MRR | 第一个相关文档排名的倒数 | 1 / 第一个相关文档排名 | 越高越好 |
| NDCG | 考虑排序质量的综合指标,高相关结果排越前得分越高 | 归一化折损累计增益(Normalized Discounted Cumulative Gain) | 越高越好 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)