百度文心一言 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>

最后的呈现效果如下:

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

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

相关文章

品牌银饰售卖|基于SSM+vue的品牌银饰售卖平台的设计与实现(源码+数据库+文档)

品牌银饰售卖平台 目录 基于SSM&#xff0b;vue的品牌银饰售卖平台的设计与实现 一、前言 二、系统设计 三、系统功能设计 1前台功能模块 2后台功能模块 5.2.1管理员功能模块 5.2.2用户功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题…

ansible——playbook

一、playbook定义 Ansible Playbook是设定自动化任务的一种蓝图&#xff0c;可在无需人工干预或有限干预的前提下执行复杂的IT操作。Ansible Playbook对一组或一类共同构成 Ansible 清单的主机执行。 Ansible Playbook本质上是一些框架&#xff0c;是一些预先编写的代码&#x…

Linux —— 线程控制

Linux —— 线程控制 创建多个线程线程的优缺点优点缺点 pthread_self进程和线程的关系pthread_exit 线程等待pthread_ join线程的返回值线程分离pthread_detach 线程取消pthread_cancel pthread_t 的理解 我们今天接着来学习线程&#xff1a; 创建多个线程 我们可以结合以前…

地平线X3开发板Swap使用说明

准备好旭日X3派、连接好网络 并验证系统版本 rootubuntu:~# cat /etc/version x3j3_lnx_db_20220622 输入以下指令系统更新&#xff08;其他版本同理&#xff0c;依赖项报错可至资源中心更新系统镜像或输入命令解决&#xff09;&#xff1a; apt update apt upgrade reboo…

将jar打包成exe可安装程序,并在html页面唤醒

一、exe4j将jar打包成exe 1.exe4j下载 下载地址&#xff1a;https://www.ej-technologies.com/download/exe4j/files 2.exe4j打包jar 2.1. welcome 可以选择历史配置&#xff0c;新增则直接下一步 2.2. project type选择“jar in exe” mode 2.3. application info设置应用…

springboot引入第三方jar包本地lib并打包

1&#xff1a;在项目根目录创建lib目录并放入第三方lib包 -- project ----lib &#xff08;放在这儿&#xff09; ----src ----target2&#xff1a;pom中引入第三方lib <!-- 引入magus模块 --><dependency><groupId>org.jeecg.msgus</groupId><art…

PADS:生成自交叉平面区域

根据板外形铺铜方法&#xff1a; pads根据板外形铺铜_铺铜如何根据板子形状改变-CSDN博客 根据板外形创建平面区域出现问题&#xff1a; 解决方法&#xff1a;去找结构&#xff0c;让他把出图之前把线合并了

[自动化]pyautogui的使用

目录 环境 包的版本 前置知识 鼠标控制函数 屏幕与鼠标位置 size() position() OnScreen() 鼠标移动 moveTo() move() 鼠标拖动 dragTo() drag() mouseDown()按下鼠标 mouseUp()松开鼠标 鼠标滚动 scroll() 键盘控制函数 write() press() keyDown()和keyU…

等保2.0的全面解读与实施策略

《网络安全等级保护基本要求》&#xff08;等保2.0&#xff09;是中华人民共和国国家安全部于2019年6月发布的网络安全等级保护标准。该标准规定了我国关键信息基础设施的网络安全等级保护要求和评估标准&#xff0c;对于保障我国网络安全具有重要的意义。下面是对等保2.0的全面…

【数据结构】数据结构大汇总 {数据结构的分类总结:定义和特性、实现方式、操作与复杂度、适用场景、相关算法、应用实例}

一、线性结构 1.1 顺序表 定义和特性&#xff1a;顺序表是一种线性表的存储结构&#xff0c;它采用一段地址连续的存储单元依次存储线性表中的元素。顺序表具有随机访问的特性&#xff0c;即可以通过元素的下标直接访问元素。 实现方式&#xff1a;顺序表可以通过数组来实现&…

数据治理框架下,如何实现高效且安全的数据提取与分析

一、引言 随着数字化时代的到来&#xff0c;数据已成为企业运营和决策的核心资产。然而&#xff0c;数据的复杂性和多样性也为企业带来了数据提取与分析的挑战。为了实现数据的有效利用&#xff0c;并确保数据的安全性&#xff0c;需要在数据治理框架下构建高效且安全的数据提…

八大设计模式:适配器模式实现栈的泛型编程 | 通用数据结构接口的秘诀(文末送书)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…