RAG概念
什么是RAG?
RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索技术和AI内容生成的混合架构,可以解决大模型的知识时效性和幻觉问题。
简单来说,RAG就像给AI配了一个“小抄本”,让AI回答问题前先查一查特定的知识库来获取知识,确保回答是基于真实资料而不是凭空想象。

从技术角度看,RAG在大语言模型生成回答之前,会先从外部知识库中检索相关信息,然后将这些检索到的内容作为额外上下文提供给模型,引导其生成更准确、更相关的回答。
RAG工作流程
RAG技术实现主要包含以下4个核心步骤:
- 文档收集和切割
- 向量转换和存储
- 文档过滤和检索
- 查询增强和关联
文档收集和切割
文档收集:从各种来源(网页、PDF、数据库等)收集原始文档
文档预处理:清洗、标准化文本格式
文档切割:将长文档分割成适当大小的片段(俗称chunks)
- 基于固定大小(如512个token)
- 基于语义边界(如段落、章节)
- 基于递归分割策略(如递归字符n-gram切割)

向量转换和存储
向量转换:使用Embedding模型将文本块转换为高维向量表示,可以捕获到文本的语义特征
向量存储:将生成的向量和对应文本存入向量数据库,支持高效的相似性搜索

文档过滤和检索
查询处理:将用户问题也转换为向量表示
过滤机制:基于元数据、关键词或自定义规则进行过滤
相似度搜索:在向量数据库中查找与问题向量最相似的文档块,常用的相似度搜索算法有余弦相似度、欧式距离等
上下文组装:将检索到的多个文档块组装成连贯上下文

查询增强和关联
提示词组装:将检索到的相关文档与用户问题组合成增强提示
上下文融合:大模型基于增强提示生成回答
源引用:在回答中添加信息来源引用
后处理:格式化、摘要或其他处理以优化最终输出

完整工作流程

RAG相关技术
Embedding和Embedding模型
Embedding嵌入是将高维离散数据(如文字、图片)转换为低维连续向量的过程。这些向量能在数学空间中表示原始数据的语义特征,使计算机能够理解数据间的相似性。
Embedding 模型是执行这种转换算法的机器学习模型,如 Word2Vec(文本),ResNet(图像)等。不同的 Embedding 模型产生的 向量表示和维度数不同,一般维度越高表达能力更强,可以捕获更丰富的语义信息和更细微的差别,但同样占用更多存储空间。
举个例子,“鱼皮”和“鱼肉”的Embedding 向量在空间中较接近,而“鱼皮”和“帅哥”则相距较远,反映了语义关系。

向量数据库
向量数据库是专门存储和检索向量数据的数据库系统。通过高效索引算法实现快速相似性搜索,支持K近邻查询等操作。

注意,并不是只有向量数据库才能存储向量数据,只不过与传统数据库不同,向量数据库优化了高维向量的存储和检索。

召回
召回是信息检索中的第一阶段,目标是从大规模数据集中快速筛选出可能相关的候选项子集。强调速度和广度,而非精确度。
精排和Rank模型
精排(精确排序)是搜索/推荐系统的最后阶段,使用计算复杂度更高的算法,考虑更多特征和业务规则,对少量候选项进行更复杂、精细的排序。
比如,短视频推荐先通过召回获取数万个可能相关视频,再通过粗排缩减至数百条,最后精排阶段会考虑用户最近的互动、视频热度、内容多样性等复杂因素,确定最终展示的10个视频及顺序。

Rank模型(排序模型)负责对召回阶段筛选出的候选集进行精确排序,考虑多种特征评估相关性。
现代 Rank 模型通常基于深度学习,如BERT,LambdaMART等,综合考虑查询与候选项的相关性、用户历史行为等因素。举个例子,电商推荐系统会根据商品特征、用户偏好、点击率等给每个候选商品打分并排序。

混合检索策略
混合检索策略结合多种检索方法,提高搜搜效果。常见组合包括关键词检索、语义检索、知识图谱等。
比如在AI大模型开发平台Dify中,就为用户提供了“基于全文检索的关键词搜索+基于向量检索的语义检索”的混合检索策略,用户还可以自己设置不同检索方式的权重。

RAG实战:Spring AI + 本地知识库
标准的RAG开发步骤:
- 文档收集和切割
- 向量转换和存储
- 文档过滤和检索
- 查询增强和关联
简化后的RAG开发步骤:
- 文档准备
- 文档读取
- 向量转换和存储
- 查询增强
文档准备
首先准备用于给AI知识库提供知识的文档,推荐Markdown格式,尽量结构化。
我们可以利用AI来生成文档,提供一段示例Prompt:
帮我生成 3 篇 Markdown 文章,主题是【恋爱常见问题和回答】,3 篇文章的问题分别针对单身、恋爱、已婚的状态,内容形式为 1 问 1 答,每个问题标题使用 4 级标题,每篇内容需要有至少 5 个问题,要求每个问题中推荐一个相关的课程,课程链接都是 https://www.codefather.cn |

文档读取
首先,我们要对自己准备好的知识库文档进行处理,然后保存到向量数据库中。这个过程俗称ETL(抽取、转换、加载),Spring AI 提供了对ETL的支持。
ETL的3大核心组件,按照顺序执行:
- DocumentReader:读取文档,得到文档列表
- DocumentTransformer:转换文档,得到处理后的文档列表
- DocumentWriter:将文档列表保存到存储中(可以是向量数据库,也可以是其他存储)

- 引入依赖
SpringAI提供了很多种DocumentReaders,用于加载不同类型的文件。

我们可以使用MarkdownDocumentReader来读取Markdown文档。需要 先引入依赖:
<dependency> |
新建rag包,编写文档加载器类LoveAppDocumentLoader,负责读取所有Markdown文档并转换为Document列表。
public class LoveAppDocumentLoader {
private final ResourcePatternResolver resourcePatternResolver;
public LoveAppDocumentLoader(ResourcePatternResolver resourcePatternResolver) {
this.resourcePatternResolver = resourcePatternResolver;
}
public List<Document> loadMarkdowns(){
List<Document> allDocuments = new ArrayList<>();
try {
Resource[] resources = resourcePatternResolver.getResources("classpath:document/*.md");
for (Resource resource : resources) {
String filename = resource.getFilename();
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true) //设为 true 时,Markdown 中的水平分隔符将生成新 Document 对象
.withIncludeCodeBlock(false) //设为 true 时,代码块将与周边文本合并到同一 Document;设为 false 时,代码块生成独立 Document 对象
.withIncludeBlockquote(false)//设为 true 时,引用块将与周边文本合并到同一 Document;设为 false 时,引用块生成独立 Document 对象
.withAdditionalMetadata("filename", filename) //允许为所有生成的 Document 对象添加自定义元数据
.build();
MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config);
List<Document> documents = reader.get(); //文档的切片集合
allDocuments.addAll(documents);
}
} catch (IOException e) {
log.error(e.getMessage());
}
return allDocuments;
}
}上述代码中,我们通过 MarkdownDocumentReaderConfig 文档加载配置来指定读取文档的细节,比如是否读取代码块、引用块等。特别需要注意的是,我们还指定了额外的元信息配置,提取文档的文件名(fileName)作为文档的元信息,可以便于后续知识库实现更精确的检索。

向量转换和存储
为了实现方便,我们先使用Spring AI内置的、基于内存读写的向量数据库SimpleVectorStore来保存文档。
SimpleVectorStore实现了Vector接口,而VectorStore接口集成了DocumentWriter,所以具备森当写入能力。如图:

简单了解下源码,在将文档写入到数据库前,会先调用Embedding大模型将文档转换为向量,实际保存到数据库中的是向量类型的数据。

在rag包下新建LoveAppVectorStoreConfig类,实现初始化向量数据库并且保存文档的方法。
|
查询增强
Spring Al 通过 Advisor 特性提供了开箱即用的 RAG 功能。主要是QuestionAnswerAdvisor 问答拦截器和 RetrievalAugmentationAdvisor 检索增强拦截器,前者更简单易用、后者更灵活强大。
查询增强的原理其实很简单。向量数据库存储着AI模型本身不知道的数据,当用户问题发送给AI模型时,QuestionAnswerAdvisor会查询向量数据库,获取与用户问题相关的文档。然后从向量数据库返回的响应会被附加到用户文本中,为AI模型提供上下文,帮助其生成回答。
查看 QuestionAnswerAdvisor 源码,可以看到让AI基于知识库进行问答的Prompt:

此处我们就选用更简单易用的QuestionAnswerAdvisor 问答拦截器,在LoveApp 中新增和RAG知识库进行对话的方法。代码如下:
|
测试
|
运行程序,通过调试发现,加载的文档被自动按照小标题拆分,并且补充了metadata元信息:

查看请求,发现根据用户的问题检索到了4个文档切片,每个切片有对应的分数和元信息:


查看请求,发现用户的提示词被修改了,让AI检索知识库:
查看响应结果,AI的回复成功包含了知识库里的内容:

RAG实战:Spring AI + 云知识库服务
很多AI大模型应用开发平台都提供了云知识库服务,这里我们还是选择阿里云百炼,因为Spring Al Alibaba 可以和它轻松集成,简化RAG开发。
准备数据。在应用数据模块中,上传原始文档数据到平台,由平台来帮忙解析文档中的内容和结构:

进入阿里云百炼平台的知识库,创建一个知识库,选择默认配置即可:

导入数据到知识库

导入数据时,可以设置数据预处理规则,智能切分文档为文档切片

创建好知识库后,进入知识库查看文档和切片

如果你觉得智能切分得到的切片不合理,可以手动编辑切片内容:

RAG开发
有了知识库后,我们就可以用程序来对接了。开发过程很简单,可以参考 Spring Al Alibaba的官方文档来学习。
Spring Al Alibaba 利用了Spring Al提供的文档检索特性(DocumentRetriever),自定义了一套文档检索的方法,使得程序会调用阿 里灵积大模型API来从云知识库中检索文档,而不是从内存中检索。
使用下列代码就可以创建一个文档检索器并发起查询:
// 调用大模型的 API |
如何使用这个文档检索器,让AI从云知识库查询文档呢?
这就需要使用Spring Al提供的另一个 RAG Advisor-RetrievalAugmentationAdvisor检索增强顾问,可以绑定文档检索器、查询转 换器和查询增强器,更灵活地构造查询。
示例代码如下,先仅作了解即可,后面章节中会带大家实战检索增强顾问的更多特性:
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder() |
回归到我们的项目中,先编写一个配置类,用于初始化基于云知识库的检索增强顾问Bean:
class LoveAppRagCloudAdvisorConfig {
private String dashScopeApiKey;
public Advisor loveAppRagCloudAdvisor() {
DashScopeApi dashScopeApi = new DashScopeApi(dashScopeApiKey);
final String KNOWLEDGE_INDEX = "恋爱大师";
DocumentRetriever documentRetriever = new DashScopeDocumentRetriever(dashScopeApi,
DashScopeDocumentRetrieverOptions.builder()
.withIndexName(KNOWLEDGE_INDEX)
.build());
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(documentRetriever)
.build();
}
}注意上述代码中指定知识库要使用名称(而不是id)。
然后在LoveApp中使用Advisor:
private Advisor loveAppRagCloudAdvisor;
public String doChatWithRag(String message, String chatId) {
ChatResponse chatResponse = chatClient
.prompt()
.user(message)
.advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
// 开启日志,便于观察效果
.advisors(new MyLoggerAdvisor())
// 应用增强检索服务(云知识库服务)
.advisors(loveAppRagCloudAdvisor)
.call()
.chatResponse();
String content = chatResponse.getResult().getOutput().getText();
log.info("content: {}", content);
return content;
}测试一下。通过调试查看请求,能发现检索到了多个文档切片,每个切片有对应的元信息:

查看请求,发现用户提示词被改写,查询到的关联文档已经作为上下文拼接到了用户提示词中:

查看响应结果,成功包含了知识库里的内容:

扩展
我们可以利用RAG知识库,实现“通过用户的问题推荐可能的恋爱对象”功能。
我们可以新建一个恋爱对象文档,每行数据包含一位用户的基本信息(年龄、星座、职业等等)。示例文档如下:
| 姓名 | 年龄 | 星座 | 爱好 | 职业 |
|---|---|---|---|---|
| 陆星辰 | 28 | 射手座 | 旅行、玩滑板、看纪录片、学外语 | 旅游博主 |
| 沈墨言 | 32 | 摩羯座 | 阅读、攀岩、品威士忌、逛博物馆 | 架构师 |
| 顾清欢 | 26 | 双鱼座 | 画画、照料植物、做手账、听民谣 | 花艺师 |
| 赵知行 | 35 | 处女座 | 马拉松、研究厨艺、参观科技展 | 大学讲师 |
| 苏小暖 | 29 | 狮子座 | 看话剧、组织朋友聚会、潜水、跳舞 | 品牌策划经理 |
将创建好的文档上传到平台依次操作即可。
接下来我们进行测试即可:
|
结果如下:

发现结果和我们预期的一样,成功!