Skip to content

Commit

Permalink
1.重构项目代码,增加阅读性
Browse files Browse the repository at this point in the history
2.群聊增加引用回复
3.修复会话过长引起GPT请求失败BUG
4.清空会话口令更改为可配置
  • Loading branch information
869413421 committed Dec 11, 2022
1 parent 4039fb4 commit afec292
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 155 deletions.
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> `友链:`[chatgpt-dingtalk](https://github.com/eryajf/chatgpt-dingtalk) 本项目可以将GPT机器人集成到钉钉群聊中。
[![Release](https://img.shields.io/github/v/release/869413421/wechatbot.svg?style=flat-square)](https://github.com/869413421/wechatbot/releases/tag/v1.1.2)
[![Release](https://img.shields.io/github/v/release/869413421/wechatbot.svg?style=flat-square)](https://github.com/869413421/wechatbot/releases/tag/v1.1.3)
![Github stars](https://img.shields.io/github/stars/869413421/wechatbot.svg)
![Forks](https://img.shields.io/github/forks/869413421/wechatbot.svg?style=flat-square)

Expand Down Expand Up @@ -52,7 +52,7 @@

```sh
# 运行项目,环境变量参考下方配置说明
$ docker run -itd --name wechatbot --restart=always -e APIKEY=xxxx -e AUTO_PASS=false -e SESSION_TIMEOUT=60s -e MODEL=text-davinci-003 -e MAX_TOKENS=512 -e TEMPREATURE=0.9 -e REPLY_PREFIX=我是来自机器人回复: docker.mirrors.sjtug.sjtu.edu.cn/qingshui869413421/wechatbot:latest
$ docker run -itd --name wechatbot --restart=always -e APIKEY=换成你的key -e AUTO_PASS=false -e SESSION_TIMEOUT=60s -e MODEL=text-davinci-003 -e MAX_TOKENS=512 -e TEMPREATURE=0.9 -e REPLY_PREFIX=我是来自机器人回复: -e SESSION_CLEAR_TOKEN=下一个问题 docker.mirrors.sjtug.sjtu.edu.cn/qingshui869413421/wechatbot:latest

# 查看二维码
$ docker exec -it wechatbot bash
Expand All @@ -79,7 +79,7 @@ $ tail -f -n 50 /app/run.log

# 快速开始

> 非技术人员请直接下载release中的[压缩包](https://github.com/869413421/wechatbot/releases/tag/v1.1.2) ,解压运行。
> 非技术人员请直接下载release中的[压缩包](https://github.com/869413421/wechatbot/releases/tag/v1.1.3) ,解压运行。
````
# 获取项目
Expand All @@ -99,19 +99,22 @@ go run main.go
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' -o wechatbot ./main.go
# 守护进程运行
nohup ./wechatbot > run.log &
# 查看二维码
# tail -f -n 50 run.log
````

# 配置文件说明

````
{
"api_key": "your api key",
"auto_pass": false,
"auto_pass": true,
"session_timeout": 60,
"max_tokens": 512,
"max_tokens": 1024,
"model": "text-davinci-003",
"temperature": 0.9,
"reply_prefix": "来自机器人回复:"
"temperature": 1,
"reply_prefix": "来自机器人回复:",
"session_clear_token": "清空会话"
}
api_key:openai api_key
Expand All @@ -121,11 +124,12 @@ max_tokens: GPT响应字符数,最大2048,默认值512。max_tokens会影响
model: GPT选用模型,默认text-davinci-003,具体选项参考官网训练场
temperature: GPT热度,0到1,默认0.9。数字越大创造力越强,但更偏离训练事实,越低越接近训练事实
reply_prefix: 私聊回复前缀
session_clear_token: 会话清空口令,默认`下一个问题`
````

# 使用示例

### 向机器人发送`我要问下一个问题`,清空会话信息。
### 向机器人发送`下一个问题`,清空会话信息。

### 私聊

Expand All @@ -135,9 +139,4 @@ reply_prefix: 私聊回复前缀

<img width="300px" src="https://raw.githubusercontent.com/869413421/study/master/static/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221208153015.jpg"/>

### 添加微信(备注: wechabot)进群交流

**如果二维码图片没显示出来,请添加微信号 huangyanming681925**

<img width="210px" src="https://raw.githubusercontent.com/869413421/study/master/static/qr.png" align="left">

7 changes: 4 additions & 3 deletions config.dev.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"api_key": "api key",
"api_key": "your api key",
"auto_pass": true,
"session_timeout": 60,
"max_tokens": 512,
"max_tokens": 1024,
"model": "text-davinci-003",
"temperature": 1,
"reply_prefix": "来自机器人回复:"
"reply_prefix": "来自机器人回复:",
"session_clear_token": "清空会话"
}
27 changes: 18 additions & 9 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package config

import (
"encoding/json"
"fmt"
"github.com/869413421/wechatbot/pkg/logger"
"log"
"os"
"strconv"
Expand All @@ -25,6 +27,8 @@ type Configuration struct {
Temperature float64 `json:"temperature"`
// 回复前缀
ReplyPrefix string `json:"reply_prefix"`
// 清空会话口令
SessionClearToken string `json:"session_clear_token"`
}

var config *Configuration
Expand All @@ -35,11 +39,12 @@ func LoadConfig() *Configuration {
once.Do(func() {
// 给配置赋默认值
config = &Configuration{
AutoPass: false,
SessionTimeout: 60,
MaxTokens: 512,
Model: "text-davinci-003",
Temperature: 0.9,
AutoPass: false,
SessionTimeout: 60,
MaxTokens: 512,
Model: "text-davinci-003",
Temperature: 0.9,
SessionClearToken: "下一个问题",
}

// 判断配置文件是否存在,存在直接JSON读取
Expand All @@ -66,6 +71,7 @@ func LoadConfig() *Configuration {
MaxTokens := os.Getenv("MAX_TOKENS")
Temperature := os.Getenv("TEMPREATURE")
ReplyPrefix := os.Getenv("REPLY_PREFIX")
SessionClearToken := os.Getenv("SESSION_CLEAR_TOKEN")
if ApiKey != "" {
config.ApiKey = ApiKey
}
Expand All @@ -75,7 +81,7 @@ func LoadConfig() *Configuration {
if SessionTimeout != "" {
duration, err := time.ParseDuration(SessionTimeout)
if err != nil {
log.Fatalf("config session timeout err: %v ,get is %v", err, SessionTimeout)
logger.Danger(fmt.Sprintf("config session timeout err: %v ,get is %v", err, SessionTimeout))
return
}
config.SessionTimeout = duration
Expand All @@ -86,25 +92,28 @@ func LoadConfig() *Configuration {
if MaxTokens != "" {
max, err := strconv.Atoi(MaxTokens)
if err != nil {
log.Fatalf("config MaxTokens err: %v ,get is %v", err, MaxTokens)
logger.Danger(fmt.Sprintf("config MaxTokens err: %v ,get is %v", err, MaxTokens))
return
}
config.MaxTokens = uint(max)
}
if Temperature != "" {
temp, err := strconv.ParseFloat(Temperature, 64)
if err != nil {
log.Fatalf("config Temperature err: %v ,get is %v", err, Temperature)
logger.Danger(fmt.Sprintf("config Temperature err: %v ,get is %v", err, Temperature))
return
}
config.Temperature = temp
}
if ReplyPrefix != "" {
config.ReplyPrefix = ReplyPrefix
}
if SessionClearToken != "" {
config.SessionClearToken = SessionClearToken
}
})
if config.ApiKey == "" {
log.Fatalf("config err: api key reqired")
logger.Danger("config err: api key required")
}

return config
Expand Down
153 changes: 96 additions & 57 deletions handlers/group_msg_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"github.com/869413421/wechatbot/gtp"
"github.com/869413421/wechatbot/pkg/logger"
"github.com/869413421/wechatbot/service"
"github.com/eatmoreapple/openwechat"
"strings"
)
Expand All @@ -13,95 +14,133 @@ var _ MessageHandlerInterface = (*GroupMessageHandler)(nil)

// GroupMessageHandler 群消息处理
type GroupMessageHandler struct {
}

// handle 处理消息
func (g *GroupMessageHandler) handle(msg *openwechat.Message) error {
if msg.IsText() {
return g.ReplyText(msg)
}
return nil
// 获取自己
self *openwechat.Self
// 群
group *openwechat.Group
// 接收到消息
msg *openwechat.Message
// 发送的用户
sender *openwechat.User
// 实现的用户业务
service service.UserServiceInterface
}

// NewGroupMessageHandler 创建群消息处理器
func NewGroupMessageHandler() MessageHandlerInterface {
return &GroupMessageHandler{}
}

// ReplyText 发送文本消息到群
func (g *GroupMessageHandler) ReplyText(msg *openwechat.Message) error {
// 接收群消息
func NewGroupMessageHandler(msg *openwechat.Message) (MessageHandlerInterface, error) {
sender, err := msg.Sender()
group := openwechat.Group{User: sender}
logger.Info(fmt.Sprintf("Received Group %v Text Msg : %v", group.NickName, msg.Content))

// 不是@的不处理
if !msg.IsAt() {
return nil
if err != nil {
return nil, err
}

// 获取@我的用户
group := &openwechat.Group{User: sender}
groupSender, err := msg.SenderInGroup()
if err != nil {
return errors.New(fmt.Sprintf("get sender in group error :%v ", err))
return nil, err
}
atText := "@" + groupSender.NickName + " "

if UserService.ClearUserSessionContext(groupSender.ID(), msg.Content) {
_, err = msg.ReplyText(atText + "上下文已经清空了,你可以问下一个问题啦。")
if err != nil {
return errors.New(fmt.Sprintf("response user error: %v", err))
}
userService := service.NewUserService(c, groupSender)
handler := &GroupMessageHandler{
self: sender.Self,
msg: msg,
group: group,
sender: groupSender,
service: userService,
}
return handler, nil

}

// handle 处理消息
func (g *GroupMessageHandler) handle() error {
if g.msg.IsText() {
return g.ReplyText()
}
return nil
}

// ReplyText 发送文本消息到群
func (g *GroupMessageHandler) ReplyText() error {
logger.Info(fmt.Sprintf("Received Group %v Text Msg : %v", g.group.NickName, g.msg.Content))
// 1.不是@的不处理
if !g.msg.IsAt() {
return nil
}

// 替换掉@文本,设置会话上下文,然后向GPT发起请求。
requestText := buildRequestText(sender, msg)
// 2.获取请求的文本,如果为空字符串不处理
requestText := g.getRequestText()
if requestText == "" {
logger.Info("user message is null")
return nil
}

// 3.请求GPT获取回复
reply, err := gtp.Completions(requestText)
if err != nil {
// 将GPT请求失败信息输出给用户,省得整天来问又不知道日志在哪里。
// 2.1 将GPT请求失败信息输出给用户,省得整天来问又不知道日志在哪里。
errMsg := fmt.Sprintf("gtp request error: %v", err)
_, err = msg.ReplyText(errMsg)
_, err = g.msg.ReplyText(errMsg)
if err != nil {
return errors.New(fmt.Sprintf("response group error: %v ", err))
}
return err
}
if reply == "" {
return nil
}

// 设置上下文
UserService.SetUserSessionContext(sender.ID(), requestText, reply)
replyText := atText + buildGroupReply(reply)
_, err = msg.ReplyText(replyText)
// 4.设置上下文,并响应信息给用户
selfName := "@" + g.self.NickName
question := strings.ReplaceAll(g.msg.Content, selfName, "")
g.service.SetUserSessionContext(question, reply)
_, err = g.msg.ReplyText(g.buildReplyText(reply))
if err != nil {
return errors.New(fmt.Sprintf("response group error: %v ", err))
return errors.New(fmt.Sprintf("response user error: %v ", err))
}

// 5.返回错误信息
return err
}

// buildUserReply 构建用户回复
func buildGroupReply(reply string) string {
// 回复@我的用户
reply = strings.Trim(strings.Trim(reply, "?"), "\n")
if reply == "" {
return "请求得不到任何有意义的回复,请具体提出问题。"
}
reply = strings.Trim(reply, "\n")
return reply
}
// getRequestText 获取请求接口的文本,要做一些清洗
func (g *GroupMessageHandler) getRequestText() string {
// 1.去除空格以及换行
requestText := strings.TrimSpace(g.msg.Content)
requestText = strings.Trim(g.msg.Content, "\n")

// buildRequestText 构建请求GPT的文本,替换掉机器人名称,然后检查是否有上下文,如果有拼接上
func buildRequestText(sender *openwechat.User, msg *openwechat.Message) string {
replaceText := "@" + sender.Self.NickName
requestText := strings.TrimSpace(strings.ReplaceAll(msg.Content, replaceText, ""))
// 2.替换掉当前用户名称
replaceText := "@" + g.self.NickName
requestText = strings.TrimSpace(strings.ReplaceAll(g.msg.Content, replaceText, ""))
if requestText == "" {
return ""
}
requestText = UserService.GetUserSessionContext(sender.ID()) + requestText

// 3.获取上下文,拼接在一起,如果字符长度超出4000,截取为4000。(GPT按字符长度算)
requestText = g.service.GetUserSessionContext() + requestText
if len(requestText) >= 4000 {
requestText = requestText[:4000]
}

// 4.返回请求文本
return requestText
}

// buildReply 构建回复文本
func (g *GroupMessageHandler) buildReplyText(reply string) string {
// 1.获取@我的用户
atText := "@" + g.sender.NickName
textSplit := strings.Split(reply, "\n\n")
if len(textSplit) > 1 {
trimText := textSplit[0]
reply = strings.Trim(reply, trimText)
}
reply = strings.TrimSpace(reply)
if reply == "" {
return atText + " 请求得不到任何有意义的回复,请具体提出问题。"
}

// 2.拼接回复,@我的用户,问题,回复
replaceText := "@" + g.self.NickName
question := strings.TrimSpace(strings.ReplaceAll(g.msg.Content, replaceText, ""))
reply = atText + "\n" + question + "\n --------------------------------\n" + reply
reply = strings.Trim(reply, "\n")

// 3.返回回复的内容
return reply
}
Loading

0 comments on commit afec292

Please sign in to comment.