目录
- 前言
- 一、前端
- 二、后端
- 1.接收前端请求的api
- `如下是继续向其他接口请求的api`
- `如下是直接返回前端数据的api`
- `甚至可以返回图片`
- 2.模拟GPT的接口
前言
我们在和GPT交流的时候发现GPT总是逐字的显示,因为GPT是一种基于神经网络的自然语言处理模型,它的训练数据是从大量的文本语料库中获取的。在训练过程中,GPT会学习到文本的语法、语义和上下文信息,并尝试预测下一个单词或字符。因此,当GPT接收到一段文本时,它会根据之前学习到的知识,逐字分析并返回相应的数据。这种逐字返回数据的行为是GPT模型的自然表现,也是它能够生成连贯、流畅文本的重要原因之一。
但当我们想研发一个套壳网站时,因为GPT会逐字返回数据,那么我们也需要逐字为前端提供数据,我们可以使用Websocket,SSE,长轮询的方式来实现,这些网络上有太多例子,这次用.net 丰富的对异步支持,编写异步流式返回数据。
一、前端
通过fetch的方式向后端请求数据,并通过时时读取异步流的方式将数据渲染在页面中
<div>
<input type="button" id="getdata" value="获取数据" />
<br />
<textarea cols="100" rows="50" id="data" style="width: 500px;"></textarea>
</div>
<script>
$(document).ready(function () {
$("#getdata").click(async function fetchText() {
fetch('/Data/GetText')
.then(response => {
const reader = response.body.getReader();
let result = '';
return reader.read().then(function processResult({ done, value }) {
if (done) {
return result;
}
result += new TextDecoder('utf-8').decode(value);
// 将result显示在页面上
$('#data').html(result);
return reader.read().then(processResult);
});
})
.catch(error => {
console.error(error);
});
})
});
</script>
二、后端
1.接收前端请求的api
如下是继续向其他接口请求的api
/// <summary>
/// 向模拟的GPT后端发送请求
/// </summary>
/// <returns></returns>
public async Task GetText()
{
var url = "http://localhost:5041/Data/GetStreamData";
var client = new HttpClient();
using HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
var response = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead);
await using var stream = await response.Content.ReadAsStreamAsync();
var streamReader = new StreamReader(stream);
var buffer = new Memory<char>(new char[5]);
int writeLength = 0;
while ((writeLength = await streamReader.ReadBlockAsync(buffer)) > 0)
{
if (writeLength < buffer.Length)
{
buffer = buffer[..writeLength];
}
await Response.WriteAsync(buffer.ToString());
}
}
HttpCompletionOption枚举有两个值,默认情况下使用的是ResponseContentRead
- ResponseContentRead:等到整个响应完成才完成操作
- ResponseHeadersRead:一旦获取到响应头即完成操作,不用等到整个内容响应
如下是直接返回前端数据的api
/// <summary>
/// 不需要转发可以使用这个接口,也可以实现逐字返回数据
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public async Task GetData(string value)
{
List<string> strings = new List<string>() { "兰", "叶", "春", "葳", "蕤", ",", "桂", "华", "秋", "皎", "洁", "。",
"欣", "欣", "此", "生", "意", ",", "自", "尔", "为", "佳", "节", "。",
"谁", "知", "林", "栖", "者", ",", "闻", "风", "坐", "相", "悦", "。",
"草", "木", "有", "本", "心", ",", "何", "求", "美", "人", "折", "!" };
string result = string.Empty;
for (int i = 0; i < strings.Count; i++)
{
result = strings[i];
await Response.WriteAsync(result);
await Task.Delay(100);
}
}
甚至可以返回图片
<input type="button" id="getimg" value="获取图片" />
<img id="img" style="width: 500px;" />
<script>
$(document).ready(function () {
$('#getimg').click(async function () {
const streamingImage = document.getElementById('img');
fetch('/Data/Stream', {
method: 'GET',
headers: {
'Content-Type': 'application/octet-stream'
}
}).then(response => {
// 将响应体转换为可读流
const reader = response.body.getReader();
// 创建一个Blob对象来存储图像数据
let chunks = [];
let blob = new Blob(chunks, { type: 'image/jpeg' });
// 定义一个函数来处理每个数据块
function handleDataChunk(chunk) {
chunks.push(chunk);
blob = new Blob(chunks, { type: 'image/jpeg' });
streamingImage.src = URL.createObjectURL(blob);
}
// 定义一个函数来处理可读流的数据
function readStream() {
reader.read().then(({ done, value }) => {
if (done) {
console.log('Stream finished');
return;
}
handleDataChunk(value);
readStream();
});
}
// 开始读取可读流的数据
readStream();
}).catch(error => {
console.error('Fetch error:', error);
});
})
});
</script>
public async Task Stream()
{
string filePath = "pixelcity.png";
new FileExtensionContentTypeProvider().TryGetContentType(filePath, out string contentType);
Response.ContentType = contentType ?? "application/octet-stream";
var fileStream = System.IO.File.OpenRead(filePath);
var bytes = new byte[1024];
int writeLength = 0;
while ((writeLength = fileStream.Read(bytes, 0, bytes.Length)) > 0)
{
await Response.Body.WriteAsync(bytes, 0, writeLength);
await Task.Delay(100);
}
}
2.模拟GPT的接口
代码如下(示例):
/// <summary>
/// 模拟GPT接口
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public async Task GetStreamData()
{
string filePath = "文档.txt";
Response.ContentType = "application/octet-stream";
var reader = new StreamReader(filePath);
var buffer = new Memory<char>(new char[5]);
int writeLength = 0;
//每次读取5个字符写入到流中
while ((writeLength = await reader.ReadBlockAsync(buffer)) > 0)
{
if (writeLength < buffer.Length)
{
buffer = buffer[..writeLength];
}
await Response.WriteAsync(buffer.ToString());
await Task.Delay(100);
}
}