🔥个人主页:代码不加冰(欢迎来访)
🎬作者简介: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 的 EmbeddingModel和Retrieval 的相似度搜索,都围绕同一个核心概念:
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, ... }; // 长度 = 384Embedding ≠ 文本
Embedding 是文本经过模型编码后得到的一组数字。
维度取决于模型:
| 模型 | 维度 |
|---|---|
| 某些轻量模型 | 384 |
| OpenAI text-embedding-ada-002 | 1536 |
| 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 |
|---|---|
| -1 | 0 |
| -0.5 | 0.25 |
| 0 | 0.5 |
| 0.5 | 0.75 |
| 1 | 1 |
然后就可以做阈值过滤:
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 是什么、为什么、怎么算相似度。
下一篇继续深入:向量数据库的存储与索引