接口的延迟

在调用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中的响应,根据情况,将响应内容追加到界面上,就实现了逐字打印的效果了。

相关链接

  1. 以上示例代码,可参见 GPT3_demo
  2. 想要了解如何理解OpenAI的API,可参考打造自己的ChatGPT:OpenAI 的API接入技巧
  3. OpenAI 官方对于延迟处理的一些意见improving-latencies
  4. Dotnet SDK for OpenAI GPT-3 and DALL·E

打造自己的ChatGPT:逐字打印的流式处理的更多相关文章

  1. android流式布局、待办事项应用、贝塞尔曲线、MVP+Rxjava+Retrofit、艺术图片应用等源码

    Android精选源码 android模仿淘宝首页效果源码 一款艺术图片应用,采用T-MVVM打造 Android MVP + RxJava + Retrofit项目 android流式布局实现热门标 ...

  2. CSS3与页面布局学习笔记(四)——页面布局大全(负边距、双飞翼、多栏、弹性、流式、瀑布流、响应式布局)

    一.负边距与浮动布局 1.1.负边距 所谓的负边距就是margin取负值的情况,如margin:-100px,margin:-100%.当一个元素与另一个元素margin取负值时将拉近距离.常见的功能 ...

  3. HttpURLConnection的流式输出的缺陷和解决方法

    转自:http://www.mzone.cc/article/198.html 最近在用applet写文件上传控件的时候发现使用URLConnection来对服务器进行流式输出时的一些问题.我们通常要 ...

  4. JDFS:一款分布式文件管理系统,第三篇(流式云存储)

    一 前言 看了一下,距离上一篇博客的发表已经过去了4个月,时间过得好快啊.本篇博客是JDFS系列的第三篇博客,JDFS的目的是为了实现一个分布式的文件管理系统,前两篇实现了基本的上传.下载功能,但是那 ...

  5. JDFS:一款分布式文件管理系统,第四篇(流式云存储续篇)

    一 前言 本篇博客是JDFS系列博客的第四篇,从最初简单的上传.下载,到后来加入分布式功能,背后经历了大量的调试,尤其当实验的虚拟计算结点数目增加后,一些潜在的隐藏很深的bug就陆续爆发.在此之前笔者 ...

  6. 【官方文档】Nginx模块Nginx-Rtmp-Module学习笔记(三)流式播放Live HLS视频

    源码地址:https://github.com/Tinywan/PHP_Experience HTTP Live Streaming(HLS)是由Apple Inc.实施的非常强大的流视频协议.HLS ...

  7. Spark Streaming流式处理

    Spark Streaming介绍 Spark Streaming概述 Spark Streaming makes it easy to build scalable fault-tolerant s ...

  8. java JAXB + STAX(是一种针对XML的流式拉分析API)读取xml

    JDK1.5需要添加jar包,1.6以后就不需要了<dependency> <groupId>stax</groupId> <artifactId>st ...

  9. 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放~ ...

  10. 构建流式应用—RxJS详解[转]

    目录 常规方式实现搜索功能 RxJS · 流 Stream RxJS 实现原理简析 观察者模式 迭代器模式 RxJS 的观察者 + 迭代器模式 RxJS 基础实现 Observable Observe ...

随机推荐

  1. input设置自定义属性,并获取值。

    HTML代码: JavaScript代码: 因为busCode不是input标签原生的属性,所以不能使用"点"操作. 非标准属性,要用:obj.getAttribute(" ...

  2. manjaro安装后配置与美化

    时间同步 sudo timedatectl set-ntp true 换源 sudo pacman-mirrors -i -c China -m rank 更新 更新系统 sudo pacman -S ...

  3. Mysql的驱动表 被驱动表 join buffer

    1.为什么小表驱动大表: for(int i=5;.......){     for(int j=1000;......)     {}} 1.1如果小的循环在外层,对于数据库连接来说就只连接5次,进 ...

  4. MySQL数据库sql_mode导致varchar字段超过长度被截断插入

    django数据库设置sql_mode MySQL的sql_mode解析与设置 mysql中sql_mode的修改 sql_mode:它定义了MySQL应该支持的sql语法,对数据的校验等等. 问题 ...

  5. OpenCV图像拼接函数

    图像拼接函数 第一种方法:通过遍历图像,将待拼接的图像每个像素赋值给输出图像 //图像拼接函数 //imageVector 输入图像数组 //outputImage 输出图像 //colCount_ ...

  6. Asp.net zero项目框架和配置

  7. protobuf怎么处理java中的Object和Object[],protobuf的bytestring和object[]

    如题,作者一开始也遇到了这个比较棘手的问题. 话不多说,直接说解决方案. 这里使用bytestring,如果是object[]的话则用repeated定义即可. 那么问题又来了,用这个类型怎么做到与j ...

  8. DFS专题1

    例题一 39.组合总和 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 , ...

  9. thread互斥测试

    thread互斥测试 实践代码 #include <stdio.h> #include <stdlib.h> #include <pthread.h> //linu ...

  10. git 强制拉取远程到本地

    git fetch --all git reset --hard origin/master git pull