关于chatgpt:花1块钱让你的网站支持-ChatGPT

6次阅读

共计 7073 个字符,预计需要花费 18 分钟才能阅读完成。

最近 ChatGPT 在技术圈子可太火了,票圈也被刷屏。我也决定来凑个冷落,给本人的博客加一个 ChatGPT 对话性能。

先附上体验链接,源码在底部也能够找到。

感激大家的反对,我的 Open AI 个人账户收费额度曾经用尽,十分道歉,请大家自行依照文章和源码搭建体验吧,或者本人注册一个账号去后盾体验。

体验 ChatGPT

ChatGPT 是 Open AI 训练的一个 AI 对话模型,能够反对在多种场景下进行智能对话。

想体验 ChatGPT,首先要注册账户,然而这个产品在国内网络并不能间接用,须要自行解决网络问题。

搞定网络问题后,注册时会让你提供邮箱验证,

接着要验证手机号,然而很遗憾国内手机号用不了。

你也能够抉择用 Google 账号登录,然而最终还是要验证手机号。

所以咱们须要先找一个国外的能接管短信验证码的手机号,此时能够上 SMS-ACTIVATE。

这是一个在这个星球上数以百万计的服务中注册帐户的网站。咱们提供世界上大多数国家的虚构号码,以便您能够在线接管带有确认代码的短信。在咱们的服务中,还有虚构号码的长期租赁,转发连贯,电话验证等等。

SMS-ACTIVATE 上的价格是卢布,咱们须要应用手机号码做短信验证,通过查问能够发现,最便宜的是印度地区的手机号,零售价格是 10.5 卢布。

依照汇率算了一下,大略是 1 块多 RMB。

SMS-ACTIVATE 反对用某宝充值,我买了一个印度号,就能够收到来自 Open AI 的验证码了。

留神,这个号码只是租用,是有期限的,所以咱们要抓紧时间把注册流程搞完,20 分钟过了,这个号码就不是你的了。

注册完 Open AI 的账号后,就能够到 ChatGPT 的 Web 工作台体验一把 AI 对话了。

通过 API 接入 Open AI 能力

体验完 ChatGPT 之后,对于搞技术的咱们来说,可能会想着怎么把这个能力接入到本人的产品中。

疾速上手

ChatGPT 是 Open AI 训练进去的模型,Open AI 也提供了 API 给开发者们调用,文档和案例也比拟全面。

机器学习很重要的一个步骤就是调参,但对于前端开发者来说,大部分人必定是不晓得怎么调参的,那咱们就参考官网提供的最符合咱们需要的案例就好了,这个 Chat 的案例就十分合乎咱们的场景须要。

官网有提供一个 nodejs 的 starter,咱们能够基于此疾速上手测试一把。

git clone https://github.com/openai/openai-quickstart-node.git

它的外围代码是这么一部分,其中用到的 openai 是官网封装好的 NodeJS Library。

const completion = await openai.createCompletion({
    model: "text-davinci-003",
    prompt: '发问内容',
    temperature: 0.9,
    max_tokens: 150,
    top_p: 1,
    frequency_penalty: 0,
    presence_penalty: 0.6,
});

在调用 API 之前须要先在你的 Open AI 账户中生成一个 API Key。

目前官网给到的收费额度是 18 刀,超过的局部就须要本人付费了。计费是依据 Token 来算的,至于什么是 Token,能够参考 Key concepts。

咱们把下面那个 Chat 案例的参数拿过去间接用上,基本上也有个七八分 AI 答复问题的样子了,这个能够本人去试一试成果,并不简单。

接着就是钻研一下怎么把这个 starter 的要害代码集成到本人的产品中。

产品剖析

我之前有在本人的博客中做过一个简略的 WebSocket 聊天性能,而在 AI 对话这个需要中,前端 UI 局部基本上能够参考着 WebSocket 聊天性能改改,工作量不是很大,次要工作量还是在前后端的逻辑和对接下面。

ChatGPT 的这个产品模式,它不是一个惯例的 WebSocket 全双工对话,而是像咱们平时调接口一样,产生用户输出后,客户端发送申请到服务端,期待服务端响应,最初反馈给用户,它仅仅是从界面上看起来像是聊天,实际上不是一个规范的聊天过程。所以前后端交互次要还是靠 HTTP 接口对接。

外围因素 Prompt

openai.createCompletion 调用时有一个很重要的参数prompt,它是对话的上下文信息,只有这个信息足够残缺,AI 能力正确地做出反馈。

举个例子,假如在对话过程中有 2 个回合。

// 回合 1
你:爱因斯坦是谁?AI: 爱因斯坦(Albert Einstein)是 20 世纪最重要的物理学家,他被誉为“时空之父”。他发现了相对论,并取得诺贝尔物理学奖。

第一个回合中,传参 prompt爱因斯坦是谁?,机器人很好了解,马上能给出符合实际的回复。

// 回合 2
你:他做了什么奉献?AI: 他为社会做出了许多奉献,例如改善公共卫生、建设教育基础设施、进步农业生产能力、促成经济倒退等。

第二个回合传参 prompt他做了什么奉献?,看到机器人的回答,你可能会感觉有点离谱,因为这根本就是牛头不对马嘴。然而认真想想,这是因为机器人不晓得上下文信息,所以机器人不能了解 代表的含意,只能通过 他做了什么奉献?整句话去揣测,所以从后果上看就是合乎语言的逻辑,然而不合乎咱们给出的语境。

如果咱们把第二个回合的传参 prompt 改成 你: 爱因斯坦是谁?\nAI: 爱因斯坦(Albert Einstein)是 20 世纪最重要的物理学家,他被誉为“时空之父”。他发现了相对论,并取得诺贝尔物理学奖。\n 你: 他做了什么奉献?\nAI:,机器人就可能了解上下文信息,给出接下来的合乎逻辑的回答。

// 改良后的回合 2
你:他做了什么奉献?AI: 爱因斯坦对迷信有着重大的奉献,他创造了相对论,扭转了人们对世界、物理定律和宇宙的意识,并为量子力学奠定了根底。他还发现了...

所以,咱们的初步论断是:prompt参数应该蕴含此次对话主题的较完整内容,能力保障 AI 给出的下一次答复合乎咱们的根本认知。

前后端交互

对于前端来说,咱们通常关注的是,我给后端发了什么数据,后端反馈给我什么数据。所以,前端关注点之一就是用户的输出,用下面的例子说,爱因斯坦是谁? 他做了什么奉献?这两个内容,应该别离作为前端两次申请的参数。而且,对于前端来说,咱们也不须要思考后端传给 Open AI 的 prompt 是不是残缺,只有把用户输出的内容正当地传给后端就够了。

对于后端来说,咱们要关注 session 问题,每个用户应该有属于本人和 AI 的私密对话空间,不能和其余的用户对话串了数据,这个能够基于 session 实现。前端每次传过来的信息只有简略的用户输出,而后端要关注与 Open AI 的对接过程,联合用户的输出以及会话中保留的一些信息,合并成一个残缺的 prompt 传给 Open AI,这样能力失去失常的对话过程。

所以根本的流程应该是这个样子:

咱们依据这个流程输入第一版代码。

后端 V1 版本代码

router.get('/chat-v1', async function(req, res, next) {
    // 获得用户输出
    const wd = req.query.wd;
    // 结构 prompt 参数
    if (!req.session.chatgptSessionPrompt) {req.session.chatgptSessionPrompt = ''}
    const prompt = req.session.chatgptSessionPrompt + `\n 发问:` + wd + `\nAI:`
    try {
        const completion = await openai.createCompletion({
            model: "text-davinci-003",
            prompt,
            temperature: 0.9,
            max_tokens: 150,
            top_p: 1,
            frequency_penalty: 0,
            presence_penalty: 0.6,
            stop: ["\n 发问:", "\nAI:"],
        });
        // 调用 Open AI 胜利后,更新 session
        req.session.chatgptSessionPrompt = prompt + completion.data
        // 返回后果
        res.status(200).json({
            code: '0',
            result: completion.data.choices[0].text
        });
    } catch (error) {console.error(error)
        res.status(500).json({message: "Open AI 调用异样"});
    }
});

前端 V1 版本要害代码

const sendChatContentV1 = async () => {
    // 先显示本人说的话
    msgList.value.push({time: format(new Date(), "HH:mm:ss"),
        user: "我说",
        content: chatForm.chatContent,
        type: "mine",
        customClass: "mine",
    });
    loading.value = true;
    try {
        // 调 chat-v1 接口,等后果
        const {result} = await chatgptService.chatV1({wd: chatForm.chatContent});
        // 显示 AI 的回答
        msgList.value.push({time: format(new Date(), "HH:mm:ss"),
            user: "Chat AI",
            content: result,
            type: "others",
            customClass: "others",
        });
    } finally {loading.value = false;}
};

根本的对话能力曾经有了,然而最显著的毛病就是一个回合等得太久了,咱们心愿他速度更快一点,至多在交互上看起来快一点。

流式输入(服务器推 + EventSource)

还好 Open AI 也反对 stream 流式输入,在前端能够配合 EventSource 一起用。

You can also set the stream parameter to true for the API to stream back text (as data-only server-sent events).

根本的数据流是这个样子的:

后端革新如下:

router.get('/chat-v2', async function(req, res, next) {
    // ... 省略局部代码
    try {
        const completion = await openai.createCompletion({
            // ... 省略局部代码
            // 减少了 stream 参数
            stream: true
        }, {responseType: 'stream'});
        // 设置响应的 content-type 为 text/event-stream
        res.setHeader("content-type", "text/event-stream")
        // completion.data 是一个 ReadableStream,res 是一个 WritableStream,能够通过 pipe 买通管道,流式输入给前端。completion.data.pipe(res)
    }
    // ... 省略局部代码
});

前端放弃应用 axios 发动 HTTP 申请,而是改用 EventSource。

const sendChatContent = async () => {
    // ... 省略局部代码
    // 先显示本人说的话
    msgList.value.push({time: format(new Date(), "HH:mm:ss"),
        user: "我说",
        content: chatForm.chatContent,
        type: "mine",
        customClass: "mine",
    });
    
    // 通过 EventSource 取数据
    const es = new EventSource(`/api/chatgpt/chat?wd=${chatForm.chatContent}`);

    // 记录 AI 回答的内容
    let content = "";
    
    // ... 省略局部代码

    es.onmessage = (e) => {if (e.data === "[DONE]") {// [DONE] 标记数据完结,调用 feedback 反馈给服务器
            chatgptService.feedback(content);
            es.close();
            loading.value = false;
            updateScrollTop();
            return;
        }
        // 从数据中取出文本
        const text = JSON.parse(e.data).choices[0].text;
        if (text) {if (!content) {
                // 第一条数据来了,先显示
                msgList.value.push({time: format(new Date(), "HH:mm:ss"),
                    user: "Chat AI",
                    content: text,
                    type: "others",
                    customClass: "others",
                });
                // 再拼接
                content += text;
            } else {
                // 先拼接
                content += text;
                // 再更新内容,实现打字机成果
                msgList.value[msgList.value.length - 1].content = content;
            }
        }
    };
};

从代码中能够发现前端在 EventSource message 接管完结时,还调用了一个 feedback 接口做反馈。这是因为在应用 Pipe 输入时,后端没有记录 AI 回答的文本,思考到前端曾经解决了文本,这里就由前端做一次反馈,把本次 AI 回答的内容残缺回传给后端,后端再更新 session 中存储的对话信息,保障对话上下文的完整性。

feedback 接口的实现比较简单:

router.post('/feedback', function(req, res, next) {if (req.body.result) {
        req.session.chatgptSessionPrompt += req.body.result
        res.status(200).json({
            code: '0',
            msg: "更新胜利"
        });
    } else {res.status(400).json({msg: "参数谬误"});
    }
});

我这里只是给出一种简略的做法,理论产品中可能要思考的会更多,或者应该在后端自行处理 session 内容,而不是依附前端的反馈。

最终的成果大略是这个样子:

限度拜访频次

因为 Open AI 也是有收费额度的,所以在调用频率和次数上也应该做个限度,避免被歹意调用,这个也能够通过 session 来解决。我这里也提供一种比拟毛糙的解决形式,具体请往下看。理论产品中可能会写 Redis,写库,加定时工作之类的,这方面我也不够业余,就不多说了。

针对拜访频率,我暂定的是 3 秒内最多调用一次,咱们能够在调用 Open AI 胜利之后,在 session 中记录时间戳。

req.session.chatgptRequestTime = Date.now()

当一个新的申请过去时,能够用以后工夫减去上次记录的chatgptRequestTime,判断一下是不是在 3 秒内,如果是,就返回 HTTP 状态码 429;如果不在 3 秒内,就能够持续前面的逻辑。

if (req.session.chatgptRequestTime && Date.now() - req.session.chatgptRequestTime <= 3000) {
    // 不容许在 3s 里反复调用
    return res.status(429).json({msg: "请升高申请频次"});
}

对于申请次数也是同样的情理,我这里也写得很简略,实际上还应该有跨天清理等逻辑要做。我这里偷懒了,临时没做这些。

if (req.session.chatgptTimes && req.session.chatgptTimes >= 50) {
    // 实际上还须要跨天清理,这里先偷懒了。return res.status(403).json({msg: "达到调用下限,欢送今天再来哦"});
}

同一个话题也不能聊太多,否则传给 Open AI 的 prompt 参数会很大,这就可能会消耗很多 Token,也有可能超过 Open AI 参数的限度。

if (req.session.chatgptTopicCount && req.session.chatgptTopicCount >= 10) {
    // 一个话题聊的次数超过限度时,须要强行重置 chatgptSessionPrompt,换个话题。req.session.chatgptSessionPrompt = ''
    req.session.chatgptTopicCount = 0
    return res.status(403).json({msg: "这个话题聊得有点深刻了,不如换一个"});
}

切换话题

客户端应该也有切换话题的能力,否则 session 中记录的信息可能会蕴含多个话题的内容,可能导致与用户的预期不符。那咱们做个接口就好了。

router.post('/changeTopic', function(req, res, next) {
    req.session.chatgptSessionPrompt = ''
    req.session.chatgptTopicCount = 0
    res.status(200).json({
        code: '0',
        msg: "能够尝试新的话题"
    });
});

结语

总的来说,Open AI 凋谢进去的智能对话能力能够满足根本需要,然而还有很大改良空间。我在文中给出的代码仅供参考,不保障性能上的完满。

附上源码地址,能够点个 star 吗,球球了[认真脸]。

正文完
 0