基于最新 ChatGPT API 實(shí)現(xiàn)命令行版 ChatGPT

引子
OpenAI 這兩天發(fā)布了 ChatGPT API,基于 gpt-3.5-turbo 模型,這是一個(gè) GPT-3.5 的優(yōu)化版本,用于支持開(kāi)發(fā)者把 ChatGPT 集成到自己的產(chǎn)品中,同時(shí)把 API 調(diào)用價(jià)格降到 $0.002 每千 token,意味著處理 100萬(wàn)字符的文本只需要 2 美元,也就是差不多十幾塊錢人民幣,效果更好、價(jià)格更低,這讓 ChatGPT API 更具性價(jià)比,因此這兩天基于 ChatGPT API 的各種套殼應(yīng)用如雨后春筍般大量冒出。
我也來(lái)湊個(gè)熱鬧,試一試水,正好在我今天新建的 ChatGPT 互助討論群里有人問(wèn)有沒(méi)有命令行版 ChatGPT,那就拿它來(lái)開(kāi)刀吧:
調(diào)用 API 需要傳遞 API KEY,我們不希望這個(gè) KEY 硬編碼在代碼中,而是從系統(tǒng)環(huán)境變量讀取,從而讓代碼更安全可維護(hù);
調(diào)用封裝好的 Go OpenAI 庫(kù)與 API 接口進(jìn)行交互,避免通過(guò) HTTP 協(xié)議與原生 API 交互編寫(xiě)大量重復(fù)代碼,讓代碼更簡(jiǎn)潔優(yōu)雅;
- 做一個(gè)給客戶使用的產(chǎn)品,美觀會(huì)帶來(lái)更好的用戶體驗(yàn),所以我希望即便是命令行應(yīng)用,也盡可能讓交互和輸出更美觀一些。
對(duì)于第 2 點(diǎn),可以使用 go-gpt3 庫(kù),這是一個(gè)通過(guò) Go 封裝的 OpenAI API 調(diào)用庫(kù)。
對(duì)于第3點(diǎn),可以使用 glamour 庫(kù),這是一個(gè) Go 語(yǔ)言實(shí)現(xiàn)的、能夠在兼容 ANSI 終端基于樣式渲染 Markdown 文本的第三方庫(kù),它是讓命令行更美觀的開(kāi)源項(xiàng)目 Charm 的一部分。
因?yàn)轫?xiàng)目很簡(jiǎn)單,又是在客戶端本地使用,所以不需要做什么復(fù)雜的架構(gòu),下面直接進(jìn)入編碼部分。
代碼編寫(xiě)
面向 ChatGPT 編程的核心就是把需求盡可能準(zhǔn)確全面地轉(zhuǎn)化為 Prompt 傳遞給 ChatGPT,這里有產(chǎn)品需求(來(lái)自業(yè)務(wù)和產(chǎn)品),也有代碼設(shè)計(jì)和架構(gòu)上的需求(來(lái)自開(kāi)發(fā)者),然后讓它生成代碼,這是作為一個(gè)合格的 Prompt 工程師自我修養(yǎng)的必要組成部分。
在《ChatGPT 提示的藝術(shù) —— 編寫(xiě)清晰有效提示指南(二)》這篇教程中,我已經(jīng)給大家介紹了編寫(xiě)清晰有效 Prompt 的原則、做法和技巧,感興趣的可以去看下,這里我先將我的需求轉(zhuǎn)化為 Prompt 讓 ChatGPT 替我編寫(xiě)對(duì)應(yīng)的 Go 代碼實(shí)現(xiàn):



代碼優(yōu)化
看起來(lái)不錯(cuò),基本流程沒(méi)問(wèn)題,但是代碼審核會(huì)發(fā)現(xiàn),它現(xiàn)在把輸入的 Prompt 寫(xiě)死了,并不能動(dòng)態(tài)接收用輸入,而且運(yùn)行一次后就退出了,這不是 ChatGPT 的問(wèn)題,而是我們的需求并沒(méi)有明確這一點(diǎn),作為一個(gè)完整的需求和程序,需要說(shuō)明是什么,怎么樣,什么時(shí)候開(kāi)始,什么時(shí)候退出。
不過(guò)細(xì)心的同學(xué)可能還留意到 go-gpt3 包引入的時(shí)候沒(méi)有設(shè)置別名,會(huì)導(dǎo)致運(yùn)行時(shí)出錯(cuò),同時(shí)調(diào)用的 OpenAI API 接口也不對(duì),最新的 ChatGPT API 接口方法應(yīng)該是 CreateChatCompletion,可能是太新的緣故,ChatGPT 還沒(méi)有學(xué)習(xí)到這里,不過(guò)這都屬于 ChatGPT 要優(yōu)化的點(diǎn)了。以及使用了另一個(gè)同名的包,這個(gè)也需要明確告知它。
細(xì)節(jié)上還是需要優(yōu)化,現(xiàn)在我們基于這些要點(diǎn)先來(lái)完善我們的 Prompt:

start 啟動(dòng),輸入 quit 退出,在此期間則不斷讀取用戶輸入,返回 ChatGPT 處理結(jié)果并渲染到控制臺(tái)輸出,另外,我還在讀取系統(tǒng)環(huán)境變量時(shí)囑咐通過(guò) os.Getenv 獲取,因?yàn)橛袝r(shí)候發(fā)現(xiàn) ChatGPT 輸出不穩(wěn)定,會(huì)嘗試從 .env 文件讀取 OPENAI_API_KEY,當(dāng)然這不是 ChatGPT 的問(wèn)題,是我們沒(méi)有給出明確清晰的 Prompt。package mainimport ("context""fmt""os""strings""github.com/charmbracelet/glamour""github.com/common-nighthawk/go-figure"gpt3 "github.com/sashabaranov/go-gpt3")func main() {// 獲取 OpenAI API KeyapiKey := os.Getenv("OPENAI_API_KEY")if apiKey == "" {fmt.Println("請(qǐng)?jiān)O(shè)置 OPENAI_API_KEY 環(huán)境變量")return}// 初始化 Glamour 渲染器renderStyle := glamour.WithEnvironmentConfig()mdRenderer, err := glamour.NewTermRenderer(renderStyle,)if err != nil {fmt.Println("初始化 Markdown 渲染器失敗")return}// 輸出歡迎語(yǔ)(命令行應(yīng)用啟動(dòng)界面)myFigure := figure.NewFigure("ChatGPT", "", true)myFigure.Print()fmt.Println("輸入 start 啟動(dòng)應(yīng)用,輸入 quit 退出應(yīng)用")// 創(chuàng)建 ChatGPT 客戶端client := gpt3.NewClient(apiKey)if err != nil {fmt.Printf("創(chuàng)建客戶端失敗: %s\n", err.Error())return}messages := []gpt3.ChatCompletionMessage{{Role: "system",Content: "你是ChatGPT, OpenAI訓(xùn)練的大型語(yǔ)言模型, 請(qǐng)盡可能簡(jiǎn)潔地回答我的問(wèn)題",},}// 讀取用戶輸入并交互var userInput stringfor {fmt.Scanln(&userInput)if strings.ToLower(userInput) == "start" {fmt.Println("ChatGPT 啟動(dòng)成功,請(qǐng)輸入您的問(wèn)題:")} else if strings.ToLower(userInput) == "quit" {fmt.Println("ChatGPT 已退出")return} else if userInput != "" {messages = append(messages, gpt3.ChatCompletionMessage{Role: "user",Content: userInput,},)// 調(diào)用 ChatGPT API 接口生成回答resp, err := client.CreateChatCompletion(context.Background(),gpt3.ChatCompletionRequest{Model: gpt3.GPT3Dot5Turbo,Messages: messages,MaxTokens: 1024,Temperature: 0,N: 1,},)if err != nil {fmt.Printf("ChatGPT 接口調(diào)用失敗: %s\n", err.Error())continue}// 格式化輸出結(jié)果output := resp.Choices[0].Message.ContentmdOutput, err := mdRenderer.Render(output)if err != nil {fmt.Printf("Markdown 渲染失敗: %s\n", err.Error())continue}fmt.Println(mdOutput)messages = append(messages, gpt3.ChatCompletionMessage{Role: "assistant",Content: output,},)}}}
gpt3 別名和 CreateChatCompletion 方法調(diào)用相關(guān)的代碼還是需要手動(dòng)調(diào)整,不過(guò)這也是我前面說(shuō)的面向 ChatGPT 編程的原則之一,最后一定要審核 ChatGPT 的代碼,它目前對(duì)于最新的知識(shí)還是有一定的遲滯性。
代碼細(xì)節(jié)我就不展開(kāi)解釋了,有不明白的地方可以參考我在《面向 ChatGPT 編程實(shí)現(xiàn)全棧開(kāi)發(fā)的 18 種方法》這篇教程中代碼解釋部分提供的方法自行去基于 ChatGPT 查看。
效果展示
最后我們?cè)诮K端體驗(yàn)一下這個(gè)命令行版 ChatGPT,我這里使用的是 Windows WSL 終端,Windows 終端本身體驗(yàn)其實(shí)不太好,尤其是中文輸入的時(shí)候,刪除字符特別費(fèi)勁,且很容易造成消息變形,如果是 Mac 或者 Ubuntu 終端可能效果會(huì)更好一些。
首先我們啟動(dòng)這個(gè)應(yīng)用,如果沒(méi)有設(shè)置 OPENAI_API_KEY 這個(gè)系統(tǒng)環(huán)境變量(運(yùn)行 export OPENAI_API_KEY={你的 OpenAI SECRET KEY} 命令設(shè)置即可),會(huì)提示你設(shè)置:


start 即可啟動(dòng)應(yīng)用,然后我們?cè)诿钚休斎雴?wèn)題,回車,就會(huì)將問(wèn)題提交給 ChatGPT,ChatGPT 處理的結(jié)果會(huì)返回并輸出到控制臺(tái),這里的格式經(jīng)過(guò)了 Glamour 庫(kù)的美化:
quit 退出應(yīng)用。終端需要能夠訪問(wèn) OpenAI API 才能調(diào)用成功,這意味著命令行也要支持科學(xué)上網(wǎng)。
好了,這就是我們基于最新 ChatGPT API 實(shí)現(xiàn)的命令行版 ChatGPT 應(yīng)用,因?yàn)?ChatGPT API 是前兩天才發(fā)布的,所以看起來(lái) ChatGPT 并沒(méi)有學(xué)習(xí)到這個(gè)最新的 API 如何調(diào)用,存在遲滯性,進(jìn)而導(dǎo)致編寫(xiě)的代碼并不能直接滿足需求,需要人為介入去修改,希望未來(lái) ChatGPT 能夠在這一塊上有所進(jìn)化。
歡迎點(diǎn)贊、關(guān)注、分享,更多關(guān)于 ChatGPT 的學(xué)習(xí)實(shí)踐探討,請(qǐng)關(guān)注這個(gè)訂閱號(hào)或者點(diǎn)擊閱讀原文了解極客書(shū)房最新動(dòng)態(tài)。

