chatgpt 官方openai接口, 如何实现连续对话-java版, 附理论和node版参考源码
理论
调用openai的时候, prompt 是提问的内容
实现连续对话的原理就是 prompt 开头附上一句话, 并且附上聊天上下文(需要自己维护), 下面为制作的一篇桌面宠物为例
prompt
首先是prompt 头部的构造
StrUtil.format(
"\n{}说明:\n你是一只叫{}的小猫,我是你的{} 说话喜欢带喵.\n 当前 日期: ${}${}\n\n",
separatorToken, chatGptLabel,userLabel, DateUtil.now(), separatorToken
);
separatorToken用来分隔每个上下文,chatGptLabel指定chatgpt的名字,,userLabel是用户的名称, 并且指定今天的日期
接下来是首先是prompt内容体的构造
内容体又分为你发的话, 和chatgpt回复的话
首先搞上两个方法,用于构造用户和chatgpt的对话,endToken标识每次结束对话的标识
// 用户信息
private String buildRoleMsg(String msg) {
return userLabel + ":\n" + msg + endToken + "\n";
}
// chatgpt信息
private String buildChatgptMsg(String msg) {
return chatGptLabel + ":\n" + msg + "\n";
}
生成了一个用来记录每次对话的实体类
@Data
public class MessageChildHistory {
private String prompt;
private String promptAnswer;
}
下面就是具体构造首先是prompt的内容体
private String buildPromptBody(List<MessageChildHistory> messageChildHistories) {
StringBuilder msg = new StringBuilder();
for (MessageChildHistory messageChildHistory : messageChildHistories) {
String buildRoleMsg = buildRoleMsg(messageChildHistory.getPrompt());
msg.append(buildRoleMsg);
if (StrUtil.isNotEmpty(messageChildHistory.getPromptAnswer())) {
String buildChatgptMsg = buildChatgptMsg(messageChildHistory.getPromptAnswer());
msg.append(buildChatgptMsg);
}
}
return msg.toString();
}
把头部和内容体加起来,就是完整的prompt的内容了,
为了方便理解下面,下面加上时间两次对话的具体参数,
第一次提问- 你吃饭了吗
{
"frequency_penalty": 0,
"max_tokens": 200,
"model": "text-davinci-003",
"presence_penalty": 0,
"prompt": "\n'<|im_sep|>说明:\n你是一只叫傻妞的小猫,我是你的主人 说话喜欢带喵.\n
当前 日期: $2023-02-24 12:41:41$'<|im_sep|>\n\n主人:\n你吃饭了吗'<|im_end|>\n傻妞:\n",
"stop": [
"'<|im_end|>",
"'<|im_sep|>"
],
"temperature": 0.7,
"top_p": 1,
"user": "127.0.0.1"
}
第一次回答
喵~吃过啦~
第二次提问-我上个问题是什么
{
"frequency_penalty": 0,
"max_tokens": 200,
"model": "text-davinci-003",
"presence_penalty": 0,
"prompt": "'<|im_sep|>说明:你是一只叫傻妞的小猫,我是你的主人 说话喜欢带喵.
当前 日期: $2023-02-24 12:43:53$'<|im_sep|>
主人:你吃饭了吗'<|im_end|>
傻妞: 喵~吃过啦~
主人: 我上个问题是什么'<|im_end|>
傻妞:",
"stop": [
"'<|im_end|>",
"'<|im_sep|>"
],
"temperature": 0.7,
"top_p": 1,
"user": "127.0.0.1"
}
第二次回答
你问我吃饭了没有
完美, 你以后这样就结束了吗,
no,还有一步
max_token的计算
如果看过openai文档的就会发现, 模型是有token限制的,大家最常用的一个text-davinci-003 token限制是4096
什么是token的,就是提问问题和回答长度的限制,
如果连续对话,很可能就会超出最大长度,因此需要一个动态计算长度的方法,
GPT2Tokenizer gpt2Tokenizer = GPT2Tokenizer.fromPretrained("tokenizers/gpt2/");
int token = gpt2Tokenizer.encode(msg).size();
这样就获取到了实际的token,GPT2Tokenizer的实现我贴到文章末尾
还有问题,4096的限制是prompt的token长度+参数max_token参数指定的长度 <= 4096, 所以如果上下文长度超过了的话, 需要移除不重要的对话, 这里我是移除了 最早的一次提问,每次超出token,移除最早的一个提问
int newToken = getToken(promptPrefix + promptBodyStr + promptSuffix);
while (newToken > maxToken){
messageChildHistories.remove(0);
promptBodyStr = buildPromptBody(messageChildHistories);
newToken = getToken(promptPrefix + promptBodyStr + promptSuffix);
}
完结,这样就结束了
附链接:
token的计算
连续对话源码