Spring Boot集成LangChain4J实战指南

0 阅读10分钟

从零开始掌握 LangChain4J 与大模型交互的核心技术。涵盖基础调用、Spring Boot集成、AI Services、流式输出、会话记忆等完整知识链。

环境准备

  • JDK: 17+
  • Spring Boot: 3.2.0
  • LangChain4J: 1.0.1-beta6
  • Redis: 用于会话持久化(可选)

第一步:快速入门 - 普通方式调用大模型

1.1 创建 Maven 项目

创建一个普通的 Maven 项目,在 pom.xml 中引入依赖:

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
    <version>1.0.1</version>
</dependency>

1.2 编写代码调用大模型

public class App {
    public static void main(String[] args) {
        // 构建 OpenAiChatModel 对象
        OpenAiChatModel model = OpenAiChatModel.builder()
            .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
            .apiKey(System.getenv("API-KEY"))  // 从环境变量获取 API-KEY
            .modelName("qwen-plus")
            .logRequests(true)   // 开启请求日志
            .logResponses(true)  // 开启响应日志
            .build();
        
        // 调用 chat 方法与大模型交互
        String result = model.chat("你好");
        System.out.println(result);
    }
}

重点说明

  • API-KEY 建议配置在系统环境变量中,避免硬编码在代码里导致泄露风险
  • logRequests/logResponses 用于调试,查看请求和响应的详细内容
  • baseUrl 使用阿里云百炼平台的兼容模式端点

第二步:Spring Boot集成LangChain4J

2.1 创建 Spring Boot 项目并引入依赖

<!-- LangChain4J Spring Boot Starter -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    <version>1.0.1-beta6</version>
</dependency>

2.2 配置文件 application.yml

langchain4j:
  open-ai:
    chat-model:
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      api-key: ${API-KEY}  # 从环境变量读取
      model-name: qwen-plus
      log-requests: true   # 请求日志
      log-responses: true  # 响应日志

logging:
  level:
    dev.langchain4j: debug  # 设置日志级别

核心原理

  • 起步依赖会自动检测配置信息
  • 自动向 IOC 容器中注入一个 OpenAiChatModel Bean 对象

2.3 开发接口调用大模型

@RestController
public class ChatController {
    
    @Autowired
    private OpenAiChatModel model;
    
    @RequestMapping("/chat")
    public String chat(String message) {
        return model.chat(message);
    }
}

启动应用后访问 http://localhost:8080/chat?message=你好 即可测试。


第三步:AI Services 工具类封装

为什么需要 AI Services?

直接使用 OpenAiChatModelchat 方法在实际开发中并不常用。因为如果要实现会话记忆、RAG知识库、Tools工具等高阶功能,在调用 chat 方法前需要做很多准备工作,比较复杂。

LangChain4J 提供了 AiServices 工具类,封装了 model 对象和其他功能,用起来非常简单。

3.1 引入 AiServices 依赖

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-spring-boot-starter</artifactId>
    <version>1.0.1-beta6</version>
</dependency>

3.2 声明用于封装聊天方法的接口

public interface ConsultantService {
    // 用于聊天的方法,message 为用户输入的内容
    String chat(String message);
}

3.3 使用 AiServices 创建接口的动态代理对象

@Configuration
public class CommonConfig {
    
    @Autowired
    private OpenAiChatModel model;
    
    @Bean
    public ConsultantService consultantService() {
        ConsultantService cs = AiServices.builder(ConsultantService.class)
                .chatModel(model)  // 设置对话时使用的模型对象
                .build();
        return cs;
    }
}

3.4 Controller 中注入并使用

@RestController
public class ChatController {
    
    @Autowired
    private ConsultantService consultantService;
    
    @RequestMapping("/chat")
    public String chat(String message) {
        return consultantService.chat(message);
    }
}

第四步:AI Services 声明式使用

为了进一步简化使用,LangChain4J 提供了声明式方法:在接口上添加 @AiService 注解,LangChain4J 扫描到后会自动创建该接口的代理对象并注入到 IOC 容器中。

4.1 修改接口添加注解

@AiService(
    wiringMode = AiServiceWiringMode.EXPLICIT,  // 手动装配
    chatModel = "openAiChatModel"               // 指定模型 Bean 名称
)
public interface ConsultantService {
    // 用于聊天的方法,message 为用户输入的内容
    String chat(String message);
}

注解说明

  • wiringMode:装配模式
    • AUTOMATIC(默认):自动装配
    • EXPLICIT:手动装配(推荐,更清晰)
  • chatModel:指定对话时使用的模型对象在 IOC 容器中的名字

注意:虽然可以省略这些属性使用自动装配,但手动设置更容易理解原理,建议都采用手动装配的方式。

4.2 简化后的优势

使用声明式后,之前的 CommonConfig 配置类中的 consultantService() Bean 定义就可以省略了,LangChain4J 会自动处理。


第五步:流式调用

5.1 流式调用 vs 阻塞式调用

之前演示的都是阻塞式调用,结果是一次性响应的。接下来学习如何使用 LangChain4J 发起流式调用,实现边生成边输出的效果。

5.2 引入依赖

<!-- WebFlux 用于支持响应式流式输出 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- LangChain4J Reactor 支持 -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-reactor</artifactId>
    <version>1.0.1-beta6</version>
</dependency>

5.3 配置流式模型对象

application.yml 中添加流式模型配置:

langchain4j:
  open-ai:
    chat-model:
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      api-key: ${API-KEY}
      model-name: qwen-plus
      log-requests: true
      log-responses: true
    streaming-chat-model:  # 流式模型配置
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      api-key: ${API-KEY}
      model-name: qwen-plus
      log-requests: true
      log-responses: true

5.4 调整 ConsultantService 接口

@AiService(
    wiringMode = AiServiceWiringMode.EXPLICIT,
    chatModel = "openAiChatModel",
    streamingChatModel = "openAiStreamingChatModel"  // 指定流式模型
)
public interface ConsultantService {
    // 返回值改为 Flux<String> 支持流式输出
    Flux<String> chat(String message);
}

关键点

  • streamingChatModel 属性指定流式聊天模型对象,值为 openAiStreamingChatModel
  • 返回值类型改为 Flux<String>(响应式流类型)

5.5 调整 ChatController

@RestController
public class ChatController {
    
    @Autowired
    private ConsultantService consultantService;
    
    @RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
    public Flux<String> chat(String message) {
        return consultantService.chat(message);
    }
}

说明

  • produces = "text/html;charset=utf-8" 解决中文乱码问题
  • 浏览器会接收到流式数据并逐步显示

第六步:消息注解(SystemMessage & UserMessage)

6.1 场景说明

假设我们开发一个 AI 顾问,它只能回答特定领域的问题,如果用户问其他问题则不予回答。这时就需要通过设定系统消息来实现。

6.2 SystemMessage 注解

@SystemMessage 用于设置系统消息,有两种使用方式:

方式一:直接在注解中书写

@AiService(
    wiringMode = AiServiceWiringMode.EXPLICIT,
    chatModel = "openAiChatModel",
    streamingChatModel = "openAiStreamingChatModel"
)
public interface ConsultantService {
    
    @SystemMessage("你是精通AI编程方向的资深讲师")
    Flux<String> chat(String message);
}

方式二:从外部文件加载(推荐)

@AiService(
    wiringMode = AiServiceWiringMode.EXPLICIT,
    chatModel = "openAiChatModel",
    streamingChatModel = "openAiStreamingChatModel"
)
public interface ConsultantService {
    
    @SystemMessage(fromResource = "system.txt")
    Flux<String> chat(String message);
}

src/main/resources 目录下创建 system.txt 文件,写入系统消息内容:

你是精通AI编程方向的资深讲师。
你的职责是为用户提供专业的咨询服务。
请保持友好、耐心、细致的回答风格。

好处

  • 系统消息与代码分离,便于维护
  • 支持复杂的多行消息
  • 非开发人员也可修改

6.3 UserMessage 注解

@UserMessage 用于在用户消息前后拼接预设内容。

基本用法:

@UserMessage("你是精通AI编程方向的资深讲师。{{it}}")
Flux<String> chat(String message);
  • {{it}} 是固定写法,用于动态获取用户传递的消息
  • 可以在 {{it}} 前后拼接任意内容

自定义参数名:

@UserMessage("你是精通AI编程方向的资深讲师。{{msg}}")
Flux<String> chat(@V("msg") String message);
  • 使用 @V 注解给参数起别名
  • {{msg}} 就会对应到 message 参数

注意:测试完毕后记得注释掉 @UserMessage,保留 @SystemMessage(fromResource = "system.txt") 设置。


第七步:会话记忆(Chat Memory)

7.1 为什么需要会话记忆?

大模型本身不具备记忆能力,每次会话都是独立的。要想让大模型产生记忆的效果,唯一的方法就是把之前聊天的内容和新的内容一起发送给大模型

LangChain4J 能够帮我们自动记录聊天消息并自动发送

7.2 会话记忆原理

用户提问 → Web 后端 → 存储到 ChatMemory 对象 → 获取所有会话记录 → 发送给大模型
          ↑                                                        ↓
          ←←←←←←←←← 大模型响应结果 ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
          ↓
复制到 ChatMemory → 响应用户

每次用户提问时:

  1. Web 后端自动把消息存放到 ChatMemory 存储对象中
  2. 获取存储对象中所有的会话记录,一并发送给大模型
  3. 大模型根据所有历史记录生成答案
  4. Web 后端把响应消息拷贝一份到存储对象,再发送给用户

7.3 ChatMemory 接口

LangChain4J 提供了 ChatMemory 接口:

public interface ChatMemory {
    Object id();                          // 记忆存储对象的唯一标识
    void add(ChatMessage var1);           // 添加一条会话记忆
    List<ChatMessage> messages();         // 获取所有会话记忆
    void clear();                         // 清除所有会话记忆
}

两个实现类:

  • TokenWindowChatMemory:基于 Token 数量限制
  • MessageWindowChatMemory:基于消息条数限制(常用)

7.4 实现会话记忆

步骤 1:定义会话记忆对象

CommonConfig 中构建 MessageWindowChatMemory 对象并注入到 IOC 容器:

@Bean
public ChatMemory chatMemory() {
    return MessageWindowChatMemory.builder()
            .maxMessages(20)  // 最大保存 20 条会话记录
            .build();
}

为什么要限制最大数量?

  1. 大模型的上下文有限(一般 10w tokens 左右)
  2. 会话记录太多会导致费用越高(按 token 收费)
  3. 超过限制后,最早的消息会被淘汰,只保留最新的 20 条

步骤 2:配置会话记忆对象

ConsultantService 接口的 @AiService 注解中配置:

@AiService(
    wiringMode = AiServiceWiringMode.EXPLICIT,
    chatModel = "openAiChatModel",
    streamingChatModel = "openAiStreamingChatModel",
    chatMemory = "chatMemory"  // 配置会话记忆对象(Bean 名称)
)
public interface ConsultantService {
    @SystemMessage(fromResource = "system.txt")
    Flux<String> chat(String message);
}

现在启动测试,就能实现连续对话了!


第八步:会话记忆隔离

8.1 问题分析

前面实现的会话记忆有个问题:所有用户共用同一个会话记忆对象,无法区分不同用户的会话记录。

那怎么办?借助 ChatMemoryid() 方法实现会话记忆隔离

8.2 隔离原理

用户 A (memoryId=1) → 查找 id=1 的 ChatMemory → 不存在 → 创建新的 → 存储会话
用户 B (memoryId=2) → 查找 id=2 的 ChatMemory → 不存在 → 创建新的 → 存储会话
用户 A (memoryId=1) → 查找 id=1 的 ChatMemory → 已存在 → 复用 → 继续存储

LangChain4J 内部维护一个容器,存储所有会话记忆对象。根据用户传递的 memoryId 查找复用的对象,找不到就创建新的。

8.3 实现会话隔离

步骤 1:定义会话记忆对象提供者

ChatMemoryProvider 会在找不到指定 id 的 ChatMemory 时被调用:

@Bean
public ChatMemoryProvider chatMemoryProvider() {
    ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
        @Override
        public ChatMemory get(Object memoryId) {
            return MessageWindowChatMemory.builder()
                    .id(memoryId)        // 设置 id 值
                    .maxMessages(20)     // 最大会话记录数量
                    .build();
        }
    };
    return chatMemoryProvider;
}

步骤 2:配置会话记忆对象提供者

@AiService 注解中指定,并注释掉之前的 chatMemory 配置:

@AiService(
    wiringMode = AiServiceWiringMode.EXPLICIT,
    chatModel = "openAiChatModel",
    streamingChatModel = "openAiStreamingChatModel",
    // chatMemory = "chatMemory",              // 注释掉
    chatMemoryProvider = "chatMemoryProvider"  // 配置提供者
)
public interface ConsultantService {
    @SystemMessage(fromResource = "system.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String message);
}

关键变化

  • chatMemoryProvider 属性指定会话记忆对象提供者
  • chatMemory 配置不再需要

步骤 3:添加 @MemoryId 参数

ConsultantService 接口的 chat 方法中添加 memoryId 参数:

@AiService(...)
public interface ConsultantService {
    @SystemMessage(fromResource = "system.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String message);
}

注解说明

  • @MemoryId:标识该参数用于指定 ChatMemory 对象的 id
  • @UserMessage:明确标识哪个参数是用户消息(多参数时必须)

细节注意

  • 如果 chat 方法只有一个参数,LangChain4J 默认把它当作用户消息
  • 如果有多个参数,必须手动指定哪个是用户消息

步骤 4:Controller 接收 memoryId 参数

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

步骤 5:前端提交 memoryId

如果使用现成的前端页面(如 index.html),通常已经包含了 memoryId 参数的提交逻辑,无需额外修改。

现在启动测试,不同用户(不同 memoryId)就有各自独立的会话记忆了!提示词


第九步:会话记忆持久化(Redis)

9.1 问题分析

实现了会话记忆隔离后,还有一个问题:后端重启后会话记忆就丢失了

为什么会这样?分析一下:

  1. 我们使用的 MessageWindowChatMemory 内部维护了一个 ChatMemoryStore 成员变量
  2. 真正存储会话记录的就是这个 ChatMemoryStore 对象
  3. 默认使用的 SingleSlotChatMemoryStore 实现类是用 ArrayList内存中存储
  4. 服务器重启,内存数据自然丢失
public class MessageWindowChatMemory implements ChatMemory {
    private final String id;
    private final ChatMemoryStore store;  // 真正存储会话的对象
    
    public void add(ChatMessage message) {
        this.store.updateMessages(this.id, messages);
    }
}

// 默认使用的实现类
class SingleSlotChatMemoryStore implements ChatMemoryStore {
    private List<ChatMessage> messages = new ArrayList();  // 内存存储!
    // ...
}

9.2 解决方案

思路很清晰:自己提供一个 ChatMemoryStore 实现类,把数据存储到外部存储器(如 Redis)中

ChatMemoryStore 接口提供了三个方法:

public interface ChatMemoryStore {
    List<ChatMessage> getMessages(Object memoryId);      // 获取会话记录
    void updateMessages(Object memoryId, List<ChatMessage> messages);  // 更新会话记录
    void deleteMessages(Object memoryId);                // 删除会话记录
}

9.3 实现 Redis 持久化

步骤 1:准备 Redis 环境

使用 Docker 快速安装 Redis:

# 安装 Redis 容器
docker run --name redis -d -p 6379:6379 redis

步骤 2:引入 Redis 依赖

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

步骤 3:配置 Redis 连接信息

application.yml 中添加:

spring:
  data:
    redis:
      host: localhost
      port: 6379

步骤 4:实现 ChatMemoryStore 接口

@Repository
public class RedisChatMemoryStore implements ChatMemoryStore {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        // 从 Redis 获取 JSON 字符串
        String json = redisTemplate.opsForValue().get(memoryId.toString());
        // 反序列化为 List<ChatMessage>
        return ChatMessageDeserializer.messagesFromJson(json);
    }
    
    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        // 序列化为 JSON
        String json = ChatMessageSerializer.messagesToJson(messages);
        // 存储到 Redis,设置 1 天过期期
        redisTemplate.opsForValue().set(memoryId.toString(), json, Duration.ofDays(1));
    }
    
    @Override
    public void deleteMessages(Object memoryId) {
        // 从 Redis 删除
        redisTemplate.delete(memoryId.toString());
    }
}

关键技术点

  • 使用 ChatMessageDeserializer.messagesFromJson() 反序列化
  • 使用 ChatMessageSerializer.messagesToJson() 序列化
  • 设置过期时间避免数据永久存储

步骤 5:配置 ChatMemoryStore

CommonConfig 中将自定义的 ChatMemoryStore 配置给 MessageWindowChatMemory

@Configuration
public class CommonConfig {
    
    @Autowired
    private ChatMemoryStore redisChatMemoryStore;
    
    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
            @Override
            public ChatMemory get(Object memoryId) {
                return MessageWindowChatMemory.builder()
                        .id(memoryId)
                        .maxMessages(20)
                        .chatMemoryStore(redisChatMemoryStore)  // 配置自定义 Store
                        .build();
            }
        };
        return chatMemoryProvider;
    }
}

9.4 效果验证

现在重新启动应用,测试会话记忆:

  1. 用户提问几次,产生会话记录
  2. 重启后端服务
  3. 再次访问,发现之前的会话记录依然存在!

达成效果

  • ✅ 会话数据持久化到 Redis
  • ✅ 服务重启后可恢复历史对话
  • ✅ 支持分布式部署共享会话
  • ✅ 自动过期清理(1 天后)

总结与回顾

核心知识点回顾

  1. 快速入门:普通 Maven 项目直接构建 OpenAiChatModel 调用大模型
  2. Spring Boot集成:使用 starter 自动配置,简化开发
  3. AI Services:工具类封装,简化高阶功能使用
  4. 声明式使用@AiService 注解自动创建代理对象
  5. 流式调用:WebFlux + Flux<String> 实现边生成边输出
  6. 消息注解
    • @SystemMessage:设置系统消息(fromResource 加载外部文件)
    • @UserMessage:在用户消息前后拼接预设内容({{it}}{{msg}}
  1. 会话记忆ChatMemory 接口自动记录历史消息
  2. 会话隔离@MemoryId + ChatMemoryProvider 实现多用户隔离
  3. 会话持久化:自定义 ChatMemoryStore 集成 Redis

最佳实践建议

  • API-KEY 安全:配置在环境变量中,不要硬编码
  • 日志控制:开发环境开启日志,生产环境关闭
  • 会话数量限制:设置合理的 maxMessages(如 20)控制成本和上下文长度
  • 持久化必选:生产环境务必使用 Redis 等外部存储
  • 声明式优先:优先使用 @AiService 声明式,代码更简洁
  • 流式体验:面向用户的场景尽量使用流式输出

进阶扩展方向

  • RAG知识库:结合向量数据库实现私有知识检索
  • Function Calling:让 AI 调用外部 API 或执行代码
  • 多模态:支持图片、文件等多模态输入
  • 监控统计:Token 用量、响应时间、成本统计

参考资源