在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就可以实现流式响应了。
文档中还说当返回值为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 "";}
}
页面
因为我的前端写的不太好,就不展示前端代码了
看结果
能够实现
相关文章:

在springboot中调用openai Api并实现流式响应
之前在《在springboot项目中调用openai API及我遇到的问题》这篇博客中,我实现了在springboot中调用openai接口,但是在这里的返回的信息是一次性全部返回的,如果返回的文字比较多,我们可能需要等很久。 所以需要考虑将请求接口响应…...
C++构造函数重难点解析
一、C构造函数是什么 C的构造函数是一种特殊的成员函数,用于初始化类的对象。它具有与类相同的名称,并且没有返回类型。构造函数在创建对象时自动调用,并且可以执行必要的初始化操作。 二、C构造函数特点 类的构造函数不能被继承,…...

QT day3 作业2.22
思维导图: 作业: 完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到…...

AR汽车行业解决方案系列之2-远程汽修
在汽车行业中,AR技术的应用正悄然改变着整个产业链的运作方式,应用涵盖培训、汽修、汽车售后、PDI交付、质检以及汽车装配等,AR技术为多个环节都带来了前所未有的便利与效率提升。 安宝特AR将以系列推文的形式为读者逐一介绍在汽车行业中安宝…...

每日五道java面试题之spring篇(五)
目录: 第一题. 使用 Spring 有哪些方式?第二题. 什么是Spring IOC 容器?第三题. 控制反转(IoC)有什么作用?第四题. IOC的优点是什么?第五题. BeanFactory 和 ApplicationContext有什么区别? 第一题. 使用 Spring 有哪…...

挑战杯 基于YOLO实现的口罩佩戴检测 - python opemcv 深度学习
文章目录 0 前言1 课题介绍2 算法原理2.1 算法简介2.2 网络架构 3 关键代码4 数据集4.1 安装4.2 打开4.3 选择yolo标注格式4.4 打标签4.5 保存 5 训练6 实现效果6.1 pyqt实现简单GUI6.3 视频识别效果6.4 摄像头实时识别 7 最后 0 前言 🔥 优质竞赛项目系列…...

12. Springboot集成Dubbo3(三)Dubbo-Admin
目录 1、前言 2、安装 2.1、下载Dubbo-admin 2.2、修改配置 2.3、编译前端 2.4、访问 2.5、加载自己的服务 2.6、服务测试 2.7、其他 3、小结 1、前言 Dubbo Admin是用于管理Dubbo服务的基于Web的管理工具。Dubbo Admin提供了一个用户友好的界面,用于在分…...

c语言的数据结构:找环状链表入口处
一起<( ̄︶ ̄)↗[GO!] 1.如何判断一个链表是否有环 思路:设定两个快慢指针fast和slow,fast每次走两个结点,slow每次走一个节点 如果fast指针遇到了Null,那么这个链表没有环,如果fast和slow可以相遇,则代表这个链表有环 代码如下 N:fast先进环,slow后…...

LabVIEW声速测定实验数据处理
LabVIEW声速测定实验数据处理 介绍了一个基于LabVIEW的声速测定实验数据处理系统的应用。该系统利用LabVIEW的强大数据处理和分析能力,通过设计友好的用户界面和高效的算法,有效提高了声速测定实验的数据处理效率和准确性。通过这个案例,可以…...
深入剖析C语言中的段错误:从内存模型到实战调试全方位解析
引言 在C语言编程的世界里,段错误(Segmentation Fault)无疑是最常见的运行时错误之一。它源自程序对内存的非法访问,可能由于数组越界、野指针、悬垂指针、栈溢出等各种原因造成。本篇文章旨在带领读者深入探索C语言中的内存管理…...
1.操作Python入门Python安装和使用教程
1. 命令行与环境 为获取各种设置信息,CPython 解析器会扫描命令行与环境。 CPython 实现细节: 其他实现的命令行方案可能会有所不同。 详见 其他实现。 1.1. 命令行 调用 Python 时,可以指定下列任意选项: python [-bBdEhiIO…...

STM32G030C8T6:定时器1ms中断(以64MHz外部晶振为例)
本专栏记录STM32开发各个功能的详细过程,方便自己后续查看,当然也供正在入门STM32单片机的兄弟们参考; 本小节的目标是,系统主频64 MHZ,采用高速外部晶振,通过定时器3 每秒中断控制 PB9 引脚输出高低电平,从…...

人工智能聊天机器人如何帮助您实现工作与生活的平衡
如何用AI聊天机器人实现高效工作生活平衡 工作与生活平衡是管理个人和职业生活需求和责任的能力。 在当今快节奏和竞争激烈的世界中,工作与生活平衡被视为一个理想的目标。然而,对于忙碌的专业人士来说,实现工作与生活的平衡可能具有挑战性&a…...

3分钟看懂设计模式01:策略模式
一、什么是策略模式 定义一些列算法类,将每一个算法封装起来,并让它们可以互相替换。 策略模式让算法独立于使用它的客户而变化,是一种对象行为型模式。 以上是策略模式的一般定义,属于是课本内容。 在没有真正理解策略模式之…...
数据结构与算法:算法详解
1. 引言 1.1 算法在计算机科学中的地位和重要性 算法是计算机科学的基石,它指导着计算机在解决各种问题时的行为。一个好的算法可以使得问题的解决更加高效、精确和可靠,因此在计算机科学中具有至关重要的地位。 1.2 学习算法的意义和目标 学习算法不…...

AOSP10 替换系统launcher
本文实现将原生的launcher 移除,替换成我们自己写的launcher。 分以下几个步骤: 一、新建一个自己的launcher项目。 1.直接使用android studio 新建一个项目。 2.修改AndroidManifest.xml <applicationandroid:persistent"true"androi…...
视频互动游戏如何暴打海王和舔狗
前言 前2篇文章回答了游戏的可取之处以及不可复制的地方还有对于这一类的情景互动游戏在2024年的发展预言。第三篇主要是回答在一篇中一个留言的读者问的问题“如何暴打海王和舔狗”,求同存异,希望能够跟更多的读者交流与互相学习。 海王和舔狗的特征 …...

大学生多媒体课程学习网站thinkphp+vue
开发语言:php 后端框架:Thinkphp 前端框架:vue.js 服务器:apache 数据库:mysql 运行环境:phpstudy/wamp/xammp等开发背景 (一) 研究课程的提出 (二)学习网站的分类与界定…...

信息系统项目管理师论文分享(质量管理)
水一篇文章。我发现身边考高项的朋友很多都是论文没过,我想着那就把我的论文分享出来,希望能有帮助。 质量管理 摘要 2020年5月,我作为项目经理参加了“某市某医联体的互联网诊疗(互联网医院和远程医疗)平台”的建设…...

Redis实现滑动窗口限流
常见限流算法 固定窗口算法 在固定的时间窗口下进行计数,达到阈值就拒绝请求。固定窗口如果在窗口开始就打满阈值,窗口后半部分进入的请求都会拒绝。 滑动窗口算法 在固定窗口的基础上,窗口会随着时间向前推移,可以在时间内平滑控…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...

Qwen系列之Qwen3解读:最强开源模型的细节拆解
文章目录 1.1分钟快览2.模型架构2.1.Dense模型2.2.MoE模型 3.预训练阶段3.1.数据3.2.训练3.3.评估 4.后训练阶段S1: 长链思维冷启动S2: 推理强化学习S3: 思考模式融合S4: 通用强化学习 5.全家桶中的小模型训练评估评估数据集评估细节评估效果弱智评估和民间Arena 分析展望 如果…...