当前位置: 首页 > news >正文

【langchain4j系列教程-05】一文读懂:人工智能如何实现会话记忆

文章目录

  • 引言
  • 依赖引入
  • 没有会话记忆示例
  • 手动添加会话记忆示例
  • langchain4j实现自动会话记忆
    • 全局会话记忆
    • 会话记忆隔离
  • 总结

引言

会话记忆是每个大模型都具备的能力,那么他是怎么实现的呢?你是否思考过大模型是如何实现知晓之前的聊天内容的。这篇文章将会由浅入深带你窥探大模型会话记忆的秘密!

依赖引入

本文涉及到的所有依赖统一放在这里

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

    <!-- 引入langchain4j依赖-->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>${langchain4j.version}</version>
    </dependency>

    <!-- 引入OpenAI依赖。由于deepseek跟open ai共用一套标准所以deepseek也用这个依赖-->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
        <version>${langchain4j.version}</version>
    </dependency>

    <!-- SpringBoot整合百炼/千问依赖 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
    </dependency>

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

没有会话记忆示例

首先,请大家看下方代码,代码中与大模型进行了两次聊天,第一次告诉大模型我是谁,第二次问大模型我是谁。

public class DemoErrorTest {
    public static void main(String[] args) {
        OpenAiChatModel chatModel = OpenAiChatModel.builder()
                .apiKey("demo")
                .modelName("gpt-4o-mini")
                .build();

        String result = chatModel.chat("我是Jayden。");
        System.out.println(result);

        String result2 = chatModel.chat("我是谁?");
        System.out.println(result2);
    }
}

输出如下:观察输出可以清晰看到,此时的大模型是没有记忆的

手动添加会话记忆示例

接下来将演示手动添加会话记忆的能力,这个demo其实在官网里也有提到,目的是让大家更了解原理,首先看代码,原理就是在下一次聊天的时候放入上一次聊天的对话与答案

public class DemoSuccessTest {
    public static void main(String[] args) {
        OpenAiChatModel chatModel = OpenAiChatModel.builder()
                .apiKey("demo")
                .modelName("gpt-4o-mini")
                .build();

        UserMessage userMessage1 = UserMessage.userMessage("我是Jayden。");
        ChatResponse result1 = chatModel.chat(userMessage1);
        AiMessage response1 = result1.aiMessage();
        System.out.println(response1);


        UserMessage userMessage2 = UserMessage.userMessage("我是谁?");
        ChatResponse result2 = chatModel.chat(userMessage1,response1,userMessage2);
        AiMessage response2 = result2.aiMessage();
        System.out.println(response2);

    }
}

我们来看执行的结果,这次大模型通过上下文知道了我是谁。那么问题来了,如果一个会话有很多对话,那这个代码是不是有问题?解决问题的方式往下看。

langchain4j实现自动会话记忆

抛开langchain4j不谈,在web2的世界里,数据都是要存储的,无非就是怎么存的问题。在会话这种场景下很容易就想到K-V结构的存储,K是会话的id,V是对应的聊天列表。

接下来来看下langchain4j里实现上述原理是怎么做的

全局会话记忆

首先要定一个对话助手Assistant,然后在SpringBoot中实例化这个bean。

@Configuration
public class ChatMemoryAiConfig {

    /**
     * 对话助手
     */
    public interface Assistant {
        String chat(String prompt);

        TokenStream streamChat(String prompt);
    }

    @Bean
    public Assistant assistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel) {
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(33);

        // 原理是对话助手增加动态代理
        /**
         * 第一次:chat
         * ==》 对话内容存储到内存(ChatMemory)
         * ==》 取出来历史对话内容
         * ==》 放到当前对话内容中
         */
        return AiServices.builder(Assistant.class)
                .chatLanguageModel(chatLanguageModel)
                .streamingChatLanguageModel(streamingChatLanguageModel)
                .chatMemory(chatMemory)
                .build();
    }
}

这里的关键代码是MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(33);这就是langchain4j提供的会话记忆的类,在通过AiServices构造Assistant时,将指定存储上下文的MessageWindowChatMemory设置到对话助手上即可实现会话记忆。参数里的33代表最多存储多少条会话内容。

我们来看下MessageWindowChatMemory源码,看看会话内容到底怎么存的?

public class MessageWindowChatMemory implements ChatMemory {

    private final Object id;
    private final Integer maxMessages;
    private final ChatMemoryStore store;

    // 省略。。。
}

可以看到会话记忆应该是存在ChatMemoryStore里的,我们打开源码查看,可以看到langchain4j是存在了内存里的,用了一个线程安全的Map,这与我们的猜想是一致的。

public class InMemoryChatMemoryStore implements ChatMemoryStore {
    private final Map<Object, List<ChatMessage>> messagesByMemoryId 
    = new ConcurrentHashMap<>();

    // 省略。。。
}

定义好会话助手Bean后就可以通过接口来测试了,测试接口代码

@RestController
@RequestMapping("/chat/memory/ai")
public class ChatMemoryAIController {

    @Autowired
    private ChatMemoryAiConfig.Assistant assistant;

    @RequestMapping("/chat")
    public String chat(@RequestBody String prompt) {
        return assistant.chat(prompt);
    }
}

大家可以通过这个接口进行测试,是带会话记忆的能力的。这里需要补充一点,Assistant是一个接口,那么为什么可以直接调用呢?这里实际是使用了动态代理。

会话记忆隔离

上一部分的会话意义我定位为全局的会话记忆,原因是所有人共用会话记忆。实际使用过DeepSeek等其他ai产品的都知道,会话记忆一般都是通过会话id来进行隔离的,所以才会有会话列表。为了解决这个问题,所以有会话隔离。

首先我们定义一个会话助手,这里需要注意的是@MemberId注解,这个注解名很容易理解,就是记忆id,他可以是任何具有唯一属性的字段,具体看业务。比如它可以是userId,也可以是会话id等等。

首先要定一个对话助手MemoryAssistant,然后在SpringBoot中实例化这个bean。

import com.jayden.ai.memory.store.ChatMemoryStorePersistent;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
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.MemoryId;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.UserMessage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatMemoryAiConfig {

    public interface MemoryAssistant{
        String chat(@MemoryId Integer memoryId, @UserMessage String prompt);

        TokenStream streamChat(@MemoryId Integer memoryId, @UserMessage String prompt);
    }

    /**
     * 带有记忆的对话助手 多轮对话的内容是存在内存中的
     * @param chatLanguageModel
     * @param streamingChatLanguageModel
     * @return
     */
    @Bean
    public MemoryAssistant memoryAssistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel) {

        return AiServices.builder(MemoryAssistant.class)
                .chatLanguageModel(chatLanguageModel)
                .streamingChatLanguageModel(streamingChatLanguageModel)
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(33).id(memoryId).build())
                .build();
    }

}

测试接口

import com.jayden.ai.memory.config.ChatMemoryAiConfig;
import dev.langchain4j.service.TokenStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/chat/memory/ai")
public class ChatMemoryAIController {

    @Autowired
    private ChatMemoryAiConfig.MemoryAssistant memoryAssistant;

    @GetMapping("/memory/chat")
    public String memoryChat(@RequestParam("prompt") String prompt, @RequestParam("chatId") Integer chatId){
        return memoryAssistant.chat(chatId, prompt);
    }

}

可以通过接口进行测试,可以发现会话之间实现了隔离。

总结

本文从基础案例一步步到会话隔离,深入浅出的介绍了会话记忆,但这里的会话记忆是存在内存的,内存的特点是断电即失,所以数据需要做持久化,如果觉得文章有帮助,可以三连走起来,专栏里有整套langchain4j的教程,欢迎关注!

相关文章:

  • 基于EfficientNet的自闭症诊断辅助系统揭秘
  • Maven工具学习使用(十)——生成项目站点
  • Python及C++中的字典
  • 【玩泰山派】5、点灯,驱动led-(2)ubuntu18.04 升级python3.6到python3.7,安装pip3
  • 20250408在荣品的PRO-RK3566开发板使用Rockchip原厂的buildroot系统时拿掉经常出现的list-iodomain.sh警告信息
  • 58-使用wordpress快速创建个人网站
  • Go小技巧易错点100例(二十六)
  • SpringBoot项目:部门管理系统
  • 防爆平板:石油化工厂智慧转型的“中枢神经”
  • BANK OF CHINA(HONG KONG)网点
  • Spring Bean 的生命周期
  • Tiny Cluster(1)——搭建树莓派小型计算集群
  • 【C++初学】C++核心编程技术详解(三):多态与文件操作
  • 重构艺术 | 如何优雅地“提炼函数“
  • 并查集(Java模板及优化点解析)
  • Java IO精讲:从传统IO到NIO的深度探索
  • Selenium之Actions事件
  • 达梦数据库-学习-18-ODBC数据源配置(Linux)
  • CCF CSP 第35次(2024.09)(2_字符串变换_C++)(哈希表+getline)
  • java常见线程安全实现方案
  • 容桂网站制作代理商/网站开发的基本流程
  • 网站内套网站代码/企业网站制作方案
  • 上海企业网站建设哪家好/八爪鱼磁力搜索引擎
  • spring boot做网站/第一接单网app地推和拉新
  • 影视公司需要的许可证/上海专业优化排名工具
  • 新加坡网站建设/最好的网站设计公司