SpringBoot仿GPT数据流传输

目录

  • Java数据流传输响应
    • 前提
    • Springboot文字流响应
    • Web端接收流数据并显示
  • SpingBoot集成ChatGPT使用流响应结果

Java数据流传输响应

前提

在折腾ChatGpt集成在SpringBoot项目时,发现了ChatGpt api返回数据时有两种返回方式,一种是使用流传输,另一种是直接返回全部的数据。如果使用流传输,响应的速度很快,不需要获取全部答案的内容后再开始响应返回,可以达到服务端返回数据时像打字机一样的效果返回答案;而直接返回全部数据的话,需要在服务端接收完ChatGpt的全部结果后再一次性把全部的数据响应返回给客户端进行展示,这个缺点就是很慢,一个结果最快也需要10秒钟。所以本文尝试模仿ChatGpt使用流数据的方式返回数据给客户端。

Springboot文字流响应

首先再服务端测试使用流响应固定的文本字符串数据
主要方法是使用HttpServletResponse响应流,需要设置响应头如下:

res.setHeader("Content-Type", "text/event-stream");
res.setContentType("text/event-stream");
res.setCharacterEncoding("UTF-8");
res.setHeader("Pragma", "no-cache");

测试接口如下:

// 测试响应流@GetMapping("/api/test/sss")@AnonymousAccesspublic void test(String prompt, HttpServletResponse res) throws IOException, InterruptedException {log.info("【prompt内容】:{}", prompt);String str = "       什么是爱而不得? \n" +"东边日出西边雨,道是无晴却有晴。\n" +"他朝若是同淋雪,此生也算共白头。\n" +"我本将心向明月,奈何明月照沟渠。\n" +"此时相望不相闻,愿逐月华流照君。\n" +"衣带渐宽终不悔,为伊消得人憔悴。\n" +"此情可待成追忆,只是当时已惘然。\n" +"人生若只如初见,何事西风悲画扇。\n" +"曾经沧海难为水,除却巫山不是云。\n" +"何当共剪西窗烛,却话巴山夜雨时。\n" +"天长地久有时尽,此恨绵绵无绝期。\n" +"\n";// 响应流res.setHeader("Content-Type", "text/event-stream");res.setContentType("text/event-stream");res.setCharacterEncoding("UTF-8");res.setHeader("Pragma", "no-cache");ServletOutputStream out = null;try {out = res.getOutputStream();for (int i = 0; i < str.length(); i++) {out.write(String.valueOf(str.charAt(i)).getBytes());// 更新数据流out.flush();Thread.sleep(100);}} catch (IOException e) {e.printStackTrace();} finally {try {if (out != null) out.close();} catch (IOException e) {e.printStackTrace();}}}

使用该接口,返回的数据就需要使用流来接受处理
如果直接再浏览器中请求该接口,效果如下:
在这里插入图片描述

Web端接收流数据并显示

在js中接收该文字流数据需要设置响应类型为:xhr.setRequestHeader("Content-Type", "text/event-stream");

具体实现数据接收代码如下:

        // 创建 XMLHttpRequest 对象const xhr = new XMLHttpRequest();// 设置请求的 URLxhr.open("GET",`http://localhost:8080/api/test/sss`);// 设置响应类型为 text/event-streamxhr.setRequestHeader("Content-Type", "text/event-stream");// 监听 readyStateChange 事件xhr.onreadystatechange = () => {// 如果 readyState 是 3,表示正在接收数据if (xhr.readyState === 3) {// 将数据添加到文本框中console.log('xhr.responseText :>> ', xhr.responseText);reply("images/touxiang.png", xhr.responseText, randomStr)var height = $("#message").height();$("html").scrollTop(height)}};// 发送请求xhr.send();

效果如下:

在这里插入图片描述

这种效果就实现了ChatGpt的那种流传输的效果。

SpingBoot集成ChatGPT使用流响应结果

具体集成ChatGPT SDK的教程可以查看官方文档:https://gitcode.net/mirrors/grt1228/chatgpt-java

导入pom依赖:

<dependency><groupId>com.unfbx</groupId><artifactId>chatgpt-java</artifactId><version>1.0.13</version>
</dependency>

使用ChatGpt流传输的demo示例可以查看:https://gitcode.net/mirrors/grt1228/chatgpt-java/blob/main/src/test/java/com/unfbx/chatgpt/OpenAiStreamClientTest.java

官方Demo SDK连接ChatGPT的教程很详细,具体教程看上面的demo文档就好了,这里主要讲一下拿到数据的细节和怎么把拿到的流数据响应给客户端。

下面是SDK调用的示例方法

 public static void ChatGptSendV1(String prompt, ChatSocketVo chatSocketVo) throws IOException {OpenAiConfig openAiConfig = new OpenAiConfig();OpenAiStreamClient openAiClient = OpenAiStreamClient.builder().apiKey(Collections.singletonList(openAiConfig.getTkoen()))//自己做了代理就传代理地址,没有可不不传.apiHost(openAiConfig.getDomain()).build();//聊天模型:gpt-3.5ConsoleEventSourceListener eventSourceListener = new ConsoleEventSourceListener();Message message = Message.builder().role(Message.Role.USER).content(prompt).build();ChatCompletion chatCompletion = ChatCompletion.builder().model(ChatCompletion.Model.GPT_3_5_TURBO.getName()).temperature(0.2).maxTokens(2048).messages(Arrays.asList(message)).stream(true).build();openAiClient.streamChatCompletion(chatCompletion, eventSourceListener);CountDownLatch countDownLatch = new CountDownLatch(1);try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}}

在代码中消息的回调处理方法是:ConsoleEventSourceListener eventSourceListener = new ConsoleEventSourceListener();.
openAiClient.streamChatCompletion(chatCompletion, eventSourceListener);调用流传输的方法中,传入了一个SSE的EventSourceListener。其中的代码如下:

package com.unfbx.chatgpt.sse;import java.util.Objects;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class ConsoleEventSourceListener extends EventSourceListener {private static final Logger log = LoggerFactory.getLogger(ConsoleEventSourceListener.class);public ConsoleEventSourceListener() {}public void onOpen(EventSource eventSource, Response response) {log.info("OpenAI建立sse连接...");}public void onEvent(EventSource eventSource, String id, String type, String data) {log.info("OpenAI返回数据:{}", data);if (data.equals("[DONE]")) {log.info("OpenAI返回数据结束了");}}public void onClosed(EventSource eventSource) {log.info("OpenAI关闭sse连接...");}public void onFailure(EventSource eventSource, Throwable t, Response response) {try {if (Objects.isNull(response)) {log.error("OpenAI  sse连接异常:{}", t);eventSource.cancel();} else {ResponseBody body = response.body();if (Objects.nonNull(body)) {log.error("OpenAI  sse连接异常data:{},异常:{}", body.string(), t);} else {log.error("OpenAI  sse连接异常data:{},异常:{}", response, t);}eventSource.cancel();}} catch (Throwable var5) {throw var5;}}
}

很容易看出OpenAI回调流消息的处理方式是建立了sse连接,然而这个sse连接和WebSocket的使用很相似,在onEvent方法中data就是ai回答的消息内容。只是该默认的消息输出只做了日志的输出。
所以我们可以这样处理:

  1. 自己新建一个类集成EventSourceListener,模仿上面的ConsoleEventSourceListener,重写对应的结果建立连接,返回数据,关闭连接等方法。
  2. 在EventSourceListener类的构造函数中可以传入你需要的场景值等,比如websocket的session,然后每次接收到消息时,立马使用websoket将消息发送到客户端。
  3. 注意全局参数的多线程安全问题,由于建立的是长连接,构造参数传进的场景值必然需要当作全局变量进行定义,但是如果在多人同时使用改接口时,场景值就会错乱出现线程安全问题。解决方法可以在定义全局变量时加上@Autowired注解,原理可以参考其他教程。

自定义EventSourceListener代码示例如下(加入了一些消息记录的处理逻辑):

package com.team.modules.system.Utils;import java.util.Date;
import java.util.Objects;import com.fasterxml.jackson.databind.ObjectMapper;
import com.team.modules.system.domain.ChatgptInfo;
import com.team.modules.system.domain.vo.ChatSocketVo;
import com.team.modules.system.domain.vo.IpDataVo;
import com.team.modules.websocket.WebSocketChatServe;
import com.team.utils.CDKeyUtil;
import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
import lombok.SneakyThrows;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;/*** Created by tao.* Date: 2023/6/8 15:51* 描述:*/
public class ChatEventSourceListener extends EventSourceListener {private static final Logger log = LoggerFactory.getLogger(com.unfbx.chatgpt.sse.ConsoleEventSourceListener.class);private static WebSocketChatServe webSocketChatServe = new WebSocketChatServe();@Autowiredprivate String resContent;@Autowiredprivate ChatSocketVo chatSocketVo;public ChatEventSourceListener(ChatSocketVo socketVo) {chatSocketVo = socketVo;resContent = CDKeyUtil.getSequence() + ":::";}public void onOpen(EventSource eventSource, Response response) {log.info("OpenAI建立sse连接...");}int i = 0;@SneakyThrowspublic void onEvent(EventSource eventSource, String id, String type, String data) {// OpenAI处理数据
//        log.info(i + "---------OpenAI返回数据:{}", data);
//        i++;if (!data.equals("[DONE]")) {ObjectMapper mapper = new ObjectMapper();ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取JsonString content = mapper.writeValueAsString(completionResponse.getChoices().get(0).getDelta());resContent = resContent + content;// 使用ws进行数据传输webSocketChatServe.sendMessageByKey(resContent, chatSocketVo.getKey());} else {log.info("OpenAI返回数据结束了");String[] split = resContent.split(":::");resContent = CDKeyUtil.getSequence() + ":::";// 记录内容和ip信息等String ip = chatSocketVo.getIpAddr();String ua = chatSocketVo.getUa();// 获取相关信息IpDataVo ipData = chatSocketVo.getIpDataVo();String address = ipData.getCountry() + " " + ipData.getProvince() + " " + ipData.getCity() + " " + ipData.getDistrict();
//            String address = "";ChatgptInfo chatgptInfo = new ChatgptInfo(chatSocketVo.getPrompt(), split[1], ip, address, ua, new Date(), ipData.getLocation());chatSocketVo.getChatgptService().save(chatgptInfo);}}public void onClosed(EventSource eventSource) {log.info("OpenAI关闭sse连接...");}@SneakyThrowspublic void onFailure(EventSource eventSource, Throwable t, Response response) {try {if (Objects.isNull(response)) {log.error("OpenAI  sse连接异常:{}", t);eventSource.cancel();} else {ResponseBody body = response.body();if (Objects.nonNull(body)) {log.error("OpenAI  sse连接异常data:{},异常:{}", body.string(), t);} else {log.error("OpenAI  sse连接异常data:{},异常:{}", response, t);}eventSource.cancel();}} catch (Throwable var5) {throw var5;}}
}

效果如下:
在这里插入图片描述

当然响应返回数据的方式也可以使用文章开头介绍的使用响应流进行,缺点是还是得去规避线程安全问题,可以加一个@Async注解;然后尝试过在本地用该方法没发现有什么问题,但是部署在服务器发现该方法就行不通了,会等所有数据全部返回才将数据返回。

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

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

相关文章

【SpringBoot】SpringBoot整合Nginx的全部流程

SpringBoot整合Nginx的全部流程 对Nginx还不了解的同学可以先看这篇文章Nginx 相关介绍(Nginx是什么?能干嘛?) 今天的目标是将SpringBoot项目由默认部署方式(jar)替换成war形式&#xff0c;部署在同一台电脑上的两个不同端口的tomcat上&#xff0c;利用Nginx做反向代理&…

Excel数据动态看板制作:数据处理、数据分析、看板制作、插入切片器、图表类型

Excel数据动态看板制作-以教师薪酬统计为例 一、数据处理二、数据分析三、看板制作四、插入切片器五、图表类型 原始数据如图所示&#xff1a; 一、数据处理 1、工龄计算&#xff1a;DATEDIF(G3,TODAY(),“Y”) 2、工龄工资计算&#xff1a;IF(H350>500,500,H350) 3、…

网页在线编辑表格|仿Excel|特定表头后超级爽

最近公司开发的EMIS系统有个模块需要按excel格式写&#xff0c;原先有个estartable插件&#xff0c;我们经理写的&#xff0c;在原来的模块上面很好用&#xff0c;由于我水平有限&#xff0c;我在短期内不能清晰的修改或扩展它&#xff0c;最近掌握了angularJS&#xff0c;突发…

UiPath中表格排序与筛选的运用

1&#xff0c;首先准备好一个表&#xff0c;例如有以下一张成绩表&#xff1a; 2&#xff0c;排序 &#xff08;1&#xff09;,根据总分来降序排序 如果你不知道表格的名称的话就在Excel表中找到开发工具里的表设计&#xff0c;跟着步骤来就可以找到了。 &#xff08;2&#x…

【炫酷EXCEL】可视化分析动态看板

本文章最终展示效果图 简单EXCEL可视化面板效果展示 利用EXCEL的透视表、切片器和数据透视图实现简单可视化数据分析 数据透视表 鼠标选中自己表格随便一个单元格→点击主菜单栏→插入→数据透视表 选中数据分析中的框选部分&#xff0c;可以进行编辑操作&#xff0c;选择你自…

excel图片技巧:如何为报表配上节日祝福动画

偶尔跳跃一下&#xff0c;改变一下&#xff0c;哪怕被说成是“拍马屁”也行&#xff0c;因为&#xff0c;快乐、传递快乐是一种幸福&#xff0c;是内心本身就有的欲望。提升自己在同事和领导心里的形象只是传递快乐的附加值。 圣诞节就快到了&#xff0c;发送报表的时候附带一个…

使用Excel制作公众号数据看板

为监控公众号日常数据&#xff0c;制作昨日公众号关键指标数据看板。 键入标题&#xff1a;公众号昨日关键指标&#xff1b; 使用VLOOKUP函数查找出昨日新关注人数、取消关注人数、净关注用户、累计用户数&#xff08;包含取消关注&#xff09;&#xff1b; 函数&#xff1a;V…

Web开发中数据表格常见的7类筛选设计

文章转载自&#xff1a;http://www.woshipm.com/pd/653433.html 目录 1、以搜索的形式进行筛选 2、标签加搜索 3、搜索加高级选项 4、tab的形式切换 5、list筛选 6、全部显示筛选 7、带标签多项筛选 结语 1、以搜索的形式进行筛选 输入框可以输入用户关心的内容&#…

推荐多款好看的报表图表配色方案(转载)

好看的图表离不开配色&#xff0c;好看的PPT离不开配色&#xff0c;好看的大屏可视化分析更离不开配色。 博主平时也要做一些数据可视化分析的大屏&#xff0c;一般都需要对背景、图表、数据列表等区域进行配色&#xff0c;根据美工那边的配色推荐&#xff0c;博主整理了一下平…

Vue之功能全面的表格(三)筛选表格中的数据

文章目录 学习计划状态过滤学习完成时间过滤搜索框过滤小结 学习计划状态过滤 1、对学习计划状态列进行美化 data () {return {data: [],filterType: ,statuses: [未开始, 进行中, 搁置, 完成], // 修改statusColors: [info, primary, warning, success] // 新…

ChatGPT时代:我们可能站到了自然语言编程的大门口

ChatGPT大火&#xff0c;我现在有种感觉&#xff1a;我们可能站到了自然语言编程的门口&#xff0c;一脚下去&#xff0c;也许能把门踹开。 当然&#xff0c;也可能会踢到一块铁板。 回顾我们的编程之路&#xff0c;基本上就是一个编程门槛不断降低的历史。 最早的一批前辈们…

ChatGPT对我们的影响-ChatGPT能给我们带来什么

ChatGPT日常应用 ChatGPT是一种应用广泛的自然语言处理算法&#xff0c;其可以应用于多种日常场景。以下是一些ChatGPT的日常应用&#xff1a; 聊天机器人&#xff1a;ChatGPT可用于构建聊天机器人&#xff0c;通过与用户进行自然语言交互来提供个性化的服务和支持。 新闻稿和…

申论范文:共同富裕“一定”会考的点

共同富裕是社会主义的本质要求&#xff0c;是中国式现代化的重要特征&#xff0c;当然也是公务员考试的热点&#xff0c;需要我们认真学习掌握。 今天&#xff0c;我们就用一篇申论范文&#xff0c;一起试试。 ⭐️ ⭐️ ⭐️ ⭐️ ⭐️ ⭐️ 这里是公考隔壁班王老师独创的“…

这碗申论鸡汤,干了

如题&#xff0c;大家周末快乐~ ⭐️ ⭐️ ⭐️ ⭐️ ⭐️ ⭐️ 这里是公考隔壁班王老师独创的“每天半小时&#xff65;申论80分”抄写团&#xff0c;欢迎加入我们&#xff01;

申论小题赏析

第一段 第一段很明显是一个话题引入的段落&#xff0c;所以不用去管它 第二段 是绿色革命的话题引入&#xff0c;点出煤炭在建设绿色革命的过程中并不是包袱&#xff0c;通过煤矿改扩改建的过程中既满足了绿色城市的建设也满足了生产发展的需求&#xff0c;带动了经济的发展…

申论基础知识1

文章目录 前言第一章&#xff1a;审题第二章&#xff1a;单一题一、问题二、影响二、影响三、对策概括对策提出对策 前言 第一章&#xff1a;审题 第二章&#xff1a;单一题 一、问题 二、影响 二、影响 三、对策 概括对策 提出对策

计算机考试怎么考申论,公务员考试申论评分标准,这些你都知道吗?

原标题&#xff1a;公务员考试申论评分标准&#xff0c;这些你都知道吗&#xff1f; 距离2019年多省公务员考试还剩两三天时间&#xff0c;很多考生对公务员考试的阅卷规则和答题格式并不清楚&#xff0c;其实了解阅卷规则和评分标准&#xff0c;对大家答题更有帮助。下面华图教…

粉笔公考——错题集——申论

做题tips 综合 面向考试&#xff0c;多做题、多积累。 提升答题能力。尽量用原文表述。回归材料。针对问题&#xff0c;全面分析材料。反面材料也要概括&#xff0c;反面正说。提炼观点。归纳总结。推导要把握好度&#xff0c;精准、切合材料。 小题 摘抄概括归纳分析推导。…

申论~~~

方法精讲—申论 摘抄&#xff1a; [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BbhvGncP-1666248488455)(…/…/AppData/Roaming/Typora/typora-user-images/image-20211004205502958.png)] 归纳概括&#xff1a; 分析推导&#xff1a; [外链图…

计算机考试怎么考申论,申论高分卷是怎么来的?申论阅卷流程大揭秘

申论的阅卷方式与行测的阅卷方式不同&#xff0c;行测全部为客观题&#xff0c;因此可以采用机读阅卷的方式&#xff0c;而申论全部为主观性试题&#xff0c;必须由专业阅卷人员进行阅卷。正因为如此&#xff0c;很多考生就会担心阅卷人的主观喜好会影响其评分标准。对此&#…