前言

随着ai时代的浪潮,我们注意到,面试官不单单只考察八股了,其对于AI知识的考察,你真的了解了吗?

RAG简介

RAG全称(Retrieval-Augmented Generation),名为检索增强生成。
助于理解,举个例子:
传统的LLM:像一个鹦鹉,它很聪明,能流利地说话。
但问题是:

  • 如果你问它昨天的新闻,它不知道(知识过时)
  • 如果你问它你公司的内部规定,它也不知道(没见过的知识)
  • 有时候它还会"胡编乱造",一本正经地瞎说(幻觉问题)

RAG的出现就是帮助LLM来解决这些问题的
RAG的解决方案: 给这只鹦鹉配一个助手,助手手里拿着一堆资料。每次你提问时:

  1. 助手先翻资料,找到相关内容
  2. 把资料递给鹦鹉
  3. 鹦鹉看着资料回答问题

总结:

RAG是一种技术,通过将检索机制与生成模型结合,使LLM能够从外部知识源获取相关文档并基于这些文档生成答案,从而解决了传统LLM的知识过时、缺乏领域知识和幻觉等问题。

RAG的设计架构

  1. 数据准备阶段:把你的文档、数据库等各种知识源准备好
  2. 索引构建阶段:把这些知识"消化"成机器能快速查找的格式
  3. 检索阶段:用户问问题时,快速找到相关的知识
  4. 生成阶段:结合找到的知识和问题,生成靠谱的答案

这就像你在图书馆找资料写论文:

  • 数据准备 = 图书馆的藏书
  • 索引构建 = 图书分类和目录卡
  • 检索 = 你根据关键词找书
  • 生成 = 你看着书写论文

一张表区分LLM和RAG

对比维度 传统 LLM(Large Language Model) RAG(Retrieval-Augmented Generation)
基本定义 只依赖模型内部参数进行回答 在回答前先检索外部知识,再让模型生成答案
知识来源 模型训练时学到的知识 模型 + 外部知识库(文档、数据库、向量库等)
是否实时更新 基本不能实时更新,需要重新训练模型 可以实时更新知识库,无需重新训练模型
回答依据 模型“记忆”中的知识 检索到的文档 + 模型推理
准确性 容易出现 幻觉(Hallucination) 因为有真实文档支撑,准确性更高
数据范围 受训练数据限制 只要知识库有数据,就可以回答
典型流程 用户问题 → LLM生成回答 用户问题 → 向量检索 → 找到相关文档 → LLM生成回答
适合场景 通用聊天、写作、翻译、代码生成 企业知识库问答、文档问答、客服系统
系统复杂度 简单,只需要模型 较复杂,需要:Embedding + 向量数据库 + 检索系统
示例 直接使用 GPT 进行问答 GPT + 向量数据库(Milvus / FAISS) + 企业文档

简单来说:

模式 类比
传统 LLM 一个只靠记忆回答问题的人
RAG 一个会先查资料再回答问题的人

举个实际例子:

假设你在做一个医疗问答系统: 用户问:“阿司匹林的常见副作用是什么?”

传统LLM可能回答: “阿司匹林可能导致胃部不适、恶心等。(但不确定是否完整或最新)”

RAG系统的工作流程:

  1. 检索系统在医学数据库中搜索"阿司匹林 副作用"
  2. 找到3篇相关医学文献
  3. 提取关键信息:胃肠道反应、出血风险、过敏反应等
  4. LLM基于这些文献生成答案: "根据医学文献,阿司匹林的常见副作用包括:
  5. 胃肠道反应(胃痛、恶心、消化不良)
  6. 出血风险增加
  7. 过敏反应(如荨麻疹) 参考来源:[文献1]、[文献2]"

索引构建简介

  1. 数据获取
    从网络,书籍,PDF,WORD,Wiki中收集信息
  2. 文档预处理
    将数据内容进行处理
#示例:文档预处理代码
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
  1. 文档分块(关键)
    举例:

你问林黛玉是谁,我不可能将正本红楼梦全部读完,我会翻到含有林黛玉的信息的那几页

把大文档切成小段,每段包含一个相对完整的语义单元(模块化)
常见的分块策略:

#每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. 出血风险:特别是长期服用”

  1. 向量化(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(非常相似)
  1. 存储到向量数据库
    我们将这些向量,存储到专门的向量数据库(Vector Database)中。
    为什么不用普通数据库?
    普通数据库(MySQL、MongoDB)擅长精确查询:“找ID=123的记录”。但向量搜索是相似性查询:“找和[0.1, 0.3, 0.5]最相似的10个向量”。

向量数据库用了特殊的索引算法(如HNSW、IVF),能在百万、千万级向量中毫秒级找到最相似的。

常用向量数据库:

  • Pinecone(云服务,简单好用)
  • Milvus(开源,功能强大)
  • FAISS(Facebook开源,本地使用)
  • Weaviate(支持混合搜索)

检索阶段(快速找到相关知识)

检索阶段的任务是:从海量知识中快速找出最相关的那几条

  1. 查询处理
    就像我们问ai一样,我们给ai的提示词也不会特别精准。所以第一步,就会对我们喂给他的提示词进行加工处理

用户原始问题:“我吃了阿司匹林后胃疼怎么办?”
需要优化为:
“阿司匹林 胃痛 副作用”(关键词提取)
“阿司匹林导致的胃部不适如何处理”(问题改写)

  1. 向量检索
    把用户问题也转换为向量,然后在向量数据库中进行搜索:
#用户问题
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

  1. 检索策略
    检索系统通常采用混合搜索方法,结合向量搜索(找语义相似的文档)和关键词搜索(精确匹配),然后对结果进行排序和过滤。
    单纯向量搜索

优点:能理解语义

  • 缺点:对专有名词、数字等不敏感

混合搜索

  • 向量搜索 + 关键词搜索
    综合排序,取最优结果
#混合搜索示例
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)]
  1. 检索效果评估指标
指标 含义 公式 目标
Recall@K 前K个结果中,系统找回了多少相关文档 检索到的相关文档数 / 总相关文档数 越高越好
Precision@K 前K个结果中,有多少是相关文档 相关文档数 / K 越高越好
MRR 第一个相关文档排名的倒数 1 / 第一个相关文档排名 越高越好
NDCG 考虑排序质量的综合指标,高相关结果排越前得分越高 归一化折损累计增益(Normalized Discounted Cumulative Gain) 越高越好
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐