feat(aichat): agent 支持识图

This commit is contained in:
源文雨 2025-09-25 00:20:07 +08:00
parent 7640f0cfac
commit 3c41c18d27
6 changed files with 381 additions and 269 deletions

View File

@ -1633,12 +1633,12 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 设置AI聊天触发概率10
- [x] 设置AI聊天温度80
- [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]
- [x] 设置AI聊天(识图)接口类型[OpenAI|OLLaMA|GenAI]
- [x] 设置AI聊天(不)使用Agent模式
- [x] 设置AI聊天(不)支持系统提示词
- [x] 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions
- [x] 设置AI聊天密钥xxx
- [x] 设置AI聊天模型名Qwen/Qwen3-8B
- [x] 设置AI聊天(识图)接口地址https://api.siliconflow.cn/v1/chat/completions
- [x] 设置AI聊天(识图)密钥xxx
- [x] 设置AI聊天(识图)模型名Qwen/Qwen3-8B
- [x] 查看AI聊天系统提示词
- [x] 重置AI聊天系统提示词
- [x] 设置AI聊天系统提示词xxx

6
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/FloatTech/sqlite v1.7.1
github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562
github.com/FloatTech/zbpctrl v1.7.0
github.com/FloatTech/zbputils v1.7.2-0.20250922144137-bf2b9bb6a8d9
github.com/FloatTech/zbputils v1.7.2-0.20250923162319-dcba1aa52b1d
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
github.com/Tnze/go-mc v1.20.2
@ -22,9 +22,9 @@ require (
github.com/disintegration/imaging v1.6.2
github.com/fumiama/ahsai v0.1.0
github.com/fumiama/cron v1.3.0
github.com/fumiama/deepinfra v0.0.0-20250923161832-8757564fe76c
github.com/fumiama/deepinfra v0.0.0-20250924134153-348633b5c6fb
github.com/fumiama/go-base16384 v1.7.0
github.com/fumiama/go-onebot-agent v0.0.0-20250922152742-c40bb3512d63
github.com/fumiama/go-onebot-agent v0.0.0-20250924153727-0cdc929590cf
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565

16
go.sum
View File

@ -17,8 +17,8 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ
github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo=
github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE=
github.com/FloatTech/zbputils v1.7.2-0.20250922144137-bf2b9bb6a8d9 h1:iR36inettls14aMOADNQ7PHNlGvgyDRRYp2dBgZCp8A=
github.com/FloatTech/zbputils v1.7.2-0.20250922144137-bf2b9bb6a8d9/go.mod h1:L1Rvdf6JUXGRIdKaXVtBWa0iW481zccCjYdYeDSaMXs=
github.com/FloatTech/zbputils v1.7.2-0.20250923162319-dcba1aa52b1d h1:B7pvVpZSSfCjSNLONq0dWTm5JahCdE2017hzDP/DmzY=
github.com/FloatTech/zbputils v1.7.2-0.20250923162319-dcba1aa52b1d/go.mod h1:CpwrdL0xCiUfN1819TDKk470WGMYQZZanLjAYVfHCEM=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
@ -59,14 +59,12 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4=
github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk=
github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo=
github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY=
github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 h1:6PglFpNVm3DalGyRldacW2/v4jGWwn3v3q1tr2PhbVQ=
github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY=
github.com/fumiama/deepinfra v0.0.0-20250923161832-8757564fe76c h1:ypY8ynfghPMamNfG1qS+XJ52BSBrA1HVR1c7aL5dUkI=
github.com/fumiama/deepinfra v0.0.0-20250923161832-8757564fe76c/go.mod h1:HgRu6n9XgeyNki7wHHlTa75ORiv3UlYZafqOAoazhOI=
github.com/fumiama/deepinfra v0.0.0-20250924134153-348633b5c6fb h1:d0gl31nyOER4O3K0wpepont8flrDtzZsKACeWGxARX0=
github.com/fumiama/deepinfra v0.0.0-20250924134153-348633b5c6fb/go.mod h1:HgRu6n9XgeyNki7wHHlTa75ORiv3UlYZafqOAoazhOI=
github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-onebot-agent v0.0.0-20250922152742-c40bb3512d63 h1:ZdPMPIgZMH4HV4A/JIBb8G7UpLM4iUHWQ8qGjKnKiVI=
github.com/fumiama/go-onebot-agent v0.0.0-20250922152742-c40bb3512d63/go.mod h1:wVMgFWkR3GpipL05FkokvrV/jWFIgoEWN1jzUGa0bWg=
github.com/fumiama/go-onebot-agent v0.0.0-20250924153727-0cdc929590cf h1:yFkCBQlFBCRQLru/ANWvvWDTRpT3qLhBP94aAJnlmaY=
github.com/fumiama/go-onebot-agent v0.0.0-20250924153727-0cdc929590cf/go.mod h1:FIhZxVeFAs201W06EgXxx/6b/l/ETSmu2sQOj10kjdk=
github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs=
github.com/fumiama/go-registry v0.2.7/go.mod h1:m+wp5fF8dYgVoFkBPZl+vlK90loymaJE0JCtocVQLEs=
github.com/fumiama/go-simple-protobuf v0.2.0 h1:ACyN1MAlu7pDR3EszWgzUeNP+IRsSHwH6V9JCJA5R5o=
@ -75,8 +73,6 @@ github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoG
github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk=
github.com/fumiama/gotracemoe v0.0.3 h1:iI5EbE9A3UUbfukG6+/soYPjp1S31eCNYf4tw7s6/Jc=
github.com/fumiama/gotracemoe v0.0.3/go.mod h1:tyqahdUzHf0bQIAVY/GYmDWvYYe5ik1ZbhnGYh+zl40=
github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak=
github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4=
github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI=
github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E=
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 h1:sQuR2+N5HurnvsZhiKdEg+Ig354TaqgCQRxd/0KgIOQ=

View File

@ -1,6 +1,7 @@
package aichat
import (
"errors"
"fmt"
"strconv"
"strings"
@ -18,19 +19,89 @@ var (
cfg = newconfig()
)
var (
apitypes = map[string]uint8{
"OpenAI": 0,
"OLLaMA": 1,
"GenAI": 2,
}
apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"}
)
type ModelType int
func newModelType(typ string) (ModelType, error) {
t, ok := apitypes[typ]
if !ok {
return 0, errors.New("未知类型 " + typ)
}
return ModelType(t), nil
}
func (mt ModelType) String() string {
return apilist[mt]
}
func (mt ModelType) protocol(modn string, temp float32, topp float32, maxn uint) (mod model.Protocol, err error) {
switch cfg.Type {
case 0:
mod = model.NewOpenAI(
modn, cfg.Separator,
temp, topp, maxn,
)
case 1:
mod = model.NewOLLaMA(
modn, cfg.Separator,
temp, topp, maxn,
)
case 2:
mod = model.NewGenAI(
modn,
temp, topp, maxn,
)
default:
err = errors.New("unsupported model type " + strconv.Itoa(int(cfg.Type)))
}
return
}
type ModelBool bool
func (mb ModelBool) String() string {
if mb {
return "是"
}
return "否"
}
type ModelKey string
func (mk ModelKey) String() string {
if len(mk) == 0 {
return "未设置"
}
if len(mk) <= 4 {
return "****"
}
key := string(mk)
return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:]
}
type config struct {
ModelName string
Type int
MaxN uint
TopP float32
SystemP string
API string
Key string
Separator string
NoReplyAT bool
NoSystemP bool
NoRecord bool
NoAgent bool
ModelName string
ImageModelName string
Type ModelType
ImageType ModelType
MaxN uint
TopP float32
SystemP string
API string
ImageAPI string
Key ModelKey
ImageKey ModelKey
Separator string
NoReplyAT ModelBool
NoSystemP ModelBool
}
func newconfig() config {
@ -41,10 +112,47 @@ func newconfig() config {
}
}
func (c *config) String() string {
topp, maxn := c.mparams()
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("• 模型名:%s\n", c.ModelName))
sb.WriteString(fmt.Sprintf("• 图像模型名:%s\n", c.ImageModelName))
sb.WriteString(fmt.Sprintf("• 接口类型:%v\n", c.Type))
sb.WriteString(fmt.Sprintf("• 图像接口类型:%v\n", c.ImageType))
sb.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn))
sb.WriteString(fmt.Sprintf("• TopP%.1f\n", topp))
sb.WriteString(fmt.Sprintf("• 系统提示词:%s\n", c.SystemP))
sb.WriteString(fmt.Sprintf("• 接口地址:%s\n", c.API))
sb.WriteString(fmt.Sprintf("• 图像接口地址:%s\n", c.ImageAPI))
sb.WriteString(fmt.Sprintf("• 密钥:%v\n", c.Key))
sb.WriteString(fmt.Sprintf("• 图像密钥:%v\n", c.ImageKey))
sb.WriteString(fmt.Sprintf("• 分隔符:%s\n", c.Separator))
sb.WriteString(fmt.Sprintf("• 响应@%v\n", !c.NoReplyAT))
sb.WriteString(fmt.Sprintf("• 支持系统提示词:%v\n", !c.NoSystemP))
return sb.String()
}
func (c *config) isvalid() bool {
return c.ModelName != "" && c.API != "" && c.Key != ""
}
// 获取全局模型参数TopP和最大长度
func (c *config) mparams() (topp float32, maxn uint) {
// 处理TopP参数
topp = c.TopP
if topp == 0 {
topp = 0.9
}
// 处理最大长度参数
maxn = c.MaxN
if maxn == 0 {
maxn = 4096
}
return topp, maxn
}
func ensureconfig(ctx *zero.Ctx) bool {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
@ -62,7 +170,7 @@ func ensureconfig(ctx *zero.Ctx) bool {
return true
}
func newextrasetstr(ptr *string) func(ctx *zero.Ctx) {
func newextrasetstr[T ~string](ptr *T) func(ctx *zero.Ctx) {
return func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
@ -74,7 +182,7 @@ func newextrasetstr(ptr *string) func(ctx *zero.Ctx) {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
*ptr = args
*ptr = T(args)
err := c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
@ -84,7 +192,7 @@ func newextrasetstr(ptr *string) func(ctx *zero.Ctx) {
}
}
func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) {
func newextrasetbool[T ~bool](ptr *T) func(ctx *zero.Ctx) {
return func(ctx *zero.Ctx) {
args := ctx.State["regex_matched"].([]string)
isno := args[1] == "不"
@ -93,7 +201,7 @@ func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
*ptr = isno
*ptr = T(isno)
err := c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
@ -156,44 +264,3 @@ func newextrasetfloat32(ptr *float32) func(ctx *zero.Ctx) {
ctx.SendChain(message.Text("成功"))
}
}
func printConfig(rate int64, temperature int64, cfg config) string {
maxn := cfg.MaxN
if maxn == 0 {
maxn = 4096
}
topp := cfg.TopP
if topp == 0 {
topp = 0.9
}
var builder strings.Builder
builder.WriteString("当前AI聊天配置\n")
builder.WriteString(fmt.Sprintf("• 模型名:%s\n", cfg.ModelName))
builder.WriteString(fmt.Sprintf("• 接口类型:%d(%s)\n", cfg.Type, apilist[cfg.Type]))
builder.WriteString(fmt.Sprintf("• 触发概率:%d%%\n", rate))
builder.WriteString(fmt.Sprintf("• 温度:%.2f\n", float32(temperature)/100))
builder.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn))
builder.WriteString(fmt.Sprintf("• TopP%.1f\n", topp))
builder.WriteString(fmt.Sprintf("• 系统提示词:%s\n", cfg.SystemP))
builder.WriteString(fmt.Sprintf("• 接口地址:%s\n", cfg.API))
builder.WriteString(fmt.Sprintf("• 密钥:%s\n", maskKey(cfg.Key)))
builder.WriteString(fmt.Sprintf("• 分隔符:%s\n", cfg.Separator))
builder.WriteString(fmt.Sprintf("• 响应@%s\n", yesNo(!cfg.NoReplyAT)))
builder.WriteString(fmt.Sprintf("• 支持系统提示词:%s\n", yesNo(!cfg.NoSystemP)))
builder.WriteString(fmt.Sprintf("• 以AI语音输出%s\n", yesNo(!cfg.NoRecord)))
return builder.String()
}
func maskKey(key string) string {
if len(key) <= 4 {
return "****"
}
return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:]
}
func yesNo(b bool) string {
if b {
return "是"
}
return "否"
}

View File

@ -3,7 +3,6 @@ package aichat
import (
"encoding/json"
"errors"
"math/rand"
"reflect"
"strconv"
@ -35,12 +34,12 @@ var (
Brief: "OpenAI聊天",
Help: "- 设置AI聊天触发概率10\n" +
"- 设置AI聊天温度80\n" +
"- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" +
"- 设置AI聊天(识图)接口类型[OpenAI|OLLaMA|GenAI]\n" +
"- 设置AI聊天(不)使用Agent模式\n" +
"- 设置AI聊天(不)支持系统提示词\n" +
"- 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
"- 设置AI聊天密钥xxx\n" +
"- 设置AI聊天模型名Qwen/Qwen3-8B\n" +
"- 设置AI聊天(识图)接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
"- 设置AI聊天(识图)密钥xxx\n" +
"- 设置AI聊天(识图)模型名Qwen/Qwen3-8B\n" +
"- 查看AI聊天系统提示词\n" +
"- 重置AI聊天系统提示词\n" +
"- 设置AI聊天系统提示词xxx\n" +
@ -59,57 +58,24 @@ var (
)
var (
apitypes = map[string]uint8{
"OpenAI": 0,
"OLLaMA": 1,
"GenAI": 2,
}
apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"}
limit = ctxext.NewLimiterManager(time.Second*30, 1)
limit = ctxext.NewLimiterManager(time.Second*30, 1)
)
// getModelParams 获取模型参数:温度(float32(temp)/100)、TopP和最大长度
func getModelParams(temp int64) (temperature float32, topp float32, maxn uint) {
// 处理温度参数
if temp <= 0 {
temp = 70 // default setting
}
if temp > 100 {
temp = 100
}
temperature = float32(temp) / 100
// 处理TopP参数
topp = cfg.TopP
if topp == 0 {
topp = 0.9
}
// 处理最大长度参数
maxn = cfg.MaxN
if maxn == 0 {
maxn = 4096
}
return temperature, topp, maxn
}
func init() {
en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool {
return ctx.ExtractPlainText() != "" &&
(!cfg.NoReplyAT || (cfg.NoReplyAT && !ctx.Event.IsToMe))
(bool(!cfg.NoReplyAT) || (bool(cfg.NoReplyAT) && !ctx.Event.IsToMe))
}).SetBlock(false).Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
stor, err := newstorage(ctx, gid)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
}
rate := c.GetData(gid)
temp := (rate >> 8) & 0xff
rate &= 0xff
rate := stor.rate()
if !ctx.Event.IsToMe && rand.Intn(100) >= int(rate) {
return
}
@ -120,33 +86,17 @@ func init() {
logrus.Warnln("ERROR: get extra err: empty key")
return
}
temperature := stor.temp()
topp, maxn := cfg.mparams()
temperature, topp, maxn := getModelParams(temp)
x := deepinfra.NewAPI(cfg.API, cfg.Key)
var mod model.Protocol
switch cfg.Type {
case 0:
mod = model.NewOpenAI(
cfg.ModelName, cfg.Separator,
temperature, topp, maxn,
)
case 1:
mod = model.NewOLLaMA(
cfg.ModelName, cfg.Separator,
temperature, topp, maxn,
)
case 2:
mod = model.NewGenAI(
cfg.ModelName,
temperature, topp, maxn,
)
default:
logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
x := deepinfra.NewAPI(cfg.API, string(cfg.Key))
mod, err := cfg.Type.protocol(cfg.ModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
}
if !cfg.NoAgent {
if !stor.noagent() {
role := goba.PermRoleUser
if zero.AdminPermission(ctx) {
role = goba.PermRoleAdmin
@ -154,7 +104,16 @@ func init() {
role = goba.PermRoleOwner
}
}
reqs, err := chat.AgentOf(ctx.Event.SelfID).GetAction(x, mod, gid, role, false)
ag := chat.AgentOf(ctx.Event.SelfID)
if cfg.ImageAPI != "" && !ag.CanViewImage() {
mod, err := cfg.ImageType.protocol(cfg.ImageModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
}
ag.SetViewImageAPI(deepinfra.NewAPI(cfg.ImageAPI, string(cfg.ImageKey)), mod)
}
reqs, err := ag.GetAction(x, mod, gid, role, false)
if err != nil {
logrus.Warnln("[aichat] agent err:", err, reqs)
return
@ -183,7 +142,7 @@ func init() {
return
}
data, err := x.Request(chat.GetChatContext(mod, gid, cfg.SystemP, cfg.NoSystemP))
data, err := x.Request(chat.GetChatContext(mod, gid, cfg.SystemP, bool(cfg.NoSystemP)))
if err != nil {
logrus.Warnln("[aichat] post err:", err)
return
@ -206,7 +165,7 @@ func init() {
logrus.Infoln("[aichat] 回复内容:", t)
recCfg := airecord.GetConfig()
record := ""
if !cfg.NoRecord {
if !stor.norecord() {
record = ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, t)
}
if record != "" {
@ -222,72 +181,8 @@ func init() {
}
}
})
en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
r, err := strconv.Atoi(args)
if err != nil {
ctx.SendChain(message.Text("ERROR: parse rate err: ", err))
return
}
if r > 100 {
r = 100
} else if r < 0 {
r = 0
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
val := c.GetData(gid) & (^0xff)
err = c.SetData(gid, val|int64(r&0xff))
if err != nil {
ctx.SendChain(message.Text("ERROR: set data err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
r, err := strconv.Atoi(args)
if err != nil {
ctx.SendChain(message.Text("ERROR: parse rate err: ", err))
return
}
if r > 100 {
r = 100
} else if r < 0 {
r = 0
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
val := c.GetData(gid) & (^0xff00)
err = c.SetData(gid, val|(int64(r&0xff)<<8))
if err != nil {
ctx.SendChain(message.Text("ERROR: set data err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).Handle(newstoragebitmap(bitmaprate, 0, 100))
en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).Handle(newstoragebitmap(bitmaptemp, 0, 100))
en.OnPrefix("设置AI聊天接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
@ -299,13 +194,37 @@ func init() {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
typ, ok := apitypes[args]
if !ok {
ctx.SendChain(message.Text("ERROR: 未知类型 ", args))
typ, err := newModelType(args)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
cfg.Type = int(typ)
err := c.SetExtra(&cfg)
cfg.Type = typ
err = c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天识图接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
typ, err := newModelType(args)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
cfg.ImageType = typ
err = c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
@ -314,10 +233,16 @@ func init() {
})
en.OnPrefix("设置AI聊天接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.API))
en.OnPrefix("设置AI聊天识图接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.ImageAPI))
en.OnPrefix("设置AI聊天密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.Key))
en.OnPrefix("设置AI聊天识图密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.ImageKey))
en.OnPrefix("设置AI聊天模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.ModelName))
en.OnPrefix("设置AI聊天识图模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.ImageModelName))
en.OnPrefix("设置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.SystemP))
en.OnFullMatch("查看AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
@ -343,31 +268,32 @@ func init() {
Handle(newextrasetbool(&cfg.NoReplyAT))
en.OnRegex("^设置AI聊天(不)?支持系统提示词$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetbool(&cfg.NoSystemP))
en.OnRegex("^设置AI聊天(不)?使用Agent模式$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetbool(&cfg.NoAgent))
en.OnRegex("^设置AI聊天(不)?使用Agent模式$", ensureconfig, zero.SuperUserPermission).SetBlock(true).
Handle(newstoragebool(bitmapnagt))
en.OnPrefix("设置AI聊天最大长度", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetuint(&cfg.MaxN))
en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetfloat32(&cfg.TopP))
en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetbool(&cfg.NoRecord))
en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.AdminPermission).SetBlock(true).
Handle(newstoragebool(bitmapnrec))
en.OnFullMatch("查看AI聊天配置", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
gid := ctx.Event.GroupID
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
gid := ctx.Event.GroupID
rate := c.GetData(gid) & 0xff
temp := (c.GetData(gid) >> 8) & 0xff
if temp <= 0 {
temp = 70 // default setting
}
if temp > 100 {
temp = 100
}
ctx.SendChain(message.Text(printConfig(rate, temp, cfg)))
ctx.SendChain(
message.Text(
"【当前AI聊天本群配置】\n",
"• 触发概率:", stor.rate(), "\n",
"• 温度:", stor.temp(), "\n",
"• 以AI语音输出", ModelBool(!stor.norecord()), "\n",
"• 使用Agent", ModelBool(!stor.noagent()), "\n",
),
message.Text("【当前AI聊天全局配置】\n", &cfg),
)
})
en.OnFullMatch("重置AI聊天", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
chat.ResetChat()
@ -381,12 +307,6 @@ func init() {
if gid == 0 {
gid = -ctx.Event.UserID
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return
}
rate := c.GetData(gid)
temp := (rate >> 8) & 0xff
p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
if p > 1000 {
p = 1000
@ -421,8 +341,13 @@ func init() {
summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n" +
strings.Join(messages, "\n")
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 调用大模型API进行总结
summary, err := llmchat(summaryPrompt, temp)
summary, err := llmchat(summaryPrompt, stor.temp())
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
@ -469,12 +394,6 @@ func init() {
if gid == 0 {
gid = -ctx.Event.UserID
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return
}
rate := c.GetData(gid)
temp := (rate >> 8) & 0xff
text := ctx.MessageString()
var query string
@ -517,8 +436,13 @@ func init() {
return
}
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 调用大模型API进行聊天
reply, err := llmchat(query, temp)
reply, err := llmchat(query, stor.temp())
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
@ -549,30 +473,14 @@ func init() {
}
// llmchat 调用大模型API包装
func llmchat(prompt string, temp int64) (string, error) {
temperature, topp, maxn := getModelParams(temp) // 使用默认温度70
func llmchat(prompt string, temp float32) (string, error) {
topp, maxn := cfg.mparams()
x := deepinfra.NewAPI(cfg.API, cfg.Key)
var mod model.Protocol
switch cfg.Type {
case 0:
mod = model.NewOpenAI(
cfg.ModelName, cfg.Separator,
temperature, topp, maxn,
)
case 1:
mod = model.NewOLLaMA(
cfg.ModelName, cfg.Separator,
temperature, topp, maxn,
)
case 2:
mod = model.NewGenAI(
cfg.ModelName,
temperature, topp, maxn,
)
default:
logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
return "", errors.New("不支持的AI类型")
x := deepinfra.NewAPI(cfg.API, string(cfg.Key))
mod, err := cfg.Type.protocol(cfg.ModelName, temp, topp, maxn)
if err != nil {
return "", nil
}
data, err := x.Request(mod.User(model.NewContentText(prompt)))

141
plugin/aichat/storage.go Normal file
View File

@ -0,0 +1,141 @@
package aichat
import (
"errors"
"math/bits"
"strconv"
"strings"
ctrl "github.com/FloatTech/zbpctrl"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
bitmaprate = 0x0000ff
bitmaptemp = 0x00ff00
bitmapnagt = 0x010000
bitmapnrec = 0x020000
)
type storage int64
func newstorage(ctx *zero.Ctx, gid int64) (storage, error) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return 0, errors.New("找不到 manager")
}
x := c.GetData(gid)
return storage(x), nil
}
func (s storage) saveto(ctx *zero.Ctx, gid int64) error {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return errors.New("找不到 manager")
}
return c.SetData(int64(s), gid)
}
func (s storage) getbybmp(bmp int64) int64 {
sft := bits.TrailingZeros64(uint64(bmp))
return (int64(s) & bmp) >> int64(sft)
}
func (s *storage) setbybmp(x int64, bmp int64) {
if bmp == 0 {
panic("cannot use bmp == 0")
}
sft := bits.TrailingZeros64(uint64(bmp))
*s = storage((int64(*s) & (^bmp)) | ((x & bmp) << int64(sft)))
}
func (s storage) rate() uint8 {
return uint8(s.getbybmp(bitmaprate))
}
func (s storage) temp() float32 {
temp := s.getbybmp(bitmaptemp)
// 处理温度参数
if temp <= 0 {
temp = 70 // default setting
}
if temp > 100 {
temp = 100
}
return float32(temp) / 100
}
func (s storage) noagent() bool {
return s.getbybmp(bitmapnagt) != 0
}
func (s storage) norecord() bool {
return s.getbybmp(bitmapnrec) != 0
}
func newstoragebitmap(bmp int64, minv, maxv int64) func(ctx *zero.Ctx) {
return func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
r, err := strconv.ParseInt(args, 10, 64)
if err != nil {
ctx.SendChain(message.Text("ERROR: parse int64 err: ", err))
return
}
if r > maxv {
r = maxv
} else if r < minv {
r = minv
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
stor.setbybmp(int64(r), bmp)
err = stor.saveto(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: set data err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}
func newstoragebool(bmp int64) func(ctx *zero.Ctx) {
if bits.OnesCount64(uint64(bmp)) != 1 {
panic("bool bmp must be 1-bit-long")
}
return func(ctx *zero.Ctx) {
args := ctx.State["regex_matched"].([]string)
iszero := args[1] == "不"
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
v := 1
if iszero {
v = 0
}
stor.setbybmp(int64(v), bmp)
err = stor.saveto(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: set data err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}