From 49983ab451886d6e05aebdb99113695b607451b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:18:39 +0800 Subject: [PATCH 1/8] feat(aichat): add agent mode --- README.md | 2 ++ go.mod | 3 ++- go.sum | 6 ++++-- plugin/aichat/cfg.go | 1 + plugin/aichat/main.go | 24 +++++++++++++++++++++++- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2d49be9b..e8b26aec 100644 --- a/README.md +++ b/README.md @@ -1634,6 +1634,7 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置AI聊天触发概率10 - [x] 设置AI聊天温度80 - [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI] + - [x] 设置AI聊天(不)使用Agent模式 - [x] 设置AI聊天(不)支持系统提示词 - [x] 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions - [x] 设置AI聊天密钥xxx @@ -1649,6 +1650,7 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 查看AI聊天配置 - [x] 重置AI聊天 - [x] 群聊总结 [消息数目]|群聊总结 1000 + - [x] /gpt [内容](使用大模型聊天)
diff --git a/go.mod b/go.mod index 5c8104b7..50896d9d 100644 --- a/go.mod +++ b/go.mod @@ -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.20250921072315-465a1f1d1fbb + github.com/FloatTech/zbputils v1.7.2-0.20250921143810-bb64d87a5416 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 @@ -24,6 +24,7 @@ require ( github.com/fumiama/cron v1.3.0 github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 github.com/fumiama/go-base16384 v1.7.0 + github.com/fumiama/go-onebot-agent v0.0.0-20250921143402-d55156efbd5b github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 diff --git a/go.sum b/go.sum index 700c1ac2..71b504f1 100644 --- a/go.sum +++ b/go.sum @@ -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.20250921072315-465a1f1d1fbb h1:/3nfw+rPFvTNsEdj+QmslUL67QZeeO7Xkx2bxOnLtLo= -github.com/FloatTech/zbputils v1.7.2-0.20250921072315-465a1f1d1fbb/go.mod h1:mkH3Tii83bsO7+gj5s3wLUWEIYY2+5G948aW4TtUxbg= +github.com/FloatTech/zbputils v1.7.2-0.20250921143810-bb64d87a5416 h1:aBhn9/QltOId6bbjkSXYP+p4vZLB1RfjYUK48XGwjc4= +github.com/FloatTech/zbputils v1.7.2-0.20250921143810-bb64d87a5416/go.mod h1:Mb9JQb4MpU0GHRQv9PS+Qq+F0UBRVPrjU+ZlAjhPccQ= 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= @@ -63,6 +63,8 @@ github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 h1:6PglFpNVm3Dal github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= 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-20250921143402-d55156efbd5b h1:LToVa4PSTFD3H2jyDFA2jFd3BFcKaHgsefqLlZt9WZo= +github.com/fumiama/go-onebot-agent v0.0.0-20250921143402-d55156efbd5b/go.mod h1:wVMgFWkR3GpipL05FkokvrV/jWFIgoEWN1jzUGa0bWg= 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= diff --git a/plugin/aichat/cfg.go b/plugin/aichat/cfg.go index f9287532..6299e169 100644 --- a/plugin/aichat/cfg.go +++ b/plugin/aichat/cfg.go @@ -30,6 +30,7 @@ type config struct { NoReplyAT bool NoSystemP bool NoRecord bool + NoAgent bool } func newconfig() config { diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index 6b34a6be..bcd9f652 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -10,6 +10,7 @@ import ( "github.com/fumiama/deepinfra" "github.com/fumiama/deepinfra/model" + goba "github.com/fumiama/go-onebot-agent" "github.com/sirupsen/logrus" "github.com/tidwall/gjson" @@ -33,6 +34,7 @@ var ( Help: "- 设置AI聊天触发概率10\n" + "- 设置AI聊天温度80\n" + "- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" + + "- 设置AI聊天(不)使用Agent模式\n" + "- 设置AI聊天(不)支持系统提示词\n" + "- 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions\n" + "- 设置AI聊天密钥xxx\n" + @@ -142,6 +144,24 @@ func init() { return } + if !cfg.NoAgent { + role := goba.PermRoleUser + if zero.AdminPermission(ctx) { + role = goba.PermRoleAdmin + if zero.SuperUserPermission(ctx) { + role = goba.PermRoleOwner + } + } + req, err := chat.AgentOf(ctx.Event.SelfID).GetAction(x, mod, gid, role, false) + if err != nil { + logrus.Warnln("[aichat] agent err:", err, &req) + return + } + logrus.Infoln("[aichat] agent do:", &req) + ctx.CallAction(req.Action, req.Params) + return + } + data, err := x.Request(chat.GetChatContext(mod, gid, cfg.SystemP, cfg.NoSystemP)) if err != nil { logrus.Warnln("[aichat] post err:", err) @@ -150,7 +170,7 @@ func init() { txt := chat.Sanitize(strings.Trim(data, "\n  ")) if len(txt) > 0 { - chat.AddChatReply(gid, zero.BotConfig.NickName[0], txt) + chat.AddChatReply(gid, txt) nick := zero.BotConfig.NickName[rand.Intn(len(zero.BotConfig.NickName))] txt = strings.ReplaceAll(txt, "{name}", ctx.CardOrNickName(ctx.Event.UserID)) txt = strings.ReplaceAll(txt, "{me}", nick) @@ -302,6 +322,8 @@ 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.OnPrefix("设置AI聊天最大长度", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetuint(&cfg.MaxN)) en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). From f09f15937cf6d3c833c54ae1c267a1164fef45fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:46:18 +0800 Subject: [PATCH 2/8] chore: update deps --- go.mod | 4 ++-- go.sum | 8 ++++---- plugin/aichat/main.go | 11 +++++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 50896d9d..9ecf4a3b 100644 --- a/go.mod +++ b/go.mod @@ -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.20250921143810-bb64d87a5416 + github.com/FloatTech/zbputils v1.7.2-0.20250922144137-bf2b9bb6a8d9 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 @@ -24,7 +24,7 @@ require ( github.com/fumiama/cron v1.3.0 github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 github.com/fumiama/go-base16384 v1.7.0 - github.com/fumiama/go-onebot-agent v0.0.0-20250921143402-d55156efbd5b + github.com/fumiama/go-onebot-agent v0.0.0-20250922144028-320c6b821c49 github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 diff --git a/go.sum b/go.sum index 71b504f1..9767d457 100644 --- a/go.sum +++ b/go.sum @@ -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.20250921143810-bb64d87a5416 h1:aBhn9/QltOId6bbjkSXYP+p4vZLB1RfjYUK48XGwjc4= -github.com/FloatTech/zbputils v1.7.2-0.20250921143810-bb64d87a5416/go.mod h1:Mb9JQb4MpU0GHRQv9PS+Qq+F0UBRVPrjU+ZlAjhPccQ= +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/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= @@ -63,8 +63,8 @@ github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 h1:6PglFpNVm3Dal github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= 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-20250921143402-d55156efbd5b h1:LToVa4PSTFD3H2jyDFA2jFd3BFcKaHgsefqLlZt9WZo= -github.com/fumiama/go-onebot-agent v0.0.0-20250921143402-d55156efbd5b/go.mod h1:wVMgFWkR3GpipL05FkokvrV/jWFIgoEWN1jzUGa0bWg= +github.com/fumiama/go-onebot-agent v0.0.0-20250922144028-320c6b821c49 h1:5Z+Ljv17X4i/PthL7eVfXq+CKlOnboRlPZeG7Nngyyk= +github.com/fumiama/go-onebot-agent v0.0.0-20250922144028-320c6b821c49/go.mod h1:wVMgFWkR3GpipL05FkokvrV/jWFIgoEWN1jzUGa0bWg= 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= diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index bcd9f652..0a1859e3 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -152,13 +152,16 @@ func init() { role = goba.PermRoleOwner } } - req, err := chat.AgentOf(ctx.Event.SelfID).GetAction(x, mod, gid, role, false) + reqs, err := chat.AgentOf(ctx.Event.SelfID).GetAction(x, mod, gid, role, false) if err != nil { - logrus.Warnln("[aichat] agent err:", err, &req) + logrus.Warnln("[aichat] agent err:", err, reqs) return } - logrus.Infoln("[aichat] agent do:", &req) - ctx.CallAction(req.Action, req.Params) + logrus.Infoln("[aichat] agent do:", reqs) + for _, req := range reqs { + ctx.CallAction(req.Action, req.Params) + process.SleepAbout1sTo2s() + } return } From 8811df59689117188d3de5dcc851d99b93d588e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:51:04 +0800 Subject: [PATCH 3/8] feat(aichat): add perm check for cross-group --- plugin/aichat/main.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index 0a1859e3..acffe960 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -159,6 +159,17 @@ func init() { } logrus.Infoln("[aichat] agent do:", reqs) for _, req := range reqs { + if req.Action == "send_group_msg" { + gid, ok := req.Params["group_id"].(int64) + if !ok { + logrus.Warnln("[aichat] invalid", req.Action, req.Params) + continue + } + if ctx.Event.GroupID != gid && !zero.SuperUserPermission(ctx) { + logrus.Warnln("[aichat] refuse to send out of grp from", ctx.Event.GroupID, "to", gid) + continue + } + } ctx.CallAction(req.Action, req.Params) process.SleepAbout1sTo2s() } From 8e87be262c2ac026abbe60c873472d7bf8156678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:06:46 +0800 Subject: [PATCH 4/8] fix(aichat): agent group send --- plugin/aichat/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index acffe960..d698dbd5 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -4,6 +4,7 @@ package aichat import ( "errors" "math/rand" + "reflect" "strconv" "strings" "time" @@ -160,11 +161,12 @@ func init() { logrus.Infoln("[aichat] agent do:", reqs) for _, req := range reqs { if req.Action == "send_group_msg" { - gid, ok := req.Params["group_id"].(int64) - if !ok { + v := reflect.ValueOf(req.Params["group_id"]) + if !v.CanInt() { logrus.Warnln("[aichat] invalid", req.Action, req.Params) continue } + gid = v.Int() if ctx.Event.GroupID != gid && !zero.SuperUserPermission(ctx) { logrus.Warnln("[aichat] refuse to send out of grp from", ctx.Event.GroupID, "to", gid) continue From 73eef961b5cf9614a73ad8fe99b0df72ffc9bdcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:55:40 +0800 Subject: [PATCH 5/8] fix(aichat): agent group send --- go.mod | 2 +- go.sum | 4 ++-- plugin/aichat/main.go | 13 +++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 9ecf4a3b..1b866d40 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/fumiama/cron v1.3.0 github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 github.com/fumiama/go-base16384 v1.7.0 - github.com/fumiama/go-onebot-agent v0.0.0-20250922144028-320c6b821c49 + github.com/fumiama/go-onebot-agent v0.0.0-20250922152742-c40bb3512d63 github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 diff --git a/go.sum b/go.sum index 9767d457..ea35bde6 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 h1:6PglFpNVm3Dal github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= 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-20250922144028-320c6b821c49 h1:5Z+Ljv17X4i/PthL7eVfXq+CKlOnboRlPZeG7Nngyyk= -github.com/fumiama/go-onebot-agent v0.0.0-20250922144028-320c6b821c49/go.mod h1:wVMgFWkR3GpipL05FkokvrV/jWFIgoEWN1jzUGa0bWg= +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-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= diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index d698dbd5..4292761b 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -2,6 +2,7 @@ package aichat import ( + "encoding/json" "errors" "math/rand" "reflect" @@ -161,12 +162,16 @@ func init() { logrus.Infoln("[aichat] agent do:", reqs) for _, req := range reqs { if req.Action == "send_group_msg" { - v := reflect.ValueOf(req.Params["group_id"]) - if !v.CanInt() { - logrus.Warnln("[aichat] invalid", req.Action, req.Params) + v, ok := req.Params["group_id"].(json.Number) + if !ok { + logrus.Warnln("[aichat] invalid group_id type", reflect.TypeOf(req.Params["group_id"])) + continue + } + gid, err = v.Int64() + if !ok { + logrus.Warnln("[aichat] agent conv req gid err:", err) continue } - gid = v.Int() if ctx.Event.GroupID != gid && !zero.SuperUserPermission(ctx) { logrus.Warnln("[aichat] refuse to send out of grp from", ctx.Event.GroupID, "to", gid) continue From 7640f0cfacf63d1fbf7ba468fbb55cdee2cc7533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:24:04 +0800 Subject: [PATCH 6/8] chore: update deps --- go.mod | 4 ++-- go.sum | 4 ++++ plugin/aichat/main.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1b866d40..28e030a3 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ 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-20250920170049-e3d1b92cc3a1 + github.com/fumiama/deepinfra v0.0.0-20250923161832-8757564fe76c github.com/fumiama/go-base16384 v1.7.0 github.com/fumiama/go-onebot-agent v0.0.0-20250922152742-c40bb3512d63 github.com/fumiama/go-registry v0.2.7 @@ -63,7 +63,7 @@ require ( github.com/faiface/beep v1.1.0 // indirect github.com/fumiama/go-simple-protobuf v0.2.0 // indirect github.com/fumiama/gofastTEA v0.0.10 // indirect - github.com/fumiama/imgsz v0.0.2 // indirect + github.com/fumiama/imgsz v0.0.4 // indirect github.com/gabriel-vasile/mimetype v1.0.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/go.sum b/go.sum index ea35bde6..ddcea5e3 100644 --- a/go.sum +++ b/go.sum @@ -61,6 +61,8 @@ 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/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= @@ -75,6 +77,8 @@ github.com/fumiama/gotracemoe v0.0.3 h1:iI5EbE9A3UUbfukG6+/soYPjp1S31eCNYf4tw7s6 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= github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565/go.mod h1:UUEvyLTJ7yoOA/viKG4wEis4ERydM7+Ny6gZUWgkS80= github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5 h1:jDxsIupsT84A6WHcs6kWbst+KqrRQ8/o0VyoFMnbBOA= diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index 4292761b..6532f616 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -575,7 +575,7 @@ func llmchat(prompt string, temp int64) (string, error) { return "", errors.New("不支持的AI类型") } - data, err := x.Request(mod.User(prompt)) + data, err := x.Request(mod.User(model.NewContentText(prompt))) if err != nil { return "", err } From 3c41c18d27eb2cd69ff43f734ac8595ddefda55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Thu, 25 Sep 2025 00:20:07 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat(aichat):=20agent=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=AF=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- go.mod | 6 +- go.sum | 16 +-- plugin/aichat/cfg.go | 181 ++++++++++++++++-------- plugin/aichat/main.go | 298 ++++++++++++++------------------------- plugin/aichat/storage.go | 141 ++++++++++++++++++ 6 files changed, 381 insertions(+), 269 deletions(-) create mode 100644 plugin/aichat/storage.go diff --git a/README.md b/README.md index e8b26aec..70361326 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/go.mod b/go.mod index 28e030a3..f6c25cd3 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index ddcea5e3..4ac84e14 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/plugin/aichat/cfg.go b/plugin/aichat/cfg.go index 6299e169..ad0136de 100644 --- a/plugin/aichat/cfg.go +++ b/plugin/aichat/cfg.go @@ -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 "否" -} diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index 6532f616..b8a70703 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -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))) diff --git a/plugin/aichat/storage.go b/plugin/aichat/storage.go new file mode 100644 index 00000000..43b2ac5f --- /dev/null +++ b/plugin/aichat/storage.go @@ -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("成功")) + } +} From 72e0e796c4d8b0745796a20ffed33e8db24e91f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Thu, 25 Sep 2025 00:23:29 +0800 Subject: [PATCH 8/8] chore: update deps --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f6c25cd3..bb3ef871 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ 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-20250924134153-348633b5c6fb + github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa github.com/fumiama/go-base16384 v1.7.0 github.com/fumiama/go-onebot-agent v0.0.0-20250924153727-0cdc929590cf github.com/fumiama/go-registry v0.2.7 diff --git a/go.sum b/go.sum index 4ac84e14..79f3e94d 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ 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-20250924134153-348633b5c6fb h1:d0gl31nyOER4O3K0wpepont8flrDtzZsKACeWGxARX0= -github.com/fumiama/deepinfra v0.0.0-20250924134153-348633b5c6fb/go.mod h1:HgRu6n9XgeyNki7wHHlTa75ORiv3UlYZafqOAoazhOI= +github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa h1:UMMNejpPp8dn92GPaVSZ2XKNSgp7+CVneOkZfExUilk= +github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa/go.mod h1:uqsWK/GM9OvKV0pXZOQB63rWugBbiXInY8E1JoRKhkg= 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-20250924153727-0cdc929590cf h1:yFkCBQlFBCRQLru/ANWvvWDTRpT3qLhBP94aAJnlmaY=