Skip to content

Commit 20d49cc

Browse files
authored
feat(aichat): 添加/gpt命令,直接聊天 (FloatTech#1190)
* ✨ 添加大模型聊天,便于使用版本的 * 🐛 减少重复关键词 * 🎨 优化换行符 * ✨ 添加转换函数 * ✨ 添加lint优化 * 🎨 按字符切片 * 🎨 修改lint
1 parent 1f66f47 commit 20d49cc

File tree

1 file changed

+179
-41
lines changed

1 file changed

+179
-41
lines changed

plugin/aichat/main.go

Lines changed: 179 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package aichat
33

44
import (
5+
"errors"
56
"math/rand"
67
"strconv"
78
"strings"
@@ -46,7 +47,9 @@ var (
4647
"- 设置AI聊天(不)以AI语音输出\n" +
4748
"- 查看AI聊天配置\n" +
4849
"- 重置AI聊天\n" +
49-
"- 群聊总结 [消息数目]|群聊总结 1000\n",
50+
"- 群聊总结 [消息数目]|群聊总结 1000\n" +
51+
"- /gpt [内容] (使用大模型聊天)\n",
52+
5053
PrivateDataFolder: "aichat",
5154
})
5255
)
@@ -61,6 +64,32 @@ var (
6164
limit = ctxext.NewLimiterManager(time.Second*30, 1)
6265
)
6366

67+
// getModelParams 获取模型参数:温度(float32(temp)/100)、TopP和最大长度
68+
func getModelParams(temp int64) (temperature float32, topp float32, maxn uint) {
69+
// 处理温度参数
70+
if temp <= 0 {
71+
temp = 70 // default setting
72+
}
73+
if temp > 100 {
74+
temp = 100
75+
}
76+
temperature = float32(temp) / 100
77+
78+
// 处理TopP参数
79+
topp = cfg.TopP
80+
if topp == 0 {
81+
topp = 0.9
82+
}
83+
84+
// 处理最大长度参数
85+
maxn = cfg.MaxN
86+
if maxn == 0 {
87+
maxn = 4096
88+
}
89+
90+
return temperature, topp, maxn
91+
}
92+
6493
func init() {
6594
en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool {
6695
return ctx.ExtractPlainText() != "" &&
@@ -88,39 +117,25 @@ func init() {
88117
return
89118
}
90119

91-
if temp <= 0 {
92-
temp = 70 // default setting
93-
}
94-
if temp > 100 {
95-
temp = 100
96-
}
120+
temperature, topp, maxn := getModelParams(temp)
97121

98122
x := deepinfra.NewAPI(cfg.API, cfg.Key)
99123
var mod model.Protocol
100-
maxn := cfg.MaxN
101-
if maxn == 0 {
102-
maxn = 4096
103-
}
104-
topp := cfg.TopP
105-
if topp == 0 {
106-
topp = 0.9
107-
}
108-
109124
switch cfg.Type {
110125
case 0:
111126
mod = model.NewOpenAI(
112127
cfg.ModelName, cfg.Separator,
113-
float32(temp)/100, topp, maxn,
128+
temperature, topp, maxn,
114129
)
115130
case 1:
116131
mod = model.NewOLLaMA(
117132
cfg.ModelName, cfg.Separator,
118-
float32(temp)/100, topp, maxn,
133+
temperature, topp, maxn,
119134
)
120135
case 2:
121136
mod = model.NewGenAI(
122137
cfg.ModelName,
123-
float32(temp)/100, topp, maxn,
138+
temperature, topp, maxn,
124139
)
125140
default:
126141
logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
@@ -319,17 +334,26 @@ func init() {
319334
// 添加群聊总结功能
320335
en.OnRegex(`^群聊总结\s?(\d*)$`, ensureconfig, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(limit.LimitByGroup).Handle(func(ctx *zero.Ctx) {
321336
ctx.SendChain(message.Text("少女思考中..."))
337+
gid := ctx.Event.GroupID
338+
if gid == 0 {
339+
gid = -ctx.Event.UserID
340+
}
341+
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
342+
if !ok {
343+
return
344+
}
345+
rate := c.GetData(gid)
346+
temp := (rate >> 8) & 0xff
322347
p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
323348
if p > 1000 {
324349
p = 1000
325350
}
326351
if p == 0 {
327352
p = 200
328353
}
329-
gid := ctx.Event.GroupID
330354
group := ctx.GetGroupInfo(gid, false)
331355
if group.MemberCount == 0 {
332-
ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取摘要"))
356+
ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取总结"))
333357
return
334358
}
335359

@@ -350,8 +374,13 @@ func init() {
350374
return
351375
}
352376

353-
// 调用大模型API进行摘要
354-
summary, err := summarizeMessages(messages)
377+
// 构造总结请求提示
378+
summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n" +
379+
strings.Join(messages, "\n")
380+
381+
// 调用大模型API进行总结
382+
summary, err := llmchat(summaryPrompt, temp)
383+
355384
if err != nil {
356385
ctx.SendChain(message.Text("ERROR: ", err))
357386
return
@@ -367,34 +396,143 @@ func init() {
367396
b.WriteString(" 条消息总结:\n\n")
368397
b.WriteString(summary)
369398

370-
// 分割总结内容为多段
371-
parts := strings.Split(b.String(), "\n\n")
372-
msg := make(message.Message, 0, len(parts))
373-
for _, part := range parts {
374-
if part != "" {
375-
msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(part)))
399+
// 分割总结内容为多段(按1000字符长度切割)
400+
summaryText := b.String()
401+
msg := make(message.Message, 0)
402+
for len(summaryText) > 0 {
403+
if len(summaryText) <= 1000 {
404+
msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(summaryText)))
405+
break
376406
}
407+
408+
// 查找1000字符内的最后一个换行符,尽量在换行处分割
409+
chunk := summaryText[:1000]
410+
lastNewline := strings.LastIndex(chunk, "\n")
411+
if lastNewline > 0 {
412+
chunk = summaryText[:lastNewline+1]
413+
}
414+
415+
msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
416+
summaryText = summaryText[len(chunk):]
417+
}
418+
if len(msg) > 0 {
419+
ctx.Send(msg)
420+
}
421+
})
422+
423+
// 添加 /gpt 命令处理(同时支持回复消息和直接使用)
424+
en.OnKeyword("/gpt", ensureconfig).SetBlock(true).Handle(func(ctx *zero.Ctx) {
425+
gid := ctx.Event.GroupID
426+
if gid == 0 {
427+
gid = -ctx.Event.UserID
428+
}
429+
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
430+
if !ok {
431+
return
432+
}
433+
rate := c.GetData(gid)
434+
temp := (rate >> 8) & 0xff
435+
text := ctx.MessageString()
436+
437+
var query string
438+
var replyContent string
439+
440+
// 检查是否是回复消息 (使用MessageElement检查而不是CQ码)
441+
for _, elem := range ctx.Event.Message {
442+
if elem.Type == "reply" {
443+
// 提取被回复的消息ID
444+
replyIDStr := elem.Data["id"]
445+
replyID, err := strconv.ParseInt(replyIDStr, 10, 64)
446+
if err == nil {
447+
// 获取被回复的消息内容
448+
replyMsg := ctx.GetMessage(replyID)
449+
if replyMsg.Elements != nil {
450+
replyContent = replyMsg.Elements.ExtractPlainText()
451+
}
452+
}
453+
break // 找到回复元素后退出循环
454+
}
455+
}
456+
457+
// 提取 /gpt 后面的内容
458+
parts := strings.SplitN(text, "/gpt", 2)
459+
460+
var gContent string
461+
if len(parts) > 1 {
462+
gContent = strings.TrimSpace(parts[1])
463+
}
464+
465+
// 组合内容:优先使用回复内容,如果同时有/gpt内容则拼接
466+
switch {
467+
case replyContent != "" && gContent != "":
468+
query = replyContent + "\n" + gContent
469+
case replyContent != "":
470+
query = replyContent
471+
case gContent != "":
472+
query = gContent
473+
default:
474+
return
475+
}
476+
477+
// 调用大模型API进行聊天
478+
reply, err := llmchat(query, temp)
479+
if err != nil {
480+
ctx.SendChain(message.Text("ERROR: ", err))
481+
return
482+
}
483+
484+
// 分割总结内容为多段(按1000字符长度切割)
485+
msg := make(message.Message, 0)
486+
for len(reply) > 0 {
487+
if len(reply) <= 1000 {
488+
msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(reply)))
489+
break
490+
}
491+
492+
// 查找1000字符内的最后一个换行符,尽量在换行处分割
493+
chunk := reply[:1000]
494+
lastNewline := strings.LastIndex(chunk, "\n")
495+
if lastNewline > 0 {
496+
chunk = reply[:lastNewline+1]
497+
}
498+
499+
msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
500+
reply = reply[len(chunk):]
377501
}
378502
if len(msg) > 0 {
379503
ctx.Send(msg)
380504
}
381505
})
382506
}
383507

384-
// summarizeMessages 调用大模型API进行消息摘要
385-
func summarizeMessages(messages []string) (string, error) {
386-
// 使用现有的AI配置进行摘要
387-
x := deepinfra.NewAPI(cfg.API, cfg.Key)
388-
mod := model.NewOpenAI(
389-
cfg.ModelName, cfg.Separator,
390-
float32(70)/100, 0.9, 4096,
391-
)
508+
// llmchat 调用大模型API包装
509+
func llmchat(prompt string, temp int64) (string, error) {
510+
temperature, topp, maxn := getModelParams(temp) // 使用默认温度70
392511

393-
// 构造摘要请求提示
394-
summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n\n" +
395-
strings.Join(messages, "\n---\n")
512+
x := deepinfra.NewAPI(cfg.API, cfg.Key)
513+
var mod model.Protocol
514+
switch cfg.Type {
515+
case 0:
516+
mod = model.NewOpenAI(
517+
cfg.ModelName, cfg.Separator,
518+
temperature, topp, maxn,
519+
)
520+
case 1:
521+
mod = model.NewOLLaMA(
522+
cfg.ModelName, cfg.Separator,
523+
temperature, topp, maxn,
524+
)
525+
case 2:
526+
mod = model.NewGenAI(
527+
cfg.ModelName,
528+
temperature, topp, maxn,
529+
)
530+
default:
531+
logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
532+
return "", errors.New("不支持的AI类型")
533+
}
396534

397-
data, err := x.Request(mod.User(summaryPrompt))
535+
data, err := x.Request(mod.User(prompt))
398536
if err != nil {
399537
return "", err
400538
}

0 commit comments

Comments
 (0)