feat(ai): 添加 Redis 支持以增强上下文对话功能
This commit is contained in:
parent
cb0e2647a9
commit
472610e757
12 changed files with 369 additions and 52 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,5 +4,5 @@ test*.json
|
||||||
request.json
|
request.json
|
||||||
config.toml
|
config.toml
|
||||||
messages.json
|
messages.json
|
||||||
.vscode/settings.json
|
.vscode
|
||||||
.idea
|
.idea
|
|
@ -33,7 +33,8 @@
|
||||||
## 使用说明:
|
## 使用说明:
|
||||||
1. 默认的配置端口是5580,如果需要修改,请修改配置文件里的`APIURL`字段。默认获取以`!`开头的消息,如果需要修改,请修改配置文件里的`Prefix`字段。
|
1. 默认的配置端口是5580,如果需要修改,请修改配置文件里的`APIURL`字段。默认获取以`!`开头的消息,如果需要修改,请修改配置文件里的`Prefix`字段。
|
||||||
|
|
||||||
当前拥有的插件:
|
2. 现在加入了`Redis`支持,如果需要使用`Redis`,请在配置文件里配置`REDIS`的相关信息,使用`Redis`可以支持`ai`插件的`Context`功能,即支持上下文对话,同时让机器人拥有更高的扩展性。
|
||||||
|
3. 当前拥有的插件:
|
||||||
- `ping`:判断程序运行,会响应`Pong!`
|
- `ping`:判断程序运行,会响应`Pong!`
|
||||||
- `ip`:查询一个ip的详细信息,使用方法`ip <域名/ip>`
|
- `ip`:查询一个ip的详细信息,使用方法`ip <域名/ip>`
|
||||||
- `lsp`:请求`api.lolicon.app`获取`Pixiv`壁纸,暂时只简单实现获取随机壁纸。
|
- `lsp`:请求`api.lolicon.app`获取`Pixiv`壁纸,暂时只简单实现获取随机壁纸。
|
||||||
|
|
|
@ -8,7 +8,14 @@ AllowRole = []
|
||||||
BlockGroup = [] # 群组黑名单
|
BlockGroup = [] # 群组黑名单
|
||||||
BlockUser = []
|
BlockUser = []
|
||||||
Master = []
|
Master = []
|
||||||
|
[OPENAI]
|
||||||
MODEL = "gpt-4o"
|
MODEL = "gpt-4o"
|
||||||
OPENAI_API_KEY = "sk-xxxx"
|
OPENAI_API_KEY = "sk-xxxx"
|
||||||
OPENAI_BaseURL = "https://api.openai.com/v1"
|
OPENAI_BaseURL = "https://api.openai.com/v1"
|
||||||
PROMPT = "## Role : \nQQ群Linux助手\n\n## Background : \n该角色是一位专门为QQ群提供Linux相关技术支持的助手,熟悉各种Linux发行版的使用和问题解决,旨在帮助群友解决他们在使用Linux过程中遇到的各种问题。\n\n## Preferences : \n该角色偏好简洁明了的回答风格,注重解决方案的精确性和有效性。喜欢友好和尊重的互动方式,致力于营造一个技术交流的和谐环境。\n\n## Profile :\n- language: 中文\n- description: 为QQ群友提供Linux相关问题的简洁、精确解决方案\n\n## Goals :\n1. 回答Linux相关问题\n2. 提供简短、精确的解决方案\n3. 鼓励群友提出问题并尊重他们\n\n## Constrains :\n1. 避免回答政治相关内容\n2. 每个问题只回答一次,确保回复简短、精确\n\n## Skills :\n1. 熟悉各种Linux发行版的使用和问题解决\n2. 能够提供简洁明了的技术支持\n3. 友好回复群友的提问\n\n## Examples :\n1. 问题:如何查看当前Linux系统的内核版本?\n 答案:可以使用命令`uname -r`来查看当前的内核版本。\n \n2. 问题:如何安装一个软件包?\n 答案:可以使用`sudo pacman -S <软件包名>`来安装软件包(适用于Archlinux系)。\n\n## OutputFormat :\n1. 接收用户输入的问题。\n2. 提供简洁、精确的解决方案。\n\n## Initialization : \n作为 QQ群Linux助手, 拥有 熟悉各种Linux发行版的使用和问题解决, 能够提供简洁明了的技术支持, 友好回复群友的提问, 避免回答政治相关内容, 每个问题只回答一次,确保回复简短、精确, 使用默认 中文 与用户对话,友好的欢迎用户。然后介绍自己,并提示用户输入。\n"
|
PROMPT = "## Role : \nQQ群Linux助手\n\n## Background : \n该角色是一位专门为QQ群提供Linux相关技术支持的助手,熟悉各种Linux发行版的使用和问题解决,旨在帮助群友解决他们在使用Linux过程中遇到的各种问题。\n\n## Preferences : \n该角色偏好简洁明了的回答风格,注重解决方案的精确性和有效性。喜欢友好和尊重的互动方式,致力于营造一个技术交流的和谐环境。\n\n## Profile :\n- language: 中文\n- description: 为QQ群友提供Linux相关问题的简洁、精确解决方案\n\n## Goals :\n1. 回答Linux相关问题\n2. 提供简短、精确的解决方案\n3. 鼓励群友提出问题并尊重他们\n\n## Constrains :\n1. 避免回答政治相关内容\n2. 每个问题只回答一次,确保回复简短、精确\n\n## Skills :\n1. 熟悉各种Linux发行版的使用和问题解决\n2. 能够提供简洁明了的技术支持\n3. 友好回复群友的提问\n\n## Examples :\n1. 问题:如何查看当前Linux系统的内核版本?\n 答案:可以使用命令`uname -r`来查看当前的内核版本。\n \n2. 问题:如何安装一个软件包?\n 答案:可以使用`sudo pacman -S <软件包名>`来安装软件包(适用于Archlinux系)。\n\n## OutputFormat :\n1. 接收用户输入的问题。\n2. 提供简洁、精确的解决方案。\n\n## Initialization : \n作为 QQ群Linux助手, 拥有 熟悉各种Linux发行版的使用和问题解决, 能够提供简洁明了的技术支持, 友好回复群友的提问, 避免回答政治相关内容, 每个问题只回答一次,确保回复简短、精确, 使用默认 中文 与用户对话,友好的欢迎用户。然后介绍自己,并提示用户输入。\n"
|
||||||
|
Context = 5
|
||||||
|
|
||||||
|
[REDIS]
|
||||||
|
REDIS_URL = "localhost:6379"
|
||||||
|
REDIS_PASSWORD = ""
|
||||||
|
REDIS_DB = 0
|
4
go.mod
4
go.mod
|
@ -13,9 +13,11 @@ require (
|
||||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
@ -56,7 +58,9 @@ require (
|
||||||
github.com/PuerkitoBio/goquery v1.9.2
|
github.com/PuerkitoBio/goquery v1.9.2
|
||||||
github.com/andybalholm/brotli v1.1.0
|
github.com/andybalholm/brotli v1.1.0
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/imroc/req/v3 v3.44.0
|
github.com/imroc/req/v3 v3.44.0
|
||||||
|
github.com/redis/go-redis/v9 v9.6.1
|
||||||
github.com/sashabaranov/go-openai v1.29.0
|
github.com/sashabaranov/go-openai v1.29.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
golang.org/x/net v0.26.0 // indirect
|
golang.org/x/net v0.26.0 // indirect
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -10,6 +10,10 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
@ -20,6 +24,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
@ -36,6 +42,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
@ -90,6 +98,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
||||||
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
||||||
|
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||||
|
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
|
||||||
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
||||||
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
|
139
test/openai.go
Normal file
139
test/openai.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go-bot/tools"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/sashabaranov/go-openai"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ctx = context.Background()
|
||||||
|
|
||||||
|
// 初始化 Redis 客户端
|
||||||
|
func initRedis() *redis.Client {
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: "localhost:6379", // Redis 地址
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
})
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查请求频率,10秒内只能请求一次
|
||||||
|
func checkRequestFrequency(rdb *redis.Client, groupID string, qqID string) bool {
|
||||||
|
key := fmt.Sprintf("last_request:%s:%s", groupID, qqID)
|
||||||
|
lastRequest, err := rdb.Get(ctx, key).Int64()
|
||||||
|
println("key:", key)
|
||||||
|
now := time.Now().Unix()
|
||||||
|
println("now:", now)
|
||||||
|
println("lastRequest:", lastRequest)
|
||||||
|
|
||||||
|
if err == redis.Nil {
|
||||||
|
// 键不存在,这是第一次请求
|
||||||
|
rdb.Set(ctx, key, now, 10*time.Second)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} else if err != nil {
|
||||||
|
fmt.Println("获取上次请求时间时出错:", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
currentRequest := getContext(rdb, key)
|
||||||
|
fmt.Println("currentRequest:", currentRequest)
|
||||||
|
if now-lastRequest < 10 {
|
||||||
|
fmt.Printf("请求过于频繁。距离上次请求还有 %d 秒。\n", 10-(now-lastRequest))
|
||||||
|
return false // 频率超限,拒绝请求
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新最后请求时间,并设置 10 秒的过期时间
|
||||||
|
rdb.Set(ctx, key, now, 10*time.Second)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加对话到上下文,超过5条则删除所有上下文
|
||||||
|
func addToContext(rdb *redis.Client, groupID string, qqID string, message string) {
|
||||||
|
key := fmt.Sprintf("context:%s:%s", groupID, qqID)
|
||||||
|
|
||||||
|
// 如果上下文超过5条,删除所有上下文
|
||||||
|
listLength := rdb.LLen(ctx, key).Val()
|
||||||
|
if listLength > 5 {
|
||||||
|
rdb.Del(ctx, key) // 删除该用户的所有上下文
|
||||||
|
}
|
||||||
|
rdb.RPush(ctx, key, message) // 添加新消息到列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前上下文
|
||||||
|
func getContext(rdb *redis.Client, key string) []string {
|
||||||
|
// key :=
|
||||||
|
context, err := rdb.LRange(ctx, key, 0, -1).Result()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error fetching context:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// ctx := context.Background()
|
||||||
|
groupID := "12345"
|
||||||
|
qqID := "67890"
|
||||||
|
rdb := initRedis()
|
||||||
|
|
||||||
|
OPENAI_API_KEY, OPENAI_BaseURL, MODEL := tools.GetOAIConfig()
|
||||||
|
oaiconfig := openai.DefaultConfig(OPENAI_API_KEY)
|
||||||
|
oaiconfig.BaseURL = OPENAI_BaseURL
|
||||||
|
client := openai.NewClientWithConfig(oaiconfig)
|
||||||
|
|
||||||
|
messages := make([]openai.ChatCompletionMessage, 0)
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
fmt.Println("Conversation")
|
||||||
|
fmt.Println("---------------------")
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Print("-> ")
|
||||||
|
// 检查请求频率
|
||||||
|
if !checkRequestFrequency(rdb, groupID, qqID) {
|
||||||
|
// fmt.Println("请求太频繁,请稍后再试。")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
text, _ := reader.ReadString('\n')
|
||||||
|
// convert CRLF to LF
|
||||||
|
text = strings.Replace(text, "\n", "", -1)
|
||||||
|
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleUser,
|
||||||
|
Content: text,
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := client.CreateChatCompletion(
|
||||||
|
ctx,
|
||||||
|
openai.ChatCompletionRequest{
|
||||||
|
Model: MODEL,
|
||||||
|
Messages: messages,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ChatCompletion error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
content := resp.Choices[0].Message.Content
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleAssistant,
|
||||||
|
Content: content,
|
||||||
|
})
|
||||||
|
// 添加新消息到上下文
|
||||||
|
addToContext(rdb, groupID, qqID, text)
|
||||||
|
|
||||||
|
fmt.Println(content)
|
||||||
|
fmt.Println("---------------------")
|
||||||
|
fmt.Println(getContext(rdb, "context:"+groupID+":"+qqID))
|
||||||
|
}
|
||||||
|
}
|
51
tools/getOAIConfig.go
Normal file
51
tools/getOAIConfig.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go-bot/config"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetOAIConfig() (string, string, string, string, int64) {
|
||||||
|
cfg := config.GetConfig()
|
||||||
|
// config.PrintConfig(cfg, "")
|
||||||
|
var OPENAI_API_KEY, OPENAI_BaseURL, MODEL, PROMPT string
|
||||||
|
var context int64
|
||||||
|
openai_cfg := cfg["OPENAI"].(map[string]interface{})
|
||||||
|
if openai_cfg["OPENAI_API_KEY"] != nil {
|
||||||
|
OPENAI_API_KEY = openai_cfg["OPENAI_API_KEY"].(string)
|
||||||
|
} else {
|
||||||
|
log.Println("OPENAI_API_KEY 未配置")
|
||||||
|
}
|
||||||
|
|
||||||
|
if openai_cfg["OPENAI_BaseURL"] != nil {
|
||||||
|
OPENAI_BaseURL = openai_cfg["OPENAI_BaseURL"].(string)
|
||||||
|
} else {
|
||||||
|
log.Println("OPENAI_BaseURL 未配置,使用openai默认配置")
|
||||||
|
OPENAI_BaseURL = "https://api.openai.com/v1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if openai_cfg["MODEL"] != nil {
|
||||||
|
MODEL = openai_cfg["MODEL"].(string)
|
||||||
|
} else {
|
||||||
|
log.Println("模型 未配置,使用默认 gpt-4o 模型")
|
||||||
|
MODEL = "gpt-4o"
|
||||||
|
}
|
||||||
|
if openai_cfg["PROMPT"] != nil {
|
||||||
|
PROMPT = openai_cfg["PROMPT"].(string)
|
||||||
|
} else {
|
||||||
|
log.Println("PROMPT 未配置,使用默认 PROMPT")
|
||||||
|
PROMPT = ""
|
||||||
|
}
|
||||||
|
if openai_cfg["Context"] != nil {
|
||||||
|
// var err error
|
||||||
|
context = openai_cfg["Context"].(int64)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println("Context 解析错误,使用默认 Context")
|
||||||
|
// context = 5
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
log.Println("Context 未配置,使用默认 Context")
|
||||||
|
context = 5
|
||||||
|
}
|
||||||
|
return OPENAI_API_KEY, OPENAI_BaseURL, MODEL, PROMPT, int64(context)
|
||||||
|
}
|
103
tools/redisClient.go
Normal file
103
tools/redisClient.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go-bot/config"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rdb *redis.Client
|
||||||
|
once sync.Once
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitRedis 初始化 Redis 客户端(懒加载模式,确保只初始化一次)
|
||||||
|
func InitRedis() *redis.Client {
|
||||||
|
// 使用 sync.Once 确保客户端只初始化一次
|
||||||
|
once.Do(func() {
|
||||||
|
redisConfig := config.GetConfig()["REDIS"].(map[string]interface{})
|
||||||
|
rdb = redis.NewClient(&redis.Options{
|
||||||
|
Addr: redisConfig["REDIS_URL"].(string), // Redis 地址
|
||||||
|
Password: redisConfig["REDIS_PASSWORD"].(string), // Redis 密码,如果有设置
|
||||||
|
DB: int(redisConfig["REDIS_DB"].(int64)), // 使用的 Redis 数据库
|
||||||
|
PoolSize: 20, // 连接池大小
|
||||||
|
MinIdleConns: 10, // 最小空闲连接数
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试连接是否成功
|
||||||
|
_, err := rdb.Ping(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Redis 连接失败:", err)
|
||||||
|
} else {
|
||||||
|
log.Println("Redis 连接成功")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRedisClient 获取 Redis 客户端实例
|
||||||
|
func GetRedisClient() *redis.Client {
|
||||||
|
if rdb == nil {
|
||||||
|
return InitRedis()
|
||||||
|
}
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加对话到上下文,超过5条则删除所有上下文
|
||||||
|
func AddToContext(key string, message string, contextlenth int64) {
|
||||||
|
// key := fmt.Sprintf("context:%s:%s:%s", worker, groupID, qqID)
|
||||||
|
|
||||||
|
// 如果上下文超过5条,删除所有上下文
|
||||||
|
listLength := GetListLength(key)
|
||||||
|
log.Println("listLength:", listLength)
|
||||||
|
if listLength >= contextlenth {
|
||||||
|
rdb.Del(ctx, key) // 删除该用户的所有上下文
|
||||||
|
}
|
||||||
|
rdb.RPush(ctx, key, message) // 添加新消息到列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// getListLength 获取列表长度
|
||||||
|
func GetListLength(key string) int64 {
|
||||||
|
return rdb.LLen(ctx, key).Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetListValue(key string, index int64) (string, error) {
|
||||||
|
return rdb.LIndex(ctx, key, index).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除列表最后一个元素
|
||||||
|
func RemoveLastValueFromList(key string) (string, error) {
|
||||||
|
// RPOP 命令会移除并返回列表的最后一个元素
|
||||||
|
value, err := rdb.RPop(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
if err == redis.Nil {
|
||||||
|
return "", fmt.Errorf("列表为空或不存在")
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("删除最后一个值时出错: %v", err)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValue 设置键值对
|
||||||
|
func SetValue(key string, value string) error {
|
||||||
|
return rdb.Set(ctx, key, value, 0).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue 获取键对应的值
|
||||||
|
func GetValue(key string) (string, error) {
|
||||||
|
return rdb.Get(ctx, key).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckKeyExists 检查键是否存在
|
||||||
|
func CheckKeyExists(key string) (bool, error) {
|
||||||
|
val, err := rdb.Exists(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return val > 0, nil
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
package utils
|
|
101
workers/ai.go
101
workers/ai.go
|
@ -4,11 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go-bot/tools"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"log"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -38,7 +39,7 @@ func (a *AI) GetMsg() string {
|
||||||
return "不问问题你说个屁!"
|
return "不问问题你说个屁!"
|
||||||
}
|
}
|
||||||
|
|
||||||
OPENAI_API_KEY, OPENAI_BaseURL, MODEL := getConfig()
|
OPENAI_API_KEY, OPENAI_BaseURL, MODEL, PROMPT, CONTEXT := tools.GetOAIConfig()
|
||||||
if OPENAI_API_KEY == "" {
|
if OPENAI_API_KEY == "" {
|
||||||
return "OPENAI_API_KEY 未配置"
|
return "OPENAI_API_KEY 未配置"
|
||||||
}
|
}
|
||||||
|
@ -48,34 +49,62 @@ func (a *AI) GetMsg() string {
|
||||||
client := openai.NewClientWithConfig(oaiconfig)
|
client := openai.NewClientWithConfig(oaiconfig)
|
||||||
msg := fmt.Sprintf("[CQ:at,qq=%s] ", a.UID)
|
msg := fmt.Sprintf("[CQ:at,qq=%s] ", a.UID)
|
||||||
if strings.ToLower(a.Parms[1]) != "models" {
|
if strings.ToLower(a.Parms[1]) != "models" {
|
||||||
// OPENAI_BaseURL = OPENAI_BaseURL + "/chat/completions"
|
|
||||||
PROMPT, ok := cfg["PROMPT"].(string)
|
|
||||||
if !ok {
|
|
||||||
log.Println("PROMPT 未配置")
|
|
||||||
PROMPT = ""
|
|
||||||
}
|
|
||||||
// var requestBody map[string]interface{}
|
|
||||||
// 如果非回复消息
|
// 如果非回复消息
|
||||||
if !strings.HasPrefix(a.Parms[len(a.Parms)-1], "[CQ:reply,id=") {
|
if !strings.HasPrefix(a.Parms[len(a.Parms)-1], "[CQ:reply,id=") {
|
||||||
|
messages := make([]openai.ChatCompletionMessage, 0)
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleSystem,
|
||||||
|
Content: PROMPT,
|
||||||
|
})
|
||||||
|
// println("messages0:", messages)
|
||||||
|
var key string
|
||||||
|
redisClient := tools.GetRedisClient()
|
||||||
|
// 如果redisClient不为空,则获取上下文
|
||||||
|
if redisClient != nil {
|
||||||
|
|
||||||
|
key = fmt.Sprintf("context:%s:%s:%s", "ai", a.GID, a.UID)
|
||||||
|
// 获取上下文
|
||||||
|
|
||||||
|
tools.AddToContext(key, a.RawMsg[strings.Index(a.RawMsg, " ")+1:], CONTEXT)
|
||||||
|
// }
|
||||||
|
// println("RawMsg:", a.RawMsg[strings.Index(a.RawMsg, " ")+1:])
|
||||||
|
length := tools.GetListLength(key)
|
||||||
|
for i := 1; i < int(length); i++ {
|
||||||
|
message, err := tools.GetListValue(key, int64(i))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("获取上下文失败:", err)
|
||||||
|
return "获取上下文失败"
|
||||||
|
}
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleUser,
|
||||||
|
Content: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleUser,
|
||||||
|
Content: a.RawMsg[strings.Index(a.RawMsg, " ")+1:],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// println("messages:", messages)
|
||||||
|
// for i, msg := range messages {
|
||||||
|
// fmt.Printf("消息 %d:\n", i+1)
|
||||||
|
// fmt.Printf(" 角色: %s\n", msg.Role)
|
||||||
|
// fmt.Printf(" 内容: %s\n", msg.Content)
|
||||||
|
// fmt.Println()
|
||||||
|
// }
|
||||||
resp, err := client.CreateChatCompletion(
|
resp, err := client.CreateChatCompletion(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
openai.ChatCompletionRequest{
|
openai.ChatCompletionRequest{
|
||||||
Model: MODEL,
|
Model: MODEL,
|
||||||
Stream: false,
|
Stream: false,
|
||||||
Messages: []openai.ChatCompletionMessage{
|
Messages: messages,
|
||||||
{
|
|
||||||
Role: "system",
|
|
||||||
Content: PROMPT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Role: "user",
|
|
||||||
Content: a.RawMsg[strings.Index(a.RawMsg, " ")+1:],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("ChatCompletion error: ", err)
|
log.Println("ChatCompletion error: ", err)
|
||||||
|
tools.RemoveLastValueFromList(key)
|
||||||
return "请求失败"
|
return "请求失败"
|
||||||
}
|
}
|
||||||
// println(resp.Choices[0].Message.Content)
|
// println(resp.Choices[0].Message.Content)
|
||||||
|
@ -170,7 +199,7 @@ func (a *AI) GetMsg() string {
|
||||||
var modelList string
|
var modelList string
|
||||||
for _, model := range models.Models {
|
for _, model := range models.Models {
|
||||||
if MODEL == model.ID {
|
if MODEL == model.ID {
|
||||||
model.ID = model.ID + "(当前使用)"
|
model.ID = model.ID + "\t(当前使用)"
|
||||||
}
|
}
|
||||||
modelList += model.ID + "\n"
|
modelList += model.ID + "\n"
|
||||||
}
|
}
|
||||||
|
@ -234,32 +263,6 @@ func stripMarkdown(text string) string {
|
||||||
return strings.TrimSpace(text)
|
return strings.TrimSpace(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfig() (string, string, string) {
|
|
||||||
var OPENAI_API_KEY, OPENAI_BaseURL, MODEL string
|
|
||||||
|
|
||||||
if cfg["OPENAI_API_KEY"] != nil {
|
|
||||||
OPENAI_API_KEY = cfg["OPENAI_API_KEY"].(string)
|
|
||||||
} else {
|
|
||||||
log.Println("OPENAI_API_KEY 未配置")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg["OPENAI_BaseURL"] != nil {
|
|
||||||
OPENAI_BaseURL = cfg["OPENAI_BaseURL"].(string)
|
|
||||||
} else {
|
|
||||||
log.Println("OPENAI_BaseURL 未配置,使用openai默认配置")
|
|
||||||
OPENAI_BaseURL = "https://api.openai.com/v1"
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg["MODEL"] != nil {
|
|
||||||
MODEL = cfg["MODEL"].(string)
|
|
||||||
} else {
|
|
||||||
log.Println("模型 未配置,使用默认 gpt-4o 模型")
|
|
||||||
MODEL = "gpt-4o"
|
|
||||||
}
|
|
||||||
|
|
||||||
return OPENAI_API_KEY, OPENAI_BaseURL, MODEL
|
|
||||||
}
|
|
||||||
|
|
||||||
func Image2Base64(path string, picUrl string) string {
|
func Image2Base64(path string, picUrl string) string {
|
||||||
// 尝试从文件路径读取图片
|
// 尝试从文件路径读取图片
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
|
Loading…
Reference in a new issue