天天精选!基于最新 ChatGPT API 实现命令行版 ChatGPT
调用 API 需要传递 API KEY,我们不希望这个 KEY 硬编码在代码中,而是从系统环境变量读取,从而让代码更安全可维护;
调用封装好的 Go OpenAI 库与 API 接口进行交互,避免通过 HTTP 协议与原生 API 交互编写大量重复代码,让代码更简洁优雅;
做一个给客户使用的产品,美观会带来更好的用户体验,所以我希望即便是命令行应用,也尽可能让交互和输出更美观一些。对于第 2 点,可以使用 go-gpt3 库,这是一个通过 Go 封装的 OpenAI API 调用库。
(资料图片)
对于第3点,可以使用 glamour 库,这是一个 Go 语言实现的、能够在兼容 ANSI 终端基于样式渲染 Markdown 文本的第三方库,它是让命令行更美观的开源项目 Charm 的一部分。
因为项目很简单,又是在客户端本地使用,所以不需要做什么复杂的架构,下面直接进入编码部分。
代码编写
面向 ChatGPT 编程的核心就是把需求尽可能准确全面地转化为 Prompt 传递给 ChatGPT,这里有产品需求(来自业务和产品),也有代码设计和架构上的需求(来自开发者),然后让它生成代码,这是作为一个合格的 Prompt 工程师自我修养的必要组成部分。在《ChatGPT 提示的艺术 —— 编写清晰有效提示指南(二)》这篇教程中,我已经给大家介绍了编写清晰有效 Prompt 的原则、做法和技巧,感兴趣的可以去看下,这里我先将我的需求转化为 Prompt 让 ChatGPT 替我编写对应的 Go 代码实现:
代码优化看起来不错,基本流程没问题,但是代码审核会发现,它现在把输入的 Prompt 写死了,并不能动态接收用输入,而且运行一次后就退出了,这不是 ChatGPT 的问题,而是我们的需求并没有明确这一点,作为一个完整的需求和程序,需要说明是什么,怎么样,什么时候开始,什么时候退出。不过细心的同学可能还留意到go-gpt3包引入的时候没有设置别名,会导致运行时出错,同时调用的 OpenAI API 接口也不对,最新的 ChatGPT API 接口方法应该是CreateChatCompletion,可能是太新的缘故,ChatGPT 还没有学习到这里,不过这都属于 ChatGPT 要优化的点了。以及使用了另一个同名的包,这个也需要明确告知它。细节上还是需要优化,现在我们基于这些要点先来完善我们的 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("请设置 OPENAI_API_KEY 环境变量")return}// 初始化 Glamour 渲染器renderStyle := glamour.WithEnvironmentConfig()mdRenderer, err := glamour.NewTermRenderer(renderStyle,)if err != nil {fmt.Println("初始化 Markdown 渲染器失败")return}// 输出欢迎语(命令行应用启动界面)myFigure := figure.NewFigure("ChatGPT", "", true)myFigure.Print()fmt.Println("输入 start 启动应用,输入 quit 退出应用")// 创建 ChatGPT 客户端client := gpt3.NewClient(apiKey)if err != nil {fmt.Printf("创建客户端失败: %s\n", err.Error())return}messages := []gpt3.ChatCompletionMessage{{Role: "system",Content: "你是ChatGPT, OpenAI训练的大型语言模型, 请尽可能简洁地回答我的问题",},}// 读取用户输入并交互var userInput stringfor {fmt.Scanln(&userInput)if strings.ToLower(userInput) == "start" {fmt.Println("ChatGPT 启动成功,请输入您的问题:")} else if strings.ToLower(userInput) == "quit" {fmt.Println("ChatGPT 已退出")return} else if userInput != "" {messages = append(messages, gpt3.ChatCompletionMessage{Role: "user",Content: userInput,},)// 调用 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 接口调用失败: %s\n", err.Error())continue}// 格式化输出结果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 方法调用相关的代码还是需要手动调整,不过这也是我前面说的面向 ChatGPT 编程的原则之一,最后一定要审核 ChatGPT 的代码,它目前对于最新的知识还是有一定的迟滞性。
代码细节我就不展开解释了,有不明白的地方可以参考我在《面向 ChatGPT 编程实现全栈开发的 18 种方法》这篇教程中代码解释部分提供的方法自行去基于 ChatGPT 查看。
效果展示最后我们在终端体验一下这个命令行版 ChatGPT,我这里使用的是 Windows WSL 终端,Windows 终端本身体验其实不太好,尤其是中文输入的时候,删除字符特别费劲,且很容易造成消息变形,如果是 Mac 或者 Ubuntu 终端可能效果会更好一些。首先我们启动这个应用,如果没有设置OPENAI_API_KEY这个系统环境变量(运行export OPENAI_API_KEY={你的 OpenAI SECRET KEY}命令设置即可),会提示你设置:终端需要能够访问 OpenAI API 才能调用成功,这意味着命令行也要支持科学上网。
好了,这就是我们基于最新 ChatGPT API 实现的命令行版 ChatGPT 应用,因为 ChatGPT API 是前两天才发布的,所以看起来 ChatGPT 并没有学习到这个最新的 API 如何调用,存在迟滞性,进而导致编写的代码并不能直接满足需求,需要人为介入去修改,希望未来 ChatGPT 能够在这一块上有所进化。
欢迎点赞、关注、分享,更多关于 ChatGPT 的学习实践探讨,请关注这个订阅号或者点击阅读原文了解极客书房最新动态。