Langchain4J

结合之前学习的SpringAI,个人觉得Langchain4j是对SpringAI实现的功能进行进一步的封装,方便大模型应用开发

提示:本次实践基于Spring Boot 3.x + Langchain4j 1.0.1-beta6 + Qwen(通义千问)模型

快速入门

  1. 引入依赖

     <!--langchain4j依赖-->
    <dependency>         
              <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
                <version>1.0.1-beta6</version>
            </dependency>
    

    在SpringAI和Langchain4j依赖中二选一,不能多选,否则会出现报错

  2. 构建OpenAIChatModel对象

    在配置类中配置

    OpenAiChatModel model = OpenAiChatModel.builder()
                    .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                    .apiKey(System.getenv("OPENAI_API_KEY"))
                    .modelName("qwen-plus")
                    .logRequests(true)
                    .logResponses(true)
                    .build();
    

    开启了logback日志,包括request和response,后续可以在yml文件中进行配置而不用在构建模式进行操作

  3. 调用Chat方法与大模型交互

    model.chat(message)
    

Spring整合Langchain4j

  1. 构建springboot项目

    这里不再展示

  2. 引入起步依赖

    <dependency>
                <!--AIService相关依赖-->
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-spring-boot-starter</artifactId>
                <version>1.0.1-beta6</version>
            </dependency>
    
  3. yml配置

    langchain4j:
      open-ai:
        chat-model:
          base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
          api-key: ${OPENAI_API_KEY}
          model-name: qwen-turbo
          #开启log日志
          log-requests: true
          log-responses: true
    

    使用不同的模型配置不同

  4. 开发接口

会话功能-AIService工具类

Langchain4j最核心特性就是通过接口代理简化开发,其中AIService是核心交互

会话功能包含:会话记忆、Rag知识库、Tools工具

架构图解

通过代理对象自动处理 Prompt 组装

大模型 (Qwen) Prompt Template AIService (Proxy) 前端/Controller 大模型 (Qwen) Prompt Template AIService (Proxy) 前端/Controller chat("你好") 组装 @UserMessage 最终 Prompt 发送请求 返回回答 返回结果

代码实现

  1. 引入langchain4j-reactor依赖

    <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-reactor</artifactId>
                <version>1.0.1-beta6</version>
            </dependency>
    
  2. 声明接口

    通过创建ConsultantService文件对软件功能进行定义。

    在Langchain4j中给出了注解@AiService(),方便对AIService进行自定义配置

    @AiService(
            wiringMode = AiServiceWiringMode.EXPLICIT,
            chatModel = "openAiChatModel"
    
    )
    public interface ConsultantService {
    
    
        //在创建代理对象时,可选自动或者手动装配,这里EXPLICIT是手动
    
        public String chat2(String message);
    }
    
  3. AiService为接口创建代理对象

    在配置类中给Spring容器扫描到ConsultantService并进行注册

        @Bean
        public ConsultantService consultantService(){
            return AiServices.builder(ConsultantService.class)
                    .chatModel(model)
                   .build();
        }
    
  4. Controller中注入并使用

    在控制层中字段注入ConsultantService,并在方法里调用chat2方法

     @Autowired
        private ConsultantService consultantService;
         @RequestMapping("/chat2")
        public String chat2(String question){
            return consultantService.chat2(question);
        }
    

流式调用

原理流程

使用 Spring WebFlux 的 Flux 来处理数据流

Async

Flux

SSE / Chunk

用户请求

Controller

AIService

WebFlux Response

前端逐字显示

大模型流式响应

代码实现

  1. 引入 spring-boot-starter-webflux 依赖
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
  1. 配置流式模型对象

    在yml文件中进行配置

        streaming-chat-model:
          base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
          api-key: ${OPENAI_API_KEY}
          model-name: qwen-turbo
          #开启log日志
          log-requests: true
          log-responses: true
    
  2. 切换接口中方法返回值

    @AiService
    public interface ConsultantService {
        // 返回 Flux<String> 实现流式输出
        Flux<String> chatStream(@UserMessage String message);
    }
    
  3. 修改控制层代码

    @RequestMapping(value = "/chat",produces = "text/html;charset=UTF-8")
        public Flux<String> chat(String message, String memoryId){
            return consultantService.chat(message,memoryId);
        }
    
    

会话功能-消息注解

  • @SystemMessage:给大模型的系统提示词

    //1.引入外部文件
    @SystemMessage(fromResource="system.txt")//2.字符串撰写
    @SystemMessage("xxx")
    //3.使用模板变量
    @SystemMessage("你是{{role}}")
    
  • @UserMessage:用户输入的提示词

    //1.模板变量
    @UserMessage("请回答这个问题:{{question}}")
    public String chat(@V("question") String question);
    
    
    //2.多参数组合
    @UserMessage("用户{{name}}的问题是:{{message}}")
    public String chat(@V("name") String name, @V("message") String message);
    
    //3.显示标注
    public String chat(@UserMessage String message);
    
    

    使用多参数组合或者是模板变量时需要在变量参数前添加@V(“”)告诉大模型当前变量是什么

会话记忆

通过SpringAI的学习,我们可以知道大模型是无状态的,这就需要通过ChatMemory引入会话记忆

  1. 定义会话记忆对象

    @Bean
        public ChatMemory chatMemory(){
            return MessageWindowChatMemory.builder()
                    .maxMessages(20)//最多存储20条信息
                    .build();
        }
    
  2. 配置会话记忆对象

     @Autowired
        private ChatMemoryStore chatMemoryStore;
    
    @Bean
        public ChatMemoryProvider chatMemoryProvider(){
            ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
                @Override
                public ChatMemory get(Object memoryId) {
                    return MessageWindowChatMemory.builder()
                            .maxMessages(20)
                            .id(memoryId)
                            .chatMemoryStore(chatMemoryStore)//存储对象
                            .build();
    
                }
            };
            return chatMemoryProvider;
    
        }
    

会话记忆隔离

那么搭建好初步的会话记忆以后,会出现一个问题:新建一个会话或者重启服务,都会记得其他会话的内容,出现会话记忆混合的问题。这就需要通过userID或者其他标志进行隔离区分

记忆隔离架构

MemoryId: 1001

MemoryId: 1002

Get

Get

用户 A

AIService Proxy

用户 B

ChatMemoryProvider

ChatMemory (Window: 1001)

ChatMemory (Window: 1002)

ChatMemoryStore

代码实现

  1. 定义会话记忆对象提供者

    @Configuration
    public class MemoryConfig {
    
        @Bean
        public ChatMemoryProvider chatMemoryProvider(ChatMemoryStore store) {
            return memoryId -> MessageWindowChatMemory.builder()
                    .id(memoryId)
                    .maxMessages(20) // 保留最近20条
                    .chatMemoryStore(store)
                    .build();
        }
        
        // 简单的内存存储,生产环境建议替换为 RedisChatMemoryStore
        @Bean
        public ChatMemoryStore chatMemoryStore() {
            return new InMemoryChatMemoryStore();
        }
    }
    
  2. 配置会话记忆对象提供者

    service类的@AiService中添加 chatMemory = "chatMemory",chatMemoryProvider = "chatMemoryProvider"

  3. ConsultantService接口方法中添加参数memoryId

  4. Controller中chat接口接收memoryId

    @AiService
    public interface ConsultantService {
        // 必须包含 @MemoryId 参数才能启用隔离记忆
        String chat(@UserMessage String message, @MemoryId String userId);
    }
    
  5. 前端请求转发memoryId

    通过js进行异步请求memoryId

会话记忆持久化

上阶段是采用内存存储用户会话的方式实现会话记忆。但是如果电脑关机或者服务重启,那么数据会被清空,所以本节通过引入Redis进行缓存用户对话数据

redis存储逻辑

Key: langchain4j:memory:userId

应用层

RedisChatMemoryStore

Redis Hash

Value: JSON Messages

  1. 准备redis环境

    可在Docker或者虚拟机中进行启动部署

  2. 引入redis起步依赖

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
  3. 配置redis连接信息

    langchain4j:
    	openai:
    		community:
    			redis:
        			host: your-host-name
        			port: 6379
    
  4. 提供ChatMemoryStore实现类

    RedisChatMemory中实现了:获取信息、添加信息和删除信息三种方法。其中Redis一般存储JSON格式的用户对话数据,而这需要对信息进行序列化和反序列化

    @Repository
    public class RedisChatMemory implements ChatMemoryStore {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
        @Override
        public List<ChatMessage> getMessages(Object memoryId) {
            //获取会话消息
            String json = redisTemplate.opsForValue().get(memoryId.toString());
            if (json == null || json.isEmpty()) {
                return List.of();
            }
            //反序列化
            List<ChatMessage> chatMessages = ChatMessageDeserializer.messagesFromJson(json);
            return chatMessages;
        }
    
        @Override
        public void updateMessages(Object memoryId, List<ChatMessage> list) {
            //更新会话消息
            //通过序列化将list转为JSON
            String json = ChatMessageSerializer.messagesToJson(list);
            //存储到redis
            redisTemplate.opsForValue().set(memoryId.toString(),json, Duration.ofDays(1));
        }
    
        @Override
        public void deleteMessages(Object memoryId) {
            redisTemplate.delete(memoryId.toString());
        }
    }
    
  5. 配置ChatMeomoryStore

@Autowired
   private ChatMemoryStore chatMemoryStore;

@Bean
    public ChatMemoryProvider chatMemoryProvider(){
        ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
            @Override
            public ChatMemory get(Object memoryId) {
                return MessageWindowChatMemory.builder()
                        .maxMessages(20)
                        .id(memoryId)
                        .chatMemoryStore(chatMemoryStore)//存储对象
                        .build();

            }
        };
        return chatMemoryProvider;

    }

Rag知识库

单单通过机器训练跟不上时代发展,外挂知识库就显得额外重要,他能够解决知识滞后、胡说八道的问题

原理

通过检索外部知识库的方式增强大模型的生成能力、

向量数据库:Milvus、Chroma、Pinecone、RedisSerach、pgvector

RAG处理流程

大模型 检索器 向量数据库 向量模型 文本分割器 文档 (PDF/TXT) 大模型 检索器 向量数据库 向量模型 文本分割器 文档 (PDF/TXT) 问答阶段 加载并切割 文本片段 存入向量 (Embedding) 查询相似向量 返回相关片段 注入上下文 (Context) 生成回答

Langchain4j相对于SpringAI提供了简洁的API

入门

  1. 存储

    • 引入依赖

      <!-- 基础RAG向量模型-->
      <dependency>
                  <groupId>dev.langchain4j</groupId>
                  <artifactId>langchain4j-easy-rag</artifactId>
                  <version>1.0.1-beta6</version>
              </dependency>
      <!--langchain4j对redis向量数据库的支持-->
              <dependency>
                  <groupId>dev.langchain4j</groupId>
                  <artifactId>langchain4j-community-redis-spring-boot-starter</artifactId>
                  <version>1.0.1-beta6</version>
              </dependency>
      
    • 加载数据库

    • 构建向量数据库操作对象

      @Autowired
          private EmbeddingModel embeddingModel ;
       //构建向量数据库
         // @Bean
          public EmbeddingStore store(){//easy-rag中本身就有注册了一个embeddingStore的bean
      //        List<Document> content = ClassPathDocumentLoader.loadDocuments("content");
              List<Document> content = ClassPathDocumentLoader.loadDocuments("content",new ApachePdfBoxDocumentParser());
              //构建向量数据库操作的是内存版本的向量数据库
              //InMemoryEmbeddingStore store = new InMemoryEmbeddingStore();
              DocumentSplitter ds = DocumentSplitters.recursive(500,100);
              //构建EmbeddingStoreIngestor对象
              EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
                      .embeddingStore(redisEmbeddingStore)
                      .documentSplitter(ds)
                      .embeddingModel(embeddingModel)//嵌入模型
                      .build();
      
              //存储数据
              ingestor.ingest(content);
      
              return redisEmbeddingStore;
          }
      
    • 文档切割、向量化存储到向量数据库

  2. 检索

    • 构建向量数据库检索对象

      @Autowired
          private RedisEmbeddingStore redisEmbeddingStore;
      
      
      @Bean //放到IOC容器
      public ContentRetriever contentRetriever(){
          EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
                  .embeddingStore(redisEmbeddingStore)
                  .maxResults(3)
                  .embeddingModel(embeddingModel)
                  .minScore(0.6)
                  .build();
          return retriever;
      }
      
    • 配置向量数据检索对象

      service类中添加contentRetriever = "contentRetriever",配置向量数据库检索对象

EmbeddingStore

这里使用的是redisEmbeddingStore

操作向量数据库

  1. 准备向量数据库
  2. 引入依赖
  3. 配置向量数据库信息
  4. 注入RedisEmbeddingStore使用

Tools工具-准备环境

调用流程

思考: 需要调用工具

执行 Java 方法

返回:28度

观察结果

最终回答

用户: 查询深圳5月天气

AIService

LLM

ToolExecutor

WeatherService

  1. 准备数据库环境
  2. 引入依赖
  3. 配置链接信息
  4. 准备实体类
  5. 开发Mapper
  6. 开发Service
  7. 完成测试

Tools工具-实现

  1. 准备工具方法

    @Component
    public class ReservationTool {
        @Autowired
        private ReservationService reservationService;
    
    
        //添加预约
        @Tool(name = "添加预约信息")
        public void addReservation(
                @P("考生姓名") String name,
                @P("考生性别")String gender,
                @P("考生手机号")String phone,
                @P("考生预约沟通时间,格式为:yyy-MM-dd'T'HH:mm")String communcationTime,
                @P("考生所在省份")String province,
                @P("考生预估分数")String estimatedScore
        ){
            Reservation reservation = new Reservation(null,name,gender,phone, LocalDateTime.parse(communcationTime),province,Integer.parseInt(estimatedScore));
            reservationService.insert(reservation);
        }
    
    
        //查询预约
        @Tool(name = "查询预约信息")
        public Reservation findReservationByPhone(@P("考生手机号")String phone){
            return reservationService.findByPhone(phone);
        }
    }
    
    
  2. 配置工具方法

service类的@AiService中添加tools = "reservationTool"

总结

通过Langchain4j的学习,这极大的简化了AI应用的开发流程:

  1. AiService
  2. Memory
  3. Rag
  4. Tool
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐