简介
我们去问AI一些问题的时候,如果这个知识是模型在训练时没有涉及到的,这个时候AI就会出现幻觉(看似合理但实则错误或虚构的信息。
所以我们想要避免AI出现幻觉就需要让AI知道相关的知识。检索增强生成(RAG)技术其核心价值在于,它以一种成本效益高且灵活的方式,为LLM连接了一个可实时更新的“外部知识库”,从而显著提升了AI应用的可靠性、时效性和专业性。
什么是RAG
RAG (Retrieval-Augmented Generation)检索增强生成。通过外部数据库检索出相关知识,然后把问题和相关知识一起给到大模型来让模型生成回答。
RAG流程

image.png

image.png

image.png
向量:向量就像是一个有序的数字列表,或者说是多维空间中的一个点、一条有方向的线段。这些数字共同描述了对象在某一个“特征空间"中的位置。
为什么要把文本转换成向量:计算机无法直接理解人类语言,但极其擅长处理数字。一旦文本变成了向量(即高维空间中的点),我们就可以运用强大的数学工具来处理它们,计算相似度。(比如余弦相似度算法)
Embedding 嵌入:把文本->多维向量的过程叫做Embedding嵌入。
EmbeddingMode嵌入模型:嵌入模型可以把文本转换成多维向量
向量数据库:能够存储向量并且具有向量相似度检索相关能力的数据库
向量维度:向量的维度就是这个列表里数字的个数。例如,向量[0.12,-0.87,0.33,1.24]的维度是4;较高维度通常能提供更丰富的语义信息和更强的区分能力,尤其利于处理复杂语义或需要高精度的场景。但这会增加计算量,占用更多内存和存储,也可能使计算速度变慢
数据库Redis Stack
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>
#启动时自动创建redis向量索引结构
spring.ai.vectorstore.redis.initialize-schema=true
#Redis向量索引名称
spring.ai.vectorstore.redis.index-name=yutian_rag_index
#Redis健名前缀,区分不同应用哦的向量名称
spring.ai.vectorstore.redis.prefix=yutian_rag_prefix
#Redis地址
spring.data.redis.host=localhost
#Redis端口
spring.data.redis.port=6379
向量数据存储
/**
* 向量数据库存储
*
* @param data
* @return
*/
@PostMapping("/importData")
public String importData(@RequestParam(name = "data") String data){
Document build = Document.builder()
.text(data)
.build();
vectorStore.add(List.of(build));
return "scucess";
}

数据库存储结果
向量数据查找
/**
* 向量数据库相似度搜索
*
* @param query
* @return
*/
@PostMapping("/search")
public List<Document> search(@RequestParam(name = "query") String query){
SearchRequest searchRequest = SearchRequest.builder()
.topK(2)
.query(query)
.build();
List<Document> documents = vectorStore.similaritySearch(searchRequest);
return documents;
}
调用请求:
POST http://localhost:3030/rag/search?query=高高的职业是什么
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jun 2026 08:00:05 GMT
[
{
"id": "786a6d4a-8d64-4c82-a25e-ef18d1f41717",
"text": "我是高高",
"media": null,
"metadata": {
"distance": 0.13283253,
"vector_score": 0.13283253
},
"score": 0.8671674728393555
},
{
"id": "76d31d55-6d74-401f-b390-a5b98d1355ef",
"text": "高高喜欢看电影,喜欢做饭,喜欢打电脑,他的工作是IT程序员",
"media": null,
"metadata": {
"distance": 0.14454788,
"vector_score": 0.14454788
},
"score": 0.8554521203041077
}
]
Response file saved.
> 2026-06-24T160005.200.json
Response code: 200; Time: 358ms (358 ms); Content length: 348 bytes (348 B)
讲FAQ常见问题简单切片后存入向量数据库
/**
* 常用FAQ导入到向量数据库
* @return
*/
@PostMapping("/faqImport")
public String faqImport(){
List<Document> messageList = new ArrayList<>();
try {
ClassPathResource classPathResource = new ClassPathResource("FAQ.xlsx");
List<Question> questionList = EasyExcel.read(classPathResource.getInputStream()).head(Question.class).sheet().doReadSync();
for (Question a : questionList) {
log.info(JSON.toJSONString(a));
if (StringUtils.isNoneBlank(a.getQuestion()) && StringUtils.isNotBlank(a.getAnswer())) {
Document document = new Document("问题:"+a.getQuestion()+"\n回答:"+a.getAnswer());
messageList.add(document);
}
//当前使用的通义向量接口单次批量最大只能传 10 条文本,之前设的 25 超限,接口返回 400 参数非法
//老版 DashScope embedding 限制 25,新版接口收紧为单次最多 10 条
//向量库 RedisVectorStore.add(List<Document>) 会把整批文档一次性丢给 Embedding 接口,批量超过 10 就报错
if(messageList.size()>=10){
vectorStore.add(messageList);
log.info("导入成功{}条",messageList.size());
messageList.clear();
}
}
}catch (IOException e){
e.printStackTrace();
}
return "导入成功";
}
请求结果
POST http://localhost:3030/rag/search?query=能不能先做效果再付费?
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jun 2026 10:10:03 GMT
[
{
"id": "be996457-502c-4886-b2a1-d61e9ef8caf9",
"text": "问题:能不能先做效果再付费?\n回答:可提供阶段性效果对赌合作,先收取基础服务成本费,约定月度/季度销售目标,目标达成后再收取提点费用;若连续2个月未达成合同约定的核心目标,商家可无条件解约,我们将按合同约定退还对应比例的服务费用。",
"media": null,
"metadata": {
"distance": 0.092870355,
"vector_score": 0.092870355
},
"score": 0.9071296453475952
}
]
Response file saved.
> 2026-06-24T181003.200.json
Response code: 200; Time: 396ms (396 ms); Content length: 276 bytes (276 B)
RetrievalAugmentationAdvisor 增强实现知识库检索后智能问答
@Bean
public ChatClient chatClient(){
// return ChatClient.builder(charmodel()).build();
// 内置MessageChatMemoryAdvisor实现消息会话存储
MessageWindowChatMemory widownchatMemory = MessageWindowChatMemory.builder()
// 最大消息数量
// .maxMessages(20)
// .chatMemoryRepository()
.build();
MessageChatMemoryAdvisor chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(widownchatMemory).build();
//检索稳定advisor
VectorStoreDocumentRetriever vectorStoreDocumentRetriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.topK(2)
.similarityThreshold(0.8)
.build();
RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(vectorStoreDocumentRetriever)
.build();
return ChatClient.builder(charmodel())
.defaultAdvisors(chatMemoryAdvisor,retrievalAugmentationAdvisor)
.build();
}
/**
* 智能客服实现RAG智能问答
* @param query
* @return
*/
@PostMapping("/rag-ask")
public String ragAskQuestion(@RequestParam(name = "query") String query){
return chatClient.prompt()
.system("你是一个智能客服助手,回答客户问答的问题")
.user(query)
.call()
.content();
}
请求结果
POST http://localhost:3030/rag/rag-ask?query=能不能先做效果再付费?Content-Type: application/x-www-form-urlencoded
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 287
Date: Wed, 24 Jun 2026 10:38:48 GMT
可提供阶段性效果对赌合作,先收取基础服务成本费,约定月度/季度销售目标,目标达成后再收取提点费用;若连续2个月未达成合同约定的核心目标,商家可无条件解约,我们将按合同约定退还对应比例的服务费用。
Response code: 200; Time: 4441ms (4 s 441 ms); Content length: 97 bytes (97 B)