RAG 进阶学习:索引构建阶段与检索增强阶段

1. RAG 全流程

2. RAG 索引构建阶段

索引构建阶段的目标是准备 RAG 知识库:将原始文档转换为可供检索的向量索引。该阶段包含三个核心步骤:

索引构建阶段的流程:原始文档(不同文档类型)→文档加载 → 文本拆分 → 文本块 → 文本向量化 → 向量存储 → 知识库

2.1 文档加载

文档加载需使用专用的加载器,例如 TextLoader(用于文本文件)、PyMuPDFLoader(用于PDF文件)以及 WebBaseLoader(用于网页内容)。这些加载器会将文档加载为 Document 类对象(导入路径为 from langchain_core.documents import Document)。


Document 类包含两个主要属性:

  1. page_content:str 类型,存储文档的主要文本内容。

  2. metadata:dict 类型,存储与文档相关的元数据信息(如来源、作者、页码等)。

典型用法:通常由各类文档加载器(如 TextLoader、PyMuPDFLoader)在读取文件或数据源后生成并返回 Document 对象列表。

python 复制代码
from langchain_core.documents import Document

# 手动创建 Document 对象
doc = Document(
    page_content="这是文档的正文内容。",
    metadata={"source": "example.txt", "page": 1}
)

print(doc.page_content)  # 输出: 这是文档的正文内容。
print(doc.metadata)      # 输出: {'source': 'example.txt', 'page': 1}


不同的文档加载器虽然具有各自特定的参数,但均提供两个统一的加载方法:load() 与 lazy_load()。(其实是在langchain的接口中规定了,这两个方法。)

  1. load():一次性加载所有文档,返回完整的 Document 列表。
  2. lazy_load():惰性加载,返回一个生成器,每次迭代加载一个 Document 对象。
2.1.1 PyMuPDFLoader - PDF文档加载器

from langchain_community.document_loaders import PyMuPDFLoader

python 复制代码
from langchain_community.document_loaders import PyMuPDFLoader

file_path = r'E:\python\k-ai-knowledge-2.0\rag-practice\data\files\sample_document.pdf'

loader = PyMuPDFLoader(file_path=file_path)
# 1.load() 一次性加载所有文档
docs = loader.load()
print(type(docs)) # <class 'list'>
print(len(docs)) # 6
print(docs) # [Document(metadata={'producer': 'Skia/PDF m119', 'creator': 'Chromium',

# 2.lazy_load():惰性加载
for document in loader.lazy_load():
    print(document)
2.1.2 TextLoader - TXT文档加载器

TextLoader 用于读取文本文件,并将其全部内容加载至 page_content 字段中。

python 复制代码
from langchain_community.document_loaders import TextLoader

file_path = r'E:\python\k-ai-knowledge-2.0\rag-practice\data\files\北京有什么好玩的.txt'

# 加载文本文件
loader = TextLoader(file_path=file_path, encoding="utf-8")
docs = loader.load()
print(f"加载了 {len(docs)} 个文档")
print(f"内容: {docs[0].page_content[:100]}...")
print(f"元数据: {docs[0].metadata}")

若文档内容较大,则需进行文本拆分,将其划分为多个语义块(chunk)。

2.1.3 WebLoader - 网页加载器
python 复制代码
from langchain_community.document_loaders import WebBaseLoader

# 加载网页内容
loader = WebBaseLoader("https://python.langchain.com/docs/")
documents = loader.load()

print(f"加载了 {len(documents)} 个文档") # 加载了 1 个文档
print(f"元数据:{documents[0].metadata}") # 元数据:{'source': 'https://python.langchain.com/docs/', 'title': 'LangChain overview - Docs by LangChain',
print(f"标题: {documents[0].metadata.get('title', 'N/A')}") # 标题: LangChain overview - Docs by LangChain
print(f"URL: {documents[0].metadata['source']}") # URL: https://python.langchain.com/docs/
print(f"内容长度: {len(documents[0].page_content)} 字符") # 内容长度: 4311 字符

2.2 文本拆分

文本拆分的目的是将一段很长的文本拆分成多个较小的文本块(chunks),文本拆分有以下几个核心目标:

  • 适应模型上下文窗口限制:大语言模型对输入文本的长度有上限要求,过长的文本无法直接处理

  • 提升检索精度:在RAG(检索增强生成)场景中,较短的文本块能提高向量检索的命中率和相关性

在LangChain中,提供了推荐的文本分割器------递归字符文本分割器 RecursiveCharacterTextSplitter,用于执行递归式字符级文本拆分。


2.2.1 RecursiveCharacterTextSplitter
python 复制代码
text = """春秋战国时期是中国历史上思想文化最为繁荣的时代。诸子百家争鸣,儒家、道家、墨家、法家等学派相继兴起。孔子周游列国,宣扬仁义礼智信的思想。老子著《道德经》,阐述无为而治的哲学理念。

秦始皇统一六国后,推行书同文、车同轨、统一度量衡等政策。焚书坑儒虽然巩固了中央集权,但也造成了文化的巨大损失。万里长城的修建抵御了北方游牧民族的侵扰,成为中华民族的象征之一。

唐朝是中国封建社会的鼎盛时期。贞观之治、开元盛世使国力达到顶峰。李白、杜甫等诗人的作品流传千古。丝绸之路的畅通促进了中外经济文化交流。长安城成为当时世界上最大的国际化都市。"""

使用RecursiveCharacterTextSplitter文本分割器进行文本分割的流程。

python 复制代码
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

file_path = r'E:\python\k-ai-knowledge-2.0\rag-practice\data\files\历史文化'

# 1.加载文档
loader = TextLoader(file_path=file_path, encoding='utf-8')
docs = loader.load()

# 2.文本拆分
splitter = RecursiveCharacterTextSplitter(
    chunk_size=55,
    chunk_overlap=8,
    separators=["\n\n", "\n", "。", ",", "、", " ", ""],
    length_function=len
)
chunks = splitter.split_text(docs[0].page_content)
for i, chunk in enumerate(chunks):
    print(f"--- 块 {i+1} (长度: {len(chunk)}) ---")
    print(chunk)
    print()

拆分规则如下:

第一步:用 \n\n 分割(段落级)

按空行分割,得到3段:

  1. 春秋战国时期是中国历史上思想文化最为繁荣的时代。诸子百家争鸣,儒家、道家、墨家、法家等学派相继兴起。孔子周游列国,宣扬仁义礼智信的思想。老子著《道德经》,阐述无为而治的哲学理念。

  2. 秦始皇统一六国后,推行书同文、车同轨、统一度量衡等政策。焚书坑儒虽然巩固了中央集权,但也造成了文化的巨大损失。万里长城的修建抵御了北方游牧民族的侵扰,成为中华民族的象征之一。

  3. 唐朝是中国封建社会的鼎盛时期。贞观之治、开元盛世使国力达到顶峰。李白、杜甫等诗人的作品流传千古。丝绸之路的畅通促进了中外经济文化交流。长安城成为当时世界上最大的国际化都市。

检查:每段是否 ≤ chunk_size(55)?

  • 段1 > 55 ❌ 继续分割

  • 段2 > 55 ❌ 继续分割

  • 段3 > 55 ❌ 继续分割

第二步:用 \n 分割(行级)

段1、段2、段3 内部都没有换行符 \n,切不动,换下一级分隔符。

第三步:用 。 分割(句子级)

声明:分割后的标点 。 归后一段开头

段1 用 。 分割

原始:春秋战国时期是中国历史上思想文化最为繁荣的时代。诸子百家争鸣,儒家、道家、墨家、法家等学派相继兴起。孔子周游列国,宣扬仁义礼智信的思想。老子著《道德经》,阐述无为而治的哲学理念。

按 。 切开,句号归后一段:

合并检查:

  • 片段1 + 片段2 = 春秋战国时期...时代 + 。诸子百家...兴起 → 合并 → 块1

  • (片段1+2) + 片段3 = 块1 + 。孔子周游...思想 → 不合并(超过55)

  • 片段3 + 片段4 = 。孔子周游...思想+。老子著...理念 → 合并

  • (片段3+4) + 片段5 = 。孔子周游...理念 + 。 → 合并 → 块2

段1 结果:


段2 用 。 分割

原始:秦始皇统一六国后,推行书同文、车同轨、统一度量衡等政策。焚书坑儒虽然巩固了中央集权,但也造成了文化的巨大损失。万里长城的修建抵御了北方游牧民族的侵扰,成为中华民族的象征之一。

按 。 切开,句号归后一段:

合并检查:

  • 片段1 + 片段2 = 秦始皇...政策 + 。焚书坑儒...损失 → 合并 → 块3

  • (片段1+2) + 片段3 = 块3 + 。万里长城...之一 → 不合并(超过55)

  • 片段3 + 片段4 = 。万里长城...之一 + 。 → 合并 → 块4

段2 结果:


段3 用 。 分割

原始:唐朝是中国封建社会的鼎盛时期。贞观之治、开元盛世使国力达到顶峰。李白、杜甫等诗人的作品流传千古。丝绸之路的畅通促进了中外经济文化交流。长安城成为当时世界上最大的国际化都市。

按 。 切开,句号归后一段:

合并检查:

段3 结果:


完整流程总结

最终6个块

2.2.1.1 如何在分隔符后面拆分?

🎯 默认情况下,RecursiveCharacterTextSplitter 匹配到分隔符时,会在分隔符前面切分,导致分隔符跑到下一个块的开头。

解决方法:用正则表达式的正向肯定预查 (?<=...),让匹配的位置从"分隔符本身"变成"分隔符后面的位置"。

具体做法:把 separators 从 "。", "!", "?" 改成 "(?\<=。)", "(?\<=!)", "(?\<=?)",同时设置 is_separator_regex=True。

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 测试文本(更长一些)
text = "今天天气很好。我们去吃饭!你觉得怎么样?走吧。明天还要上班。记得早起!"

# ===== 模式1:False(普通字符串,切掉标点)=====
splitter1 = RecursiveCharacterTextSplitter(
    separators=["。", "!", "?"],
    is_separator_regex=False,
    chunk_size=10,   # 改小,强制切分
    chunk_overlap=0,
)
chunks1 = splitter1.split_text(text)
print("\n【is_separator_regex=False】切分结果:")
for i, chunk in enumerate(chunks1, 1):
    print(f"  块{i}: '{chunk}'")

# ===== 模式2:True(正则表达式,保留标点)=====
splitter2 = RecursiveCharacterTextSplitter(
    separators=["(?<=。)", "(?<=!)", "(?<=?)"],
    is_separator_regex=True,
    chunk_size=10,   # 同样改小
    chunk_overlap=0,
)
chunks2 = splitter2.split_text(text)
print("\n【is_separator_regex=True】切分结果:")
for i, chunk in enumerate(chunks2, 1):
    print(f"  块{i}: '{chunk}'")

拆分结果如下所示:

2.2.1.2 chunk_overlap 什么时候生效?

chunk_overlap 的目的很简单:避免一句话或一段意思被硬生生从中间砍成两半,导致语义不连贯。所以,这个参数只有在按固定字符数硬切的时候才会起作用。


当某个最小的不可再分单元(比如一个没有分隔符的超长单词、或按最后分隔符 "" 切出的单个字符)本身的长度已经超过了 chunk_size 时,分割器会启动"保底策略",强行按 chunk_size 切割这个单元,此时 chunk_overlap 就会生效,用于控制相邻两个强制切分片段之间的重叠字符数。

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 测试文本:一个没有空格和标点的超长字符串
text = "这是一个超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长的单词"# 创建分割器
splitter = RecursiveCharacterTextSplitter(
    chunk_size=20,  # 每个块最大20个字符
    chunk_overlap=5,  # 块之间重叠5个字符
    separators=[" ", ""],  # 先尝试按空格切,最后按字符切
    length_function=len
)
# 执行分割
chunks = splitter.split_text(text)

for i, chunk in enumerate(chunks):
    print(f"\n--- 块 {i + 1} (长度: {len(chunk)}) ---")
    print(f"内容: {chunk}")
    print("-" * 40)
python 复制代码
块1: [────────────────────────]
     这是一个超长超长超长超长超长超长超长超长
                           [-----]  ← 末尾5字符: "长超长超长"
                              ↓↓↓↓↓
块2:                      [────────────────────────]
                          长超长超长超长超长超长超长超长超长超长的
                           [-----]  ← 开头5字符: "长超长超长"
                          相同的5个字符 = 重叠 ✅
                    
--------------------------------------------------------------------------------------
块2:                           [────────────────────────]
                               长超长超长超长超长超长超长超长超长超长的
                                                [-----]  ← 末尾5字符: "超长超长的"
                                                   ↓↓↓↓↓
块3:                                           [────────]
                                               超长超长的单词
                                                [-----]  ← 开头5字符: "超长超长的"
                                               相同的5个字符 = 重叠 ✅

🔢 重叠关系

2.3 文本向量化

文本向量化是将拆分后得到的多个文本块(chunk),通过嵌入模型转换为高维向量(即一个 float 类型的列表、Listfloat)表示的过程。

其目标是把一个词、一句话或一篇文章,映射成一个高维空间中的数值向量。在这个空间里,语义越相似的文本,其对应的向量在几何上就越接近(例如余弦相似度更高)。举例来说,"猫坐在垫子上"与"一只猫在垫子上休息"这两句话语义相近,它们的向量在空间中的夹角很小,余弦相似度接近1;而"猫坐在垫子上"与"今天天气很好"语义无关,其向量近乎垂直,余弦相似度接近0。


注意: 实际应用中,向量维度通常很高(如1536维),这里为了直观理解,假设为二维。


如何实现文本向量化?

要实现文本向量化,需要借助嵌入模型(embedding model)。例如,我们常使用的阿里云百炼平台,也提供了这类嵌入模型。大模型服务平台百炼控制台、文本与多模态向量化

2.3.1 将文本转换成向量

具体调用方式如下(以 LangChain 集成 DashScope 为例):以下代码将输入文本"猫坐在垫子上"转换成了一个长度为1536的浮点数向量。

python 复制代码
from langchain_community.embeddings import DashScopeEmbeddings

# 创建模型对象,默认使用 text-embedding-v1
embedding_model = DashScopeEmbeddings()

user_input = "猫坐在垫子上"
result = embedding_model.embed_query(user_input)
print(type(result))  # <class 'list'>
print(len(result))  # 1536
print(result)  # 输出一个包含1536个浮点数的向量列表
2.3.2 将批量文本转换成向量

将多个文本转换成向量:以下代码将输入文本"猫坐在垫子上"、"一只猫在垫子上休息"和"今天天气很好"批量转换成了三个长度为1536的浮点数向量。

python 复制代码
from langchain_community.embeddings import DashScopeEmbeddings

# 创建嵌入模型对象,默认使用 text-embedding-v1
embedding_model = DashScopeEmbeddings()

# 批量处理多个文本
texts = ["猫坐在垫子上", "一只猫在垫子上休息", "今天天气很好"]
results = embedding_model.embed_documents(texts)

# 每个文本被转换成一个1536维的浮点数向量
for text, vector in zip(texts, results):
    print(f"文本:{text}")
    print(f"向量维度:{len(vector)}")
    print(f"向量:{vector}...\n")
2.3.3 测试文本语义与余弦相似度的关系

根据我们之前所讲的理论:两个文本的语义越相似,它们的向量在空间中的夹角越小,余弦相似度就越高。下面通过实际代码来验证这一结论。

python 复制代码
from langchain_community.embeddings import DashScopeEmbeddings
import numpy as np

def cosine_similarity(a, b):
    """计算余弦相似度"""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

if __name__ == '__main__':
    # 准备三个测试文本:前两个语义相近,第三个语义无关
    texts = ["猫坐在垫子上", "一只猫在垫子上休息", "今天天气很好"]
    vectors = DashScopeEmbeddings().embed_documents(texts)

    # 计算相似度并观察结果
    print(f"'{texts[0]}' 与 '{texts[1]}' 的相似度: {cosine_similarity(vectors[0], vectors[1])}")
    print(f"'{texts[0]}' 与 '{texts[2]}' 的相似度: {cosine_similarity(vectors[0], vectors[2])}")
    print(f"'{texts[1]}' 与 '{texts[2]}' 的相似度: {cosine_similarity(vectors[1], vectors[2])}")

结果如下:

2.3.4 总结

文本向量化的本质

  • 将文本(词、句子、文章)转换为固定长度的浮点数列表(向量)

  • 核心目标:将文本映射到高维空间,使语义相似的文本在几何上彼此接近

实现方式

  • 需要借助嵌入模型(Embedding Model)

  • 以阿里云百炼平台的 text-embedding-v1 为例

  • 支持单个文本(embed_query)和批量文本(embed_documents)两种调用方式

  • 输出向量维度为 1536

结论: 语义相近的文本,余弦相似度较高;语义无关的文本,余弦相似度接近 0。

2.4 向量数据库的存储

文本向量化完成后,需要将向量存储到向量数据库中,以便后续进行相似度检索。常见的向量数据库包括 Chroma、Milvus、Qdrant 等。

=

2.4.1 相似度搜索

向量数据库的搜索本质上是相似度搜索(Similarity Search),而非传统关系型数据库(如MySQL)中的等值查询(Exact Match Query)。


2.4.2 向量数据库 Chroma 的存储

如果没有安装,就先安装:pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple langchain-chroma chromadb。

基于Chroma完成增加、删除、查询:rag-practice/12_chromadb的使用.ipynb · liangzai/k-ai-knowledge-2.0 - Gitee.com

2.4.3 最大边际相关性搜索 MMR
2.4.3.1 为什么需要 MMR?

在讲解MMR是什么之前,我们先来看一下如下代码:

python 复制代码
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_chroma import Chroma
from langchain_core.documents import Document

if __name__ == '__main__':
    # 用户查询
    user_query = "如何学习人工智能?"  # 向量数据库中存储的文本内容
    documents = [
        "学习AI需要先掌握线性代数、概率论和微积分,这是算法的基础",
        "如何做红烧肉,需要准备五花肉、冰糖和生抽",
        "Python是AI开发的首选语言,需要熟悉numpy、pandas等数据处理库",
        "通过Kaggle竞赛实战,从泰坦尼克号预测等项目开始练手",
        "人工智能的核心数学知识包括矩阵运算、概率统计和导数计算",
        "推荐李飞飞的CS231n课程和《深度学习》花书,系统学习理论"
    ]

    # 转换为 Document 对象
    data = [Document(page_content=doc) for doc in documents]

    # 创建 Chroma 客户端并存入文档
    chroma_db = Chroma(
        collection_name="ai_learning",
        embedding_function=DashScopeEmbeddings(),
        persist_directory="./chroma_db",
        collection_metadata={"hnsw:space": "cosine"}
    )

    # 添加文档
    chroma_db.add_documents(
        documents=data,
        ids=[f"id-{i}" for i in range(len(data))]
    )

    # 计算查询与各文本的相似度(使用 Chroma 检索)
    print(f"=== 查询 Q: '{user_query}' ===")
    print("-" * 65)

    # 获取所有文档及其相似度
    results = chroma_db.similarity_search_with_relevance_scores(user_query, k=len(documents))

    for i, (doc, score) in enumerate(results):
        print(f"文档{i}: '{doc.page_content[:30]}...' 相似度: {score:.4f}")

代码结果运行如下:

假设我们现在需要选择K=3个文档,那么我们基于相似度检索选中的文本就是:文档0、文档1、文档2。

问题分析:

问题1:信息冗余

  • 文档0和文档1都在讲"数学基础"
  • 两段内容高度相似,本质上是在重复同一个信息
    问题2:上下文浪费
  • 如果只有3个上下文位置给大模型
  • 实际上只提供了2个有效信息维度:数学 + Python
    问题3:信息不全面
  • 用户问的是"如何学习人工智能"
  • 丢失了"课程推荐"(文档4)、"实战项目"(文档3)等有价值的信息

为什么会出现这种情况?

因为向量检索只关心"相关性",不关心"重复性"。当数据库中存在多个与用户查询高度相似、但彼此表达意思相同的片段时,检索结果就会全部包含它们,导致信息冗余。

2.4.3.2 什么是 MMR?

在检索增强生成(RAG)中,MMR(最大边际相关性,Maximum Marginal Relevance) 是一种用于平衡检索结果"相关性"与"多样性"的排序与选择算法。

简单来说,它的核心思想是:在选择要返回给大模型(LLM)的文本片段时,既要保证每个片段与问题的相关度高,又要避免这些片段之间过于相似(冗余)。


现在的向量数据库其实都支持 MMR,比如 Chroma 的 MMR 操作如下:

python 复制代码
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_chroma import Chroma
from langchain_core.documents import Document
import numpy as np

def cosine_similarity(a, b):
    """计算余弦相似度"""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

if __name__ == '__main__':
    # 1. 准备文档数据
    documents = [
        "学习AI需要先掌握线性代数、概率论和微积分,这是算法的基础",
        "人工智能的核心数学知识包括矩阵运算、概率统计和导数计算",
        "Python是AI开发的首选语言,需要熟悉numpy、pandas等数据处理库",
        "通过Kaggle竞赛实战,从泰坦尼克号预测等项目开始练手",
        "推荐李飞飞的CS231n课程和《深度学习》花书,系统学习理论",
        "如何做红烧肉,需要准备五花肉、冰糖和生抽",
    ]

    # 转换为 Document 对象
    data = [Document(page_content=doc) for doc in documents]

    # 2. 创建 Chroma 客户端并存入文档(显式指定余弦相似度)
    chroma_db = Chroma(
        collection_name="ai_learning",
        embedding_function=DashScopeEmbeddings(),
        persist_directory="./chroma_db",
        collection_metadata={"hnsw:space": "cosine"}  # 指定使用余弦相似度
    )

    # 添加文档
    chroma_db.add_documents(
        documents=data,
        ids=[f"id-{i}" for i in range(len(data))]
    )
    print("文档已存入 Chroma 数据库")
    print("-" * 65)

    # 3. 用户查询
    user_query = "如何学习人工智能?"# 4. 普通相似度检索(k=3)
    print(f"=== 查询 Q: '{user_query}' ===")
    print("\n【普通相似度检索 results】")
    results = chroma_db.similarity_search_with_relevance_scores(user_query, k=3)

    for i, (doc, score) in enumerate(results):
        print(f"排名{i + 1}: 相似度={score:.4f} | 内容: {doc.page_content[:40]}...")

    print("\n" + "=" * 65)

    # 5. MMR 检索(k=3,fetch_k=5)
    print("\n【MMR 检索 results】")
    mmr_results = chroma_db.max_marginal_relevance_search(
        query=user_query,
        k=3,  # 最终返回 3 个,top-k
        fetch_k=5,  # 候选池 5 个
        lambda_mult=0.6  # 平衡参数
    )

    for i, doc in enumerate(mmr_results):
        print(f"排名{i + 1}: 内容: {doc.page_content[:40]}...")

运行结果如下:

可以看到:

  • 普通相似度检索:返回的前3个文档中,文档0和文档1都在讲解"数学基础",内容高度冗余,导致有效信息维度只有2个(数学 + Python)

  • MMR 检索:跳过了与文档0高度相似的文档1(数学基础B),选择了文档4(课程+书籍推荐),使最终结果覆盖了3个不同维度:数学基础 + Python编程 + 课程推荐。在保证相关性的前提下增加了检索结果的多样性。

2.4.3.3 MMR 在 RAG 流程中的步骤

参数解释:

MMR 并非在初次检索时使用,而是在重排阶段发挥作用。

2.4.3.4 MMR 算法的工作原理

参考链接:RAG 的检索优化:MMR 平衡相关性与多样性

MMR(Maximum Marginal Relevance,最大边际相关性)的目标是平衡相关性与多样性,避免检索结果过于同质化。

MMR 的核心计算公式如下:


原文此处为飞书画板:MMR 算法工作原理示意图。

公式参数解释:

公式拆解:

这个公式的核心思想就是:相关性减去冗余性。

  • 相关性:文档 Di 与查询 Q 的匹配程度,越高越好。

  • 冗余性:文档 Di 与已选文档集 S 的相似程度,越高说明信息越重复,应当扣分。

当 λ 越大时:

  • 相关性 的权重越大

  • 冗余性 的权重越小(因为 1−λ 变小)

因此,MMR 在每一步都选择相关性最高、但与已选文档最不重复的文档,从而在相关性与多样性之间取得平衡。


MMR 完整的算法流程如下:

3. 检索增强生成阶段

检索增强生成(Retrieval-Augmented Generation, RAG)阶段的核心,是将用户提问与向量数据库中检索到的相关内容相结合,再交由大语言模型生成最终答案。

核心流程如下:

python 复制代码
用户提问:「猫在哪?」
     		  	 │
     		     ▼
┌───────────────────────────────────────┐
│  1. 向量检索                            │
│  - 将用户提问转换为向量(Embedding)      │
│  - 在向量数据库中检索相似度最高的 Top-K  │
│    相关内容(如文档片段)                │
└───────────────────────────────────────┘
       			 │
      		 	 ▼
┌───────────────────────────────────────┐
│  2. 构建增强提示                        │
│  - 将检索到的内容作为上下文              │
│  - 组合成最终 Prompt:                  │
│    「基于以下内容回答:...               │
│      用户问题:...」                    │
└───────────────────────────────────────┘
      		  	  │
     		      ▼
┌───────────────────────────────────────┐
│  3. 生成答案                            │
│  - 将增强后的 Prompt 发给大语言模型      │
│  - LLM 结合上下文生成准确答案            │
└───────────────────────────────────────┘
      		      │
      		      ▼
      答案:「猫在垫子上休息」

关键点总结:

🎯 RAG 的核心价值:让大语言模型不依赖自身训练记忆,而是基于实时检索到的外部知识生成答案,从而减少幻觉、增强可解释性、支持知识更新。

相关推荐
nice_lcj52010 小时前
【从零理解 Spring AI · 15】让检索更准:先宽召回,再精排序
java·后端·rag·spring ai·ai应用开发·检索优化·重排序
Java后端的Ai之路2 天前
RAG 还是 Agent 自主检索?Claude Code 删掉向量库之后,我把选型讲透了
开发语言·人工智能·prompt·agent·rag
Flynn Liang2 天前
AiServices 源码级拆解:一个接口如何变成 RAG Agent
java·langchain·源码分析·rag·langchain4j·aiservices
Flynn Liang2 天前
LangChain4j 1.23 架构全景:Java 大模型应用的 6 层抽象
java·langchain·大模型·rag·ai agent·langchain4j
Flynn Liang2 天前
LangChain4j 生产级工程骨架:12 个模块 + Docker 一键启动
java·spring boot·langchain·rag·langchain4j
OxYGC3 天前
智能体项目复盘 · 智能客服篇:对话栈、Flow 与策略集成器——LLM 是怎么嵌进传统多轮对话架构的
对话系统·智能客服·rag·智能体
橙臣程4 天前
RAG、SFT、RL 怎么选?基座模型越来越强,什么时候还值得微调
rag
雪碧聊技术7 天前
RAG技术解析、落地指南(详解)
向量数据库·rag·增强检索生成