RAG新人学习笔记(未完结)

0 阅读52分钟

前情提要:该文章是博主自己总结+不断优化ai后给出的比较好理解的点+总结别的up知识点,一点一点搜集,总结,整合的还是挺全面的笔记,博主也是将他视为自己的RAG这块的学习笔记,也适合新入一同学习

一:为什么要引入RAG知识库

用自己的话来说,大模型本质就是不断搜寻网络上的数据,然后对用户的问题进行预测,从网络上搜寻与用户问题相近的回答,然后整合结果,返回给用户。既然本质就是搜索数据,那么就会出现以下情况:幻觉知识滞后

1.幻觉

模型生成看似合理、逻辑通顺,但与客观事实完全不符、无中生有或错误拼接的内容,即 “一本正经地胡说八道”,典型特征就是他会造假我们的数据,比如我们写论文时让他查询两篇论文数据引用自哪里,他可能会返回给我们两篇根本不存在的引用,如果你不仔细看,根本不会发现,但是光看逻辑上来说,模型确实给你完成了任务。幻觉会出现以下特征 ,如:

无中生有:编造不存在的人名、事件、数据、文献引用。
事实错误:张冠李戴、篡改历史、给出错误结论。

为什么会这样?(底层 4 个原因)
1. 它不是 “记忆”,而是 “预测下一个字”

大模型的工作只有一件事:根据前面的话,预测下一个最可能出现的字。

它不是在 “回忆知识”,而是在组词造句

只要句子通顺、合理、像人话,它就会输出。真不真实,它没有感知。


2. 它没有 “真假判断模块”

模型不知道:

  • 这件事是否发生过
  • 数据是否真实
  • 引用是否存在

它只懂:

  • 语法对不对
  • 逻辑顺不顺
  • 语气自不自然

真假 = 不在它的判断体系里。


3. 训练数据里本来就有噪音、冲突、错误

互联网内容本身:

  • 有错别字
  • 有谣言
  • 有过时信息
  • 有不同说法

模型学到的是混合概率,不是唯一真理。当信息冲突,它就会瞎编一个最通顺的


4. 你逼它 “必须回答”,它就只能编

你问它一个它不懂 / 不确定的问题,它不会像人一样说:“我不知道。”

它的训练目标是:必须给出流畅、完整、看起来专业的回答。

于是它开始:

  • 拼接信息
  • 补全逻辑
  • 编造细节

→ 这就是幻觉

2.知识滞后

模型的知识有明确的时间截止点,无法获取该时间点之后发生的新事件、新数据、新知识。简单来说模型本质就会去网上查找数据,如果我们想查询24年中国人口总数,但是网络上只公布了23年的,模型就无法帮助我们查询

核心特征

  • 时间边界:知识停留在训练数据的 “截止日期”(如 2023 年 10 月)。
  • 非编造:不是故意说谎,而是确实不知道最新信息。
  • 可预测:询问截止日后的事件,模型通常会承认信息不足,或基于旧数据推测。

常见例子

  • 问:“2026 年 3 月 18 日的股市行情如何?” 模型无法实时回答,因为其知识未更新到此刻。
  • 问:“2025 年新发布的手机有哪些?” 若模型训练截止于 2023 年,则无法提供准确信息。
3.知识错误

简而言之就是网络上的数据如果都是假数据,那么模型就会返回假数据给我们

4.知识局限

每个公司都有自己内部的业务数据啥的,这些数据是不公布在网络上的,模型就无法获取到这些信息。知识局限主要分 3 类

1. 时间局限(消息滞后)
  • AI 只学到某个时间点之前的内容
  • 之后发生的事、新数据、新新闻、新政策
  • 完全没见过,所以不知道

这就是你之前问的消息滞后

2. 内容局限(没见过的信息)
  • 没公开在网上的内容
  • 你的私人信息、公司内部文档、私密聊天
  • 小众领域、极专业的冷知识
  • 刚写出来、刚发出去、还没被爬取的内容

AI 没学过 = 不知道

3. 理解局限(推理能力不够)

有些东西它 “字面上见过”,但真不懂

  • 特别复杂的逻辑、数学证明
  • 需要真实世界经验才能懂的常识
  • 多层推理、多条件嵌套问题
  • 抽象、隐喻、弦外之音

它能模仿着说,但不一定真理解

二:RAG知识库的介绍

1.RAG定义

RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合检索生成能力的大模型优化技术,通过在生成答案前,从外部知识库中检索与用户问题相关的真实、最新数据,将其作为上下文补充给大模型,最终让模型基于检索到的可靠信息生成回答。它是基于以下工作流程:

1. 问题解析

用户输入问题后,系统先对问题进行语义理解(如关键词提取、意图识别),明确需要检索的核心信息方向(例:用户问 “2024 年行业政策”,核心检索方向是 “2024 年 + 目标行业 + 政策文件”)。

2. 外部检索

基于解析后的问题,系统从预设的外部知识库中快速匹配相关内容:

  • 知识库类型:私有文档(企业手册、内部数据)、公开数据(最新新闻、政策文件)、实时数据(股市行情、天气信息)等;

  • 检索方式:通过向量数据库(将文本转化为语义向量,实现快速相似性匹配)提升检索效率与准确性。

3. 上下文补充

系统将检索到的相关信息(如具体条款、数据、案例)整理为结构化上下文,与用户问题一起同步给大模型,相当于给模型 “提供参考资料”。

4. 生成回答

大模型不再仅依赖自身训练参数,而是基于 “问题 + 参考资料” 进行逻辑整合,生成符合要求的回答,且可追溯答案的信息来源。

简而言之:它就相当于给ai配备了一个标准知识库,ai会根据用户问题,到我们的知识库中检索到对应的数据,通过ai润色后返回给我们。因为知识库的数据是我们提前提供的,数据质量能得到保证,从而尽可能减少幻觉等问题

2.RAG工作流程

Capture_20260318_135713.jpg

1.离线准备阶段(构建知识库)

这个阶段是 RAG系统的基础,目的是将原始文档处理并存储为易于检索的格式。

(1)文档加载(Document Loading)

从各种数据源(如PDF、Word、网页、数据库、企业内部文档等)中加载原始数据。

(2)文本分块(Text Chunking)

将加载的长文档切分成较小的、语义连贯的文本片段(chunks)。这一步至关重要,因为大语言模型有输入长度限制,且过长的文本会引入噪音。常见的分块策略包括按固定大小、按句子或段落边界、或按文档结构(如Markdown的标题) 进行分割。

(3)向量化与存储(Embedding & Storage)

使用嵌入模型(Embedding Model)将每个文本块转换为高维向量(即数字数组),这些向量能捕捉文本的语义信息。然后,将这些向量连同其对应的原始文本块一起存储到向量数据库 (Vector Database)中,为后续的快速相似度搜索做好准备。

下面来讲解一下具体内容:

1、文档加载器 (Document Loaders)

LangChain4j 提供了多种文档加载器,用于从不同来源加载文档:

加载器功能描述
FileSystemDocumentLoader从文件系统加载文档,支持单文件、多文件和递归加载 
ClassPathDocumentLoader从类路径加载文档 
UrlDocumentLoader从 URL 加载文档
使用示例
// 文件系统加载器 - 单文件
Document doc = FileSystemDocumentLoader.loadDocument(Path.of("file.txt"), new TextDocumentParser());

// 递归加载目录下所有文件
List<Document> docs = FileSystemDocumentLoader.loadDocumentsRecursively(Path.of("docs/"), new TextDocumentParser());

// URL 加载器
Document doc = UrlDocumentLoader.load("https://example.com/page.html", new TextDocumentParser());

2、文档解析器 (Document Parsers)

文档解析器用于将不同格式的文档(PDF、Word、Excel 等)转换为 Document 对象:

表格

Maven 依赖功能描述底层技术
langchain4j-document-parser-apache-pdfbox解析 PDF 文档Apache PDFBox 
langchain4j-document-parser-apache-poi解析 Excel 和 Word 文档Apache POI 
langchain4j-document-parser-apache-tika解析多种文档格式(通用型)Apache Tika 
langchain4j-community-document-parser-llamaparse使用 LlamaParse 解析文档(社区版)LlamaParse

Document 对象是一个核心数据结构,用于表示从外部来源(如 PDF、Word、网页等)加载的原始文本内容及其元数据

在 LangChain4j 中,Document 类定义如下(简化版):

public class Document {
    private final String text;          // 文档的完整文本内容
    private final Metadata metadata;    // 元数据(来源、作者、时间等)
    
    // 构造方法
    public static Document from(String text, Metadata metadata) { ... }
    public static Document from(String text) { ... } // 无元数据
    
    // Getter
    public String text() { return text; }
    public Metadata metadata() { return metadata; }
}
🔹 关键字段说明:

表格

字段类型说明
textString整个文档的纯文本内容(解析后,不含格式)
metadataMetadata键值对形式的元信息,例如: • "source""manual.pdf" • "page""5" • "url""https://example.com"
使用示例
// 文件系统加载器 - 单文件
Document doc = FileSystemDocumentLoader.loadDocument(Path.of("file.txt"), new TextDocumentParser());

// 递归加载目录下所有文件
List<Document> docs = FileSystemDocumentLoader.loadDocumentsRecursively(Path.of("docs/"), new TextDocumentParser());

// URL 加载器
Document doc = UrlDocumentLoader.load("https://example.com/page.html", new TextDocumentParser());
3、文档分割器 (Document Splitters)

文档分割器用于将长文本分割成较小的片段,以适应大模型的上下文窗口限制:

分割器功能描述特点
DocumentSplitters.recursive()递归字符分割器按字符递归分割,可配置块大小和重叠 
DocumentBySentenceSplitter按句子分割基于 Apache OpenNLP 的智能句子检测 
DocumentByParagraphSplitter按段落分割保留段落语义完整性
CharacterSplitter固定字符分割按固定字符数分割,无视语义 
HierarchicalDocumentSplitter层次化分割支持多级分割策略

使用示例

// 递归分割器 - 最大 256 tokens,重叠 0
DocumentSplitter splitter = DocumentSplitters.recursive(256, 0);
List<TextSegment> segments = splitter.split(document);

// 按句子分割(需引入 easy-rag 或相关依赖)
Tokenizer tokenizer = new HuggingFaceTokenizer();
DocumentBySentenceSplitter sentenceSplitter = new DocumentBySentenceSplitter(100, 0, tokenizer);
List<TextSegment> segments = sentenceSplitter.split(document);

// 结合嵌入存储的完整流程
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
    .documentSplitter(DocumentSplitters.recursive(256, 0))
    .embeddingModel(embeddingModel)
    .embeddingStore(embeddingStore)
    .build();
ingestor.ingest(document);
4、文本向量化

文本向量化(Text Vectorization)是将人类可读的自然语言文本(如句子、段落)转换为计算机可计算的数值向量(一串数字)的过程。这是现代 AI(尤其是大模型和 RAG 系统)理解语义的核心技术。

一、为什么需要文本向量化?

计算机无法直接“理解”文字,但可以高效处理数字。 而人类语言的关键在于语义(意思),不是字面。 向量就是让计算机理解语义方法,将中文语义的远近关系转化为向量之间的举例,这样计算机就能检索出符合符合问题的向量,从而回答我们的问题

✅ 向量化的目标:让语义相近的文本,在向量空间中距离更近

🌰 举例:

  • “如何重置密码?”
  • “怎样修改我的登录密码?”

这两句话用词不同,但意思高度相似
理想情况下,它们的向量在空间中应该非常接近

而:

  • “如何重置密码?”
  • “今天天气怎么样?”

语义无关,向量应相距很远


二、向量长什么样?

一个文本向量通常是一个固定长度的浮点数数组,例如:

[0.82, -0.34, 1.21, ..., 0.05]  # 长度可能是 384、768、1024 等
  • 每个数字代表文本在某个“语义维度”上的强度(如“疑问语气”、“技术性”、“情感倾向”等)。
  • 这些维度由模型在训练时自动学习,人类难以直观解释(称为“隐空间”)。

三、嵌入模型(Embedding Model)

嵌入模型是一种 AI 模型,它能把文字(词、句子、段落)转换成一串数字(向量),这串数字能代表这段文字的“意思。简单来说,他就是将我们之前切割好的文本块转化为向量的工具### 🔑 核心作用

  • 把语言变成数字 → 计算机才能处理
  • 保留语义信息 → 意思相近的文字,数字也“靠近”

🌰 举个例子

表格

文本向量(简化示意)
“如何重置密码?”[0.82, -0.34, 1.21, …]
“怎样修改登录密码?”[0.80, -0.32, 1.19, …]
“今天天气怎么样?”[-0.15, 0.91, -0.44, …]

✅ 前两句意思相似 → 向量很接近
❌ 和“天气”那句意思无关 → 向量相差很远

四、向量维度的定义

向量维度 = 向量中包含的数字的个数

向量: [0.1, -0.3, 0.8, 0.2, -0.5, ...]  ← 这是一个384维的向量(包含384个数字)
         ↑
      第1维  第2维  第3维  第4维 ... 第384

简单理解:

  • 384维 = 384个数字排成一排
  • 768维 = 768个数字排成一排
  • 1536维 = 1536个数字排成一排

形象类比

类比维度说明
直线上的点1维只需要1个数字确定位置 (x)
平面上的点2维需要2个数字 (x, y)
空间中的点3维需要3个数字 (x, y, z)
文本向量384/768/1536维需要几百到几千个数字表示语义

关键洞察:维度越高,能表达的信息越丰富,但计算成本也越高

为什么需要这么多维度?

核心:编码更多语义信息

低维度 (2D平面)          高维度 (1000D空间)
     y
     ↑                    ● "开心"
     │                   /  \
     │                  /    \
  ───┼───→ x          ●"难过""高兴""兴奋"
                     \    / \    /
                      \  /   \  /
                       ● "情绪"
                       
2维:只能区分"开心/难过"(左右分布)
1000维:可以区分"开心/难过/高兴/兴奋/沮丧/欣慰..."等无数细微差别

简言之:向量维度越高,他能在空间中表示的点更多。我们把一个点想象为语义切分的一个块,点多了,原本一段固定的文本就会切分为更多的块,就能表示更多的语义差异。比如一段文本最多切分为两个点,他只能表示开心或者难过。但是当这个点被切分的更细,比如10,100个,原本的开心会被切分为更细的喜悦,快乐,高兴等更细致的语义,这些语义又有更细微的差别。这也是高维度的好处,能够将一段文本的语义切分的更细致,让智能体更能把握住用户的问题,从而更精准的回答问题

下面罗列了一些常用的国内厂商的嵌入,本人项目中用的就是阿里云的v3嵌入模型

厂商模型特点
阿里云text-embedding-v1/v2/v3中文优化,按token计费
百度Embedding-V1文心系列,中文语义强
智谱AIembedding-2GLM系列,长文本支持
MiniMaxemb-001多模态嵌入
讯飞星火嵌入模型语音文本联合嵌入
这里附上阿里云百炼平台大模型网址大模型服务平台百炼控制台
5、向量数据库

这一块博主比较迷,也不能很好理解,第一次接触到这个概念,项目中的RAG存储用的也是postgres或者redis来存储向量。为了更好理解,这里博主引用了kimi的解释,还是挺形象的

一、向量数据库讲解与核心架构

📌 什么是向量数据库?

**向量数据库是一种专门用于存储、索引和快速检索高维向量(如文本、图像的嵌入向量)的数据库系统。**它不是用来存表格数据(如 MySQL),而是用来存“语义数字指纹”,核心能力是:
在海量向量中,毫秒级找出“最相似”(topK)的几个。

🔑 为什么需要它?——传统数据库不行吗?

场景传统数据库(MySQL/PostgreSQL)向量数据库
存用户信息(ID、姓名、邮箱)✅ 擅长❌ 不适合
存文本向量(768 维 float 数组)⚠️ 能存但无法高效搜索✅ 专为向量设计
“找和这句话意思最像的 3 条”❌ 只能精确匹配或关键词(如where name=xxx)✅ 支持语义相似度搜索,根据语义返回最相近的结果

💡 关键区别

  • 传统 DB:查“等于”、“包含”
  • 向量 DB:查“
┌─────────────────────────────────────────┐
│              用户查询                    │
│         "找与'苹果'相似的文本"            │
└─────────────────┬───────────────────────┘
                  ↓
┌─────────────────────────────────────────┐
│  ① 查询向量化 (Embedding Model)         │
│     "苹果" → [0.2, -0.5, 0.8, ...]      │
└─────────────────┬───────────────────────┘
                  ↓
┌─────────────────────────────────────────┐
│  ② ANN索引检索 (Approximate Nearest Neighbor) │
│     快速定位候选集(无需遍历全库)         │
└─────────────────┬───────────────────────┘
                  ↓
┌─────────────────────────────────────────┐
│  ③ 精确距离计算 + 排序                    │
│     计算余弦相似度,返回Top-K            │
└─────────────────┬───────────────────────┘
                  ↓
┌─────────────────────────────────────────┐
│  ④ 返回结果 + 可选元数据过滤              │
└─────────────────────────────────────────┘

向量数据库构造

┌─────────────────────────────────────────┐
│           向量数据库架构                  │
├─────────────────────────────────────────┤
│  接入层 │  REST API / SDK / CLI     │
├─────────┬─────────────────────────────┤
│  索引层 │  ANN索引构建 (HNSW/IVF/Flat)  │
├─────────┼─────────────────────────────┤
│  存储层 │  向量数据 + 元数据 (ID/标签等)   │
├─────────┼─────────────────────────────┤
│  计算层 │  距离计算 (余弦/欧氏/点积)   │
└─────────┴─────────────────────────────┘

二、ANN索引算法详解

向量数据库索引(Vector Index)是向量数据库实现高效相似度搜索的核心技术。它的目标是:

在海量高维向量中,快速找到与查询向量最相似的 Top-K 结果,而无需暴力计算所有距离。

由于“暴力搜索”(Brute-force)在大数据量下速度极慢(如 100 万向量需数秒),向量数据库普遍采用 近似最近邻搜索(Approximate Nearest Neighbor, ANN)算法构建索引,在精度损失极小的前提下,将检索速度提升 10~1000 倍

┌─────────────────────────────────────────────────────────┐
│                    ANN算法家族                          │
├─────────────────────────────────────────────────────────┤
│  图算法        │  HNSW, NSW, Vamana, DiskANN            │
│  树算法        │  Annoy, KD-Tree, Ball Tree             │
│  哈希算法      │  LSH (局部敏感哈希)                     │
│  量化算法      │  PQ (乘积量化), SQ, OPQ                │
│  聚类算法      │  IVF, IVF-PQ, IVF-HNSW                 │
│  混合算法      │  IVF-PQ + HNSW                         │
└─────────────────────────────────────────────────────────┘

  1. HNSW(Hierarchical Navigable Small World)
分层图结构示意:

Layer 2 (稀疏层):    ●─────●          ← 少量远距离连接,快速定位区域
                    /       \
Layer 1 (中间层):   ●───●───●───●     ← 中等密度,逐步逼近
                  / \   / \   / \
Layer 0 (密集层):  ●─●─●─●─●─●─●─●    ← 全量数据,精确查找

查询路径:从顶层进入 → 找到最近节点 → 下沉到下一层 → 重复直到底层
特性说明
时间复杂度O(logN)
精度>95%(与暴力搜索相比)
内存占用高(需存储多层图结构)
适用场景内存充足,追求高召回
代表产品Milvus, Weaviate, pgvector
  1. IVF(Inverted File Index)
聚类中心示意:

        ● 中心1                    ● 中心2
       /  |  \                   /  |  \
      /   |   \                 /   |   \
    ●     ●    ●               ●     ●    ●
   簇123456

查询过程:
1. 计算查询向量与所有中心的距离
2. 选择最近的n个簇(nprobe参数)
3. 只在选中簇内搜索
4. 返回Top-K结果
特性说明
时间复杂度O(√N) ~ O(N/k),k为簇数
精度~90%(依赖nprobe设置)
内存占用低(只需存储中心点)
适用场景大规模数据,内存受限
可调参数nlist(簇数)、nprobe(查询簇数)
  1. PQ(Product Quantization)乘积量化
原始向量(128维)        分割为8个子向量(每段16维)
[0.1, -0.3, ..., 0.8]  →  [0.1,...] | [-0.3,...] | ... | [0.8,...]
                                ↓           ↓              ↓
                           查码本1      查码本2         查码本8
                                ↓           ↓              ↓
                            编码ID       编码ID          编码ID
                                ↓           ↓              ↓
                           压缩表示:[12, 45, 7, 89, 23, 56, 91, 3](仅8字节)

压缩比:512float(2048字节) → PQ编码(64字节) = 32倍压缩
特性说明
压缩比10x ~ 50x
精度损失轻微(5-10%召回下降)
适用场景超大规模数据,存储成本敏感
常组合IVF + PQ = IVF_PQ
  1. LSH(Locality Sensitive Hashing)
哈希函数设计:相似向量 → 相同哈希桶

哈希表1:  h1(v) = sign(w1·v)  →  桶A / 桶B
哈希表2:  h2(v) = sign(w2·v)  →  桶C / 桶D
哈希表3:  h3(v) = sign(w3·v)  →  桶E / 桶F

查询时:计算查询向量的多个哈希值 → 找所有匹配桶 → 合并候选集
“让相似的向量,以高概率被哈希到同一个‘桶’(bucket)中。”这样,在检索时只需在同一个桶内做暴力搜索,大幅减少计算量。
想象你有一堆彩色小球(向量),要快速找出颜色相近的:
1.  普通哈希(如 MD5)  
    → 颜色相近的球会被打散到不同桶 → ❌ 没用
1.  局部敏感哈希(LSH)  
    → 设计一种“按颜色分桶”的规则  
    → 红色球大概率进“红桶”,蓝色进“蓝桶”  
    → 查“粉红色”时,只查“红桶”即可 ✅
 🔑 **关键**:LSH 的哈希函数是对距离敏感的函数——距离越近,哈希值相同的概率越高。
特性说明
时间复杂度O(1) ~ O(logN)
精度中等(依赖哈希函数设计)
内存占用极低
适用场景超大规模,允许一定精度损失
缺点高维数据效果下降(维度诅咒)
  1. 算法对比总表
算法精度速度内存适用规模代表实现
Flat(暴力)100%极慢<10万Milvus-Flat
HNSW95%+<1亿Milvus-HNSW, pgvector
IVF90%中等1亿+Milvus-IVF
IVF_PQ85%极低10亿+Milvus-IVF_PQ, Faiss
LSH70%极快极低100亿+Annoy,随机投影

6.向量数据库选型

数据库部署方式核心算法特点适用场景Java支持
Milvus云原生/本地HNSW, IVF_PQ, GPU分布式,十亿级规模大规模生产环境✅ 官方SDK
Pinecone全托管SaaS内部优化零运维,自动扩缩容快速上线,中小规模✅ REST API
Weaviate云/本地HNSWGraphQL接口,模块化知识图谱,混合搜索✅ 官方客户端
Qdrant云/本地HNSW + 过滤索引Rust高性能,复杂过滤高并发推荐系统✅ 官方客户端
Chroma嵌入式HNSW极简,pip即用原型开发,本地测试❌ 仅Python/JS
PGVectorPostgreSQL插件HNSW, IVFFlatSQL原生,事务支持已有PG基础设施✅ JDBC + SQL
Redis内存数据库HNSW极速查询,易失性缓存热数据✅ Jedis/Lettuce
Elasticsearch搜索引擎HNSW混合搜索(文本+向量)需要关键词+语义联合✅ 官方Java客户端

7.Java 引入向量数据库详解

这里讲解PGVector

1.引入依赖

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-pgvector</artifactId>
    <version>1.0.0-beta1</version>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.1</version>
</dependency>

2.创建向量数据库

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.bge.small.en.v15.BgeSmallEnV15QuantizedEmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.store.embedding.EmbeddingMatch;

public class PgVectorExample {
    
    public static void main(String[] args) {
        // 1. 创建嵌入模型(384维)
        EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
        
        // 2. 初始化PGVector存储
        EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
            .host("localhost")           // PostgreSQL地址
            .port(5432)                   // 端口
            .database("vectordb")         // 数据库名
            .user("postgres")             // 用户名
            .password("password")         // 密码
            .table("embeddings")          // 存储表名
            .dimension(384)              // 必须匹配模型维度!
            .useIndex(true)               // 启用HNSW索引
            .indexType(PgVectorIndexType.HNSW)  // HNSW算法
            .metric(PgVectorDistanceMetric.COSINE)  // 余弦相似度
            .build();
        
        // 3. 添加文档
        String text = "LangChain4j 是 Java 的 LLM 应用框架";
        Embedding embedding = embeddingModel.embed(text).content();
        
        TextSegment segment = TextSegment.from(text);
        String id = embeddingStore.add(embedding, segment);
        System.out.println("已存入,ID: " + id);
        
        // 4. 语义搜索
        String query = "Java 如何开发大模型应用?";
        Embedding queryEmbedding = embeddingModel.embed(query).content();
        
        List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
            queryEmbedding, 
            5,           // Top-5结果
            0.7          // 相似度阈值(0-1)
        );
        
        // 5. 输出结果
        for (EmbeddingMatch<TextSegment> match : matches) {
            System.out.printf("相似度: %.3f | 内容: %s%n", 
                match.score(), 
                match.embedded().text()
            );
        }
    }
}

8.这里附上向量数据库更加完整的别的博主的讲解什么是向量数据库?向量数据库概念,详细入门-CSDN博客

2.在线查询阶段(响应用户)

当用户输入一个问题时,系统会实时执行以下流程来生成回答。

1.用户查询(User Query):接收用户的自然语言问题。

2.查询向量化(Query Embedding):使用与离线阶段相同的嵌入模型,将用户的查询也转换为一个向量。

3.检索(Retrieval):在向量数据库中,通过计算查询向量与所有文档块向量的相似度(如余弦相似度),找出与用户问题最相关的Top-K个文本块。高级系统可能会使用混合检索(结合向量检索和关键词检索如BM25)和重排序(Re-ranking)技术来进一步提高检索的准确率。

4.构建提示词(Prompt Construction):将检索到的相关文本块作为上下文,与用户的原始问题一起,按照预设的模板组合成一个新的提示词(Prompt)。这个提示词的作用是“增强”大模型的知识背景。

5.生成响应(Response Generation):将构建好的提示词输入给大语言模型(LLM)。模型会基于提示词中提供的最新、最相关的上下文信息,结合其自身学到的通用知识,生成一个准确、流畅的回答。

6.回答输出(Answer Output):将生成的回答返回给用户。为了增强可信度和可追溯性,系统通常会附带引用其答案所依据的文档来源

三:RAG检索优化

先来看一下整个优化流程图片,该图片也是博主找的别人的图片,原作者是小红书AI白方

Capture_20260318_230920.jpg 可以看到我们整个优化是分为三个阶段的,即检索前->检索中->检索后

1.检索前优化

Capture_20260318_231258.jpg

Capture_20260318_231518.jpg

解释一下元数据与元数据索引

(1).向量数据库存储结构

向量数据库不会孤立存储向量,而是通过“唯一标识+向量+元数据”的组合,实现“可定位、可计算、可关联业务”的目标,具体组成如下:

组成部分作用与示例
1. 唯一标识(ID)类似传统数据库的“主键”,用于唯一标记一条向量数据,方便后续定位、更新或删除。 示例:商品ID(prod_123)、文档片段ID(doc_chunk_456)。
2. 核心向量(Vector)非结构化数据(文本、图片、音频等)的“数值化表达”,是相似性查询的计算基础,本质是一个固定长度的数值数组。示例:文本“猫咪”通过BERT模型转化的768维向量 [0.12, -0.34, 0.56, ..., 0.78];图片通过ResNet模型转化的2048维向量 [0.09, 0.21, -0.43, ..., 0.65]
3. 辅助元数据(Metadata)描述向量对应原始数据的“结构化属性”,用于**缩小查询范围(过滤)**或关联业务信息,格式通常为键值对(Key-Value)。 示例:向量对应图片的“拍摄时间(2024-05-01)”“类别(宠物)”“上传用户(user_789)”;向量对应文档的“来源(企业手册)”“章节(第3章)”。

原文链接:blog.csdn.net/sjc212/arti…
可知:metadata元数据本质就是我们向量数据的一些附加信息,我们通过这些附加信息快速过滤其他向量,定位到我们具体想要的向量区间,然后在这个区间精查,细查

(2).元数据讲解:

可以用一个简单的类比来理解:

想象你正在整理一个装满各种文件的实体文件柜。

  • 文件本身(文档) :是一份项目报告、一张照片、一封邮件。这是你要切分和存储的主要内容。

  • 元数据(关于文件的数据) :是贴在文件柜每个文件夹上的标签,或者文件索引卡上的信息条目。比如:

    • 文件名:2023-10-27_项目A_季度报告_v2.docx
    • 创建日期:2023年10月27日
    • 作者:张三
    • 所属项目:项目A
    • 关键词:预算、进度、风险
    • 存储位置:柜子3,抽屉B,文件夹5

当你需要找文件时,你不会把整个文件柜倒出来翻找,而是会先查索引卡,根据“项目A”和“季度报告”这些标签,快速定位到少数几个可能的目标,然后再取出文件查看细节


1. 什么是元数据?

元数据,简单来说,就是“描述数据的数据”或“关于信息的信息,可以理解为数据的标签tag,是当前向量化数据的辅助信息,它本身不表示任何文本,文本内容任然由向量表示,他只是起到一个辅助和定位向量的作用
类比快递员送快递,元数据就相当于快递地址,外卖员根据快递地址可以快速定位到某个小区,然后在通知买家获取具体地址,这个是细查,最后买家成功拿到外卖,相当于找到目标向量”。

在文档处理的语境下,元数据就是用来描述、解释、定位或以其他方式让某个文档(或文档片段)更容易被理解、管理和使用的结构化信息。

2. 切分文档时,元数据是被自动加入的,还是我们自己加的?

答案是:两者都有!这是一个协同的过程。

通常情况下,我们会结合使用系统自动提取人工自定义的方式。

A. 系统自动加入的元数据

现代的文档加载器和切分器(比如LangChain的RecursiveCharacterTextSplitter配合相应的文档加载器)非常智能,它们会自动为你抓取和保留一些基础但非常重要的元数据。这通常是默认行为,无需你额外编写代码。

常见的自动加入的元数据包括:

  1. 数据来源 (source) :这是最重要的一个。加载器会自动记录文档来自哪里,是本地的文件路径,还是一个网页的URL。这对于追溯信息源头至关重要。
  2. 文档标题:如果文档格式支持(如PDF、HTML、Word),加载器会尝试提取文档的标题。
  3. 页码 (page) :对于PDF或Word文档,当切分器把一个长文档切成很多小块时,它通常会记录这一小块内容来自于原文的哪一页。这对于引用和验证信息非常有用。
  4. 创建/修改日期:从文件属性中读取。
  5. 作者:从文件属性中读取。
  6. 文档类型 (doctype) :比如是PDF、TXT还是Markdown。

举例:
当你加载一个名为 2024_产品手册.pdf 的文件并对其进行切分后,生成的每个小块(Chunk)可能会自动拥有如下元数据:

{
  "source": "./data/2024_产品手册.pdf",
  "page": 5,
  "author": "市场部",
  "last_modified": "2024-01-15"
}

切分后生成的document对象就包含了元数据metadata,我们可以查询该字段看看到底存了啥

B. 我们自己添加的元数据

系统自动提取的信息有时不够用,或者不符合你的特定业务需求。这时,你就可以在加载文档后、切分文档前,或在切分过程中,手动添加自定义的元数据

为什么要自己加?

  1. 增强业务上下文:你可以添加**“所属项目”、“客户名称”、“保密级别”、“产品线”**等信息。
  2. 支持精细化过滤:在检索时,你可以利用这些自定义字段,只搜索某个特定项目下的文档块。
  3. 改进检索结果:有时文档块本身的内容可能很模糊,但元数据可以提供关键背景,帮助LLM(大语言模型)判断这个块是否真的是用户需要的。

举例:
接上面的例子,你可能想加上这些自定义元数据:

{
  // ... (自动生成的元数据)
  "source": "./data/2024_产品手册.pdf",
  "page": 5,
  // ... (你手动添加的)
  "product_line": "智能家居系列",
  "audience": "渠道合作伙伴",
  "version": "v2.1"
}

怎么手动添加元数据?

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.segment.TextSegment;

// 方式一:创建文档时添加元数据
Map<String, Object> metadataMap = new HashMap<>();//metadata本质就是一个key-value的数据结构
metadataMap.put("document_id", "contract_2024_001");
metadataMap.put("version", "v2.3");
metadataMap.put("author", "张三");
metadataMap.put("last_modified", "2024-05-20");
metadataMap.put("status", "active");

Metadata metadata = Metadata.from(metadataMap);
Document document = Document.from("文档内容...", metadata);

// 方式二:动态添加元数据
document.metadata().put("department", "法务部");
document.metadata().put("page_count", 10);

// 获取元数据
String docId = document.metadata().getString("document_id");
Integer pages = document.metadata().getInteger("page_count");  

举例我们项目中怎么使用的

// 处理文件上传(如果有文件)
if (files != null && !files.isEmpty()) {
    for (MultipartFile file : files) {
        // 解析文档内容
        TikaDocumentReader documentReader = new TikaDocumentReader(file.getResource());
        List<Document> documentList = tokenTextSplitter.apply(documentReader.get());
        
        //创建知识库标签元数据,用于检索时过滤
        //tag – 知识库标签(如:"grafana-mcp-tools-guide"),用于检索时过滤
        documentList.forEach(doc -> doc.getMetadata().put("knowledge", tag));
        vectorStore.accept(documentList);
        totalDocumentCount += documentList.size();
        
        log.info("知识库文件上传完成 - 名称: {}, 标签: {}, 文件: {}, 文档片段数: {}", 
                name, tag, file.getOriginalFilename(), documentList.size());
    }
} else {
    log.info("创建空知识库配置 - 名称: {}, 标签: {}", name, tag);
}

数据库存储情况 image.png

我们设置了该顾问advisor,他可以通过filter表达式关联rag知识库

// AiClientAdvisorVO.RagAnswer 结构
public static class RagAnswer {
    private int topK = 4;  // 检索返回最相关的前 K 个文档
    private String filterExpression;  // 过滤表达式
    private Integer knowledgeStatus;  // 知识库状态(0:禁用,1:启用)
}

filter表达式的json形式

{
  "topK": 4,
  "filter_expression": "knowledge == 'tech' || knowledge == 'docs'",
  "knowledge_status": 1
}

后续我们通过给顾问创建searchRequest,指定我们的元数据过滤标签和topk,来创建我们的请求查询参数

// 创建 RAG 顾问时配置过滤条件
RagAnswerAdvisor advisor = new RagAnswerAdvisor(vectorStore, SearchRequest.builder()
        .topK(ragAnswer.getTopK())
        .filterExpression(ragAnswer.getFilterExpression())  // 这里指定过滤条件
        .build());
3. 元数据有什么用?(为什么它如此重要?)

元数据在RAG(检索增强生成)应用中扮演着至关重要的角色,主要有三大用途:

用途一:精确检索与过滤(最重要!)

这是元数据的核心价值。当用户提问时,我们可以先利用元数据进行“预筛选”或“后过滤”。

  • 场景:用户问“我们公司去年的销售额是多少?”
  • 没有元数据:系统可能检索出所有包含“销售额”的文档片段,其中可能混杂了其他公司的数据,或者前年的旧数据,导致回答错误。
  • 有元数据:系统可以这样检索:“找出所有文档片段,其中内容包含‘销售额’,并且元数据中的 company 字段等于‘我们公司’,并且 year 字段大于等于‘2023’。” 这极大地提高了检索的准确性。
用途二:提供上下文与引用(可解释性)

当LLM生成了答案,它不仅可以给出答案,还可以“告诉”用户这个答案的依据是什么。

  • 场景:LLM回答:“2023年我们的销售额增长了20%。”
  • 利用元数据:LLM可以同时返回它所依据的文档片段及其元数据,比如“信息来源:2023_财报.pdf,第12页”。这让用户能够核实信息,增加了结果的可信度。
用途三:辅助文档管理与处理

元数据可以帮助我们追踪文档的来源、版本,方便后续的数据清洗、更新或删除。例如,当我们发现某个 source 的文档有更新时,可以精准地找到向量数据库中对应的旧数据并替换掉。

总结
  • 元数据是什么:是关于文档的标签和信息卡片,用于描述文档本身。
  • 如何产生一半自动,一半手动。系统负责提取基础信息(来源、页码),我们负责添加业务相关的关键信息(项目、类别、版本)。
  • 有什么用:它让检索从“大海捞针”变成“按标签找针”,提高了RAG系统的准确性、可解释性和可管理性。没有元数据的RAG,就像没有目录和索引的图书馆,虽然能找到书,但效率极低。
(3).元数据索引

结论: 只有为元数据字段创建了索引(metadata index),向量数据库才能高效地支持基于这些字段的过滤(filtering)功能。类似于mysql的索引机制,不给字段加索引,那么根据该字段过滤还是会触发全表扫描。这里给元数据加索引就是为了元数据过滤时,能够快速查询到所有符合元数据的向量,实现快速定位向量的功能

🔍 为什么必须“创建索引”才能过滤?
虽然你把元数据(比如 {"year": 2025, "category": "finance"})随文本块一起存进了向量数据库,但默认情况下,这些字段只是“被存储”,并未被“优化查询”。
向量数据库的核心是向量相似度搜索(如余弦相似度),而元数据过滤属于结构化查询,两者机制不同

没有索引 → 数据库只能对每个结果做“全量扫描 + 条件判断”(O(N) 时间复杂度),慢且可能被禁用。
有索引 → 数据库能快速定位满足 year >= 2024 的子集,再在这个子集上做向量检索(高效、可扩展)。

结论一句话:

元数据提供了“可以按什么条件过滤”的信息,而元数据索引决定了“能不能高效地按这些条件过滤”。

所以,在构建 RAG 或语义搜索系统时:

  1. 设计好元数据结构(哪些字段要用于过滤?)
  2. 在向量数据库中为这些字段创建索引
  3. 测试 filter 查询是否生效 & 高效

这样才能真正发挥“语义搜索 + 结构化过滤”的混合检索威力 🚀。

(4).Small to Big策略讲解
一、核心思想

用“小”信息精准检索,用“大”上下文保障生成质量

这句话是整个策略的精髓:

  • “小”信息:指文档中关键的、细粒度的信息点(如某个概念、数据、结论等),它们可以被用来做精确匹配
  • “大”上下文:指包含这些关键信息的原始文本块或段落,内容更完整、语义更丰富,适合用于 LLM 生成高质量回答。

目标:

  1. 先通过精细索引快速定位到最相关的“核心信息”
  2. 再召回其所属的更大、更完整的上下文
  3. 最终用这个完整上下文生成更准确、连贯的答案

🧩 二、具体实现方式
方式一:子问题检索(Subquestion Retrieval)

利用大模型对上传的文本块自动提取出几个关键子问题,并为每个子问题建立索引。

  1. 预处理阶段

    • 输入一个长文档(比如一篇论文、报告)

    • 使用大模型(如 GPT、Qwen)分析内容,提出若干个“子问题”
      例如:

      • “该政策的目标是什么?”
      • “2024 年的GDP增长率是多少?”
      • “主要影响因素有哪些?”
    • 将这些子问题和对应的原文片段一起存入向量数据库(作为索引项)

  2. 检索阶段

    • 用户提问:“2024年经济形势如何?”
    • 系统将这个问题去检索所有子问题(语义相似度)
    • 找到匹配的子问题(如“2024年的GDP增长率是多少?”)
    • 召回与该子问题关联的原始文本块
  3. 生成阶段

    • 用召回的原始文本块作为输入,让大模型生成最终答案

优点:

  • 检索精度高(因为子问题是高度聚焦的)
  • 能避免“相关但不准确”的结果

简述:让大模型根据你的知识库文本提出几个问题,然后将子问题与知识库对应的片段保存在一起,建立索引。这样用户在提出问题时,他会先去向量化匹配语义相近的子问题,再去子问题中查询关联的知识库片段,类似于key-value形式,子问题是key,对应的知识库片段为value


方式二:文本摘要(Text Summarization + Indexing)

对每个文本块生成一个简短的摘要,并以摘要为索引进行检索。

  1. 预处理阶段

    • 对每个文本块使用大模型生成一段摘要(summary)
    • 将摘要和原始文本块一起存储,摘要作为“检索键”
  2. 检索阶段

    • 用户提问时,先在摘要库中搜索最相关的摘要
    • 找到后,召回对应的原始文本块
  3. 生成阶段

    • 用原始文本块生成最终答案

优点:

  • 摘要更简洁,适合快速检索
  • 减少向量维度,提高检索效率
  • 避免了“碎片化”检索带来的信息断层

简述:对文本的每个文本块先让ai生成简述,让简述与对应的文本块一起向量化存储,建立索引。这里key就是简述,value为具体文本块。先根据用户的问题匹配语义相近的简述,再去每个简述中细查


三、两种方式对比

表格

方法优势缺点
子问题检索精准性强,能捕捉复杂逻辑关系需要额外生成子问题,计算成本稍高
文本摘要实现简单,易于扩展摘要可能丢失细节,不如子问题精准

实际应用中,两者可以结合使用:先用摘要快速筛选,再用子问题精炼结果。


四、为什么叫 “Small to Big”?

表格

步骤“小”“大”
检索阶段检索的是“子问题”或“摘要” → 信息量小但精准检索的是“原始文本块” → 信息量大但冗余
生成阶段不直接用“小”信息生成用“大”上下文生成高质量答案
  • “小” :用于高效、精准地定位核心信息
  • “大” :用于保障生成的完整性与准确性

这正是“从精细到完整”的过程,故称 Small to Big


五、适用场景
  • 文档较长、结构复杂(如年报、科研论文)
  • 需要高准确率的回答(如金融、医疗、法律)
  • 希望减少幻觉(hallucination)和信息遗漏
    • *S

总结一句话:

“Small to Big” 是一种“先精后广”的检索策略:用小而准的索引(子问题/摘要)找到关键信息,再用大的原始上下文生成可靠答案,兼顾检索效率与生成质量。

检索前优化可以总结为以下几点

1.选用更好的embedding模型
2.RAG文档切分时选择合适的切分器,以及RAG文档解析时使用专业的解析器,比如PDF的文档就用专门的PDF解析器
3.创建元数据(metadata)索引,设置元数据
4.提前对rag知识库文件进行数据清洗,给ai提示词,让他修改知识库文件,目的是保证知识库检索的准确性
5.Small to Big策略
6.提示用户端,让他提出更完整,更精确的问题,便于ai快速理解用户需求
7.设置一个专门的语义识别ai,让他在用户提出问题后,自动结合上下文,理解用户需求,并且将用户问题转化为更正规化,标准化,提供一些标准化示例,目的还是避免后续ai无法理解用户真实需求,从而影响rag检索质量

2.检索中优化

Capture_20260319_114001.jpg

1、HyDE(Hypothetical Document Embeddings)

(1)定义讲解

定义:让大模型先对用户的问题生成一些“假设答案”(hypothetical answer),然后用这个“假设答案”的向量去检索文档。

核心思想:

用户的问题可能很模糊或抽象(如:“为什么2024年经济复苏?”)
直接拿这个问题去查,容易召回不相关的内容
如果我们能先猜一个合理的答案(比如:“因为消费支出增长了65%”),再用这个答案去查,就能更精准地找到支持它的原文

用户问题 → 大模型 → 生成“假设答案” → 转成 embedding → 向量检索 → 召回真实文档

举个例子:
用户问:

“2024年中国经济复苏的主要原因是什么?”

步骤1:生成假设答案(由大模型生成)

“主要原因是最终消费支出对经济增长的贡献率达到65%,拉动了整体复苏。”

步骤2:将这个假设答案转成 embedding

把这句话变成向量(embedding)

步骤3:用这个向量去检索

在知识库中找和这个“假设答案”最相似的文本块

步骤4:召回结果

找到一段原文:“根据国家统计局数据,2024年消费支出贡献率高达65%……” ✅ 这样就找到了真正相关的证据!

💡 为什么叫“双向奔赴”?

  • 传统方法:从文档出发 → 提取问题 → 用问题检索 (文档 → 问题 → 检索)
  • HyDE:从问题出发 → 生成答案 → 用答案检索 (问题 → 答案 → 检索)
  • 两者是反向的,所以称为“双向奔赴”。
  • 实际上,这属于“查询改写”的一种高级形式:把自然语言问题转化为语义上更接近文档内容的“虚拟答案”。
(2)为什么要这么去查询
核心矛盾:查询与文档的语义鸿沟
维度用户问题真实文档
表达方式简短、口语化、抽象详细、书面化、具体
信息密度低(只包含意图)高(包含事实证据)
关键词"为什么""是什么"数据、结论、专业术语

直接检索的问题:问题和文档在向量空间中距离很远,即使语义相关,相似度分数也不高。


HyDE 解决的根本问题
1. 对齐语义空间
原始问题向量空间          文档向量空间
    "为什么经济复苏?"  ←——鸿沟——→  "消费支出增长65%..."
         ↓                          ↑
    【HyDE桥接】                      │
         ↓                          │
    "消费支出贡献率65%拉动复苏"  ————→  完美匹配!
2. 具体化模糊意图
用户真实意图表面问题HyDE生成的假设答案
找原因"为什么业绩好?""Q3新上线会员体系使复购率提升30%"
找数据"去年销量如何?""2024年总销量突破100万台,同比增长25%"
找方法"怎么降本增效?""通过供应链数字化改造降低库存成本15%"

假设答案把模糊意图转化为具体语义内容,更容易命中文档。


对比:传统检索 vs HyDE
传统检索(问题→文档)
用户问:"公司为什么盈利?"
↓
检索 query"公司为什么盈利"
↓
召回结果:
❌ "公司盈利预测模型研究...""盈利性分析的方法论...""上市公司盈利能力排名..."  ← 相关但不够精准
HyDE(答案→文档)
用户问:"公司为什么盈利?"
↓
生成假设答案:"主要是因为新产品线毛利率达45%,拉动整体利润增长"
↓
检索 query"新产品线毛利率45%拉动利润增长"
↓
召回结果:
✅ "Q3财报:新产品线毛利率45.2%,贡献利润增长12亿元..."  ← 精准命中证据

HyDE 的深层价值
价值说明
意图扩展把用户的"为什么"扩展成可能的"因为..."
术语对齐把口语映射到文档中的专业表达
证据预演先"猜"答案长什么样,再去找证据
噪声过滤假设答案的语义更聚焦,减少召回无关内容

一句话总结

HyDE 的本质:用"答案的语言"去检索"答案的载体,用户的问题如果过于模糊,ai直接去知识库查询太难了,他不知道用户到底想要什么方面的数据,但是如果我们根据问题,用ai模拟生成几个答案,比如用户问"公司为什么盈利了",他会生成几个答案,比如"原因是公司制度好","公司产品好"等等,接着他就有方向去查询数据了,比如去查询制度类的,去查询产品营收类的,这种方式的本质还是将用户的问题更加具体化,给出可能要查询的方向,不至于没有头绪的去查找

用户用"问题语言"提问,但文档用"答案语言"写作。HyDE 让大模型充当翻译器,把问题翻译成答案可能的模样,从而跨越语义鸿沟,实现精准检索。

这也是为什么它被称为"查询改写的高级形式"——不是改几个关键词,而是直接改写语义空间。

(3)具体存在的问题
HyDE 的核心风险:幻觉传染
用户问题:"2024年新能源补贴退坡影响"
    ↓
大模型生成假设答案:"补贴退坡导致比亚迪Q1销量下滑30%"
    ↓
⚠️ 问题:如果知识库里根本没有这个信息?
    ↓
结果:检索失败 或 召回无关内容,甚至引入错误前提

本质矛盾:HyDE 依赖大模型的"先验知识"生成假设,但这份知识可能与你的私域知识库不一致


三种失败场景
场景大模型假设知识库真相结果
幻觉假设"消费增长65%"实际增长是35%检索不到,或召回错误数据
过时假设"使用GPT-3架构"已升级为GPT-4召回旧文档,信息过时
领域错配生成通用答案知识库是专业垂直领域语义不匹配,检索失败

业界如何缓解这个问题
1. RAG-Fusion:多假设+投票

不生成一个假设,而是生成多个 diverse 的假设答案,分别检索后融合:

假设1"消费增长驱动复苏" → 检索结果A
假设2"出口回暖拉动经济" → 检索结果B  
假设3"基建投资加大促进" → 检索结果C
↓
合并去重,综合排序

优势:单个假设错了,其他假设可能命中。

2. 假设与原文对比验证

检索回来后,让大模型对比"假设答案" vs "召回文档":

步骤1:生成假设答案
步骤2:用假设答案检索
步骤3:{召回文档} 是否真的支持 {假设答案}?
步骤4:如果不支持,丢弃假设,用原始问题重试
3. 降低假设权重:混合检索

同时用原始问题假设答案检索,合并结果:

检索方式权重作用
原始问题40%保底,防止假设跑偏
HyDE假设60%提升精准度
4. 领域微调:让假设更靠谱

用知识库内容微调生成假设的模型,使其生成风格与私域文档一致。


关键结论
问题答案
HyDE 会出错吗?,假设答案本质是大模型幻觉
还能用吗?,但需要容错机制
最佳实践?作为增强手段,而非唯一手段

建议策略

  • 简单/常见/通用问题 → 直接用原始问题检索
  • 复杂/抽象/语义鸿沟大的问题 → HyDE + 多假设 + 验证
  • 高敏感场景(医疗/法律/金融)→ 慎用,或强制人工校验

2.子问题分解

1.定义:

使用大模型把用户的复杂问题分解成多个简单子问题,然后分别检索,最后合并结果。

适用场景: 当用户提出一个综合性强、涉及多个方面的问题时,比如: “请分析2024年新能源汽车市场的发展趋势、政策影响和技术瓶颈。”

这种问题包含三个子主题: 发展趋势 政策影响 技术瓶颈 如果直接检索,很容易漏掉某些部分。

复杂问题 → 大模型 → 分解为多个子问题 → 并行检索 → 合并结果 → 生成综合回答
2. 举个例子:

用户问:

“2024年新能源汽车市场的发展趋势、政策影响和技术瓶颈是什么?”

步骤1:分解为子问题(由大模型生成)

年新能源汽车市场的销量和渗透率如何?

国家有哪些支持新能源汽车的政策?

当前技术面临哪些瓶颈(如电池续航、充电速度等)?

步骤2:并行检索

对每个子问题分别进行向量检索

得到各自的上下文片段

步骤3:合并结果

将所有相关文档整合

用大模型生成统一的回答

3.多路召回

1. 什么是多路召回?

用一个生活场景来理解:

想象你是一个侦探,要调查一宗案件:

  • 单路召回:你只询问一个目击者(单一检索方式)。如果这个目击者记忆有误或者有偏见,你的调查结果就可能出错。
  • 多路召回:你同时询问目击者、查看监控录像、分析物证、调查手机记录(多种检索方式)。然后综合所有线索,得出最可靠的结论。

多路召回正是这个思路:同时使用多种检索策略,从不同角度、不同数据源或不同表示形式中召回相关信息,最后合并、去重、重排,得到最优结果集,将他返回给ai

┌─────────────────┐
│    用户查询      │
└────────┬────────┘
         │
    ┌────┴────┬────────┬────────┐
    ▼         ▼        ▼        ▼
  向量检索   关键词检索  图谱检索  规则匹配
 (语义)     (精确)    (关系)   (业务)
    │         │        │        │
    └────┬────┴────────┴────────┘
         ▼
    结果融合 + 重排序
         ▼
    最终返回Top-K 
2.为什么需要多路召回
场景:某公司的智能客服系统

假设你正在为一家大型企业开发智能客服机器人,需要回答员工关于公司政策、IT支持、HR福利等各种问题。

单一检索方式的致命缺陷

用户提问:"我的笔记本电脑太慢了,能申请换新吗?"

让我们看看只用一种检索方式会出什么问题:

// ========== 方案A:只用向量检索(语义相似)==========
// 优点:能理解"慢"和"性能差"是相似的
// 缺点:可能检索出这些结果:

检索结果1: "电脑性能优化指南" (相似度高,但不是换新政策)
检索结果2: "如何清理电脑垃圾文件" (相似度高,但不是换新政策)
检索结果3: "IT资产报废标准" (相似度中,包含换新条件)
检索结果4: "公司电脑配置标准" (相似度中,没有换新流程)
检索结果5: "软件安装规范" (完全不相关,但出现了"电脑")

// 问题:向量检索不知道"换新"是个精确操作
// 结果:可能找到一堆性能优化文档,但就是找不到换新政策
// ========== 方案B:只用关键词检索(BM25/全文搜索)==========
// 优点:精确匹配关键词
// 缺点:太死板,找不到同义词

检索结果1: "新员工电脑申请流程" (匹配"新",但不是换新)
检索结果2: "设备更换申请表" (匹配"换",完美!)
检索结果3: "新年促销活动" (匹配"新",完全不相关)
检索结果4: "更新系统补丁通知" (匹配"新",不相关)

// 问题:如果用户说"电脑太卡想换一台",没有"换新"关键词就搜不到
// 结果:可能完全漏掉"设备更换申请表"这个关键文档
// ========== 方案C:只用元数据过滤 ==========
// 优点:精确,速度快
// 缺点:太死板,需要用户知道精确条件

假设元数据过滤规则:doc_type='replacement_policy' AND device_type='laptop'

用户问题:"我的笔记本电脑太慢了"
// 系统无法自动提取出这些元数据条件
// 结果:一条都搜不到,因为没有指定任何过滤条件

可以看见,单纯走某一条路去查询数据,他都会有一些局限性,比如某些知识无法查询到,某些内容完全不相关,但是如果多路一起并行查询,将他们返回的内容合并,过滤,筛选,重排序,取topk,那么经过多路查询后,最终返回的查询数据,其数据准确性将大幅度提高

根本原因:没有一种检索方式是完美的
检索方式擅长不擅长打个比方
向量检索理解语义、找相似概念,取语义最相似的精确匹配、专业术语懂你意思,但记性不好
关键词检索精确匹配(查询包含关键字的数据)、速度快同义词、语义理解记性好,但太死板
元数据过滤精确筛选、范围限定模糊查询、自由表达按规矩办事,不懂变通
规则检索处理特定业务逻辑覆盖面窄专家但偏科
3.多路召回具体有哪些"路"?
一、核心5大召回路径
召回路径核心原理适用场景代码示例
1. 向量检索将文本转为向量,计算语义相似度开放式问题、意图模糊、同义词替换embedding -> vectorDB.search()
2. 关键词检索BM25/倒排索引,精确匹配关键词专业术语、产品型号、标准流程esClient.match().field("content")
3. 元数据过滤利用文档标签精确筛选按部门/时间/类型限定范围filter: department='IT' AND page>5
4. 规则检索基于业务逻辑的硬编码规则特定场景、合规检查、异常处理if (query.contains("合同") && query.contains("金额"))
5. 知识图谱实体关系查询需要推理、多跳问题、关系查询graph.query("张三 负责 哪个项目")

二、每条路的详细拆解
 第1路:向量检索(语义相似),用嵌入模型,即文本转化为向量,存入向量库,再将用户问题转化为向量,检索topK
// 工作原理:将文本转为向量,找"意思相近"的
输入:"电脑太卡了"
找出的结果:
✅ "笔记本运行缓慢" (语义相似)
✅ "设备性能不足" (语义相似)
✅ "电脑卡顿怎么办" (语义相似)
❌ "电脑清洁指南" (虽然也有"电脑",但语义不同)
 第2路:关键词检索(BM25/全文搜索)
// 工作原理:精确匹配关键词,计算词频权重
输入:"电脑太卡了"
找出的结果:
✅ "电脑更换申请表" (匹配"电脑")
✅ "电脑卡顿处理流程" (匹配"电脑"+"卡")
✅ "电脑性能优化" (匹配"电脑")
❌ "笔记本电脑促销" (匹配"电脑",但意图不对)

可用ElasticSearch实现,底层就用到了精确查询算法

要实现精准匹配,首先需要在索引文档时,将需要精确查询的字段(如ID、状态码、分类标签)的映射类型设置为 keywordtext 类型用于全文检索(会被分词),而 keyword 类型则会被当作一个完整的、不可分割的单元进行存储和匹配

// 创建索引时设置映射 (DSL示例)
PUT /products
{
  "mappings": {
    "properties": {
      "product_name": { "type": "text" },        // 全文检索,会被分词
      "status": { "type": "keyword" },           // 精确匹配,如 "ACTIVE"
      "product_id": { "type": "keyword" }        // 精确匹配,如 "P-1001"
    }
  }
}

具体精确检索实现 (java引入ES的操作类,构建查询方式,指定精确查询字段)

  //构建精准查询: 方式term查询查找 status = "ACTIVE"
        SearchResponse<Object> response = client.search(s -> s
                .index("products") // 指定索引
                .query(q -> q      // 构建查询
                        .term(t -> t
                                .field("status") // 字段名,必须是keyword类型
                                .value("ACTIVE") // 精确值
                        )
                ),
                Object.class // 结果映射类型
        );

🚀 实战:三种主流Java实现

第3路:元数据过滤
// 工作原理:利用文档的标签字段精确筛选
元数据字段:
- document_type: "IT政策" | "HR手册" | "财务制度"
- department: "技术部" | "市场部" | "人事部"
- created_date: 2023-01-01
- version: "v2.1"
- status: "active" | "archived"

// 使用场景:用户说"找一下去年IT部门的电脑政策"
filter: department='IT' AND document_type='政策' AND year>=2023
第4路:规则检索(业务硬编码)
// 工作原理:针对特定业务场景写死规则
public List<Document> ruleBasedRetrieve(String query) {
    List<Document> results = new ArrayList<>();
    
    // 规则1:合同金额查询
    if (query.contains("合同") && query.contains("金额")) {
        results.addAll(findContractAmountDocs(query));
    }
    
    // 规则2:离职流程咨询
    if (query.contains("离职") || query.contains("辞职")) {
        results.addAll(findHRDocsByType("离职流程"));
    }
    
    // 规则3:密码重置
    if (query.contains("密码") && (query.contains("忘记") || query.contains("重置"))) {
        results.addAll(findITDocsByTag("password_reset"));
    }
    
    return results;
}
 第5路:知识图谱检索
// 工作原理:查询实体之间的关系
知识图谱结构:
(张三) -[就职于]-> (技术部)
(技术部) -[负责]-> (A项目)
(A项目) -[使用]-> (电脑设备)

// 用户问:"张三用什么电脑?"
推理路径:张三 → 技术部 → A项目 → 电脑设备
// 返回:A项目配备的ThinkPad X1
4.常用的多路检测方式

根据工业界实践,混合检索主要有以下4种经典组合:

方式组合适用场景效果
方式1向量检索 + BM25关键词通用场景,最主流⭐⭐⭐⭐⭐
方式2向量检索 + 元数据过滤多维度筛选(按部门/时间/类型)⭐⭐⭐⭐
方式3向量检索 + 关键词 + 元数据企业知识库、复杂业务⭐⭐⭐⭐⭐

3.检索后优化

Capture_20260319_133432.jpg

6大检索后优化技术

优化技术核心思想解决什么问题效果提升
1. 重排序重新计算相关性分数初排不准+15-25%
2. 上下文压缩精简文档内容Token太长、噪声多减少50%+Token
3. 去重移除重复内容冗余信息+10%
4. 文档融合多文档信息聚合答案分散+15%
5. 动态上下文选择按需选择内容无关信息干扰+20%
6. 验证与修正检查答案准确性幻觉问题+30%

我们还可以设计promt提示词,比如不许构造虚假的检索后的信息,严格输入输出形式等给他加约束

或者再设计一个专门的client,给他设计专门的提示词,让他结合我们是知识库,上下文信息,给我们检索出来的信息打分

也可以多次调试,不断优化提示词