找回密码
 立即注册
首页 业界区 安全 10. Spring AI + RAG

10. Spring AI + RAG

士沌 4 天前
10. Spring AI  + RAG

@
目录

  • 10. Spring AI  + RAG

    • RAG

      • 概念

        • 向量:
        • 文本向量化
        • 向量数据库
        • 匹配检索

          • SearchRequest


      • 接入ChatClient

        • RetrievalAugmentationAdvisor



  • 最后:

RAG

检索增强生成(Retrieval-augmented Generation)
对于基础大模型来说, 他只具备通用信息,他的参数都是拿公网进行训练,并且有一定的时间延迟, 无法得知一些具体业务数据和实时数据, 这些数据往往在各种文件中(比如txt、word、html、数据库...)
虽然function-call、SystemMessage可以用来解决一部分问题
但是它只能少量,并且针对的场景不一样
如果你要提供大量的业务领域信息, 就需要给他外接一个知识库:
比如

  • 我问他退订要多少费用
  • 这些资料可能都由产品或者需求编写在了文档中:

    • 所以需要现在需求信息存到向量数据库(这个过程叫Embedding, 涉及到文档读取、分词、向量化存入)

  • 去向量数据库中查询“退订费用相关信息”
  • 将查询到的数据和对话信息再请求大模型
  • 此时会响应退订需要多少费用
1.png

概念

向量:

向量通常用来做相似性搜索,比如语义的一维向量,可以表示词语或短语的语义相似性。例如,“你好”、“hello”和“见到你很高兴”可以通过一维向量来表示它们的语义接近程度。
2.png

然而,对于更复杂的对象,比如小狗,无法仅通过一个维度来进行相似性搜索。这时,我们需要提取多个特征,如颜色、大小、品种等,将每个特征表示为向量的一个维度,从而形成一个多维向量。例如,一只棕色的小型泰迪犬可以表示为一个多维向量 [棕色, 小型, 泰迪犬]。
3.png

如果需要检索见过更加精准, 我们肯定还需要更多维度的向量, 组成更多维度的空间,在多维向量空间中,相似性检索变得更加复杂。我们需要使用一些算法,如余弦相似度或欧几里得距离,来计算向量之间的相似性。向量数据库会帮我实现。
4.png

文本向量化

通过向量模型即可向量化, 这里我们学到了一种新的模型, 叫“向量模型” 专门用来做文本向量化的。
大语言模型不能做向量化, 所以需要单独找一个向量模型

  • deepseek不支持向量模型
  • 阿里百炼有大量向量模型

    • 默认模型DashScopeEmbeddingProperties#DEFAULT_EMBEDDING_MODEL="text-embedding-v1"

5.png
  1. spring.ai.dashscope.embedding.options.model= text-embedding-v4
复制代码

  • ollama有大量向量模型, 自己拉取
6.png

以ollama为例:
  1. spring.ai.ollama.embedding.model= nomic-embed-text
复制代码
  1. @SpringBootTest
  2. public class EmbaddingTest {
  3.     @Test
  4.     public void testEmbadding(@Autowired OllamaEmbeddingModel ollamaEmbeddingModel) {
  5.         // .embed() 转换为向量模型
  6.         float[] embedded = ollamaEmbeddingModel.embed("我叫徐庶");
  7.         System.out.println(embedded.length);
  8.         System.out.println(Arrays.toString(embedded));
  9.     }
  10. }
复制代码
7.png

从结果可以知道"我叫徐庶"这句话经过OllamaEmbeddingModel向量化之后得到的一个长度为768的float数组。注意,768是向量模型nomic-embed-text-v1.5固定的,不会随着句子长度而变化,不同的向量模型提供了不同的维度。
那么,我们通过这种向量模型得到一句话对应的向量有什么作用呢?非常有用,因为我们可以基于向量来判断两句话之间的相似度,举个例子:
查询跟秋田犬类似的狗, 在向量数据库中根据每个狗的特点进行多维向量, 你会发现秋田犬的向量数值和柴犬的向量数值最接近, 就可以查到类似的狗。 (当然我这里只是举例,让你对向量数据库有一个印象)
8.png


  • 向量模型的本质目标,就是把语义相似的内容用“相近”的向量表示,把“不相关”内容尽量拉远。
  • 所以好的向量模型能够更好的识别语义, 进行向量化.
  向量数据库

对于向量模型生成出来的向量,我们可以持久化到向量数据库,并且能利用向量数据库来计算两个向量之间的相似度,或者根据一个向量查找跟这个向量最相似的向量。
在SpringAi中,VectorStore 表示向量数据库,目前支持的向量数据库有

  • Azure Vector Search- TheAzurevector store.
  • Apache Cassandra- TheApache Cassandravector store.
  • Chroma Vector Store- TheChromavector store.
  • Elasticsearch Vector Store - The Elasticsearch vector store. 可以“以向量+关键词”方式做混合检索。深度优化更多针对文本,不是专门“向量搜索引擎”。向量存储和检索容量有限制,查询延迟高于 Milvus。
  • GemFire Vector Store- TheGemFirevector store.
  • MariaDB Vector Store- TheMariaDBvector store.
  • Milvus Vector Store - The Milvus vector store.
  • MongoDB Atlas Vector Store- TheMongoDB Atlasvector store.
  • Neo4j Vector Store - The Neo4j vector store.可以结合结构化图谱查询与向量检索, 大规模嵌入检索(如千万—亿级高维向量)性能明显落后于 Milvus
  • OpenSearch Vector Store- TheOpenSearchvector store.
  • Oracle Vector Store- TheOracle Databasevector store.
  • PgVector Store- ThePostgreSQL/PGVectorvector store.
  • Pinecone Vector Store-PineConevector store.
  • Qdrant Vector Store-Qdrantvector store.
  • Redis Vector Store - The Redis vector store. 低门槛实现小规模向量检索。对于高维大规模向量(如几百万到上亿条),性能和存储效率不如专用向量库。
  • SAP Hana Vector Store- TheSAP HANAvector store.
  • Typesense Vector Store- TheTypesensevector store.
  • Weaviate Vector Store- TheWeaviatevector store.
  • SimpleVectorStore - A simple implementation of persistent vector storage, good for educational purposes.
其中有我们熟悉的几个数据库都可以用来存储向量,比如Elasticsearch、MongoDb、Neo4j、Pgsql、Redis。
视频中我会讲解2种:

  • SimpleVectorStore教学版向量数据库
  • Milvus Vector StoreMilvus(国产团队)、文档友好、社区国内活跃、性能最佳、市场占用率大。 实战中使用的向量数据库.
9.png

匹配检索

在这个示例中, 我分别存储了预订航班和取消预订2段说明到向量数据库中
然后通过"退票要多少钱" 进行查询
10.png

代码执行结果为:
OllamaEmbedding结果
  1. @Bean
  2.     public VectorStore vectorStore(OllamaEmbeddingModel embeddingModel) {
  3. // 绑定一个向量大模型,进行将数据信息向量化
  4.         SimpleVectorStore.SimpleVectorStoreBuilder builder = SimpleVectorStore.builder(embeddingModel);
  5.         return builder.build();
  6.     }
复制代码
SearchRequest

可以利用searchRequest设置检索请求:

  • query 代表要检索的内容
  • topK 设置检索结果的前N条

    • 通常我们查询所有结果查出来, 因为查询结果最终要发给大模型, 查询过多的结果会:


        • 过多的token意味着更长延迟, 更多的费用, 并且过多上下文会超限;


        • 研究表明过多的内容会降低 LLM 的_**召回性能;**_



  • similarityThreshold 设置相似度阈值, 可以通关设置分数限制召回内容相似度. 从而过滤掉废料。 (中文语料要适当降低分数) , 所以应遵循**始终以“业务召回效果”为主,而不是追求网上常说的高分阈值。
  1. @BeforeEach
  2. public void init( @Autowired
  3.                   VectorStore vectorStore) {
  4.     // 1. 声明内容文档
  5.     Document doc = Document.builder()
  6.     .text("""
  7.           预订航班:
  8.           - 通过我们的网站或移动应用程序预订。
  9.           - 预订时需要全额付款。
  10.           - 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生 25 的费用。
  11.           """)
  12.     .build();
  13.     Document doc2 = Document.builder()
  14.     .text("""
  15.           取消预订:
  16.           - 最晚在航班起飞前 48 小时取消。
  17.           - 取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
  18.           - 退款将在 7 个工作日内处理。
  19.           """)
  20.     .build();
  21.     // 2. 将文本进行向量化,并且存入向量数据库(无需再手动向量化)
  22.     vectorStore.add(Arrays.asList(doc,doc2));
  23. }
  24. @Test
  25. void similaritySearchTest(
  26.     @Autowired
  27.     VectorStore vectorStore) {
  28.     // 3. 相似性查询
  29.     SearchRequest searchRequest = SearchRequest
  30.     .builder().query("预定航班")
  31.     .topK(5) // 查询几个
  32.     .similarityThreshold(0.3) // 过滤相似度 0.3 的内容,才返回
  33.     .build();
  34.     List<Document> results = vectorStore.similaritySearch(searchRequest);
  35.     // 4.输出
  36.     System.out.println(results);
  37. }
复制代码
可以看到明显阿里的向量模型归类的更加准确,Ollama的向量模型查出来后结果并不正确。 所以为了你的准确性,请选择性能更好的向量模型。 想要更快更相似的搜索,用好的向量数据库。
接入ChatClient


  • 依赖
  1. <dependency>
  2.    <groupId>org.springframework.ai</groupId>
  3.    spring-ai-advisors-vector-store</artifactId>
  4. </dependency>
复制代码

  • 代码
  1. @Bean
  2.     public VectorStore vectorStore(DashScopeEmbeddingModel embeddingModel) {
  3.         SimpleVectorStore.SimpleVectorStoreBuilder builder = SimpleVectorStore.builder(embeddingModel);
  4.         return builder.build();
  5.     }
复制代码

  • 测试
实际你会发现, 最核心的是通过拦截器:QuestionAnswerAdvisor . 你应该能猜到底层肯定会通过拦截对话将相似内容发给大模型。 可以结合SimpleLoggerAdvisor 查看日志内容.
  1. @SpringBootTest
  2. public class SimpleVectorStoreTest {
  3.     @BeforeEach
  4.     public void init( @Autowired
  5.             VectorStore vectorStore) {
  6.         // 1. 声明内容文档
  7.         Document doc = Document.builder()
  8.                 .text("""
  9.                 预订航班:
  10.                 - 通过我们的网站或移动应用程序预订。
  11.                 - 预订时需要全额付款。
  12.                 - 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生 25 的费用。
  13.                 """)
  14.                 .build();
  15.         Document doc2 = Document.builder()
  16.                 .text("""
  17.                 取消预订:
  18.                 - 最晚在航班起飞前 48 小时取消。
  19.                 - 取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
  20.                 - 退款将在 7 个工作日内处理。
  21.                 """)
  22.                 .build();
  23.         // 2. 将文本进行向量化,并且存入向量数据库(无需再手动向量化)
  24.         vectorStore.add(Arrays.asList(doc,doc2));
  25.     }
  26.     @Test
  27.     void chatRagTest(
  28.             @Autowired
  29.             VectorStore vectorStore,
  30.             @Autowired DashScopeChatModel chatModel
  31.             ) {
  32.         ChatClient chatClient = ChatClient.builder(chatModel)
  33.                 .build();
  34.         String message="退费需要多少费用?";
  35.         String content = chatClient.prompt().user(message)
  36.         // 通过 advisors 查询向量数据库,进行过滤
  37.                 .advisors(
  38.                         new SimpleLoggerAdvisor(),
  39.                         QuestionAnswerAdvisor.builder(vectorStore)
  40.                                 .searchRequest(
  41.                                         SearchRequest
  42.                                         .builder().query(message)
  43.                                                 .topK(5)
  44.                                                 .similarityThreshold(0.3)
  45.                                                 .build())
  46.                                 .build()
  47.                 ).call().content();
  48.         System.out.println(content);
  49.     }
  50. }
复制代码
RetrievalAugmentationAdvisor


  • 查询空时扩展策略 :
  1. .queryAugmenter(ContextualQueryAugmenter.builder()
  2.                         .allowEmptyContext(false)
  3.                         .emptyContextPromptTemplate(PromptTemplate.builder().template("用户查询位于知识库之外。礼貌地告知用户您无法回答").build())
  4.                         .build())
复制代码

  • 查询检索器

    • 检索提示词重写

  1. .queryTransformers(RewriteQueryTransformer.builder()
  2.                         .chatClientBuilder(ChatClient.builder(dashScopeChatModel))
  3.                         .targetSearchSystem("航空票务助手")
  4.                         .build())
复制代码
翻译重写
  1. .queryTransformers(TranslationQueryTransformer.builder()
  2.                                     .chatClientBuilder(ChatClient.builder(dashScopeChatModel))
  3.                                     .targetLanguage("中文")
  4.                                     .build())
复制代码

  • 后置处理器:需要文档后处理和重排序
  • 实现复杂的 RAG 流水线
  1. @Test    public void testRag3(@Autowired VectorStore vectorStore,                        @Autowired DashScopeChatModel dashScopeChatModel) {        chatClient = ChatClient.builder(dashScopeChatModel)                .defaultAdvisors(SimpleLoggerAdvisor.builder().build())                .build();        // 增强多        Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()                // 查 = QuestionAnswerAdvisor                .documentRetriever(VectorStoreDocumentRetriever.builder()                        .similarityThreshold(0.50)                        .vectorStore(vectorStore)                        .build())                // 检索为空时,返回提示                /*.queryAugmenter(ContextualQueryAugmenter.builder()
  2.                         .allowEmptyContext(false)
  3.                         .emptyContextPromptTemplate(PromptTemplate.builder().template("用户查询位于知识库之外。礼貌地告知用户您无法回答").build())
  4.                         .build())*/                // 相似性查询内容转换                /*.queryTransformers(RewriteQueryTransformer.builder()
  5.                         .chatClientBuilder(ChatClient.builder(dashScopeChatModel))
  6.                         .targetSearchSystem("航空票务助手")
  7.                         .build())*/                // 检索后文档监控、操作                /*.documentPostProcessors((query, documents) -> {                    System.out.println("Original query: " + query.text());                    System.out.println("Retrieved documents: " + documents.size());                    return documents;                })*/                .build();        String answer = chatClient.prompt()                .advisors(retrievalAugmentationAdvisor)                .user("退一张票大概要多少费用?希望别扣太多啊")                .call()                .content();        System.out.println(answer);    }    @TestConfiguration    static class TestConfig {        @Bean        public VectorStore vectorStore(DashScopeEmbeddingModel embeddingModel) {            return SimpleVectorStore.builder(embeddingModel).build();        }    }
复制代码
最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
11.gif


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册