从原理到实战:全方位构建大模型 RAG 检索增强生成系统

120 阅读14分钟

RAG(Retrieval-Augmented Generation,检索增强生成技术)于2020年由Meta AI(原Facebook AI)在论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》中提出,核心是通过文本检索策略让大模型问答前带入相关文本,提升回答准确性。该技术初期关注度低,2022年随大模型技术爆发进入视野;因早期大模型应用以知识库问答为主,且RAG易上手、上限高,迅速成为大模型技术人必备技能。

一、RAG核心作用

RAG检索增强流程的核心价值,需从大模型的三项核心技术缺陷切入说明。

缺陷一:大模型幻觉

大模型常出现无中生有编造答案的“幻觉”现象,例如第一代DeepSeek R1模型平均七次回答就有一次存在幻觉。本质原因是大模型通过学习文本概率关系掌握“合理表述”,但不具备事实理解与验证能力,且未接入实时知识库,遇陌生问题、模糊描述时,会基于语料库中似是而非的关联编造看似流畅合理的答案,易误导用户。

缺陷二:有限的最大上下文

大模型的对话、背景设置、工具调用等均需占用上下文窗口,导致单次对话可输入的知识量有限。这是由模型架构与计算方式决定的——输入文本需转换为固定长度的token加载到上下文窗口,窗口大小有限(不同模型为几千到几万token),超出则截断或丢弃信息,造成对话历史、长文档细节遗失,出现回答不连贯等问题。尽管顶尖模型(如Gemini 2.5 Pro、GPT-4.1)已实现1M上下文长度,普通模型达64K或128K,但开发者可一次性输入的信息仍有上限。

缺陷三:模型专业知识与时效性知识不足

大模型训练依赖预收集的大规模语料,虽覆盖广,但在医学、法律、前沿科技等专业领域的深度和准确性不足;且训练有时间终点,知识存在天然时效性,无法实时反映最新研究成果、政策变化或行业动态,难以应对高度专业化或最新的问题。

综上,RAG的价值核心在于:对话时为模型输入问题相关的精准文档,从而拓展知识边界——提升专业知识准确性、补充时效性信息、消除幻觉。同时,RAG也是海量文本本地问答知识库搭建、无限上下文聊天机器人构建的最佳解决方案。

二、RAG基础原理

时至今日,RAG技术已经是非常庞大的技术体系了,从简单的文档切分、存储、匹配,再到复杂的入GraphRAG(基于知识图谱的检索增强),以及复杂文档解析+多模态识别技术等等。而对于初学者来说,为了更好的上手学习RAG技术,我们首先需要对RAG技术最简单的实现形式有个基础的了解。

基础RAG系统的完整执行流程可分为五个核心步骤,形成闭环:

  1. 数据预处理阶段:对原始文档进行清洗(去除停用词、冗余标点、空白行等噪声数据),再通过选定的分块策略切割为chunk;
  2. 向量构建阶段:使用Embedding模型将所有chunk转化为高维向量,同时建立chunk与向量的映射关系,将向量批量存入向量数据库;
  3. 查询转化阶段:接收用户输入的Query后,立即通过同一Embedding模型将其转化为向量(确保向量空间一致,保证相似度计算的有效性);
  4. 相似检索阶段:将Query向量传入向量数据库,通过内置算法检索出相似度最高的若干个chunk(通常为3-5个,可根据效果调整),并返回这些chunk的原始文本;
  5. 增强生成阶段:将检索到的相关chunk与用户原始Query按照固定模板拼接为Prompt(如“结合以下参考信息回答问题:{chunk内容}\n问题:{Query}”),再将完整Prompt输入大模型,由模型基于精准参考信息生成最终答案。

image-20250319144224706.png

具体执行过程如下所示:

image-20250924100539992.png


三、相关核心概念

3.1 向量数据库

向量数据库(Vector Database),也叫矢量数据库,主要用来存储和处理向量数据。

在数学中,向量是有大小和方向的量,可以使用带箭头的线段表示,箭头指向即为向量的方向,线段的长度表示向量的大小。两个向量的距离或者相似性可以通过欧式距离或者余弦距离得到。

图像、文本和音视频这种非结构化数据都可以通过某种变换或者嵌入学习转化为向量数据存储到向量数据库中,从而实现对图像、文本和音视频的相似性搜索和检索。这意味着您可以使用向量数据库根据语义或上下文含义查找最相似或相关的数据。

向量数据库的主要特点是高效存储与检索。利用索引技术和向量检索算法能实现高维大数据下的快速响应。

3.2 向量嵌入Vector Embeddings

对于传统数据库,搜索功能都是基于不同的索引方式加上精确匹配和排序算法等实现的。本质还是基于文本的精确匹配,这种索引和搜索算法对于关键字的搜索功能非常合适,但对于语义搜索功能就非常弱。

例如,如果你搜索 “小狗”,那么你只能得到带有“小狗” 关键字相关的结果,而无法得到 “柯基”、“金毛” 等结果,因为 “小狗” 和“金毛”是不同的词,传统数据库无法识别它们的语义关系,所以传统的应用需要人为的将 “小狗” 和“金毛”等词之间打上小狗特征标签进行关联,这样才能实现语义搜索。

同样,当你在处理非结构化数据时,你会发现非结构化数据的特征数量会迅速增加,处理过程会变得十分困难。比如我们处理图像、音频、视频等类型的数据时,这种情况尤为明显。就拿图像来说,可以标注的特征包括颜色、形状、纹理、边缘、对象、场景等多个方面。然而,这些特征数量众多,而且依靠人工进行标注的难度很大。因此,我们需要一种自动化的方式来提取这些特征,而Vector Embedding技术就能够实现这一目标。

Vector Embedding 是由专门的向量模型生成的,它会根据不同的算法生成高维度的向量数据,代表着数据的不同特征,这些特征代表了数据的不同维度。例如,对于文本,这些特征可能包括词汇、语法、语义、情感、情绪、主题、上下文等。对于音频,这些特征可能包括音调、节奏、音高、音色、音量、语音、音乐等。

3.3 相似性测量

如何衡量向量之间的相似性呢?有三种常见的向量相似度算法:欧几里德距离、余弦相似度和点积。

  • 点积(内积): 两个向量的点积是一种衡量它们在同一方向上投影的大小的方法。如果两个向量是单位向量(长度为1),它们的点积等于它们之间夹角的余弦值。因此,点积经常被用来计算两个向量的相似度。
  • 余弦相似度: 这是一种通过测量两个向量之间的角度来确定它们相似度的方法。余弦相似度是两个向量点积和它们各自长度乘积的商。这个值的范围从-1到1,其中1表示完全相同的方向,-1表示完全相反,0表示正交。
  • 欧氏距离: 这种方法测量的是两个向量在n维空间中的实际距离。虽然它通常用于计算不相似度(即距离越大,不相似度越高),但可以通过某些转换(如取反数或用最大距离归一化)将其用于相似度计算。

像我们最常用的余弦相似度,其代码实现也非常简单,如下所示:

import numpy as np

def cosine_similarity(A, B):
    # 使用numpy的dot函数计算两个数组的点积
    # 点积是向量A和向量B在相同维度上对应元素乘积的和
    dot_product = np.dot(A, B)
    
    # 计算向量A的欧几里得范数(长度)
    # linalg.norm默认计算2-范数,即向量的长度
    norm_A = np.linalg.norm(A)
    
    # 计算向量B的欧几里得范数(长度)
    norm_B = np.linalg.norm(B)
    
    # 计算余弦相似度
    # 余弦相似度定义为向量点积与向量范数乘积的比值
    # 这个比值表示了两个向量在n维空间中的夹角的余弦值
    return dot_product / (norm_A * norm_B)

3.4 相似性搜素

既然我们知道了可以通过比较向量之间的距离来判断它们的相似度,那么如何将它应用到真实的场景中呢?如果想要在一个海量的数据中找到和某个向量最相似的向量,我们需要对数据库中的每个向量进行一次比较计算,但这样的计算量是非常巨大的,所以我们需要一种高效的算法来解决这个问题。

高效的搜索算法有很多,其主要思想是通过两种方式提高搜索效率:

1)减少向量大小——通过降维或减少表示向量值的长度。

2)缩小搜索范围——可以通过聚类或将向量组织成基于树形、图形结构来实现,并限制搜索范围仅在最接近的簇中进行。

我们首先来介绍⼀下大部分算法共有的核心概念,也就是kmeans聚类。

K-Means聚类

我们可以在保存向量数据后,先对向量数据先进行聚类。例如下图在二维坐标系中,划定了 4 个聚类中心,然后将每个向量分配到最近的聚类中心,经过聚类算法不断调整聚类中心位置,这样就可以将向量数据分成 4 个簇。每次搜索时,只需要先判断搜索向量属于哪个簇,然后再在这一个簇中进行搜索,这样就从 4 个簇的搜索范围减少到了 1 个簇,大大减少了搜索的范围。

image-20250628090809249.png

HNSW

除了聚类以外,也可以通过构建树或者构建图的方式来实现近似最近邻搜索。这种方法的基本思想是每次将向量加到数据库中的时候,就先找到与它最相邻的向量,然后将它们连接起来,这样就构成了一个图。当需要搜索的时候,就可以从图中的某个节点开始,不断的进行最相邻搜索和最短路径计算,直到找到最相似的向量。

image-20250628091249234.png

3.5 Embedding models

对于Embedding Models我们只需要学会如何去使用就可以,是因为有非常多的模型供应商,如OpenAI、Hugging Face,国内的有百川、千帆都提供了标准接口并集成在LangChian框架中,这意味着:Embedding Models已经有人帮我们训练好了,我们只要按照其提供的接口规范,将自然语言文本传入进去,就能得到其对应的向量表示。

如何使用Baichuan Text Embeddings

要使用相关的Embedding模型,首先需要获取API密钥。您可以通过以下步骤获取:

四、简易RAG代码示例

安装环境依赖

  • pip install chromadb
  • pip install openai
  • pip install python-dotenv

创建向量数据库,并封装相应工具方法

import uuid
import os
import chromadb
from openai import OpenAI
from dotenv import load_dotenv
# 从 .env 文件加载环境变量
load_dotenv()


#创建数据库,类似创建一个文件夹
client = chromadb.PersistentClient(path="./db/chroma_demo")
#创建数据集合(库表)
collection = client.get_or_create_collection(name="collection_v1")


#构建一个数据切分的函数(原始知识库文件内容进行分块处理)
def file_chunk_list(filePath):
    #1.进行原始文件数据的读取
    with open(filePath,'r',encoding='utf-8') as fp:
        data = fp.read()
    #2.进行数据切分
    chunk_list = data.split('\n\n')
    return chunk_list

#构建一个向量转换的函数实现(阿里百炼向量模型)
def embedding_by_api(text):
    #1.创建向量模型客户端
    client = OpenAI(
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 百炼服务的base_url
        api_key=os.getenv('ALIBAILIAN_API_KEY')  # 巴里百炼apiKey
    )
    #2.向量模型调用
    completion = client.embeddings.create(
        model="text-embedding-v3",
        input=text,
        dimensions=1024,
        encoding_format="float"
    )
    return completion.data[0].embedding

#构建一个模型调用的函数
def generate_by_api(prompt):
    # 实例化客户端
    client = OpenAI( 
        base_url="https://api.deepseek.com",
        api_key=os.getenv('DEEPSEEK_API_KEY'),
    )

    # 调用 deepseek-r1 模型
    response = client.chat.completions.create(
        model="deepseek-reasoner", #调用推理模型deepseek-r1 模型标识/名称,存在推理过程
        messages=[
            {"role": "user", "content": prompt}
        ]
    )
    # 最终回复
    return response.choices[0].message.content
    

数据清洗:把文本内容导入向量数据库

下面代码只需要执行一次即可

#1.数据分块处理
documents = file_chunk_list("./files/中医问诊.txt")

#2.将分块结果进行向量转换
embeddings = [] #存放对每一个分块结果进行向量转换后的结果
for doc in documents:
    doc_emd = embedding_by_api(doc)
    embeddings.append(doc_emd)
    
#3.将生成的向量结果添加到向量数据库
ids = []
for _ in documents:
    ids.append(str(uuid.uuid4()))
    
collection.add(
    ids=ids,
    documents=documents,
    embeddings=embeddings
)

在大模型中使用RAG

该组代码可以多次测试执行

#1.接受用户提问,去向量数据库中进行检索
qs = '我好像是感冒了,症状是头痛、轻微发烧、肢节酸痛、打喷嚏和流鼻涕。'

#将用户提问转换成向量表示
qs_embedding = embedding_by_api(qs)

#去向量数据库中检索和用户提问向量相似度最高的1个结果
res = collection.query(query_embeddings=[qs_embedding,],query_texts=qs,n_results=1)
result = res['documents'][0]
context = '\n'.join(result)
                    
#2.进行提示词构建(把从向量数据库找到的内容与原始query拼接在一起,产生最终的Prompt)
prompt = f'''你是一个中医问答机器人,任务是根据参考信息回答用户问题,如果你参考信息不足以回答用户问题,请回复不知道,切记不要去杜撰和自由发挥任何内容和信息,请用中文回答,参考信息:{context},来回答问题:{qs},'''

#3.进行模型调用
result = generate_by_api(prompt)
print(result)

五、核心开发任务与落地要点

搭建基础RAG架构,需重点攻克五大核心开发任务,每个任务都直接影响系统最终效果:

  1. 文档分块策略选择:需结合文档类型(如结构化报告、非结构化散文)、领域特性(如技术文档需保留代码片段完整性)选择合适的分块粒度和方式,避免拆分破坏语义完整性;
  2. chunk向量转化与存储:需评估不同Embedding模型的效果(如语义捕捉能力、生成向量维度),同时考虑向量数据库的存储成本、扩展性;
  3. 向量相似度算法选型:需根据向量维度、数据分布选择适配的算法,平衡检索速度与准确性;
  4. 向量数据库应用:需掌握向量数据库的索引构建、参数调优(如检索阈值设置)等技巧;
  5. Prompt拼接优化:需设计合理的Prompt模板,引导模型优先参考检索到的chunk信息,避免忽视参考内容。

需特别注意: RAG系统的核心竞争力是“检索准确率”——只有检索到的chunk精准匹配用户需求,才能真正提升大模型回答质量。目前行业内尚无通用的“最优RAG方案”,因为不同场景的需求和数据特性差异极大:比如面向医疗领域的RAG,需优先保证分块的医学术语完整性和Embedding的专业语义捕捉能力;面向通用问答的RAG,则更注重分块效率和检索速度。 因此,RAG系统的落地必然是“理论+实践”的结合,需要开发者根据具体场景反复测试、调整参数(如分块粒度、检索数量、Prompt模板),积累领域适配经验,才能实现最优效果。