AIGC: 关于ChatGPT中基于API实现一个StreamClient流式客户端
Java版GPT的StreamClient
- 可作为其他编程语言的参考
- 注意: 下面包名中的 xxx 可以换成自己的
- 代码基于java,来源于网络,可修改成其他编程语言实现
- 参考前文: https://blog.csdn.net/Tyro_java/article/details/134748994
1 )核心代码结构设计
- src
- main
- java
- com.xxx.gpt.client
- listener
- AbstractStreamListener.java
- ConsoleStreamListener.java
- ChatGPTStreamClient.java
- …
- listener
- com.xxx.gpt.client
- java
- test
- java
- com.xxx.gpt.client.test
- StreamClientTest.java
- …
- com.xxx.gpt.client.test
- java
- main
2 )相关程序如下
- 前文,通过我们开发的Client能够正常的和 Open AI 进行交互,能够去调用GPT的API
- 通过API将我们的 message 请求发送给GPT并且正常的接收到了GPT对我们的返回
- 在前面我们去浏览 GPT 它的API的时候,我们发现它是支持流式访问的
- 我们可以开发一个Stream的Client,能够支持流式的接收GPT的响应
- 流式的Client在很多场景下也是非常有必要的
- 首先需要去先创建一个listener,去流式的接收GPT的返回, 我们实现一个 AbstractStreamListener 类 和 ConsoleStreamListener 类
- 需要继承于 EventSourceListener, 内部添加几个方法
- onOpen
- onEvent, 可以获取到流失的输入,这个是重点
- onClosed
- onFailure
AbstractStreamListener.java
package com.xxx.gpt.client.listener;import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.xxx.gpt.client.entity.ChatChoice;
import com.xxx.gpt.client.entity.ChatCompletionResponse;
import com.xxx.gpt.client.entity.Message;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;@Slf4j
public abstract class AbstractStreamListener extends EventSourceListener {protected String lastMessage = "";/*** Called when all new message are received.** @param message the new message*/@Setter@Getterprotected Consumer<String> onComplate = s -> {};/*** Called when a new message is received.* 收到消息 单个字** @param message the new message*/public abstract void onMsg(String message);/*** Called when an error occurs.* 出错时调用** @param throwable the throwable that caused the error* @param response the response associated with the error, if any*/public abstract void onError(Throwable throwable, String response);@Overridepublic void onOpen(EventSource eventSource, Response response) {// do nothing}@Overridepublic void onClosed(EventSource eventSource) {// do nothing}@Overridepublic void onEvent(EventSource eventSource, String id, String type, String data) {if (data.equals("[DONE]")) {onComplate.accept(lastMessage);return;}// 将数据反序列化为 GPT的 responseChatCompletionResponse response = JSON.parseObject(data, ChatCompletionResponse.class);// 获取GPT的返回,读取JsonList<ChatChoice> choices = response.getChoices();// 为空则 returnif (choices == null || choices.isEmpty()) {return;}// 获取流式信息Message delta = choices.get(0).getDelta();String text = delta.getContent();if (text != null) {lastMessage += text;onMsg(text);}}@SneakyThrows@Overridepublic void onFailure(EventSource eventSource, Throwable throwable, Response response) {try {log.error("Stream connection error: {}", throwable);String responseText = "";if (Objects.nonNull(response)) {responseText = response.body().string();}log.error("response:{}", responseText);String forbiddenText = "Your access was terminated due to violation of our policies";if (StrUtil.contains(responseText, forbiddenText)) {log.error("Chat session has been terminated due to policy violation");log.error("检测到号被封了");}String overloadedText = "That model is currently overloaded with other requests.";if (StrUtil.contains(responseText, overloadedText)) {log.error("检测到官方超载了,赶紧优化你的代码,做重试吧");}this.onError(throwable, responseText);} catch (Exception e) {log.warn("onFailure error:{}", e);// do nothing} finally {eventSource.cancel();}}
}
ConsoleStreamListener.java
package com.xxx.gpt.client.listener;import lombok.extern.slf4j.Slf4j;@Slf4j
public class ConsoleStreamListener extends AbstractStreamListener {@Overridepublic void onMsg(String message) {System.out.print(message);}@Overridepublic void onError(Throwable throwable, String response) {}
}
- 再创建一个 ChatGPTStreamClient 类
- 添加如下相关属性
- 添加 init 方法
- 完成 streamChatCompletion 方法
ChatGPTStreamClient.java
package com.xxx.gpt.client;import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.ContentType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxx.gpt.client.entity.ChatCompletion;
import com.xxx.gpt.client.entity.Message;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;import java.net.Proxy;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;@Slf4j
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatGPTStreamClient {private String apiKey;private List<String> apiKeyList;private OkHttpClient okHttpClient;/*** 连接超时*/@Builder.Defaultprivate long timeout = 90;/*** 网络代理*/@Builder.Defaultprivate Proxy proxy = Proxy.NO_PROXY;/*** 反向代理*/@Builder.Defaultprivate String apiHost = ChatApi.CHAT_GPT_API_HOST;/*** 初始化*/public ChatGPTStreamClient init() {OkHttpClient.Builder client = new OkHttpClient.Builder();client.connectTimeout(timeout, TimeUnit.SECONDS);client.writeTimeout(timeout, TimeUnit.SECONDS);client.readTimeout(timeout, TimeUnit.SECONDS);if (Objects.nonNull(proxy)) {client.proxy(proxy);}okHttpClient = client.build();return this;}/*** 流式输出*/public void streamChatCompletion(ChatCompletion chatCompletion,EventSourceListener eventSourceListener) {chatCompletion.setStream(true);try {EventSource.Factory factory = EventSources.createFactory(okHttpClient);ObjectMapper mapper = new ObjectMapper();String requestBody = mapper.writeValueAsString(chatCompletion);String key = apiKey;if (apiKeyList != null && !apiKeyList.isEmpty()) {key = RandomUtil.randomEle(apiKeyList);}Request request = new Request.Builder().url(apiHost + "v1/chat/completions").post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()),requestBody)).header("Authorization", "Bearer " + key).build();factory.newEventSource(request, eventSourceListener);} catch (Exception e) {log.error("请求出错:{}", e);}}/*** 流式输出*/public void streamChatCompletion(List<Message> messages,EventSourceListener eventSourceListener) {ChatCompletion chatCompletion = ChatCompletion.builder().messages(messages).stream(true).build();streamChatCompletion(chatCompletion, eventSourceListener);}
}
再添加一个测试方法 StreamClientTest.java
package com.xxx.gpt.client.test;import com.xxx.gpt.client.ChatGPTStreamClient;
import com.xxx.gpt.client.entity.ChatCompletion;
import com.xxx.gpt.client.entity.Message;
import com.xxx.gpt.client.listener.ConsoleStreamListener;
import com.xxx.gpt.client.util.Proxys;
import org.junit.Before;
import org.junit.Test;import java.net.Proxy;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;public class StreamClientTest {private ChatGPTStreamClient chatGPTStreamClient;@Beforepublic void before() {Proxy proxy = Proxys.http("127.0.0.1", 7890);chatGPTStreamClient = ChatGPTStreamClient.builder().apiKey("sk-6kchadsfsfkc3aIs66ct") // 填入自己的 key.proxy(proxy).timeout(600).apiHost("https://api.openai.com/").build().init();}@Testpublic void chatCompletions() {ConsoleStreamListener listener = new ConsoleStreamListener();Message message = Message.of("写一段七言绝句诗");ChatCompletion chatCompletion = ChatCompletion.builder().messages(Arrays.asList(message)).build();chatGPTStreamClient.streamChatCompletion(chatCompletion, listener);try {Thread.sleep(10000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
- 这样,程序基本已经完成了
- 这里构建了一个流式访问的参数,然后去调用GPT的API实现了流式的输出
- 可参考以上 Java 版实现, 去实现其他语言版本的 StreamClient
相关文章:
AIGC: 关于ChatGPT中基于API实现一个StreamClient流式客户端
Java版GPT的StreamClient 可作为其他编程语言的参考注意: 下面包名中的 xxx 可以换成自己的代码基于java,来源于网络,可修改成其他编程语言实现参考前文: https://blog.csdn.net/Tyro_java/article/details/134748994 1 )核心代码结构设计 …...

FutureTask
1. 作用 异步操作获取执行结果取消任务执行,判断是否取消执行判断任务执行是否完毕 2. demo public static void main(String[] args) throws Exception {Callable<String> callable () -> search();FutureTask<String> futureTasknew FutureTask&…...

【力扣热题100】207. 课程表 python 拓扑排序
【力扣热题100】207. 课程表 python 拓扑排序 写在最前面207. 课程表解决方案:判断是否可以完成所有课程的学习方法:拓扑排序实现步骤Python 实现性能分析结论 写在最前面 刷一道力扣热题100吧 难度中等 https://leetcode.cn/problems/course-schedule…...

【滑动窗口】LeetCode2953:统计完全子字符串
作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 本题其它解法 【离散差分】LeetCode2953:统计完全子字符串 题目 给你一个字符串 word 和一个整数 k 。 如果 word 的一个子字符串 s 满足以下条件,我们称它是 完全字符串: s 中每个字符…...
base64转PDF
今天做皖事通的对接,下载电子证照后发现回传的是base64,调试确认是个麻烦事,网上搜了一下没有base64转PDF的在线预览功能,只能自己写个调试工具了,以下是通过纯JS方式写的代码,可直接拿去使用: …...

clip-path,css裁剪函数
https://www.cnblogs.com/dzyany/p/13985939.html clip-path - CSS:层叠样式表 | MDN 我们看下这个例子 polygon里有四个值分别代表这四个点相对于原图左上方的偏移量。 裁剪个五角星...

第二证券:食品饮料板块拉升,乳业股亮眼,西部牧业“20cm”涨停
证券时报网讯,食物饮料板块5日盘中拉升走高,乳业股体现活跃,到发稿,骑士乳业涨超27%,西部牧业“20cm”涨停,阳光乳业亦涨停。 其它个股方面,盖世食物涨超20%,润普食物涨超18%&#…...
React 好用的工具库
1、html-react-parser HTML 到 React 解析器,适用于服务器 (Node.js) 和客户端(浏览器),适用于React节点修改过滤等需求 解析器将 HTML 字符串转换为一个或多个 React 元素。可以将一个元素替换为另一个元素…...

C++面试宝典第2题:逆序输出整数
题目 写一个方法,将一个整数逆序打印输出到控制台。注意:当输入的数字含有结尾的0时,输出不应带有前导的0。比如:123的逆序输出为321,8600的逆序输出为68,-609的逆序输出为-906。 解析 这道题本身并没有什么…...
Twincat功能块使用经验总结
控制全局变量: //轴控制指令 bi_Power: BOOL; //使能 bi_Reset: BOOL; //复位 bi_Stop: BOOL; //停止 bi_JogForward: BOOL; //正向点动 bi_JogBackwards: BOOL; //反向点动 bi_MoveAdditive: BOOL; //增量位…...

香港服务器时间不准,差8小时
解决方案1 1、timedatectl查看系统时间 2、查看系统时区 ls /usr/share/zoneinfo 3、删除当前系统所处时区 rm /etc/localtime 4、创建软链接,以替换当前的时区信息 ln -s /usr/share/zoneinfo/Universal /etc/localtime 解决方案2 手动设置硬件时钟 1、设置系…...

C++ 抽象类和接口 详解
目录 0 引言1 抽象类2 接口2.1 Java与C接口的区别 🙋♂️ 作者:海码007📜 专栏:C专栏💥 标题:C 抽象类和接口 详解❣️ 寄语:书到用时方恨少,事非经过不知难!…...

【Linux】awk 使用
awk 输出 // 打印所有列 $ awk {print $0} file // 打印第一列 $ awk {print $1} file // 打印第一和第三列 $ awk {print $1, $3} file // 打印第三列和第一列,注意先后顺序 $ cat file | awk {print $3, $1} …...

LeetCode力扣每日一题(Java):9、回文数
一、题目 二、解题思路 1、我的思路 当x<0时,x一定不是回文数,直接返回false 当x>0且x<10时,x一定是回文数,直接返回true x>10时,先将x转为字符串。将数字转成字符串方法挺多的,以下是&…...

WPF前端实现人脸扫描动画效果
前言 本章实现的效果主要通过OpacityMask与LinearGradientBrush(径向渐变) 的组合应用来实现。最终实现效果如下: LinearGradientBrush线性渐变画刷 LinearGradientBrush其实很简单,我们只需要关注5个属性,使用这5个属性你就可以完成这个画刷几乎所有的变化。 属性介…...

更改AndroidStudio模拟器位置
C盘何等的珍贵,可是好多工具,软件非得默认安装在C盘。。导致C盘越来越紧张。。 在日常使用过程中,安装任何软件都会将其安装到非系统盘下,Android模拟器也不能例外。保护好C盘也是日常一个良好的习惯。 Android AVD默认路径&…...
Dash 协议介绍
<?xml version"1.0" encoding"utf-8"?> <MPD xmlns"urn:mpeg:dash:schema:mpd:2011" minBufferTime"PT1.5S" type"static" mediaPresentationDuration"PT0H1M0.3S" maxSegmentDuration"PT0H0M2.0…...

RabbitMQ的消息发送和接收机制
所有 MQ 产品从模型抽象上来说都是一样的过程: 消费者(consumer)订阅某个队列。生产者(producer)创建消息,然后发布到队列(queue)中,最后将消息发送到监听的消费者。 上…...
记录111
在两台 RHEL 8 服务器上搭建 PostgreSQL 和 pgpool-II 环境涉及到安装 PostgreSQL、配置流复制(Streaming Replication)以及安装和配置 pgpool-II。以下是详细的步骤: ### 准备工作 1. **获取服务器**:确保你有两台运行 RHEL 8 的…...
振动和震动的区别?
问题描述:振动和震动的区别? 问题解决: 震动(Oscillation): 特点: 随机的、突发的、不经常的、无规律的运动。例子: 地壳震动、消息震动全国,强调的是运动的力度或幅度&…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...