在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实现滑动窗口限流
常见限流算法 固定窗口算法 在固定的时间窗口下进行计数,达到阈值就拒绝请求。固定窗口如果在窗口开始就打满阈值,窗口后半部分进入的请求都会拒绝。 滑动窗口算法 在固定窗口的基础上,窗口会随着时间向前推移,可以在时间内平滑控…...
从LED驱动到继电器控制:深入解析NPN与PNP三极管在电路设计中的选型避坑指南
从LED驱动到继电器控制:深入解析NPN与PNP三极管在电路设计中的选型避坑指南 在电子电路设计中,三极管作为基础却关键的元件,其选型直接影响着电路的可靠性和性能。特别是当我们需要驱动LED、继电器或电机等负载时,NPN与PNP三极管的…...
系统提示词工程:构建稳定可控的大语言模型应用实践
1. 项目概述与核心价值 最近在GitHub上看到一个挺有意思的项目,叫 edoardoavenia/chatgpt-system-prompts 。乍一看,这似乎又是一个收集ChatGPT提示词的仓库,但当你真正点进去,花点时间研究一下它的结构和内容,你会发…...
Arduino库持续集成实战:Travis CI自动化编译测试指南
1. 项目概述:为什么Arduino库需要持续集成? 如果你和我一样,维护过几个甚至几十个Arduino库,那你一定对下面这个场景深恶痛绝:你修复了一个库里的Bug,或者添加了一个新功能,满怀信心地提交了代…...
SkillZero:零样本AI智能体的分层规划与工具调用实战解析
1. 项目概述:从“零技能”到“零样本”的智能体进化最近在开源社区里,一个名为“SkillZero”的项目引起了我的注意。它来自浙江大学REAL实验室,名字本身就很有意思——“技能为零”。乍一听,这似乎是个悖论,一个智能体…...
连接池失效——高并发下的隐形杀手
连接池失效——高并发下的隐形杀手 系统挂了 现象:用户打开页面,一直转圈。5分钟后,页面报错。 错误日志: org.apache.tomcat.jdbc.pool.PoolExhaustedException: [http-nio-8080-exec-72] Timeout: Pool empty. Unable to fetch …...
macOS LaunchAgent 开机自启服务配置实战:以 OpenClaw 为例
title: “macOS LaunchAgent 开机自启服务配置实战:以 OpenClaw 为例” tags: macOSLaunchAgent开机自启launchdOpenClaw categories:macOS description: “从原理到实战,详解 macOS LaunchAgent 的配置方法,以 OpenClaw Gateway 和 CLIProx…...
基于RK3568的边缘AIoT实战:多模态行为识别系统设计与优化
1. 项目概述:从赛题到全国一等奖的实战复盘去年,我们团队抱着“试试看”的心态参加了瑞芯微与飞凌嵌入式联合举办的全国大学生嵌入式设计大赛,最终捧回了全国一等奖的奖杯。现在比赛尘埃落定,我想把整个项目从破题、选型、开发到最…...
基于RAG与LLM的智能文献分析工具OpenResearcher:从部署到实战全解析
1. 项目概述:一个为研究者量身打造的AI驱动开源工具箱 如果你是一名科研工作者、学术写手,或者任何需要深度处理文献、进行系统性知识梳理的人,那么你大概率经历过这样的场景:面对海量的PDF文献,手动下载、整理、阅读、…...
不只是安装:在龙芯2k1000LA上为Loongnix配置WiFi、蓝牙与触摸屏驱动的完整流程
龙芯2k1000LA开发板外设驱动深度配置指南:从WiFi到触摸屏的全栈解决方案 在国产化硬件开发领域,龙芯2k1000LA开发板凭借其完全自主的LoongArch架构,正成为物联网和嵌入式设备开发者的重要选择平台。不同于x86架构的"开箱即用"体验&…...
WinAuth加密机制详解:如何保护你的认证密钥安全 [特殊字符]
WinAuth加密机制详解:如何保护你的认证密钥安全 🔐 【免费下载链接】winauth Authenticator on Windows for Battle.net / Steam / Guild Wars 2 / Glyph / Runescape / SWTOR / Bitcoin and digital currency exchanges 项目地址: https://gitcode.co…...
