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

news2025/1/18 21:03:21

之前在《在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=你的key

openai.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;


    @Override
    public 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;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // chatgpt、gpt4
        HttpClient 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 {
    @Bean
    public 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连接时自动调用
    @OnOpen
    public void onOpen(Session session,@PathParam("id") String id){
        this.session = session;
        aiWebSocketServicesMap.put(id, this);
        logger.debug("有新的websocket连接进入,当前连接总数为" + aiWebSocketServicesMap.size());
    }

    //关闭websocket连接时自动调用
    @OnClose
    public void onClose(){
        aiWebSocketServicesMap.remove(this);
        logger.debug("连接断开,当前连接总数为" + aiWebSocketServicesMap.size());
    }

    //websocket接收到消息时自动调用
    @OnMessage
    public 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;

    @Resource
    private AiWebsocketService aiWebsocketService;

    @Override
    public 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));

        // 对话轮数过多删除最早的历史对话,避免大量消耗tokens
        while (chatMessages.size() > ConstValuePool.CHAT_MAX_MESSAGE) {
            chatMessages.remove(0);
        }

        return "";
    }
}

页面

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

看结果

能够实现 

openai api流式调用结果1

openai api流式调用结果2

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

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

相关文章

LeetCode--代码详解 235.二叉搜索树得最近公共祖先

235.二叉搜索树得最近公共祖先 题目 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可…

【架构】面向人工智能 (AI) 的硬件的可靠性(2021)

由于激进的技术扩展&#xff0c;现代系统越来越容易受到可靠性威胁的影响&#xff0c;例如软错误、老化和工艺变化。这些威胁在硬件级别表现为位翻转&#xff0c;并且根据位置&#xff0c;可能会损坏输出&#xff0c;从而导致不准确或潜在的灾难性结果。 传统的缓解技术基于冗…

计算机网络Day03--物理层

信道复用技术 频分复用 时分复用 统计时分复用 频分复用&#xff08;FDM&#xff09; 最基本 将整个宽带分为多份&#xff0c;用户在分配到一定的频带后&#xff0c;在通信过程中自始至终都使用这个频带 所有的用户在同一时间占用不同的带宽资源&#xff0c;以并行的方式工…

一文带你彻底搞懂 Python 编程进阶之闭包

什么是闭包&#xff1a;在函数嵌套的情况下&#xff0c;内部的函数使用外部函数中的变量&#xff0c;并且外部函数返回了内部函数&#xff0c;我们将这个内部函数称之为闭包。 闭包是实现装饰器的基础&#xff0c;通过装饰器可以在不修改原函数代码的情况下增强其功能。 在Py…

JDK10新特性:探索Java10的编程新境界

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

nodejs+vue+ElementUi废品废弃资源回收系统

系统主要是以后台管理员管理为主。管理员需要先登录系统然后才可以使用本系统&#xff0c;管理员可以对系统用户管理、用户信息管理、回收站点管理、站点分类管理、站点分类管理、留言板管理、系统管理进行添加、查询、修改、删除&#xff0c;以保障废弃资源回收系统系统的正常…

异步框架Celery在Django中的运用

参考博客&#xff1a;https://www.cnblogs.com/pyedu/p/12461819.html 参考视频&#xff1a;01 celery的工作机制_哔哩哔哩_bilibili 定义&#xff1a;简单灵活、处理大量消息的分布式系统&#xff0c;专注于实时处理异步队列&#xff0c;支持任务调度 主要架构&#xff1a; …

YOLOv7基础 | 第2种方式:简化网络结构之yolov7.yaml(由104层简化为30层)

前言:Hello大家好,我是小哥谈。通过下载YOLOv7源码可知,原始的yolov7.yaml文件是拆开写的,比较混乱,也不好理解,并且为后续改进增添了很多困难。基于此种情况,笔者就给大家介绍一种将yolov7.yaml文件简化的方法,将104层简化为30层,并且参数量和计算量和原来是一致的,…

RK3568平台开发系列讲解(Linux系统篇)字符设备驱动:分配和注册字符设备

🚀返回专栏总目录 文章目录 一、分配和注册字符设备二、file_operations沉淀、分享、成长,让自己和他人都能有所收获!😄 一、分配和注册字符设备 字符设备在内核中表示为struct cdev的实例。在编写字符设备驱动程序时,目标是最终创建并注册与struct file_operations关联…

线程池的常用实现及执行流程

线程池 线程池线程池接口线程池参数线程池分类动态数目线程池固定数目线程池单例线程池任务调度线程池 线程池的执行流程 线程池 线程池接口 线程池参数 1、corePoolSize&#xff1a;核心线程数&#xff0c;线程池中最少线程&#xff0c;核心线程不会被回收。 2、maximumPoo…

Edting While Playing 瓦片地图编辑器开发整合导入自定义贴图 DEVC++ VS2022都可复制粘贴运行

接 多种类型图片模块读取-CSDN博客 与 Editing While Playing 使用 Easyx 开发的 RPG 地图编辑器 tilemap eaitor-CSDN博客 整合实现平面贴图纹理自定义 操作同上 导入步骤&#xff1a; 先运行程序&#xff0c;然后关闭&#xff0c;同目录下有四个文件夹&#xff0c; 把…

家政小程序有哪些功能 怎么制作

随着人们生活节奏的加快&#xff0c;家政服务变得越来越受到人们的青睐。为了提升家政服务的便捷性和高效性&#xff0c;家政小程序成为了越来越受欢迎的选择。下面具体介绍家政小程序有哪些功能&#xff0c;如何制作。 1. 展示家政服务 在小程序中&#xff0c;上传所有的家政…

Spring Cloud Alibaba - 利用Nacos实现高效动态线程池管理

文章目录 引言概述什么是动态线程池Nacos简介如何利用Nacos实现动态线程池管理应用场景Code版本说明POM配置文件Nacos Config配置文件加载顺序1. bootstrap.yml的加载2. application.yml的加载注意事项示例 nacos配置Data IdNacos中Data ID的命名格式解释${spring.application.…

力扣● 343. 整数拆分 ● 96.不同的二叉搜索树

● 343. 整数拆分 想不到&#xff0c;要勇于看题解。 关键在于理解递推公式。 1、DP数组及其下标的含义&#xff1a;dp[i]是分解i这个数得到的最大的乘积。 2、DP数组如何初始化&#xff1a;dp[0]和dp[1]都没意义&#xff0c;所以直接不赋值&#xff0c;初始化dp[2]1即可。…

maven 打包命令

Maven是基于项目对象模型(POM project object model)&#xff0c;可以通过一小段描述信息&#xff08;配置&#xff09;来管理项目的构建&#xff0c;报告和文档的软件项目管理工具。 Maven的核心功能便是合理叙述项目间的依赖关系&#xff0c;通俗点讲&#xff0c;就是通过po…

【openGL教程08】基于C++的着色器(02)

LearnOpenGL - Shaders 一、说明 着色器是openGL渲染的重要内容&#xff0c;客户如果想自我实现渲染灵活性&#xff0c;可以用着色器进行编程&#xff0c;这种程序小脚本被传送到GPU的显卡内部&#xff0c;起到动态灵活的着色作用。 二、着色器简述 正如“Hello Triangle”一章…

单片机05__串口USART通信__按键控制向上位机传输字符串

串口USART通信 通用UART介绍 1.通信的概念 计算机与外界进行信息交换的过程称之为通信。 在通信的过程中&#xff0c;通信双方都需要遵守的规则称之为通信协议。 硬件协议&#xff1a;将数据以什么样的方式传输过去 软件协议&#xff1a;将数据以什么样的顺序传输过去 2.常用…

C#与VisionPro联合开发——跳转页面

1、跳转页面并打开相机 From1 所有代码展示 using System; using System.IO; using System.Windows.Forms; //引入VisionPro命名空间 using Cognex.VisionPro;namespace ConnectCamera {public partial class Form1 : Form {public Form1() {InitializeComponent();}CogAcqFif…

云原生之API网关Traefik

1. 前言 说到web服务的开源网关&#xff0c;我首先想到的是Nginx&#xff0c;最早使用的就是它&#xff0c;现在都还在使用它。系统上线了Docker Swarm集群之后&#xff0c;不继续使用Nginx直接做Docker服务的网关&#xff0c;是因为Nginx毕竟比Docker Swarm出现的早&#xff0…

【C#】用于基于 UV DLP 的 3D 打印机的切片软件源码解析(一)DLP原理 GUI

0. 原理 基于 UV DLP 的 3D 打印机的工作原理是这样的&#xff1a; UV DLP 是一种使用数字光处理&#xff08;Digital Light Processing&#xff09;技术的 3D 打印方法&#xff0c;它利用紫外光&#xff08;UV&#xff09;来固化液态树脂&#xff0c;从而形成实体物体。UV DLP…