springboot+vue实现ChatGPT逐字输出打字效果

文章目录

  • 前言
  • 一、效果
  • 二、Springboot后端
    • 1.封装请求OpenAI接口的客户端
    • 2.对话处理
    • 3.对话请求接口
  • 二.Vue前端


前言

在调用OpenAI GPT接口时,如果不使用流式(stream:true)参数,接口会等待所有数据生成完成后一次返回。这个等待时间可能会很长,给用户带来不良体验。

为了提升用户体验,我们需要使用流式调用方式。在这篇文章中,我们将介绍如何使用Spring Boot和Vue对接OpenAI GPT接口,并实现类似ChatGPT逐字输出的效果。


一、效果

体验地址+源码联系我。
PC端
请添加图片描述
移动端
请添加图片描述

二、Springboot后端

1.封装请求OpenAI接口的客户端

官方给的Example request:

curl https://api.openai.com/v1/chat/completions \-H "Content-Type: application/json" \-H "Authorization: Bearer $OPENAI_API_KEY" \-d '{"model": "gpt-3.5-turbo","messages": [{"role": "user", "content": "Hello!"}]}'

根据官方示例,用java封装请求接口的客户端。本文选择使用OkHttpClient作为http请求客户端。
注意:接口调用需要魔法

GtpClient.java

@Component
public class GptClient {private final String COMPLETION_ENDPOINT = "https://api.openai.com/v1/chat/completions";// OpenAI的API key@Value("${gpt.api-key}")private String apiKey;// 魔法服务器地址@Value("${network.proxy-host}")private String proxyHost;// 魔法服务器端口@Value("${network.proxy-port}")private int proxyPort;OkHttpClient client = new OkHttpClient();MediaType mediaType;Request.Builder requestBuilder;@PostConstructprivate void init() {client.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));client.setConnectTimeout(60, TimeUnit.SECONDS);client.setReadTimeout(60, TimeUnit.SECONDS);mediaType = MediaType.parse("application/json; charset=utf-8");requestBuilder = new Request.Builder().url(COMPLETION_ENDPOINT).header("Content-Type", "application/json").header("Authorization", "Bearer " + apiKey);}/*** 聊天接口* @param requestBody 聊天接口请求体* @return 接口请求响应*/public Response chat(ChatRequestBody requestBody) throws ChatException {RequestBody bodyOk = RequestBody.create(mediaType, requestBody.toString());Request requestOk = requestBuilder.post(bodyOk).build();Call call = client.newCall(requestOk);Response response;try {response = call.execute();} catch (IOException e) {throw new ChatException("请求时IO异常: " + e.getMessage());}if (response.isSuccessful()) {return response;}try(ResponseBody body = response.body()) {throw new ChatException("chat api 请求异常, code: " + response.code() + "body: " + body.string());} catch (IOException e) {throw new ChatException("请求后IO异常: " + e.getMessage());}}}

请求体封装
ChatRequestBody .java

@Data
public class ChatRequestBody {private static String model = "gpt-3.5-turbo";private static boolean stream = true;// 对话上下文,详情请看OpenAI接口文档private List<MessageItem> messages;@Overridepublic String toString() {return "{\"model\":\"" + model +"\"," +"\"messages\":" + JSON.toJSONString(messages) + "," +"\"stream\":"+ stream +"}";}
}

2.对话处理

调用OpenAI接口可以看到,Content-Type 为 text/event-stream。
在这里插入图片描述
它指示服务器返回的响应体是一个流式事件的序列。这个响应体通常被用于服务器向客户端推送实时事件,客户端可以通过一个持久连接(HTTP 长轮询)来接收这些事件。
我们后端请求OpenAI接口,接口会通过多次向我们后端发送数据,数据格式如下:

data: {"id": "chatcmpl-7CgfIDnXzGXreE5LbTnM7GFnqd8ZH","object": "chat.completion.chunk","created": 1683258296,"model": "gpt-3.5-turbo-0301","choices": [{"delta": {"content": "你"},"index": 0,"finish_reason": null}]
}

发送的数据都会追加在响应体(Response)中的body(ResponseBody)中, 从中可以获取到InputStream,这就是OpenAI向我们后端发送数据的数据流了。
为了方便获取每行的数据,我们将这个流封装成BufferedReader,使用它的readLine()方法,获取每行数据(见下文中ConverseHandleWrapper.java下的run()方法),每次调用此方法都会得到一行内容(编码好的String,每行内容如上文JSON或换行符,出现换行符的原因是SSE协议导致的),这里每一行内容称之为line。我们循环调用BufferedReader的readLine()方法,即可实时获取到OpenAI接口发送来的每一行数据line。直至获取到null值,表明数据传输完毕。
其实line中的绝大数内容都是无用的,只有choices[0].delta.content字段(上文JSON中的)是我们想要的内容。我们只要这个字段值(即上文JSON中的‘你’)即可。笔者用了java.util.regex.Pattern来匹配这个content字段中的内容:

Pattern contentPattern = Pattern.compile("\"content\":\"(.*?)\"}");
Matcher matcher = contentPattern.matcher(line);
matcher.find();
String content = matcher.group(1); // content就是json中choices[0].delta.content字段的值,即上文JSON中的‘你’

我们再将每个content实时的发送到前端即可,那么如何通过http分批次的实时的将数据发送给前端呢?模仿我们调用的OpenAI的接口就好了。即SSE(Server-Sent Events)事件流连接。SSE 是一种基于 HTTP 的推送技术,它允许服务器在数据准备好时将事件推送到客户端,而不需要客户端发送请求。
SseEmitter 是 Spring 框架提供的一个异步的响应对象,它可以用于向客户端发送 SSE 事件流。当在控制器方法(Controller)中创建一个 SseEmitter 对象并返回它时,Spring MVC 将自动将响应类型设置为 “text/event-stream”,以支持 SSE 事件流协议。例如:

@RestController
public class EventController {@RequestMapping("/events")public SseEmitter handleEvents() {SseEmitter emitter = new SseEmitter();// 在这里设置 SSE 事件流的处理逻辑,例如推送实时事件// 我们可以在这里请求OpenAI接口,实时的获取OpenAI分批次发送来的数据,再通过emitter的send()方法实时发送给前端// 注意:异步处理!!!return emitter;}
}

笔者对对话处理做了封装,前端每次请求对话时,都封装成一个对象,对话的所有处理都在这个对象中进行。对话的所有处理包括用户鉴权、用户状态维护等。

对话处理的封装,内容过长,删除了部分代码,核心代码为run()方法
注意对话的处理需要是异步的,这里将对话处理放到了线程池中处理
ConverseHandleWrapper.java

@Slf4j
public class ConverseHandleWrapper{public static GptClient gptClient;public static RightsManager rightsManager;private static final ExecutorService executorService = Executors.newFixedThreadPool(10);private final static Pattern contentPattern = Pattern.compile("\"content\":\"(.*?)\"}");private final static String EVENT_DATA = "d";private final static String EVENT_ERROR = "e";// 用于数据传输的 SseEmitterprivate final SseEmitter emitter = new SseEmitter(0L);// 用户唯一标识private String userKey;// 对话上下文private List<MessageItem> messageItemList;/*** 向客户端发送数据* @param event 事件类型* @param data 数据*/private boolean sendData2Client(String event, String data) {try {emitter.send(SseEmitter.event().name(event).data("{" + data + "}"));return true;} catch (IOException e) {log.error("向客户端发送消息时出现异常");e.printStackTrace();}return false;}/*** 对话上下文检查* @return 是否通过*/private boolean messageListCheck() {}/*** 对话处理* @return SseEmitter*/public SseEmitter handle() {if (!messageListCheck() || !authenticate()) {return emitter;}rightsManager.lockUserKey(userKey);doConverse();return emitter;}/*** 鉴权* @return 是否通过*/public boolean authenticate() {}/*** 对话,异步的,在新的线程的*/public SseEmitter doConverse() {executorService.execute(this::run);return emitter;}private void run() {ChatRequestBody chatRequestBody = new ChatRequestBody();chatRequestBody.setMessages(messageItemList);Response chatResponse;try {chatResponse = gptClient.chat(chatRequestBody);} catch (ChatException e) {sendData2Client(EVENT_ERROR, "我累垮了");emitter.complete();e.printStackTrace();return;}try (ResponseBody responseBody = chatResponse.body();InputStream inputStream = responseBody.byteStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {String line;while ((line = bufferedReader.readLine()) != null) {if (StringUtils.hasLength(line)) {Matcher matcher = contentPattern.matcher(line);if (matcher.find()) {String content = matcher.group(1);if (!sendData2Client(EVENT_DATA, content)) {break;}}}}} catch (IOException e) {log.error("ResponseBody读取错误");e.printStackTrace();} finally {emitter.complete();// 用户权限相关,可以忽略rightsManager.decrementUsage(userKey);rightsManager.unlockUserKey(userKey);}}
}

通过SseEmitter的send()方法向前端发送事件流时,可以指定事件(event),上述代码中区分了两种事件:e和d。e代表这个事件是错误提示数据,d代表是正常的数据。这样前端可以通过这个字段做出判断。例如以下错误提示效果:
在这里插入图片描述

3.对话请求接口

在2中,将绝大部分的对话处理都封装好了,所以,当前端请求对话时,创建一个ConverseHandleWrapper对象并操作即可。

对话请求接口
ChatController.java

@RestController
@RequestMapping("chat")
public class ChatController {@PostMapping("/converse")public SseEmitter converseEvents(@RequestBody ConverseRequestBody requestBody) {// 封装对话处理ConverseHandleWrapper converseHandleWrapper =new ConverseHandleWrapper(requestBody.getUserKey(), requestBody.getMessageList());return converseHandleWrapper.handle();}
}

ConverseRequestBody.java

@Data
public class ConverseRequestBody {private String userKey;private List<MessageItem> messageList;
}

MessageItem.java

@Data
public class MessageItem {/*** 角色,user,assistant,system*/private String role;/*** 内容*/private String content;
}

二.Vue前端

待更新…

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

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

相关文章

分享收集的ChatGPT专题学习资料~

Datawhale分享 作者&#xff1a;Coggle数据科学 ChatGPT是未来的技术趋势&#xff0c;也拥有非常多的干货的分享&#xff0c;如解析ChatGPT的原理&#xff0c;或者ChatGPT使用指南。 本文将分享整理的干货学习资料&#xff0c;和最新的学习活动。领取方式见文末。 专题学习资料…

关于GPT4.0和6款AI工具

下面6个非常实用的AI工具强烈推荐给大家 1.ChatGPT: 上线两个月&#xff0c;用户数量达到1亿&#xff0c;ChatGPT不但能够写文案&#xff0c;剧本&#xff0c;企划等多种任务&#xff0c;还具有编写和调试计算机程序的能力等诸多功能&#xff0c;它具有3.5,4.0两个版本供账户使…

记录一次使用 Ubuntu 编译 Wakaama 踩坑过程

1. 背景 公司项目需要使用 LwM2M 协议&#xff0c;由于是个相对冷门的小众协议&#xff0c;客户端模拟器几乎没有&#xff0c;只有 Eclipse Wakaama 相对来说功能齐全、易用&#xff0c;但是这个软件没有编译好的程序包&#xff0c;只能从 Github 拉取源码手动编译运行。 不知道…

【人工智能】科大讯飞API接口调用(第一集)

前言 这学期有一门人工智能教育课程&#xff0c;恰巧又有这么一个实践&#xff0c;要求进行人工智能接口调用 于是首选了科大讯飞&#xff0c;下面是详细过程记录 科大讯飞接口调用 以下是流程以及实现细节描述 调用流程 第一步 来到科大讯飞开放平台 http://www.xfyun.…

14个适用于Eclipse IDE的有用(免费)插件

在线Eclipse市场上有1,667个可用的插件和解决方案&#xff01; 在您阅读本文时&#xff0c;可能还有更多。 如果您在官方市场之外冒险&#xff0c;您会发现数百种其他开发的插件都可以与Eclipse IDE兼容。 听起来像是经典的第一世界问题。 太多的插件可供选择。 您将永远不需要…

问答系统(QA)调研

引言 智能问答系统广泛用于回答人们以自然语言形式提出的问题&#xff0c;经典应用场景包括&#xff1a;智能语音交互、在线客服、知识获取、情感类聊天等。根据QA任务&#xff0c;可以将QA大致分为5大类&#xff0c;分别为&#xff1a; 文本问答&#xff08;text-based QA&am…

ChatGPT与码农的机会

之前博客中已经写了很多有关AI在博客编写方面的优势与对未来博客的编写方面的思考。这篇文档我继续分享我在开发中的一个案例和相关的感想。 事件还原 我发现ChatGPT也可以帮助我编写OData&#xff0c;于是我也利用GPT帮助我编程。 OData如何将filter与apply字段联合使用&am…

ChatGPT:竟然精通ENVI IDL、ArcGIS等软件!

目录 01 使用途径 02 使用 01 使用途径 我试了很多网站&#xff0c;包括注册登录、插件、镜像网站&#xff0c;微信机器人&#xff0c;QQ机器人&#xff0c;但是目前这些或多或少都由于OpenAI的限制无法正常使用。所以总的来说需要科学上网并且需要国外手机号&#xff0c;这有…

matplotlib——1. 常用功能汇总

文章目录 1.IDE里的1.1 显示模式&#xff08;plt.ion()和plt.ioff()&#xff09;1.2 backend说明 2. jupyter里的&#xff08;主要是和iwidgets组件结合交互式&#xff09;3 通用的3.0 根据颜色名称获取对应rgb值&#xff08;0~255&#xff09;3.1 cmap3.1.1 cmap绘制CMYK四个通…

大模型综述来了!一文带你理清全球AI巨头的大模型进化史

夕小瑶科技说 原创 作者 | 小戏&#xff0c;Python 如果自己是一个大模型的小白&#xff0c;第一眼看到 GPT、PaLm、LLaMA 这些单词的怪异组合会作何感想&#xff1f;假如再往深里入门&#xff0c;又看到 BERT、BART、RoBERTa、ELMo 这些奇奇怪怪的词一个接一个蹦出来&#x…

Maven依赖传递失效问题解决

Maven依赖传递失效问题解决 背景介绍问题描述解决方式 记一次非常规问题解决: maven依赖传递关联(传递)失效 背景介绍 首先maven工程结构大致是这样 (注意maven仓库 是本地仓库-公司中央仓库-远程仓库, 可能对理解遇到的问题原因和为何那样解决有些帮助): <groupId>com…

利用chatGPT搭配plantUML生成UML图

是不是感觉自己画uml图繁琐复杂没思路&#xff0c;画出来的uml图太简单&#xff1f;那就快来试试利用chatGPT搭配plantUML生成UML图&#xff0c;一键生成&#xff0c;自己调试解放双手&#xff0c;开拓思维&#xff0c;快来试试吧&#xff01; 在chatGPT中生成plantUML代码 如果…

谷歌发文批评苹果:实在是 “拖大家后腿”

1992 年 12 月 3 日&#xff0c;世界上第一条 SMS 消息成功发送&#xff0c;如今已经过去 30 周年。在这个关键节点&#xff0c;谷歌再度发文赞美 RCS 短信标准&#xff0c;并批评苹果迟迟不采用该标。 RCS 全称 Rich Communication Suite - 富媒体通信标准&#xff0c;属于高…

库克「豪赌」MR:七年磨一剑,不行也得行

苹果MR是一款在重重阻力之下、由库克强行拍板推向市场的产品。 作者&#xff1a;常嘉帅 “你们看懂了吗&#xff08;Are you getting it&#xff09;?”乔布斯这样问&#xff0c;“一台音乐播放器&#xff0c;一部手机&#xff0c;和一部互联网通讯设备。” 乔布斯以他魅力十足…

我问了10个博客专家好友,原来他们都在用这些高效率软件

大家好&#xff0c;我是记得诚。 在CSDN上我认识了很多大佬级别的博客专家&#xff0c;最近询问了我的10个博客专家好友&#xff0c;向他们取经&#xff0c;扒出了他们珍藏的高效率软件&#xff0c;很多软件我用完之后&#xff0c;当场发出了OMG的尖叫&#xff0c;我重度使用了…

何同学采访苹果CEO库克上热搜,网友表示自愧不如

昨日&#xff0c;22岁B站知名UP主“老师好我叫何同学”采访了苹果CEO蒂姆-库克&#xff0c;登上微博热搜榜&#xff0c;引起了网友们的热议。 何同学是北京邮电大学学霸&#xff0c;22岁的大学生&#xff0c;出于兴趣的原因在B站注册账号发布数码产品的测评视频&#xff0c;他做…

“AI教父”李开复筹办新公司,掘金 AI 2.0 三个大方向

图片&#xff1a;由无界AI画图工具生成 3月19日&#xff0c;创新工场董事长兼CEO李开复在朋友圈宣布&#xff0c;正在筹组一个名为“Project Al 2.0”的新公司&#xff0c;一个旨在探索AI2.0的创新项目。 图片&#xff1a;李开复在朋友圈紧急“摇人”PK chatGPT 目前&#xff0…

谁能真正替代你? AI编码工具深度对比 (chatGPT/Copilot/Cursor/New Bing)

写在开头 这几个月AI相关新闻的火爆程度大家都已经看见了&#xff0c;作为一个被裹挟在AI时代浪潮中的程序员&#xff0c;在这几个月里我也是异常兴奋和焦虑。甚至都兴奋的不想拖更了。不仅仅兴奋于AI对于我们生产力的全面提升&#xff0c;也焦虑于Copilot等AI辅助编码工具&…

【ChatGPT】《ChatGPT 算法原理与实战》1: 引言:从 CNN、RNN 到 Transformers 架构、自注意力机制(图文+数学公式+代码实例详解)

文章目录 1: 引言:从 CNN、RNN 到 Transformers自然语言处理的挑战传统方法的限制Recurrent neural networks | 循环神经网络How RNN works : RNN 的工作原理RNN 的数学模型最新研究发展:RNN、LSTM等Transformers的出现GPT和ChatGPT2: 基本概念编码器解码器训练 Transformer …

解放创作生产力:30+实用AI工具汇总

除了ChatGPT&#xff0c;还有哪些好用AI工具&#xff1f; 带着这个问题&#xff0c;也为了解AIGC已经在哪些场景落地&#xff0c;我体验了30多个AI工具并且分享出来&#xff0c;希望对你有帮助。 文字 ChatGPT —— 解决任何问题 地址&#xff1a;https://chat.openai.com/ch…