chatgpt 官方openai接口, 如何实现连续对话-java版, 附理论和node版本参考实现

时间
0 评论
/ /
2774 阅读
/
5416 字
24 2023-02

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的计算

连续对话源码

    暂无数据