百度文心一言 java 支持流式输出,Springboot+ sse的demo
参考:GitHub - mmciel/wenxin-api-java: 百度文心一言Java库,支持问答和对话,支持流式输出和同步输出。提供SpringBoot调用样例。提供拓展能力。
1、依赖
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>4.16.18</version>
</dependency>
2、配置apikey和secretkey
3、主要使用的接口
4、返回的json格式
3、WenxinEventSourceListener 事件监听器
和其他的接口不一样 需要 CompletionsResponse.data 封装下 ,不然前端页面需要兼容非json的格式
@Slf4j
public class WenxinEventSourceListener extends EventSourceListener {private long tokens;private SseEmitter sseEmitter;public WenxinEventSourceListener(SseEmitter sseEmitter) {this.sseEmitter = sseEmitter;}@Overridepublic void onOpen(EventSource eventSource, Response response) {log.info("建立sse连接...");}@SneakyThrows@Override@JsonIgnoreProperties(ignoreUnknown = true)public void onEvent(EventSource eventSource, String id, String type, String data) {ChatResponse bean = JSONUtil.parseObj(data).toBean(ChatResponse.class);log.info("返回数据:{}", data);if (bean.getIs_end()) {log.info("返回数据结束了");sseEmitter.send(SseEmitter.event().id("[TOKENS]").data("<br/><br/>tokens:" + tokens()).reconnectTime(3000));sseEmitter.send(SseEmitter.event().id("[DONE]").data("[DONE]").reconnectTime(3000));// 传输完成后自动关闭ssesseEmitter.complete();return;}log.info("OpenAI返回数据:{}", data);tokens += 1;if (data.equals("[DONE]")) {log.info("OpenAI返回数据结束了");sseEmitter.send(SseEmitter.event().id("[TOKENS]").data("<br/><br/>tokens:" + tokens()).reconnectTime(3000));sseEmitter.send(SseEmitter.event().id("[DONE]").data("[DONE]").reconnectTime(3000));// 传输完成后自动关闭ssesseEmitter.complete();return;}CompletionsResponse completionResponse = new CompletionsResponse();CompletionsResponse.Data dataResult = new CompletionsResponse.Data();dataResult.setText(bean.getResult());completionResponse.setData(dataResult);try {sseEmitter.send(SseEmitter.event().id(bean.getId()).data(completionResponse.getData()).reconnectTime(3000));} catch (Exception e) {log.error("sse信息推送失败!");eventSource.cancel();e.printStackTrace();}}@Overridepublic void onClosed(EventSource eventSource) {log.info("关闭sse连接...");}@SneakyThrows@Overridepublic void onFailure(EventSource eventSource, Throwable t, Response response) {if(Objects.isNull(response)){log.error("sse连接异常:{}", t);eventSource.cancel();return;}ResponseBody body = response.body();if (Objects.nonNull(body)) {// 错误处理 {"error_code":110,"error_msg":"Access token invalid or no longer valid"},异常:{}log.error("sse连接异常data:{},异常:{}", body.string(), t);} else {log.error("sse连接异常data:{},异常:{}", response, t);}eventSource.cancel();}/*** tokens* @return*/public long tokens() {return tokens;}
}
4、WenXinClient 流式主要看下 streamChat 方式,之前从千帆上找到流式例子 返回type是json的,所以之前自己手写的demo总报异常。
public void streamChat(ChatBody chatBody, EventSourceListener eventSourceListener, ModelE modelE) {if (Objects.isNull(eventSourceListener)) {throw new WenXinException("参数异常:EventSourceListener不能为空");}chatBody.setStream(true);try {EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);Request request = new Request.Builder().url(assembleUrl(modelE)).post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()),new ObjectMapper().writeValueAsString(chatBody))).build();factory.newEventSource(request, eventSourceListener);} catch (Exception e) {log.error("请求参数解析异常:", e);e.printStackTrace();}}private String assembleUrl(ModelE modelE) {accessToken = WenXinConfig.refreshAccessToken();return modelE.getApiHost() + "?access_token=" + accessToken;}
5、定义Sse的接口是实现方法
public interface SseService {/*** 创建SSE* @param uid* @return*/SseEmitter createSse(String uid);/*** 关闭SSE* @param uid*/void closeSse(String uid);/*** 客户端发送消息到服务端* @param uid* @param chatRequest*/ChatResponse sseChat(String uid, ChatRequest chatRequest);
}
public class WenXinSseServiceImpl implements SseService {@Value("${chat.accessKeyId}")private String accessKeyId;@Value("${chat.accessKeySecret}")private String accessKeySecret;@Value("${chat.agentKey}")private String agentKey;@Value("${chat.appId}")private String appId;@AutowiredWenXinClient wenXinClient;@Overridepublic SseEmitter createSse(String uid) {//默认30秒超时,设置为0L则永不超时SseEmitter sseEmitter = new SseEmitter(0l);//完成后回调sseEmitter.onCompletion(() -> {log.info("[{}]结束连接...................", uid);LocalCache.CACHE.remove(uid);});//超时回调sseEmitter.onTimeout(() -> {log.info("[{}]连接超时...................", uid);});//异常回调sseEmitter.onError(throwable -> {try {log.info("[{}]连接异常,{}", uid, throwable.toString());sseEmitter.send(SseEmitter.event().id(uid).name("发生异常!").data(Message.builder().content("发生异常请重试!").build()).reconnectTime(3000));LocalCache.CACHE.put(uid, sseEmitter);} catch (IOException e) {e.printStackTrace();}});try {sseEmitter.send(SseEmitter.event().reconnectTime(5000));} catch (IOException e) {e.printStackTrace();}LocalCache.CACHE.put(uid, sseEmitter);log.info("[{}]创建sse连接成功!", uid);return sseEmitter;}@Overridepublic void closeSse(String uid) {SseEmitter sse = (SseEmitter) LocalCache.CACHE.get(uid);if (sse != null) {sse.complete();//移除LocalCache.CACHE.remove(uid);}}@Overridepublic ChatResponse sseChat(String uid, ChatRequest chatRequest) {if (StringUtils.isBlank(chatRequest.getMsg())) {log.error("参数异常,msg为null", uid);throw new BaseException("参数异常,msg不能为空~");}SseEmitter sseEmitter = (SseEmitter) LocalCache.CACHE.get(uid);if (sseEmitter == null) {log.info("聊天消息推送失败uid:[{}],没有创建连接,请重试。", uid);throw new BaseException("聊天消息推送失败uid:[{}],没有创建连接,请重试。~");}WenxinEventSourceListener openAIEventSourceListener = new WenxinEventSourceListener(sseEmitter);List<MessageItem> messages = new ArrayList<>();messages.add(MessageItem.builder().role(MessageItem.Role.USER).content(chatRequest.getMsg()).build());wenXinClient.streamChat(messages, openAIEventSourceListener, ModelE.ERNIE_Bot);LocalCache.CACHE.put("msg" + uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT);ChatResponse response = new ChatResponse();response.setQuestionTokens(1);return response;}
}
6、主要的controller接口
/*** 创建sse连接** @param headers* @return*/@CrossOrigin@GetMapping("/createSse")public SseEmitter createConnect(@RequestHeader Map<String, String> headers) {String uid = getUid(headers);return sseService.createSse(uid);}/*** 聊天接口** @param chatRequest* @param headers*/@CrossOrigin@PostMapping("/chat")@ResponseBodypublic ChatResponse sseChat(@RequestBody ChatRequest chatRequest, @RequestHeader Map<String, String> headers, HttpServletResponse response) {String uid = getUid(headers);return sseService.sseChat(uid, chatRequest);}/*** 关闭连接** @param headers*/@CrossOrigin@GetMapping("/closeSse")public void closeConnect(@RequestHeader Map<String, String> headers) {String uid = getUid(headers);sseService.closeSse(uid);}
7、主要的页面代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能问答</title><link rel="stylesheet" href="styles.css"> <!-- 引入外部CSS --><script src="HZRecorder.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script src="js/markdown.min.js"></script><script src="js/eventsource.min.js"></script><script>function setText(text, uuid_str) {let content = document.getElementById(uuid_str);content.innerHTML = marked(text);}function uuid() {var s = [];var hexDigits = "0123456789abcdef";for (var i = 0; i < 36; i++) {s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);}s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01s[8] = s[13] = s[18] = s[23] = "-";var uuid = s.join("");console.log(uuid)return uuid;}window.onload = function () {/*let disconnectBtn = document.getElementById("disconnectSSE");*/let messageElement = document.getElementById("messageInput");let chat = document.getElementById("chat-messages");let sse;let uid = window.localStorage.getItem("uid");if (uid == null || uid == "" || uid == "null") {uid = uuid();}let text = "";let uuid_str;// 设置本地存储window.localStorage.setItem("uid", uid);// 发送消息按钮点击事件document.getElementById('sendTextButton').addEventListener('click', async function () {try {const userInput = document.getElementById('messageInput').value.trim();if (userInput) {await sseOneTurn(userInput)userInput.value = ''; // 清空输入框} else {alert('请输入文字消息!');}} catch (error) {alert('发送消息时发生错误: ' + error.message);}});// 回车事件messageElement.onkeydown = function () {if (window.event.keyCode === 13) {if (!messageElement.value) {return;}sseOneTurn(messageElement.value);}};function sseOneTurn(InputText) {uuid_str = uuid();//创建sseconst eventSource = new EventSourcePolyfill("/createSse", {headers: {uid: uid,},});eventSource.onopen = (event) => {console.log("开始输出后端返回值");sse = event.target;};eventSource.onmessage = (event) => {debugger;if (event.lastEventId == "[TOKENS]") {text = text + event.data;setText(text, uuid_str);text = "";return;}if (event.data == "[DONE]") {text = "";if (sse) {sse.close();}return;}let json_data = JSON.parse(event.data);console.log(json_data);if (json_data.text == null || json_data.text == "null") {return;}text = text + json_data.text;setText(text, uuid_str);};eventSource.onerror = (event) => {console.log("onerror", event);alert("服务异常请重试并联系开发者!");if (event.readyState === EventSource.CLOSED) {console.log("connection is closed");} else {console.log("Error occured", event);}event.target.close();};eventSource.addEventListener("customEventName", (event) => {console.log("Message id is " + event.lastEventId);});eventSource.addEventListener("customEventName", (event) => {console.log("Message id is " + event.lastEventId);});$.ajax({type: "post",url: "/chat",data: JSON.stringify({msg: InputText,}),contentType: "application/json;charset=UTF-8",dataType: "json",headers: {uid: uid,},beforeSend: function (request) {},success: function (result) {//新增问题框debugger;chat.innerHTML +='<tr><td style="height: 30px;">' +InputText +"<br/><br/> tokens:" +result.question_tokens +"</td></tr>";InputText = null;//新增答案框chat.innerHTML +='<tr><td><article id="' +uuid_str +'" class="markdown-body"></article></td></tr>';},complete: function () {},error: function () {console.info("发送问题失败!");},});}/*disconnectBtn.onclick = function () {if (sse) {sse.close();}};*/};</script></head>
<body><div class="chat-container"><div class="chat-header"><h1>智能问答</h1></div><div class="chat-messages" id="chat-messages"><!-- 聊天消息将会在这里显示 --></div><form class="message-form" onsubmit="return false;"><input type="text" id="messageInput" placeholder="输入消息..." autocomplete="off"><button type="button" id="sendTextButton">发送文字</button><button type="button" id="recordAndUploadButton">按住录音</button><progress id="uploadProgress" value="0" max="100" style="display:none;"></progress></form>
</div></body></html>
最后的呈现效果如下:
相关文章:

百度文心一言 java 支持流式输出,Springboot+ sse的demo
参考:GitHub - mmciel/wenxin-api-java: 百度文心一言Java库,支持问答和对话,支持流式输出和同步输出。提供SpringBoot调用样例。提供拓展能力。 1、依赖 <dependency> <groupId>com.baidu.aip</groupId> <artifactId…...

59.基于SSM实现的网上花店系统(项目 + 论文)
项目介绍 本站是一个B/S模式系统,网上花店是在MySQL中建立数据表保存信息,运用SSMVue框架和Java语言编写。并按照软件设计开发流程进行设计实现充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得基于SSM的网…...
什么是字节码?
字节码(Bytecode)是Java虚拟机(JVM)能够理解和执行的中间代码。Java源代码首先编译成字节码文件(扩展名为 .class),而不是直接编译成特定机器的机器码。字节码具有以下特点: 平台无…...
C++ JWT的使用
接入sdk需要使用JWT加密参数,做个记录以备后查 #include <iostream> #include <jwt-cpp/jwt.h> int main() { // 设置JWT的密钥(对于HS256) std::string secret_key "your-256-bit-secret"; // 创建一个新的JW…...

SpringBoot内置插件的使用(jackson和lombok)
文章目录 引言I lombok(自动为属性生成构造器)II jacksonsee also引言 idea正式版2021.2.2 已经捆绑安装jackson和lombok插件 I lombok(自动为属性生成构造器) Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。 htt…...

Franz Electron + React 源码启动运行填坑指南
环境要求 安装miniconda python 环境electron/rebuild用得着,miniconda 默认自带的 python 是 3.11 版本,比较新; 安装virsual studio 2019 要把C桌面相关的都安装了,大概需要20G,不要安装到 C 盘,都安装到…...

网络安全法中关于网络信息的保护和监管,有哪些规定?
网络安全法作为我们数字时代的重要法律保障,对于网络信息的保护和监管有着明确且详细的规定。这些规定不仅体现了国家对于网络安全的重视,也为我们每个人在数字世界中提供了坚实的法律屏障。 首先,我们来看一个关于网络运营者主体责任的案例。…...

前端XHR请求数据
axios封装了XHR(XMLHttpRequest) 效果 项目结构 Jakarta EE9,Web项目。 无额外的maven依赖 1、Web页面 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title&…...

利用香港多IP服务器优化网站访问速度的关键策略?
利用香港多IP服务器优化网站访问速度的关键策略? 随着数字化时代的不断发展,网站的全球访问速度成为企业吸引用户、提升竞争力的重要因素。特别对于跨国企业而言,如何确保全球用户都能享受到稳定快速的访问体验显得尤为重要。在这一背景下,…...

如何快速将视频做成二维码?扫描二维码播放视频的制作方法
视频二维码的用途越来越多,比如常见的有产品展示、企业宣传、教程说明、个人展示等都可以生成二维码,通过扫码在手机或者其他设备上预览内容,从而提升其他人获取视频的速度,实现内容的快速分享。 对于有制作视频二维码需求的小伙…...

使用python开发的闭运算调试器
使用python开发的开运算调试器 简介效果代码 简介 用来调试闭运算效果的小工具,滑动条可以控制滤波核的大小,用来查看不同滤波核下的闭运算效果。 效果 代码 import sys from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayou…...

一例Phorpiex僵尸网络变种的分析
概述 这是一例Phorpiex僵尸网络变种,通过NSIS打包,加载恶意dll(Flaminius.dll),读取dat文件(Preoral.dat),在内存解密并解压缩出一个Pe,创建同名傀儡进程并注入。通过可移动存储介质传播&#…...

PDF文件转换为CAD的方法
有时候我们收到一个PDF格式的设计图纸,但还需要进行编辑或修改时,就必须先将PDF文件转换回CAD格式。分享两个将PDF转换回CAD的方法,一个用到在线网站,一个用到PC软件,大家根据情况选择就可以了。 ☞在线CAD网站转换 …...

Java为什么会成为现在主流的编程语言
Java为什么会成为现在的主流语言 前言一、Java语言概述Java是什么为什么大多数人会选择从事Java为什么从事Java的工作者数量从年递减 二、Java语言的特点简单性面向对象分布式(微服务)健壮性安全性体系结构中立可移植性解释型高性能多线程动态性 三、Jav…...

动手学深度学习16 Pytorch神经网络基础
动手学深度学习16 Pytorch神经网络基础 1. 模型构造2. 参数管理1. state_dict()2. normal_() zeros_()3. xavier初始化共享参数的好处 3. 自定义层4. 读写文件net.eval() 评估模式 QA 1. 模型构造 定义隐藏层–模型结构定义前向函数–模型结构的调用 import torch from torch…...

前端无样式id或者class等来定位标签
目录: 1、使用背景2、代码处理 1、使用背景 客户使用我们产品组件,发现替换文件,每次替换都会新增如下的样式,造就样式错乱,是组件的文件,目前临时处理的话就是替换文件时删除新增的样式,但是发…...

机器人工具箱学习(三)
一、动力学方程 机器人的动力学公式描述如下: 式中, τ \boldsymbol{\tau} τ表示关节驱动力矩矢量; q , q ˙ , q \boldsymbol{q} ,\; \dot{\boldsymbol { q }} ,\; \ddot{\boldsymbol { q }} q,q˙,q分别为广义的关节位置、速度和加速…...

华为OD机试 - CPU算力分配(Java 2024 C卷 100分)
华为OD机试 2024C卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷B卷C卷)》。 刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试…...

web前端框架设计第八课-表单控件绑定
web前端框架设计第八课-表单控件绑定 一.预习笔记 1.v-model实现表单数据双向绑定 2.搜索数据的实现 3.全选案例实现1—JQ方法 4.单选案例实现 5.数据级联(二级级联) 6.v-model中的修饰符 二.课堂笔记 三.课后回顾 –行动是治愈恐惧的良药,…...

这三个网站我愿称之为制作答辩PPT的神
很多快要毕业的同学在做答辩PPT的时候总是感觉毫无思路,一窍不通。但这并不是你们的错,对于平时没接触过相关方面,第一次搞答辩PPT的人来说,这是很正常的一件事。一个好的答辩PPT可以根据以下分为以下几部分来写。 1.研究的背景和…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...