检索增强生成-RAG

简介

我们去问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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容