koishi-plugin-isthattrue 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +1431 -1393
- package/lib/services/tofTool.d.ts +3 -2
- package/package.json +1 -1
- package/lib/agents/searchAgent.d.ts +0 -53
- package/lib/services/anspire.d.ts +0 -30
- package/lib/services/grok.d.ts +0 -32
- package/lib/services/kimi.d.ts +0 -22
- package/lib/services/tavily.d.ts +0 -30
- package/lib/services/zhipu.d.ts +0 -22
package/lib/index.js
CHANGED
|
@@ -1,1393 +1,1431 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
{
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
${
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
1.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
output2 +=
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
${
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
${
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
output
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
this.logger.
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
${
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
}
|
|
890
|
-
result
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
result.
|
|
905
|
-
result.images =
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
if (
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
);
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
this.logger.
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
};
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
return
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name2 in all)
|
|
10
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Config: () => Config,
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
inject: () => inject,
|
|
36
|
+
name: () => name,
|
|
37
|
+
usage: () => usage
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(src_exports);
|
|
40
|
+
var import_koishi3 = require("koishi");
|
|
41
|
+
|
|
42
|
+
// src/services/chatluna.ts
|
|
43
|
+
var import_messages = require("@langchain/core/messages");
|
|
44
|
+
var ChatlunaAdapter = class _ChatlunaAdapter {
|
|
45
|
+
constructor(ctx, config) {
|
|
46
|
+
this.ctx = ctx;
|
|
47
|
+
this.config = config;
|
|
48
|
+
this.logger = ctx.logger("isthattrue");
|
|
49
|
+
}
|
|
50
|
+
static {
|
|
51
|
+
__name(this, "ChatlunaAdapter");
|
|
52
|
+
}
|
|
53
|
+
logger;
|
|
54
|
+
// 串行化代理环境变量操作,避免并发请求互相干扰
|
|
55
|
+
static proxyMutex = Promise.resolve();
|
|
56
|
+
/**
|
|
57
|
+
* 检查 Chatluna 服务是否可用
|
|
58
|
+
*/
|
|
59
|
+
isAvailable() {
|
|
60
|
+
return !!this.ctx.chatluna;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 在临时移除系统代理的环境中串行执行 fn,防止并发请求污染全局 env
|
|
64
|
+
*/
|
|
65
|
+
runWithProxyBypass(fn) {
|
|
66
|
+
const proxyVars = ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "ALL_PROXY", "all_proxy"];
|
|
67
|
+
let release;
|
|
68
|
+
const slot = new Promise((resolve) => {
|
|
69
|
+
release = resolve;
|
|
70
|
+
});
|
|
71
|
+
const task = _ChatlunaAdapter.proxyMutex.then(async () => {
|
|
72
|
+
const saved = {};
|
|
73
|
+
proxyVars.forEach((v) => {
|
|
74
|
+
saved[v] = process.env[v];
|
|
75
|
+
delete process.env[v];
|
|
76
|
+
});
|
|
77
|
+
this.logger.debug("已临时移除系统代理环境变量");
|
|
78
|
+
try {
|
|
79
|
+
return await fn();
|
|
80
|
+
} finally {
|
|
81
|
+
proxyVars.forEach((v) => {
|
|
82
|
+
if (saved[v] !== void 0) process.env[v] = saved[v];
|
|
83
|
+
});
|
|
84
|
+
release();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
_ChatlunaAdapter.proxyMutex = slot;
|
|
88
|
+
return task;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 发送聊天请求
|
|
92
|
+
*/
|
|
93
|
+
async chat(request) {
|
|
94
|
+
if (!this.isAvailable()) {
|
|
95
|
+
throw new Error("Chatluna 服务不可用,请确保已安装并启用 koishi-plugin-chatluna");
|
|
96
|
+
}
|
|
97
|
+
if (this.config?.bypassProxy) {
|
|
98
|
+
return this.runWithProxyBypass(() => this.doChat(request));
|
|
99
|
+
}
|
|
100
|
+
if (this.config?.logLLMDetails) {
|
|
101
|
+
const proxyVars = ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "ALL_PROXY", "all_proxy"];
|
|
102
|
+
const activeProxies = proxyVars.filter((v) => process.env[v]).map((v) => `${v}=${process.env[v]}`);
|
|
103
|
+
if (activeProxies.length > 0) {
|
|
104
|
+
this.logger.debug(`当前环境代理: ${activeProxies.join(", ")}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return this.doChat(request);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 实际发送请求(不处理代理)
|
|
111
|
+
*/
|
|
112
|
+
async doChat(request) {
|
|
113
|
+
const startTime = Date.now();
|
|
114
|
+
try {
|
|
115
|
+
const modelRef = await this.ctx.chatluna.createChatModel(request.model);
|
|
116
|
+
const model = modelRef.value;
|
|
117
|
+
if (!model) {
|
|
118
|
+
throw new Error(`无法创建模型: ${request.model},请确保模型已正确配置`);
|
|
119
|
+
}
|
|
120
|
+
const messages = [];
|
|
121
|
+
if (request.systemPrompt) {
|
|
122
|
+
messages.push(new import_messages.SystemMessage(request.systemPrompt));
|
|
123
|
+
}
|
|
124
|
+
const messageContent = request.message;
|
|
125
|
+
if (request.images && request.images.length > 0) {
|
|
126
|
+
const multimodalContent = [
|
|
127
|
+
{ type: "text", text: request.message }
|
|
128
|
+
];
|
|
129
|
+
for (const base64Image of request.images) {
|
|
130
|
+
multimodalContent.push({
|
|
131
|
+
type: "image_url",
|
|
132
|
+
image_url: `data:image/jpeg;base64,${base64Image}`
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
messages.push(new import_messages.HumanMessage({ content: multimodalContent }));
|
|
136
|
+
this.logger.debug(`构建多模态消息,包含 ${request.images.length} 张图片`);
|
|
137
|
+
} else {
|
|
138
|
+
messages.push(new import_messages.HumanMessage(messageContent));
|
|
139
|
+
}
|
|
140
|
+
if (this.config?.logLLMDetails) {
|
|
141
|
+
this.logger.info(`[LLM Request] Model: ${request.model}
|
|
142
|
+
System: ${request.systemPrompt || "None"}
|
|
143
|
+
Message: ${typeof messageContent === "string" ? messageContent.substring(0, 500) : "Complex content"}`);
|
|
144
|
+
}
|
|
145
|
+
const response = await model.invoke(messages, {
|
|
146
|
+
temperature: 0.3
|
|
147
|
+
// 低温度以减少幻觉
|
|
148
|
+
});
|
|
149
|
+
const processingTime = Date.now() - startTime;
|
|
150
|
+
this.logger.debug(`Chatluna 请求完成,耗时 ${processingTime}ms`);
|
|
151
|
+
const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
152
|
+
if (this.config?.logLLMDetails) {
|
|
153
|
+
this.logger.info(`[LLM Response] Model: ${request.model}
|
|
154
|
+
Content: ${content}`);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
content,
|
|
158
|
+
model: request.model,
|
|
159
|
+
sources: this.extractSources(content)
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.logger.error("Chatluna 请求失败:", error);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 带重试的聊天请求
|
|
168
|
+
*/
|
|
169
|
+
async chatWithRetry(request, maxRetries = 2, fallbackModel) {
|
|
170
|
+
let lastError = null;
|
|
171
|
+
let currentModel = request.model;
|
|
172
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
173
|
+
try {
|
|
174
|
+
return await this.chat({
|
|
175
|
+
...request,
|
|
176
|
+
model: currentModel
|
|
177
|
+
});
|
|
178
|
+
} catch (error) {
|
|
179
|
+
lastError = error;
|
|
180
|
+
this.logger.warn(`请求失败 (尝试 ${attempt + 1}/${maxRetries + 1}):`, error);
|
|
181
|
+
if (attempt === maxRetries - 1 && fallbackModel && fallbackModel !== currentModel) {
|
|
182
|
+
this.logger.info(`切换到备用模型: ${fallbackModel}`);
|
|
183
|
+
currentModel = fallbackModel;
|
|
184
|
+
}
|
|
185
|
+
if (attempt < maxRetries) {
|
|
186
|
+
await this.sleep(1e3 * (attempt + 1));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
throw lastError || new Error("请求失败,已达最大重试次数");
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 从响应中提取来源链接
|
|
194
|
+
*/
|
|
195
|
+
extractSources(content) {
|
|
196
|
+
const sources = [];
|
|
197
|
+
const urlRegex = /https?:\/\/[^\s\])"']+/g;
|
|
198
|
+
const matches = content.match(urlRegex);
|
|
199
|
+
if (matches) {
|
|
200
|
+
sources.push(...matches);
|
|
201
|
+
}
|
|
202
|
+
const sourceRegex = /\[来源[::]\s*([^\]]+)\]/g;
|
|
203
|
+
let match;
|
|
204
|
+
while ((match = sourceRegex.exec(content)) !== null) {
|
|
205
|
+
sources.push(match[1]);
|
|
206
|
+
}
|
|
207
|
+
return [...new Set(sources)];
|
|
208
|
+
}
|
|
209
|
+
sleep(ms) {
|
|
210
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/utils/prompts.ts
|
|
215
|
+
var SUB_SEARCH_AGENT_SYSTEM_PROMPT = `你是事实核查搜索员,专门使用 X (Twitter) 和网络搜索验证声明。
|
|
216
|
+
|
|
217
|
+
重点搜索:
|
|
218
|
+
- X (Twitter) 上的相关讨论和官方账号声明
|
|
219
|
+
- 新闻报道和权威媒体来源
|
|
220
|
+
- 社交媒体上的第一手证据
|
|
221
|
+
|
|
222
|
+
输出 JSON:
|
|
223
|
+
\`\`\`json
|
|
224
|
+
{"findings":"详细发现摘要","sources":["来源URL"],"confidence":0.0-1.0}
|
|
225
|
+
\`\`\`
|
|
226
|
+
`;
|
|
227
|
+
function buildSubSearchPrompt(claim) {
|
|
228
|
+
return `请验证以下声明的真实性,重点搜索 X (Twitter) 和社交媒体上的相关讨论和证据:
|
|
229
|
+
|
|
230
|
+
"${claim}"
|
|
231
|
+
|
|
232
|
+
搜索要点:
|
|
233
|
+
1. 在 X/Twitter 上搜索相关话题和讨论
|
|
234
|
+
2. 查找官方账号的声明或澄清
|
|
235
|
+
3. 搜索相关新闻报道
|
|
236
|
+
4. 注意时间线和来源可信度`;
|
|
237
|
+
}
|
|
238
|
+
__name(buildSubSearchPrompt, "buildSubSearchPrompt");
|
|
239
|
+
var VERIFY_AGENT_SYSTEM_PROMPT = `你是事实核查裁判。基于搜索证据做出判决。
|
|
240
|
+
|
|
241
|
+
判决类别:TRUE(真实)、FALSE(虚假)、PARTIALLY_TRUE(部分真实)、UNCERTAIN(无法确定)
|
|
242
|
+
|
|
243
|
+
输出JSON:
|
|
244
|
+
\`\`\`json
|
|
245
|
+
{"verdict":"TRUE/FALSE/PARTIALLY_TRUE/UNCERTAIN","confidence":0.0-1.0,"reasoning":"判决理由","sources":["来源"]}
|
|
246
|
+
\`\`\`
|
|
247
|
+
|
|
248
|
+
原则:证据不足时判UNCERTAIN,重视权威来源,考虑时效性。`;
|
|
249
|
+
function buildVerifyPrompt(originalContent, searchResults, hasImages) {
|
|
250
|
+
const resultsText = searchResults.map((r, i) => `[${i + 1}] ${r.findings}
|
|
251
|
+
来源: ${r.sources.slice(0, 3).join(", ") || "无"}`).join("\n\n");
|
|
252
|
+
let prompt = `声明:"${originalContent}"
|
|
253
|
+
|
|
254
|
+
搜索结果:
|
|
255
|
+
${resultsText}
|
|
256
|
+
|
|
257
|
+
`;
|
|
258
|
+
if (hasImages) {
|
|
259
|
+
prompt += `请结合图片内容和搜索结果进行判决。注意核实图片中的信息是否与搜索结果一致。
|
|
260
|
+
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
prompt += `请判决。`;
|
|
264
|
+
return prompt;
|
|
265
|
+
}
|
|
266
|
+
__name(buildVerifyPrompt, "buildVerifyPrompt");
|
|
267
|
+
var IMAGE_DESCRIPTION_PROMPT = `请仔细观察这张图片,描述其中的主要内容。
|
|
268
|
+
|
|
269
|
+
重点关注:
|
|
270
|
+
1. 图片中是否包含可核查的声明或信息
|
|
271
|
+
2. 任何文字内容(标题、正文、水印等)
|
|
272
|
+
3. 图片展示的事件、人物或场景
|
|
273
|
+
4. 可能的来源或出处线索
|
|
274
|
+
|
|
275
|
+
请用简洁的中文描述,便于后续进行事实核查搜索。`;
|
|
276
|
+
var VERIFY_AGENT_SYSTEM_PROMPT_MULTIMODAL = `你是事实核查裁判。基于搜索证据和图片内容做出判决。
|
|
277
|
+
|
|
278
|
+
如果消息包含图片:
|
|
279
|
+
- 仔细分析图片内容
|
|
280
|
+
- 将图片中的信息与搜索证据对比
|
|
281
|
+
- 判断图片是否被篡改、断章取义或误导
|
|
282
|
+
|
|
283
|
+
判决类别:TRUE(真实)、FALSE(虚假)、PARTIALLY_TRUE(部分真实)、UNCERTAIN(无法确定)
|
|
284
|
+
|
|
285
|
+
输出JSON:
|
|
286
|
+
\`\`\`json
|
|
287
|
+
{"verdict":"TRUE/FALSE/PARTIALLY_TRUE/UNCERTAIN","confidence":0.0-1.0,"reasoning":"判决理由","sources":["来源"]}
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
原则:证据不足时判UNCERTAIN,重视权威来源,考虑时效性。`;
|
|
291
|
+
function formatVerificationOutput(content, searchResults, verdict, reasoning, sources, confidence, processingTime, format = "markdown") {
|
|
292
|
+
const verdictEmoji = {
|
|
293
|
+
true: "✅ 真实",
|
|
294
|
+
false: "❌ 虚假",
|
|
295
|
+
partially_true: "⚠️ 部分真实",
|
|
296
|
+
uncertain: "❓ 无法确定"
|
|
297
|
+
};
|
|
298
|
+
const confidenceValue = Math.round(confidence * 100);
|
|
299
|
+
const confidenceBar = "█".repeat(Math.round(confidence * 10)) + "░".repeat(10 - Math.round(confidence * 10));
|
|
300
|
+
if (format === "plain") {
|
|
301
|
+
let output2 = `🔍 事实核查结果
|
|
302
|
+
|
|
303
|
+
`;
|
|
304
|
+
output2 += `📋 待验证内容:
|
|
305
|
+
${content.substring(0, 200)}${content.length > 200 ? "..." : ""}
|
|
306
|
+
|
|
307
|
+
`;
|
|
308
|
+
output2 += `🤖 搜索发现:
|
|
309
|
+
`;
|
|
310
|
+
output2 += searchResults.map((r) => `• ${r.perspective}: ${r.findings.substring(0, 100)}...`).join("\n");
|
|
311
|
+
output2 += `
|
|
312
|
+
|
|
313
|
+
⚖️ 最终判决: ${verdictEmoji[verdict] || verdict}
|
|
314
|
+
`;
|
|
315
|
+
output2 += `📊 可信度: ${confidenceValue}%
|
|
316
|
+
|
|
317
|
+
`;
|
|
318
|
+
output2 += `📝 判决依据:
|
|
319
|
+
${reasoning}
|
|
320
|
+
`;
|
|
321
|
+
if (sources.length > 0) {
|
|
322
|
+
output2 += `
|
|
323
|
+
🔗 参考来源:
|
|
324
|
+
`;
|
|
325
|
+
output2 += sources.map((s) => `• ${s}`).join("\n");
|
|
326
|
+
output2 += `
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
output2 += `
|
|
330
|
+
⏱️ 处理耗时: ${(processingTime / 1e3).toFixed(1)}秒`;
|
|
331
|
+
return output2;
|
|
332
|
+
}
|
|
333
|
+
let output = `🔍 **事实核查结果**
|
|
334
|
+
|
|
335
|
+
📋 **待验证内容:**
|
|
336
|
+
> ${content.substring(0, 200)}${content.length > 200 ? "..." : ""}
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
🤖 **搜索Agent结果:**
|
|
341
|
+
${searchResults.map((r) => `• **${r.perspective}**: ${r.findings.substring(0, 100)}...`).join("\n")}
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
⚖️ **最终判决: ${verdictEmoji[verdict] || verdict}**
|
|
346
|
+
|
|
347
|
+
📊 **可信度:** ${confidenceBar} ${confidenceValue}%
|
|
348
|
+
|
|
349
|
+
📝 **判决依据:**
|
|
350
|
+
${reasoning}
|
|
351
|
+
`;
|
|
352
|
+
if (sources.length > 0) {
|
|
353
|
+
output += `
|
|
354
|
+
🔗 **参考来源:**
|
|
355
|
+
${sources.map((s) => `• ${s}`).join("\n")}
|
|
356
|
+
`;
|
|
357
|
+
}
|
|
358
|
+
output += `
|
|
359
|
+
⏱️ *处理耗时: ${(processingTime / 1e3).toFixed(1)}秒*`;
|
|
360
|
+
return output;
|
|
361
|
+
}
|
|
362
|
+
__name(formatVerificationOutput, "formatVerificationOutput");
|
|
363
|
+
var VERDICT_EMOJI = {
|
|
364
|
+
true: "✅ 真实",
|
|
365
|
+
false: "❌ 虚假",
|
|
366
|
+
partially_true: "⚠️ 部分真实",
|
|
367
|
+
uncertain: "❓ 无法确定"
|
|
368
|
+
};
|
|
369
|
+
function formatForwardMessages(content, searchResults, verdict, reasoning, sources, confidence, processingTime, maxSegmentLength = 500) {
|
|
370
|
+
const MAX_SOURCES = 5;
|
|
371
|
+
const confidenceValue = Math.round(confidence * 100);
|
|
372
|
+
const summary = `${VERDICT_EMOJI[verdict] || verdict} (${confidenceValue}%)
|
|
373
|
+
|
|
374
|
+
📋 ${content.substring(0, 100)}${content.length > 100 ? "..." : ""}
|
|
375
|
+
|
|
376
|
+
⏱️ ${(processingTime / 1e3).toFixed(1)}秒`;
|
|
377
|
+
const details = [];
|
|
378
|
+
const truncatedReasoning = reasoning.length > maxSegmentLength ? reasoning.substring(0, maxSegmentLength) + "..." : reasoning;
|
|
379
|
+
details.push(`📝 判决依据
|
|
380
|
+
|
|
381
|
+
${truncatedReasoning}`);
|
|
382
|
+
for (const r of searchResults) {
|
|
383
|
+
let cleanFindings = r.findings;
|
|
384
|
+
if (r.agentId === "chatluna-search") {
|
|
385
|
+
const summaryEndIndex = r.findings.indexOf("================================");
|
|
386
|
+
if (summaryEndIndex !== -1) {
|
|
387
|
+
cleanFindings = r.findings.substring(0, summaryEndIndex + 32) + "\n\n(搜索详情已在合并消息中省略,请查看判决依据)";
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const truncatedFindings = cleanFindings.length > maxSegmentLength ? cleanFindings.substring(0, maxSegmentLength) + "..." : cleanFindings;
|
|
391
|
+
details.push(`🔍 ${r.perspective}
|
|
392
|
+
|
|
393
|
+
${truncatedFindings}`);
|
|
394
|
+
}
|
|
395
|
+
if (sources.length > 0) {
|
|
396
|
+
const limitedSources = sources.slice(0, MAX_SOURCES);
|
|
397
|
+
const sourcesText = limitedSources.map((s) => `• ${s.substring(0, 100)}`).join("\n");
|
|
398
|
+
const suffix = sources.length > MAX_SOURCES ? `
|
|
399
|
+
... 及其他 ${sources.length - MAX_SOURCES} 个来源` : "";
|
|
400
|
+
details.push(`🔗 参考来源
|
|
401
|
+
|
|
402
|
+
${sourcesText}${suffix}`);
|
|
403
|
+
}
|
|
404
|
+
return { summary, details };
|
|
405
|
+
}
|
|
406
|
+
__name(formatForwardMessages, "formatForwardMessages");
|
|
407
|
+
|
|
408
|
+
// src/agents/verifyAgent.ts
|
|
409
|
+
var VerifyAgent = class {
|
|
410
|
+
constructor(ctx, config) {
|
|
411
|
+
this.ctx = ctx;
|
|
412
|
+
this.config = config;
|
|
413
|
+
this.chatluna = new ChatlunaAdapter(ctx, config);
|
|
414
|
+
this.logger = ctx.logger("isthattrue");
|
|
415
|
+
}
|
|
416
|
+
static {
|
|
417
|
+
__name(this, "VerifyAgent");
|
|
418
|
+
}
|
|
419
|
+
chatluna;
|
|
420
|
+
logger;
|
|
421
|
+
/**
|
|
422
|
+
* 执行验证判决
|
|
423
|
+
* @param originalContent 原始消息内容
|
|
424
|
+
* @param searchResults 搜索结果
|
|
425
|
+
* @param images 可选的图片 base64 列表(多模态验证)
|
|
426
|
+
*/
|
|
427
|
+
async verify(originalContent, searchResults, images) {
|
|
428
|
+
const startTime = Date.now();
|
|
429
|
+
const hasImages = images && images.length > 0;
|
|
430
|
+
this.logger.info(`开始综合验证...${hasImages ? " (包含图片)" : ""}`);
|
|
431
|
+
try {
|
|
432
|
+
const prompt = buildVerifyPrompt(
|
|
433
|
+
originalContent.text,
|
|
434
|
+
searchResults.map((r) => ({
|
|
435
|
+
perspective: r.perspective,
|
|
436
|
+
findings: r.findings,
|
|
437
|
+
sources: r.sources
|
|
438
|
+
})),
|
|
439
|
+
hasImages
|
|
440
|
+
// 传递是否有图片
|
|
441
|
+
);
|
|
442
|
+
const systemPrompt = hasImages ? VERIFY_AGENT_SYSTEM_PROMPT_MULTIMODAL : VERIFY_AGENT_SYSTEM_PROMPT;
|
|
443
|
+
const response = await this.chatluna.chatWithRetry(
|
|
444
|
+
{
|
|
445
|
+
model: this.config.mainModel,
|
|
446
|
+
message: prompt,
|
|
447
|
+
systemPrompt,
|
|
448
|
+
images
|
|
449
|
+
// 传递图片
|
|
450
|
+
},
|
|
451
|
+
this.config.maxRetries
|
|
452
|
+
);
|
|
453
|
+
const parsed = this.parseVerifyResponse(response.content);
|
|
454
|
+
const processingTime = Date.now() - startTime;
|
|
455
|
+
const result = {
|
|
456
|
+
originalContent,
|
|
457
|
+
searchResults,
|
|
458
|
+
verdict: parsed.verdict,
|
|
459
|
+
reasoning: parsed.reasoning,
|
|
460
|
+
sources: this.aggregateSources(searchResults, parsed.sources),
|
|
461
|
+
confidence: parsed.confidence,
|
|
462
|
+
processingTime
|
|
463
|
+
};
|
|
464
|
+
this.logger.info(`验证完成,判决: ${result.verdict},可信度: ${result.confidence}`);
|
|
465
|
+
return result;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
this.logger.error("验证失败:", error);
|
|
468
|
+
return {
|
|
469
|
+
originalContent,
|
|
470
|
+
searchResults,
|
|
471
|
+
verdict: "uncertain" /* UNCERTAIN */,
|
|
472
|
+
reasoning: `验证过程发生错误: ${error.message}`,
|
|
473
|
+
sources: this.aggregateSources(searchResults, []),
|
|
474
|
+
confidence: 0,
|
|
475
|
+
processingTime: Date.now() - startTime
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* 解析验证响应
|
|
481
|
+
*/
|
|
482
|
+
parseVerifyResponse(content) {
|
|
483
|
+
try {
|
|
484
|
+
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/);
|
|
485
|
+
let parsed;
|
|
486
|
+
if (jsonMatch) {
|
|
487
|
+
parsed = JSON.parse(jsonMatch[1]);
|
|
488
|
+
} else {
|
|
489
|
+
parsed = JSON.parse(content);
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
verdict: this.normalizeVerdict(parsed.verdict),
|
|
493
|
+
reasoning: parsed.reasoning || parsed.key_evidence || "无详细说明",
|
|
494
|
+
sources: parsed.sources || [],
|
|
495
|
+
confidence: parsed.confidence || 0.5
|
|
496
|
+
};
|
|
497
|
+
} catch {
|
|
498
|
+
return {
|
|
499
|
+
verdict: this.extractVerdictFromText(content),
|
|
500
|
+
reasoning: content,
|
|
501
|
+
sources: [],
|
|
502
|
+
confidence: 0.3
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* 标准化判决结果
|
|
508
|
+
*/
|
|
509
|
+
normalizeVerdict(verdict) {
|
|
510
|
+
const normalized = verdict?.toLowerCase()?.trim();
|
|
511
|
+
const mapping = {
|
|
512
|
+
"true": "true" /* TRUE */,
|
|
513
|
+
"真实": "true" /* TRUE */,
|
|
514
|
+
"正确": "true" /* TRUE */,
|
|
515
|
+
"false": "false" /* FALSE */,
|
|
516
|
+
"虚假": "false" /* FALSE */,
|
|
517
|
+
"错误": "false" /* FALSE */,
|
|
518
|
+
"partially_true": "partially_true" /* PARTIALLY_TRUE */,
|
|
519
|
+
"partial": "partially_true" /* PARTIALLY_TRUE */,
|
|
520
|
+
"部分真实": "partially_true" /* PARTIALLY_TRUE */,
|
|
521
|
+
"uncertain": "uncertain" /* UNCERTAIN */,
|
|
522
|
+
"不确定": "uncertain" /* UNCERTAIN */,
|
|
523
|
+
"无法确定": "uncertain" /* UNCERTAIN */
|
|
524
|
+
};
|
|
525
|
+
return mapping[normalized] || "uncertain" /* UNCERTAIN */;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* 从文本中提取判决
|
|
529
|
+
*/
|
|
530
|
+
extractVerdictFromText(text) {
|
|
531
|
+
const lower = text.toLowerCase();
|
|
532
|
+
if (lower.includes("虚假") || lower.includes("false") || lower.includes("错误")) {
|
|
533
|
+
return "false" /* FALSE */;
|
|
534
|
+
}
|
|
535
|
+
if (lower.includes("部分真实") || lower.includes("partially")) {
|
|
536
|
+
return "partially_true" /* PARTIALLY_TRUE */;
|
|
537
|
+
}
|
|
538
|
+
if (lower.includes("真实") || lower.includes("true") || lower.includes("正确")) {
|
|
539
|
+
return "true" /* TRUE */;
|
|
540
|
+
}
|
|
541
|
+
return "uncertain" /* UNCERTAIN */;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* 汇总所有来源
|
|
545
|
+
*/
|
|
546
|
+
aggregateSources(searchResults, verifySources) {
|
|
547
|
+
const allSources = /* @__PURE__ */ new Set();
|
|
548
|
+
for (const result of searchResults) {
|
|
549
|
+
for (const source of result.sources) {
|
|
550
|
+
allSources.add(source);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
for (const source of verifySources) {
|
|
554
|
+
allSources.add(source);
|
|
555
|
+
}
|
|
556
|
+
return [...allSources];
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/agents/subSearchAgent.ts
|
|
561
|
+
var SubSearchAgent = class {
|
|
562
|
+
constructor(ctx, config) {
|
|
563
|
+
this.ctx = ctx;
|
|
564
|
+
this.config = config;
|
|
565
|
+
this.chatluna = new ChatlunaAdapter(ctx, config);
|
|
566
|
+
this.logger = ctx.logger("isthattrue");
|
|
567
|
+
}
|
|
568
|
+
static {
|
|
569
|
+
__name(this, "SubSearchAgent");
|
|
570
|
+
}
|
|
571
|
+
chatluna;
|
|
572
|
+
logger;
|
|
573
|
+
/**
|
|
574
|
+
* 执行深度搜索
|
|
575
|
+
* @param claim 原始声明文本
|
|
576
|
+
*/
|
|
577
|
+
async deepSearch(claim) {
|
|
578
|
+
this.logger.info(`[SubSearchAgent] 开始深度搜索,模型: ${this.config.subSearchModel}`);
|
|
579
|
+
try {
|
|
580
|
+
const response = await this.chatluna.chatWithRetry(
|
|
581
|
+
{
|
|
582
|
+
model: this.config.subSearchModel,
|
|
583
|
+
message: buildSubSearchPrompt(claim),
|
|
584
|
+
systemPrompt: SUB_SEARCH_AGENT_SYSTEM_PROMPT,
|
|
585
|
+
enableSearch: true
|
|
586
|
+
},
|
|
587
|
+
this.config.maxRetries
|
|
588
|
+
);
|
|
589
|
+
const parsed = this.parseResponse(response.content);
|
|
590
|
+
return {
|
|
591
|
+
agentId: "grok-deep-search",
|
|
592
|
+
perspective: "Grok 深度搜索 (X/Twitter)",
|
|
593
|
+
findings: parsed.findings || response.content,
|
|
594
|
+
sources: parsed.sources || response.sources || [],
|
|
595
|
+
confidence: parsed.confidence || 0.8
|
|
596
|
+
};
|
|
597
|
+
} catch (error) {
|
|
598
|
+
this.logger.error("[SubSearchAgent] 搜索失败:", error);
|
|
599
|
+
return {
|
|
600
|
+
agentId: "grok-deep-search",
|
|
601
|
+
perspective: "Grok 深度搜索 (X/Twitter)",
|
|
602
|
+
findings: `深度搜索失败: ${error.message}`,
|
|
603
|
+
sources: [],
|
|
604
|
+
confidence: 0
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
parseResponse(content) {
|
|
609
|
+
try {
|
|
610
|
+
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/);
|
|
611
|
+
let parsed;
|
|
612
|
+
if (jsonMatch) {
|
|
613
|
+
parsed = JSON.parse(jsonMatch[1]);
|
|
614
|
+
} else {
|
|
615
|
+
parsed = JSON.parse(content);
|
|
616
|
+
}
|
|
617
|
+
return {
|
|
618
|
+
findings: parsed.findings,
|
|
619
|
+
sources: parsed.sources,
|
|
620
|
+
confidence: parsed.confidence
|
|
621
|
+
};
|
|
622
|
+
} catch {
|
|
623
|
+
return {};
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/services/chatlunaSearch.ts
|
|
629
|
+
var ChatlunaSearchAgent = class {
|
|
630
|
+
constructor(ctx, config) {
|
|
631
|
+
this.ctx = ctx;
|
|
632
|
+
this.config = config;
|
|
633
|
+
this.logger = ctx.logger("isthattrue");
|
|
634
|
+
this.chatluna = new ChatlunaAdapter(ctx, config);
|
|
635
|
+
this.initTool();
|
|
636
|
+
}
|
|
637
|
+
static {
|
|
638
|
+
__name(this, "ChatlunaSearchAgent");
|
|
639
|
+
}
|
|
640
|
+
logger;
|
|
641
|
+
// 存储 toolInfo 而不是 tool 实例,每次搜索时重新创建
|
|
642
|
+
toolInfo = null;
|
|
643
|
+
toolReady = false;
|
|
644
|
+
emptyEmbeddings = null;
|
|
645
|
+
chatluna;
|
|
646
|
+
async initTool() {
|
|
647
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
648
|
+
try {
|
|
649
|
+
try {
|
|
650
|
+
const inMemory = await import("koishi-plugin-chatluna/llm-core/model/in_memory");
|
|
651
|
+
this.emptyEmbeddings = inMemory.emptyEmbeddings;
|
|
652
|
+
this.logger.debug("[ChatlunaSearch] emptyEmbeddings 已导入");
|
|
653
|
+
} catch {
|
|
654
|
+
this.logger.debug("[ChatlunaSearch] 无法导入 emptyEmbeddings,将使用 null");
|
|
655
|
+
}
|
|
656
|
+
const chatluna = this.ctx.chatluna;
|
|
657
|
+
if (!chatluna?.platform) {
|
|
658
|
+
this.logger.warn("[ChatlunaSearch] chatluna.platform 不可用");
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const tools = chatluna.platform.getTools();
|
|
662
|
+
this.logger.debug(`[ChatlunaSearch] 可用工具列表: ${JSON.stringify(tools.value)}`);
|
|
663
|
+
if (tools.value && tools.value.includes("web_search")) {
|
|
664
|
+
this.toolInfo = chatluna.platform.getTool("web_search");
|
|
665
|
+
this.logger.debug(`[ChatlunaSearch] toolInfo: ${JSON.stringify(this.toolInfo ? Object.keys(this.toolInfo) : null)}`);
|
|
666
|
+
if (this.toolInfo && typeof this.toolInfo.createTool === "function") {
|
|
667
|
+
this.toolReady = true;
|
|
668
|
+
this.logger.info("[ChatlunaSearch] web_search 工具注册信息已获取");
|
|
669
|
+
} else {
|
|
670
|
+
this.logger.warn("[ChatlunaSearch] toolInfo 无效或没有 createTool 方法");
|
|
671
|
+
this.toolInfo = null;
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
this.logger.warn("[ChatlunaSearch] web_search 工具未注册,请确保已启用 chatluna-search-service");
|
|
675
|
+
}
|
|
676
|
+
} catch (error) {
|
|
677
|
+
this.logger.warn("[ChatlunaSearch] 初始化工具失败:", error);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* 创建搜索工具实例
|
|
682
|
+
*/
|
|
683
|
+
createSearchTool() {
|
|
684
|
+
if (!this.toolInfo) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
const tool = this.toolInfo.createTool({
|
|
689
|
+
embeddings: this.emptyEmbeddings,
|
|
690
|
+
summaryType: "speed"
|
|
691
|
+
});
|
|
692
|
+
this.logger.debug(`[ChatlunaSearch] 创建的 tool: name=${tool?.name}, type=${typeof tool}`);
|
|
693
|
+
this.logger.debug(`[ChatlunaSearch] tool.invoke: ${typeof tool?.invoke}`);
|
|
694
|
+
this.logger.debug(`[ChatlunaSearch] tool._call: ${typeof tool?._call}`);
|
|
695
|
+
return tool;
|
|
696
|
+
} catch (error) {
|
|
697
|
+
this.logger.error("[ChatlunaSearch] createTool 失败:", error);
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* 检查服务是否可用
|
|
703
|
+
*/
|
|
704
|
+
isAvailable() {
|
|
705
|
+
const enabled = this.config.enableChatlunaSearch !== false;
|
|
706
|
+
const hasModel = !!this.config.chatlunaSearchModel;
|
|
707
|
+
const hasChatluna = !!this.ctx.chatluna?.platform;
|
|
708
|
+
return enabled && hasModel && hasChatluna && this.toolReady;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 多样化搜索关键词
|
|
712
|
+
* 使用小模型生成多个不同角度的搜索关键词
|
|
713
|
+
*/
|
|
714
|
+
async diversifyQuery(query) {
|
|
715
|
+
const diversifyModel = this.config.chatlunaSearchDiversifyModel;
|
|
716
|
+
if (!diversifyModel) {
|
|
717
|
+
return [query];
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
this.logger.info("[ChatlunaSearch] 使用小模型多样化搜索关键词...");
|
|
721
|
+
const response = await this.chatluna.chatWithRetry({
|
|
722
|
+
model: diversifyModel,
|
|
723
|
+
systemPrompt: `你是一个搜索关键词优化专家。给定一个声明或问题,生成3个不同角度的搜索关键词,用于事实核查。
|
|
724
|
+
|
|
725
|
+
要求:
|
|
726
|
+
1. 关键词应该简洁有效,适合搜索引擎
|
|
727
|
+
2. 从不同角度切入:如正面验证、反面查证、相关背景
|
|
728
|
+
3. 每个关键词单独一行
|
|
729
|
+
4. 只输出关键词,不要编号或其他说明`,
|
|
730
|
+
message: `请为以下内容生成3个多样化的搜索关键词:
|
|
731
|
+
|
|
732
|
+
${query}`
|
|
733
|
+
}, this.config.maxRetries);
|
|
734
|
+
const keywords = response.content.split("\n").map((k) => k.trim()).filter((k) => k.length > 0 && k.length < 100);
|
|
735
|
+
if (keywords.length > 0) {
|
|
736
|
+
this.logger.info(`[ChatlunaSearch] 生成了 ${keywords.length} 个多样化关键词: ${keywords.join(" | ")}`);
|
|
737
|
+
return keywords.slice(0, 3);
|
|
738
|
+
}
|
|
739
|
+
} catch (error) {
|
|
740
|
+
this.logger.warn("[ChatlunaSearch] 关键词多样化失败,使用原始查询:", error);
|
|
741
|
+
}
|
|
742
|
+
return [query];
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* 执行搜索
|
|
746
|
+
*/
|
|
747
|
+
async search(query) {
|
|
748
|
+
const startTime = Date.now();
|
|
749
|
+
const modelName = this.config.chatlunaSearchModel;
|
|
750
|
+
const shortModelName = modelName.includes("/") ? modelName.split("/").pop() : modelName;
|
|
751
|
+
this.logger.info(`[ChatlunaSearch] 开始搜索,模型: ${modelName}`);
|
|
752
|
+
try {
|
|
753
|
+
const chatluna = this.ctx.chatluna;
|
|
754
|
+
if (!this.toolReady || !this.toolInfo) {
|
|
755
|
+
this.logger.info("[ChatlunaSearch] 工具未就绪,尝试重新获取...");
|
|
756
|
+
const tools = chatluna.platform.getTools();
|
|
757
|
+
if (tools.value && tools.value.includes("web_search")) {
|
|
758
|
+
this.toolInfo = chatluna.platform.getTool("web_search");
|
|
759
|
+
if (this.toolInfo && typeof this.toolInfo.createTool === "function") {
|
|
760
|
+
this.toolReady = true;
|
|
761
|
+
this.logger.info("[ChatlunaSearch] 工具重新获取成功");
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (!this.toolReady || !this.toolInfo) {
|
|
766
|
+
throw new Error("web_search 工具未就绪,请确保已启用 chatluna-search-service 并配置了搜索引擎");
|
|
767
|
+
}
|
|
768
|
+
const queries = await this.diversifyQuery(query);
|
|
769
|
+
this.logger.info(`[ChatlunaSearch] 将并行执行 ${queries.length} 次搜索`);
|
|
770
|
+
const searchPromises = queries.map(async (q) => {
|
|
771
|
+
const searchTool = this.createSearchTool();
|
|
772
|
+
if (!searchTool) {
|
|
773
|
+
this.logger.warn(`[ChatlunaSearch] 关键词 "${q}" 创建搜索工具失败`);
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
try {
|
|
777
|
+
this.logger.info(`[ChatlunaSearch] 正在搜索关键词: ${q}`);
|
|
778
|
+
let searchResult;
|
|
779
|
+
if (typeof searchTool.invoke === "function") {
|
|
780
|
+
searchResult = await searchTool.invoke(q);
|
|
781
|
+
} else if (typeof searchTool._call === "function") {
|
|
782
|
+
searchResult = await searchTool._call(q, void 0, {});
|
|
783
|
+
} else {
|
|
784
|
+
throw new Error("搜索工具没有可用的调用方法");
|
|
785
|
+
}
|
|
786
|
+
let searchData = [];
|
|
787
|
+
if (typeof searchResult === "string") {
|
|
788
|
+
try {
|
|
789
|
+
searchData = JSON.parse(searchResult);
|
|
790
|
+
} catch {
|
|
791
|
+
searchData = [{ description: searchResult }];
|
|
792
|
+
}
|
|
793
|
+
} else if (Array.isArray(searchResult)) {
|
|
794
|
+
searchData = searchResult;
|
|
795
|
+
}
|
|
796
|
+
return searchData.map((item) => ({ ...item, searchQuery: q }));
|
|
797
|
+
} catch (err) {
|
|
798
|
+
this.logger.warn(`[ChatlunaSearch] 关键词 "${q}" 搜索失败:`, err);
|
|
799
|
+
return [];
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
const searchResultsArray = await Promise.all(searchPromises);
|
|
803
|
+
const allSearchData = [];
|
|
804
|
+
const allSources = [];
|
|
805
|
+
for (const results of searchResultsArray) {
|
|
806
|
+
if (Array.isArray(results)) {
|
|
807
|
+
for (const item of results) {
|
|
808
|
+
allSearchData.push(item);
|
|
809
|
+
if (item.url && !allSources.includes(item.url)) {
|
|
810
|
+
allSources.push(item.url);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const totalResults = allSearchData.length;
|
|
816
|
+
this.logger.info(`[ChatlunaSearch] 共获取 ${totalResults} 条搜索结果,来自 ${queries.length} 个关键词`);
|
|
817
|
+
const formattedResults = allSearchData.length > 0 ? allSearchData.map(
|
|
818
|
+
(item, i) => `[${i + 1}] ${item.title || "未知标题"}
|
|
819
|
+
来源: ${item.url || "未知"}
|
|
820
|
+
${item.description || item.content || ""}`
|
|
821
|
+
).join("\n\n---\n\n") : "未找到搜索结果";
|
|
822
|
+
const summary = `=== Chatluna Search 统计 ===
|
|
823
|
+
搜索关键词: ${queries.join(" | ")}
|
|
824
|
+
返回结果数: ${totalResults}
|
|
825
|
+
来源数: ${allSources.length}
|
|
826
|
+
================================
|
|
827
|
+
|
|
828
|
+
`;
|
|
829
|
+
const elapsed = Date.now() - startTime;
|
|
830
|
+
this.logger.info(`[ChatlunaSearch] 搜索完成,耗时 ${elapsed}ms,共 ${totalResults} 条结果`);
|
|
831
|
+
return {
|
|
832
|
+
agentId: "chatluna-search",
|
|
833
|
+
perspective: `Chatluna Search (${shortModelName})`,
|
|
834
|
+
findings: summary + formattedResults,
|
|
835
|
+
sources: allSources,
|
|
836
|
+
confidence: totalResults > 0 ? Math.min(0.5 + totalResults * 0.05, 0.9) : 0.3
|
|
837
|
+
};
|
|
838
|
+
} catch (error) {
|
|
839
|
+
this.logger.error("[ChatlunaSearch] 搜索失败:", error);
|
|
840
|
+
return {
|
|
841
|
+
agentId: "chatluna-search",
|
|
842
|
+
perspective: `Chatluna Search (${shortModelName})`,
|
|
843
|
+
findings: `搜索失败: ${error.message}`,
|
|
844
|
+
sources: [],
|
|
845
|
+
confidence: 0
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/services/messageParser.ts
|
|
852
|
+
var import_koishi = require("koishi");
|
|
853
|
+
var MessageParser = class {
|
|
854
|
+
constructor(ctx) {
|
|
855
|
+
this.ctx = ctx;
|
|
856
|
+
}
|
|
857
|
+
static {
|
|
858
|
+
__name(this, "MessageParser");
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* 从会话中提取引用消息的内容
|
|
862
|
+
*/
|
|
863
|
+
async parseQuotedMessage(session) {
|
|
864
|
+
const result = {
|
|
865
|
+
text: "",
|
|
866
|
+
images: [],
|
|
867
|
+
hasQuote: false
|
|
868
|
+
};
|
|
869
|
+
const quote = session.quote;
|
|
870
|
+
if (!quote) {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
result.hasQuote = true;
|
|
874
|
+
const elements = quote.elements || [];
|
|
875
|
+
for (const element of elements) {
|
|
876
|
+
if (element.type === "text") {
|
|
877
|
+
result.text += element.attrs?.content || "";
|
|
878
|
+
} else if (element.type === "img" || element.type === "image") {
|
|
879
|
+
const src = element.attrs?.src || element.attrs?.url;
|
|
880
|
+
if (src) {
|
|
881
|
+
result.images.push(src);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (elements.length === 0 && quote.content) {
|
|
886
|
+
const parsed = this.parseContent(quote.content);
|
|
887
|
+
result.text = parsed.text;
|
|
888
|
+
result.images = parsed.images;
|
|
889
|
+
}
|
|
890
|
+
return result;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* 从整个会话中提取可验证内容
|
|
894
|
+
* 同时解析引用消息和当前消息,合并内容
|
|
895
|
+
*/
|
|
896
|
+
async parseSession(session) {
|
|
897
|
+
const result = {
|
|
898
|
+
text: "",
|
|
899
|
+
images: [],
|
|
900
|
+
hasQuote: false
|
|
901
|
+
};
|
|
902
|
+
const quoted = await this.parseQuotedMessage(session);
|
|
903
|
+
if (quoted) {
|
|
904
|
+
result.hasQuote = true;
|
|
905
|
+
result.images = [...quoted.images];
|
|
906
|
+
if (quoted.text.trim()) {
|
|
907
|
+
result.text = quoted.text;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const elements = session.elements || [];
|
|
911
|
+
this.ctx.logger("isthattrue").debug("Parsing session elements:", JSON.stringify(elements));
|
|
912
|
+
let currentText = "";
|
|
913
|
+
for (let i = 0; i < elements.length; i++) {
|
|
914
|
+
const element = elements[i];
|
|
915
|
+
if (element.type === "text") {
|
|
916
|
+
let content = element.attrs?.content || "";
|
|
917
|
+
if (i === 0) {
|
|
918
|
+
content = content.replace(/^[^\s]+\s*/, "");
|
|
919
|
+
}
|
|
920
|
+
currentText += content;
|
|
921
|
+
} else if (element.type === "img" || element.type === "image") {
|
|
922
|
+
const src = element.attrs?.src || element.attrs?.url;
|
|
923
|
+
if (src && !result.images.includes(src)) {
|
|
924
|
+
result.images.push(src);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
currentText = currentText.trim();
|
|
929
|
+
if (currentText) {
|
|
930
|
+
if (result.text) {
|
|
931
|
+
result.text = `${result.text}
|
|
932
|
+
|
|
933
|
+
[用户评论]: ${currentText}`;
|
|
934
|
+
} else {
|
|
935
|
+
result.text = currentText;
|
|
936
|
+
}
|
|
937
|
+
this.ctx.logger("isthattrue").info(`用户附加文字: ${currentText}`);
|
|
938
|
+
}
|
|
939
|
+
if (result.text.trim() || result.images.length > 0) {
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* 解析消息内容字符串
|
|
946
|
+
*/
|
|
947
|
+
parseContent(content) {
|
|
948
|
+
const text = [];
|
|
949
|
+
const images = [];
|
|
950
|
+
try {
|
|
951
|
+
const elements = import_koishi.h.parse(content);
|
|
952
|
+
for (const el of elements) {
|
|
953
|
+
if (el.type === "text") {
|
|
954
|
+
text.push(el.attrs?.content || String(el));
|
|
955
|
+
} else if (el.type === "img" || el.type === "image") {
|
|
956
|
+
const src = el.attrs?.src || el.attrs?.url;
|
|
957
|
+
if (src) {
|
|
958
|
+
images.push(src);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
} catch {
|
|
963
|
+
text.push(content);
|
|
964
|
+
}
|
|
965
|
+
return {
|
|
966
|
+
text: text.join(" ").trim(),
|
|
967
|
+
images
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* 获取图片的base64编码
|
|
972
|
+
*/
|
|
973
|
+
async imageToBase64(url) {
|
|
974
|
+
try {
|
|
975
|
+
if (url.startsWith("data:image")) {
|
|
976
|
+
return url.split(",")[1] || url;
|
|
977
|
+
}
|
|
978
|
+
if (url.startsWith("file://")) {
|
|
979
|
+
this.ctx.logger("isthattrue").warn("本地文件暂不支持:", url);
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
const response = await this.ctx.http.get(url, {
|
|
983
|
+
responseType: "arraybuffer"
|
|
984
|
+
});
|
|
985
|
+
const buffer = Buffer.from(response);
|
|
986
|
+
return buffer.toString("base64");
|
|
987
|
+
} catch (error) {
|
|
988
|
+
this.ctx.logger("isthattrue").error("图片转换失败:", error);
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* 准备消息内容用于LLM处理
|
|
994
|
+
* 将图片转换为base64,合并文本
|
|
995
|
+
*/
|
|
996
|
+
async prepareForLLM(content) {
|
|
997
|
+
const imageBase64List = [];
|
|
998
|
+
for (const imageUrl of content.images) {
|
|
999
|
+
const base64 = await this.imageToBase64(imageUrl);
|
|
1000
|
+
if (base64) {
|
|
1001
|
+
imageBase64List.push(base64);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
text: content.text,
|
|
1006
|
+
imageBase64List
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
// src/agents/mainAgent.ts
|
|
1012
|
+
var MainAgent = class {
|
|
1013
|
+
constructor(ctx, config) {
|
|
1014
|
+
this.ctx = ctx;
|
|
1015
|
+
this.config = config;
|
|
1016
|
+
this.subSearchAgent = new SubSearchAgent(ctx, config);
|
|
1017
|
+
this.chatlunaSearchAgent = new ChatlunaSearchAgent(ctx, config);
|
|
1018
|
+
this.verifyAgent = new VerifyAgent(ctx, config);
|
|
1019
|
+
this.chatluna = new ChatlunaAdapter(ctx, config);
|
|
1020
|
+
this.messageParser = new MessageParser(ctx);
|
|
1021
|
+
this.logger = ctx.logger("isthattrue");
|
|
1022
|
+
}
|
|
1023
|
+
static {
|
|
1024
|
+
__name(this, "MainAgent");
|
|
1025
|
+
}
|
|
1026
|
+
subSearchAgent;
|
|
1027
|
+
chatlunaSearchAgent;
|
|
1028
|
+
verifyAgent;
|
|
1029
|
+
chatluna;
|
|
1030
|
+
messageParser;
|
|
1031
|
+
logger;
|
|
1032
|
+
/**
|
|
1033
|
+
* 执行完整的核查流程
|
|
1034
|
+
*/
|
|
1035
|
+
async verify(content) {
|
|
1036
|
+
const startTime = Date.now();
|
|
1037
|
+
this.logger.info("开始主控 Agent 核查流程...");
|
|
1038
|
+
try {
|
|
1039
|
+
let imageBase64List = [];
|
|
1040
|
+
if (content.images.length > 0) {
|
|
1041
|
+
this.logger.info(`[Phase 0] 处理 ${content.images.length} 张图片...`);
|
|
1042
|
+
const prepared = await this.messageParser.prepareForLLM(content);
|
|
1043
|
+
imageBase64List = prepared.imageBase64List;
|
|
1044
|
+
this.logger.info(`[Phase 0] 成功转换 ${imageBase64List.length} 张图片为 base64`);
|
|
1045
|
+
}
|
|
1046
|
+
let searchText = content.text;
|
|
1047
|
+
if (!content.text.trim() && imageBase64List.length > 0) {
|
|
1048
|
+
this.logger.info("[Phase 0] 纯图片输入,提取图片描述...");
|
|
1049
|
+
searchText = await this.extractImageDescription(imageBase64List);
|
|
1050
|
+
this.logger.info(`[Phase 0] 图片描述:${searchText.substring(0, 100)}...`);
|
|
1051
|
+
}
|
|
1052
|
+
this.logger.info("[Phase 1+2] 并行搜索中 (Chatluna + Grok)...");
|
|
1053
|
+
const searchPromises = [];
|
|
1054
|
+
if (this.chatlunaSearchAgent.isAvailable()) {
|
|
1055
|
+
searchPromises.push(
|
|
1056
|
+
this.withTimeout(
|
|
1057
|
+
this.chatlunaSearchAgent.search(searchText),
|
|
1058
|
+
this.config.timeout,
|
|
1059
|
+
"ChatlunaSearch"
|
|
1060
|
+
)
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
searchPromises.push(
|
|
1064
|
+
this.withTimeout(
|
|
1065
|
+
this.subSearchAgent.deepSearch(searchText),
|
|
1066
|
+
this.config.timeout,
|
|
1067
|
+
"GrokSearch"
|
|
1068
|
+
)
|
|
1069
|
+
);
|
|
1070
|
+
const results = await Promise.allSettled(searchPromises);
|
|
1071
|
+
const searchResults = results.filter((r) => r.status === "fulfilled" && r.value !== null).map((r) => r.value);
|
|
1072
|
+
results.forEach((r, i) => {
|
|
1073
|
+
if (r.status === "rejected") {
|
|
1074
|
+
this.logger.warn(`搜索 ${i === 0 ? "Chatluna" : "Grok"} 失败: ${r.reason}`);
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
this.logger.info(`[Phase 1+2] 搜索完成,成功 ${searchResults.length} 个`);
|
|
1078
|
+
if (searchResults.length === 0) {
|
|
1079
|
+
return {
|
|
1080
|
+
originalContent: content,
|
|
1081
|
+
searchResults: [],
|
|
1082
|
+
verdict: "uncertain" /* UNCERTAIN */,
|
|
1083
|
+
reasoning: "所有搜索都失败了,无法验证",
|
|
1084
|
+
sources: [],
|
|
1085
|
+
confidence: 0,
|
|
1086
|
+
processingTime: Date.now() - startTime
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
this.logger.info("[Phase 3] Gemini 判决中...");
|
|
1090
|
+
const finalResult = await this.verifyAgent.verify(
|
|
1091
|
+
content,
|
|
1092
|
+
searchResults,
|
|
1093
|
+
imageBase64List
|
|
1094
|
+
);
|
|
1095
|
+
return {
|
|
1096
|
+
...finalResult,
|
|
1097
|
+
processingTime: Date.now() - startTime
|
|
1098
|
+
};
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
this.logger.error("主控 Agent 流程出错:", error);
|
|
1101
|
+
return {
|
|
1102
|
+
originalContent: content,
|
|
1103
|
+
searchResults: [],
|
|
1104
|
+
verdict: "uncertain" /* UNCERTAIN */,
|
|
1105
|
+
reasoning: `流程执行失败: ${error.message}`,
|
|
1106
|
+
sources: [],
|
|
1107
|
+
confidence: 0,
|
|
1108
|
+
processingTime: Date.now() - startTime
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* 带超时的 Promise 包装
|
|
1114
|
+
*/
|
|
1115
|
+
async withTimeout(promise, timeout, name2) {
|
|
1116
|
+
try {
|
|
1117
|
+
return await Promise.race([
|
|
1118
|
+
promise,
|
|
1119
|
+
new Promise(
|
|
1120
|
+
(_, reject) => setTimeout(() => reject(new Error(`${name2} 超时`)), timeout)
|
|
1121
|
+
)
|
|
1122
|
+
]);
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
this.logger.warn(`[${name2}] 失败: ${error.message}`);
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* 从图片中提取描述(用于纯图片输入场景)
|
|
1130
|
+
*/
|
|
1131
|
+
async extractImageDescription(images) {
|
|
1132
|
+
try {
|
|
1133
|
+
const response = await this.chatluna.chat({
|
|
1134
|
+
model: this.config.mainModel,
|
|
1135
|
+
message: IMAGE_DESCRIPTION_PROMPT,
|
|
1136
|
+
images
|
|
1137
|
+
});
|
|
1138
|
+
return response.content;
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
this.logger.error("图片描述提取失败:", error);
|
|
1141
|
+
return "图片内容需要验证";
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
// src/services/tofTool.ts
|
|
1147
|
+
var import_tools = require("@langchain/core/tools");
|
|
1148
|
+
var TofTool = class extends import_tools.Tool {
|
|
1149
|
+
constructor(ctx, config, toolName, toolDescription) {
|
|
1150
|
+
super();
|
|
1151
|
+
this.ctx = ctx;
|
|
1152
|
+
this.config = config;
|
|
1153
|
+
this.name = toolName;
|
|
1154
|
+
this.description = toolDescription;
|
|
1155
|
+
this.logger = ctx.logger("isthattrue");
|
|
1156
|
+
}
|
|
1157
|
+
static {
|
|
1158
|
+
__name(this, "TofTool");
|
|
1159
|
+
}
|
|
1160
|
+
name;
|
|
1161
|
+
description;
|
|
1162
|
+
logger;
|
|
1163
|
+
async _call(input) {
|
|
1164
|
+
const text = (input || "").trim();
|
|
1165
|
+
if (!text) {
|
|
1166
|
+
return "请输入需要核查的文本内容。";
|
|
1167
|
+
}
|
|
1168
|
+
try {
|
|
1169
|
+
const mainAgent = new MainAgent(this.ctx, this.config);
|
|
1170
|
+
const result = await mainAgent.verify({ text, images: [], hasQuote: false });
|
|
1171
|
+
const verdictLabel = {
|
|
1172
|
+
["true" /* TRUE */]: "真实",
|
|
1173
|
+
["false" /* FALSE */]: "虚假",
|
|
1174
|
+
["partially_true" /* PARTIALLY_TRUE */]: "部分真实",
|
|
1175
|
+
["uncertain" /* UNCERTAIN */]: "无法确定"
|
|
1176
|
+
};
|
|
1177
|
+
const confidence = Math.round(result.confidence * 100);
|
|
1178
|
+
const reasoning = result.reasoning?.trim() || "无";
|
|
1179
|
+
const sources = result.sources?.length ? result.sources.slice(0, 8).map((s) => `- ${s}`).join("\n") : "- 无";
|
|
1180
|
+
return [
|
|
1181
|
+
`[tof] 判定: ${verdictLabel[result.verdict]} (${confidence}%)`,
|
|
1182
|
+
"",
|
|
1183
|
+
"[reasoning]",
|
|
1184
|
+
reasoning,
|
|
1185
|
+
"",
|
|
1186
|
+
"[sources]",
|
|
1187
|
+
sources
|
|
1188
|
+
].join("\n");
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
this.logger.error("[tof-tool] 核查失败:", error);
|
|
1191
|
+
return `tof 工具执行失败: ${error.message}`;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
function registerTofTool(ctx, config) {
|
|
1196
|
+
const logger = ctx.logger("isthattrue");
|
|
1197
|
+
const chatluna = ctx.chatluna;
|
|
1198
|
+
if (!chatluna?.platform?.registerTool) {
|
|
1199
|
+
logger.warn("[tof-tool] chatluna.platform.registerTool 不可用,跳过 tof 工具注册");
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
const toolName = "tof";
|
|
1203
|
+
const toolDescription = "用于事实核查。输入待验证的文本,返回结论、置信度、依据和来源链接。";
|
|
1204
|
+
ctx.effect(() => {
|
|
1205
|
+
logger.info("[tof-tool] 注册 ChatLuna 工具: tof");
|
|
1206
|
+
const dispose = chatluna.platform.registerTool(toolName, {
|
|
1207
|
+
createTool() {
|
|
1208
|
+
return new TofTool(ctx, config, toolName, toolDescription);
|
|
1209
|
+
},
|
|
1210
|
+
selector() {
|
|
1211
|
+
return true;
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
return () => {
|
|
1215
|
+
if (typeof dispose === "function") {
|
|
1216
|
+
dispose();
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
__name(registerTofTool, "registerTofTool");
|
|
1222
|
+
|
|
1223
|
+
// src/config.ts
|
|
1224
|
+
var import_koishi2 = require("koishi");
|
|
1225
|
+
var Config = import_koishi2.Schema.intersect([
|
|
1226
|
+
import_koishi2.Schema.object({
|
|
1227
|
+
mainModel: import_koishi2.Schema.dynamic("model").default("google/gemini-3-flash").description("主控 Agent 模型 (用于编排和最终判决,推荐 Gemini-3-Flash)"),
|
|
1228
|
+
subSearchModel: import_koishi2.Schema.dynamic("model").default("x-ai/grok-4-1").description("子搜索 Agent 模型 (用于深度搜索,推荐 Grok-4-1)")
|
|
1229
|
+
}).description("模型配置"),
|
|
1230
|
+
import_koishi2.Schema.object({
|
|
1231
|
+
chatlunaSearchModel: import_koishi2.Schema.dynamic("model").default("").description("Chatluna Search 使用的模型 (可选,用于调用 chatluna-search-service)"),
|
|
1232
|
+
enableChatlunaSearch: import_koishi2.Schema.boolean().default(true).description("启用 Chatluna 搜索集成"),
|
|
1233
|
+
chatlunaSearchDiversifyModel: import_koishi2.Schema.dynamic("model").default("").description("搜索关键词多样化模型 (可选,推荐 Gemini 2.5 Flash Lite)")
|
|
1234
|
+
}).description("搜索配置"),
|
|
1235
|
+
import_koishi2.Schema.object({
|
|
1236
|
+
timeout: import_koishi2.Schema.number().min(1e4).max(3e5).default(6e4).description("单次请求超时时间(毫秒)"),
|
|
1237
|
+
maxRetries: import_koishi2.Schema.number().min(0).max(5).default(2).description("失败重试次数")
|
|
1238
|
+
}).description("Agent配置"),
|
|
1239
|
+
import_koishi2.Schema.object({
|
|
1240
|
+
verbose: import_koishi2.Schema.boolean().default(false).description("显示详细验证过程 (进度提示)"),
|
|
1241
|
+
outputFormat: import_koishi2.Schema.union([
|
|
1242
|
+
import_koishi2.Schema.const("auto").description("自动 (QQ使用纯文本)"),
|
|
1243
|
+
import_koishi2.Schema.const("markdown").description("Markdown"),
|
|
1244
|
+
import_koishi2.Schema.const("plain").description("纯文本")
|
|
1245
|
+
]).default("auto").description("输出格式"),
|
|
1246
|
+
useForwardMessage: import_koishi2.Schema.boolean().default(true).description("使用合并转发消息展示详情 (仅支持QQ)"),
|
|
1247
|
+
forwardMaxNodes: import_koishi2.Schema.number().min(0).max(99).default(8).description("合并转发最大节点数,超过则回退普通消息(0 表示直接回退)"),
|
|
1248
|
+
forwardMaxTotalChars: import_koishi2.Schema.number().min(0).max(2e4).default(3e3).description("合并转发总字符数上限,超过则回退普通消息(0 表示直接回退)"),
|
|
1249
|
+
forwardMaxSegmentChars: import_koishi2.Schema.number().min(50).max(2e3).default(500).description("合并转发单节点字符数上限"),
|
|
1250
|
+
bypassProxy: import_koishi2.Schema.boolean().default(false).description("是否绕过系统代理"),
|
|
1251
|
+
logLLMDetails: import_koishi2.Schema.boolean().default(false).description("是否打印 LLM 请求体和响应详情 (Debug用)")
|
|
1252
|
+
}).description("其他设置")
|
|
1253
|
+
]);
|
|
1254
|
+
|
|
1255
|
+
// src/index.ts
|
|
1256
|
+
var name = "isthattrue";
|
|
1257
|
+
var inject = ["chatluna"];
|
|
1258
|
+
var usage = `
|
|
1259
|
+
## 事实核查插件
|
|
1260
|
+
|
|
1261
|
+
使用多Agent架构对消息进行事实核查验证。
|
|
1262
|
+
|
|
1263
|
+
### 使用方法
|
|
1264
|
+
|
|
1265
|
+
1. 引用一条需要验证的消息
|
|
1266
|
+
2. 发送 \`tof\` 指令
|
|
1267
|
+
3. 等待验证结果
|
|
1268
|
+
|
|
1269
|
+
### 工作流程
|
|
1270
|
+
|
|
1271
|
+
1. **解析阶段**: 提取引用消息中的文本和图片
|
|
1272
|
+
2. **搜索阶段**: 多个Agent并行从不同角度搜索信息
|
|
1273
|
+
3. **验证阶段**: 综合搜索结果,由低幻觉率LLM做出判决
|
|
1274
|
+
|
|
1275
|
+
### 判决类别
|
|
1276
|
+
|
|
1277
|
+
- ✅ **真实**: 有充分可靠证据支持
|
|
1278
|
+
- ❌ **虚假**: 有充分可靠证据反驳
|
|
1279
|
+
- ⚠️ **部分真实**: 声明中部分内容属实
|
|
1280
|
+
- ❓ **无法确定**: 证据不足或相互矛盾
|
|
1281
|
+
`;
|
|
1282
|
+
function apply(ctx, config) {
|
|
1283
|
+
const logger = ctx.logger("isthattrue");
|
|
1284
|
+
const messageParser = new MessageParser(ctx);
|
|
1285
|
+
registerTofTool(ctx, config);
|
|
1286
|
+
const mainAgent = new MainAgent(ctx, config);
|
|
1287
|
+
ctx.command("tof", "验证消息的真实性").alias("真假").alias("事实核查").alias("factcheck").option("verbose", "-v 显示详细过程").action(async ({ session, options }) => {
|
|
1288
|
+
logger.info("tof 命令被触发");
|
|
1289
|
+
if (!session) {
|
|
1290
|
+
logger.warn("session 为空");
|
|
1291
|
+
return "无法获取会话信息";
|
|
1292
|
+
}
|
|
1293
|
+
logger.info(`用户 ${session.userId} 在 ${session.channelId} 触发 tof 命令`);
|
|
1294
|
+
logger.debug("Session elements:", JSON.stringify(session.elements));
|
|
1295
|
+
const verbose = options?.verbose ?? config.verbose;
|
|
1296
|
+
const format = config.outputFormat === "auto" ? session.platform === "qq" ? "plain" : "markdown" : config.outputFormat;
|
|
1297
|
+
const chatluna = new ChatlunaAdapter(ctx, config);
|
|
1298
|
+
if (!chatluna.isAvailable()) {
|
|
1299
|
+
return "❌ Chatluna 服务不可用,请确保已安装并启用 koishi-plugin-chatluna";
|
|
1300
|
+
}
|
|
1301
|
+
const content = await messageParser.parseSession(session);
|
|
1302
|
+
if (!content || !content.text && content.images.length === 0) {
|
|
1303
|
+
return "❌ 请提供需要验证的内容\n\n使用方法:\n1. 引用一条消息后发送 tof\n2. 直接发送 tof [文本或图片]";
|
|
1304
|
+
}
|
|
1305
|
+
if (verbose) {
|
|
1306
|
+
await session.send("🔍 正在验证消息真实性,请稍候...");
|
|
1307
|
+
}
|
|
1308
|
+
try {
|
|
1309
|
+
if (content.images.length > 0 && verbose) {
|
|
1310
|
+
await session.send("📷 正在处理图片内容...");
|
|
1311
|
+
}
|
|
1312
|
+
const result = await mainAgent.verify(content);
|
|
1313
|
+
const textToDisplay = content.text.trim() || "[图片内容]";
|
|
1314
|
+
const searchResultsForOutput = result.searchResults.map((r) => ({
|
|
1315
|
+
agentId: r.agentId,
|
|
1316
|
+
perspective: r.perspective,
|
|
1317
|
+
findings: r.findings
|
|
1318
|
+
}));
|
|
1319
|
+
const useForward = config.useForwardMessage && session.platform === "onebot";
|
|
1320
|
+
if (useForward) {
|
|
1321
|
+
const { summary, details } = formatForwardMessages(
|
|
1322
|
+
textToDisplay,
|
|
1323
|
+
searchResultsForOutput,
|
|
1324
|
+
result.verdict,
|
|
1325
|
+
result.reasoning,
|
|
1326
|
+
result.sources,
|
|
1327
|
+
result.confidence,
|
|
1328
|
+
result.processingTime,
|
|
1329
|
+
config.forwardMaxSegmentChars
|
|
1330
|
+
);
|
|
1331
|
+
const maxNodes = config.forwardMaxNodes ?? 8;
|
|
1332
|
+
const maxTotalChars = config.forwardMaxTotalChars ?? 3e3;
|
|
1333
|
+
const totalChars = details.reduce((sum, detail) => sum + detail.length, 0);
|
|
1334
|
+
if (maxNodes <= 0 || maxTotalChars <= 0 || details.length > maxNodes || totalChars > maxTotalChars) {
|
|
1335
|
+
logger.warn(`合并转发内容过长,回退普通消息: nodes=${details.length}/${maxNodes}, chars=${totalChars}/${maxTotalChars}`);
|
|
1336
|
+
const output2 = formatVerificationOutput(
|
|
1337
|
+
textToDisplay,
|
|
1338
|
+
searchResultsForOutput,
|
|
1339
|
+
result.verdict,
|
|
1340
|
+
result.reasoning,
|
|
1341
|
+
result.sources,
|
|
1342
|
+
result.confidence,
|
|
1343
|
+
result.processingTime,
|
|
1344
|
+
format
|
|
1345
|
+
);
|
|
1346
|
+
return output2;
|
|
1347
|
+
}
|
|
1348
|
+
const forwardNodes = details.map(
|
|
1349
|
+
(detail) => (0, import_koishi3.h)("message", { nickname: "事实核查", userId: session.selfId }, detail)
|
|
1350
|
+
);
|
|
1351
|
+
let summarySent = false;
|
|
1352
|
+
try {
|
|
1353
|
+
await session.send(summary);
|
|
1354
|
+
summarySent = true;
|
|
1355
|
+
} catch (sendSummaryError) {
|
|
1356
|
+
logger.warn("发送摘要失败,将尝试回退由 Koishi 发送:", sendSummaryError);
|
|
1357
|
+
}
|
|
1358
|
+
try {
|
|
1359
|
+
await session.send((0, import_koishi3.h)("message", { forward: true }, forwardNodes));
|
|
1360
|
+
} catch (forwardError) {
|
|
1361
|
+
logger.warn("合并转发发送失败,回退到普通消息:", forwardError);
|
|
1362
|
+
for (const detail of details) {
|
|
1363
|
+
try {
|
|
1364
|
+
await session.send(detail);
|
|
1365
|
+
} catch (detailError) {
|
|
1366
|
+
logger.warn("回退详情发送失败,已忽略:", detailError);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
if (!summarySent) {
|
|
1370
|
+
return summary;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const output = formatVerificationOutput(
|
|
1376
|
+
textToDisplay,
|
|
1377
|
+
searchResultsForOutput,
|
|
1378
|
+
result.verdict,
|
|
1379
|
+
result.reasoning,
|
|
1380
|
+
result.sources,
|
|
1381
|
+
result.confidence,
|
|
1382
|
+
result.processingTime,
|
|
1383
|
+
format
|
|
1384
|
+
);
|
|
1385
|
+
return output;
|
|
1386
|
+
} catch (error) {
|
|
1387
|
+
logger.error("验证过程出错:", error);
|
|
1388
|
+
return `❌ 验证过程发生错误: ${error.message}`;
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
ctx.command("tof.quick <text:text>", "快速验证文本真实性").action(async ({ session }, text) => {
|
|
1392
|
+
if (!session) return "无法获取会话信息";
|
|
1393
|
+
if (!text?.trim()) return "请提供需要验证的文本";
|
|
1394
|
+
const format = config.outputFormat === "auto" ? session.platform === "qq" ? "plain" : "markdown" : config.outputFormat;
|
|
1395
|
+
const chatluna = new ChatlunaAdapter(ctx, config);
|
|
1396
|
+
if (!chatluna.isAvailable()) {
|
|
1397
|
+
return "❌ Chatluna 服务不可用";
|
|
1398
|
+
}
|
|
1399
|
+
await session.send("🔍 快速验证中...");
|
|
1400
|
+
try {
|
|
1401
|
+
const result = await mainAgent.verify({ text, images: [], hasQuote: false });
|
|
1402
|
+
const verdictEmoji = {
|
|
1403
|
+
["true" /* TRUE */]: "✅ 真实",
|
|
1404
|
+
["false" /* FALSE */]: "❌ 虚假",
|
|
1405
|
+
["partially_true" /* PARTIALLY_TRUE */]: "⚠️ 部分真实",
|
|
1406
|
+
["uncertain" /* UNCERTAIN */]: "❓ 无法确定"
|
|
1407
|
+
};
|
|
1408
|
+
const confidenceValue = Math.round(result.confidence * 100);
|
|
1409
|
+
const reasoning = result.reasoning.substring(0, 200);
|
|
1410
|
+
if (format === "plain") {
|
|
1411
|
+
return `${verdictEmoji[result.verdict]} (${confidenceValue}%)
|
|
1412
|
+
${reasoning}`;
|
|
1413
|
+
}
|
|
1414
|
+
return `**${verdictEmoji[result.verdict]}** (${confidenceValue}%)
|
|
1415
|
+
|
|
1416
|
+
${reasoning}`;
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
return `❌ 验证失败: ${error.message}`;
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
logger.info("isthattrue 插件已加载");
|
|
1422
|
+
}
|
|
1423
|
+
__name(apply, "apply");
|
|
1424
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1425
|
+
0 && (module.exports = {
|
|
1426
|
+
Config,
|
|
1427
|
+
apply,
|
|
1428
|
+
inject,
|
|
1429
|
+
name,
|
|
1430
|
+
usage
|
|
1431
|
+
});
|