LangChain4j 整合SpringBoot开发

由于前面部分我们是采用的单元测试模块进行开发的,并没有引入SpringBoot的web模块,这里我们采用开发中使用的比较多的一种方式通过SpringBoot来进行开发,首先我们需要创建Springboot项目,然后加入依赖:

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
            <version>${langchain4j.verion}</version>
        </dependency>
        
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>${langchain4j.verion}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

版本:

<langchain4j.verion>1.0.0-beta1</langchain4j.verion>

最终样子:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.gec</groupId>
    <artifactId>LangChain4</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>LangChain4</name>
    <description>LangChain4</description>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
        <langchain4j.verion>1.0.0-beta1</langchain4j.verion>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>${langchain4j.verion}</version>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
            <version>${langchain4j.verion}</version>
        </dependency>

        <!-- Springboot整合阿里通义千问       -->
<!--        <dependency>-->
<!--            <groupId>dev.langchain4j</groupId>-->
<!--            <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>-->
<!--            <version>1.0.0-alpha1</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
            <version>${langchain4j.verion}</version>
        </dependency>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.gec.langchain4.LangChain4Application</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

修改application.properties配置文件,设置deepseek的密钥、地址、模型名称:

# 应用服务 WEB 访问端口
server.port=8080

langchain4j.open-ai.chat-model.api-key=你的密钥
langchain4j.open-ai.chat-model.model-name=deepseek-chat
langchain4j.open-ai.chat-model.base-url=https://api.deepseek.com/v1

编写一个简单的控制器,实现对话接口:

package com.gec.langchain4.controller;

import dev.langchain4j.model.chat.ChatLanguageModel;
import opennlp.tools.ml.maxent.io.QNModelWriter;
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;

@RestController
@RequestMapping("/ai")
public class AiController {

    @Autowired
    ChatLanguageModel chatLanguageModel;

    @RequestMapping("/chat")
    public String test(@RequestParam(defaultValue = "你是谁") String name){
        return chatLanguageModel.chat(name);
    }
}

使用deepseek速度会有点慢的,运行结果:

大模型理解上下文(多轮对话)

好了继续我们上面的,从图中可以看出

⼤模型并不能够理解” 再说⼀次 “ 的含义。这是因为,在⽬前的代码中,每次调⽤就是⼀次新的会话。

所以对⼤模型来说,没有第⼀次的“ 你是谁 ” 的记录,⼤模型⾃然就⽆法理解 “ 再说⼀次 ” 的含义。
如何让⼤模型能够了解之前的聊天记录呢?

代码如下:

@Test
public void test2(){
		//这里采用langChain的官方接口
    ChatLanguageModel model = OpenAiChatModel.builder().apiKey("demo")
            .modelName("gpt-4o-mini").build();
    UserMessage userMessage1 = UserMessage.userMessage("你好,我是谢韦宝?");
    ChatResponse response1 = model.chat(userMessage1);
    //大模型的第一次响应
    AiMessage aiMessage1 = response1.aiMessage();

    System.out.println(aiMessage1.text());
    System.out.println("-------");

    ChatResponse response2 = model.chat(userMessage1, aiMessage1, UserMessage.userMessage("我叫什么"));

    System.out.println(response2.aiMessage().text());
}

但是如果要我们每次把之前的记录自己去维护, 未免太麻烦, 所以提供了ChatMemory。

实现方式,通过加入一个配置类来实现:

package com.gec.langchain4.config;

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.service.AiServices;
import dev.langchain4j.service.TokenStream;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiConfig {

    // 定义AI助手的公共接口
    public interface Assistant{
        // 普通聊天方法(阻塞式,返回完整响应)
        String chat(String message);

        // 流式响应方法(实时返回生成的token)
        TokenStream stream(String message);
    }

    // 定义Spring Bean来创建助手实例
    @Bean  // 标记为Spring管理的Bean
    public Assistant getAssistant(
            // 注入标准聊天语言模型(用于普通响应)
            ChatLanguageModel chatLanguageModel,
            // 注入流式聊天语言模型(用于流式响应)
            StreamingChatLanguageModel streamingChatLanguageModel){

        // 创建聊天记忆,保留最近10条消息的对话上下文
        ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

        // 使用AI服务构建助手实现
        Assistant assistant = AiServices.builder(Assistant.class)
                // 设置标准聊天模型(用于chat()方法)
                .chatLanguageModel(chatLanguageModel)
                // 设置流式聊天模型(用于stream()方法)
                .streamingChatLanguageModel(streamingChatLanguageModel)
                // 配置聊天记忆保持对话上下文
                .chatMemory(chatMemory)
                // 完成构建
                .build();

        // 返回配置好的助手实例
        return assistant;
    }
}

原理:

  1. 通过AiService创建的代理对象(AiServices.builder(Assistant.class))调用chat方法
  2. 代理对象会去ChatMemory中获取之前的对话记录(获取记忆)
  3. 将获取到的对话记录合并到当前对话中(此时大模型根据之前的聊天记录肯定就拥有了“记忆”)
  4. 将当前的对话内容存入ChatMemory(保存记忆)

上面的代码还需要配置streamingChatLanguageModel流式聊天对象:

将deepseek的streamingChatLanguageModel配置信息配置到application.properties中

# 配置流式响应的api信息
langchain4j.open-ai.streaming-chat-model.api-key=你的密钥
langchain4j.open-ai.streaming-chat-model.model-name=deepseek-chat
langchain4j.open-ai.streaming-chat-model.base-url=https://api.deepseek.com/v1

编写控制器代码

package com.gec.langchain4.controller;

import com.gec.langchain4.config.AiConfig;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.TokenStream;
import opennlp.tools.ml.maxent.io.QNModelWriter;
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;

import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/ai")
public class AiController {

    //注入langChain的封装好的语言对象
    @Autowired
    ChatLanguageModel chatLanguageModel;

    @Autowired
    AiConfig.Assistant assistant;

    @RequestMapping("/chat")
    public String test(@RequestParam(defaultValue = "你是谁") String name){
        return chatLanguageModel.chat(name);
    }

    // 定义HTTP接口端点,处理带有记忆功能的流式聊天请求
    @RequestMapping(value = "/memory_chat",produces = "text/stream;charset=utf-8")
// 返回Flux<String>类型实现流式响应,参数message默认值为"我是谁"
    public String memoryStreamChat(
            // 从请求参数获取用户消息,默认值"我是谁"
            @RequestParam(defaultValue = "我是谁") String message,
            // 注入HttpServletResponse对象(虽然响应式编程中通常不直接使用)
            HttpServletResponse response) {
            return assistant.chat(message);
    }
}

可以观察到响应回来的字符串是直接一次性显示出来的:

再次提问看看它是否能够记住我们的姓名:

对比一下官方的DeepSeek聊天工具,我们可以看出它的聊天信息是一个一个文字显示出来的,那么这种功能是如何实现的呢?

流式输出

接下来如果我们需要在自己的控制器中实现聊天信息是一个一个文字显示出来的使用的话,可以引入流式输出的依赖webflux:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

引入 WebFlux 的核心目的是为了:

  1. 高效处理流式 AI 响应(如逐词生成)。
  2. 提升高并发下的系统吞吐量
  3. 与响应式 AI 组件(如 StreamingChatLanguageModel)无缝集成

编写控制器代码:

package com.gec.langchain4.controller;

import com.gec.langchain4.config.AiConfig;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.TokenStream;
import opennlp.tools.ml.maxent.io.QNModelWriter;
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;

import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/ai")
public class AiController {

    //注入langChain的封装好的语言对象
    @Autowired
    ChatLanguageModel chatLanguageModel;

    @Autowired
    AiConfig.Assistant assistant;

    @RequestMapping("/chat")
    public String test(@RequestParam(defaultValue = "你是谁") String name){
        return chatLanguageModel.chat(name);
    }

    // 定义HTTP接口端点,处理带有记忆功能的流式聊天请求
    @RequestMapping(value = "/memory_steam_chat",produces = "text/stream;charset=utf-8")
// 返回Flux<String>类型实现流式响应,参数message默认值为"我是谁"
    public Flux<String> memoryStreamChat(
            // 从请求参数获取用户消息,默认值"我是谁"
            @RequestParam(defaultValue = "我是谁") String message,
            // 注入HttpServletResponse对象(虽然响应式编程中通常不直接使用)
            HttpServletResponse response) {

        // 调用助手服务的流式接口,获取TokenStream对象
        TokenStream stream = assistant.stream(message);

        // 创建Flux流式响应
        return Flux.create(sink -> {
            // 设置部分响应回调:每次收到部分响应时通过sink发送数据
            stream.onPartialResponse(s -> sink.next(s))
                    // 设置完成回调:当收到完成信号时关闭流
                    .onCompleteResponse(c-> sink.complete())
                    // 设置错误回调:发生错误时传递错误信号 它的作用是将 sink 对象的 error 方法作为函数式接口的实现传递进去。
                    .onError(sink::error)//stream.onError(error -> sink.error(error));
                    // 启动流处理
                    .start();
        });
    }
}

输出结果:

再说一次我是谁:

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐