如何在5分钟内用GPT调教专属“小黑子”?

释放双眼,带上耳机,听听看~!
学习如何在5分钟内使用GPT进行个性化调教,让AI生成专属于你的“小黑子”,提供快速高效的人工智能教程。

专栏目录

耗时一下午,我实现了 GPT Terminal,真正拥有了专属于我的 GPT 终端!

如何用 GPT 在 5 分钟内 ”调教“ 出一个专属于你的 ”小黑子“?

如何丝滑实现 GPT 打字机流式回复?Server-Sent Events!

我是如何让我的 GPT Terminal “长记性” 的?还是老配方!

一个合格的类 GPT 应用需要具备什么?一文带你打通 GPT 产品功能!

开发一个 ChatGPT 真的只是当 “接口侠” 吗?GPT Terminal 细节分享!

如何借助于 OpenAI 以命令的方式在 GPT 终端上画一只 “坤”?

不满足当 ChatGPT “接口侠”?轻松可视化 Fine-tuning 训练你的模型!

耗时一下午,我终于上线了我的 GPT 终端!(内含详细部署方案记录)

项目地址:GPT Terminal 项目中寻找答案。

至于我为什么不用前端直接去请求 GPT 服务,考虑有一下几点,供大家参考:

  1. 职责分离。GPT 服务属于第三方库,按照一般设计理念来看,需要交由后端处理,前端只需要负责请求后端。

  2. 便于扩展。之后在 GPT Terminal 中可能会引入用户服务以及 GPT 图片生成功能,为了避免功能都耦合到前端,导致前端臃肿,因此我选择将 GPT 服务抽取到后端。

前端请求后端

如下部分代码对应项目路径为:src/core/commands/gpt/subCommands/ChatBox.vue

const response = await fetch('http://127.0.0.1:7345/api/gpt/get', {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      message: message.value,
      role: role.value,
    }),
  });

  if (!response.body) return;
  const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
  while (true) {
    var { value, done } = await reader.read();
    if (done) break;
    value = value?.replace('undefined', '')
    console.log("received data -", value)
    output.value += value?.replace('undefined', '')
  }

前端向后端发起请求,得到请求响应体,然后通过 pipeThrough() 方法将其转换为一个文本解码器流(TextDecoderStream),这个流可以将字节流(如网络请求的响应)转换为Unicode字符串,最后调用 getReader() 方法返回一个 reader 对象,用于读取响应体数据。

读取时循环读取,并对数据做一些处理(数据流开头、结尾为 undefined),然后拼接到 output.value 中,渲染到页面中。这样的话 output.value 就是动态浮现的,给用户视觉效果即为 打字机

在这里大家很容易发现,后端与 GPT 服务的交互对于前端而言就是透明的,前端仅知道其响应是一个流式数据,其它一概不知。

说到这里,大家可能还有些疑惑,Server-Sent Events 似乎什么都还没配,前端不就是发了一个常规的 POST 请求嘛!我知道你很急,但你先别急,跟我慢慢往下看~重头戏是在后端与 GPT 服务的交互!

后端请求 GPT 服务

如下部分代码对应项目路径为:server/src/thirdpart/gptApi/gptApi.js

async function createChatCompletion(messages) {
  // 如下为 流式数据传输 写法
  const res = openai.createChatCompletion(
    {
      model: "gpt-3.5-turbo",
      messages,
      stream: true,
    },
    {
      responseType: "stream",
    }
  );
  return res
}

后端拿到前端传递的参数后,对角色进行简单的模板渲染,得到消息数组后,调用 GPT 服务。

其参数如下所示,设置 GPT 模型类型,传入消息数组。

关键在于,设置 stream 参数为 true。这一步就是告诉 GPT 服务,我需要获取流式响应!

而如果你只想要 GPT 给你回复整个消息内容,可以不设置 stream,即为普通响应。

接下来,关键在于后端是如何解析 GPT 返回的响应。

如下部分代码对应项目路径为:server/src/handler/gptStreamHandler.js

我将这部分的处理单独抽取到了 gptStreamHandler.js 中,将其与其它普通请求的处理区分开,便于之后扩展

res.setHeader("Cache-Control", "no-cache");
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Connection", "keep-alive");
res.flushHeaders();
const response = handlerFunction(req.body, req, res);
response.then((resp) => {
  resp.data.on("data", (data) => {
    console.log("stream data -", data.toString());
    const lines = data
      .toString()
      .split("nn")
      .filter((line) => line.trim() !== "");
    for (const line of lines) {
      const message = line.replace("data: ", "");
      if (message === "[DONE]") {
        res.end();
        return;
      }
      const parsed = JSON.parse(message);
      console.log("parsed content -", parsed.choices[0].delta.content);
      res.write(`${parsed.choices[0].delta.content}`);
    }
  });
});

该响应是一个流式响应,因此需要用事件回调函数来处理。具体来说,response.then() 是一个 Promise 对象的方法,用于处理异步操作的结果。其中 resp.data 是一个可读流对象,通过订阅 data 事件,可以在每次获取到数据时触发回调函数。

回调函数需要做的很简单,先将数据转换为字符串,然后使用 split() 和 filter() 方法将其分离为一个个独立的消息行。每一行都是以 data: 开头,如下所示:

data: {  
    "id":"chatcmpl-7RNOsBXERLBhETQxgg5RpF2EGDSpi",  
    "object":"chat.completion.chunk",  
    "created":1686759162,  
    "model":"gpt-3.5-turbo-0301",  
    "choices":[  
        {  
            "delta":{  
                "content":"你"  
            },  
            "index":0,  
            "finish_reason":null  
        }  
    ]  
}

数据看起来比较复杂,但是咱们重点只需要关注 choices.delta.content,这是我们真正需要的数据!

后端需要做的事情就是把这个数据返回给前端即可。当其读到 message === "[DONE]",这也就是 GPT 服务给我们提供的信号,告诉这个时候已经没有内容给你啦,你可以停止接收了。这样就实现了一次消息的回复!

相信细致的大家已经发现了,我还没有提到代码一开始响应的 header 设置,这正是 Server-Sent Events 的核心配置,是不是很简单?

res.setHeader("Cache-Control", "no-cache");
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Connection", "keep-alive");

只需要简单设置一下 header,即可实现服务端到客户端的流式传输!

但是很细致的大家又发现了,为什么 GPT服务向后端传输数据的时候,并没有设置 header 呢?原因我认为很简单,因为我们调用的是 Open AI 提供的 SDK 包。其对于响应的封装对于我们而言是透明的,也就是说,我们无需去设置,这些繁琐的操作 SDK 都帮我们做好啦!

成果

来吧,展示!让我们看看,经过这一系列骚操作之后,GPT Terminal 会为我们呈现什么样的效果?

如何在5分钟内用GPT调教专属“小黑子”?

总结

今天带着大家通过项目实战的方式,了解了 Server-Sent Events 的基本实现原理。

在此,我也有一点心得想与大家分享,在学习新技术的时候,一定不要畏手畏脚,总想着先把原理看会再去做,这其实是一种 纸上谈兵。只有真正地去实践,去动手做,才能更加深刻地理解其原理!在做与踩坑的过程中,去学习与理解,并及时地补充相关知识,这样最后学到的东西才是自己的!

好啦,今天就暂时告一段落啦!如果大家想要了解 GPT Terminal 项目的更多细节与解锁更多玩法的话,请到其主页查看哦。

看在我这么认真的份上,大家点个Star、点个赞不过分吧(磕头!)下期再见!

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

解读未来元宇宙发展趋势:腾讯云TVP葛颀分享观点

2023-11-24 6:15:14

AI教程

AI训练数据中添加水印的重要性及应用技术分析

2023-11-24 7:01:55

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索