自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

花1塊錢讓你的網(wǎng)站支持 ChatGPT

人工智能
這是一個在這個星球上數(shù)以百萬計的服務(wù)中注冊帳戶的網(wǎng)站。 我們提供世界上大多數(shù)國家的虛擬號碼,以便您可以在線接收帶有確認(rèn)代碼的短信。在我們的服務(wù)中,還有虛擬號碼的長期租賃,轉(zhuǎn)發(fā)連接,電話驗(yàn)證等等。

本文轉(zhuǎn)載自微信公眾號「前端司南」,作者Tusi。轉(zhuǎn)載本文請聯(lián)系前端司南公眾號。

最近 ChatGPT 在技術(shù)圈子可太火了,票圈也被刷屏。我也決定來湊個熱鬧,給自己的博客加一個 ChatGPT 對話功能。

先附上體驗(yàn)鏈接,源碼在底部也可以找到。

感謝大家的支持,我的 Open AI個人賬戶免費(fèi)額度已經(jīng)用盡,非常抱歉,請大家自行按照文章和源碼搭建體驗(yàn)吧,或者自己注冊一個賬號去后臺體驗(yàn)。

chatgpt_博客效果.gif

體驗(yàn) ChatGPT

ChatGPT 是 Open AI 訓(xùn)練的一個 AI 對話模型,可以支持在多種場景下進(jìn)行智能對話。

image.png

想體驗(yàn) ChatGPT,首先要注冊賬戶,但是這個產(chǎn)品在國內(nèi)網(wǎng)絡(luò)并不能直接用,需要自行解決網(wǎng)絡(luò)問題。

image.png

搞定網(wǎng)絡(luò)問題后,注冊時會讓你提供郵箱驗(yàn)證,

image.png

接著要驗(yàn)證手機(jī)號,但是很遺憾國內(nèi)手機(jī)號用不了。

image.png

你也可以選擇用 Google 賬號登錄,但是最終還是要驗(yàn)證手機(jī)號。

所以我們需要先找一個國外的能接收短信驗(yàn)證碼的手機(jī)號,此時可以上SMS-ACTIVATE。

這是一個在這個星球上數(shù)以百萬計的服務(wù)中注冊帳戶的網(wǎng)站。 我們提供世界上大多數(shù)國家的虛擬號碼,以便您可以在線接收帶有確認(rèn)代碼的短信。 在我們的服務(wù)中,還有虛擬號碼的長期租賃,轉(zhuǎn)發(fā)連接,電話驗(yàn)證等等。

SMS-ACTIVATE 上的價格是盧布,我們需要使用手機(jī)號碼做短信驗(yàn)證,經(jīng)過查詢可以發(fā)現(xiàn),最便宜的是印度地區(qū)的手機(jī)號,零售價格是 10.5 盧布。

image.png

按照匯率算了一下,大概是1塊多RMB。

image.png

SMS-ACTIVATE 支持用某寶充值,我買了一個印度號,就可以收到來自 Open AI 的驗(yàn)證碼了。

image.png

注意,這個號碼只是租用,是有期限的,所以我們要抓緊時間把注冊流程搞完,20分鐘過了,這個號碼就不是你的了。

注冊完 Open AI 的賬號后,就可以到 ChatGPT 的 Web工作臺體驗(yàn)一把 AI 對話了。

chatgpt體驗(yàn).gif

通過 API 接入 Open AI 能力

體驗(yàn)完 ChatGPT 之后,對于搞技術(shù)的我們來說,可能會想著怎么把這個能力接入到自己的產(chǎn)品中。

快速上手

ChatGPT 是 Open AI 訓(xùn)練出來的模型,Open AI 也提供了 API 給開發(fā)者們調(diào)用,文檔和案例也比較全面。

機(jī)器學(xué)習(xí)很重要的一個步驟就是調(diào)參,但對于前端開發(fā)者來說,大部分人肯定是不知道怎么調(diào)參的,那我們就參考官方提供的最契合我們需求的案例就好了,這個 Chat 的案例就非常符合我們的場景需要。

image.png

官方有提供一個 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: '提問內(nèi)容',
temperature: 0.9,
max_tokens: 150,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0.6,
});

在調(diào)用 API 之前需要先在你的 Open AI 賬戶中生成一個 API Key。

目前官方給到的免費(fèi)額度是 18 刀,超過的部分就需要自己付費(fèi)了。計費(fèi)是根據(jù) Token 來算的,至于什么是 Token,可以參考Key concepts。

image.png

我們把上面那個 Chat 案例的參數(shù)拿過來直接用上,基本上也有個七八分 AI 回答問題的樣子了,這個可以自己去試一試效果,并不復(fù)雜。

接著就是研究一下怎么把這個 starter 的關(guān)鍵代碼集成到自己的產(chǎn)品中。

產(chǎn)品分析

我之前有在自己的博客中做過一個簡單的 WebSocket 聊天功能,而在 AI 對話這個需求中,前端 UI 部分基本上可以參考著WebSocket 聊天功能改改,工作量不是很大,主要工作量還是在前后端的邏輯和對接上面。

image.png

ChatGPT 的這個產(chǎn)品模式,它不是一個常規(guī)的 WebSocket 全雙工對話,而是像我們平常調(diào)接口一樣,發(fā)生用戶輸入后,客戶端發(fā)送請求到服務(wù)端,等待服務(wù)端響應(yīng),最后反饋給用戶,它僅僅是從界面上看起來像是聊天,實(shí)際上不是一個標(biāo)準(zhǔn)的聊天過程。所以前后端交互主要還是靠 HTTP 接口對接。

核心要素 Prompt

在openai.createCompletion調(diào)用時有一個很重要的參數(shù)prompt,它是對話的上下文信息,只有這個信息足夠完整,AI 才能正確地做出反饋。

舉個例子,假設(shè)在對話過程中有2個回合。

// 回合1
你:愛因斯坦是誰?
AI: 愛因斯坦(Albert Einstein)是20世紀(jì)最重要的物理學(xué)家,他被譽(yù)為“時空之父”。他發(fā)現(xiàn)了相對論,并獲得諾貝爾物理學(xué)獎。

第一個回合中,傳參prompt是愛因斯坦是誰?,機(jī)器人很好理解,馬上能給出符合實(shí)際的回復(fù)。

// 回合2
你:他做了什么貢獻(xiàn)?
AI: 他為社會做出了許多貢獻(xiàn),例如改善公共衛(wèi)生、建立教育基礎(chǔ)設(shè)施、提高農(nóng)業(yè)生產(chǎn)能力、促進(jìn)經(jīng)濟(jì)發(fā)展等。

第二個回合傳參prompt是他做了什么貢獻(xiàn)?,看到機(jī)器人的答復(fù),你可能會覺得有點(diǎn)離譜,因?yàn)檫@根本就是牛頭不對馬嘴。但是仔細(xì)想想,這是因?yàn)闄C(jī)器人不知道上下文信息,所以機(jī)器人不能理解他代表的含義,只能通過他做了什么貢獻(xiàn)?整句話去推測,所以從結(jié)果上看就是符合語言的邏輯,但是不符合我們給出的語境。

如果我們把第二個回合的傳參prompt改成你: 愛因斯坦是誰?\nAI: 愛因斯坦(Albert Einstein)是20世紀(jì)最重要的物理學(xué)家,他被譽(yù)為“時空之父”。他發(fā)現(xiàn)了相對論,并獲得諾貝爾物理學(xué)獎。\n你: 他做了什么貢獻(xiàn)?\nAI:,機(jī)器人就能夠理解上下文信息,給出接下來的符合邏輯的答復(fù)。

// 改進(jìn)后的回合2
你:他做了什么貢獻(xiàn)?
AI: 愛因斯坦對科學(xué)有著重大的貢獻(xiàn),他發(fā)明了相對論,改變了人們對世界、物理定律和宇宙的認(rèn)識,并為量子力學(xué)奠定了基礎(chǔ)。他還發(fā)現(xiàn)了...

所以,我們的初步結(jié)論是:prompt參數(shù)應(yīng)該包含此次對話主題的較完整內(nèi)容,才能保證 AI 給出的下一次回答符合我們的基本認(rèn)知。

前后端交互

對于前端來說,我們通常關(guān)注的是,我給后端發(fā)了什么數(shù)據(jù),后端反饋給我什么數(shù)據(jù)。所以,前端關(guān)注點(diǎn)之一就是用戶的輸入,用上面的例子說,愛因斯坦是誰?和他做了什么貢獻(xiàn)?這兩個內(nèi)容,應(yīng)該分別作為前端兩次請求的參數(shù)。而且,對于前端來說,我們也不需要考慮后端傳給 Open AI 的prompt是不是完整,只要把用戶輸入的內(nèi)容合理地傳給后端就夠了。

對于后端來說,我們要關(guān)注 session 問題,每個用戶應(yīng)該有屬于自己和 AI 的私密對話空間,不能和其他的用戶對話串了數(shù)據(jù),這個可以基于 session 實(shí)現(xiàn)。前端每次傳過來的信息只有簡單的用戶輸入,而后端要關(guān)注與 Open AI 的對接過程,結(jié)合用戶的輸入以及會話中保留的一些信息,合并成一個完整的prompt傳給 Open AI,這樣才能得到正常的對話過程。

所以基本的流程應(yīng)該是這個樣子:

image.png

我們根據(jù)這個流程輸出第一版代碼。

后端V1版本代碼

router.get('/chat-v1', async function(req, res, next) {
// 取得用戶輸入
const wd = req.query.wd;
// 構(gòu)造 prompt 參數(shù)
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:"],
});
// 調(diào)用 Open AI 成功后,更新 session
req.session.chatgptSessionPrompt = prompt + completion.data
// 返回結(jié)果
res.status(200).json({
code: '0',
result: completion.data.choices[0].text
});
} catch (error) {
console.error(error)
res.status(500).json({
message: "Open AI 調(diào)用異常"
});
}
});

前端V1版本關(guān)鍵代碼

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 {
// 調(diào) chat-v1 接口,等結(jié)果
const { result } = await chatgptService.chatV1({ wd: chatForm.chatContent });
// 顯示 AI 的答復(fù)
msgList.value.push({
time: format(new Date(), "HH:mm:ss"),
user: "Chat AI",
content: result,
type: "others",
customClass: "others",
});
} finally {
loading.value = false;
}
};

chatgpt_v1.gif

基本的對話能力已經(jīng)有了,但是最明顯的缺點(diǎn)就是一個回合等得太久了,我們希望他速度更快一點(diǎn),至少在交互上看起來快一點(diǎn)。

流式輸出(服務(wù)器推 + 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).

基本的數(shù)據(jù)流是這個樣子的:

image.png

后端改造如下:

router.get('/chat-v2', async function(req, res, next) {
// ...省略部分代碼
try {
const completion = await openai.createCompletion({
// ...省略部分代碼
// 增加了 stream 參數(shù)
stream: true
}, { responseType: 'stream' });
// 設(shè)置響應(yīng)的 content-type 為 text/event-stream
res.setHeader("content-type", "text/event-stream")
// completion.data 是一個 ReadableStream,res 是一個 WritableStream,可以通過 pipe 打通管道,流式輸出給前端。
completion.data.pipe(res)
}
// ...省略部分代碼
});

前端放棄使用 axios 發(fā)起 HTTP 請求,而是改用 EventSource。

const sendChatContent = async () => {
// ...省略部分代碼
// 先顯示自己說的話
msgList.value.push({
time: format(new Date(), "HH:mm:ss"),
user: "我說",
content: chatForm.chatContent,
type: "mine",
customClass: "mine",
});

// 通過 EventSource 取數(shù)據(jù)
const es = new EventSource(`/api/chatgpt/chat?wd=${chatForm.chatContent}`);

// 記錄 AI 答復(fù)的內(nèi)容
let content = "";

// ...省略部分代碼

es.onmessage = (e) => {
if (e.data === "[DONE]") {
// [DONE] 標(biāo)志數(shù)據(jù)結(jié)束,調(diào)用 feedback 反饋給服務(wù)器
chatgptService.feedback(content);
es.close();
loading.value = false;
updateScrollTop();
return;
}
// 從數(shù)據(jù)中取出文本
const text = JSON.parse(e.data).choices[0].text;
if (text) {
if (!content) {
// 第一條數(shù)據(jù)來了,先顯示
msgList.value.push({
time: format(new Date(), "HH:mm:ss"),
user: "Chat AI",
content: text,
type: "others",
customClass: "others",
});
// 再拼接
content += text;
} else {
// 先拼接
content += text;
// 再更新內(nèi)容,實(shí)現(xiàn)打字機(jī)效果
msgList.value[msgList.value.length - 1].content = content;
}
}
};
};

從代碼中可以發(fā)現(xiàn)前端在 EventSource message 接收結(jié)束時,還調(diào)用了一個 feedback 接口做反饋。這是因?yàn)樵谑褂?Pipe 輸出時,后端沒有記錄 AI 答復(fù)的文本,考慮到前端已經(jīng)處理了文本,這里就由前端做一次反饋,把本次 AI 答復(fù)的內(nèi)容完整回傳給后端,后端再更新 session 中存儲的對話信息,保證對話上下文的完整性。

feedback 接口的實(shí)現(xiàn)比較簡單:

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: "參數(shù)錯誤"
});
}
});

我這里只是給出一種簡單的做法,實(shí)際產(chǎn)品中可能要考慮的會更多,或者應(yīng)該在后端自行處理 session 內(nèi)容,而不是依靠前端的反饋。

最終的效果大概是這個樣子:

chatgpt_博客效果.gif

限制訪問頻次

由于 Open AI 也是有免費(fèi)額度的,所以在調(diào)用頻率和次數(shù)上也應(yīng)該做個限制,防止被惡意調(diào)用,這個也可以通過 session 來處理。我這里也提供一種比較粗糙的處理方式,具體請往下看。實(shí)際產(chǎn)品中可能會寫 Redis,寫庫,加定時任務(wù)之類的,這方面我也不夠?qū)I(yè),就不多說了。

針對訪問頻率,我暫定的是 3 秒內(nèi)最多調(diào)用一次,我們可以在調(diào)用 Open AI 成功之后,在 session 中記錄時間戳。

req.session.chatgptRequestTime = Date.now()

當(dāng)一個新的請求過來時,可以用當(dāng)前時間減去上次記錄的chatgptRequestTime,判斷一下是不是在 3 秒內(nèi),如果是,就返回 HTTP 狀態(tài)碼 429;如果不在 3 秒內(nèi),就可以繼續(xù)后面的邏輯。

if (req.session.chatgptRequestTime && Date.now() - req.session.chatgptRequestTime <= 3000) {
// 不允許在3s里重復(fù)調(diào)用
return res.status(429).json({
msg: "請降低請求頻次"
});
}

關(guān)于請求次數(shù)也是同樣的道理,我這里也寫得很簡單,實(shí)際上還應(yīng)該有跨天清理等邏輯要做。我這里偷懶了,暫時沒做這些。

if (req.session.chatgptTimes && req.session.chatgptTimes >= 50) {
// 實(shí)際上還需要跨天清理,這里先偷懶了。
return res.status(403).json({
msg: "到達(dá)調(diào)用上限,歡迎明天再來哦"
});
}

同一個話題也不能聊太多,否則傳給 Open AI 的 prompt 參數(shù)會很大,這就可能會耗費(fèi)很多 Token,也有可能超過 Open AI 參數(shù)的限制。

if (req.session.chatgptTopicCount && req.session.chatgptTopicCount >= 10) {
// 一個話題聊的次數(shù)超過限制時,需要強(qiáng)行重置 chatgptSessionPrompt,換個話題。
req.session.chatgptSessionPrompt = ''
req.session.chatgptTopicCount = 0
return res.status(403).json({
msg: "這個話題聊得有點(diǎn)深入了,不如換一個"
});
}

切換話題

客戶端應(yīng)該也有切換話題的能力,否則 session 中記錄的信息可能會包含多個話題的內(nèi)容,可能導(dǎo)致與用戶的預(yù)期不符。那我們做個接口就好了。

router.post('/changeTopic', function(req, res, next) {
req.session.chatgptSessionPrompt = ''
req.session.chatgptTopicCount = 0
res.status(200).json({
code: '0',
msg: "可以嘗試新的話題"
});
});

結(jié)語

總的來說,Open AI 開放出來的智能對話能力可以滿足基本需求,但是還有很大改進(jìn)空間。我在文中給出的代碼僅供參考,不保證功能上的完美。

附上源碼地址,可以點(diǎn)個 star 嗎,球球了[認(rèn)真臉]。

參考

?[1]體驗(yàn)鏈接: https://blog.wbjiang.cn/chatgpt

[2]ChatGPT: https://openai.com/

[3]注冊: https://beta.openai.com/login/

[4]SMS-ACTIVATE: https://sms-activate.org/cn

[5]Web工作臺: https://chat.openai.com/chat

[6]文檔: https://beta.openai.com/

[7]案例: https://beta.openai.com/examples

[8]openai: https://www.npmjs.com/package/openai

[9]生成一個 API Key: https://beta.openai.com/account/api-keys

[10]Key concepts: https://beta.openai.com/docs/introduction/key-concepts

[11]WebSocket 聊天功能: https://blog.wbjiang.cn/chat

[12]data-only server-sent events: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format

[13]源碼地址: https://github.com/cumt-robin/vue3-ts-blog-frontend

責(zé)任編輯:武曉燕 來源: 前端司南
相關(guān)推薦

2018-09-13 13:54:41

2014-12-28 09:05:28

2023-03-15 15:56:09

新華三

2023-09-07 16:18:50

網(wǎng)絡(luò)方案

2017-10-09 15:10:07

2015-10-21 14:53:24

微信賺錢用戶

2021-03-25 12:32:14

樹莓派Linux代碼

2017-08-23 09:35:14

Intel酷睿內(nèi)核

2017-08-15 16:45:14

鍵盤薄膜鍵盤機(jī)械鍵盤

2024-05-21 11:35:48

阿里云通義千問

2020-07-08 09:27:01

公司短信平臺

2015-03-30 00:56:48

2016-04-25 15:38:27

老司機(jī)VR看片

2021-04-19 11:30:20

激活碼Window 10微軟

2021-03-06 07:15:07

微軟Windows 10Windows

2019-12-25 14:15:02

開發(fā)技能代碼

2017-10-31 16:06:51

大數(shù)據(jù)90后消費(fèi)

2022-12-16 15:11:39

AI模型

2011-01-13 14:38:00

JavascriptCSSWeb

2023-02-27 10:45:16

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號