背景
最近chatGPT實在太火了,不談下都不好意思說自己在技術(shù)圈混了,剛好前段時間公司里面在舉辦一個企業(yè)微信機器人比賽,然后就用注冊了openai的一個賬號,用python寫了一個玩玩,但是想想不過癮只能公司內(nèi)部體驗,于是乎又花了2個小時寫了一個基于微信的聊天機器人,這里和大家交流下。
現(xiàn)實步驟
- 首先要能繞開openai 國內(nèi)的訪問限制,這一步驟大家自己想辦法;
- 需要注冊openai賬號,這一步驟大家也自己想辦法;
- 實現(xiàn)模擬登錄微信賬號,找到一個不錯的開源代碼,在我的github里面有;
- 在接收到微信信息時調(diào)用openai的api,這里我使用了go sdk 開源地址也在在我的github里。
相關(guān)功能
我的簡單demo代碼開源拉了github地址點這里
功能介紹:文章來源:http://www.zghlxwxcb.cn/news/detail-428005.html
- 支持回復(fù)微信文本消息
- 支持文本回復(fù)圖片消息
- 支持群消息at回復(fù)
- 支持上下文記憶
- 支持基于網(wǎng)頁鏈接地址讀取總結(jié)文件
演示圖片
群里時at它
單聊時直接發(fā)送消息,支持根據(jù)語義來返回圖片文章來源地址http://www.zghlxwxcb.cn/news/detail-428005.html
實現(xiàn)原理
- 調(diào)用微信桌面客戶端協(xié)議登錄微信號
- 監(jiān)聽微信文本消息進行回復(fù)
- 在回復(fù)消息之前對消息進行語義識別,這里單獨寫了一個prompt來識別文本的語義是來生成圖片還是進行文本回復(fù)就行,如果是生成圖片的話,就直接回復(fù)圖片。
源碼
package main
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"image/png"
"os"
"strings"
"time"
"github.com/eatmoreapple/openwechat"
openai "github.com/sashabaranov/go-openai"
)
// OpenaiWechat
type OpenaiWechat struct {
ChatBot *openwechat.Bot // chat bot
Openai *openai.Client // openai client
userDialogue map[string][]openai.ChatCompletionMessage // save user gialogue context
}
var singleton *OpenaiWechat
// firstUserDialogue
var firstUserDialogue = []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "你是一個非常有幫助的助手",
},
{
Role: openai.ChatMessageRoleUser,
Content: "請問我怎么規(guī)劃我的職業(yè)發(fā)展呢?",
},
{
Role: openai.ChatMessageRoleAssistant,
Content: `你好,我很高興能幫助你。職業(yè)發(fā)展是一個非常重要的話題,你可以從以下幾個方面考慮:
你的興趣愛好和技能:你可以考慮你的興趣愛好和技能,看看哪些職業(yè)與你的興趣愛好和技能相匹配。這樣可以讓你在工作中感到更有成就感和滿足感。
行業(yè)前景:你可以考慮一些行業(yè)的前景,看看哪些行業(yè)在未來幾年內(nèi)會有更好的發(fā)展前景。這樣可以讓你在職業(yè)發(fā)展中更有保障。
學(xué)歷和培訓(xùn):你可以考慮你的學(xué)歷和培訓(xùn),看看哪些職業(yè)需要更高的學(xué)歷和培訓(xùn)。這樣可以讓你更好地規(guī)劃你的職業(yè)發(fā)展。
希望這些建議能幫到你。你還有其他問題嗎?`,
},
}
func init() {
//bot := openwechat.DefaultBot()
bot := openwechat.DefaultBot(openwechat.Desktop) // deskop mode,you can switch deskop mode if defualt can not login
// Register message handler function
bot.MessageHandler = func(msg *openwechat.Message) {
replyChatMsg(msg)
}
// Register login QR code callback
bot.UUIDCallback = openwechat.PrintlnQrcodeUrl
// login
if err := bot.Login(); err != nil {
fmt.Println(err)
return
}
client := openai.NewClient(os.Getenv("OPENAI_KEY")) // your openai key
//client := openai.NewClient("")
singleton = &OpenaiWechat{
Openai: client,
ChatBot: bot,
}
singleton.userDialogue = make(map[string][]openai.ChatCompletionMessage)
// lock goroutine, until an exception occurs or the user actively exits
bot.Block()
}
func main() {
}
// replyChatMsg
func replyChatMsg(msg *openwechat.Message) error {
if msg.IsSendByGroup() { // only accept group messages and send them to yourself
if !msg.IsAt() {
return nil
}
} else if !msg.IsSendByFriend() { // non-group messages only accept messages from your own friends
return nil
}
if msg.IsSendBySelf() { // self sent to self no reply
return nil
}
// only handle text messages
if msg.IsText() {
msg.Content = strings.Replace(msg.Content, "@GPT3.5?", "", 1)
fmt.Println(msg.Content)
// simple match processing
if isImage, _ := isImageContent(msg.Content); isImage {
return replyImage(msg)
}
return replyText(msg)
}
return nil
}
// 圖片生成校驗
var imageMessage = []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "你現(xiàn)在是一個語義識別助手,用戶輸入一個文本,你根據(jù)文本的內(nèi)容來判斷用戶是不是想生成圖片,是的話你就回復(fù)是,不是的話你就回復(fù)否,記住只能回復(fù):是 或者 否",
},
{
Role: openai.ChatMessageRoleUser,
Content: "我想生成一張小花貓的圖片",
},
{
Role: openai.ChatMessageRoleAssistant,
Content: "是",
},
{
Role: openai.ChatMessageRoleUser,
Content: "請問冬天下雨我該穿什么衣服",
},
{
Role: openai.ChatMessageRoleAssistant,
Content: "否",
},
}
// isImageConntent
func isImageContent(content string) (bool, error) {
temp := append(imageMessage, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: content,
})
result, err := callOpenaiChat(temp)
if err != nil {
fmt.Printf("isImageConntent callOpenaiChat error: %v\n", err)
return false, err
}
fmt.Printf("isImageContent result: %s", result)
if strings.TrimSpace(result) == "是" {
return true, nil
}
return false, nil
}
// replyImage
func replyImage(msg *openwechat.Message) error {
path, err := generateImage(msg)
if err != nil {
fmt.Printf("replyImage generateImage error: %v\n", err)
return err
}
img, err := os.Open(path)
if err != nil {
fmt.Printf("replyImage Open error: %v\n", err)
return err
}
defer img.Close()
msg.ReplyImage(img)
return nil
}
// replyText
func replyText(msg *openwechat.Message) error {
messages, err := genMessage(msg)
if err != nil {
return err
}
result, err := callOpenaiChat(messages)
if err != nil {
fmt.Println(err)
return err
}
msg.ReplyText(result)
addResultToMessage(result, msg)
return nil
}
// dialogue context max length
const maxLength = 33
// temporary folder to save pictures
const filePath = "./images/"
// genMessage
func genMessage(msg *openwechat.Message) ([]openai.ChatCompletionMessage, error) {
// TODO
if _, ok := singleton.userDialogue[msg.FromUserName]; !ok {
singleton.userDialogue[msg.FromUserName] = firstUserDialogue
} else if len(singleton.userDialogue[msg.FromUserName]) >= maxLength {
singleton.userDialogue[msg.FromUserName] = singleton.userDialogue[msg.FromUserName][2:]
}
element := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: msg.Content,
}
singleton.userDialogue[msg.FromUserName] = append(singleton.userDialogue[msg.FromUserName], element)
return singleton.userDialogue[msg.FromUserName], nil
}
// addResultToMessage
func addResultToMessage(result string, msg *openwechat.Message) error {
element := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleAssistant,
Content: result,
}
singleton.userDialogue[msg.FromUserName] = append(singleton.userDialogue[msg.FromUserName], element)
return nil
}
// generateImage
func generateImage(msg *openwechat.Message) (string, error) {
ctx := context.Background()
// Sample image by link
reqUrl := openai.ImageRequest{
Prompt: msg.Content,
Size: openai.CreateImageSize512x512,
ResponseFormat: openai.CreateImageResponseFormatB64JSON,
N: 1,
}
respBase64, err := singleton.Openai.CreateImage(ctx, reqUrl)
if err != nil {
fmt.Printf("Image creation error: %v\n", err)
return "", err
}
imgBytes, err := base64.StdEncoding.DecodeString(respBase64.Data[0].B64JSON)
if err != nil {
fmt.Printf("Base64 decode error: %v\n", err)
return "", err
}
r := bytes.NewReader(imgBytes)
imgData, err := png.Decode(r)
if err != nil {
fmt.Printf("PNG decode error: %v\n", err)
return "", err
}
filePath := fmt.Sprintf("%s%d.png", filePath, time.Now().UnixMicro())
file, err := os.Create(filePath)
if err != nil {
fmt.Printf("File creation error: %v\n", err)
return "", err
}
defer file.Close()
if err := png.Encode(file, imgData); err != nil {
fmt.Printf("PNG encode error: %v\n", err)
return "", err
}
return filePath, nil
}
// callOpenaiChat
func callOpenaiChat(messages []openai.ChatCompletionMessage) (string, error) {
resp, err := singleton.Openai.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: messages,
MaxTokens: 512,
},
)
if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return "", err
}
fmt.Println(resp.Choices[0].Message.Content)
return resp.Choices[0].Message.Content, nil
}
注意
- 微信號最好不要用自己常用的微信號,因為模擬登錄微信屬于作弊行為很有可能會被封號;
- 注冊的新的微信號可能無法馬上模擬登錄成功微信,需要實名認證之后,過一段時間才能成功登錄。
- 注冊的openai賬號里面默認有18刀,調(diào)用不同的模型話費的tokens不一樣,測試的時候注意測試自己的消耗速度,圖片比文本更耗錢。
到了這里,關(guān)于3分鐘自定義你的chatGPT聊天微信機器人的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!