koishi-plugin-satori-ai 1.4.0-beta.2 → 1.4.2
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.d.ts +1 -0
- package/lib/index.js +132 -62
- package/lib/memory.d.ts +1 -3
- package/lib/types.d.ts +2 -0
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -63,5 +63,6 @@ export declare class SAT extends Sat {
|
|
|
63
63
|
handleRandomMiddleware(session: Session): Promise<string>;
|
|
64
64
|
handleNickNameMiddleware(session: Session, prompt: string): Promise<string>;
|
|
65
65
|
handleChannelMemoryManager(session: Session): Promise<void>;
|
|
66
|
+
dispose(): Promise<void>;
|
|
66
67
|
}
|
|
67
68
|
export default SAT;
|
package/lib/index.js
CHANGED
|
@@ -272,20 +272,20 @@ function filterResponse(prompt, words, options) {
|
|
|
272
272
|
const applyTagFilter = options?.applyTagFilter ?? true;
|
|
273
273
|
let working = prompt;
|
|
274
274
|
if (applyBracketFilter) {
|
|
275
|
-
const parts = working.split(/([(\[【《(][^))]
|
|
275
|
+
const parts = working.split(/([(\[【《(][^))]{0,500}[)\]】》)])/g);
|
|
276
276
|
const filtered = parts.map((part) => {
|
|
277
277
|
if (part.startsWith("(") && part.endsWith(")") || part.startsWith("(") && part.endsWith(")") || part.startsWith("[") && part.endsWith("]") || part.startsWith("【") && part.endsWith("】") || part.startsWith("《") && part.endsWith("》")) {
|
|
278
278
|
return words.some((word) => part.includes(word)) ? "" : part;
|
|
279
279
|
}
|
|
280
280
|
return part;
|
|
281
|
-
}).join("");
|
|
281
|
+
}).join("…");
|
|
282
282
|
working = filtered.replace(/\s+/g, "…");
|
|
283
283
|
if (!working) {
|
|
284
284
|
working = prompt;
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
if (!applyTagFilter) {
|
|
288
|
-
return working ? { content: working, error: false } : { content: "
|
|
288
|
+
return working ? { content: working, error: false } : { content: "返回内容为空,请重置对话", error: true };
|
|
289
289
|
}
|
|
290
290
|
const pTagRegex = /<p>[\s\S]*?<\/p>/g;
|
|
291
291
|
const doubaoRegex = /<doubaothinking>[\s\S]*?<\/doubaothinking>/g;
|
|
@@ -302,11 +302,11 @@ function filterResponse(prompt, words, options) {
|
|
|
302
302
|
combined = answerTagMatches.map((s) => s.replace(/<answer>/i, "").replace(/<\/answer>/i, "")).join(",");
|
|
303
303
|
}
|
|
304
304
|
if (!combined) {
|
|
305
|
-
return { content: "
|
|
305
|
+
return { content: "内容抽取失败,请重置对话", error: true };
|
|
306
306
|
}
|
|
307
307
|
const cleanupRegex = /<p>|<\/p>|<doubaothinking>|<\/doubaothinking>|<answer>|<\/answer>|<br>|<\/br>|<br\/>/gi;
|
|
308
308
|
const cleanedContent = combined.replace(cleanupRegex, "").trim();
|
|
309
|
-
return cleanedContent ? { content: cleanedContent, error: false } : { content: "
|
|
309
|
+
return cleanedContent ? { content: cleanedContent, error: false } : { content: "标签过滤失败,请重置对话", error: true };
|
|
310
310
|
}
|
|
311
311
|
__name(filterResponse, "filterResponse");
|
|
312
312
|
function addOutputCensor(session, word, baseURL) {
|
|
@@ -417,7 +417,16 @@ async function wrapInHTML(str, width = 20) {
|
|
|
417
417
|
<div class="satori-text">${str.replaceAll(/\n/g, "<br/>")}</div>
|
|
418
418
|
</body>
|
|
419
419
|
</html>`;
|
|
420
|
-
|
|
420
|
+
try {
|
|
421
|
+
const renderPromise = puppeteer.render(html);
|
|
422
|
+
const timeoutPromise = new Promise(
|
|
423
|
+
(_, reject) => setTimeout(() => reject(new Error("Puppeteer 渲染超时")), 3e4)
|
|
424
|
+
);
|
|
425
|
+
return await Promise.race([renderPromise, timeoutPromise]);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
logger.error(`Puppeteer 渲染失败: ${error}`);
|
|
428
|
+
return "渲染失败,请联系管理员";
|
|
429
|
+
}
|
|
421
430
|
}
|
|
422
431
|
__name(wrapInHTML, "wrapInHTML");
|
|
423
432
|
|
|
@@ -539,6 +548,7 @@ var APIClient = class {
|
|
|
539
548
|
messages,
|
|
540
549
|
temperature: this.config.temperature,
|
|
541
550
|
top_p: 1,
|
|
551
|
+
max_tokens: this.config.max_output_tokens,
|
|
542
552
|
frequency_penalty: this.config.frequency_penalty,
|
|
543
553
|
presence_penalty: this.config.presence_penalty
|
|
544
554
|
};
|
|
@@ -570,11 +580,8 @@ var APIClient = class {
|
|
|
570
580
|
if (reasoning_content != "无" && content && content.length > reasoning_content.length) {
|
|
571
581
|
logger2.warn("返回内容疑似包含推理内容");
|
|
572
582
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const lastMsg = payload.messages[payload.messages.length - 1];
|
|
576
|
-
payload.messages[payload.messages.length - 1].content = lastMsg.content + "(注意不要重复之前的内容)";
|
|
577
|
-
logger2.warn(`返回内容与之前内容相同,重试(第${i}次)中...`);
|
|
583
|
+
if (response.usage.completion_tokens > this.config.max_output_tokens) {
|
|
584
|
+
logger2.warn("返回内容过长导致被截断");
|
|
578
585
|
continue;
|
|
579
586
|
}
|
|
580
587
|
return { content, error: false, reasoning_content };
|
|
@@ -587,7 +594,7 @@ var APIClient = class {
|
|
|
587
594
|
continue;
|
|
588
595
|
}
|
|
589
596
|
}
|
|
590
|
-
return { content: "
|
|
597
|
+
return { content: "请求失败,请重试", error: true };
|
|
591
598
|
}
|
|
592
599
|
// 生成请求头
|
|
593
600
|
createHeaders(keys) {
|
|
@@ -658,7 +665,6 @@ var MemoryManager = class {
|
|
|
658
665
|
channelDialogues = /* @__PURE__ */ new Map();
|
|
659
666
|
charactersToRemove = ["的", "一", "是", "了", "什", "么", "我", "谁", "不", "人", "在", "他", "有", "这", "个", "上", "们", "来", "到", "时", "大", "地", "为", "子", "中", "你", "说", "生", "国", "年", "着", "就", "那", "和", "要", "她", "出", "也", "得", "里", "后", "自", "以", "会", "id="];
|
|
660
667
|
MAX_MEMORY_LENGTH = 5e3;
|
|
661
|
-
userMemoryCache = /* @__PURE__ */ new Map();
|
|
662
668
|
// 更新记忆
|
|
663
669
|
async updateMemories(session, prompt, config, response) {
|
|
664
670
|
if (response.error)
|
|
@@ -792,10 +798,10 @@ var MemoryManager = class {
|
|
|
792
798
|
if (updated.length > this.MAX_MEMORY_LENGTH) {
|
|
793
799
|
updated = updated.slice(-this.MAX_MEMORY_LENGTH);
|
|
794
800
|
}
|
|
795
|
-
fs2.
|
|
801
|
+
await fs2.promises.writeFile(filePath, JSON.stringify(updated, null, 2), "utf-8");
|
|
796
802
|
}
|
|
797
803
|
// 记忆检索
|
|
798
|
-
async searchMemories(session,
|
|
804
|
+
async searchMemories(session, type = "user") {
|
|
799
805
|
const filePathMap = {
|
|
800
806
|
"user": this.getUserMemoryPath(session.userId),
|
|
801
807
|
"common": path2.join(this.config.dataDir, "common_sense.txt"),
|
|
@@ -811,14 +817,16 @@ var MemoryManager = class {
|
|
|
811
817
|
if (!fs2.existsSync(filePath)) {
|
|
812
818
|
return "";
|
|
813
819
|
}
|
|
814
|
-
const
|
|
820
|
+
const channelId = this.config.personal_memory ? session.userId : session.channelId;
|
|
821
|
+
const recentMemories = this.getChannelContext(channelId);
|
|
822
|
+
const userDialogues = recentMemories.filter((entry) => entry.role === "user").map((entry) => entry.content).join("");
|
|
823
|
+
const keywords = userDialogues.split("").filter((word) => !this.charactersToRemove.includes(word));
|
|
815
824
|
let entries = await this.loadMemoryFile(filePath);
|
|
816
825
|
const matched = this.findBestMatches(entries, keywords).slice(0, topNMap[type] * 5);
|
|
817
|
-
this.userMemoryCache.set(session.userId, matched);
|
|
818
826
|
if (type === "user") {
|
|
819
827
|
const remainingEntries = entries.filter((entry) => !matched.includes(entry));
|
|
820
828
|
let updatedEntries = [...remainingEntries, ...matched];
|
|
821
|
-
fs2.
|
|
829
|
+
await fs2.promises.writeFile(filePath, JSON.stringify(updatedEntries, null, 2), "utf-8");
|
|
822
830
|
}
|
|
823
831
|
const result = this.formatMatches(matched, type, topNMap[type]);
|
|
824
832
|
return result;
|
|
@@ -885,15 +893,16 @@ ${matched.map((entry) => entry.content).join("\n")}
|
|
|
885
893
|
}
|
|
886
894
|
// 确保记忆文件存在
|
|
887
895
|
async ensureMemoryFile(filePath) {
|
|
888
|
-
fs2.
|
|
896
|
+
await fs2.promises.mkdir(path2.dirname(filePath), { recursive: true });
|
|
889
897
|
if (!fs2.existsSync(filePath)) {
|
|
890
|
-
fs2.
|
|
898
|
+
await fs2.promises.writeFile(filePath, "[]", "utf-8");
|
|
891
899
|
}
|
|
892
900
|
}
|
|
893
901
|
// 加载记忆文件
|
|
894
902
|
async loadMemoryFile(filePath) {
|
|
895
903
|
try {
|
|
896
|
-
|
|
904
|
+
const content = await fs2.promises.readFile(filePath, "utf-8");
|
|
905
|
+
return JSON.parse(content);
|
|
897
906
|
} catch {
|
|
898
907
|
return [];
|
|
899
908
|
}
|
|
@@ -902,10 +911,6 @@ ${matched.map((entry) => entry.content).join("\n")}
|
|
|
902
911
|
getChannelContext(channelId) {
|
|
903
912
|
return this.channelMemories.get(channelId)?.dialogues || [];
|
|
904
913
|
}
|
|
905
|
-
// 获取用户缓存记忆
|
|
906
|
-
getUserCachedMemory(userId) {
|
|
907
|
-
return this.userMemoryCache.get(userId) || [];
|
|
908
|
-
}
|
|
909
914
|
};
|
|
910
915
|
|
|
911
916
|
// src/fixed-dialogues.ts
|
|
@@ -1265,6 +1270,7 @@ var Sat = class extends import_koishi5.Service {
|
|
|
1265
1270
|
authority: import_koishi5.Schema.number().role("slider").min(0).max(5).step(1).description("允许使用的最低权限").default(1),
|
|
1266
1271
|
no_system_prompt: import_koishi5.Schema.boolean().default(false).description("是否将系统提示的system替换为user(用于具有思维链的模型)"),
|
|
1267
1272
|
max_tokens: import_koishi5.Schema.number().description("最大请求长度(字符数)").default(100),
|
|
1273
|
+
max_output_tokens: import_koishi5.Schema.number().description("最大返回长度(不含思维链)(token数)").default(4096),
|
|
1268
1274
|
message_max_length: import_koishi5.Schema.number().description("最大频道上下文长度(条数)").default(10),
|
|
1269
1275
|
temperature: import_koishi5.Schema.number().role("slider").min(0).max(2).step(0.01).default(0.5).description("温度"),
|
|
1270
1276
|
frequency_penalty: import_koishi5.Schema.number().default(0).description("频率惩罚"),
|
|
@@ -1463,8 +1469,7 @@ var UserPortraitManager = class {
|
|
|
1463
1469
|
1. 基本信息(性别、年龄、生日等)
|
|
1464
1470
|
2. 生活习惯(作息等)
|
|
1465
1471
|
3. 反感点(如有)
|
|
1466
|
-
4.
|
|
1467
|
-
5. 用户希望记住的信息(如有)
|
|
1472
|
+
4. 近期可能需要记住的信息(如有)
|
|
1468
1473
|
|
|
1469
1474
|
用户名:${user.usersname}
|
|
1470
1475
|
你与用户的关系:${level}
|
|
@@ -1490,8 +1495,7 @@ ${history}
|
|
|
1490
1495
|
1. 基本信息(性别、年龄、生日等,不要遗漏或改动已有的信息)
|
|
1491
1496
|
2. 生活习惯(作息等)
|
|
1492
1497
|
3. 反感点(如有)
|
|
1493
|
-
4.
|
|
1494
|
-
5. 用户希望记住的信息(如有)
|
|
1498
|
+
4. 近期可能需要记住的信息(如有)
|
|
1495
1499
|
|
|
1496
1500
|
用户名:${user.usersname}
|
|
1497
1501
|
你与用户的关系:${level}
|
|
@@ -1506,6 +1510,7 @@ ${NewPortrait}
|
|
|
1506
1510
|
·根据已有画像,补充新信息,避免重复,并且不要遗漏你认为重要的信息
|
|
1507
1511
|
·新信息中声称未知的内容,如果已有画像中有相关信息,请优先保留已有画像的信息,尤其是基本信息部分
|
|
1508
1512
|
·你认为不太重要的信息可以删除
|
|
1513
|
+
·近期可能需要记住的信息最多保留最后三条
|
|
1509
1514
|
·使用尽可能简洁的表达
|
|
1510
1515
|
·保留不确定性的表述(如"可能"、"似乎"、"用户自称")
|
|
1511
1516
|
·保持中立和客观,避免带有个人情感色彩的描述,不要添加评价或建议
|
|
@@ -3422,11 +3427,13 @@ var Galgame = class {
|
|
|
3422
3427
|
return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
3423
3428
|
}
|
|
3424
3429
|
async sendStoryContent(session, content) {
|
|
3430
|
+
const username = session.username || "你";
|
|
3431
|
+
const storyText = String(content.text).replace(/\{?_USERNAME_\}?/g, username);
|
|
3425
3432
|
if (content.isNarration) {
|
|
3426
|
-
const wrappedText = await wrapInHTML(
|
|
3433
|
+
const wrappedText = await wrapInHTML(storyText, 25);
|
|
3427
3434
|
await session.send(wrappedText);
|
|
3428
3435
|
} else {
|
|
3429
|
-
await session.send(
|
|
3436
|
+
await session.send(storyText);
|
|
3430
3437
|
}
|
|
3431
3438
|
if (content.imageName) {
|
|
3432
3439
|
const imagePath = path6.resolve(this.config.dataDir, "event", content.imageName);
|
|
@@ -3434,6 +3441,7 @@ var Galgame = class {
|
|
|
3434
3441
|
const buffer = fs6.readFileSync(imagePath);
|
|
3435
3442
|
const ext = path6.extname(imagePath).toLowerCase();
|
|
3436
3443
|
const mime = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
|
|
3444
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
3437
3445
|
await session.send(import_koishi14.h.image(buffer, mime));
|
|
3438
3446
|
}
|
|
3439
3447
|
}
|
|
@@ -3443,9 +3451,9 @@ var Galgame = class {
|
|
|
3443
3451
|
// 然后在阅读时间上加上额外 3 秒缓冲,最终向上取整并转为毫秒。
|
|
3444
3452
|
// 对中英文混合文本:中文按汉字计数,拉丁文按空格分词计数。
|
|
3445
3453
|
computeAutoAdvanceDelay(text) {
|
|
3446
|
-
const readingWpm =
|
|
3454
|
+
const readingWpm = 500;
|
|
3447
3455
|
if (!text)
|
|
3448
|
-
return
|
|
3456
|
+
return 1e3;
|
|
3449
3457
|
const cjkMatch = text.match(/[\u4e00-\u9fff]/g) || [];
|
|
3450
3458
|
const cjkCount = cjkMatch.length;
|
|
3451
3459
|
const withoutCjk = text.replace(/[\u4e00-\u9fff]/g, "");
|
|
@@ -3453,7 +3461,7 @@ var Galgame = class {
|
|
|
3453
3461
|
const effectiveWords = cjkCount + latinWords;
|
|
3454
3462
|
const wordsPerSecond = readingWpm / 60;
|
|
3455
3463
|
const readingSeconds = effectiveWords / Math.max(wordsPerSecond, 1e-3);
|
|
3456
|
-
const delaySeconds = Math.max(1, Math.ceil(readingSeconds) +
|
|
3464
|
+
const delaySeconds = Math.max(1, Math.ceil(readingSeconds) + 2);
|
|
3457
3465
|
return delaySeconds * 1e3;
|
|
3458
3466
|
}
|
|
3459
3467
|
async handleEventProgress(session, event) {
|
|
@@ -3471,9 +3479,19 @@ var Galgame = class {
|
|
|
3471
3479
|
delete this.ongoingEvents[userId];
|
|
3472
3480
|
delete this.currentId[userId];
|
|
3473
3481
|
this.completedEvents[userId] = event.eventName;
|
|
3482
|
+
try {
|
|
3483
|
+
const portraitPath = path6.join(this.config.dataDir, "UserPortrait", `${userId}.txt`);
|
|
3484
|
+
if (fs6.existsSync(portraitPath)) {
|
|
3485
|
+
const appendText = `
|
|
3486
|
+
近期你和他进行了约会:${event.eventName}`;
|
|
3487
|
+
fs6.appendFileSync(portraitPath, appendText, "utf-8");
|
|
3488
|
+
}
|
|
3489
|
+
} catch (e) {
|
|
3490
|
+
}
|
|
3474
3491
|
return;
|
|
3475
3492
|
}
|
|
3476
3493
|
if (content.options) {
|
|
3494
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
3477
3495
|
const optionsText = content.options.map((opt, idx) => `选项 ${idx + 1}: ${opt.text}`).join("\n");
|
|
3478
3496
|
await session.send(optionsText + '\n发送格式类似 "选项 1" 来进行选择');
|
|
3479
3497
|
delete this.ongoingEvents[userId];
|
|
@@ -3489,10 +3507,10 @@ var Galgame = class {
|
|
|
3489
3507
|
async handleEnding(session, ending) {
|
|
3490
3508
|
const rewardParts = [];
|
|
3491
3509
|
if (ending.favorabilityReward) {
|
|
3492
|
-
rewardParts.push(`好感度
|
|
3510
|
+
rewardParts.push(`好感度 ${ending.favorabilityReward}`);
|
|
3493
3511
|
}
|
|
3494
3512
|
if (ending.pReward) {
|
|
3495
|
-
rewardParts.push(`P点
|
|
3513
|
+
rewardParts.push(`P点 ${ending.pReward}`);
|
|
3496
3514
|
}
|
|
3497
3515
|
if (ending.itemRewards) {
|
|
3498
3516
|
for (const [name, qty] of Object.entries(ending.itemRewards)) {
|
|
@@ -3503,6 +3521,7 @@ var Galgame = class {
|
|
|
3503
3521
|
|
|
3504
3522
|
获得奖励:
|
|
3505
3523
|
${rewardParts.join("\n")}` : "";
|
|
3524
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
3506
3525
|
await session.send(ending.text + rewardText);
|
|
3507
3526
|
if (ending.image) {
|
|
3508
3527
|
const imagePath = path6.resolve(this.config.dataDir, "event", ending.image);
|
|
@@ -3510,6 +3529,7 @@ ${rewardParts.join("\n")}` : "";
|
|
|
3510
3529
|
const buffer = fs6.readFileSync(imagePath);
|
|
3511
3530
|
const ext = path6.extname(imagePath).toLowerCase();
|
|
3512
3531
|
const mime = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
|
|
3532
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
3513
3533
|
await session.send(import_koishi14.h.image(buffer, mime));
|
|
3514
3534
|
}
|
|
3515
3535
|
}
|
|
@@ -3560,7 +3580,7 @@ ${rewardParts.join("\n")}` : "";
|
|
|
3560
3580
|
}
|
|
3561
3581
|
const events = fs6.readdirSync(eventDir).filter((file) => file.endsWith(".json")).map((file) => this.loadEvent(file.replace(".json", ""))).filter((event2) => event2 && user.favorability >= event2.favorabilityThreshold).map((event2) => event2.eventName);
|
|
3562
3582
|
const result = "当前可用的约会事件有:\n" + events.join("\n") + "\n请输入命令:约会 [事件名] 来开始对应的事件。";
|
|
3563
|
-
const wrappedText = await wrapInHTML(result);
|
|
3583
|
+
const wrappedText = await wrapInHTML(result, 25);
|
|
3564
3584
|
await session.send(wrappedText);
|
|
3565
3585
|
return;
|
|
3566
3586
|
}
|
|
@@ -3661,7 +3681,7 @@ story 为有序的段落数组,每项至少需要 id 和 text。
|
|
|
3661
3681
|
},
|
|
3662
3682
|
{
|
|
3663
3683
|
id: 4,
|
|
3664
|
-
text: "
|
|
3684
|
+
text: "此段展示分支和用户名占位,现在进入分支环节:请选择_USERNAME_的回应。\n发送格式:选项 1 或 选项 2。",
|
|
3665
3685
|
options: [
|
|
3666
3686
|
{ text: "友好回应", targetId: 5 },
|
|
3667
3687
|
{ text: "冷淡回应", targetId: 6 }
|
|
@@ -3747,6 +3767,17 @@ var SAT = class extends Sat {
|
|
|
3747
3767
|
this.galgame = new Galgame(ctx, config);
|
|
3748
3768
|
this.registerGalgameCommands(ctx);
|
|
3749
3769
|
}
|
|
3770
|
+
process.on("exit", () => {
|
|
3771
|
+
void this.dispose();
|
|
3772
|
+
});
|
|
3773
|
+
process.on("SIGINT", () => {
|
|
3774
|
+
void this.dispose();
|
|
3775
|
+
process.exit();
|
|
3776
|
+
});
|
|
3777
|
+
process.on("SIGTERM", () => {
|
|
3778
|
+
void this.dispose();
|
|
3779
|
+
process.exit();
|
|
3780
|
+
});
|
|
3750
3781
|
}
|
|
3751
3782
|
static {
|
|
3752
3783
|
__name(this, "SAT");
|
|
@@ -3807,7 +3838,8 @@ var SAT = class extends Sat {
|
|
|
3807
3838
|
temperature: this.config.temperature,
|
|
3808
3839
|
frequency_penalty: this.config.frequency_penalty,
|
|
3809
3840
|
presence_penalty: this.config.presence_penalty,
|
|
3810
|
-
reasoning_content: this.config.log_reasoning_content
|
|
3841
|
+
reasoning_content: this.config.log_reasoning_content,
|
|
3842
|
+
max_output_tokens: this.config.max_output_tokens
|
|
3811
3843
|
};
|
|
3812
3844
|
}
|
|
3813
3845
|
getMemoryConfig() {
|
|
@@ -3934,7 +3966,6 @@ var SAT = class extends Sat {
|
|
|
3934
3966
|
const dialogueCountCheck = await this.checkUserDialogueCount(session, user);
|
|
3935
3967
|
if (dialogueCountCheck)
|
|
3936
3968
|
return dialogueCountCheck;
|
|
3937
|
-
await this.updateChannelParallelCount(session, 1);
|
|
3938
3969
|
const response = await this.generateResponse(session, processedPrompt);
|
|
3939
3970
|
const auxiliaryResult = await this.handleAuxiliaryDialogue(session, processedPrompt, response);
|
|
3940
3971
|
await this.memoryManager.updateMemories(session, processedPrompt, this.getMemoryConfig(), response);
|
|
@@ -4032,7 +4063,8 @@ var SAT = class extends Sat {
|
|
|
4032
4063
|
}
|
|
4033
4064
|
// 更新频道并发数
|
|
4034
4065
|
async updateChannelParallelCount(session, value) {
|
|
4035
|
-
this.onlineUsers.
|
|
4066
|
+
if (value > 0 && !this.onlineUsers.includes(session.userId))
|
|
4067
|
+
this.onlineUsers.push(session.userId);
|
|
4036
4068
|
this.ChannelParallelCount.set(session.channelId, (this.ChannelParallelCount.get(session.channelId) || 0) + value);
|
|
4037
4069
|
}
|
|
4038
4070
|
// 获取频道并发数
|
|
@@ -4058,27 +4090,39 @@ var SAT = class extends Sat {
|
|
|
4058
4090
|
if (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
|
|
4059
4091
|
logger13.info(`频道 ${session.channelId} 并发数过高(${this.getChannelParallelCount(session)}),${session.username}等待中...`);
|
|
4060
4092
|
}
|
|
4061
|
-
|
|
4062
|
-
|
|
4093
|
+
let waitTimes = 0;
|
|
4094
|
+
while (this.getChannelParallelCount(session) >= this.config.max_parallel_count) {
|
|
4095
|
+
waitTimes++;
|
|
4096
|
+
if (waitTimes > 60) {
|
|
4097
|
+
logger13.error(`并发等待超时:channel=${session.channelId}`);
|
|
4098
|
+
return { content: "系统繁忙,请稍后再试", error: true };
|
|
4099
|
+
}
|
|
4063
4100
|
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
4064
|
-
this.updateChannelParallelCount(session, 1);
|
|
4065
4101
|
}
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
response = await this.getChatResponse(user, messages, prompt);
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4102
|
+
await this.updateChannelParallelCount(session, 1);
|
|
4103
|
+
try {
|
|
4104
|
+
logger13.info(`频道 ${session.channelId} 处理:${session.userId},剩余${this.getChannelParallelCount(session)}并发`);
|
|
4105
|
+
const messages = await this.buildMessages(session, prompt);
|
|
4106
|
+
const user = await getUser(this.ctx, session.userId);
|
|
4107
|
+
let response = await this.getChatResponse(user, messages, prompt);
|
|
4108
|
+
if (response.error)
|
|
4109
|
+
response = await this.getChatResponse(user, messages, prompt);
|
|
4110
|
+
if (response.error)
|
|
4111
|
+
updateUserUsage(this.ctx, user, -1);
|
|
4112
|
+
return response;
|
|
4113
|
+
} finally {
|
|
4114
|
+
this.onlineUsers = this.onlineUsers.filter((id) => id !== session.userId);
|
|
4115
|
+
await this.updateChannelParallelCount(session, -1);
|
|
4116
|
+
}
|
|
4075
4117
|
}
|
|
4076
4118
|
// 获取聊天回复
|
|
4077
4119
|
async getChatResponse(user, messages, prompt) {
|
|
4078
4120
|
const hasTicket = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description === "on";
|
|
4079
4121
|
const maxLength = hasTicket ? user?.items?.["地灵殿通行证"]?.metadata?.use_not_reasoner_LLM_length : this.config.use_not_reasoner_LLM_length;
|
|
4080
4122
|
const useNoReasoner = prompt.length <= maxLength && this.config.enable_reasoner_like;
|
|
4123
|
+
const startTime = Date.now();
|
|
4081
4124
|
let response = await this.apiClient.chat(user, messages);
|
|
4125
|
+
logger13.info(`API 调用耗时: ${Date.now() - startTime}ms`);
|
|
4082
4126
|
if (this.config.log_ask_response) {
|
|
4083
4127
|
if (this.config.enable_favorability && this.config.enable_mood)
|
|
4084
4128
|
logger13.info(`Satori AI:(心情值:${this.moodManager.getMoodValue(user.userid)})${response.content}`);
|
|
@@ -4086,10 +4130,15 @@ var SAT = class extends Sat {
|
|
|
4086
4130
|
logger13.info(`Satori AI:${response.content}`);
|
|
4087
4131
|
}
|
|
4088
4132
|
if (!response.error) {
|
|
4133
|
+
const filterStartTime = Date.now();
|
|
4089
4134
|
response.content = filterResponse(response.content, this.config.reasoner_filter_word.split("-"), {
|
|
4090
4135
|
applyBracketFilter: this.config.reasoner_filter,
|
|
4091
4136
|
applyTagFilter: useNoReasoner || this.config.enhanceReasoningProtection
|
|
4092
4137
|
}).content;
|
|
4138
|
+
const filterTime = Date.now() - filterStartTime;
|
|
4139
|
+
if (filterTime > 1e3) {
|
|
4140
|
+
logger13.warn(`filterResponse 耗时异常: ${filterTime}ms, 内容长度: ${response.content?.length || 0}`);
|
|
4141
|
+
}
|
|
4093
4142
|
}
|
|
4094
4143
|
return response;
|
|
4095
4144
|
}
|
|
@@ -4126,10 +4175,10 @@ ${payload}`);
|
|
|
4126
4175
|
}
|
|
4127
4176
|
// 构建系统提示
|
|
4128
4177
|
async buildSystemPrompt(session, prompt) {
|
|
4129
|
-
const commonSense = await this.memoryManager.searchMemories(session,
|
|
4130
|
-
const groupSense = await this.memoryManager.searchMemories(session,
|
|
4178
|
+
const commonSense = await this.memoryManager.searchMemories(session, "common");
|
|
4179
|
+
const groupSense = await this.memoryManager.searchMemories(session, "group");
|
|
4131
4180
|
const channelDialogue = await this.memoryManager.getChannelDialogue(session);
|
|
4132
|
-
const userMemory = await this.memoryManager.searchMemories(session,
|
|
4181
|
+
const userMemory = await this.memoryManager.searchMemories(session, "user");
|
|
4133
4182
|
const weatherPrompt = await this.weatherManager.buildWeatherPrompt(session);
|
|
4134
4183
|
const user = await getUser(this.ctx, session.userId);
|
|
4135
4184
|
const moodLevel = this.moodManager.getMoodLevel(user.userid);
|
|
@@ -4203,7 +4252,9 @@ ${payload}`);
|
|
|
4203
4252
|
#参考信息到此为止,接下来是思考要求
|
|
4204
4253
|
#请你在回复时先进行分析思考,并且模仿思维链的模式输出思考内容,${reasonerPrompt};
|
|
4205
4254
|
#你在思考时必须以 "<think>" 开头, "</think>" 结尾。仔细揣摩用户意图,**完整输出思考内容后**再输出正式的回复内容;
|
|
4206
|
-
#注意:你的正式回复内容必须使用“<p>”开头,在输出全部回复后使用“</p
|
|
4255
|
+
#注意:你的正式回复内容必须使用“<p>”开头,在输出全部回复后使用“</p>”结尾(即最终回答只允许有一组标签),并且无论如何都要把标签输出完整。
|
|
4256
|
+
例子格式如下:
|
|
4257
|
+
<think>你的思考内容</think><p>你的正式回复内容</p>
|
|
4207
4258
|
#接下来是对话要求,以下要求仅对最终的回复内容生效,不限制思考过程
|
|
4208
4259
|
`;
|
|
4209
4260
|
const promptForReasoner = this.config.enhanceReasoningProtection ? `
|
|
@@ -4225,8 +4276,6 @@ ${useNoReasoner ? promptForNoReasoner : promptForReasoner}
|
|
|
4225
4276
|
// 处理回复
|
|
4226
4277
|
async formatResponse(session, response, auxiliaryResult, reasoningContent) {
|
|
4227
4278
|
const user = await getUser(this.ctx, session.userId);
|
|
4228
|
-
this.updateChannelParallelCount(session, -1);
|
|
4229
|
-
this.onlineUsers = this.onlineUsers.filter((id) => id !== session.userId);
|
|
4230
4279
|
if (!response)
|
|
4231
4280
|
return session.text("commands.sat.messages.no-response");
|
|
4232
4281
|
const catEar = user?.items?.["猫耳发饰"]?.description && user.items["猫耳发饰"].description == "on";
|
|
@@ -4290,8 +4339,18 @@ ${useNoReasoner ? promptForNoReasoner : promptForReasoner}
|
|
|
4290
4339
|
logger13.info("Puppeteer 未就绪,正在重新初始化 Puppeteer...");
|
|
4291
4340
|
this.setPuppeteer(this.ctx.puppeteer);
|
|
4292
4341
|
}
|
|
4293
|
-
if (firstReasoning.length > 15)
|
|
4294
|
-
|
|
4342
|
+
if (firstReasoning.length > 15) {
|
|
4343
|
+
const renderStartTime = Date.now();
|
|
4344
|
+
try {
|
|
4345
|
+
await session.send(await wrapInHTML(firstReasoning));
|
|
4346
|
+
const renderTime = Date.now() - renderStartTime;
|
|
4347
|
+
if (renderTime > 5e3) {
|
|
4348
|
+
logger13.warn(`wrapInHTML 渲染耗时异常: ${renderTime}ms, 内容长度: ${firstReasoning.length}`);
|
|
4349
|
+
}
|
|
4350
|
+
} catch (error) {
|
|
4351
|
+
logger13.error(`wrapInHTML 渲染失败: ${error}`);
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4295
4354
|
if (this.config.sentences_divide && response.length > 10) {
|
|
4296
4355
|
const sentences = splitSentences(response, this.config.min_sentences_length, this.config.max_sentences_length).map((text) => import_koishi15.h.text(text));
|
|
4297
4356
|
for (const sentence of sentences) {
|
|
@@ -4394,7 +4453,6 @@ ${useNoReasoner ? promptForNoReasoner : promptForReasoner}
|
|
|
4394
4453
|
}
|
|
4395
4454
|
// 随机中间件转接
|
|
4396
4455
|
async handleRandomMiddleware(session) {
|
|
4397
|
-
await this.updateChannelParallelCount(session, 1);
|
|
4398
4456
|
const response = await this.generateResponse(session, randomPrompt);
|
|
4399
4457
|
return await this.formatResponse(session, response.content, null);
|
|
4400
4458
|
}
|
|
@@ -4418,6 +4476,18 @@ ${useNoReasoner ? promptForNoReasoner : promptForReasoner}
|
|
|
4418
4476
|
this.memoryManager.updateChannelDialogue(session, censored, session.username);
|
|
4419
4477
|
return null;
|
|
4420
4478
|
}
|
|
4479
|
+
async dispose() {
|
|
4480
|
+
if (this.puppeteer) {
|
|
4481
|
+
try {
|
|
4482
|
+
await this.puppeteer.stop();
|
|
4483
|
+
logger13.info("Puppeteer 已成功释放");
|
|
4484
|
+
} catch (error) {
|
|
4485
|
+
logger13.warn("释放 Puppeteer 时出错", error);
|
|
4486
|
+
} finally {
|
|
4487
|
+
this.puppeteer = null;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4421
4491
|
};
|
|
4422
4492
|
var src_default = SAT;
|
|
4423
4493
|
// Annotate the CommonJS export names for ESM import in node:
|
package/lib/memory.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export declare class MemoryManager {
|
|
|
7
7
|
private channelDialogues;
|
|
8
8
|
private charactersToRemove;
|
|
9
9
|
private MAX_MEMORY_LENGTH;
|
|
10
|
-
private userMemoryCache;
|
|
11
10
|
constructor(ctx: Context, config: MemoryConfig);
|
|
12
11
|
updateMemories(session: Session, prompt: string, config: MemoryConfig, response: {
|
|
13
12
|
content: string;
|
|
@@ -24,7 +23,7 @@ export declare class MemoryManager {
|
|
|
24
23
|
clearAllMemories(): void;
|
|
25
24
|
getChannelMemory(channelId: string): MemoryEntry[];
|
|
26
25
|
saveLongTermMemory(session: Session, dialogues: MemoryEntry[], filePath?: string): Promise<void>;
|
|
27
|
-
searchMemories(session: Session,
|
|
26
|
+
searchMemories(session: Session, type?: 'user' | 'common' | 'group'): Promise<string>;
|
|
28
27
|
private findBestMatches;
|
|
29
28
|
private calculateMatchScore;
|
|
30
29
|
private formatMatches;
|
|
@@ -32,5 +31,4 @@ export declare class MemoryManager {
|
|
|
32
31
|
private ensureMemoryFile;
|
|
33
32
|
private loadMemoryFile;
|
|
34
33
|
getChannelContext(channelId: string): MemoryEntry[];
|
|
35
|
-
getUserCachedMemory(userId: string): MemoryEntry[];
|
|
36
34
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export interface APIConfig {
|
|
|
105
105
|
maxRetryTimes?: number;
|
|
106
106
|
retry_delay_time?: number;
|
|
107
107
|
reasoning_content?: boolean;
|
|
108
|
+
max_output_tokens: number;
|
|
108
109
|
}
|
|
109
110
|
export interface APIError extends Error {
|
|
110
111
|
response?: {
|
|
@@ -162,6 +163,7 @@ export declare namespace Sat {
|
|
|
162
163
|
reasoner_prompt: string;
|
|
163
164
|
no_system_prompt: boolean;
|
|
164
165
|
max_tokens: number;
|
|
166
|
+
max_output_tokens: number;
|
|
165
167
|
message_max_length: number;
|
|
166
168
|
remember_min_length: number;
|
|
167
169
|
temperature: number;
|
package/package.json
CHANGED