2
2
package aichat
3
3
4
4
import (
5
+ "errors"
5
6
"math/rand"
6
7
"strconv"
7
8
"strings"
46
47
"- 设置AI聊天(不)以AI语音输出\n " +
47
48
"- 查看AI聊天配置\n " +
48
49
"- 重置AI聊天\n " +
49
- "- 群聊总结 [消息数目]|群聊总结 1000\n " ,
50
+ "- 群聊总结 [消息数目]|群聊总结 1000\n " +
51
+ "- /gpt [内容] (使用大模型聊天)\n " ,
52
+
50
53
PrivateDataFolder : "aichat" ,
51
54
})
52
55
)
61
64
limit = ctxext .NewLimiterManager (time .Second * 30 , 1 )
62
65
)
63
66
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
+
64
93
func init () {
65
94
en .OnMessage (ensureconfig , func (ctx * zero.Ctx ) bool {
66
95
return ctx .ExtractPlainText () != "" &&
@@ -88,39 +117,25 @@ func init() {
88
117
return
89
118
}
90
119
91
- if temp <= 0 {
92
- temp = 70 // default setting
93
- }
94
- if temp > 100 {
95
- temp = 100
96
- }
120
+ temperature , topp , maxn := getModelParams (temp )
97
121
98
122
x := deepinfra .NewAPI (cfg .API , cfg .Key )
99
123
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
-
109
124
switch cfg .Type {
110
125
case 0 :
111
126
mod = model .NewOpenAI (
112
127
cfg .ModelName , cfg .Separator ,
113
- float32 ( temp ) / 100 , topp , maxn ,
128
+ temperature , topp , maxn ,
114
129
)
115
130
case 1 :
116
131
mod = model .NewOLLaMA (
117
132
cfg .ModelName , cfg .Separator ,
118
- float32 ( temp ) / 100 , topp , maxn ,
133
+ temperature , topp , maxn ,
119
134
)
120
135
case 2 :
121
136
mod = model .NewGenAI (
122
137
cfg .ModelName ,
123
- float32 ( temp ) / 100 , topp , maxn ,
138
+ temperature , topp , maxn ,
124
139
)
125
140
default :
126
141
logrus .Warnln ("[aichat] unsupported AI type" , cfg .Type )
@@ -319,17 +334,26 @@ func init() {
319
334
// 添加群聊总结功能
320
335
en .OnRegex (`^群聊总结\s?(\d*)$` , ensureconfig , zero .OnlyGroup , zero .AdminPermission ).SetBlock (true ).Limit (limit .LimitByGroup ).Handle (func (ctx * zero.Ctx ) {
321
336
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
322
347
p , _ := strconv .ParseInt (ctx .State ["regex_matched" ].([]string )[1 ], 10 , 64 )
323
348
if p > 1000 {
324
349
p = 1000
325
350
}
326
351
if p == 0 {
327
352
p = 200
328
353
}
329
- gid := ctx .Event .GroupID
330
354
group := ctx .GetGroupInfo (gid , false )
331
355
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 , "),无法获取总结 " ))
333
357
return
334
358
}
335
359
@@ -350,8 +374,13 @@ func init() {
350
374
return
351
375
}
352
376
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
+
355
384
if err != nil {
356
385
ctx .SendChain (message .Text ("ERROR: " , err ))
357
386
return
@@ -367,34 +396,143 @@ func init() {
367
396
b .WriteString (" 条消息总结:\n \n " )
368
397
b .WriteString (summary )
369
398
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
376
406
}
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 ):]
377
501
}
378
502
if len (msg ) > 0 {
379
503
ctx .Send (msg )
380
504
}
381
505
})
382
506
}
383
507
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
392
511
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
+ }
396
534
397
- data , err := x .Request (mod .User (summaryPrompt ))
535
+ data , err := x .Request (mod .User (prompt ))
398
536
if err != nil {
399
537
return "" , err
400
538
}
0 commit comments