打造自己的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 ...
随机推荐
- 狐漠漠养成日记 Cp.00002 第一周
主要目标 (1)考研 考研数学二16-22年的真题卷(已完成真题卷:0/7) 记忆考研英语中高频词汇(已记忆词汇:高频:0/10:中频:0/10) 考研英语二16-22年的真题卷(已完成真题卷:0/7 ...
- 网络同步时钟单路耐压测试突破17V
自动同步标准化考场时钟系统------专业LED时钟厂家![点击进入] 一.网络同步时钟耐压测试作用概述: 同步时钟耐压试验是鉴定时钟绝缘强度和稳定性最直接的方法,它对于判断NTP同步时钟设备能否投入 ...
- pytorch代码练习
pytorch练习 使用torch.Tensor定义数据 , tensor的意思是张量,是数字各种形式的总称,可以定义数.向量.二维数组和张量. import torch # 可以是一个数 x = t ...
- VUE相关面试题目01
一.MVVM是什么; MVC: MVVM的描述: 常见库实现数据双向绑定的效果: 发布订阅模式; ...
- 含字母数字的字符串排序算法,仿Windows文件名排序算法
不废话,上排序前后对比: 类似与windows的目录文件排序,分几种版本C++/C#/JAVA给大家: 1.Java版 package com.eam.util;/* * The Alphanum A ...
- 网页返回unicode源码 python解码详细步骤
刚入门python! 记录一下网页返回源码,中文部分被unicode编码,python如何处理 1.先提取编码后的数据(如果不提取正篇源码直接unicode解码,解码方法无法识别) 这个步骤属于逻辑问 ...
- 1008.Django模型基础03
一.关系表的数据操作 关系表中的数据操作 查看数据库中的表结构 一对多表关系数据的添加: 1. 第一种方式就是跟之前一样,用传参的方法添加,需要注意的是外键的值必须是关联表中已存在的值: 2. 第二种 ...
- sql server 索引检测
-- 声明表变量 DECLARE @userTable TABLE (table_name NVARCHAR(20)); -- 将源表中的数据插入到表变量中 INSERT INTO @userTabl ...
- 测试环境docker化实践
测试环境对于任何一个软件公司来讲,都是核心基础组件之一.测试环境伴随着发展也从单一的几套环境发展成现在的任意的docker动态环境+docker稳定环境环境体系.期间环境系统不断的演进,去适应集群扩张 ...
- lnmp重新安装mysql
安装mysql好长时间,一直没去管,后来一直频繁重启,各种网上找方案去解决,最后问题太异常,一顿操作猛如虎之后把mysql彻底搞垮,无奈只能进行重装. whereis mysql mysql: /us ...