koishi-plugin-satori-ai 1.2.1 → 1.2.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 +296 -121
- package/lib/memory.d.ts +3 -2
- package/lib/types.d.ts +3 -2
- package/lib/userportrait.d.ts +5 -3
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -44,8 +44,8 @@ __export(src_exports, {
|
|
|
44
44
|
default: () => src_default
|
|
45
45
|
});
|
|
46
46
|
module.exports = __toCommonJS(src_exports);
|
|
47
|
-
var
|
|
48
|
-
var
|
|
47
|
+
var import_koishi7 = require("koishi");
|
|
48
|
+
var path6 = __toESM(require("path"));
|
|
49
49
|
|
|
50
50
|
// src/api.ts
|
|
51
51
|
var import_koishi2 = require("koishi");
|
|
@@ -131,6 +131,8 @@ function isErrorWithMessage(error) {
|
|
|
131
131
|
}
|
|
132
132
|
__name(isErrorWithMessage, "isErrorWithMessage");
|
|
133
133
|
function processPrompt(prompt) {
|
|
134
|
+
if (!prompt)
|
|
135
|
+
return "";
|
|
134
136
|
if (prompt.includes(":poke"))
|
|
135
137
|
return "戳戳";
|
|
136
138
|
return prompt.replace(/<[^>]*?name="([^\"]*)"[^>]*>/g, (_, name) => `@${name}`);
|
|
@@ -206,16 +208,23 @@ var APIClient = class {
|
|
|
206
208
|
// 发送辅助聊天请求
|
|
207
209
|
async auxiliaryChat(messages) {
|
|
208
210
|
const AuxiliaryPayload = this.createAuxiliaryPayload(messages, this.config.auxiliary_LLM);
|
|
209
|
-
|
|
211
|
+
const url = `${trimSlash(this.config.auxiliary_LLM_URL)}/chat/completions`;
|
|
212
|
+
const headers = this.createHeaders(this.config.auxiliary_LLM_key);
|
|
213
|
+
let content;
|
|
214
|
+
for (let i = 1; i <= this.config.maxRetryTimes; i++) {
|
|
210
215
|
try {
|
|
211
|
-
|
|
216
|
+
const response = await this.ctx.http.post(url, AuxiliaryPayload, { headers, timeout: 36e5 });
|
|
217
|
+
content = response.choices[0].message.content;
|
|
218
|
+
return { content, error: false };
|
|
212
219
|
} catch (error) {
|
|
213
|
-
if (i == this.config.
|
|
220
|
+
if (i == this.config.maxRetryTimes) {
|
|
214
221
|
return this.handleAPIError(error);
|
|
215
222
|
}
|
|
216
|
-
|
|
223
|
+
logger2.warn(`辅助模型API请求失败(${error}),重试(第${i}次)中...`);
|
|
224
|
+
continue;
|
|
217
225
|
}
|
|
218
226
|
}
|
|
227
|
+
return { content: "unreachable", error: true };
|
|
219
228
|
}
|
|
220
229
|
// 发送生成用户画像请求
|
|
221
230
|
async generateUserPortrait(user, messages) {
|
|
@@ -234,16 +243,24 @@ var APIClient = class {
|
|
|
234
243
|
baseURL = this.config.baseURL;
|
|
235
244
|
}
|
|
236
245
|
const payload = this.createAuxiliaryPayload(messages, modle);
|
|
237
|
-
|
|
246
|
+
const url = `${trimSlash(baseURL)}/chat/completions`;
|
|
247
|
+
const headers = this.createHeaders(keys);
|
|
248
|
+
let content;
|
|
249
|
+
for (let i = 1; i <= this.config.maxRetryTimes; i++) {
|
|
238
250
|
try {
|
|
239
|
-
|
|
251
|
+
const response = await this.ctx.http.post(url, payload, { headers, timeout: 36e5 });
|
|
252
|
+
content = response.choices[0].message.content;
|
|
253
|
+
return { content, error: false };
|
|
240
254
|
} catch (error) {
|
|
241
|
-
if (i ==
|
|
255
|
+
if (i == this.config.maxRetryTimes) {
|
|
242
256
|
return this.handleAPIError(error);
|
|
243
257
|
}
|
|
244
|
-
this.
|
|
258
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.config.retry_delay_time || 5e3));
|
|
259
|
+
logger2.warn(`生成画像时API请求失败(${error}),重试(第${i}次)中...`);
|
|
260
|
+
continue;
|
|
245
261
|
}
|
|
246
262
|
}
|
|
263
|
+
return { content: "unreachable", error: true };
|
|
247
264
|
}
|
|
248
265
|
// 生成请求体
|
|
249
266
|
createPayload(messages, model) {
|
|
@@ -362,9 +379,101 @@ var APIClient = class {
|
|
|
362
379
|
var import_koishi3 = require("koishi");
|
|
363
380
|
var fs2 = __toESM(require("fs"));
|
|
364
381
|
var path2 = __toESM(require("path"));
|
|
382
|
+
|
|
383
|
+
// src/database.ts
|
|
384
|
+
async function isTargetIdNotExists(ctx, userId) {
|
|
385
|
+
const users = await ctx.database.get("p_system", { userid: userId });
|
|
386
|
+
return users.length === 0;
|
|
387
|
+
}
|
|
388
|
+
__name(isTargetIdNotExists, "isTargetIdNotExists");
|
|
389
|
+
async function createUser(ctx, user) {
|
|
390
|
+
await ctx.database.create("p_system", {
|
|
391
|
+
userid: user.userid || "",
|
|
392
|
+
usersname: user.usersname || "",
|
|
393
|
+
p: user.p || 0,
|
|
394
|
+
favorability: user.favorability || 0,
|
|
395
|
+
userlevel: user.userlevel || 0,
|
|
396
|
+
usage: user.usage || 0,
|
|
397
|
+
lastChatTime: user.lastChatTime || (/* @__PURE__ */ new Date()).getDate(),
|
|
398
|
+
items: user.items || {}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
__name(createUser, "createUser");
|
|
402
|
+
async function updateFavorability(ctx, user, adjustment) {
|
|
403
|
+
if (!user)
|
|
404
|
+
return;
|
|
405
|
+
let newValue;
|
|
406
|
+
if (typeof adjustment === "number")
|
|
407
|
+
newValue = user.favorability + adjustment;
|
|
408
|
+
else
|
|
409
|
+
newValue = user.favorability;
|
|
410
|
+
await ctx.database.set("p_system", { userid: user.userid }, { favorability: newValue });
|
|
411
|
+
}
|
|
412
|
+
__name(updateFavorability, "updateFavorability");
|
|
413
|
+
async function updateUserLevel(ctx, user, level) {
|
|
414
|
+
if (!user)
|
|
415
|
+
return;
|
|
416
|
+
await ctx.database.set("p_system", { userid: user.userid }, { userlevel: level });
|
|
417
|
+
}
|
|
418
|
+
__name(updateUserLevel, "updateUserLevel");
|
|
419
|
+
async function updateUserUsage(ctx, user, adjustment = 1) {
|
|
420
|
+
if (!user)
|
|
421
|
+
return;
|
|
422
|
+
if (user.lastChatTime && (/* @__PURE__ */ new Date()).getDate() !== user.lastChatTime)
|
|
423
|
+
user.usage = 0;
|
|
424
|
+
await ctx.database.set("p_system", { userid: user.userid }, { usage: user.usage + adjustment });
|
|
425
|
+
await ctx.database.set("p_system", { userid: user.userid }, { lastChatTime: (/* @__PURE__ */ new Date()).getDate() });
|
|
426
|
+
return user.usage + adjustment;
|
|
427
|
+
}
|
|
428
|
+
__name(updateUserUsage, "updateUserUsage");
|
|
429
|
+
async function updateUserItems(ctx, user) {
|
|
430
|
+
if (!user)
|
|
431
|
+
return;
|
|
432
|
+
await ctx.database.set("p_system", { userid: user.userid }, { items: user.items });
|
|
433
|
+
}
|
|
434
|
+
__name(updateUserItems, "updateUserItems");
|
|
435
|
+
async function getUser(ctx, userId) {
|
|
436
|
+
const users = await ctx.database.get("p_system", { userid: userId });
|
|
437
|
+
return users[0] || null;
|
|
438
|
+
}
|
|
439
|
+
__name(getUser, "getUser");
|
|
440
|
+
async function ensureUserExists(ctx, userId, username) {
|
|
441
|
+
const notExists = await isTargetIdNotExists(ctx, userId);
|
|
442
|
+
if (notExists) {
|
|
443
|
+
await createUser(ctx, {
|
|
444
|
+
userid: userId,
|
|
445
|
+
usersname: username,
|
|
446
|
+
p: 0,
|
|
447
|
+
favorability: 0,
|
|
448
|
+
userlevel: 0,
|
|
449
|
+
usage: 0,
|
|
450
|
+
lastChatTime: (/* @__PURE__ */ new Date()).getDate(),
|
|
451
|
+
items: {}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return getUser(ctx, userId);
|
|
455
|
+
}
|
|
456
|
+
__name(ensureUserExists, "ensureUserExists");
|
|
457
|
+
function extendDatabase(ctx) {
|
|
458
|
+
ctx.model.extend("p_system", {
|
|
459
|
+
id: "unsigned",
|
|
460
|
+
userid: "string",
|
|
461
|
+
usersname: "string",
|
|
462
|
+
p: "integer",
|
|
463
|
+
favorability: "integer",
|
|
464
|
+
userlevel: "integer",
|
|
465
|
+
usage: "integer",
|
|
466
|
+
lastChatTime: "integer",
|
|
467
|
+
items: "object"
|
|
468
|
+
}, { autoInc: true });
|
|
469
|
+
}
|
|
470
|
+
__name(extendDatabase, "extendDatabase");
|
|
471
|
+
|
|
472
|
+
// src/memory.ts
|
|
365
473
|
var logger3 = new import_koishi3.Logger("satori-memory");
|
|
366
474
|
var MemoryManager = class {
|
|
367
|
-
constructor(config) {
|
|
475
|
+
constructor(ctx, config) {
|
|
476
|
+
this.ctx = ctx;
|
|
368
477
|
this.config = config;
|
|
369
478
|
}
|
|
370
479
|
static {
|
|
@@ -379,7 +488,8 @@ var MemoryManager = class {
|
|
|
379
488
|
this.updateChannelMemory(session, prompt, config, response.content);
|
|
380
489
|
prompt = prompt.replace("我", session.username);
|
|
381
490
|
const date = ` (对话日期和时间:${(/* @__PURE__ */ new Date()).toLocaleString()})`;
|
|
382
|
-
|
|
491
|
+
const userFavourbility = await (await getUser(this.ctx, session.userId)).favorability;
|
|
492
|
+
if (this.shouldRemember(prompt, userFavourbility)) {
|
|
383
493
|
await this.saveLongTermMemory(session, [{
|
|
384
494
|
role: date,
|
|
385
495
|
content: prompt
|
|
@@ -387,8 +497,8 @@ var MemoryManager = class {
|
|
|
387
497
|
}
|
|
388
498
|
}
|
|
389
499
|
// 是否应当记忆
|
|
390
|
-
shouldRemember(content) {
|
|
391
|
-
return (content.length >= this.config.remember_min_length || content.includes("记住")) && !this.config.memory_block_words.some((word) => content.includes(word));
|
|
500
|
+
shouldRemember(content, userFavourbility) {
|
|
501
|
+
return (content.length >= this.config.remember_min_length || content.includes("记住") && userFavourbility >= 50) && !this.config.memory_block_words.some((word) => content.includes(word));
|
|
392
502
|
}
|
|
393
503
|
// 更新频道对话
|
|
394
504
|
async updateChannelDialogue(session, prompt, name) {
|
|
@@ -595,95 +705,6 @@ ${matched.map((entry) => entry.content).join("\n")}
|
|
|
595
705
|
}
|
|
596
706
|
};
|
|
597
707
|
|
|
598
|
-
// src/database.ts
|
|
599
|
-
async function isTargetIdNotExists(ctx, userId) {
|
|
600
|
-
const users = await ctx.database.get("p_system", { userid: userId });
|
|
601
|
-
return users.length === 0;
|
|
602
|
-
}
|
|
603
|
-
__name(isTargetIdNotExists, "isTargetIdNotExists");
|
|
604
|
-
async function createUser(ctx, user) {
|
|
605
|
-
await ctx.database.create("p_system", {
|
|
606
|
-
userid: user.userid || "",
|
|
607
|
-
usersname: user.usersname || "",
|
|
608
|
-
p: user.p || 0,
|
|
609
|
-
favorability: user.favorability || 0,
|
|
610
|
-
userlevel: user.userlevel || 0,
|
|
611
|
-
usage: user.usage || 0,
|
|
612
|
-
lastChatTime: user.lastChatTime || (/* @__PURE__ */ new Date()).getDate(),
|
|
613
|
-
items: user.items || {}
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
__name(createUser, "createUser");
|
|
617
|
-
async function updateFavorability(ctx, user, adjustment) {
|
|
618
|
-
if (!user)
|
|
619
|
-
return;
|
|
620
|
-
let newValue;
|
|
621
|
-
if (typeof adjustment === "number")
|
|
622
|
-
newValue = user.favorability + adjustment;
|
|
623
|
-
else
|
|
624
|
-
newValue = user.favorability;
|
|
625
|
-
await ctx.database.set("p_system", { userid: user.userid }, { favorability: newValue });
|
|
626
|
-
}
|
|
627
|
-
__name(updateFavorability, "updateFavorability");
|
|
628
|
-
async function updateUserLevel(ctx, user, level) {
|
|
629
|
-
if (!user)
|
|
630
|
-
return;
|
|
631
|
-
await ctx.database.set("p_system", { userid: user.userid }, { userlevel: level });
|
|
632
|
-
}
|
|
633
|
-
__name(updateUserLevel, "updateUserLevel");
|
|
634
|
-
async function updateUserUsage(ctx, user, adjustment = 1) {
|
|
635
|
-
if (!user)
|
|
636
|
-
return;
|
|
637
|
-
if (user.lastChatTime && (/* @__PURE__ */ new Date()).getDate() !== user.lastChatTime)
|
|
638
|
-
user.usage = 0;
|
|
639
|
-
await ctx.database.set("p_system", { userid: user.userid }, { usage: user.usage + adjustment });
|
|
640
|
-
await ctx.database.set("p_system", { userid: user.userid }, { lastChatTime: (/* @__PURE__ */ new Date()).getDate() });
|
|
641
|
-
return user.usage + adjustment;
|
|
642
|
-
}
|
|
643
|
-
__name(updateUserUsage, "updateUserUsage");
|
|
644
|
-
async function updateUserItems(ctx, user) {
|
|
645
|
-
if (!user)
|
|
646
|
-
return;
|
|
647
|
-
await ctx.database.set("p_system", { userid: user.userid }, { items: user.items });
|
|
648
|
-
}
|
|
649
|
-
__name(updateUserItems, "updateUserItems");
|
|
650
|
-
async function getUser(ctx, userId) {
|
|
651
|
-
const users = await ctx.database.get("p_system", { userid: userId });
|
|
652
|
-
return users[0] || null;
|
|
653
|
-
}
|
|
654
|
-
__name(getUser, "getUser");
|
|
655
|
-
async function ensureUserExists(ctx, userId, username) {
|
|
656
|
-
const notExists = await isTargetIdNotExists(ctx, userId);
|
|
657
|
-
if (notExists) {
|
|
658
|
-
await createUser(ctx, {
|
|
659
|
-
userid: userId,
|
|
660
|
-
usersname: username,
|
|
661
|
-
p: 0,
|
|
662
|
-
favorability: 0,
|
|
663
|
-
userlevel: 0,
|
|
664
|
-
usage: 0,
|
|
665
|
-
lastChatTime: (/* @__PURE__ */ new Date()).getDate(),
|
|
666
|
-
items: {}
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
return getUser(ctx, userId);
|
|
670
|
-
}
|
|
671
|
-
__name(ensureUserExists, "ensureUserExists");
|
|
672
|
-
function extendDatabase(ctx) {
|
|
673
|
-
ctx.model.extend("p_system", {
|
|
674
|
-
id: "unsigned",
|
|
675
|
-
userid: "string",
|
|
676
|
-
usersname: "string",
|
|
677
|
-
p: "integer",
|
|
678
|
-
favorability: "integer",
|
|
679
|
-
userlevel: "integer",
|
|
680
|
-
usage: "integer",
|
|
681
|
-
lastChatTime: "integer",
|
|
682
|
-
items: "object"
|
|
683
|
-
}, { autoInc: true });
|
|
684
|
-
}
|
|
685
|
-
__name(extendDatabase, "extendDatabase");
|
|
686
|
-
|
|
687
708
|
// src/fixed-dialogues.ts
|
|
688
709
|
var fs3 = __toESM(require("fs"));
|
|
689
710
|
var path3 = __toESM(require("path"));
|
|
@@ -1059,13 +1080,11 @@ var Sat = class extends import_koishi5.Service {
|
|
|
1059
1080
|
randnum: import_koishi5.Schema.number().role("slider").min(0).max(1).step(0.01).default(0).description("在群聊中随机触发对话的概率,如需关闭可设置为 0"),
|
|
1060
1081
|
sentences_divide: import_koishi5.Schema.boolean().default(true).description("是否分句发送"),
|
|
1061
1082
|
time_interval: import_koishi5.Schema.number().default(1e3).description("每句话的时间间隔"),
|
|
1062
|
-
reply_pointing: import_koishi5.Schema.boolean().default(true).description("是否在与多人同时对话时显示回复指向")
|
|
1063
|
-
enable_user_portrait: import_koishi5.Schema.boolean().default(true).description("是否启用用户画像功能"),
|
|
1064
|
-
portrait_min_dialogues: import_koishi5.Schema.number().default(100).description("触发画像生成的最小对话条数")
|
|
1083
|
+
reply_pointing: import_koishi5.Schema.boolean().default(true).description("是否在与多人同时对话时显示回复指向")
|
|
1065
1084
|
}).description("对话设置"),
|
|
1066
1085
|
import_koishi5.Schema.object({
|
|
1067
1086
|
enable_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度系统(每次对话默认+1好感度)"),
|
|
1068
|
-
max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(
|
|
1087
|
+
max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(引发好感度增长)对话次数上限"),
|
|
1069
1088
|
input_censor_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度审查(通过输入屏蔽词扣除好感)"),
|
|
1070
1089
|
value_of_input_favorability: import_koishi5.Schema.number().default(15).description("输入触发屏蔽词每次扣除的好感度"),
|
|
1071
1090
|
output_censor_favorability: import_koishi5.Schema.boolean().default(false).description("通过输出屏蔽词扣除好感,在dataDir中的output_censor.txt修改)"),
|
|
@@ -1073,6 +1092,9 @@ var Sat = class extends import_koishi5.Service {
|
|
|
1073
1092
|
enable_auxiliary_LLM: import_koishi5.Schema.boolean().default(false).description("是否使用辅助大模型判断好感度增减(量与输入屏蔽词每次扣除的好感度相关,不稳定,慎用)"),
|
|
1074
1093
|
offset_of_fafavorability: import_koishi5.Schema.number().default(3.5).description("辅助大模型好感度偏移量(越大越容易扣好感度)"),
|
|
1075
1094
|
visible_favorability: import_koishi5.Schema.boolean().default(true).description("是否开启好感度升降显示"),
|
|
1095
|
+
enable_user_portrait: import_koishi5.Schema.boolean().default(true).description("是否启用用户画像功能"),
|
|
1096
|
+
portrait_usage: import_koishi5.Schema.number().default(2).description("每天触发第几次对话后触发画像生成(最小为2)"),
|
|
1097
|
+
portrait_min_favorability: import_koishi5.Schema.number().default(100).description("触发画像生成的最小好感度"),
|
|
1076
1098
|
prompt_0: import_koishi5.Schema.string().role("textarea").description("厌恶好感补充设定"),
|
|
1077
1099
|
favorability_div_1: import_koishi5.Schema.number().default(15).description("厌恶-陌生分界线"),
|
|
1078
1100
|
prompt_1: import_koishi5.Schema.string().role("textarea").description("陌生好感补充设定"),
|
|
@@ -1090,8 +1112,154 @@ var Sat = class extends import_koishi5.Service {
|
|
|
1090
1112
|
]);
|
|
1091
1113
|
})(Sat || (Sat = {}));
|
|
1092
1114
|
|
|
1115
|
+
// src/userportrait.ts
|
|
1116
|
+
var import_koishi6 = require("koishi");
|
|
1117
|
+
var fs5 = __toESM(require("fs"));
|
|
1118
|
+
var path5 = __toESM(require("path"));
|
|
1119
|
+
var logger5 = new import_koishi6.Logger("satori-portrait");
|
|
1120
|
+
var UserPortraitManager = class {
|
|
1121
|
+
constructor(ctx, config) {
|
|
1122
|
+
this.ctx = ctx;
|
|
1123
|
+
this.config = config;
|
|
1124
|
+
}
|
|
1125
|
+
static {
|
|
1126
|
+
__name(this, "UserPortraitManager");
|
|
1127
|
+
}
|
|
1128
|
+
// 获取用户印象路径
|
|
1129
|
+
getPortraitPath(userId) {
|
|
1130
|
+
return path5.join(this.config.dataDir, "UserPortrait", `${userId}.txt`);
|
|
1131
|
+
}
|
|
1132
|
+
// 检查用户印象文件
|
|
1133
|
+
hasPortrait(userId) {
|
|
1134
|
+
return fs5.existsSync(this.getPortraitPath(userId));
|
|
1135
|
+
}
|
|
1136
|
+
// 读取用户印象
|
|
1137
|
+
readPortrait(userId) {
|
|
1138
|
+
try {
|
|
1139
|
+
return fs5.readFileSync(this.getPortraitPath(userId), "utf-8");
|
|
1140
|
+
} catch {
|
|
1141
|
+
return "";
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
// 获取长期记忆对话
|
|
1145
|
+
async getDialogues(user) {
|
|
1146
|
+
const userId = user.userid;
|
|
1147
|
+
const memoryPath = path5.join(this.config.dataDir, "dialogues", `${userId}.txt`);
|
|
1148
|
+
if (!fs5.existsSync(memoryPath))
|
|
1149
|
+
return [];
|
|
1150
|
+
const MemoryContent = await JSON.parse(fs5.readFileSync(memoryPath, "utf-8"));
|
|
1151
|
+
const dialoguesContent = MemoryContent.map((msg) => `${msg.content}${msg.role == "user" ? "(未记录时间)" : msg.role}`);
|
|
1152
|
+
const level = user.userlevel < 5 ? user.userlevel : 4;
|
|
1153
|
+
const usageLimit = this.config.max_usage[level] || 0;
|
|
1154
|
+
if (dialoguesContent.length > usageLimit) {
|
|
1155
|
+
dialoguesContent.splice(0, dialoguesContent.length - usageLimit);
|
|
1156
|
+
}
|
|
1157
|
+
return dialoguesContent;
|
|
1158
|
+
}
|
|
1159
|
+
// 生成提示词模板
|
|
1160
|
+
buildMessage(level, history, existingPortrait) {
|
|
1161
|
+
return [{
|
|
1162
|
+
role: "user",
|
|
1163
|
+
content: `你是一个角色扮演智能体工作流中的一环,请根据以下信息生成用户画像。需包括:
|
|
1164
|
+
1. 基本信息(性别、年龄、生日等)
|
|
1165
|
+
2. 生活习惯(作息、爱好等)
|
|
1166
|
+
3. 性格特征(用几句话描述)
|
|
1167
|
+
4. 交流偏好(常用语气词、话题倾向)
|
|
1168
|
+
5. 敏感点(明确表示反感的内容)
|
|
1169
|
+
6. 对AI的期望(从对话历史中推断)
|
|
1170
|
+
7. 其他你认为需要记录的信息(如有)
|
|
1171
|
+
|
|
1172
|
+
你与用户的关系:${level}
|
|
1173
|
+
历史画像:
|
|
1174
|
+
${existingPortrait ? "无" : existingPortrait}
|
|
1175
|
+
|
|
1176
|
+
近期的用户发言记录:
|
|
1177
|
+
${history}
|
|
1178
|
+
|
|
1179
|
+
注意事项:
|
|
1180
|
+
·需要综合历史画像和用户发言记录,避免重复信息或遗漏信息
|
|
1181
|
+
·基于事实推断,避免盲目相信或主观臆测
|
|
1182
|
+
·使用简洁的条目式表达,但内容要全面
|
|
1183
|
+
·保留不确定性的表述(如"可能"、"似乎")
|
|
1184
|
+
·保持中立和客观,避免带有个人情感色彩的描述,不要添加评价或建议
|
|
1185
|
+
·仅给出画像内容,不要添加额外的描述、建议、评价、注解等
|
|
1186
|
+
·不必使用标记语言,直接书写即可`
|
|
1187
|
+
}];
|
|
1188
|
+
}
|
|
1189
|
+
// 执行画像生成
|
|
1190
|
+
async generatePortrait(session, user, apiClient) {
|
|
1191
|
+
if (!this.config.enable_favorability)
|
|
1192
|
+
return;
|
|
1193
|
+
if (!this.config.enable_user_portrait)
|
|
1194
|
+
return;
|
|
1195
|
+
if (user.favorability < this.config.portrait_min_favorability)
|
|
1196
|
+
return;
|
|
1197
|
+
const dialogues = await this.getDialogues(user);
|
|
1198
|
+
const existing = this.readPortrait(user.userid);
|
|
1199
|
+
const userlevel = getFavorabilityLevel(user, this.getFavorabilityConfig());
|
|
1200
|
+
const messages = this.buildMessage(userlevel, dialogues.join("\n"), existing);
|
|
1201
|
+
logger5.info(`用户 ${user.userid} 画像生成中...`);
|
|
1202
|
+
logger5.info(`${messages[0].content}`);
|
|
1203
|
+
try {
|
|
1204
|
+
const response = await apiClient.generateUserPortrait(user, messages);
|
|
1205
|
+
if (response && !response.error) {
|
|
1206
|
+
this.savePortrait(user, response.content);
|
|
1207
|
+
if (user.usage > 0)
|
|
1208
|
+
session.send(`用户画像更新成功。`);
|
|
1209
|
+
logger5.success(`用户 ${user.userid} 画像更新成功`);
|
|
1210
|
+
}
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
logger5.error(`画像生成失败:${error.message}`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
savePortrait(user, portrait) {
|
|
1216
|
+
const filePath = this.getPortraitPath(user.userid);
|
|
1217
|
+
this.ensurePortraitFile(user.userid);
|
|
1218
|
+
fs5.writeFileSync(filePath, portrait);
|
|
1219
|
+
}
|
|
1220
|
+
ensurePortraitFile(userId) {
|
|
1221
|
+
const filePath = this.getPortraitPath(userId);
|
|
1222
|
+
if (!fs5.existsSync(filePath)) {
|
|
1223
|
+
fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
1224
|
+
fs5.writeFileSync(filePath, "");
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
// 获取用户画像
|
|
1228
|
+
getUserPortrait(session) {
|
|
1229
|
+
if (!this.config.enable_user_portrait)
|
|
1230
|
+
return "";
|
|
1231
|
+
const portrait = this.readPortrait(session.userId);
|
|
1232
|
+
return portrait ? `
|
|
1233
|
+
以下是当前用户的补充信息:{${portrait}
|
|
1234
|
+
}
|
|
1235
|
+
` : "";
|
|
1236
|
+
}
|
|
1237
|
+
getFavorabilityConfig() {
|
|
1238
|
+
return {
|
|
1239
|
+
enable_favorability: this.config.enable_favorability,
|
|
1240
|
+
dataDir: this.config.dataDir,
|
|
1241
|
+
max_favorability_perday: this.config.max_favorability_perday,
|
|
1242
|
+
input_censor_favorability: this.config.input_censor_favorability,
|
|
1243
|
+
value_of_input_favorability: this.config.value_of_input_favorability,
|
|
1244
|
+
output_censor_favorability: this.config.output_censor_favorability,
|
|
1245
|
+
value_of_output_favorability: this.config.value_of_output_favorability,
|
|
1246
|
+
enable_auxiliary_LLM: this.config.enable_auxiliary_LLM,
|
|
1247
|
+
offset_of_fafavorability: this.config.offset_of_fafavorability,
|
|
1248
|
+
prompt_0: this.config.prompt_0,
|
|
1249
|
+
favorability_div_1: this.config.favorability_div_1,
|
|
1250
|
+
prompt_1: this.config.prompt_1,
|
|
1251
|
+
favorability_div_2: this.config.favorability_div_2,
|
|
1252
|
+
prompt_2: this.config.prompt_2,
|
|
1253
|
+
favorability_div_3: this.config.favorability_div_3,
|
|
1254
|
+
prompt_3: this.config.prompt_3,
|
|
1255
|
+
favorability_div_4: this.config.favorability_div_4,
|
|
1256
|
+
prompt_4: this.config.prompt_4
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1093
1261
|
// src/index.tsx
|
|
1094
|
-
var
|
|
1262
|
+
var logger6 = new import_koishi7.Logger("satori-ai");
|
|
1095
1263
|
var SAT = class extends Sat {
|
|
1096
1264
|
// 重写构造函数
|
|
1097
1265
|
constructor(ctx, config) {
|
|
@@ -1100,7 +1268,8 @@ var SAT = class extends Sat {
|
|
|
1100
1268
|
ctx.i18n.define("zh", require_zh());
|
|
1101
1269
|
extendDatabase(ctx);
|
|
1102
1270
|
this.apiClient = new APIClient(ctx, this.getAPIConfig());
|
|
1103
|
-
this.memoryManager = new MemoryManager(this.getMemoryConfig());
|
|
1271
|
+
this.memoryManager = new MemoryManager(ctx, this.getMemoryConfig());
|
|
1272
|
+
this.portraitManager = new UserPortraitManager(ctx, config);
|
|
1104
1273
|
ensureCensorFileExists(this.config.dataDir);
|
|
1105
1274
|
ctx.middleware(createMiddleware(ctx, this, this.getMiddlewareConfig()));
|
|
1106
1275
|
this.registerCommands(ctx);
|
|
@@ -1110,7 +1279,7 @@ var SAT = class extends Sat {
|
|
|
1110
1279
|
}
|
|
1111
1280
|
apiClient;
|
|
1112
1281
|
memoryManager;
|
|
1113
|
-
|
|
1282
|
+
portraitManager;
|
|
1114
1283
|
ChannelParallelCount = /* @__PURE__ */ new Map();
|
|
1115
1284
|
onlineUsers = [];
|
|
1116
1285
|
getAPIConfig() {
|
|
@@ -1231,6 +1400,8 @@ var SAT = class extends Sat {
|
|
|
1231
1400
|
const response = await this.generateResponse(session, processedPrompt);
|
|
1232
1401
|
const auxiliaryResult = await this.handleAuxiliaryDialogue(session, processedPrompt, response);
|
|
1233
1402
|
await this.memoryManager.updateMemories(session, processedPrompt, this.getMemoryConfig(), response);
|
|
1403
|
+
if (user.usage == this.config.portrait_usage - 1)
|
|
1404
|
+
this.portraitManager.generatePortrait(session, user, this.apiClient);
|
|
1234
1405
|
return await this.formatResponse(session, response.content, auxiliaryResult);
|
|
1235
1406
|
}
|
|
1236
1407
|
// 处理辅助判断
|
|
@@ -1250,9 +1421,9 @@ var SAT = class extends Sat {
|
|
|
1250
1421
|
const messages = generateAuxiliaryPrompt(prompt, response.content, user, this.getFavorabilityConfig());
|
|
1251
1422
|
const result = await this.apiClient.auxiliaryChat(messages);
|
|
1252
1423
|
if (result.error) {
|
|
1253
|
-
|
|
1424
|
+
logger6.error(`辅助判断失败:${result.content}`);
|
|
1254
1425
|
} else {
|
|
1255
|
-
|
|
1426
|
+
logger6.info(`辅助判断:${result.content}`);
|
|
1256
1427
|
return handleAuxiliaryResult(this.ctx, session, this.getFavorabilityConfig(), result.content);
|
|
1257
1428
|
}
|
|
1258
1429
|
}
|
|
@@ -1266,7 +1437,7 @@ var SAT = class extends Sat {
|
|
|
1266
1437
|
}
|
|
1267
1438
|
// 前置检查
|
|
1268
1439
|
performPreChecks(session, prompt) {
|
|
1269
|
-
if (
|
|
1440
|
+
if (prompt === "")
|
|
1270
1441
|
return session.text("commands.sat.messages.no-prompt");
|
|
1271
1442
|
if (prompt.length > this.config.max_tokens)
|
|
1272
1443
|
return session.text("commands.sat.messages.tooLong");
|
|
@@ -1312,7 +1483,7 @@ var SAT = class extends Sat {
|
|
|
1312
1483
|
const usage = await updateUserUsage(this.ctx, user, adjustment);
|
|
1313
1484
|
if (user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description === "on")
|
|
1314
1485
|
return null;
|
|
1315
|
-
const level = user.userlevel
|
|
1486
|
+
const level = user.userlevel < 5 ? user.userlevel : 4;
|
|
1316
1487
|
const usageLimit = this.config.max_usage[level] || 0;
|
|
1317
1488
|
if (usage && usageLimit != 0 && usage > usageLimit) {
|
|
1318
1489
|
return session.text("commands.sat.messages.exceeds");
|
|
@@ -1339,13 +1510,13 @@ var SAT = class extends Sat {
|
|
|
1339
1510
|
await inputContentCheck(this.ctx, censored, session.userId, this.getFavorabilityConfig(), session);
|
|
1340
1511
|
}
|
|
1341
1512
|
if (this.config.log_ask_response)
|
|
1342
|
-
|
|
1513
|
+
logger6.info(`用户 ${session.username}:${processedPrompt}`);
|
|
1343
1514
|
return censored;
|
|
1344
1515
|
}
|
|
1345
1516
|
// 生成回复
|
|
1346
1517
|
async generateResponse(session, prompt) {
|
|
1347
1518
|
if (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
|
|
1348
|
-
|
|
1519
|
+
logger6.info(`频道 ${session.channelId} 并发数过高(${this.getChannelParallelCount(session)}),${session.username}等待中...`);
|
|
1349
1520
|
}
|
|
1350
1521
|
while (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
|
|
1351
1522
|
this.updateChannelParallelCount(session, -1);
|
|
@@ -1353,11 +1524,11 @@ var SAT = class extends Sat {
|
|
|
1353
1524
|
this.updateChannelParallelCount(session, 1);
|
|
1354
1525
|
}
|
|
1355
1526
|
const messages = this.buildMessages(session, prompt);
|
|
1356
|
-
|
|
1527
|
+
logger6.info(`频道 ${session.channelId} 处理:${session.userId},剩余${this.getChannelParallelCount(session)}并发`);
|
|
1357
1528
|
const user = await getUser(this.ctx, session.userId);
|
|
1358
1529
|
const response = await this.apiClient.chat(user, await messages);
|
|
1359
1530
|
if (this.config.log_ask_response)
|
|
1360
|
-
|
|
1531
|
+
logger6.info(`Satori AI:${response.content}`);
|
|
1361
1532
|
return response;
|
|
1362
1533
|
}
|
|
1363
1534
|
// 构建消息
|
|
@@ -1382,7 +1553,7 @@ var SAT = class extends Sat {
|
|
|
1382
1553
|
});
|
|
1383
1554
|
let payload = messages.map((msg) => msg.role + ":" + msg.content).join("\n");
|
|
1384
1555
|
if (this.config.log_system_prompt)
|
|
1385
|
-
|
|
1556
|
+
logger6.info(`系统提示:
|
|
1386
1557
|
${payload}`);
|
|
1387
1558
|
return messages;
|
|
1388
1559
|
}
|
|
@@ -1396,6 +1567,7 @@ ${payload}`);
|
|
|
1396
1567
|
systemPrompt += channelDialogue;
|
|
1397
1568
|
systemPrompt += userMemory;
|
|
1398
1569
|
const user = await getUser(this.ctx, session.userId);
|
|
1570
|
+
systemPrompt += this.portraitManager.getUserPortrait(session);
|
|
1399
1571
|
const nickName = user.items["情侣合照"]?.metadata?.userNickName;
|
|
1400
1572
|
systemPrompt += `用户的名字是:${session.username}, id是:${session.userId}`;
|
|
1401
1573
|
if (nickName)
|
|
@@ -1430,7 +1602,7 @@ ${payload}`);
|
|
|
1430
1602
|
response = `@${session.username} ` + response;
|
|
1431
1603
|
}
|
|
1432
1604
|
if (this.config.sentences_divide && response.length > 10) {
|
|
1433
|
-
const sentences = splitSentences(response).map((text) =>
|
|
1605
|
+
const sentences = splitSentences(response).map((text) => import_koishi7.h.text(text));
|
|
1434
1606
|
for (const sentence of sentences) {
|
|
1435
1607
|
await session.send(sentence);
|
|
1436
1608
|
await new Promise((resolve2) => setTimeout(resolve2, this.config.time_interval));
|
|
@@ -1458,7 +1630,7 @@ ${payload}`);
|
|
|
1458
1630
|
async addCommonSense(session, content) {
|
|
1459
1631
|
if (!content)
|
|
1460
1632
|
return session.text("commands.sat.common_sense.messages.no-prompt");
|
|
1461
|
-
const filePath =
|
|
1633
|
+
const filePath = path6.join(this.config.dataDir, "common_sense.txt");
|
|
1462
1634
|
await this.memoryManager.saveLongTermMemory(session, [{
|
|
1463
1635
|
role: "user",
|
|
1464
1636
|
content
|
|
@@ -1471,6 +1643,9 @@ ${payload}`);
|
|
|
1471
1643
|
const level = options.level || 1;
|
|
1472
1644
|
const user = await ensureUserExists(this.ctx, userId, session.username);
|
|
1473
1645
|
await updateUserLevel(this.ctx, user, level);
|
|
1646
|
+
const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
|
|
1647
|
+
if (enableUserKey)
|
|
1648
|
+
await this.portraitManager.generatePortrait(session, user, this.apiClient);
|
|
1474
1649
|
return session.text("commands.sat.messages.update_level_succeed", [level]);
|
|
1475
1650
|
}
|
|
1476
1651
|
// 处理查询用户使用次数
|
package/lib/memory.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Session } from 'koishi';
|
|
1
|
+
import { Session, Context } from 'koishi';
|
|
2
2
|
import { MemoryEntry, MemoryConfig } from './types';
|
|
3
3
|
export declare class MemoryManager {
|
|
4
|
+
private ctx;
|
|
4
5
|
private config;
|
|
5
6
|
private channelMemories;
|
|
6
7
|
private channelDialogues;
|
|
7
|
-
constructor(config: MemoryConfig);
|
|
8
|
+
constructor(ctx: Context, config: MemoryConfig);
|
|
8
9
|
updateMemories(session: Session, prompt: string, config: MemoryConfig, response: {
|
|
9
10
|
content: string;
|
|
10
11
|
error: boolean;
|
package/lib/types.d.ts
CHANGED
|
@@ -189,8 +189,6 @@ export declare namespace Sat {
|
|
|
189
189
|
time_interval: number;
|
|
190
190
|
max_parallel_count: number;
|
|
191
191
|
reply_pointing: boolean;
|
|
192
|
-
enable_user_portrait: boolean;
|
|
193
|
-
portrait_min_dialogues: number;
|
|
194
192
|
enable_favorability: boolean;
|
|
195
193
|
max_favorability_perday: number;
|
|
196
194
|
input_censor_favorability: boolean;
|
|
@@ -200,6 +198,9 @@ export declare namespace Sat {
|
|
|
200
198
|
enable_auxiliary_LLM: boolean;
|
|
201
199
|
offset_of_fafavorability: number;
|
|
202
200
|
visible_favorability: boolean;
|
|
201
|
+
enable_user_portrait: boolean;
|
|
202
|
+
portrait_usage: number;
|
|
203
|
+
portrait_min_favorability: number;
|
|
203
204
|
prompt_0: string;
|
|
204
205
|
favorability_div_1: number;
|
|
205
206
|
prompt_1: string;
|
package/lib/userportrait.d.ts
CHANGED
|
@@ -8,9 +8,11 @@ export declare class UserPortraitManager {
|
|
|
8
8
|
private getPortraitPath;
|
|
9
9
|
hasPortrait(userId: string): boolean;
|
|
10
10
|
readPortrait(userId: string): string;
|
|
11
|
-
private
|
|
12
|
-
private
|
|
11
|
+
private getDialogues;
|
|
12
|
+
private buildMessage;
|
|
13
13
|
generatePortrait(session: Session, user: User, apiClient: APIClient): Promise<void>;
|
|
14
|
-
|
|
14
|
+
private savePortrait;
|
|
15
|
+
private ensurePortraitFile;
|
|
16
|
+
getUserPortrait(session: Session): string;
|
|
15
17
|
private getFavorabilityConfig;
|
|
16
18
|
}
|