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

Java8+Spring Boot + Vue + Langchain4j 实现阿里云百炼平台 AI 流式对话对接

1. 引言

在本文中,我们将介绍如何使用 Spring BootVue.jsLangchain4j,实现与 阿里云百炼平台 的 AI 流式对话对接。通过结合这些技术,我们将创建一个能够实时互动的 AI 聊天应用。

这是一个基于 Spring Boot + Vue.js + Langchain4j 的智能对话系统,实现了类似 ChatGPT 的流式交互体验。系统通过 阿里云百炼 qwen-max 大模型 提供 AI 能力,支持多轮对话记忆(Redis 存储)、本地知识库检索(RAG)和实时流式响应(SSE 协议)。前端 Vue.js 动态渲染对话内容,后端 Spring Boot 处理业务逻辑,Langchain4j 简化大模型集成,具有低延迟、易扩展的特点,适用于智能客服、知识问答等场景。

在线预览地址
https://www.coderman.club

对话记忆功能
在这里插入图片描述
代码编写效果图
在这里插入图片描述
预览效果

2. 技术栈概述

  • Spring Boot:后端框架,用于处理与阿里云百炼平台的 API 对接与业务逻辑。
  • Vue.js:前端框架,用于实现与用户的实时交互和流式数据展示。
  • Langchain4j:Java 库,用于简化与文本生成模型的交互。
  • 阿里云百炼平台:为智能对话提供 NLP 模型和 API,支持流式对话功能。

3. 阿里云百炼平台接入准备

3.1 注册阿里云百炼账号

首先,注册一个阿里云账号并访问百炼平台,获取相关的 API 密钥

3.2 创建对话模型

在阿里云百炼平台上创建并配置一个 AI 对话模型,为后续的对接做好准备。

3.3 获取 API 文档

参考阿里云的 API 文档,获取具体的接口信息和请求参数。

4. 后端实现 - Spring Boot 与阿里云百炼平台对接

4.1 创建 Spring Boot 项目

使用 Spring Initializr 创建一个基础的 Spring Boot 项目,配置相关依赖。

 <dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain4j.version}</version></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-dashscope</artifactId><version>${langchain4j.version}</version></dependency>

4.2 编写Controller代码

@Api(value = "AI接口", tags = "AI接口")
@RestController
@Slf4j
@RequestMapping(value = "/common/chat")
public class ChatController {@Resourceprivate ChatService chatService;@ApiModelProperty(value = "AI对话")@PostMapping(value = "/completion",produces = {MediaType.TEXT_EVENT_STREAM_VALUE})public SseEmitter completion(@RequestBody ChatGptDTO chatGptDTO) {SseEmitter sseEmitter = new SseEmitter(5 * 60 * 1000L);this.chatService.completion(chatGptDTO, sseEmitter);sseEmitter.onTimeout(() -> {log.warn("SSE连接超时");sseEmitter.complete();});sseEmitter.onError(e -> {log.error("SSE连接错误", e);sseEmitter.complete();});return sseEmitter;}
}

4.2 编写Service代码

package com.coderman.admin.service.common.impl;import com.alibaba.fastjson.JSONObject;
import com.coderman.admin.dto.common.ChatGptDTO;
import com.coderman.admin.service.common.Assistant;
import com.coderman.admin.service.common.ChatService;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import javax.annotation.Resource;
import java.io.IOException;@Service
@Slf4j
@Configuration
public class ChatServiceImpl implements ChatService {@Resourceprivate Assistant assistant;/*** 调用大模型接口* @param chatGptDTO 参数* @param sseEmitter sse*/public void completion(ChatGptDTO chatGptDTO, SseEmitter sseEmitter) {try {TokenStream stream = assistant.completion(chatGptDTO.getSessionId(), chatGptDTO.getPrompt());// 监听回调stream.onNext(result -> sendSseData(sseEmitter, result));stream.onError(throwable -> handleError(sseEmitter, throwable));stream.onComplete(response -> completeSse(sseEmitter));stream.start();} catch (Exception e) {throw new RuntimeException("completion error:"+e.getMessage());}}private void sendSseData(SseEmitter sseEmitter, String result) {try {JSONObject jsonObject =  new JSONObject();jsonObject.put("text",result);sseEmitter.send(jsonObject, MediaType.APPLICATION_JSON);} catch (IOException e) {log.warn("SSE 发送数据失败:{}", e.getMessage());}}private void handleError(SseEmitter sseEmitter, Throwable throwable) {log.error("SSE 发生错误:{}", throwable.getMessage());sseEmitter.completeWithError(throwable);}private void completeSse(SseEmitter sseEmitter) {try {sseEmitter.send("[DONE]");sseEmitter.complete();} catch (Exception e) {log.warn("SSE 关闭失败:{}", e.getMessage());}}
}

4.3 langchain4j配置类

package com.coderman.admin.config;import com.coderman.admin.service.common.Assistant;
import com.coderman.api.constant.CommonConstant;
import com.coderman.service.util.DesUtil;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;
import java.util.List;/*** @author :zhangyukang* @date :2025/04/02 11:12*/
@Configuration
@Slf4j
public class ChatAiConfigure {@Resourceprivate RedisProperties redisProperties;@Beanpublic Assistant assistant() {EmbeddingModel embeddingModel =  QwenEmbeddingModel.builder().apiKey(this.getApiKey()).build();InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore).embeddingModel(embeddingModel)// 最相似的3个结果.maxResults(3)// 只找相似度在0.8以上的内容.minScore(0.8).build();// 初始化知识库this.loadEmbedding(embeddingModel, embeddingStore);// 自定义存储方式RedisChatMemoryStore redisChatMemoryStore = RedisChatMemoryStore.builder().port(redisProperties.getPort()).password(redisProperties.getPassword()).host(redisProperties.getHost()).build();// 构建模型StreamingChatLanguageModel model = QwenStreamingChatModel.builder().apiKey(this.getApiKey()).modelName("qwen-max").maxTokens(1024).enableSearch(true).build();// 构建服务return AiServices.builder(Assistant.class).streamingChatLanguageModel(model).chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder().id(memoryId).maxMessages(10).chatMemoryStore(redisChatMemoryStore).build()).contentRetriever(contentRetriever).build();}/*** 初始化RAG向量* @param embeddingModel 模型* @param embeddingStore 向量存储*/private void loadEmbedding(EmbeddingModel embeddingModel, InMemoryEmbeddingStore<TextSegment> embeddingStore) {try {Document document = FileSystemDocumentLoader.loadDocument("/opt/rag.txt", new TextDocumentParser());List<TextSegment> textSegments = new RagDocumentSplitter().split(document);Response<List<Embedding>> response = embeddingModel.embedAll(textSegments);List<Embedding> embeddings = response.content();embeddingStore.addAll(embeddings, textSegments);} catch (Exception e) {log.warn("初始化知识库失败:{}", e.getMessage());}}private String getApiKey() {try {final String API_KEY_ENCRYPTED = "你的app秘钥";return DesUtil.decrypt(API_KEY_ENCRYPTED, CommonConstant.SECRET_KEY);} catch (Exception e) {log.error("API Key 解密失败", e);throw new RuntimeException("无法解密 API Key");}}
}

4.4 自定义AI对话接口类

package com.coderman.admin.service.common;import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.UserMessage;/*** @author :zhangyukang* @date :2025/04/02 11:14*/
public interface Assistant {@SystemMessage(value = {"你是由小章鱼开发的AI智能助手,专注于高效、友好地帮助用户解决问题。","你的核心能力包括:问题解答、信息查询、任务协助、建议提供等。","当用户询问你的身份时,请明确回复:'我是由小章鱼开发的AI助手,很高兴为你服务!'","回答时需保持专业且亲切,语言简洁易懂,避免冗长或模糊表述。","如果遇到无法回答的问题,应礼貌说明并建议替代方案。","始终以用户需求为核心,主动提供有价值的帮助。"})TokenStream completion(@MemoryId String sessionId, @UserMessage String userMessage);
}

4.5 使用Redis存储实现对话记忆

package com.coderman.admin.config;import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.internal.ValidationUtils;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import redis.clients.jedis.*;import java.util.ArrayList;
import java.util.List;/*** 使用 Redis 存储聊天记录的 MemoryStore*/
public class RedisChatMemoryStore implements ChatMemoryStore {private final JedisPool jedisPool;private final static String KEY_PREFIX = "chat_memory:";public RedisChatMemoryStore(String host, Integer port, String password) {// 配置 Redis 连接池JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(10);poolConfig.setMaxIdle(5);poolConfig.setMinIdle(2);// 这里 JedisPool 支持传入密码this.jedisPool = new JedisPool(poolConfig, host, port, 2000, password);}@Overridepublic List<ChatMessage> getMessages(Object memoryId) {try (Jedis jedis = jedisPool.getResource()) {String json = jedis.get(KEY_PREFIX+ toMemoryIdString(memoryId));return json == null ? new ArrayList<>() : ChatMessageDeserializer.messagesFromJson(json);}}@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages) {try (Jedis jedis = jedisPool.getResource()) {String json = ChatMessageSerializer.messagesToJson(ValidationUtils.ensureNotEmpty(messages, "messages"));String res = jedis.set(KEY_PREFIX + toMemoryIdString(memoryId), json);if (!"OK".equals(res)) {throw new RuntimeException("Redis set memory error: " + res);}}}@Overridepublic void deleteMessages(Object memoryId) {try (Jedis jedis = jedisPool.getResource()) {jedis.del(KEY_PREFIX + toMemoryIdString(memoryId));}}private static String toMemoryIdString(Object memoryId) {if (memoryId == null || memoryId.toString().trim().isEmpty()) {throw new IllegalArgumentException("memoryId cannot be null or empty");}return memoryId.toString();}public static Builder builder() {return new Builder();}/*** Builder 模式,方便配置 Redis 连接*/public static class Builder {private String host = "127.0.0.1";private Integer port = 6379;private String password = null;public Builder host(String host) {this.host = host;return this;}public Builder port(Integer port) {this.port = port;return this;}public Builder password(String password) {this.password = password;return this;}public RedisChatMemoryStore build() {return new RedisChatMemoryStore(host, port, password);}}
}

源码地址: https://github.com/zykzhangyukang/admin

相关文章:

Java8+Spring Boot + Vue + Langchain4j 实现阿里云百炼平台 AI 流式对话对接

1. 引言 在本文中&#xff0c;我们将介绍如何使用 Spring Boot、Vue.js 和 Langchain4j&#xff0c;实现与 阿里云百炼平台 的 AI 流式对话对接。通过结合这些技术&#xff0c;我们将创建一个能够实时互动的 AI 聊天应用。 这是一个基于 Spring Boot Vue.js Langchain4j 的智…...

可发1区的超级创新思路(python 实现):一种轻量化的动态稀疏门控网络

首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 一、应用领域 视频异常检测、生成视频检测。 二、模型解析 该模型由1.关键帧动态选择机制、2.关键帧动态选择机制以及3.关键帧动态选择机制三大核心组件构成,形成端到端的视频异常…...

【Kafka基础】单机安装与配置指南,从零搭建环境

学习Kafka&#xff0c;掌握Kafka的单机部署是理解其分布式特性的第一步。本文将手把手带你完成Kafka单机环境的安装、配置及基础验证&#xff0c;涵盖常见问题排查技巧。 1 环境准备 1.1 系统要求 操作系统&#xff1a;CentOS 7.9依赖组件&#xff1a;JDK 8&#xff08;Kafka …...

Scala 转义字符

Scala 转义字符 引言 Scala作为一种多范式编程语言&#xff0c;拥有丰富的字符处理能力。在Scala编程中&#xff0c;转义字符的使用非常频繁&#xff0c;它们可以用来处理字符串中的特殊字符&#xff0c;使得字符串的表示更加直观和符合预期。本文将详细探讨Scala中的转义字符…...

TCP/IP五层协议

目录 1. 五层模型结构 2. 各层核心功能与协议 (1) 应用层&#xff08;Application Layer&#xff09; (2) 传输层&#xff08;Transport Layer&#xff09; (3) 网络层&#xff08;Network Layer&#xff09; (4) 数据链路层&#xff08;Data Link Layer&#xff09; (5…...

Dify接口api对接,流式接收流式返回(.net)

试了好多种方法除了Console.WriteLine()能打印出来&#xff0c;试了好些方法都不行&#xff0c;不是报错就是打印只有一行&#xff0c;要么就是接收完才返回...下面代码实现调用api接收流式数据&#xff0c;并进行流式返回给前端&#xff1a; using Furion.HttpRemote; using …...

微信小程序开发前端培训课程

大前端培训课程 1.HTML课程&#xff1a; 1.HTML标签基础 2.布局DIVspan 3.表单标签 4.多媒体标签 5.Table使用 2.CSS课程&#xff1a; 1.Box 盒子模型&#xff0c;列表布局&#xff08;一行两列&#xff0c;一行多列&#xff09; 2.单行文字&#xff0c;多行文字 3.文…...

代码随想录算法训练营第五十二天|图论专题: 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿

101. 孤岛的总面积 本题要求找到不靠边的陆地面积&#xff0c;那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋&#xff0c;然后再去重新遍历地图 统计此时还剩下的陆地就可以了。 1、从左边和后边向中间遍历 2、从上边和下边向中间遍历…...

仿modou库one thread one loop式并发服务器

源码&#xff1a;田某super/moduo 目录 SERVER模块&#xff1a; Buffer模块&#xff1a; Socket模块&#xff1a; Channel模块&#xff1a; Connection模块&#xff1a; Acceptor模块&#xff1a; TimerQueue模块&#xff1a; Poller模块&#xff1a; EventLoop模块&a…...

MNIST 数据集 与 TFOD API

此处给出我在进行毕业设计过程中写的三份脚本&#xff0c;作为demo 展示模型的预处理&#xff0c;输出信息提取和TFOD API的应用。 script1 加载本地的MNIST模型&#xff0c;对本地的手写数字进行推理 # test the validation of the saved file and the camera import cv2 i…...

SpringSecurity6.0 通过JWTtoken进行认证授权

之前写过一个文章&#xff0c;从SpringSecurity 5.x升级到6.0&#xff0c;当时是为了配合公司的大版本升级做的&#xff0c;里面的各项配置都是前人留下来的&#xff0c;其实没有花时间进行研究SpringSecurity的工作机制。现在新东家有一个简单的系统要搭建&#xff0c;用户的认…...

【Java】Maven

一、概念 是一个项目管理和构建工具&#xff0c;它基于项目对象模型&#xff08;POM&#xff09;的概念&#xff0c;通过一小段描述信息来管理项目的构建。 二、Maven坐标 <groupId>com.itheima</groupId><artifactId>maven-project01</artifactId>&…...

第十五届蓝桥杯PythonC组题解

A题&#xff1a;拼正方形 问题描述 给定一定数量的 22 和 11 的方块&#xff0c;求能拼出的最大正方形边长。 解题思路 二分法&#xff1a;将奇数和偶数边长分开处理&#xff0c;通过二分法寻找最大满足条件的边长。面积验证&#xff1a;总方块面积需大于等于目标正方形面积…...

MATLAB中plot函数的详细参数表

LineSpec - 线型、标记和颜色 线型说明-实线--虚线:点线-.点划线 标记说明o圆圈加号*星号.点x叉号_水平线条|垂直线条s方形d菱形^上三角v下三角>右三角<左三角p五角形h六角形 颜色说明 y 黄色 m 品红色 c 青蓝色 r 红色 g 绿色 b 蓝色 w 白色 k 黑色 MarkerFaceColor…...

R语言赋能气象水文科研:从多维数据处理到学术级可视化

全球气候变化加剧了极端天气与水文事件的复杂性&#xff0c;气象卫星、雷达、地面观测站及水文传感器每天产生TB级‌时空异质数据‌。传统研究常面临四大瓶颈&#xff1a; ‌数据清洗低效‌&#xff1a;缺失值、异常值处理耗时&#xff1b;‌时空分析模型构建复杂‌&#xff1…...

虚拟试衣间-云尚衣橱小程序-衣橱管理实现

衣橱管理实现 目标 (Goal): 用户 (User): 能通过 UniApp 小程序上传衣服图片。 后端 (Backend): 接收图片,存到云存储,并将图片信息(URL、用户ID等)存入数据库。 用户 (User): 能在小程序里看到自己上传的所有衣服图片列表。 技术栈细化 (Refined Tech Stack for this Pha…...

BGP路由协议之属性2

Orgin 起源 公认必遵属性 起源名称标记描述IGPi如果路由是由始发的 BGP 路由器使用 network 命令注入到 BGP 的&#xff0c;那么该 BGP 路由的 origin 属性为 IGPEGPe如果路由是通过 EGP 学习到的&#xff0c;那么该 BGP 路由的 Origin 属性为 EGPIncomplete?如果路由是通过…...

纯个人整理,蓝桥杯使用的算法模板day2(0-1背包问题),手打个人理解注释,超全面,且均已验证成功(附带详细手写“模拟流程图”,全网首个

算法索引 01背包优化前空间优化版&#xff08;使用一维数组&#xff09;优化后的模拟流程图为何优化后&#xff0c;j不能使用正序遍历模拟流程图 代码对应实现案例 01背包 优化前 /*** 0-1背包问题解法&#xff08;与下方代码表格示例对应&#xff0c;已模拟验证&#xff09;*…...

算法与数据结构线性表之栈和队列

Hello大家好&#xff01; 很高兴与大家见面&#xff01; 给生活添点快乐&#xff0c;开始今天的编程之路。 我的博客:<但愿. 我的专栏:C语言、题目精讲、算法与数据结构、C 欢迎点赞&#xff0c;关注 一 栈 1概念&#xff1a;栈是⼀种特殊的线性表&#xff0c;其只允许…...

python应用之使用pdfplumber 解析pdf文件内容

目录标题 一. 通过 pdfplumber.open() 解析复杂PDF&#xff1a;1-2. 报错&#xff1a;V2 &#xff1a; 1-3. v3 使用tk 库&#xff0c;弹框选择文件运行环境准备完整代码保存运行测试步骤方式二&#xff1a;命令行方式&#xff08;适用于自动化&#xff09; 测试用例示例常见问…...

laravel update报In PackageManifest.php line 122:Undefined index: name 错误的解决办法

用 composer 更新 laravel依赖包时报错 > Illuminate\Foundation\ComposerScripts::postAutoloadDump > Illuminate\Foundation\ComposerScripts::postAutoloadDump > php artisan package:discover --ansiIn PackageManifest.php line 122:Undefined index: nameScr…...

Vue中使用antd-table组件实现数据选择、禁用、已选择禁用-demo

实现案例 实现过程 表格代码 关键代码 :row-selection="rowSelection" <div><div class="flex items-center justify-between pt-[24px] pb-[16px]"><p>已选:{{ keysNum }}</p><a-input-search v-model:value="productN…...

C语言--统计输入字符串中的单词个数

输入 输入&#xff1a;大小写字母以及空格&#xff0c;单词以空格分隔 输出&#xff1a;单词个数 代码 如果不是空格且inWord0说明是进入单词的第一个字母&#xff0c;则单词总数加一。 如果是空格&#xff0c;证明离开单词&#xff0c;inWord 0。 #include <stdio.h&g…...

Kubernetes 集群搭建(三):使用dashboard用户界面(需要访问外网获取yaml)

&#xff08;一&#xff09;简介 K8s Dashboard是Kubernetes提供的一种基于Web的用户界面工具&#xff0c;用于可视化地管理和监控Kubernetes集群 主要功能&#xff1a; 资源查看与管理&#xff1a; 查看Kubernetes集群中的各种资源&#xff0c;如节点、Pod、服务、部署等。 对…...

Debian 12 服务器搭建Beego环境

一、Debian 12系统准备 1.更新系统 #apt update && apt upgrade -y 2.安装基础工具 #apt install -y git curl wget make gcc 二、安装Go环境 Go语言的镜像官网&#xff1a;https://golang.google.cn/ 1.下载go最新版 #cd /usr/local/src #wget -o https://golang.go…...

游戏引擎学习第208天

运行游戏并回顾我们的情况 今天&#xff0c;我们将继续完成之前中断的调试输出工作。最近的工作偏离了一些&#xff0c;展示了如何进行元编程的实践&#xff0c;主要涉及了一个小的解析器。尽管这个解析器本身是一个玩具&#xff0c;但它展示了如何完成一个完整的循环&#xf…...

【在校课堂笔记】Python 第 7 节课 总结

- 第 85 篇 - Date: 2025 - 04 - 06 Author: 郑龙浩/仟墨 【Python 在校课堂笔记】 南山-第 7 节课 上课时间: 2025-03-27 文章目录 南山-第 7 节课一 99乘法表 –> 三角二 函数1 已接触的函数&#xff0c;部分举例2 自定函数的定义与使用自定义函数:举例 3 带参数的4 阶乘…...

评价区动态加载是怎么实现的?

淘宝商品评价区的动态加载是通过一系列前端技术和后端接口实现的&#xff0c;其核心目的是提升用户体验和页面性能。以下是其实现原理和关键技术的详细解析&#xff1a; 1. 前端实现&#xff1a;AJAX 和 JavaScript 淘宝利用 AJAX&#xff08;Asynchronous JavaScript and XM…...

【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的监控:使用 Actuator 实现健康检查

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、引子&…...

蓝桥杯—数字接龙(dfs+减枝)

一.题目 二.思路 一看就是迷宫问题的变种&#xff0c;从左上角到达右下角&#xff0c;要解决 1.8个方向的方向向量&#xff0c;用dx&#xff0c;dy数组代表方向向量 2.要按照一个规律的数值串进行搜索0&#xff0c;1&#xff0c;2&#xff0c;k-1&#xff0c;0&#xff0c;1…...