Node.js对接ChatGPT的流式接口-----满满的干货!!!!

一 、使用原生nodejs来对接ChatGPT API流式响应:

  1. 首先得创建一个空的文件夹,在vscode中打开
  2. 安装可以直接运行TS的开发工具
npm i -g ts-node-dev
  1. 新建TS文件
  2. 在vscode中可以有node一些内置包的提示
npm i -D @types/node
  1. 在创建的TS文件中输入代码
// 从http中导入createServer方法,目的是创建一个web服务
import {createServer} from 'http'

createServer((req, res) => {
    res.end('ok')
}).listen(9001)// 这个9001是监听的端口号
console.log('http://localhost:9001');
  1. 在终端运行
    tsnd server.ts => 我这里的server是我起的名字,你们根据自己起的名字写

最后效果如图所示:

​ 因为我代码里面写的端口号是9001,所以我输出的是http://localhost:9001,最后运行结束后,终端里面会出现log出来的接口,Ctrl+单击终端中的链接,浏览器就会出现我代码中的OK。
在这里插入图片描述
7. 如果以上都正确的话,就说明我们基本的效果已经出来了
8. 接下来就可以对接ChatGPT的接口了
https://platform.openai.com/docs/api-reference/making-requests
这个网址需要开启魔法小猫🧙‍♂️
打开上面的链接(ChatGPT的官方文档),找到下图这个地方,这个图片中就是ChatGPT的接口和其他一些东西了
在这里插入图片描述

  1. 新建一个窗口(终端),安装axios
npm i axios
  1. 接着就是在TS文件中导入axios和api实例
// 导入axios
import axios from 'axios'
// 创建axios的一个api实例
const api = axios.create({
    baseURL:'https://api.openai.com/v1',//这里写的就是ChatGPT的接口
    timeout: 5000,// 可以不写
    headers: {
        'Content-Type': 'application/json', // 这个可以不写,重要的是下面的Authorization
        Authorization: `Bearer ${}`// 这里是咱们自己的token,看env文件,这里我把key放到了环境变量中
    }
})
  1. 创建一个env文件
    这里的key就是之前群里发的那个Excel表格里面的东西
OPENAI_API_KEY = 你自己的key
  1. 因为nodejs中无法直接使用env的文件,所以这里需要安装一个包
npm i dotenv
  1. 接着就导入dotenv
    从一下代码中可以看到Authorization后面添加了东西,其实这是引入dotenv/config后,会自动加载dotenv文件,并且会输出到process.env中去,然后后面再加上OPENAI_API_KEY就可以了。这就设置好了最基本的axios实例
import 'dotenv/config'

const api = axios.create({
    baseURL:'https://api.openai.com/v1',//这里写的就是ChatGPT的接口
    timeout: 5000,// 可以不写
    headers: {
        'Content-Type': 'application/json', // 这个可以不写,重要的是下面的Authorization
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`// 这里是咱们自己的token
    }
})
  1. 接下来就是createServer方法中的了
createServer(async (req, res) => {
  // 这里是ChatGPT的接口根地址,返回的数据是JSON格式
  const {data} = await api.post("chat/completions", {
    // 这里是ChatGPT的JSON数据
    model: "gpt-3.5-turbo",
    messages: [{ role: "user", content: "Say this is a test!" }],
    max_tokens: 30,// 测试的时候加一个max_tokens,目的是控制ChatGPT的输出长度,可以减少请求的时间
    // temperature: 0.7,
  });
//   res.end无法直接返回JSON格式,需要利用JSON.stringify()转成字符串
  res.end(JSON.stringify(data));
}).listen(9001); // 这个9001是监听的端口号

上面代码中的这个部分是ChatGPT官方文档中的-d这个部分的东西。content: “Say this is a test!” 中引号里面的东西就是咱们平时向小柴提问的问题

{
    // 这里是ChatGPT的JSON数据
    model: "gpt-3.5-turbo",
    messages: [{ role: "user", content: "Say this is a test!" }],
    max_tokens: 30,// 测试的时候加一个max_tokens,目的是控制ChatGPT的输出长度,可以减少请求的时间
    // temperature: 0.7,
}
  1. 因为请求的速度会很慢,所以需要安装proxy
npm i socks-proxy-agent
  1. 导入socks-proxy-agent
import {SocksProxyAgent} from 'socks-proxy-agent'
  1. 接着就是在env和TS中写入代理地址
    在这里插入图片描述
    从上述图片中可以看到,红框框中的地址是7890,所以我这里写的是7890,你们需要根据自己的来写
SOCKS_PROXY = socks5://127.0.0.1:7890
httpsAgent: new SocksProxyAgent(process.env.SOCKS_PROXY)

如图所示:这就是运行出来的结果,ChatGPT直接返回的是一个JSON对象,最重要的是choices中的。可以看到message下的content是直接返回的一个内容
在这里插入图片描述
18. 接着就试着一下输出stream格式

// 导入stream
import { Stream } from 'stream';

createServer(async (req, res) => {
  // 这里是ChatGPT的接口根地址,返回的数据是JSON格式。post默认的是任何格式,但是这里是stream格式
  const {data} = await api.post<Stream>("chat/completions", {
    // 这里是ChatGPT的JSON数据
    "model": "gpt-3.5-turbo",
    "messages": [{ "role": "user", "content": "Say this is a test!" }],
    max_tokens: 30,// 测试的时候加一个max_tokens,目的是控制ChatGPT的输出长度,可以减少请求的时间
    // temperature: 0.7,
    stream:true,
  },{
    // 设置返回的数据类型为stream
    responseType:'stream'
  });
  data.pipe(res);// 可以直接流向res响应的对象
//   res.end无法直接返回JSON格式,需要利用JSON.stringify()转成字符串。但是因为ChatGPT的返回是stream格式,所以需要用pipe方法将stream转成字符串,这里就把JSON给注销了
//   res.end(JSON.stringify(data));
}).listen(9001); // 这个9001是监听的端口号

在这里插入图片描述
19. 也可以试着更改message中的内容

"messages": [{ "role": "user", "content": "你可以讲一个爱情故事吗?" }],
max_tokens: 100,// 并且,我把这里的最大限度设成100,在运行过程中就可以看到页面在不停输出,直到我设置的最大限度为止
  1. 接下来就开始前端的业务了
  2. 创建一个html文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>流式响应</title>
</head>
<body>
    <p>流式响应是指服务器在发送响应时,可以不等待客户端请求,而是直接发送响应,这样可以减少服务器的响应时间,提高服务器的性能。</p>
</body>
</html>
  1. 再来看一下req
    依然是在刚刚的TS文件当中,在createServer中所有代码之前加上下方代码即可
createServer(async (req, res) => {
    console.log(req.url);// 来看一下req的url,看他请求的是什么
    return res.end(req.url);
}).listen(9001); // 这个9001是监听的端口号

如图所示,这就是浏览器页面中输出的东西
在这里插入图片描述
所以我们需要实现一个界面,上方url中的地址是/的时候显示html页面,是chat的时候就是接口的东西
23. 请求的是跟地址
但是如果地址后面有参数的话,会被忽略掉,所以我们需要将url转化为一个URL对象。这里就需要改刚刚19.2步的代码了

// console.log(req.url);// 来看一下req的url,看他请求的是什么
// 因为如果地址后面有参数的话,会被忽略掉,所以我们需要将url转化为一个URL对象
const url = new URL(req.url!,'file:///')
// return res.end(url.pathname),// 这里输出url的pathname,也就是请求的接口地址,他就会是一个纯路径
    
// 做个判断,看看请求的是什么接口
switch (url.pathname) {
	// 让数据以流的形式返回给客户端
	case "/":
		createReadStream("./index.html").pipe(res);
		break;
}
  1. 如图所示,这就是运行的结果了。页面显示的是刚刚html中写的内容
    在这里插入图片描述
  2. 请求的是chat
    这里就是再加一个case,然后后面跟的是之前写的东西,就直接把那一部分的代码XC过来就可以
// 做个判断,看看请求的是什么接口
  switch (url.pathname) {
    // 让数据以流的形式返回给客户端
    case "/":
      createReadStream("./index.html").pipe(res);
      break;
    // 如果请求的是/chat接口的话,执行的就是之前写的ChatGPT接口那一部分的内容了
    case "/chat":
      // 这里是ChatGPT的接口根地址,返回的数据是JSON格式。post默认的是任何格式,但是这里是stream格式
      const { data } = await api.post<Stream>(
        "chat/completions",
        {
          // 这里是ChatGPT的JSON数据
          model: "gpt-3.5-turbo",
          messages: [{ role: "user", content: "你可以讲一个爱情故事吗?" }],
          max_tokens: 100, // 测试的时候加一个max_tokens,目的是控制ChatGPT的输出长度,可以减少请求的时间
          temperature: 0.7,
          stream: true,
        },
        {
          // 设置返回的数据类型为stream。stream是node.js中专门用来处理流数据的。他可以不断的将数据流输出到res响应的对象中,所以它可以实现流式的传输数据
          responseType: "stream",
        }
      );
      data.pipe(res); // 可以直接流向res响应的对象
      break;
  }
  1. 如果请求的是其他地址
    如果请求的是根地址和chat之外的地址的话,直接返回一个空的字符串
 default:    // 如果请求的接口地址不在上面定义的接口地址中,就返回一个空的字符串    res.end('')
  1. 注意一点

messages: [{ role: "user", content: "你可以讲一个爱情故事吗?" }]这一部分的内容应该是由前端那边传过来的,所以我们需要把他设置成变量

//   entries()是返回了键值对的一个数组;Object.fromEntries()是将键值对数组转化为一个对象
const query = Object.fromEntries(url.searchParams.entries());
console.log(query);

如下图所示,因为我url后面没有接参数,所以我终端输出的是一个空的对象。为什么会有两个呢?因为一般浏览器还会发一个favicon的请求,所以会多一个空的对象,不用管它
在这里插入图片描述
所以下方的请求chat中的message就可以写成query.prompt,但是在写的时候一定要确保query.prompt存在,所以一定要在刚开始加上判断

case "/chat":
	res.setHeader("Content-Type", "text/event-stream");// 一定要加,要不然浏览器控制台会报错,报错信息看下方图片
	if(!query.prompt){
		res.statusCode = 400;
        return res.end(JSON.stringify({
        	message: "请输入提问内容"
        }))
    }
      // 这里是ChatGPT的接口根地址,返回的数据是JSON格式。post默认的是任何格式,但是这里是stream格式
      const { data } = await api.post<Stream>(
        "chat/completions",
        {
          // 这里是ChatGPT的JSON数据
          model: "gpt-3.5-turbo",
          messages: [{ role: "user", content: query.prompt}],// 但是一定要确保prompt是字符串格式且存在,所以在上面加一个判断
          max_tokens: 100, // 测试的时候加一个max_tokens,目的是控制ChatGPT的输出长度,可以减少请求的时间
          temperature: 0.7,
          stream: true,
        },
        {
          // 设置返回的数据类型为stream。stream是node.js中专门用来处理流数据的。他可以不断的将数据流输出到res响应的对象中,所以它可以实现流式的传输数据
          responseType: "stream",
        }
      );
      data.pipe(res); // 可以直接流向res响应的对象
      break;

html中的代码

 <script>
        // 在这里定义一个函数,方便调用
        function send(prompt){
            // 定义一个url,根地址是当前页面的地址
            const url = new URL('/chat',location.href)
            // 将prompt添加到url中,直接可以拼接参数,不需要手写。设置一个参数,键名为prompt,值为上面传进来的prompt
            url.searchParams.set('prompt',prompt)
            // url定义好之后就直接给es就行了
            const es = new EventSource(url)
            // es就可以监听到服务器的推送了。得到一个e是message响应的事件
            es.onmessage = (e) => {
                // e.data就是服务器返回的数据
                console.log(e.data);
            }
        }
        send('你好呀')
    </script>

但是后面你就会发现浏览器中的控制台会报错,这是因为在TS文件里面中的请求chat的那一块少写了东西res.setHeader("Content-Type", "text/event-stream"),这个代码我已经在上面写过了,不过你们可以尝试一下把这句话注了,然后看看报错。当把这个代码加上之后,再刷新一下页面,控制台就没有报错了,但是控制台中会输出一些东西(见下方图片)。但是有一个小bug,就是它会持续不断的输出,因为我们没有关闭它。可以翻到控制台的最后,可以看到[DONE],这个是openai返回的一个特殊的标记,表示这个代码已经完成了
在这里插入图片描述
在这里插入图片描述
所以我们可以在es.onmessage中加一个判断,当输出[DONE]的时候就关闭es,这样就不会有重复的内容了

es.onmessage = (e) => {
	// e.data就是服务器返回的数据
	console.log(e.data);
	if(e.data ==='[DONE]'){
	// 当服务器推送[DONE]时,关闭es
	return es.close();
}

返回字符串
从浏览器的控制台中可得知,我们每次输出的都是一个对象,所以这里我们把对象中输出的内容拿出来

es.onmessage = (e) => {
	// 定义一个data变量,将服务器返回的数据转换为JSON格式
	const data = JSON.parse(e.data);
	// 获取content。从浏览器的控制台中可以看到服务器返回的数据,里面的内容就在choices下面的delta中的content里。如果content为空,则给一个空的字符串
	const {content = ''} = data.choices[0].delta;
	console.log(content);
}
send('你好呀')

如图所示,这就是我们可以看到的一个实时输出的一个效果
在这里插入图片描述
在页面输出
在html中用标签输出,给他设一个id。然后在js代码中获取一下这个标签,在进行连接之前需要清空一下之前的记录,然后在收到任何content后,把这些内容加到这个标签当中。

<!-- 这里用了pre标签,因为它可以保留换行符,方便查看 -->
<pre id="out"></pre>

<script>
// 获取输出元素
const out = document.getElementById('out');
// 在连接之前清空之前的记录
out.innerHTML = ''

es.onmessage = (e) => {
	const {content = ''} = data.choices[0].delta;
	// 然后在收到任何一个content之后把他加进去
	out.innerHTML += content;
	console.log(content);
}
send('你好呀')
</script>

用文本框的形式输出

<!-- 一定要把文本框放到pre的上面,要不然可能会导致答案在上面 -->
<input type="text" id="input">

<script>
 const input = document.getElementById('input');
// 给input加一个事件监听
input.onkeydown = e => {
	// 按下回车键,就触发事件
	if (e.keyCode === 13) {
		// 获取输入框的值
		send(e.target.value)
	}
}
</script>

下面是一个B站的视频,大家可以看着视频整一下!
B站的视频

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值