JavaAI04-RAG

RAG是什么

  • 检索增强生成(Retrieval-augmented Generation)
  • 基础的大模型,只能知道一些公网上的信息,并且也是基于公网数据训练学习的,不可能知道我们私有的业务数据。
  • 虽然function-call、SystemMessage可以用来解决一部分问题,但归根结底还是太少了,最好的办法就是给AI外接一个专业知识库。

向量

  • 向量通常用来做相似性搜索,相当于一个一维坐标,语义相似的词会在附近。
  • 然而,对于更复杂的对象,比如狗,是没法通过一个维度展示的。
    • 这时,我们需要提取多个特征,如颜色、大小、品种等,将每个特征表示为向量的一个维度,从而形成一个多维向量
    • 例如,一只棕色的小型泰迪犬可以表示为一个多维向量 [棕色, 小型, 泰迪犬]。
    • 如果需要检索见过更加精准, 我们肯定还需要更多维度的向量, 组成更多维度的空间,在多维向量空间中,相似性检索变得更加复杂。我们需要使用一些算法,如余弦相似度或欧几里得距离,来计算向量之间的相似性。
    • 但我们在应用层,可以不去接触这些算法,向量数据库会帮我们实现。

案例

  • 新建一个项目,引入maven依赖

    xml 复制代码
    <dependencyManagement>
            <dependencies>
                <!-- 引入 Spring Boot BOM -->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>3.4.3</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <!-- SpringBoot 核心包 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <!-- SpringBoot Web容器 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!-- 阿里云百炼 -->
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
                <version>1.0.0-beta3</version>
            </dependency>
    
            <!-- webflux -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>
    
            <!-- langchain4j核心 -->
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j</artifactId>
                <version>1.0.0-beta3</version>
            </dependency>
        </dependencies>
  • 文本向量化(这里选用阿里云百炼的通义千问)

    • 调用 embeddingModel.embed 方法,可以获取文本向量化后的数据

      java 复制代码
      import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
      import dev.langchain4j.data.embedding.Embedding;
      import dev.langchain4j.model.output.Response;
      
      public class Demo01 {
      
          public static String xlKey = "sk-xxxxxxxxx";
      
          public static void main(String[] args) {
              QwenEmbeddingModel embeddingModel= QwenEmbeddingModel.builder()
                      .apiKey(xlKey)
                      .build();
      
              Response<Embedding> embed = embeddingModel.embed("你好");
              System.out.println(embed.content().toString());
              System.out.println(embed.content().vector().length);
          }
      
      }
    • 结果是一个向量数组;其中每个浮点类型的数字,就是一个维度;下方的1536,表示一共有1536个维度;向量的维度越多,模型检索的精度就越高

向量数据库

  • 文本向量值有了,现在要进行相似性检索,需要用到向量数据库
  • 可以看下LangChain4j官网中,对于向量数据库的支持
  • 其中有很多我们熟悉的,Elasticsearch、Oracle、Redis等,用哪个点击进入会有详细说明
  • LangChain4j官网中支持的向量数据库

相似性检索的步骤

  • 首先是数据嵌入阶段(embedding),将文本、图像等数据转化为低维数值向量。
  • 数据检索阶段:将问题转换为向量,然后去检索。然后把相似性的数据结合用户的提问,一起发给大模型。
  • 大模型最终会结合对话和向量信息,返回给用户。

案例

  • 这里我们使用最简单的内存存储 ,即In-memory

    java 复制代码
    import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
    import dev.langchain4j.data.embedding.Embedding;
    import dev.langchain4j.data.segment.TextSegment;
    import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
    import dev.langchain4j.store.embedding.EmbeddingSearchResult;
    import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
    
    public class Demo02 {
        public static void main(String[] args) {
            InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
    
            QwenEmbeddingModel embeddingModel= QwenEmbeddingModel.builder()
                    .apiKey("sk-xxxxxx")
                    .build();
    
            // 利用向量模型进行向量化, 然后存储向量到向量数据库
            TextSegment segment1 = TextSegment.from("""
                    预订航班:
                    - 通过我们的网站或移动应用程序预订。
                    - 预订时需要全额付款。
                    - 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生 25 的费用。
                    """);
            Embedding embedding1 = embeddingModel.embed(segment1).content();
            embeddingStore.add(embedding1, segment1);
    
            // 利用向量模型进行向量化, 然后存储向量到向量数据库
            TextSegment segment2 = TextSegment.from("""
                    取消预订:
                    - 最晚在航班起飞前 48 小时取消。
                    - 取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
                    - 退款将在 7 个工作日内处理。
                    """);
            Embedding embedding2 = embeddingModel.embed(segment2).content();
            embeddingStore.add(embedding2, segment2);
    
    
            // 需要查询的内容 向量化
            Embedding queryEmbedding = embeddingModel.embed("退票要多少钱").content();
    
            // 去向量数据库查询
            // 构建查询条件
            EmbeddingSearchRequest build = EmbeddingSearchRequest.builder()
                    .queryEmbedding(queryEmbedding)
                    .maxResults(1)//取最符合的几条数据,这里是1条
                    .minScore(0.6)//只有符合0.6的才会选用,也可以不设置
                    .build();
    
            // 查询
            EmbeddingSearchResult<TextSegment> segmentEmbeddingSearchResult = embeddingStore.search(build);
            segmentEmbeddingSearchResult.matches().forEach(embeddingMatch -> {
                System.out.println(embeddingMatch.score()); // 0.8144288515898701
                System.out.println(embeddingMatch.embedded().text()); // I like football.
            });
        }
    }
  • 运行结果

  • 可以看到AI将最符合的返回了,相似度为73.2%

知识库RAG演练

实现逻辑

  • 知识库存储
    • 文档读取
    • 通过分割器将文档切片为文本集合
    • 利用向量模型,将切片转化为向量
    • 将向量存储到数据库
  • 智能对话
    • 用户的问题转化为向量
    • 用向量从数据库中检索数据
    • 把检索后的数据和用户的对话一起传到AI大模型
    • AI大模型返回数据

Document Parser 文档解析器

  • 要开发一个知识库,这些资料可能存在各种文档中,比如:word、txt、pdf、image、html等等, 所以langchain4j也提供了不同的文档解析器(不满足的时候也可以自定义解析器):
    • TextDocumentParser:来自 langchain4j 模块,可解析 TXT、HTML、MD 等纯文本格式文件。
    • ApachePdfBoxDocumentParser:来自 langchain4j-document-parser-apache-pdfbox 模块,用于解析 PDF 文件。
    • ApachePoiDocumentParser:来自 langchain4j-document-parser-apache-poi 模块,支持解析 DOC、DOCX、PPT、PPTX、XLS、XLSX 等 MS Office 文件格式。
    • ApacheTikaDocumentParser:来自 langchain4j-document-parser-apache-tika 模块,能自动检测并解析几乎所有现有文件格式。
  • 代码实现文档解析器
    • 实现从类路径(resources)下读取文件;使用 TextDocumentParser

    • resourcesx下新建一个txt文件:cs.txt

      txt 复制代码
      - 最晚在航班起飞前 48 小时取消。取消费用:经济舱 75 美元,豪华经济舱 50 美元,
      商务舱 25 美元。退款将在 7 个工作日内处理。
    • java代码

      java 复制代码
      import dev.langchain4j.data.document.Document;
      import dev.langchain4j.data.document.loader.ClassPathDocumentLoader;
      import dev.langchain4j.data.document.parser.TextDocumentParser;
      
      public class Demo03 {
      
          public static void main(String[] args) {
              //文档读取
              Document document = ClassPathDocumentLoader.
                      loadDocument("demo03/cs.txt", new TextDocumentParser());
              String text = document.text();
              System.out.println(text);
          }
      
      }
    • 控制台

DocumentSplitter‌ 文档拆分器

  • 目的:要保证同一个主题保存在一个片段当中,不要出现断句
  • 作用
    • 文本读取到之后,按语义分成一段一段,每一块叫chunk
    • 在后面可以更精确地进行语义相似性检索
    • 可以避免LLM的最大Token限制
  • langchain4j也提供了不同的文档拆分器
    • 下面这些内置分割器都实现了一个split方法用来分隔
    • DocumentByCharacterSplitter‌
      • 无符号分割
      • 就是严格根据字数分隔(不推荐,会出现断句)
    • DocumentByRegexSplitter‌
      • 正则表达式分隔
      • 根据自定义正则‌分隔
    • DocumentByParagraphSplitter‌
      • 删除大段空白内容
      • 处理连续换行符(如段落分隔)(\s*(?>\R)\s*(?>\R)\s*
    • DocumentByLineSplitter‌
      • 删除单个换行符周围的空白, 替换一个换行
      • (\s*\R\s*)
      • ‌示例‌:
        • 输入文本:"This is line one.\n\tThis is line two."
        • 使用 \s*\R\s* 替换为单个换行符:"This is line one.\nThis is line two."
    • DocumentByWordSplitter‌
      • 删除连续的空白字符。
      • \s+
      • 示例‌
        • 输入文本:"Hello World"
        • 使用 \s+ 替换为单个空格:"Hello World"
    • DocumentBySentenceSplitter‌
      • 按句子分割
      • 该分割器使用Apache OpenNLP 库中的一个类,用于检测文本中的句子边界。它能够识别标点符号(如句号、问号、感叹号等)是否标记着句子的末尾,从而将一个较长的文本字符串分割成多个句子。
    • 不管是哪个,依旧会优先单块字数,所以要设置长一点(但也不能太大,会导致检索精度下降;而且大模型有最大token数限制)

演示 DocumentByCharacterSplitter‌

  • java代码,还是在之前的基础上

    java 复制代码
    import dev.langchain4j.data.document.Document;
    import dev.langchain4j.data.document.loader.ClassPathDocumentLoader;
    import dev.langchain4j.data.document.parser.TextDocumentParser;
    import dev.langchain4j.data.document.splitter.DocumentByCharacterSplitter;
    import dev.langchain4j.data.segment.TextSegment;
    
    import java.util.List;
    
    public class Demo03 {
    
        public static void main(String[] args) {
            //文档读取
            Document document = ClassPathDocumentLoader.
                    loadDocument("demo03/cs.txt", new TextDocumentParser());
    //        String text = document.text();
    //        System.out.println(text);
    
            DocumentByCharacterSplitter splitter = new DocumentByCharacterSplitter(
                    20,         // 每段最长字数
                    10          // 自然语言最大重叠字数
            );
            List<TextSegment> segments = splitter.split(document);
            System.out.println(segments);
        }
    
    }
  • 可以看到被分成了4份

  • 查看这个构造方法

  • 可以看到不怎么好用,如果选用按分割符分割呢?

分割符分割(正则)

  • 在使用按字符切分时,需要指定分割符,另外需要指定块的大小以及块之间重叠的大小(允许重叠是为了尽可能地避免按照字符进行分割造成的语义损失)。

  • chunk_size(块大小)指的就是我们分割的字符块的大小;

  • chunk_overlap(块间重叠大小)就是下图中加深的部分,上一个字符块和下一个字符块重叠的部分,即上一个字符块的末尾是下一个字符块的开始。

  • 比如:

    复制代码
    - 最晚在航班起飞前 48 小时取消。取消费用:经济舱 75 美元,豪华经济舱 50 美元,
    商务舱 25 美元。退款将在 7 个工作日内处理。
    	
    按照chunksize可能会分隔成:
    	-最晚在航班起飞前 48 小时取消。取消费用:经济舱 7
    	5 美元,豪华经济舱......
    		
    如果设置了重叠可能会:
    	-最晚在航班起飞前 48 小时取消。取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
    	-取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。退款将在 7 个工作日内处理。
  • 整个流程如下

    • 首先按照指定的分割符进行切分,切分过之后,如果块本身的长度小于 chunk_size 的大小,则触发合并逻辑。在进行合并时,遵循下面的规则:
      • 如果相邻块加在一起的长度小于或等于chunk_size,则进行合并;
      • 在进行合并时,如果重叠部分长度 小于或等于chunk_overlap,并且和前后两个相邻块合并后,两个合并后的块均不超过chunk_size,则两个合并后的块允许有重叠
  • 如果分割后每个块均大于 chunk_size,则必须要有子分割器,如果没有则报错

  • 代码示例

    • txt文件:demo04/cs.txt

      复制代码
      1.- 最晚在航班起飞前 48 小时取消。取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。退款将在 7 个工作日内处理。
      2.- 在航班起飞前 72小时(包括)~48小时(不包括)之间取消。取消费用:经济舱 30 美元,豪华经济舱 20 美元,商务舱 10 美元。退款将在 7 个工作日内处理。
      3.- 在航班起飞前 72小时(不包括)之间取消。取消费用:经济舱 50 美元,豪华经济舱 5 美元,商务舱 5 美元。退款将在 7 个工作日内处理。
    • java代码

      java 复制代码
      import dev.langchain4j.data.document.Document;
      import dev.langchain4j.data.document.loader.ClassPathDocumentLoader;
      import dev.langchain4j.data.document.parser.TextDocumentParser;
      import dev.langchain4j.data.document.splitter.DocumentByRegexSplitter;
      import dev.langchain4j.data.segment.TextSegment;
      
      import java.util.List;
      
      public class Demo04 {
      
          public static void main(String[] args) {
              //文档读取
              Document document = ClassPathDocumentLoader.
                      loadDocument("demo04/cs.txt", new TextDocumentParser());
      
              DocumentByRegexSplitter splitter = new DocumentByRegexSplitter(
                      "\\n\\d\\.", //匹配 换行+"1. 这样的标题"格式
                      "\\n", //保留换行符作为段落连接符
                      200,         // 每段最长字数
                      10                              // 自然语言最大重叠字数
              );
              List<TextSegment> segments = splitter.split(document);
          }
      
      }
    • 设置为200的话,可以看到最终还是被分割成了两块

    • 设置为100的话,就正确分割成了三块

分隔经验

  • 过细分块的潜在问题
    • 语义割裂‌: 破坏上下文连贯性,影响模型理解‌ 。
    • 计算成本增加‌:分块过细会导致向量嵌入和检索次数增多,增加时间和算力开销‌。
    • 信息冗余与干扰‌:碎片化的文本块可能引入无关内容,干扰检索结果的质量,降低生成答案的准确性‌。
  • 分块过大的弊端
    • 信息丢失风险‌:过大的文本块可能超出嵌入模型的输入限制,导致关键信息未被有效编码‌。
    • 检索精度下降‌:大块内容可能包含多主题混合,与用户查询的相关性降低,影响模型反馈效果‌。
  • 按场景经验总结
    • 微博/短文本
      • 句子级分块,保留完整语义
      • 每块100-200字符‌
    • 学术论文
      • 段落级分块,叠加10%重叠
      • 每块300-500字符‌
    • 法律合同
      • 条款级分块,严格按条款分隔
      • 每块200-400字符‌
    • 长篇小说
      • 章节级分块,过长段落递归拆分为段落
      • 每块500-1000字符‌
  • 总体常用设置
    • 固定长度分块‌
      • ‌字符数范围‌:通常建议每块控制在 ‌100-500字符‌(约20-100词),以平衡上下文完整性与检索效率‌。
      • ‌重叠比例‌:相邻块间保留 ‌10-20%的重叠内容‌(如块长500字符时重叠50-100字符),减少语义断层‌。
    • ‌语义分块‌
      • 段落或章节‌:优先按自然段落、章节标题划分,保持逻辑单元完整‌。
      • 动态调整‌:对于长段落,可递归分割为更小单元(如先按段落分块,过长时再按句子拆分)‌。
    • 专业领域调整‌
      • 高信息密度文本‌(如科研论文、法律文件):采用更细粒度分块(100-200字符),保留专业术语细节‌。
      • 通用文本‌(如新闻、社交媒体):适当放宽分块大小(300-500字符)

文本向量化

  • 通过向量模型库进行向量化
  • 在上文向量数据库的案例中已经演示过了
java 复制代码
TextSegment segment1 = TextSegment.from("""
                需要向量化的片段内容
                """);
//利用
向量模型进行向量化
QwenEmbeddingModel embeddingModel= QwenEmbeddingModel.builder()
                .apiKey("sk-xxxxxxxxxxxxx")
                .build();
Embedding embedding1 = embeddingModel.embed(segment1).content();

//存储向量到向量数据库
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
embeddingStore.add(embedding1, segment1);

用户的问题转化为向量、用向量从数据库中检索数据

  • 在上文向量数据库的案例中已经演示过了

    java 复制代码
    // 需要查询的内容 向量化
    Embedding queryEmbedding = embeddingModel.embed("退票要多少钱").content();
    // 构建查询条件
    EmbeddingSearchRequest build = EmbeddingSearchRequest.builder()
                    .queryEmbedding(queryEmbedding)
                    .maxResults(1)//取最符合的几条数据,这里是1条
                    .minScore(0.6)//只有符合0.6的才会选用,也可以不设置
                    .build();
    // 查询
    EmbeddingSearchResult<TextSegment> segmentEmbeddingSearchResult = embeddingStore.search(build);
  • 或者

    java 复制代码
    // 需要查询的内容 向量化
    Response<Embedding> embed = embeddingModel.embed("退费费用");
    // 构建查询条件
    EmbeddingSearchRequest build = EmbeddingSearchRequest.builder()
                    .queryEmbedding(embed.content())
                    .maxResults(1)//取最符合的几条数据,这里是1条
                    .minScore(0.6)//只有符合0.6的才会选用,也可以不设置
                    .build();
    // 查询
    EmbeddingSearchResult<TextSegment> segmentEmbeddingSearchResult = embeddingStore.search(build);

将匹配到的向量和用户提问一起发给大模型

  • 就是检索增强

  • 在 第三章-数据来源 中有体现

  • 利用框架中的AiServices即可实现

    java 复制代码
    ......
    
    interface Assistant {
        String chat(String message);
    }
    
    ChatLanguageModel model = QwenChatModel
            .builder()
            .apiKey("sk-xxxxxxx")
            .modelName("qwen-max")
            .build();
    ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.build
            .embeddingStore(embeddingStore) //向量数据库
            .embeddingModel(embeddingModel) //向量模型
            .maxResults(5) // 最相似的5个结果
            .minScore(0.6) // 只找相似度在0.6以上的内容
            .build();
    // 为Assistant动态代理对象  chat  --->  对话内容存储ChatMemory----> 聊天记录ChatMemory取出来
    Assistant assistant = AiServices.builder(Assistant.class)
            .chatLanguageModel(model)
            .contentRetriever(contentRetriever) //内容检索器
            .build();
    System.out.println("用户开始询问退费费用");
    System.out.println(assistant.chat("退费费用"));
  • 控制台

  • AiService向量检索原理

整合Springboot

  • 新建一个项目

  • pom.xml

    xml 复制代码
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.qi</groupId>
    <artifactId>LangChain4j_demo04_2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Archetype - LangChain4j_demo04_2</name>
      <url>http://maven.apache.org</url>
    
        <dependencyManagement>
            <dependencies>
                <!-- 引入 Spring Boot BOM -->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>3.4.3</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <!-- SpringBoot 核心包 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <!-- SpringBoot Web容器 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!-- 阿里云百炼 -->
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
                <version>1.0.0-beta3</version>
            </dependency>
    
            <!-- webflux -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>
    
            <!-- langchain4j核心 -->
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j</artifactId>
                <version>1.0.0-beta3</version>
            </dependency>
        </dependencies>
    
    </project>
  • application.yml

    yml 复制代码
    langchain4j:
      community:
        dashscope:
          chatModel:
            api-key: "sk-xxxxxx"
            model-name: qwen-plus
          streaming-chat-model:
            api-key: "sk-xxxxxx"
            model-name: qwen-plus
          embedding-model:
            api-key: "sk-xxxxxx"
    #        model-name: 
  • Application

    java 复制代码
    package com.qi.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
            System.out.println("----------------项目启动成功----------------");
        }
    
    }
  • resources/demo/cs.txt

    复制代码
    1.- 最晚在航班起飞前 48 小时取消。取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。退款将在 7 个工作日内处理。
    2.- 在航班起飞前 72小时(包括)~48小时(不包括)之间取消。取消费用:经济舱 30 美元,豪华经济舱 20 美元,商务舱 10 美元。退款将在 7 个工作日内处理。
    3.- 在航班起飞前 72小时(不包括)之间取消。取消费用:经济舱 50 美元,豪华经济舱 5 美元,商务舱 5 美元。退款将在 7 个工作日内处理。
  • AssistantConfig

    java 复制代码
    package com.qi.demo.config;
    
    import com.qi.demo.Application;
    import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
    import dev.langchain4j.data.document.Document;
    import dev.langchain4j.data.document.DocumentParser;
    import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
    import dev.langchain4j.data.document.parser.TextDocumentParser;
    import dev.langchain4j.data.document.splitter.DocumentByLineSplitter;
    import dev.langchain4j.data.embedding.Embedding;
    import dev.langchain4j.data.segment.TextSegment;
    import dev.langchain4j.memory.ChatMemory;
    import dev.langchain4j.memory.chat.MessageWindowChatMemory;
    import dev.langchain4j.model.chat.ChatLanguageModel;
    import dev.langchain4j.model.chat.StreamingChatLanguageModel;
    import dev.langchain4j.rag.content.retriever.ContentRetriever;
    import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
    import dev.langchain4j.service.AiServices;
    import dev.langchain4j.service.TokenStream;
    import dev.langchain4j.store.embedding.EmbeddingStore;
    import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.net.URISyntaxException;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.List;
    
    @Configuration
    public class AssistantConfig {
    
        public interface Assistant {
            String chat(String message);
            // 流式响应
            TokenStream stream(String message);
        }
    
        //向量数据库
        @Bean
        public InMemoryEmbeddingStore<TextSegment> embeddingStore (){
            return new InMemoryEmbeddingStore<>();
        }
    
        @Bean
        public Assistant assistant(ChatLanguageModel qwenChatModel,
                                   StreamingChatLanguageModel qwenStreamingChatModel,
    //                               ToolsService toolsService,
                                   EmbeddingStore embeddingStore,
                                   QwenEmbeddingModel qwenEmbeddingModel) {
            ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
    
    
            ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
                    .embeddingStore(embeddingStore)
                    .embeddingModel(qwenEmbeddingModel)
                    .maxResults(5) // 最相似的5个结果
                    .minScore(0.6) // 只找相似度在0.6以上的内容
                    .build();
    
            Assistant assistant = AiServices.builder(Assistant.class)
                    .chatLanguageModel(qwenChatModel)
                    .streamingChatLanguageModel(qwenStreamingChatModel)
    //                .tools(toolsService)
                    .contentRetriever(contentRetriever)
                    .chatMemory(chatMemory)
                    .build();
    
            return  assistant;
        }
    
        //提前存储向量数据到向量数据库
        @Bean
        CommandLineRunner ingestTermOfServiceToVectorStore(QwenEmbeddingModel qwenEmbeddingModel,
                                                           EmbeddingStore embeddingStore) throws URISyntaxException {
            // 读取
            Path documentPath = Paths.get(Application.class.getClassLoader().getResource("demo/cs.txt").toURI());
    
            return args -> {
                DocumentParser documentParser = new TextDocumentParser();
                Document document = FileSystemDocumentLoader.loadDocument(documentPath, documentParser);
    
                DocumentByLineSplitter splitter = new DocumentByLineSplitter(
                        500,
                        200
                );
                List<TextSegment> segments = splitter.split(document);
    
                // 向量化
                List<Embedding> embeddings = qwenEmbeddingModel.embedAll(segments).content();
    
                // 存入
                embeddingStore.addAll(embeddings,segments);
    
            };
        }
    
    }
  • DemoController

    java 复制代码
    package com.qi.demo.controller;
    
    import com.qi.demo.config.AssistantConfig;
    import dev.langchain4j.service.TokenStream;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import reactor.core.publisher.Flux;
    
    @RequestMapping("/demo")
    @RestController
    public class DemoController {
    
        @Autowired
        private AssistantConfig.Assistant assistant;
    
        @RequestMapping(value = "/test",produces ="text/stream;charset=UTF-8")
        public Flux<String> test1(@RequestParam("message") String message) {
            TokenStream stream = assistant.stream(message);
    
            return Flux.create(sink -> {
                stream.onPartialResponse(s -> sink.next(s))
                        .onCompleteResponse(c -> sink.complete())
                        .onError(sink::error)
                        .start();
    
            });
        }
    
    }
  • 调用接口

  • 参考bilibili学习视频

相关推荐
小猪配偶儿_oaken1 天前
SpringBoot实现单号生成功能(Java&若依)
java·spring boot·okhttp
毕设源码-钟学长1 天前
【开题答辩全过程】以 中医健康管理系统为例,包含答辩的问题和答案
java
susu10830189111 天前
docker部署 Java 项目jar
java·docker·jar
Haooog1 天前
LangChain4j 学习
java·学习·大模型·langchain4j
yuanmenghao1 天前
现代汽车中的通信方式 ——以智能驾驶系统为例
人工智能·自动驾驶·汽车·信息与通信
爬山算法1 天前
Hibernate(25)Hibernate的批量操作是什么?
java·后端·hibernate
2501_941875281 天前
从日志语义到可观测性的互联网工程表达升级与多语言实践分享随笔
java·前端·python
高山上有一只小老虎1 天前
小红的字符串
java·算法