在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=你的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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++服务器开源项目Tinywebserver运行

c服务器开源项目Tinywebserver运行 一、Tinywebserver介绍二、环境搭建三、构建数据库四、编译Tinywebserver五、查看效果 Tinywebserver是github上一个十分优秀的开源项目&#xff0c;帮助初学者学习如何搭建一个服务器。 本文讲述如何在使用mysql跟该项目进行连接并将项目运行…

集合、List、Set、Map、Collections、queue、deque

概述 相同类型的数据进行统一管理操作&#xff0c;使用数据结构、链表结构&#xff0c;二叉树 分类&#xff1a;Collection、Map、Iterator 集合框架 List接口 有序的Collection接口&#xff0c;可以对列表中的每一个元u尿素的插入位置进行精确的控制&#xff0c;用户可以根…

Vue2 基础面试题

v-show 和 v-if 区别 v-show 通过 CSS display 控制显示和隐藏v-if 通过判断组件真实渲染和销毁&#xff0c;而不是显示和隐藏频繁切换显示状态用 v-show&#xff0c;否则用 v-if v-if 当 v-if 与 v-for 一起使用时&#xff0c;v-for 具有比 v-if 更高的优先级&#xff0c;意…

DT DAY3 信号和槽

作业&#xff1a; 1> 思维导图 2> 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 btn3 new QPushButton("按钮3",this);btn3->resize(ui->btn2->width(),ui->b…

有名管道的大小

管道&#xff1a;有名管道、无名管道 通信&#xff1a; 单工通信&#xff1a;固定的读端和写端 -- 广播 半双工通信&#xff1a;同一时刻&#xff0c;只有有一方写&#xff0c;另外一方读:对讲机 全双工通信&#xff1a;随时两方都能读写 -- 电话 特点&#xff1a; 管道属…

Cosmos收益协议Hover以800%的超额认购结束公开销售

Hover&#xff0c;建立在Cosmos的Kava EVM上的可持续收益生态系统&#xff0c;在其公开销售中积累了超过800万美元的存款。 Hover&#xff0c;Kava EVM上新推出的收益生态系统&#xff0c;已经在顶级加密货币Launchpad DAO Maker上结束了其公开销售。通证销售旨在筹集100万美元…

Shell 脚本系列 | xsync同步脚本的使用

xsync是一个同步脚本&#xff0c;它实际上是对rsync脚本的二次封装&#xff0c;可以简化在多个节点之间同步文件的过程。以下是使用xsync工具的基本步骤&#xff1a; 1.确保已安装rsync。如果没有安装&#xff0c;可以使用以下命令进行安装&#xff1a; yum -y install rsync…

GB28181 —— Ubuntu20.04下使用ZLMediaKit+WVP搭建GB28181流媒体监控平台(连接带云台摄像机)

最终效果 简介 GB28181协议是视频监控领域的国家标准。该标准规定了公共安全视频监控联网系统的互联结构&#xff0c; 传输、交换、控制的基本要求和安全性要求&#xff0c; 以及控制、传输流程和协议接口等技术要求&#xff0c;是视频监控领域的国家标准。GB28181协议信令层面…

umi - react web端 集成腾讯即时通信IM,实现自定义翻译功能

项目使用umi - react 框架 在集成腾讯的IM的时候需要用到自定义翻译功能,调用自己的翻译服务 , 于是进行更改,发现按照官网提示的集成含UI的IM是直接下载依赖,然后引入组件包直接用,在官网上没看到有哪里配置自定义翻译的文档 , 于是咨询客服 最初的思路是在消息的更多选项中…

【Linux网络】网络编程套接字(TCP)

目录 地址转换函数 字符串IP转整数IP 整数IP转字符串IP 关于inet_ntoa 简单的单执行流TCP网络程序 TCP socket API 详解及封装TCP socket 服务端创建套接字 服务端绑定 服务端监听 服务端获取连接 服务端处理请求 客户端创建套接字 客户端连接服务器 客户端…

ubuntu20配置protobuf 2.5.0

python安装protobuf包 sudo pip2 install protobuf2.5.0github克隆获取安装包 wget https://github.com/protocolbuffers/protobuf/releases/download/v2.5.0/protobuf-2.5.0.tar.gz解压并进入该目录 tar -zxvf Protobuf-2.5.0.tar.gz cd protobuf-2.5.0配置安装环境 sudo …

【Vuforia+Unity】AR03-圆柱体物体识别(Cylinder Targets)

1.创建数据库模型 这个是让我们把生活中类似圆柱体和圆锥体的物体进行AR识别所选择的模型 Bottom Diameter:底部直径 Top Diameter:顶部直径 Side Length:圆柱侧面长度 请注意&#xff0c;您不必上传所有三个部分的图片&#xff0c;但您需要先为侧面曲面关联一个图像&#…

Atcoder ABC340 A-D题解

比赛链接:ABC340 话不多说&#xff0c;看题。 Problem A: 签到。 #include <bits/stdc.h> using namespace std; int main(){int a,b,d;cin>>a>>b>>d;for(int ia;i<b;id)cout<<i<<endl;return 0; } Problem B: 还是签到题。一个v…

张宇2025基础三十讲高等数学笔记(数二)

本次主要是根据书课包视频的进程&#xff0c;第1讲的01和02&#xff0c;分为基础知识和书上的例题加上经典1000题对应的相关的知识点 1.基础知识 1&#xff09;函数 2&#xff09;反函数 3&#xff09;复合函数 2.书上例题1000题 1&#xff09;函数 2&#xff09;反函数 3&a…

【Git】:远程仓库操作

远程仓库操作 一.理解版本控制系统二.远程仓库1.克隆2.Push操作3.fetch操作4. .gitnore文件 一.理解版本控制系统 我们⽬前所说的所有内容&#xff08;⼯作区&#xff0c;暂存区&#xff0c;版本库等等&#xff09;&#xff0c;都是在本地&#xff01;也就是在你的笔记本或者计…

杂题——1097: 蛇行矩阵

题目描述 蛇形矩阵是由1开始的自然数依次排列成的一个矩阵上三角形。 输入格式 本题有多组数据&#xff0c;每组数据由一个正整数N组成。&#xff08;N不大于100&#xff09; 输出格式 对于每一组数据&#xff0c;输出一个N行的蛇形矩阵。两组输出之间不要额外的空行。矩阵三角…

猜字谜|构建生成式 AI 应用实践(一)

在 2023 亚马逊云科技 re:Invent 之后&#xff0c;细心的开发者们也许已经发现有一个很有趣的动手实验&#xff1a;开发一款可部署的基于大语言模型的字谜游戏&#xff1a; 该款游戏使用了文生图模型为玩家提供一个未知的提示词&#xff0c;玩家需要根据模型生成的图像来猜测该…

Covalent Network(CQT)与 Movement Labs 达成合作,重新定义 M2 系统区块链数据可用性与性能

Covalent Network&#xff08;CQT&#xff09;是行业领先的多链索引器&#xff0c;正在与 Movement Labs 的 M2 展开具有突破性意义的合作。M2 是以太坊上的首个 Move-EVM&#xff08;MEVM&#xff09;ZK rollup 。这一战略合作标志着先进的实时数据索引和部署工具&#xff0c;…

如何实现H5和小程序之间相互跳转

嗨&#xff0c;各位小伙伴们&#xff0c;我是你们的好朋友咕噜铁蛋&#xff01;今天&#xff0c;我要和大家分享一下关于如何实现H5和小程序之间相互跳转的话题。随着移动互联网的发展&#xff0c;H5网页和小程序已经成为了我们日常生活中不可或缺的一部分。那么&#xff0c;如…

SD-WAN组网:打造跨国企业无缝网络连接体验

在数字化转型的时代&#xff0c;越来越多的企业迈向国际化&#xff0c;然而&#xff0c;由于自建网络架构的限制和跨域网络的复杂性&#xff0c;企业在不同地理位置的站点之间难以实现高效的数据互通和协作。这就是为什么SD-WAN成为跨国企业组网的理想选择的原因。 跨国企业常见…