在springboot中调用openai Api并实现流式响应

之前在《在springboot项目中调用openai API及我遇到的问题》这篇博客中,我实现了在springboot中调用openai接口,但是在这里的返回的信息是一次性全部返回的,如果返回的文字比较多,我们可能需要等很久。 所以需要考虑将请求接口响应方式改为流式响应。

目录

openai api文档

码代码!!!

配置

properties

pom文件

1.请求体类

请求体中的信息类

2.响应类

1)响应体主体类

2)Delta类

常量池类

客户端类

websocket后端配置

1)websocket配置类

2)websocket类

ai消息工具类

页面

看结果


openai api文档

查阅openai的api文档,文档中说我们只需要在请求体中添加"stream":true就可以实现流式响应了。

openai api文档流式响应参数

 文档中还说当返回值为data: [DONE]时,标识响应结束。

码代码!!!

跟之前一样,为了缩减篇幅,set、get、构造器都省略

配置

properties

openai.key=你的keyopenai.chatgtp.model=gpt-3.5-turbo
openai.gpt4.model=gpt-4-turbo-preview
openai.chatgtp.api.url=/v1/chat/completions

pom文件

我们在项目中引入websocket和webflux 之前使用的RestTemplate并不擅长处理异步流式的请求。所以我们改用web flux。

<!--		websocket依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
<!--		流式异步响应客户端--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>

请求体类

public class ChatRequest {// 使用的模型private String model;// 历史对话记录private List<ChatMessage> messages;private Boolean stream = Boolean.TRUE;@Overridepublic String toString() {try {return ConstValuePool.OBJECT_MAPPER.writeValueAsString(this);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}
}

请求体中的信息类

public class ChatMessage {// 角色private String role;// 消息内容private String content;
}

响应类

响应类先看接口的返回格式的示例吧。下面json中的content就是本次响应数据

{"id": "chatcmpl-8uk7ofAZnSJhsHlsQ9mSYwFInuSFq","object": "chat.completion.chunk","created": 1708534364,"model": "gpt-3.5-turbo-0125","system_fingerprint": "fp_cbdb91ce3f","choices": [{"index": 0,"delta": {"content": "吗"},"logprobs": null,"finish_reason": null}]
}

根据json格式,我们构造响应体类如下

1)响应体主体类

public class ChatResponse {private String id;private String object;private Long created;private String model;private String system_fingerprint;// GPT返回的对话列表private List<Choice> choices;public static class Choice {private int index;private Delta delta;private Object logprobs;private Object finish_reason;}
}

2)Delta类

public class Delta {private String role;private String content;
}

常量池类

public class ConstValuePool {// openai代理客户端public static WebClient PROXY_OPENAI_CLIENT = null;
}

客户端类

客户端一样还是在钩子函数中生成。

@Component
public class ApiCodeLoadAware implements EnvironmentAware, ApplicationContextAware {Environment environment;@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {// chatgpt、gpt4HttpClient httpClient = HttpClient.create().proxy(clientProxy ->clientProxy.type(ProxyProvider.Proxy.HTTP) // 设置代理类型.host("127.0.0.1") // 代理主机.port(7890)); // 代理端口ConstValuePool.PROXY_OPENAI_CLIENT = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).baseUrl("https://api.openai.com").defaultHeader("Authorization", "Bearer " + environment.getProperty("openai.key")).build();}
}

websocket后端配置

webscoekt具体可以看我之前的博客使用websocket实现服务端主动发送消息到客户端

1)websocket配置类

@Configuration
public class WebsocketConfig {@Beanpublic ServerEndpointExporter getServerEndpointExporter() {return new ServerEndpointExporter();}}

2)websocket类

这里的参数id是为了区分具体是那个websocket需要推送消息,可以通过登录等方式提供给用户

@Component
@ServerEndpoint("/aiWebsocket/{id}")
public class AiWebsocketService {private final Logger logger = LoggerFactory.getLogger(AiWebsocketService.class);private Session session;//存放所有的websocket连接private static Map<String,AiWebsocketService> aiWebSocketServicesMap = new ConcurrentHashMap<>();//建立websocket连接时自动调用@OnOpenpublic void onOpen(Session session,@PathParam("id") String id){this.session = session;aiWebSocketServicesMap.put(id, this);logger.debug("有新的websocket连接进入,当前连接总数为" + aiWebSocketServicesMap.size());}//关闭websocket连接时自动调用@OnClosepublic void onClose(){aiWebSocketServicesMap.remove(this);logger.debug("连接断开,当前连接总数为" + aiWebSocketServicesMap.size());}//websocket接收到消息时自动调用@OnMessagepublic void onMessage(String message){logger.debug("this:" + message);}//通过websocket发送消息public void sendMessage(String message, String id){AiWebsocketService aiWebsocketService = aiWebSocketServicesMap.get(id);if (aiWebsocketService == null) {return;}try {aiWebsocketService.session.getBasicRemote().sendText(message);} catch (IOException e) {logger.debug(this + "发送消息错误:" + e.getClass() + ":" + e.getMessage());}}}

ai消息工具类

@Component
public class ChatGptModelService implements AiModelService{private static final Logger logger = LoggerFactory.getLogger(ChatGptModelService.class);@Value("${openai.chatgtp.api.url}")private String uri;@Value(("${openai.chatgtp.model}"))private String model;@Resourceprivate AiWebsocketService aiWebsocketService;@Overridepublic String answer(String prompt, HttpServletRequest request) throws InterruptedException {HttpSession session = request.getSession();String identity = AiIdentityFlagUtil.getAiIdentity(request);// 获取历史对话列表,chatMessages实现连续对话、chatDialogues便于页面显示List<ChatMessage> chatMessages = (List<ChatMessage>) session.getAttribute(ConstValuePool.CHAT_MESSAGE_DIALOGUES);List<AiDialogue> chatDialogues = (List<AiDialogue>) session.getAttribute(ConstValuePool.CHAT_DIALOGUES);if (chatMessages == null) {chatMessages = new ArrayList<>();chatMessages.add(ChatMessage.createSystemDialogue("You are a helpful assistant."));chatDialogues = new ArrayList<>();session.setAttribute(ConstValuePool.CHAT_DIALOGUES, chatDialogues);session.setAttribute(ConstValuePool.CHAT_MESSAGE_DIALOGUES, chatMessages);}chatMessages.add(new ChatMessage("user", prompt));chatDialogues.add(AiDialogue.createUserDialogue(prompt));ChatRequest chatRequest = new ChatRequest(this.model, chatMessages);logger.debug("发送的请求为:{}",chatRequest);Flux<String> chatResponseFlux = ConstValuePool.PROXY_OPENAI_CLIENT.post().uri(uri).contentType(MediaType.APPLICATION_JSON).bodyValue(chatRequest.toString()).retrieve().bodyToFlux(String.class);// 得到string返回,便于查看结束标志StringBuilder resultBuilder = new StringBuilder();// 设置同步信号量Semaphore semaphore = new Semaphore(0);chatResponseFlux.subscribe(value -> {logger.debug("返回结果:{}", value);if ("[DONE]".equals(value)) {return;}try {ChatResponse chatResponse = ConstValuePool.OBJECT_MAPPER.readValue(value, ChatResponse.class);List<ChatResponse.Choice> choices = chatResponse.getChoices();ChatResponse.Choice choice = choices.get(choices.size() - 1);Delta delta = choice.getDelta();String res = delta.getContent();if (res != null) {resultBuilder.append(res);aiWebsocketService.sendMessage(resultBuilder.toString(), identity);}} catch (JsonProcessingException e) {throw new AiException("chatgpt运行出错",e);}}, // 获得数据,拼接结果,发送给前端error -> {semaphore.release();throw new AiException("chatpgt执行出错",error);}, // 失败释放信号量,并报错semaphore::release// 成功释放信号量);semaphore.acquire();String resString = resultBuilder.toString();logger.debug(resString);chatDialogues.add(AiDialogue.createAssistantDialogue(resString));chatMessages.add(ChatMessage.createAssistantDialogue(resString));// 对话轮数过多删除最早的历史对话,避免大量消耗tokenswhile (chatMessages.size() > ConstValuePool.CHAT_MAX_MESSAGE) {chatMessages.remove(0);}return "";}
}

页面

因为我的前端写的不太好,就不展示前端代码了

看结果

能够实现 

openai api流式调用结果1

openai api流式调用结果2

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

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

相关文章

QT_day4

1.思维导图 2. 输入闹钟时间格式是小时:分钟 widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);id startTimer(1000);flag1;speecher new QTextT…

nginx 配置文件详细介绍

一&#xff0c; nginx 配置文件架构 上一篇 已对 main 全局配置做了详细介绍 本章对剩下的配置文件部分做介绍 二&#xff0c;event 设置 &#xff08;一&#xff09;event 相关的配置文件为 配置工作模式以及连接数 &#xff08;二&#xff09;具体表现 1&#xff…

c语言经典测试题3

1.题1 int a 248, b 4; int const *c 21; const int *d &a; int *const e &b; int const * const f &a; 请问下列表达式哪些会被编译器禁止&#xff1f; A: *c 32; B: *d 43 C: e&a D: f0x321f 我们来分析一下&#xff1a;const用来修饰变量是想其…

鸿蒙自定义侧滑菜单布局(DrawerLayout)

前言 为了实现安卓中的侧滑菜单布局效果&#xff0c;通过查找鸿蒙的布局控件&#xff0c;发现SideBarContainer控件有点像&#xff0c;但是使用中发现并不是很符合我们的UI交互效果&#xff0c;因此我在鸿蒙中通过自定义布局的方式实现&#xff0c;本文主要介绍该自定义控件如…

kubernetes负载均衡部署

目录 1.新master节点的搭建 对master02进行初始化配置&#xff08;192.168.88.31&#xff09; 将master01的配置移植到master02 修改master02配置文件 2.负载均衡的部署 两台负载均衡器配置nginx 部署keepalived服务 所有node节点操作 总结 实验准备&#xff1a; k8s…

Vue3的computed计算属性和watch监视(四)

一、computed计算属性 <template><div class"person">姓:<input type"text" v-model"first_name"><br>名:<input type"text" v-model"last_name"><br><button click"changeFu…

Linux:Jenkins用户权限和管理

1.下载插件 由于Jenkins的默认权限管理并不是很精细所以我们安装一个插件进行权限的一个管理 插件名称为&#xff1a;Role-based Authorization Strategy 安装完插件我们再去配置一下 进入全局安全配置 选择这个Role-Based Strategy策略然后保存 2.创建角色 我们这里主要使…

【C++私房菜】面向对象中的多重继承以及菱形继承

文章目录 一、多重继承1、多重继承概念2、派生类构造函数和析构函数 二、菱形继承和虚继承2、虚继承后的构造函数和析构函数 三、has-a 与 is-a 一、多重继承 1、多重继承概念 **多重继承&#xff08;multiple inheritance&#xff09;**是指从多个直接基类中产生派生类的能力…

MybatisPlus--03--IService、ServiceImpl

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1. IService接口1.1 IService、ServiceImpl 接口的使用第一步&#xff1a;实现basemapper接口第二步&#xff1a;编写service类第三步&#xff1a;编写serviceImpl第…

开发Chrome插件,background.js中log打印未出现在控制台

不同于内容脚本&#xff08;通常命名content.js&#xff09;&#xff0c;在后台脚本&#xff08;通常命名background.js或service-worker.js&#xff09;中console.log并不会在控制台中直接显示。 要查看后台脚本上下文的正确控制台&#xff0c;执行如下步骤&#xff1a; 访问…

蓝桥杯备赛系列——倒计时50天!

蓝桥杯备赛系列 倒计时50天&#xff01; 前缀和和差分 知识点 **前缀和数组&#xff1a;**假设原数组用a[i]表示&#xff0c;前缀和数组用sum[i]表示&#xff0c;那么sum[i]表示的是原数组前i项之和&#xff0c;注意一般用前缀和数组时&#xff0c;原数组a[i]的有效下标是从…

【Python笔记-设计模式】工厂模式

一、说明 (一) 解决问题 提供了一种方式&#xff0c;在不指定具体类将要创建的情况下&#xff0c;将类的实例化操作延迟到子类中完成。可以实现客户端代码与具体类实现之间的解耦&#xff0c;使得系统更加灵活、可扩展和可维护。 (二) 使用场景 希望复用现有对象来节省系统…

Maven depoly:Skipping artifact deployment

问题描述&#xff1a; 使用IDEA执行mvn depoly将本地开发的模块发布到Maven私服时&#xff0c;一直提示&#xff1a;Skipping artifact deployment&#xff0c;自动跳过了depoly部署阶段。 问题分析 Maven构建生命周期中的每一个阶段都是由对应的maven插件执行具体工作的。既然…

使用IDEA创建spring boot web项目并测试运行

文章目录 准备工作构建项目1、通过Maven Archetype构建一个webapp项目2、添加 Spring Boot 所需依赖3、创建配置文件4、创建启动类5、创建web api 接口6、测试web api接口 准备工作 idea下载地址&#xff1a; https://www.jetbrains.com/idea/download/?sectionwindows java下…

关于torch.cuda.is_available() 返回False 详细说明及解决

一 cuda 环境检测失败 cuda 环境检测代码&#xff1a; import torchprint(torch.__version__) print(torch.cuda.is_available()) print(torch.version.cuda) cuda 环境检测代码执行结果如下图&#xff1a; 关键代码print(torch.cuda.is_available()) 返回 False 通常表示当…

java.lang.IllegalStateException: Promise already completed.

spark submit 提交作业的时候提示Promise already complete 完整日志如下 File "/data5/hadoop/yarn/local/usercache/processuser/appcache/application_1706192609294_136972/container_e41_1706192609294_136972_02_000001/py4j-0.10.6-src.zip/py4j/protocol.py"…

Draw.io绘制UML图教程

一、draw.io介绍 1、draw.io简介 draw.io 是一款强大的免费在线图表绘制工具&#xff0c;支持创建流程图、组织结构图、时序图等多种图表类型。它提供丰富的形状库、强大的文本编辑和样式设置功能&#xff0c;使用户能够轻松创建专业级图表。draw.io 具有用户友好的界面&…

LeetCode 热题 100 | 二叉树(终)

目录 1 二叉树小结 1.1 模式一 1.2 模式二 2 236. 二叉树的最近公共祖先 3 124. 二叉树中的最大路径和 菜鸟做题&#xff08;返校版&#xff09;&#xff0c;语言是 C 1 二叉树小结 菜鸟碎碎念 通过对二叉树的练习&#xff0c;我对 “递归” 有了一些肤浅的理解。…

【激光SLAM】基于滤波的激光SLAM方法(Grid-based)

Filter-based SLAM 贝叶斯滤波数学概念贝叶斯滤波特性贝叶斯滤波的推导 粒子滤波&#xff08;Particle filter&#xff09;特性流程状态传播权重评估重采样算法流程存在的问题 FastSLAM的原理及优化FastSLAM介绍算法流程FastSLAM优化存在的问题及优化进一步优化proposal分布最终…

RabbitMQ学习整理————基于RabbitMQ实现RPC

基于RabbitMQ实现RPC 前言什么是RPCRabbitMQ如何实现RPCRPC简单示例通过Spring AMQP实现RPC 前言 这边参考了RabbitMQ的官网&#xff0c;想整理一篇关于RabbitMQ实现RPC调用的博客&#xff0c;打算把两种实现RPC调用的都整理一下&#xff0c;一个是使用官方提供的一个Java cli…