【.Net/C#之ChatGPT开发系列】二、C#异步流+SSE通信实现ChatGPT流式响应并实现打字机效果

目录

1、C#异步流

2、SSE通信

​一、 服务端接口调整

二、Web端js脚本调整

三、服务端优化

四、Web端再次调整


前面我们利用了ChatGPT提供的聊天API接口,实现了一个简单的聊天应用,可以与ChatGPT进行基本的对话交互,今天我们继续,还请大家点个关注。

当你使用ChatGPT官网提供的聊天工具时,你会发现,ChatGPT的内容是一个字一个字输出的,而我们实现的却是整句的输入,这是如何实现的呢,这样做又是为什么?(公众号“猿惑豁”没有评论功能,我自问自答吧)

先说一下为什么,我们先看一下ChatGPT4.0的回答:

 

我选择NewBing的更有创造力模式,它是基于ChatGPT4.0的模型来生成回答。我的理解是:

当ChatGPT接收到用户的输入后,它将该输入作为上下文信息,通过处理,预测下一个可能出现的词语,并输出。然后再将新生成的词语与之间的输出内容结合,形成新的上下文信息,再预测下一个可能出现的词语并输出。过程一直持续下去,直到ChatGPT生成完整内容。同时也解决了此过程中长时间等待的问题,增强了体验。

开始之前,我们先来了解两个知识点:C#异步流和SSE技术。

1、C#异步流

异步流是一种可以使用 async foreach 语法来处理的数据流,它返回一个 IAsyncEnumerable<T> 类型,该类型支持异步迭代。异步流可以在不阻塞主线程的情况下,等待数据的异步生成和消费,是C#8.0新增功能。

//要创建一个异步流,你需要定义一个返回 IAsyncEnumerable<T> 的 async 方法,并在方法体中使用 yield return 语句来返回数据12。例如:
public static async IAsyncEnumerable<int> GetData()
{for (int i = 0; i < 3; i++){await Task.Delay(1000); // 模拟异步操作yield return i; // 返回数据}
}
//要使用一个异步流,你需要在 foreach 循环前加上 await 关键字,并等待每个元素的生成。例如:
await foreach (int number in GetData())
{Console.WriteLine(number); // 处理数据
}

2、SSE通信

SSE全称Server-Sent Events, 属于 HTML 5 规范中的一个组成部分,它是一种从服务端实时推送数据到浏览器端的技术。SSE 通信的优点是使用简单,不需要额外的组件,只需要基于HTTP协议即可。缺点是只能实现单向的数据传输,即从服务端到客户端,如果需要双向的数据通讯,可以考虑使用 WebSocket 技术。

客户端需要使用 EventSource 对象来监听服务端的接口,并注册相应的事件处理方法来接收和处理数据。例如:

var source = new EventSource("/subscribe");
source.onmessage = function(event) {// 处理数据console.log(event.data);
};
source.onerror = function(event) {// 处理错误console.log(event);
};

​我们利用C#异步流和SSE通信来构建ChatGPT对话,再合适不过。为了方便查阅代码区别,我将新建一个项目,命名为:ChatGPT.Demo2,和上一章的ChatGPT.Demo1一样,使用相同的配方。

​一、 服务端接口调整

打开Controllers/ChatController.cs文件,将原来的Input方法替换为以下内容:

[HttpPost]
public async Task Input([FromForm] string message)
{Response.Headers.Add("Content-Type", "text/event-stream");Response.Headers.Add("Cache-Control", "no-cache");var completionResult = _openAiService.ChatCompletion.CreateCompletionAsStream(new ChatCompletionCreateRequest{Messages = new List<ChatMessage>{ChatMessage.FromUser(message)},Stream = true,MaxTokens = 500,Model = OpenAI.ObjectModels.Models.ChatGpt3_5Turbo,});
​await foreach (var completion in completionResult){if (completion.Successful){await Response.WriteAsync($"ChatGPT:{completion.Choices.FirstOrDefault()?.Message.Content}");await Response.Body.FlushAsync();}else{if (completion.Error == null)throw new Exception("Unknown Error");
​await Response.WriteAsync($"{completion.Error.Code}: {completion.Error.Message}");await Response.Body.FlushAsync();}}
}

我们通过设置Content-Type为text/event-stream,允许服务器向客户端推送数据。通过设置Cache-Control为no-cache,告诉客户端或代理不要缓存响应,而应该每次都从服务器重新获取。

接着把上一节中的ChatCompletion.CreateCompletion改为ChatCompletion.CreateCompletionAsStream,该方法返回IAsyncEnumerable异步迭器:

然后我们可以使用await foreach语法糖,用来异步迭代一个IAsyncEnumerable序列,它类似于foreach,但是它会等待序列中的每个元素异步生成,而不是同步获取。这样可以在等待下一个元素时不阻塞当前线程,提高性能和响应性。

我们使用Response.WriteAsync来异步向HTTP响应的输出流中写入字符串,使用Response.Body.FlushAsync将HTTP响应的输出流中的缓冲数据发送到客户端,所有方法全部使用异步方法,提高性能和响应。

二、Web端js脚本调整

打开Views/Home/Index.cshtml文件,将页面中的js脚本替换为以下内容:

<script type="text/javascript">//内容显示框var messagesList = document.getElementById("messagesList")//消息输入框var messageInput = document.getElementById("messageInput");//发送按钮var sendButton = document.getElementById("sendButton");// 定义一个XMLHttpRequest对象,用于发送请求和接收响应var httpRequest = new XMLHttpRequest();
​//发送按钮绑定click事件sendButton.addEventListener("click", function (event) {var message = messageInput.value;if (message.length == 0) {alert('请输入聊天内容');return;}send(message);event.preventDefault();});
​function send(message) {//修改按钮状态sendButton.disabled = true;
​//向内容显示框中追加发送的内容var div = document.createElement("div");div.className = "alert alert-secondary";div.textContent = `Me:${message}`;messageInput.value = '';messagesList.appendChild(div);
​//向内容显示框中追加ChatGpt返回的内容div = document.createElement("div");div.className = "alert alert-primary";messagesList.appendChild(div);div.textContent = "……";
​//创建FormData格式消息var formData = new FormData();formData.set('message', message);
​httpRequest.onprogress = function (progressEvent) {//处理响应数据div.textContent = `GPT:${progressEvent.target.responseText}`;};//请求是否成功都会执行httpRequest.onloadend = function (progressEvent) {//恢复按钮状态sendButton.disabled = false;};
​//打开请求,设置请求方法和地址,并设置异步为truehttpRequest.open("POST", "api/chat", true);httpRequest.responseType = "text";// 设置请求头为text格式httpRequest.send(formData);//发送请求}
</script>

这里我继续使用XMLHttpReques来实现SSE通信,并没有使用EventSource实现,主要是考虑它的浏览器支持更广泛一些,以下是两者的主要区别:

​为了实现打字机效果,我们需要在请求过程中逐步接收数据,而不是等到请求完成时一次性接收所有数据。因此,我们在send方法里使用了httpRequest.onprogress事件,而不是httpRequest.onload事件。

同时,为了防止用户重复点击发送按钮,我们在send方法里对发送按钮进行了锁定。并使用httpRequest.onloadend对按钮解出状态,onloadend事件无论请求是成功还是失败,在请求结束时都会触发。

F5启动项目看一下效果:

在和ChatGPT对话过程中,有时候它的回答并不符合我们的期望,或者我们的输入可能有误,想让它停止回答,然后重新调整对话内容,此时增加一个停止响应的功能很实用,我们来实现一下。

三、服务端优化

为了优化服务端的性能,我们需要对Controllers/ChatController.cs中的Input方法添加一个CancellationToken类型的参数cancellationToken,用于检测HTTP请求是否被客户端或服务器中止了。如果是,cancellationToken的IsCancellationRequested属性将变为true,我们便可以取消正在运行的任务,避免浪费资源。所以我们要把cancellationToken传递给所有异步方法,让它们能够响应取消信号。

四、Web端再次调整

打开Views/Home/Index.cshtml文件,在

发送按钮后边增加一个停止响应按钮:

​<input type="button" id="stopButton" value="停止响应" class="btn btn-warning" disabled />

找到js脚本中sendButton的定义,增加停止响应按钮代码和事件:

//停止响应按钮
var stopButton = document.getElementById("stopButton");
//停止响应信号
var stopRequest = false;
//停止响应按钮绑定click事件
stopButton.addEventListener("click", function (event) {stopRequest = true;event.preventDefault();
});

在这里我们定义了一个stopRequest变量来接收停止信号,当触发停止响应事件时,stopRequest被设置为true,当请求结束时,stopRequest会重置为false。

在send方法中新增XMLHttpRequest的onreadystatechange事件调用,代码如下:

 //监听请求状态
httpRequest.onreadystatechange = function () {if (stopRequest) {httpRequest.abort();stopRequest = false;}
};

onreadystatechange事件是一个属性,它存储了一个函数(或函数名),每当XMLHttpRequest的readyState属性改变时,就会调用该函数,readyState的值有4个,0表示请求未初始化;1表示服务器连接已建立;2表示请求已接收;3表示请求处理中;4表示请求已完成,且响应已就绪。因此我们可以根据它来执行stopRequest的重置任务。

httpRequest.abort方法用于取消已经发送的请求。当一个请求被取消时,readyState属性会被设置为0。

同时增加对停止响应按钮的状态控制,当处理SSE通信时,按钮变为可用,当请求结束时,状态变为不可用。

​F5启动项目看一下效果:

今天就先到这里,下节我们继续探索如何实现上下文聊天功能和会话管理功能,请大家多多关注。

源码地址:GitHub - ynanech/ChatGPT.Demo: ChatGPT机器人开发示例

创作不易,转载请注明博文地址,否则禁转!!! 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RocketMQ4.4.0搭建过程记录

版本选型 https://zhuanlan.zhihu.com/p/573949726 官方demo https://rocketmq.apache.org/zh/docs/quickStart/01quickstart/ 官方部署方案选型 https://rocketmq.apache.org/zh/docs/deploymentOperations/01deploy rocketmq-client官方支持 https://github.com/apache/rocke…

北大直博保送生论文涉嫌抄袭?原作者实名举报,北大南开火速调查

【导读】保送北大直博的学生被扒出抄袭了川大学生的SCI论文&#xff0c;还是从论文机构买的&#xff1f; 南开保送北大直博的学生&#xff0c;抄袭川大学生的SCI论文发了本普刊&#xff0c;还是直接英翻中&#xff1f; 更为离奇的是&#xff0c;这篇抄袭论文似乎是从论文辅导…

完成童年的梦想,用python画一个奥特曼

上班闲来无事 画个凹凸曼给女同事博博她开心。 首先奥特曼是由斜的椭圆&#xff0c;圆角矩形&#xff0c;圆形&#xff0c;以及曲线的组成的。此处绘制中&#xff0c;主要应用了曲线的的绘制&#xff0c;将奥特曼画的比较饱满。 本次绘制过过程中&#xff0c;自己编写了两个函…

OpenAI大动作|山姆·奥特曼亲探 GPT4: 极速+便宜将变成现实!

Laf 公众号已接入了 AI 绘画工具 Midjourney&#xff0c;可以让你轻松画出很多“大师”级的作品。同时还接入了 AI 聊天机器人&#xff0c;支持 GPT、Claude 以及 Laf 专有模型&#xff0c;可通过指令来随意切换模型。欢迎前来调戏&#x1f447; <<< 左右滑动见更多 &…

Sam Altman 山姆奥特曼:强化学习进展 Reinforcement Learning Progress

目录 Reinforcement Learning Progress 强化学习进展 PPO(近端策略优化)

Sam Altman 山姆奥特曼:How To Invest In Startups如何投资初创公司

How To Invest In Startups 如何投资初创公司 There is a lot of advice about how to be a good startup founder. But there isn’t very much about how to be a good startup investor. 关于如何成为一名优秀的创业公司创始人&#xff0c;有很多建议。但是&#xff0c;关于…

危险试探,产品经理赋予AI人格来打造品牌忠诚度

图片来源&#xff1a;由无界 AI工具生成 你可能不会相信&#xff0c;你的手机很可能变成你的虚拟情人&#xff0c;升级情人需要升级手机&#xff0c;而你从此再也不想换其他品牌手机。 AI时代&#xff0c;赋予产品以人格&#xff0c;让用户爱上产品&#xff0c;这或许是接下来产…

爬虫 post请求百度翻译 v2transapi 爬取信息失败

问题&#xff1a;在写爬虫的时候&#xff0c;使用到了eidtplus&#xff0c;将图中的表单数据复制下来之后&#xff0c;放入eidtplus添加成字典数据格式的时候&#xff0c;没有注意到query后面的单词的空格&#xff0c;导致爬虫爬取信息失败。 解决办法&#xff1a;将love单词前…

QT接入百度翻译api实现翻译

思路&#xff1a;将自己需要翻译的内容添加到百度翻译的api里面&#xff0c;然后通过get方法发送请求&#xff0c;异步接收返回的json格式数据并解析&#xff0c;然后将解析后的内容显示到界面。 步骤&#xff1a; 1.要实现该功能首先需要百度api的相关信息&#xff0c;因此需…

百度翻译API的调用

首先需要注册一个账号并申请成为个人开发者获取APP ID和密匙 百度翻译开放平台进入该网站注册即可 完成之后即可点击开通翻译其他服务 选择通用翻译并选择标准版&#xff0c;然后填入信息即可&#xff0c;服务器地址百度搜索IP地址&#xff0c;然后填写搜到的本机地址就可以了…

调用百度翻译api(超简单)

&#xff08;一共十行代码在最下面&#xff09; 1.打开百度翻译官网network找api 2.查看网址 和请求方式 3.查看传入的字典参数 4.返回的参数解析 返回的参数 s{errno: 0, data: [{k: good, v: adj. 好的; 优秀的; 有益的; 漂亮的&#xff0c;健全的 n. 好处&#xff0c;利益…

【基于stm32】【附代码】两块蓝牙模块HC-05进行通讯,不占用串口一的代码教程

目录 前言 一、两块hc-05蓝牙通讯的连接 二、使用步骤 1.hc-05从机&#xff0c;该教程主要是应用于【收数据】 usar.h代码 usart.c代码&#xff08;只引用stm32的串口2&#xff0c;串口一用于烧程序&#xff09; main.c 2.hc-05主机&#xff0c;该教程主要是应用于【发…

chatgpt赋能python:烧录单片机程序:Python的力量

烧录单片机程序&#xff1a;Python的力量 随着技术的发展和人类渴求的不断追求&#xff0c;电子设备的普及程度越来越高。在一个电子设备内部&#xff0c;单片机的应用非常广泛。然而&#xff0c;单片机作为计算机的重要组成部分&#xff0c;也需要相对应的程序来实现不同的功…

51单片机串口

该部分的笔记来自视频教程链接https://www.bilibili.com/video/BV1bt4y197NR/?spm_id_from333.788&vd_sourceb91967c499b23106586d7aa35af46413 一、51单片机串口基础介绍 一般的应用层的协议中采用和校验或CRC校验&#xff0c;而奇偶校验还是解决基本通信中的帧格式中的…

初学者入门:认识STM32单片机

本教程含有较多专业词汇&#xff0c;大部分时候&#xff0c;不完全理解并不影响继续往下阅读&#xff0c;大家只需要了解大致的概念即可。当然&#xff0c;也鼓励大家多查百度和多问chatgpt&#xff0c;让自己学会的更多。 什么是单片机&#xff1f; 单片机&#xff0c;就是把…

chatgpt赋能python:Python单片机:从入门到实践

Python单片机&#xff1a;从入门到实践 近年来&#xff0c;Python在嵌入式领域越来越受到开发者的青睐。Python具有易学易用的特点&#xff0c;方便开发者快速实现单片机的开发。本文将介绍Python单片机的基础知识以及实践应用。 Python单片机的基础知识 Python单片机用的是…

chatgpt赋能python:Python烧录单片机:快速的开发工具

Python烧录单片机&#xff1a;快速的开发工具 简介 Python是一种高级的编程语言&#xff0c;被广泛应用于各种领域&#xff0c;包括机器学习、数据分析和物联网等领域。Python的易用性和简洁性已经成为其成功的关键因素之一。Python也能在烧录单片机时提供极大的方便性和灵活…

推荐给程序员的书:七月图书推荐

七月&#xff0c;图灵原创书相继出炉&#xff0c;并在网店的排行榜上荣登前三甲&#xff0c;图灵原创书的作者皆是各社区的领军人物&#xff0c;有着相当深厚的技术功底&#xff0c;这是图灵原创书在《结网》后的一个跨越。 本月推荐&#xff0c;是本版书与外版书相结合&#x…

这五本 Python 急速入门必读的书,送给正在学习 Python 的你!

书籍是人类进步的阶梯&#xff0c;这句话从古至今都是适用的。为什么会这么说呢&#xff1f;书籍&#xff0c;它记录了人们实践的经验&#xff0c;这些经验有助于我们快速的学习&#xff0c;对于编程学习来说也不例外&#xff0c;今天就给大家带来了以下的书籍干货&#xff0c;…

人际沟通必看的书推荐

人际沟通与口才训练方面的书籍我推荐你看两本&#xff08;并且看这两本就完全足够了&#xff09;&#xff0c;一本是《沟通与说服必读12篇》&#xff0c;另一本是《演讲与口才必读12篇》&#xff0c;注意这两本书都仅能从12READS官网购买&#xff0c;避免广告&#xff0c;地址请…