共计 11771 个字符,预计需要花费 30 分钟才能阅读完成。
我是跟着 B 站一个老师写的:B 站老师视频
文章末尾附全部代码,如有那个地方不对的或者说是不准确的还请各位大佬指出来哇(っ˘зʕ•̫͡•ʔ
目录
1. 首先得创建一个空的文件夹,在 vscode 中打开
2. 安装可以直接运行 TS 的开发工具
3. 新建 TS 文件
4. 在 vscode 中可以有 node 一些内置包的提示
5. 在创建的 TS 文件中输入代码
6. 在终端运行
6. 如果以上都正确的话,就说明我们基本的效果已经出来了
7. 接下来就可以对接 ChatGPT 的接口了
8. 新建一个窗口(终端),安装 axios
9. 接着就是在 TS 文件中导入 axios 和 api 实例
10. 创建一个 env 文件
11. 因为 nodejs 中无法直接使用 env 的文件,所以这里需要安装一个包
12. 接着就导入 dotenv
13. 接下来就是 createServer 方法中的了
14. 因为请求的速度会很慢,所以需要安装 proxy
15. 导入 socks-proxy-agent
16. 接着就是在 env 和 TS 中写入代理地址
17. 接着就试着一下输出 stream 格式
18. 也可以试着更改 message 中的内容
19. 接下来就开始前端的业务了
19.1 创建一个 html 文件
19.2 再来看一下 req
19.3 请求的是跟地址
19.4 请求的是 chat
19.5 如果请求的是其他地址
19.6 注意一点
19.7html 中的代码
19.8 返回字符串
19.9 在页面输出
19.10 用文本框的形式输出
20. 全部代码
TS 代码
env 代码
html 代码
1. 首先得创建一个空的文件夹,在 vscode 中打开
2. 安装可以直接运行 TS 的开发工具
npm i -g ts-node-dev
3. 新建 TS 文件
4. 在 vscode 中可以有 node 一些内置包的提示
npm i -D @types/node
5. 在创建的 TS 文件中输入代码
// 从 http 中导入 createServer 方法,目的是创建一个 web 服务
import {createServer} from 'http'
createServer((req, res) => {res.end('ok')
}).listen(9001)// 这个 9001 是监听的端口号
console.log('http://localhost:9001');
6. 在终端运行
tsnd server.ts => 我这里的 server 是我起的名字,你们根据自己起的名字写
最后效果如图所示:
因为我代码里面写的端口号是 9001,所以我输出的是 http://localhost:9001,最后运行结束后,终端里面会出现 log 出来的接口,Ctrl+ 单击终端中的链接,浏览器就会出现我代码中的 OK
6. 如果以上都正确的话,就说明我们基本的效果已经出来了
7. 接下来就可以对接 ChatGPT 的接口了
https://platform.openai.com/docs/api-reference/making-requests
打开上面的链接(ChatGPT 的官方文档),找到下图这个地方,这个图片中就是 ChatGPT 的接口和其他一些东西了
8. 新建一个窗口(终端),安装 axios
npm i axios
9. 接着就是在 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 放到了环境变量中}
})
10. 创建一个 env 文件
这里的 key 就是之前群里发的那个 Excel 表格里面的东西
OPENAI_API_KEY = 你自己的 key
11. 因为 nodejs 中无法直接使用 env 的文件,所以这里需要安装一个包
npm i dotenv
12. 接着就导入 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
}
})
13. 接下来就是 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,
}
14. 因为请求的速度会很慢,所以需要安装 proxy
npm i socks-proxy-agent
15. 导入 socks-proxy-agent
import {SocksProxyAgent} from 'socks-proxy-agent'
16. 接着就是在 env 和 TS 中写入代理地址
从上述图片中可以看到,红框框中的地址是 7890,所以我这里写的是 7890,你们需要根据自己的来写
SOCKS_PROXY = socks5://127.0.0.1:7890
httpsAgent: new SocksProxyAgent(process.env.SOCKS_PROXY)
如图所示:这就是运行出来的结果,ChatGPT 直接返回的是一个 JSON 对象,最重要的是 choices 中的。可以看到 message 下的 content 是直接返回的一个内容
17. 接着就试着一下输出 stream 格式
// 导入 stream
import {Stream} from 'stream';
createServer(async (req, res) => {
// 这里是 ChatGPT 的接口根地址, 返回的数据是 JSON 格式。post 默认的是任何格式,但是这里是 stream 格式
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,
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 是监听的端口号
如下图所示,这就是运行出来的结果:
18. 也可以试着更改 message 中的内容
"messages": [{"role": "user", "content": "你可以讲一个爱情故事吗?"}],
max_tokens: 100,// 并且,我把这里的最大限度设成 100,在运行过程中就可以看到页面在不停输出,直到我设置的最大限度为止
19. 接下来就开始前端的业务了
19.1 创建一个 html 文件
流式响应
流式响应是指服务器在发送响应时,可以不等待客户端请求,而是直接发送响应,这样可以减少服务器的响应时间,提高服务器的性能。
19.2 再来看一下 req
依然是在刚刚的 TS 文件当中,在 createServer 中所有代码之前加上下方代码即可
createServer(async (req, res) => {console.log(req.url);// 来看一下 req 的 url,看他请求的是什么
return res.end(req.url);
}).listen(9001); // 这个 9001 是监听的端口号
如图所示,这就是浏览器页面中输出的东西
下方图片是 vscode 终端中输出的东西
所以我们需要实现一个界面,上方 url 中的地址是 / 的时候显示 html 页面,是 chat 的时候就是接口的东西
19.3 请求的是跟地址
但是如果地址后面有参数的话,会被忽略掉,所以我们需要将 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;
}
如图所示,这就是运行的结果了。页面显示的是刚刚 html 中写的内容
19.4 请求的是 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(
"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;
}
19.5 如果请求的是其他地址
如果请求的是根地址和 chat 之外的地址的话,直接返回一个空的字符串
default:
// 如果请求的接口地址不在上面定义的接口地址中,就返回一个空的字符串
res.end('')
19.6 注意一点
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(
"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;
19.7html 中的代码
但是后面你就会发现浏览器中的控制台会报错,这是因为在 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();}
19.8 返回字符串
从浏览器的控制台中可得知,我们每次输出的都是一个对象,所以这里我们把对象中输出的内容拿出来
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('你好呀')
如图所示,这就是我们可以看到的一个实时输出的一个效果
19.9 在页面输出
在 html 中用标签输出,给他设一个 id。然后在 js 代码中获取一下这个标签,在进行连接之前需要清空一下之前的记录,然后在收到任何 content 后,把这些内容加到这个标签当中。
19.10 用文本框的形式输出
20. 全部代码
TS 代码
// 从 http 中导入 createServer 方法,目的是创建一个 web 服务 import {createServer} from "http"; // 导入 axios import axios from "axios"; // 导入 dotenv import "dotenv/config"; // 导入 socks-proxy-agent import {SocksProxyAgent} from "socks-proxy-agent"; // 导入 stream import {Stream} from "stream"; // 导入 fs import {createReadStream} from "fs"; const socksProxy = process.env.SOCKS_PROXY; // 检查 socksProxy 是否为 undefined if (socksProxy === undefined) {throw new Error("SOCKS_PROXY environment variable is not defined"); } // 创建 axios 的一个 api 实例 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 }, // 因为我们请求的是 https,所以需要设置代理 httpsAgent: new SocksProxyAgent(socksProxy), }); createServer(async (req, res) => {// console.log(req.url);// 来看一下 req 的 url,看他请求的是什么 // 因为如果地址后面有参数的话,会被忽略掉,所以我们需要将 url 转化为一个 URL 对象 const url = new URL(req.url!, "file:///"); // entries()是返回了键值对的一个数组;Object.fromEntries()是将键值对数组转化为一个对象 const query = Object.fromEntries(url.searchParams.entries()); console.log(query); // return res.end(url.pathname),// 这里输出 url 的 pathname,也就是请求的接口地址,他就会是一个纯路径 // 做个判断,看看请求的是什么接口, 这个 url 中会包含接口的很多信息 switch (url.pathname) { // 让数据以流的形式返回给客户端 case "/": createReadStream("./index.html").pipe(res); break; // 如果请求的是 /chat 接口的话,执行的就是之前写的 ChatGPT 接口那一部分的内容了 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
( "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; default: // 如果请求的接口地址不在上面定义的接口地址中,就返回一个空的字符串 res.end('') } //res.end 无法直接返回 JSON 格式,需要利用 JSON.stringify()转成字符串。但是因为 ChatGPT 的返回是 stream 格式,所以需要用 pipe 方法将 stream 转成字符串,这里就把 JSON 给注销了 // res.end(JSON.stringify(data)); }).listen(9002); // 这个 9001 是监听的端口号 console.log("http://localhost:9002"); env 代码
OPENAI_API_KEY = 你自己的 key
# 这里我把 key 放到了.env 文件 (也就是环境变量) 中,这样在代码中就不需要写 key 了 # 但是如果要在代码中写 key,就需要把 key 写到代码中,这样不好,所以我把 key 写到了.env 文件中,这样在代码中就不需要写 key 了 # 这里的 key 是之前在 OpenAI 官网申请的,也就是群里那个文档中的东西,每个人都有的 # 这里写的是猫的地址 SOCKS_PROXY = socks5://127.0.0.1:7890html 代码
流式响应
原文地址: 使用原生 nodejs 来对接 ChatGPT API 流式响应