springboot集成langchain4j记忆对话

流式输出

LLM 一次生成一个标记(token),因此许多 LLM 提供商提供了一种方式,可以逐个标记地流式传输响应,而不是等待整个文本生成完毕。 这显著改善了用户体验,因为用户不需要等待未知的时间,几乎可以立即开始阅读响应。

  1. 在springboot中集成流式输出,需要引入依赖
        <!-- 流式输出 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>

在这里插入图片描述

  1. langchain4j的Api中有对应的流式输出Api,只需要在 application.properties 配置文件中加上即可
langchain4j.community.dashscope.streaming-chat-model.api-key=${QWEN_API_KEY}
langchain4j.community.dashscope.streaming-chat-model.model-name=qwq-32b
  1. 在Controller中写一个方法测试
    @RequestMapping(value = "/stream_qwen",produces = "text/stream;charset=UTF8")public Flux<String> stream(@RequestParam(defaultValue="你是谁") String message){Flux<String> flux = Flux.create(fluxSink -> {qwenStreamingChatModel.chat(message, new StreamingChatResponseHandler() {@Overridepublic void onPartialResponse(String s) {log.info(s);fluxSink.next(s);}@Overridepublic void onCompleteResponse(ChatResponse chatResponse) {log.info(chatResponse.toString());fluxSink.complete();}@Overridepublic void onError(Throwable throwable) {log.error(throwable.getMessage());fluxSink.error(throwable);}});});return flux;}

记忆对话

在上面的例子中,每次与大模型的对话都是没有记忆的,也就是说每次对话大模型并不会记住你上一次问了什么。举个例子来讲,第一次与大模型对话告诉它我的名字,第二次问他我叫什么大模型并不会知道。那么,如何让大模型显得有“记忆”呢?
手动实现的例子:

    @Testvoid testMemoryChat(){OpenAiChatModel model = OpenAiChatModel.builder().baseUrl("http://langchain4j.dev/demo/openai/v1").apiKey("demo").modelName("gpt-4o-mini").build();UserMessage userMessage1 = UserMessage.userMessage("你好,我是kizzo");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("你好,我是kizzo"));// 第二次响应AiMessage aiMessage2 = response2.aiMessage();System.out.println(aiMessage2.text());}
  • 以上例子不难看出,手动维护和管理ChatMessage是很麻烦的。 因此,LangChain4j提供了ChatMemory抽象以及多种开箱即用的实现。ChatMemory可以作为独立的低级组件使用, 或者作为高级组件(如AI服务)的一部分。
  • ChatMemory封装了聊天记录,并且可以设置最多存储多少聊天记录,默认是内存级别的存储,底层用到了jdk的动态代理。这里需要新建一个配置类写一个聊天助手对象,通过聊天助手来进行聊天。
package com.kizzo.langchain4j_spingboot_demo.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 {public interface Assistant{String chat(String message);// 流式响应TokenStream stream(String message);}@Beanpublic Assistant assistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel){// 最多存储多少聊天记录ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);// 为Assistant动态代理对象chat ---> 对话内容存储ChatMemoryi ---> 聊天记录ChatMemory取出来 ---->放入到当前对话中Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel).chatMemory(chatMemory).build();return assistant;}}

进入MessageWindowChatMemory类发现里面的store默认用的map存储聊天记录(记忆)
在这里插入图片描述
在这里插入图片描述

引入核心依赖

        <!--核心--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain4j.version}</version></dependency>

在Controller中添加方法,调用memoryChat方法后再调用memoryStreamChat方法,大模型会返回我的名字。这里本质上还是把第一次调用的结果传给了第二次调用

    @AutowiredAiConfig.Assistant assistant;// 记忆普通对话@RequestMapping(value = "/memory_chat")public String memoryChat(@RequestParam(defaultValue="我是kizzo") String message){return assistant.chat(message);}// 记忆流对话@RequestMapping(value = "/memory_chat_stream",produces = "text/stream;charset=UTF8")public Flux<String> memoryStreamChat(@RequestParam(defaultValue="我是谁") String message) {TokenStream stream = assistant.stream(message);return Flux.create(sink ->  {stream.onPartialResponse(s -> sink.next(s)).onCompleteResponse(c -> sink.complete()).onError(sink::error).start();});}

对话隔离

我们平常使用AI产品时,每次新建的对话和上一次都不会有联系。在上面的例子中并没有区分聊天的轮次,ChatMemory也集成了相应的Api,可以通过memoryId来区分。在配置类上新增Bean对象

    @Beanpublic AssistantUnique assistantUnique(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel){AssistantUnique assistant = AiServices.builder(AssistantUnique.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel)// chatMemory变为了chatMemoryProvider,让memoryId与聊天记录绑定并作为Map的key.chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().maxMessages(10).id(memoryId).build() ).build();return assistant;}

然后在Controller中加入自动装配的对象,以及新增调用的方法。

    @AutowiredAiConfig.AssistantUnique assistantUnique;// 记忆隔离对话@RequestMapping(value = "/memoryId_chat")public String memoryIdChat(@RequestParam(defaultValue="我是kizzo") String message,Integer userId){return assistantUnique.chat(userId,message);}

运行结果如图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对话记忆持久化

默认情况下,ChatMemory实现在内存中存储ChatMessage。
如果需要持久化,可以实现自定义的ChatMemoryStore, 将ChatMessage存储在持久化存储中,以mysql为例:

  1. 创建mysql数据库和表
    CREATE TABLE chat_messages (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    memory_id VARCHAR(255) NOT NULL,
    message_json TEXT NOT NULL,
    gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
  2. 引入依赖
    <properties><mysql-connector.version>8.0.33</mysql-connector.version><mybatis-spring-boot.version>3.0.1</mybatis-spring-boot.version></properties><dependencies>  <!--mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis-spring-boot.version}</version></dependency><!--Mysql数据库驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connector.version}</version></dependency></dependencies>
  1. 新增mapper以及xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kizzo.langchain4j_spingboot_demo.mapper.ChatMessageMapper"><select id="selectMessagesByMemoryId" resultType="string">SELECT message_json FROM chat_messages WHERE memory_id = #{memoryId} ORDER BY created_at DESC LIMIT 10</select><delete id="deleteMessagesByMemoryId">DELETE FROM chat_messages WHERE memory_id = #{memoryId}</delete><insert id="insertMessages">INSERT INTO chat_messages (memory_id, message_json, )VALUES (#{memoryId}, #{messageJson})</insert></mapper>
package com.kizzo.langchain4j_spingboot_demo.mapper;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;@Mapper
public interface ChatMessageMapper {List<String> selectMessagesByMemoryId(@Param("memoryId") String memoryId);int deleteMessagesByMemoryId(@Param("memoryId") String memoryId);int insertMessages(@Param("memoryId") String memoryId, @Param("messageJson") String messageJson);
}
  1. config包下新增mybatis配置和对话记忆持久化配置类
package com.kizzo.langchain4j_spingboot_demo.config;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;/*** MyBatis配置类*/
@Configuration
@MapperScan({"com.kizzo.langchain4j_spingboot_demo.mapper"})
@EnableTransactionManagement
public class MyBatisConfig {}
package com.kizzo.langchain4j_spingboot_demo.config;import com.kizzo.langchain4j_spingboot_demo.mapper.ChatMessageMapper;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.stream.Collectors;@Component
public class PersistentChatMemoryStore implements ChatMemoryStore {private final ChatMessageMapper chatMessageMapper;public PersistentChatMemoryStore(ChatMessageMapper chatMessageMapper) {this.chatMessageMapper = chatMessageMapper;}@Overridepublic List<ChatMessage> getMessages(Object memoryId) {String memoryIdStr = memoryId.toString();List<String> jsonMessages = chatMessageMapper.selectMessagesByMemoryId(memoryIdStr);return jsonMessages.stream().map(ChatMessageDeserializer::messagesFromJson).flatMap(List::stream).collect(Collectors.toList());}@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages) {String memoryIdStr = memoryId.toString();String json = ChatMessageSerializer.messagesToJson(messages);chatMessageMapper.insertMessages(memoryIdStr, json);}@Overridepublic void deleteMessages(Object memoryId) {chatMessageMapper.deleteMessagesByMemoryId(memoryId.toString());}
}
  1. 在配置类上新增Bean对象
    @Beanpublic AssistantUnique assistantUniqueStore(ChatLanguageModel chatLanguageModel,StreamingChatLanguageModel streamingChatLanguageModel,PersistentChatMemoryStore store){ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()// 这个设置只会影响内存中的 MessageWindowChatMemory 实例,并不会自动限制写入数据库的数据量.maxMessages(10).chatMemoryStore(store).id(memoryId).build();AssistantUnique assistant = AiServices.builder(AssistantUnique.class).chatLanguageModel(chatLanguageModel).streamingChatLanguageModel(streamingChatLanguageModel)// chatMemory变为了chatMemoryProvider,让memoryId与聊天记录绑定并作为Map的key.chatMemoryProvider(chatMemoryProvider).build();return assistant;}
  1. 注入自动配置类,并新增用数据库持久化的接口
    @AutowiredAiConfig.AssistantUnique assistantUniqueStore;/*** 带 memoryId 的记忆对话接口(使用数据库持久化)*/@RequestMapping("/memory_id_chat_store")public String memoryIdChatWithStore(@RequestParam("message") String message,@RequestParam("userId") Integer userId) {return assistantUniqueStore.chat(userId, message);}/*** 带 memoryId 的流式记忆对话接口(使用数据库持久化)*/@RequestMapping(value = "/memory_id_chat_store_stream", produces = "text/stream;charset=UTF-8")public Flux<String> memoryIdChatWithStoreStream(@RequestParam("message") String message,@RequestParam("userId") Integer userId) {TokenStream stream = assistantUniqueStore.stream(userId, message);return Flux.create(sink -> {stream.onPartialResponse(s -> sink.next(s)).onCompleteResponse(c -> sink.complete()).onError(sink::error).start();});}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/78879.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud GateWay】Connection prematurely closed BEFORE response 报错分析与解决方案

一、背景 今天业务方调用我们的网关服务报错: Connection prematurely closed BEFORE response二、原因分析 三、解决方案 第一步: 增加 SCG 服务的JVM启动参数,调整连接获取策略。 将连接池获取策略由默认的 FIFO&#xff08;先进先出&#xff09;变更为 LIFO&#xff08…

使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第十一讲)

这一期讲解lvgl中下拉框的基础使用&#xff0c;下拉列表允许用户从选项列表中选择一个值&#xff0c;下拉列表的选项表默认是关闭的&#xff0c;其中的选项可以是单个值或预定义文本。 当单击下拉列表后&#xff0c;其将创建一个列表&#xff0c;用户可以从中选择一个选项。 当…

【神经网络与深度学习】VAE 在解码前进行重参数化

在 VAE 中&#xff0c;解码之前进行重参数化主要有以下几个重要原因&#xff1a; 可微分性 在深度学习里&#xff0c;模型是通过反向传播算法来学习的&#xff0c;而这需要计算梯度。若直接从潜在变量的分布 (q_{\theta}(z|x))&#xff08;由编码器输出的均值 (\mu) 和方差 (…

BBDM学习笔记

1. configs 1.1 LBBDM: Latent BBDM [readme]

mysql主从复制搭建,并基于‌Keepalived + VIP实现高可用

以下是基于 ‌Keepalived VIP‌ 实现 MySQL 主从复制高可用的详细步骤&#xff0c;涵盖主从复制搭建与故障自动切换&#xff1a; 一、MySQL 主从复制搭建&#xff08;基础步骤回顾&#xff09; 1. ‌主库&#xff08;Master&#xff09;配置‌ 修改配置文件‌ /etc/my.cnf&…

CD36.【C++ Dev】STL库的string的使用 (下)

目录 1.reserve函数(不是reverse) 代码示例 2.resize 代码示例 3.reserve和resize的区别 4.shrink_to_fit 代码示例 5.与C语言的配合的接口函数: c_str 代码示例 6.rfind 知识回顾:find函数 rfind 代码示例 练习题: 字符串最后一个单词的长度 代码 提交结果 ​…

STM32的网络天气时钟项目

一、项目概述与硬件架构 1.1 核心功能 本智能天气时钟系统集成了实时天气获取、网络时间同步、环境监测和低功耗管理四大核心功能&#xff1a; 网络数据获取&#xff1a; 通过ESP8266 WiFi模块连接心知天气API&#xff08;每小时更新&#xff09;获取北京标准时间服务器的时…

FPGA DDR4多通道管理控制器设计

DDR4控制器一般采用自带的MIG控制器&#xff0c;用户控制主要是基于MIG IP核进行设计 实际工程项目中可能只挂载了一组DDR&#xff0c;但是用户数据可能有很多种&#xff0c;用户通过给每种数据划分特定地址进行存储&#xff0c;如何实现灵活管理成为设计的关键 为了方便后端数…

低代码 x AI,解锁数智化应用的创新引擎

AI 智能体开发指南 随着全球信息化浪潮的持续推进&#xff0c;数字化、智能化转型已成为企业发展的必经之路。在这个变革的时代&#xff0c;企业面临着前所未有的挑战与机遇。一方面&#xff0c;市场环境瞬息万变&#xff0c;企业需要快速响应并调整业务模式&#xff1b;另一方…

【Spring Boot 注解】@Configuration与@AutoConfiguration

文章目录 Configuration与AutoConfiguration一、Configuration二、AutoConfiguration Configuration与AutoConfiguration 一、Configuration 这是最常用的 Spring 注解之一&#xff0c;表示当前类是一个 配置类&#xff0c;可以定义 Bean 方法&#xff0c;等效于传统的 XML 配…

arXiv论文 MALOnt: An Ontology for Malware Threat Intelligence

文章讲恶意软件威胁情报本体。 作者信息 作者是老美的&#xff0c;单位是伦斯勒理工学院&#xff0c;文章是2020年的预印本&#xff0c;不知道后来发表在哪里&#xff08;没搜到&#xff0c;或许作者懒得投稿&#xff0c;也可能是改了标题&#xff09;。 中心思想 介绍开源…

【存储管理—动态不等长存储资源分配算法】

文章目录 一、实验目的二、实验内容与设计思想实验内容设计思路 三、实验代码实现四、总结 一、实验目的 理解动态异长存储分区资源管理&#xff0c;掌握所需数据结构和管理程序&#xff0c;了解各种存储分配算法的优点和缺点。 二、实验内容与设计思想 实验内容 1.分析uni…

快速上手 Docker:从入门到安装的简易指南(Mac、Windows、Ubuntu)

PS&#xff1a;笔者在五一刚回来一直搞Docker部署AI项目&#xff0c;发现从开发环境迁移到生成环境时&#xff0c;Docker非常好用。但真的有一定上手难度&#xff0c;推荐读者多自己尝试踩踩坑。 本篇幅有限&#xff0c;使用与修改另起篇幅。 一、Docker是什么 #1. Docker是什…

LabVIEW高冲击加速度校准系统

在国防科技领域&#xff0c;高 g 值加速度传感器广泛应用于先进兵器研制&#xff0c;如深侵彻系统、精确打击弹药及钻地弹药等。其性能指标直接影响研究结果的准确性与可靠性&#xff0c;因此对该传感器进行定期校准意义重大。高冲击加速度校准系统具备多方面功能&#xff0c;适…

FPGA 纯逻辑NVME raid0 IP核

系统采用XCZU19EG搭载4个三星990 PRO SSD 单盘读写不低于3GB/s 4盘总带宽不低于12GB/s

GStreamer开发笔记(三):测试gstreamer/v4l2+sdl2/v4l2+QtOpengl打摄像头延迟和内存

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/147714800 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、O…

CATIA高效工作指南——零件建模篇(二)

一、PowerCopy特征复用技术 1.1 智能特征封装 通过​​几何图形集(Geometrical Set)​​构建参数化特征组&#xff0c;将关联的草图、曲面、实体等元素进行逻辑封装。操作流程如下&#xff1a; 创建新几何图形集并完成特征建模激活PowerCopy命令&#xff0c;选择目标几何集定…

CentOS 7 安装OpenJDK 17 JRE

CentOS 7 自带的java 版本为&#xff1a;java version "1.8.0_311"&#xff0c; 有些软件的运行需要更高的java版本。CentOS 7 自带的默认仓库里 没有 OpenJDK 17&#xff0c;但是 Adoptium 项目&#xff08;前身 AdoptOpenJDK&#xff09;提供了稳定的 OpenJDK 17 版…

【c++】 我的世界

太久没更新小游戏了 给个赞和收藏吧&#xff0c;求求了 要游戏的请私聊我 #include <iostream> #include <vector>// 定义世界大小 const int WORLD_WIDTH 20; const int WORLD_HEIGHT 10;// 定义方块类型 enum BlockType {AIR,GRASS,DIRT,STONE };// 定义世界…

angular的cdk组件库

目录 一、虚拟滚动 一、虚拟滚动 <!-- itemSize相当于每个项目的高度为30px --><!-- 需要给虚拟滚动设置宽高&#xff0c;否则无法正常显示 --> <cdk-virtual-scroll-viewport [itemSize]"40" class"view_scroll"><div class"m…