【技术专题】LangChain4j 开发Java Agent智能体 - 会话记忆

大家好,我是锋哥。最近连载更新《LangChain4j 视频教程,开发Java Agent智能体》技术专题。

本课程主要介绍和讲解 LangChain4j 简介,阿里云百炼大模型平台接入,Ollama简介以及安装和使用,HelloWorld 实现,日志配置,集成SpringBoot,Ai Service 使用,对话与提示词工程(Prompt),结构化输出,会话记忆,工具调用(Function Calling),嵌入模型与向量数据库,RAG(检索增强生成),MCP(模型上下文协议),多模态支持。同时也配套视频教程 《2027版本 LangChain4j 视频教程,开发Java Agent智能体》

LangChain4j 提供了强大的会话记忆(Chat Memory)功能,用于解决大模型"无状态"的问题,能让你的 AI 应用在多轮对话中保持上下文连贯性。

我们测试下前面的代码,先问下一个 我是小锋。

继续再问一个 我是谁? 大模型已经不知道我是谁了,说明大模型默认是没有记忆功能的。

🧠 会话记忆的核心机制

  • Memory vs. History : ChatMemory 是 LangChain4j 提供的核心机制,它管理与送入模型的上下文。与记录完整对话的 History 不同,ChatMemory 会利用算法(如驱逐旧消息、摘要、注入信息等)来"记忆"部分信息,以适应模型的上下文限制控制调用成本降低响应延迟

💡 内置的 ChatMemory 实现

LangChain4j 提供了两种开箱即用的 ChatMemory 实现,你也可以通过实现 ChatMemory 接口来扩展。

  • MessageWindowChatMemory:作为滑动窗口保留最近 N 条消息,简单易懂,非常适合快速原型开发和轮次不多的对话场景。
  • TokenWindowChatMemory:同样是滑动窗口,但以 Token 数为限制指标。它能更精细地控制发送给模型的上下文长度(尤其在 Token 敏感场景),但会完全驱逐超过限制的消息。

👤 会话隔离:@MemoryId 注解

默认情况下,ChatMemory 是所有用户/会话共享的,会导致"记忆串线"。@MemoryId 注解提供了优雅的解决方案。在 AI Service 接口方法中标注该注解,LangChain4j 会自动为每个 memoryId 创建并维护一个独立的 ChatMemory 实例。

先看一个简单会话实例

首先AssistantConfig里定义ChatMemoryProvider

scss 复制代码
    @Bean
    public ChatMemoryProvider memoryChatMemoryProvider(){
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)  // 记忆id 会话隔离用
                .maxMessages(20)   // 窗口大小,可按需调整
                .build();
    }

再新建一个Assistant4Service,AIService里要配置 chatMemoryProvider

less 复制代码
package com.java1234.service;
​
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;
​
@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT, // 手动指定接入模型 手动装配
        streamingChatModel = "openAiStreamingChatModel", // 配置模型
        chatMemoryProvider  = "memoryChatMemoryProvider" // 会话记忆
​
)
public interface Assistant4Service {
​
    /**
     *  会话记忆
     * @param memoryId 会话id
     * @param question 提问
     * @return
     */
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String question);
}

最后MyChatController里注入Assistant4Service以及实现chat6

java 复制代码
@Autowired
private Assistant4Service assistant4Service;
typescript 复制代码
    @RequestMapping(value = "/chat6",produces = "text/html;charset=utf-8")
    public Flux<String> chat6(String memoryId,String question) {
        return assistant4Service.chat(memoryId,question);
    }

再测试下:先提问,我是小锋。记得浏览器地址栏带上memoryId

再次提问,我是谁?,大模型已经记住我是小锋了。

本质上是第二次向大模型发送提问请求的时候,已经把历史提问和回答都一并作为提示词,请求大模型了。

再学习一个会话持久化实例

做会话持久化,可以存db或者redis,我们一般企业级开发用的是redis。

我们windows跑redis的话,一般都是跑docker里的,windows平台建议大家去安装一个Docker Desktop,比较方便。

www.docker.com/products/do...

接下来 直接 运行 安装,运行镜像命令:

arduino 复制代码
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 -v d:/redis-data:/data redis/redis-stack:latest

6379端口映射的是redis-stack服务,8001端口映射的是可视化服务。

首先第一步:pom.xml里加下redis依赖

xml 复制代码
<dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-community-redis-spring-boot-starter</artifactId>
            <version>1.15.0-beta25</version>
        </dependency>

接着第二步:application.yml里配置redis连接配置

yaml 复制代码
langchain4j:
  community:
    redis:
      enabled: false   # 关闭 Redis 向量库自动配置,仅保留手动配置的会话记忆
  redis:
    host: localhost
    port: 6379
    password:          # 无密码留空
    ttl-seconds: 3600  # 每个会话 key 过期时间,演示用 1 小时

接着第三步:新建redis会话配置类:RedisChatMemoryConfig

typescript 复制代码
package com.java1234.config;
​
import dev.langchain4j.community.store.memory.chat.redis.RedisChatMemoryStore;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
@Configuration
@ConfigurationProperties(prefix = "langchain4j.redis")
@EnableConfigurationProperties(RedisChatMemoryConfig.class)
public class RedisChatMemoryConfig {
    private String host;
    private int port;
    private String password;
    private long ttlSeconds;
​
    @Bean
    public RedisChatMemoryStore redisChatMemoryStore() {
        var builder = RedisChatMemoryStore.builder()
                .host(host)
                .port(port)
                .ttl(ttlSeconds);
        if (password != null && !password.isBlank()) {
            builder.password(password);
            // Redis 6+ ACL 可再加 .user("default")
        }
        return builder.build();
    }
​
    public String getHost() {
        return host;
    }
​
    public void setHost(String host) {
        this.host = host;
    }
​
    public int getPort() {
        return port;
    }
​
    public void setPort(int port) {
        this.port = port;
    }
​
    public String getPassword() {
        return password;
    }
​
    public void setPassword(String password) {
        this.password = password;
    }
​
    public long getTtlSeconds() {
        return ttlSeconds;
    }
​
    public void setTtlSeconds(long ttlSeconds) {
        this.ttlSeconds = ttlSeconds;
    }
}

最后:修改AssistantConfig,设置下chatMemoryStore

scss 复制代码
@Bean
    public ChatMemoryProvider memoryChatMemoryProvider(RedisChatMemoryStore redisChatMemoryStore){
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)  // 记忆id 会话隔离用
                .chatMemoryStore(redisChatMemoryStore)  // redis会话存储
                .maxMessages(20)   // 窗口大小,可按需调整
                .build();
    }

我们继续测试,先跟大模型说,我是小锋;

redis客户端里,我们看下,已经存储了会话信息。

我们再次向大模型提问,我是谁?

redis客户端里我们刷新看下,也进行了更新:

相关推荐
计算机安禾2 小时前
【算法分析与设计】第43篇:空间复杂度类与Savitch定理
java·服务器·网络·数据库·算法
JAVA社区2 小时前
Java高级全套教程(十四)—— SpringData超详细实战详解
java·开发语言·spring cloud·面试·职场和发展
青岛前景互联信息技术有限公司2 小时前
AI驱动的消防通信指挥系统:实现风险预警与智能接处警的秒级响应
大数据·人工智能·物联网
Java爱好狂.2 小时前
Java高并发系统架构设计核心技术开源!
java·高并发·并发编程·java面试·java面试题·java程序员·java八股文
美团技术团队2 小时前
报名|ACL'26 美团中稿精选:从能力评测到推理优化,构建生成新范式
人工智能
Legend NO242 小时前
非结构化数据治理全解:从合规痛点、中台架构到 AI 智能化分类落地
大数据·人工智能·架构
闻道参看2 小时前
智能搜索生态驱动的流量卡位实操:中小微入局者的 GEO 优化 服务选型全维度实证分析
大数据·人工智能
武子康2 小时前
Java-16 深入浅出MyBatis 架构设计与源码剖析:从初始化到 SQL 执行全流程
java·后端
8Qi82 小时前
LeetCode 416:分割等和子集 —— (0-1背包)
java·算法·leetcode·动态规划·背包问题·01背包