演示
项目地址
如果该项目对你有帮助,请点个star支持下吧!
- 线上体验地址:https://www.wang-xiaowu.site/chat-gpt/
- 仓库地址:https://github.com/behappy-project/behappy-chatgpt-assistant
- 技术沟通群二维码:https://raw.githubusercontent.com/wang-xiaowu/picture_repository/master/behappy_group.jpg
移动端
pdf格式无法展示动图:可点击该地址进行查看

PC端
pdf格式无法展示动图:可点击该地址进行查看

教程实现效果
pdf格式无法展示动图:可点击该地址进行查看

前置准备
openai
注册
- 关于注册账户可以查看我的这篇文章
- 不建议花钱买账户,一个是不确保它稳定。二来你花点时间熟悉这个东西也是帮助你长进的过程
API_KEY
- 地址:https://platform.openai.com/account/api-keys
- 登录点击个人头像,进入
View API keys - 首次进入应该是空的,点击
Create new secret key进行创建 - 记录下你创建的key,因为之后就没办法找回了
Api文档
文档中介绍了一些api的使用样例,包括但不仅限于:
列举支持的数据模型
聊天
图片功能
音频识别
......
文档中也列举了python和nodejs的代码使用样例,请求时需要带着认证header,如下格式:
Authorization: Bearer OPENAI_API_KEY
Chat Api的补充说明
model参数,我们需要使用的数据模型,案例中我们使用gpt-3.5-turbomessages参数,主要输入是消息参数。消息必须是一个对象数组,其中每个对象都有一个role(system、user或assistant)和content(消息的内容)。对话可以只有1条信息,也可以有许多。通常情况下,一次会话以system消息开头,然后是交替出现的user和assistant消息。system消息帮助设置assistant的行为。在官方文档的示例中,使用You are a helpful assistant.指示了assistant。后续的prompt优化就可以围绕着这部分来实现
让我们再多看几个例子来弄懂这几个角色是干嘛的
- 第一个请求我们使用
assistant角色问它哪只球队是2015年冠军,它帮我们列出了四大联盟各自的总冠军,这个答案是没问题的

- 第二个请求我们使用
system为它添加人设(测试发现gpt3.5并不总是关注system的预设行为,这一点官网也有指出),可以看到它现在只关注NBA

- 第三个请求我们使用
user,以用户身份问它谁是2022年总冠军。我们知道gpt3.5的数据模式是截止到2021年9月份,所以问它2022年的总冠军它肯定不知道是谁

- 所以第四个请求我们使用
assistant角色,它是用来存储先前的响应信息的。这一类消息也可以由我们编写,以帮助给予它所需要的信息。

我们用到最多的可能就是聊天api,这里还有涉及到的参数如
temperature、top_p、n等都会影响到你的输出结果,所以我们需要仔细了解这些参数的含义,才能更好的结合messages输出我们想要的结果
其他
- 可能会遇到登录不上的问题,例如遇到429限流,或者access denied等等,这类错误都是节点问题,此文档编辑于4.6,测试台湾节点可用
- 关于使用量:可以点击此地址查看
vercel
为什么使用vercel
- vercel是一个网站托管服务
- 其不仅支持静态网站,还支持部署serverless接口,意味着我们可以利用它部署我们的服务端项目。但需要记住Serverless 架构通常是无状态、不可变和短暂的。所以不要指望它放些内存数据
- 最主要的是:他免费!(这是我们用它的主要原因)
- 官方支持使用Nodejs 、 Go 、 Python 、 Ruby这几种语言来编写serverless接口,下文我们将会使用Nodejs编写服务端。
注册
- 这里没啥特殊的,使用github账户注册就好
其他
- 待会我们开发项目时,需要额外配置个
vercel.json文件,会在部署vercel时用到。见:https://vercel.com/docs/concepts/projects/project-configuration - 我们还需要用到vercel提供的cli,用来帮助我们测试、部署,相关指令见:https://vercel.com/docs/concepts/deployments/overview
- vercel自带一些环境变量,当然我们也可以配置运行时环境变量,见:https://vercel.com/docs/concepts/projects/environment-variables
- 既然是免费的,当然不会让我们无限用,所以vercel针对免费用户以及付费用户都会有些限制,见:https://vercel.com/docs/concepts/limits/overview
- vercel在部署好项目之后,会提供地址供我们访问。但自2021年5月起,这个地址已经无法在国内访问,但官方也给出了解决方案,见:

Nodejs
nodejs官网:https://nodejs.org/en/download
IDE
aardio
- 官网地址:https://www.aardio.com/
- aardio由国内大佬开发,专注桌面端应用。aardio有很好的扩展性,你可以使用它调用C,调用js,调用java...但作者老婆似乎病了,aardio也不打算再维护了,后续大概率就是逐渐被遗忘...我们这里不需要过多学习aardio的语法,全程写html+js,仅用它来发布exe程序即可。(btw,希望作者的老婆能好起来)
- aardio下载好,安装即可。不要放在带有中文或者空格的路径下
技术选型
| 功能 | 技术栈 | 版本 |
|---|---|---|
| 服务端语言 | Nodejs | 18.x |
| 服务端web框架 | Koa | 2.x |
| 客户端语言 | aardio | latest |
| 客户端UI框架 | ChatUI | 2.4.2 |
| JavaScript 库 | jQuery | 3.6.4 |
服务端开发
创建项目
- 创建个空项目,命名
chatGpt-project,创建好后cmd进入根目录

初始化package.json,执行
npm init,按需配置,这里我们一路默认即可,得到这样的一个文件
安装依赖
| 依赖 | 功能介绍 |
|---|---|
| @koa/cors | 解决跨域问题 |
| koa-body | 解析HTTP请求体 |
| koa-router | 路由配置 |
| openai | openai官方提供 |
| tunnel | 配置代理 |
| eslint-xxx | 代码格式化 |
| esm | 可以使用 import 和 export 关键字 |
安装运行时依赖,执行
npm i -S @koa/cors@4.0.0 @vercel/node@2.10.2 fs@0.0.1-security koa@2.13.4 koa-body@4.2.0 koa-router@12.0.0 openai@3.2.1 tunnel@0.0.6 axios@0.21.1 esm@3.2.25安装开发依赖:执行
npm i -D eslint@7.30.0 eslint-config-airbnb@18.2.1 eslint-plugin-import@2.23.4
目录结构建设
分别创建
bin,config,lib,routes文件夹,如下bin:存放我们启动脚本
config:存放我们的配置文件
lib:存放我们待会会用到的第三方包,我们封装下
routes:存放路由

代码开发
创建
bin/run文件作为启动脚本,内容如下1
2
3
require = require('esm')(module);
require('../index');

- 创建
config/config.json文件,作为我们的配置文件,将一些配置信息配置在里面,启动的时候替换value
1 | { |
创建
config/local.json文件,作为我们本地开发环境的配置文件(注:port不要配置为3000,待会儿会用到vercel,其启动端口默认占用3000)1
2
3
4
5
6
7
8
9{
"sys": {
"port": "4000"
},
"chatGpt": {
"host": "https://api.openai.com/v1",
"key": "替换成你的API_KEY"
}
}创建
config/index.js文件,启动项目时加载,执行替换配置信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57import {existsSync} from 'fs';
import {name} from '../package.json';
// 配置自检
export const envCfg = (() => {
// 加载本地配置
console.log(`环境: ${process.env.NODE_ENV}`);
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
console.log(`加载本地开发环境...`);
if (existsSync(`${__dirname}/local.json`)) {
const conf = require('./local.json');
console.log(`${name} 服务配置参数加载成功`);
return conf;
}
console.error(`${name} 服务配置自检未通过,服务已停止`);
process.exit();
}
// 非本地开发环境
const conf = require('./config.json');
// 配置自检
const checkProperty = (cfgNode) => {
let result = true;
for (const pro of Object.keys(cfgNode)) {
if (typeof cfgNode[pro] === 'object') {
result = checkProperty(cfgNode[pro]) && result;
continue;
}
// 服务调用参数配置
if (!process.env[cfgNode[pro]]) {
console.error(`参数: ${cfgNode[pro]} 未设置.`);
result = false;
} else {
cfgNode[pro] = process.env[cfgNode[pro]];
result = result && true;
}
}
return result;
};
if (!checkProperty(conf)) {
console.error(`${name} 服务配置自检未通过,服务已停止`);
process.exit();
} else {
console.log(`${name} 服务配置参数加载成功`);
return conf;
}
})();
// 系统配置
export const sysCfg = {
name,
port: envCfg.sys.port,
};我们再来封装下第三方包,创建
lib/openai.js文件- openai
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34const {Configuration, OpenAIApi} = require("openai");
const tunnel = require('tunnel');
const axios = require('axios');
module.exports = (opts = {}) => {
const configuration = new Configuration({
apiKey: opts.key,
});
let defaultOpts = {
timeout: 60000,
maxContentLength: 20 * 1024 * 1024,
maxBodyLength: 20 * 1024 * 1024,
withCredentials: true,
};
let isProduction = process.env.NODE_ENV === 'production';
if (!isProduction) {
// 本地开发,此处我配置的是我本地的代理地址和端口,需要改成你的。如果是clash的话默认都是7890
defaultOpts = {
...defaultOpts,
httpsAgent: tunnel.httpsOverHttp({
proxy: {
host: '127.0.0.1',
port: 7890,
}
})
}
}
const client = axios.create(defaultOpts);
const openai = new OpenAIApi(configuration, opts.host, client);
return async (ctx, next) => {
// 这样一来,我们就可以使用ctx.openai.xxx调用openai对应的api了
ctx.openai = openai;
await next();
};
};创建
routes/index.js,我们再开发两个路由,一个用于聊天,一个用于图片功能。同时别忘了一个根路由,部署完项目后,vercel默认会发送get请求到根路径,来判断项目是否部署成功1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60import Router from 'koa-router';
const router = Router();
/*聊天*/
router.post('/completions', async (ctx) => {
const params = {...ctx.request.body};
console.debug(__filename, '[createChatCompletion] Request params:', params);
try {
const response = await ctx.openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{
role: "user",
content: params.content
}],
});
if (response.status !== 200) {
console.error(response.statusText)
return ctx.body = response.statusText
}
ctx.body = response.data.choices[0].message
} catch (e) {
console.error(e.stack);
ctx.body = e.stack
}
});
/*图片*/
router.post('/imagesGenerations', async (ctx) => {
const params = {...ctx.request.body};
console.debug(__filename, '[imageGenerations] Request params:', params);
// 字数太多ai联想也慢,所以我们这里限制下
if (params.prompt.length >= 10) {
return ctx.body = "图片描述超过限制"
}
try {
// 支持的图片尺寸可以查看下官方文档,最小则为256x256
const response = await ctx.openai.createImage({
prompt: params.prompt,
n: 1,
size: "256x256",
});
if (response.status !== 200) {
console.error(response.statusText)
return ctx.body = response.statusText
}
ctx.body = response.data.data[0].url
} catch (e) {
console.error(e.stack);
ctx.body = e.stack
}
});
/*根路径*/
router.get('/',async (ctx) => {
console.debug('success deploy...');
ctx.body = "ok"
});
export default router;- 根路径下创建
index.js文件(必须命名index.js,测试其他名字在部署vercel时候不生效),作为启动类。内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43import Koa from 'koa';
import koaBody from 'koa-body';
import * as routes from './routes';
import {sysCfg, envCfg} from "./config";
import openai from "./lib/openai";
import cors from '@koa/cors';
const app = new Koa();
// ctx.openai
app.use(openai({...envCfg.chatGpt}))
// cors
app.use(cors({
origin: '*',
credentials: true
}))
// body parser
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 20 * 1024 * 1024,
},
}));
// routes
Object.keys(routes).forEach((k) => {
app.use(routes[k].routes())
.use(routes[k].allowedMethods());
});
// error handler
app.on('error', async (err, ctx) => {
ctx.status = 500;
console.error('×××××× System error:', err.stack);
});
// listening
const port = Number(sysCfg.port);
app.listen(port, '0.0.0.0')
.on('listening', () => {
console.log(`Listening on port: ${port}`);
});- 根路径下创建
启动测试前,我们修改下
package.json的scripts,方便后续使用(build指令必须有,部署vercel时候强制要求)
1 | "scripts": { |
- 启动,执行
npm run start,看到如下输出说明成功了
1 | npm run start |
- 发送请求测试下,看到如下结果说明成功

客户端开发
创建项目
- 打开aardio程序,选择web界面,并选择htmx项目

代码开发
- 我们双击main文件,看到灰色的界面之后双击即可打开代码界面
- 客户端主要用到的ui框架为ChatUI,阿里开发的。官网地址:https://chatui.io/docs/quick-start
- 这里我们做了些逻辑,当发送内容以
图片:开头,则说明请求图片接口,否则是聊天接口。然后利用jQuery+Ajax发送请求(希望大家没忘了jQuery这个老伙计是咋用的) - 具体代码如下,可以直接将其粘贴至main文件中。关于详细的讲解见注释部分
1 | import win.ui; |
点击运行,启动测试(这期间可以按F12打开开发者工具进行调试)
看到如下界面说明成功

Prompt问题优化,联系上下文
问题复现
这里我们复现个问题

我们明明刚跟它说完李白,程序就给"忘了"。
这是因为程序并没将上下文联系起来,如果我们将对话变成这样

程序就可以正确的将上下文联系起来了
代码调整
所以我们这里将客户端代码调整下(看注释)
1 | import win.ui; |
启动测试
这样一来就符合我们想要的结果了

项目上线
将项目部署到vercel
安装vercel-cli,执行
npm i vercel -g,并使用vercel --version查看安装是否成功(如果是windows,这里最好不要使用power shell,可能会不识别vercel命令)切回到服务端的根目录
先创建个
vercel.json文件,用于vercel部署引导,内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
"version": 2,
"builds": [
{
"src": "index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/index.js"
}
]
}执行
vercel login,会指引你到浏览器登录vercel接下来我们就需要正式部署了。我们在项目中使用环境变量来进行配置替换,所以这里我们执行
vercel --prod -e SYS_PORT=4000 -e CHAT_GPT_HOST=https://api.openai.com/v1 -e OPEN_API_KEY={这里替换成你的key}具体内容如下
1
2
3
4
5
6
7
8
9
10
11
12$ vercel --prod -e SYS_PORT=4000 -e CHAT_GPT_HOST=https://api.openai.com/v1 -e OPEN_API_KEY=sk-xxx
Vercel CLI 28.18.3
? Set up and deploy “D:\Project\behappy-project\chatGpt-project”? [Y/n] y
? Which scope do you want to deploy to? wang-xiaowu
? Link to existing project? [y/N] n
? What’s your project’s name? chat-gpt-project
? In which directory is your code located? ./
🔗 Linked to wang-xiaowu/chat-gpt-project (created .vercel)
🔍 Inspect: https://vercel.com/wang-xiaowu/chat-gpt-project/9dRaCg4ooTRJFphKBJAFxCA2bdRc [3s]
✅ Production: https://chat-gpt-project-sandy.vercel.app [36s]
❗️ Due to `builds` existing in your configuration file, the Build and Development Settings defined in your Project Settings will not apply. Learn More: https://vercel.link/unused-build-settings查看你的vercel -> dashboard,如下则成功

客户端测试
- 接下来我们改下客户端地址

- 测试效果

客户端发布
- 点击发布

- 更新排除目录

- 打开发布目录即可找到你发布的exe程序了

配置DNS解析,解决国内无法访问的情况
问题
在国内的话,这个域名只有在我们开了代理的情况下可以访问,所以这里我们需要购买域名并配置DNS解析,去"解除限制"
购买域名
购买域名的平台有很多,国外有google,Cloudflare。国内有阿里云,腾讯云,西部数据等。下面我们以西部数据举例
登录网站:https://www.west.cn/
点击登录,最好以微信登录,方便。首次注册需要你实名认证些东西,按指引做即可

点击域名注册

目前发现
.icu是最便宜的,一年7元,我买了.site。之后会进入检验当前域名是否已被注册的界面,如果没有那么恭喜你,可以点击立即注册按钮进行购买了
DNS解析
- 购买成功后,进入管理中心 -> 域名管理,找到你刚刚购买的域名,点击解析

- 将其配置成如下方式
| 主机名 | 类型 | 对应值 |
|---|---|---|
| @ | A | 76.76.21.21 |
| www | CNAME | cname-china.vercel-dns.com |

vercel配置Domains
回到你的vercel dashboard,按下图将你购买的域名配置进去

修改客户端代码,再次测试
修改连接地址为你的域名

效果

Stream流式应答,实现打字机效果
现象
我们平时使用ChatGPT的时候会发现它的回答实际是以类似打字机的方式回应的,我们也来做下调整。
服务端代码调整
在createChatCompletion的api中,我们将参数stream设置为true如:
stream: true,即可以实现streaming输出。该api还提供了第二个options参数,用来配置axios,这里将第二个参数配置为{responseType: 'stream'}其次,既然传输方式改了,我们就需要考虑下技术选型。以前我们使用的是客户端利用ajax一次性将服务端的数据"拿过来",而现在我们需要改变成服务器向客户端推送数据。关于推送数据,常用的方式除了 WebSocket,还有Server-Sent Events,这两篇文章都出自阮一峰老师的博客,可以看下。
而我们将会采用websocket。
为什么不用sse?一:sse只支持单向传输,也就是说客户端还是要以其他方式向服务端请求数据。二:EventSource仅支持监听
GET。安装
koa-websocket,服务端websocket实现:npm i -S koa-websocket@7.0.0添加文件
lib/chat.js,内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54module.exports = () => {
return async (ctx, next) => {
try {
ctx.websocket.on('message', async function (message) {
const params = message.toString()
if (!params || params.length === 0) {
return
}
console.debug(__filename, '[createChatCompletion] Request params:', params);
const streamResponse = await ctx.openai.createChatCompletion({
model: "gpt-3.5-turbo",
stream: true,
messages: [{
role: "user",
content: params
}],
}, {responseType: 'stream'});
if (streamResponse.status !== 200) {
console.error(streamResponse.statusText)
return ctx.websocket.send(streamResponse.statusText);
}
streamResponse.data.on('data', chunk => {
const lines = chunk
.toString()
.split('\n')
.filter((line) => line.trim().startsWith('data: '))
for (const line of lines) {
const message = line.replace(/^data: /, '')
if (message === '[DONE]') {
// 客户端判断输出内容是否是`[DONE]`
console.debug('内容结束...')
return ctx.websocket.send('[DONE]')
}
const json = JSON.parse(message)
const token = json.choices[0].delta.content
if (token) {
console.debug('发送...', token)
ctx.websocket.send(token)
}
}
})
});
} catch (e) {
console.error(e.message)
ctx.websocket.send(e.message)
} finally {
await next();
}
};
};修改
index.js,添加以下代码,使koa和websocket共享一个端口
1 | ...... |
客户端代码调整
这里参考了几个链接
- ChatUI提供了updateMsg函数,可以使用updateMsg监听result来实现打字机效果
- 使用 useState 需要注意的 5 个问题。最开始想用ChatUI提供的useState来存些状态数据,然后测试发现他这个东西本身也是个promise,不是实时的...
贴下完整改造后代码(看注释)
1 | import win.ui; |
启动测试
- 因为vercel目前还不支持websocket,所以我们只能在本地测试这一块的改造

- 启动测试后,效果图和文章开头的一样
语音功能
参考官网:https://platform.openai.com/docs/api-reference/audio
官方提供了两个接口
将音频转录为输入语言。
我们主要对接这个接口,该接口提供了几个参数
file:必选,要转录的音频文件,格式为以下格式之一:mp3、mp4、mpeg、mpga、m4a、wav或webm
model:必选,要使用的模型的ID。目前只有whisper-1可用。
prompt:一个可选的文本来指导模型的风格或继续以前的音频片段。提示应与音频语言匹配。
response_format:转录输出的格式,使用以下选项之一:json、text、srt、verbose_json或vtt。默认json
temperature:介于0和1之间。较高的值(如0.8)将使输出更随机,而较低的值(如0.2)将使其更集中和确定性。如果设置为0,模型将使用对数概率自动增加temperature,直到达到某些阈值。
language:输入音频的语言。以ISO-639-1格式提供输入语言将改善准确性和延迟。
将音频翻译成英语。
实现效果
pdf格式无法展示动图:可点击该地址进行查看

问题
aardio生成桌面端控制录音比较麻烦,所以下面的操作我们将会把客户端集成到h5
代码开发
依赖安装
1 | // 页面渲染 |

修改index.js,添加如下代码
1 | ... |
修改routes/index.js中根路径代码
1 | ... |
添加router,/audio/transcriptions
需要注意的是,之后我们需要将音频文件暂存磁盘,这里我是windows,配置在D://,按需自行修改
1 | router.post('/audio/transcriptions', async (ctx) => { |
添加/static/index.html
这里我们仅是将之前aardio中的页面copy过来,并添加录音功能
1 | <html> |
启动项目测试
浏览器输入
http://localhost:4000/
Fine-tune与Embedding
OpenAI 提供了两项基于 GPT-3 模型的能力:
- fine-tune 微调
- embedding 嵌入
fine-tune
一般称之为微调。
模型底层更通用,顶层更垂直,fine-tune 的原理是在不改动(或不能改动)预训练模型的基础上,在模型「顶层」增加分类器或一些特征映射,使微调后的模型能够更贴合实际使用场景。
在 OpenAI 中,可以在不改动 GPT-3 大模型的情况下,针对 prompt 和 completion 的进行训练,对「句式」、「情感」等特征进行优化。
OpenAI-fine-tune
GPT-3 已经预训练了大量的互联网内容。只需要在 prompt 里写少量的用例,他基本可以感知你的用意,并生成一段基本合理的 completion。这个功能一般叫做 "few-shot learning".
fine-tune 基于 few-shot learning,通过训练比写在 prompt 里更多的示例,可以得到更好的结果。模型被微调后,就不用在 prompt 里再写一些实例了。这样可以节省成本和耗时。
使用场景:
- 想让 GPT-3 按照某种格式来识别 Prompt ,或按照某种格式来回答
- 想让 GPT-3 按照某种语气、性格来回答
- 想让 completion 具有某种倾向
比如不进行 fine-tune 提问
1 | prompt: |
GPT-3 详尽的回答了你的问题,但是可能你只想知道是 男 or 女。
在 prompt 里加入示例
1 | prompt: |
经过提示的 prompt 会按照示例的格式回答你的问题。
如果通过对大量如下数据进行 fine-tuning。
1 | prompt: |
训练后的模型中,按照 prompt 格式书写,那么 completion 会自动按照期望的格式返回,而不是返回其他内容.
类似于 Masked Language Modeling(MLM) ,系统会将回答识别为「答案: [mask] 」,模型去预测 mask 的内容,或者理解为「完形填空」
embedding
一般称之为嵌入。
embedding 一般是指将一个内容实体映射为低维向量,从而可以获得内容之间的相似度。
OpenAI 的 embedding 是计算文本与维度的相关性,默认的 ada-002 模型会将文本解析为 1536 个维度。用户可以通过文本之间的 embedding 计算相似度。
embedding 的使用场景是可以根据用户提供的语料片段与 prompt 内容计算相关度,然后将最相关的语料片段作为上下文放到 prompt 中,以提高 completion 的准确率。
具体可以看 (二)如何使用 Embedding 提升回答质量?
使用场景:
- 获取文本特征向量
- 提供「相关」上下文,让 GPT-3 依据上下文回答
fine-tune 和 embedding 可以结合使用,比如通过 fine-tune 训练基于 context 识别 prompt 模型,再使用此模型使用 embedding 插入上下文,这样新模型也能更好地理解 prompt
两者如何选择
我有一堆语料,想让 GPT-3 依据我的语料输出内容 - 使用 embedding
想让 GPT-3 模仿一个温柔贤惠的女人和我对话 - 使用 fine-tune
希望用户按照一定格式提交问题 - 使用 fine-tune
可以根据产品的使用手册来回答用户的问题 - 使用 embedding
数据训练步骤
安装openai(python环境3以上)
1 | pip install --upgrade openai |
绑定key
1 | export OPENAI_API_KEY="<OPENAI_API_KEY>" |
准备数据
分类
在分类问题中,提示中的每个输入都应对应到预定义的类别
判断真假:判断真实性
假设你希望确保你网站上的广告文字提及正确的产品和公司。换句话说,你要确保模型没有胡编乱造。你可能想要微调过滤掉不正确广告的分类器。
数据集可能类似于以下内容:
1 | {"prompt":"Company: BHFF insurance\nProduct: allround insurance\nAd:One stop shop for all your insurance needs!\nSupported:", "completion":" yes"} |
在上面的示例中,我们使用了包含公司名称、产品和相关广告的结构化输入。作为分隔符,我们使用\nSupported:它清楚地将提示与完成分开。对于足够数量的示例,分隔符不会产生太大影响(通常小于 0.4%),只要它没有出现在提示或完成中即可。
对于这个用例,我们微调了一个 ada 模型,因为它会更快、更便宜,而且性能将与更大的模型相当,因为它是一个分类任务
判断好坏:判断情绪
假设你想要了解特定文字的正面或负面程度。数据集可能类似于以下内容:
1 | {"prompt":"Overjoyed with the new iPhone! ->", "completion":" positive"} |
对模型进行微调后,你可以通过在logprobs=2完成请求上设置来取回第一个完成令牌的对数概率。正类别的概率越高,相对情绪就越高。
区分类别:邮件分类
假设你希望将收到的电子邮件归入大量预定义类别之一。对于大量类别的分类,我们建议你将这些类别转换为数字,最多可处理约 500 个类别。我们观察到,由于标记化,在数字前添加一个空格有时会对性能略有帮助。你可能希望按如下方式构建训练数据:
1 | {"prompt":"Subject: Update my address\nFrom:Joe Doe\nTo:support@ourcompany.com\nDate:2021-06-03\nContent:Hi,\nI would like to update my billing address to match my delivery address.\n\nPlease let me know once done.\n\nThanks,\nJoe\n\n###\n\n", "completion":" 4"} |
在上面的示例中,我们使用了一封上限为 2043 个令牌的传入电子邮件作为输入。(这允许使用 4 个标记分隔符和一个标记完成,总计为 2048。)作为分隔符,我们使用并删除了电子邮件中\n\n###\n\n出现的所有内容。###
条件生成
条件生成是需要在给定某种输入的情况下生成内容的问题。这包括释义、总 结、实体提取、编写给定规范的产品描述、聊天机器人等。
生成:广告
这是一个生成用例,因此你需要确保提供的样本具有最高质量,因为微调模 型将尝试模仿给定示例的风格(和错误)。一个好的起点是大约 500 个示例。示 例数据集可能如下所示:
1 | {"prompt":"Samsung Galaxy Feel\nThe Samsung Galaxy Feel is an Android smartphone developed by Samsung Electronics exclusively for the Japanese market. The phone was released in June 2017 and was sold by NTT Docomo. It runs on Android 7.0 (Nougat), has a 4.7 inch display, and a 3000 mAh battery.\nSoftware\nSamsung Galaxy Feel runs on Android 7.0 (Nougat), but can be later updated to Android 8.0 (Oreo).\nHardware\nSamsung Galaxy Feel has a 4.7 inch Super AMOLED HD display, 16 MP back facing and 5 MP front facing cameras. It has a 3000 mAh battery, a 1.6 GHz Octa-Core ARM Cortex-A53 CPU, and an ARM Mali-T830 MP1 700 MHz GPU. It comes with 32GB of internal storage, expandable to 256GB via microSD. Aside from its software and hardware specifications, Samsung also introduced a unique a hole in the phone's shell to accommodate the Japanese perceived penchant for personalizing their mobile phones. The Galaxy Feel's battery was also touted as a major selling point since the market favors handsets with longer battery life. The device is also waterproof and supports 1seg digital broadcasts using an antenna that is sold separately.\n\n###\n\n", "completion":"Looking for a smartphone that can do it all? Look no further than Samsung Galaxy Feel! With a slim and sleek design, our latest smartphone features high-quality picture and video capabilities, as well as an award winning battery life. END"} |
这里我们使用了多行分隔符,因为维基百科文章包含多个段落和标题。我们还使用了一个简单的结束标记,以确保模型知道何时应该完成完成
生成:提炼总结
这类似于语言转换任务。为了提高性能,最好按字母顺序或按照它们在原始文本中出现的相同顺序对不同的提取实体进行排序。这将有助于模型跟踪需要按顺序生成的所有实体。数据集可能如下所示:
1 | {"prompt":"Portugal will be removed from the UK's green travel list from Tuesday, amid rising coronavirus cases and concern over a "Nepal mutation of the so-called Indian variant". It will join the amber list, meaning holidaymakers should not visit and returnees must isolate for 10 days...\n\n###\n\n", "completion":" Portugal\nUK\nNepal mutation\nIndian variant END"} |
多行分隔符效果最好,因为文本可能包含多行。理想情况下,输入提示的类 型会高度多样化(新闻文章、维基百科页面、推文、法律文件),这反映了提取 实体时可能遇到的文本。
生成:聊天机器人
聊天机器人通常会包含有关对话的相关上下文(订单详细信息)、到目前为止的对话摘要以及最近的消息。对于这个用例,相同的过去对话可以在数据集中生成多行,每次都有稍微不同的上下文,对于每个代理生成作为完成。这个用例将需要几千个示例,因为它可能会处理不同类型的请求和客户问题。为确保高质量的性能,我们建议审查对话样本以确保代理消息的质量。可以使用单独的文本转换微调模型生成摘要。数据集可能如下所示:
1 | {"prompt":"Summary: <summary of the interaction so far>\n\nSpecific information:<for example order details in natural language>\n\n###\n\nCustomer: <message1>\nAgent: <response1>\nCustomer: <message2>\nAgent:", "completion":" <response2>\n"} |
数据验证
通过官方的数据验证工具,会验证传入的数据的正确性,同时会生成符合要求的jsonl文件。(支持上传CSV、TSV、XLSX、JSON或JSONL文件)
1 | openai tools fine_tunes.prepare_data -f <LOCAL_FILE> |

训练
准备好数据集之后,通过命令行开始训练
基本模型分类:
ada:最快、价格最低的模型,适用于简单的分析文本,简单分类,文本修正,关键词搜索等
babbage:比ada价格、性能高一点的模型,适用于一般类型的分类识别、语义分析等
curie :性能、功能、价格高于babbage,适用于语言翻译、复杂的分类,情感、总结等
davinci:功能、性能、价格最高的模型,适用于表达复杂的意图、因果关系分析、创意生成、语义搜索、摘要总结等
1 | openai api fine_tunes.create -t <数据文件> -m <基础模型> |

训练会根据数据量大小、选择的模型,花费不同的时长(可以通过查询训练模型查看训练的状态),训练完成后,通过查询,获得训练完成后的模型名称
1 | # 查询全部训练模型清单(windows需要加 --api-key <OPENAI_API_KEY>) |
等待训练

训练中

训练完成

使用
完成训练后可以通过模型名称来调用训练好的模型(注:采用训练模型返回的结果均为训练数据类的结果集,chatgpt会根据问题的语义进行结果集的匹配,所以训练数据决定了训练模型的精确度和范围)
CLI (windows需要加 --api-key
1 | openai api completions.create -m <模型名称> -p "你的问题" |

cURL
1 | curl https://api.openai.com/v1/completions \ |
Pyton
1 | import openai |
Node.js:
1 | const response = await openai.createCompletion({ |
删除
如果不再使用以上模型,可以进行删除
CLI
1 | openai api models.delete -i <FINE_TUNED_MODEL> |
CURL
1 | curl -X "DELETE" https://api.openai.com/v1/models/<FINE_TUNED_MODEL> \ |
Python:
1 | import openai |
kaggle
这里再介绍个网站kaggle
该网站可以摘到很多高质量数据集,我们可以借助它来生成最终的promt
比如下图,我们找到nba【1996-2021】赛季每个球员的基准数据,然后再把csv数据发给chatGpt,让它生成{"prompt":"xxx", "completion":" xxx"}格式,就可以做到“模型生产模型”

附件
kaggle航班数据地址:https://www.kaggle.com/datasets/hassanamin/atis-airlinetravelinformationsystem
规范后的示例jsonl文件,点击此处进行下载