打造自己的ChatGPT:逐字打印的流式处理
接口的延迟
在调用OpenAI的接口时,不免会有很慢的感觉,抛去地理位置上的网络延迟,大量的延迟往往发生在响应生成的过程中。
因此,如果使用同步接口的话,需要等待响应完全生成之后才能最终显示输出结果,虽然在对接微信或者其他需要通过接口请求的应用服务时没什么特别好的处理方案,但是如果是Web应用的话,就可以通过流式处理实现实时的数据返回,从而提升响应的优先级。
后端的流式处理
绝大部分的SDK都已经对OpenAI API的流式响应做了封装,这里以.NET 的Betalgo.OpenAI.GPT3
为例,详细的配置部分不再赘述,可参考其说明文档。
//使用CreateCompletionAsStream获取 completions
var completions = openAIService.Completions.CreateCompletionAsStream(new OpenAI.GPT3.ObjectModels.RequestModels.CompletionCreateRequest
{
Prompt = BuildPrompt(input.Prompt),
MaxTokens = 100,
Temperature = 0.5f,
TopP = 1,
FrequencyPenalty = 0,
PresencePenalty = 0,
StopAsList = new List<string> { "Q:", "A:" }
}, Models.TextDavinciV3);
//将返回内容直接经过UTF编码写入Response
async Task ResponseWrite(string? text) {
if (text == null) return;
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes(text));
await Response.Body.FlushAsync();
}
//逐个获取 completion的返回,并写入到Response中
await foreach (var completionResponse in completions)
{
if (completionResponse.Successful)
{
await ResponseWrite(completionResponse.Choices[0].Text);
}else {
await ResponseWrite("<ERR>");
if(completionResponse.Error != null){
await ResponseWrite(completionResponse.Error.Code);
await ResponseWrite(completionResponse.Error.Message);
}else {
await ResponseWrite("<unknow>");
}
}
}
其中最核心的一点就是 ResponseWrite 部分,提前将内容写入到Response的Body中,并进行Flush。
另外需要注意的就是,Action 不能再有返回值(如果是.NET 7 的话,可以返回 Empty),因为已经在Action结束之前向Response写入了内容,再有返回值的话,就会报错。
前端的流式处理
同样的,后端作为流式发送,前端就需要以流式形式接收。
这里不需要使用到EventSource、WebSocket或是其他的实时通讯框架,仅需要使用普通的fetch即可。
async function send() {
const input = document.getElementById("input").value;
const output = document.getElementById("output");
output.innerText = "";
const url = "/api/stream";
const data = { "Prompt": input };
//直接获取 Fetch 的response, 无法使用 await的话, Promise的方式也是可以的。
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
})
//获取UTF8的解码
const encode = new TextDecoder("utf-8");
//获取body的reader
const reader = response.body.getReader();
// 循环读取reponse中的内容
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// 解码内容
const text = encode.decode(value);
// 当获取错误token时,输出错误信息
if (text === "<ERR>") {
output.innerText = "Error";
break;
} else {
// 获取正常信息时,逐字追加输出
output.innerText += text;
}
}
}
fetch获取的response可以,可以通过 response.body.getReader() 获取reader,然后循环获取reader中的响应,根据情况,将响应内容追加到界面上,就实现了逐字打印的效果了。
相关链接
- 以上示例代码,可参见 GPT3_demo
- 想要了解如何理解OpenAI的API,可参考打造自己的ChatGPT:OpenAI 的API接入技巧
- OpenAI 官方对于延迟处理的一些意见improving-latencies
- Dotnet SDK for OpenAI GPT-3 and DALL·E
打造自己的ChatGPT:逐字打印的流式处理的更多相关文章
- android流式布局、待办事项应用、贝塞尔曲线、MVP+Rxjava+Retrofit、艺术图片应用等源码
Android精选源码 android模仿淘宝首页效果源码 一款艺术图片应用,采用T-MVVM打造 Android MVP + RxJava + Retrofit项目 android流式布局实现热门标 ...
- CSS3与页面布局学习笔记(四)——页面布局大全(负边距、双飞翼、多栏、弹性、流式、瀑布流、响应式布局)
一.负边距与浮动布局 1.1.负边距 所谓的负边距就是margin取负值的情况,如margin:-100px,margin:-100%.当一个元素与另一个元素margin取负值时将拉近距离.常见的功能 ...
- HttpURLConnection的流式输出的缺陷和解决方法
转自:http://www.mzone.cc/article/198.html 最近在用applet写文件上传控件的时候发现使用URLConnection来对服务器进行流式输出时的一些问题.我们通常要 ...
- JDFS:一款分布式文件管理系统,第三篇(流式云存储)
一 前言 看了一下,距离上一篇博客的发表已经过去了4个月,时间过得好快啊.本篇博客是JDFS系列的第三篇博客,JDFS的目的是为了实现一个分布式的文件管理系统,前两篇实现了基本的上传.下载功能,但是那 ...
- JDFS:一款分布式文件管理系统,第四篇(流式云存储续篇)
一 前言 本篇博客是JDFS系列博客的第四篇,从最初简单的上传.下载,到后来加入分布式功能,背后经历了大量的调试,尤其当实验的虚拟计算结点数目增加后,一些潜在的隐藏很深的bug就陆续爆发.在此之前笔者 ...
- 【官方文档】Nginx模块Nginx-Rtmp-Module学习笔记(三)流式播放Live HLS视频
源码地址:https://github.com/Tinywan/PHP_Experience HTTP Live Streaming(HLS)是由Apple Inc.实施的非常强大的流视频协议.HLS ...
- Spark Streaming流式处理
Spark Streaming介绍 Spark Streaming概述 Spark Streaming makes it easy to build scalable fault-tolerant s ...
- java JAXB + STAX(是一种针对XML的流式拉分析API)读取xml
JDK1.5需要添加jar包,1.6以后就不需要了<dependency> <groupId>stax</groupId> <artifactId>st ...
- centos 正则,grep,egrep,流式编辑器 sed,awk -F 多个分隔符 通配符 特殊符号. * + ? 总结 问加星 cat -n nl 输出文件内容并加上行号 alias放~/.bash_profile 2015-4-10 第十三节课
centos 正则,grep,egrep,流式编辑器 sed,awk -F 多个分隔符 通配符 特殊符号. * + ? 总结 问加星 cat -n nl 输出文件内容并加上行号 alias放~ ...
- 构建流式应用—RxJS详解[转]
目录 常规方式实现搜索功能 RxJS · 流 Stream RxJS 实现原理简析 观察者模式 迭代器模式 RxJS 的观察者 + 迭代器模式 RxJS 基础实现 Observable Observe ...
随机推荐
- cobbler安装CentOS-8系统
cobbler安装CentOS-8系统 cobbler 1. cobbler简介 2. cobbler服务端部署 3. 客户端安装 4. 定制安装 1. cobbler简介 Cobbler是一个Lin ...
- PTA两个有序链表序列的合并
本题要求实现一个函数,将两个链表表示的递增整数序列合并为一个非递减的整数序列. 函数接口定义: List Merge( List L1, List L2 ); 其中List结构定义如下: ...
- 学习JavaScript第四周
创建闭包的四个条件: 1.要有函数嵌套,且里层函数要操作外层函数被保护的变量. 2.返回里层函数 3.里层定义的函数要被调用,哪怕是返回一个匿名函数也行. 4.用一个变量接住,方便后续的使用. 注意: ...
- 12.6linux学习第十三天
今天老刘开始讲第9第章使用ssh服务管理远程主机.第10章开了个小头. 9.1 配置网卡服务 9.1.1 配置网卡参数 截至目前,大家已经完全可以利用当前所学的知识来管理Linux系统了.当然,大家的 ...
- 项目:在wiki标记中添加无序列表(split、join巩固)
# coding: utf-8 import pyperclip text = pyperclip.paste() lines = text.split("\n") for i i ...
- windows 获取USB,发现安卓设备,转载自www.jb51.net/article/164456.htm
转载 作者:jgw2008 import win32com.client def CheckDev(): wmi = win32com.client.GetObject ("winmgmts ...
- 导出数据库表以及备注为excel
import com.alibaba.excel.annotation.ExcelProperty; import lombok.AllArgsConstructor; import lombok.D ...
- 剑指Offer2---------替换空格
题目描述 请实现一个函数,将一个字符串中的每个空格替换成"%20".例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy. 重点: ...
- NFS存储安装配置
一.NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种, 它允许网络中的计算机之间通过TCP/IP网络共享资源.在NFS的应用中,本地NFS的客 户 ...
- DB2通过java代码生成自定义uuid()函数
一.简单的方法 此种方法在快速大量生成时,会有重复 SELECT concat (hex (RAND ()), hex (RAND ())) as uuid FROM SYSIBM.SYSDUMMY1 ...