【0-1的agent进阶篇】RAG:从Embedding到检索增强生成的底层逻辑

🔥个人主页:代码不加冰(欢迎来访)
🎬作者简介:java后端学习者
❄️个人专栏:LeetCode刷题日记 , 苍穹外卖日记,SSM框架深入,JavaWeb,
命运的结局尽可永在,不屈的挑战却不可须臾或缺!


大家好,我是代码不加冰,好久没有发文章了,最近开始回归更新,让我们一起进步。这篇主要给大家分享一下RAG的相关内容。不是仅仅是一个词汇,而是带你从项目的源码看起,分析内部的逻辑。


RAG = 把用户的问题变成向量 → 去知识库找相关内容 → 把找到的内容塞回 Prompt → 再让大模型回答。

而 LangChain4j 做的事情,就是把这套流程拆成了一组非常清晰的抽象。

而我们今天主要侧重第一步Embedding,也就是文本向量化,文字怎么变成向量,刚听到这些词的时候我也会觉得莫名其妙,这明明是毫不相关的领域,到底是什么联系的,这篇文章就带你深入了解。

一、RAG 到底是什么

RAG = Retrieval-Augmented Generation

翻译成人话:

先检索,再增强,最后生成。

它解决的核心问题是:

LLM 的知识是死的(训练数据截止日期之后的事它不知道),
但 RAG 可以在用户提问时,从外部知识库实时检索相关信息,塞进 Prompt,让 LLM 基于这些信息回答。

所以 RAG 不是重新训练 LLM,而是给 LLM 配一个“外挂知识库”


二、RAG 整体链路

RAG 有两条链路,一上一下,缺一不可:

text RAG │ ┌──────────┴──────────┐ │ │ Ingestion Retrieval 数据入库 数据检索

链路一:Ingestion(数据入库)—— 提前准备好知识

text 公司员工手册.pdf ↓ Document ↓ DocumentSplitter(切分) ↓ TextSegment(文本片段) ↓ EmbeddingModel(向量化) ← 这里就是 Embedding ↓ Embedding(向量) ↓ EmbeddingStore(向量数据库)

最终库里存的是:

text TextSegment: "员工请假需要提前3天提交申请..." Embedding: [0.023, -0.182, 0.731, ...]

Ingestion 的本质:把人类能理解的知识,转换成机器能进行语义检索的向量。


链路二:Retrieval(检索增强)—— 用户提问时实时检索

text 用户:“请假需要提前多久?” ↓ UserMessage ↓ Query ↓ QueryTransformer(查询改写) ↓ QueryRouter(路由选择) ↓ ContentRetriever(真正检索) ← 这里会用到 Embedding 进行相似度搜索 ↓ EmbeddingStore(向量库) ↓ Top-K 相关内容 ↓ ContentAggregator(汇总排序) ↓ ContentInjector(注入 Prompt) ↓ 增强后的 UserMessage ↓ ChatModel(LLM) ↓ Answer

三、现在,我们聚焦Embedding

上面的链路中,Ingestion 的 EmbeddingModelRetrieval 的相似度搜索,都围绕同一个核心概念:

Embedding(文本向量化)


四、Embedding 到底是什么

在 LangChain4j 源码中,Embedding 就是一个float[]

java public class Embedding { private final float[] vector; }

例如一个 384 维的向量:

java float[] vector = { 0.12f, -0.52f, 0.83f, 0.19f, ... }; // 长度 = 384

Embedding ≠ 文本
Embedding 是文本经过模型编码后得到的一组数字

维度取决于模型:

模型维度
某些轻量模型384
OpenAI text-embedding-ada-0021536
Cohere 某些模型768

所以:

text Embedding = float[dimension]

五、为什么一定要 Embedding

这是最核心的问题。

用户问:

“请假需要提前多久申请?”

知识库里可能根本没有这句话,它写的是:

“员工休假须至少提前三个工作日提交审批。”

如果用传统字符串匹配(关键词搜索),基本匹配不到。

但如果用Embedding

text 问题:“请假需要提前多久申请?” ↓ Embedding ↓ [0.21, 0.83, -0.14, 0.52, ...] 知识:“员工休假须至少提前三个工作日提交审批。” ↓ Embedding ↓ [0.19, 0.79, -0.11, 0.48, ...]

这两个向量在向量空间中距离很近,所以能被检索到。

text 请假需要提前多久? ↓ Embedding ↓ [0.21, 0.83...] ↓ 向量数据库进行相似度搜索 ↓ 找到:“员工休假须至少提前三个工作日……”

这就是Semantic Search(语义搜索)

不是字面匹配,而是意思匹配。


六、CosineSimilarity:怎么判断两个向量像不像

向量数据库找到了候选结果,但怎么排序呢
相似度计算,最常用的是余弦相似度

源码中的计算逻辑:

java double dotProduct = 0.0; double normA = 0.0; double normB = 0.0; for (int i = 0; i < vectorA.length; i++) { dotProduct += vectorA[i] * vectorB[i]; normA += vectorA[i] * vectorA[i]; normB += vectorB[i] * vectorB[i]; } return dotProduct / Math.sqrt(normA) * Math.sqrt(normB);

用人话翻译:

判断两个向量的“方向”有多一致。

比如:

text A ────────────────> B ──────────────> 方向非常接近 → 相似度高 ✅ text A ────────────────> B <──────────────── 方向相反 → 相似度低 ❌

七、为什么用余弦,而不是算距离

因为 Embedding 关注的是语义方向,而不是长度

举个例子:

text

A = [1, 1] B = [10, 10]

虽然|A| ≠ |B|(长度不同),但方向完全一样。
余弦相似度:

text

cos θ = 1

所以它认为A 和 B 非常相似,这非常适合语义向量——
“很小的事”和“很大的事”在语义上可能是同一类。


八、为什么 LangChain4j 又搞了一个 RelevanceScore

余弦相似度的范围是[-1, 1],但 RAG 开发中更希望看到[0, 1]

所以 LangChain4j 做了转换:

java return (cosineSimilarity + 1) / 2;

映射关系:

余弦相似度RelevanceScore
-10
-0.50.25
00.5
0.50.75
11

然后就可以做阈值过滤:

java if (match.score() >= 0.7) { // 认为比较相关,采纳 }

九、不同 Embedding 模型为什么不能混用

这是一个非常容易踩的坑。

  • 模型 A:维度 = 1536

  • 模型 B:维度 = 384

如果你的向量数据库 Collection 定义的是dimension = 1536,却塞入 384 维向量:

text ❌ 报错

可以这样理解:

text EmbeddingModel.dimension() = 数据库表结构的 Schema

就像age INT不能塞字符串一样,向量维度必须严格匹配


十、Embedding 在 RAG 中的两个位置

理解了 Embedding,你就能看懂它在 RAG 中的两次出现:

text RAG │ ┌──────────┴──────────┐ │ │ Ingestion Retrieval │ │ ▼ ▼ 文档 → EmbeddingModel 问题 → EmbeddingModel │ │ ▼ ▼ Embedding Embedding │ │ ▼ ▼ EmbeddingStore 相似度搜索(Cosine) │ ▼ 找到相关内容
阶段Embedding 的作用
Ingestion把文档变成向量,存入数据库
Retrieval把用户问题变成向量,去数据库里找最相似的

十一、整体回顾

text ┌─────────────────────────────────────────────────────────────┐ │ RAG 完整链路 │ ├──────────────────────────┬──────────────────────────────────┤ │ Ingestion │ Retrieval │ ├──────────────────────────┼──────────────────────────────────┤ │ │ │ │ PDF → Document │ 用户问题 → Query │ │ ↓ │ ↓ │ │ DocumentSplitter │ QueryTransformer │ │ ↓ │ ↓ │ │ TextSegment │ QueryRouter │ │ ↓ │ ↓ │ │ ★ EmbeddingModel ★ │ ContentRetriever │ │ ↓ │ ↓ │ │ ★ Embedding ★ │ ★ 相似度搜索(Cosine)★ │ │ ↓ │ ↓ │ │ EmbeddingStore │ ContentAggregator │ │ │ ↓ │ │ │ ContentInjector │ │ │ ↓ │ │ │ ChatModel → Answer │ └──────────────────────────┴──────────────────────────────────┘

下一篇预告

这一篇我们重点攻克了Embedding 是什么、为什么、怎么算相似度

下一篇继续深入:向量数据库的存储与索引