koishi-plugin-satori-ai 1.2.1 → 1.2.4
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 +313 -123
- package/lib/memory.d.ts +3 -2
- package/lib/types.d.ts +4 -2
- package/lib/userportrait.d.ts +5 -3
- package/package.json +2 -2
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,19 +208,28 @@ 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) {
|
|
231
|
+
if (user.userid == "Alice")
|
|
232
|
+
return { content: "测试画像", error: false };
|
|
222
233
|
const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
|
|
223
234
|
let keys;
|
|
224
235
|
let modle;
|
|
@@ -234,16 +245,24 @@ var APIClient = class {
|
|
|
234
245
|
baseURL = this.config.baseURL;
|
|
235
246
|
}
|
|
236
247
|
const payload = this.createAuxiliaryPayload(messages, modle);
|
|
237
|
-
|
|
248
|
+
const url = `${trimSlash(baseURL)}/chat/completions`;
|
|
249
|
+
const headers = this.createHeaders(keys);
|
|
250
|
+
let content;
|
|
251
|
+
for (let i = 1; i <= this.config.maxRetryTimes; i++) {
|
|
238
252
|
try {
|
|
239
|
-
|
|
253
|
+
const response = await this.ctx.http.post(url, payload, { headers, timeout: 36e5 });
|
|
254
|
+
content = response.choices[0].message.content;
|
|
255
|
+
return { content, error: false };
|
|
240
256
|
} catch (error) {
|
|
241
|
-
if (i ==
|
|
257
|
+
if (i == this.config.maxRetryTimes) {
|
|
242
258
|
return this.handleAPIError(error);
|
|
243
259
|
}
|
|
244
|
-
this.
|
|
260
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.config.retry_delay_time || 5e3));
|
|
261
|
+
logger2.warn(`生成画像时API请求失败(${error}),重试(第${i}次)中...`);
|
|
262
|
+
continue;
|
|
245
263
|
}
|
|
246
264
|
}
|
|
265
|
+
return { content: "unreachable", error: true };
|
|
247
266
|
}
|
|
248
267
|
// 生成请求体
|
|
249
268
|
createPayload(messages, model) {
|
|
@@ -290,7 +309,7 @@ var APIClient = class {
|
|
|
290
309
|
const responseMsg = { role: "assistant", content };
|
|
291
310
|
if (payload.messages.some((msg) => msg === responseMsg) && content.length > 5) {
|
|
292
311
|
const lastMsg = payload.messages[payload.messages.length - 1];
|
|
293
|
-
payload.messages[payload.messages.length - 1].content = lastMsg.content + "(
|
|
312
|
+
payload.messages[payload.messages.length - 1].content = lastMsg.content + "(注意不要重复之前的内容)";
|
|
294
313
|
logger2.warn(`返回内容与之前内容相同,重试(第${i}次)中...`);
|
|
295
314
|
continue;
|
|
296
315
|
}
|
|
@@ -362,9 +381,101 @@ var APIClient = class {
|
|
|
362
381
|
var import_koishi3 = require("koishi");
|
|
363
382
|
var fs2 = __toESM(require("fs"));
|
|
364
383
|
var path2 = __toESM(require("path"));
|
|
384
|
+
|
|
385
|
+
// src/database.ts
|
|
386
|
+
async function isTargetIdNotExists(ctx, userId) {
|
|
387
|
+
const users = await ctx.database.get("p_system", { userid: userId });
|
|
388
|
+
return users.length === 0;
|
|
389
|
+
}
|
|
390
|
+
__name(isTargetIdNotExists, "isTargetIdNotExists");
|
|
391
|
+
async function createUser(ctx, user) {
|
|
392
|
+
await ctx.database.create("p_system", {
|
|
393
|
+
userid: user.userid || "",
|
|
394
|
+
usersname: user.usersname || "",
|
|
395
|
+
p: user.p || 0,
|
|
396
|
+
favorability: user.favorability || 0,
|
|
397
|
+
userlevel: user.userlevel || 0,
|
|
398
|
+
usage: user.usage || 0,
|
|
399
|
+
lastChatTime: user.lastChatTime || (/* @__PURE__ */ new Date()).getDate(),
|
|
400
|
+
items: user.items || {}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
__name(createUser, "createUser");
|
|
404
|
+
async function updateFavorability(ctx, user, adjustment) {
|
|
405
|
+
if (!user)
|
|
406
|
+
return;
|
|
407
|
+
let newValue;
|
|
408
|
+
if (typeof adjustment === "number")
|
|
409
|
+
newValue = user.favorability + adjustment;
|
|
410
|
+
else
|
|
411
|
+
newValue = user.favorability;
|
|
412
|
+
await ctx.database.set("p_system", { userid: user.userid }, { favorability: newValue });
|
|
413
|
+
}
|
|
414
|
+
__name(updateFavorability, "updateFavorability");
|
|
415
|
+
async function updateUserLevel(ctx, user, level) {
|
|
416
|
+
if (!user)
|
|
417
|
+
return;
|
|
418
|
+
await ctx.database.set("p_system", { userid: user.userid }, { userlevel: level });
|
|
419
|
+
}
|
|
420
|
+
__name(updateUserLevel, "updateUserLevel");
|
|
421
|
+
async function updateUserUsage(ctx, user, adjustment = 1) {
|
|
422
|
+
if (!user)
|
|
423
|
+
return;
|
|
424
|
+
if (user.lastChatTime && (/* @__PURE__ */ new Date()).getDate() !== user.lastChatTime)
|
|
425
|
+
user.usage = 0;
|
|
426
|
+
await ctx.database.set("p_system", { userid: user.userid }, { usage: user.usage + adjustment });
|
|
427
|
+
await ctx.database.set("p_system", { userid: user.userid }, { lastChatTime: (/* @__PURE__ */ new Date()).getDate() });
|
|
428
|
+
return user.usage + adjustment;
|
|
429
|
+
}
|
|
430
|
+
__name(updateUserUsage, "updateUserUsage");
|
|
431
|
+
async function updateUserItems(ctx, user) {
|
|
432
|
+
if (!user)
|
|
433
|
+
return;
|
|
434
|
+
await ctx.database.set("p_system", { userid: user.userid }, { items: user.items });
|
|
435
|
+
}
|
|
436
|
+
__name(updateUserItems, "updateUserItems");
|
|
437
|
+
async function getUser(ctx, userId) {
|
|
438
|
+
const users = await ctx.database.get("p_system", { userid: userId });
|
|
439
|
+
return users[0] || null;
|
|
440
|
+
}
|
|
441
|
+
__name(getUser, "getUser");
|
|
442
|
+
async function ensureUserExists(ctx, userId, username) {
|
|
443
|
+
const notExists = await isTargetIdNotExists(ctx, userId);
|
|
444
|
+
if (notExists) {
|
|
445
|
+
await createUser(ctx, {
|
|
446
|
+
userid: userId,
|
|
447
|
+
usersname: username,
|
|
448
|
+
p: 0,
|
|
449
|
+
favorability: 0,
|
|
450
|
+
userlevel: 0,
|
|
451
|
+
usage: 0,
|
|
452
|
+
lastChatTime: (/* @__PURE__ */ new Date()).getDate(),
|
|
453
|
+
items: {}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return getUser(ctx, userId);
|
|
457
|
+
}
|
|
458
|
+
__name(ensureUserExists, "ensureUserExists");
|
|
459
|
+
function extendDatabase(ctx) {
|
|
460
|
+
ctx.model.extend("p_system", {
|
|
461
|
+
id: "unsigned",
|
|
462
|
+
userid: "string",
|
|
463
|
+
usersname: "string",
|
|
464
|
+
p: "integer",
|
|
465
|
+
favorability: "integer",
|
|
466
|
+
userlevel: "integer",
|
|
467
|
+
usage: "integer",
|
|
468
|
+
lastChatTime: "integer",
|
|
469
|
+
items: "object"
|
|
470
|
+
}, { autoInc: true });
|
|
471
|
+
}
|
|
472
|
+
__name(extendDatabase, "extendDatabase");
|
|
473
|
+
|
|
474
|
+
// src/memory.ts
|
|
365
475
|
var logger3 = new import_koishi3.Logger("satori-memory");
|
|
366
476
|
var MemoryManager = class {
|
|
367
|
-
constructor(config) {
|
|
477
|
+
constructor(ctx, config) {
|
|
478
|
+
this.ctx = ctx;
|
|
368
479
|
this.config = config;
|
|
369
480
|
}
|
|
370
481
|
static {
|
|
@@ -379,7 +490,8 @@ var MemoryManager = class {
|
|
|
379
490
|
this.updateChannelMemory(session, prompt, config, response.content);
|
|
380
491
|
prompt = prompt.replace("我", session.username);
|
|
381
492
|
const date = ` (对话日期和时间:${(/* @__PURE__ */ new Date()).toLocaleString()})`;
|
|
382
|
-
|
|
493
|
+
const userFavourbility = await (await getUser(this.ctx, session.userId)).favorability;
|
|
494
|
+
if (this.shouldRemember(prompt, userFavourbility)) {
|
|
383
495
|
await this.saveLongTermMemory(session, [{
|
|
384
496
|
role: date,
|
|
385
497
|
content: prompt
|
|
@@ -387,8 +499,8 @@ var MemoryManager = class {
|
|
|
387
499
|
}
|
|
388
500
|
}
|
|
389
501
|
// 是否应当记忆
|
|
390
|
-
shouldRemember(content) {
|
|
391
|
-
return (content.length >= this.config.remember_min_length || content.includes("记住")) && !this.config.memory_block_words.some((word) => content.includes(word));
|
|
502
|
+
shouldRemember(content, userFavourbility) {
|
|
503
|
+
return (content.length >= this.config.remember_min_length || content.includes("记住") && userFavourbility >= 50) && !this.config.memory_block_words.some((word) => content.includes(word));
|
|
392
504
|
}
|
|
393
505
|
// 更新频道对话
|
|
394
506
|
async updateChannelDialogue(session, prompt, name) {
|
|
@@ -595,95 +707,6 @@ ${matched.map((entry) => entry.content).join("\n")}
|
|
|
595
707
|
}
|
|
596
708
|
};
|
|
597
709
|
|
|
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
710
|
// src/fixed-dialogues.ts
|
|
688
711
|
var fs3 = __toESM(require("fs"));
|
|
689
712
|
var path3 = __toESM(require("path"));
|
|
@@ -1059,13 +1082,11 @@ var Sat = class extends import_koishi5.Service {
|
|
|
1059
1082
|
randnum: import_koishi5.Schema.number().role("slider").min(0).max(1).step(0.01).default(0).description("在群聊中随机触发对话的概率,如需关闭可设置为 0"),
|
|
1060
1083
|
sentences_divide: import_koishi5.Schema.boolean().default(true).description("是否分句发送"),
|
|
1061
1084
|
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("触发画像生成的最小对话条数")
|
|
1085
|
+
reply_pointing: import_koishi5.Schema.boolean().default(true).description("是否在与多人同时对话时显示回复指向")
|
|
1065
1086
|
}).description("对话设置"),
|
|
1066
1087
|
import_koishi5.Schema.object({
|
|
1067
1088
|
enable_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度系统(每次对话默认+1好感度)"),
|
|
1068
|
-
max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(
|
|
1089
|
+
max_favorability_perday: import_koishi5.Schema.number().default(100).description("每日有效(引发好感度增长)对话次数上限"),
|
|
1069
1090
|
input_censor_favorability: import_koishi5.Schema.boolean().default(false).description("是否开启好感度审查(通过输入屏蔽词扣除好感)"),
|
|
1070
1091
|
value_of_input_favorability: import_koishi5.Schema.number().default(15).description("输入触发屏蔽词每次扣除的好感度"),
|
|
1071
1092
|
output_censor_favorability: import_koishi5.Schema.boolean().default(false).description("通过输出屏蔽词扣除好感,在dataDir中的output_censor.txt修改)"),
|
|
@@ -1073,6 +1094,10 @@ var Sat = class extends import_koishi5.Service {
|
|
|
1073
1094
|
enable_auxiliary_LLM: import_koishi5.Schema.boolean().default(false).description("是否使用辅助大模型判断好感度增减(量与输入屏蔽词每次扣除的好感度相关,不稳定,慎用)"),
|
|
1074
1095
|
offset_of_fafavorability: import_koishi5.Schema.number().default(3.5).description("辅助大模型好感度偏移量(越大越容易扣好感度)"),
|
|
1075
1096
|
visible_favorability: import_koishi5.Schema.boolean().default(true).description("是否开启好感度升降显示"),
|
|
1097
|
+
enable_user_portrait: import_koishi5.Schema.boolean().default(true).description("是否启用用户画像功能"),
|
|
1098
|
+
portrait_usage: import_koishi5.Schema.number().default(2).description("每天触发第几次对话后触发画像生成(最小为2)"),
|
|
1099
|
+
max_portrait_dialogues: import_koishi5.Schema.number().default(100).description("画像生成获取的最大对话条数"),
|
|
1100
|
+
portrait_min_favorability: import_koishi5.Schema.number().default(100).description("触发画像生成的最小好感度"),
|
|
1076
1101
|
prompt_0: import_koishi5.Schema.string().role("textarea").description("厌恶好感补充设定"),
|
|
1077
1102
|
favorability_div_1: import_koishi5.Schema.number().default(15).description("厌恶-陌生分界线"),
|
|
1078
1103
|
prompt_1: import_koishi5.Schema.string().role("textarea").description("陌生好感补充设定"),
|
|
@@ -1090,8 +1115,155 @@ var Sat = class extends import_koishi5.Service {
|
|
|
1090
1115
|
]);
|
|
1091
1116
|
})(Sat || (Sat = {}));
|
|
1092
1117
|
|
|
1118
|
+
// src/userportrait.ts
|
|
1119
|
+
var import_koishi6 = require("koishi");
|
|
1120
|
+
var fs5 = __toESM(require("fs"));
|
|
1121
|
+
var path5 = __toESM(require("path"));
|
|
1122
|
+
var logger5 = new import_koishi6.Logger("satori-portrait");
|
|
1123
|
+
var UserPortraitManager = class {
|
|
1124
|
+
constructor(ctx, config) {
|
|
1125
|
+
this.ctx = ctx;
|
|
1126
|
+
this.config = config;
|
|
1127
|
+
}
|
|
1128
|
+
static {
|
|
1129
|
+
__name(this, "UserPortraitManager");
|
|
1130
|
+
}
|
|
1131
|
+
// 获取用户印象路径
|
|
1132
|
+
getPortraitPath(userId) {
|
|
1133
|
+
return path5.join(this.config.dataDir, "UserPortrait", `${userId}.txt`);
|
|
1134
|
+
}
|
|
1135
|
+
// 检查用户印象文件
|
|
1136
|
+
hasPortrait(userId) {
|
|
1137
|
+
return fs5.existsSync(this.getPortraitPath(userId));
|
|
1138
|
+
}
|
|
1139
|
+
// 读取用户印象
|
|
1140
|
+
readPortrait(userId) {
|
|
1141
|
+
try {
|
|
1142
|
+
return fs5.readFileSync(this.getPortraitPath(userId), "utf-8");
|
|
1143
|
+
} catch {
|
|
1144
|
+
return "";
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
// 获取长期记忆对话
|
|
1148
|
+
async getDialogues(user) {
|
|
1149
|
+
const userId = user.userid;
|
|
1150
|
+
const memoryPath = path5.join(this.config.dataDir, "dialogues", `${userId}.txt`);
|
|
1151
|
+
if (!fs5.existsSync(memoryPath))
|
|
1152
|
+
return [];
|
|
1153
|
+
const MemoryContent = await JSON.parse(fs5.readFileSync(memoryPath, "utf-8"));
|
|
1154
|
+
const dialoguesContent = MemoryContent.map((msg) => `${msg.content}${msg.role == "user" ? "(未记录时间)" : msg.role}`);
|
|
1155
|
+
const level = user.userlevel < 5 ? user.userlevel : 4;
|
|
1156
|
+
const usageLimit = this.config.max_usage[level] == 0 ? this.config.max_portrait_dialogues : this.config.max_usage[level];
|
|
1157
|
+
if (dialoguesContent.length >= usageLimit || dialoguesContent.length >= this.config.max_portrait_dialogues) {
|
|
1158
|
+
dialoguesContent.splice(0, dialoguesContent.length - Math.min(usageLimit, this.config.max_portrait_dialogues));
|
|
1159
|
+
}
|
|
1160
|
+
return dialoguesContent;
|
|
1161
|
+
}
|
|
1162
|
+
// 生成提示词模板
|
|
1163
|
+
buildMessage(level, history, existingPortrait) {
|
|
1164
|
+
return [{
|
|
1165
|
+
role: "user",
|
|
1166
|
+
content: `你是一个角色扮演智能体工作流中的一环,请根据以下信息生成用户画像。需包括:
|
|
1167
|
+
1. 基本信息(性别、年龄、生日等)
|
|
1168
|
+
2. 生活习惯(作息、爱好等)
|
|
1169
|
+
3. 性格特征(用几句话描述)
|
|
1170
|
+
4. 交流偏好(常用语气词、话题倾向)
|
|
1171
|
+
5. 敏感点(明确表示反感的内容)
|
|
1172
|
+
6. 对AI的期望(从对话历史中推断)
|
|
1173
|
+
7. 其他你认为需要记录的信息(如有)
|
|
1174
|
+
|
|
1175
|
+
你与用户的关系:${level}
|
|
1176
|
+
历史画像:
|
|
1177
|
+
${existingPortrait ? "无" : existingPortrait}
|
|
1178
|
+
|
|
1179
|
+
近期的用户发言记录:
|
|
1180
|
+
${history}
|
|
1181
|
+
|
|
1182
|
+
注意事项:
|
|
1183
|
+
·用户一定是人类,其他情况是角色扮演
|
|
1184
|
+
·需要综合历史画像和用户发言记录,避免重复信息或遗漏信息,并根据发言修正历史画像内容
|
|
1185
|
+
·基于事实推断,避免盲目相信或主观臆测
|
|
1186
|
+
·使用简洁的条目式表达,但内容要全面
|
|
1187
|
+
·保留不确定性的表述(如"可能"、"似乎"、"用户自称")
|
|
1188
|
+
·保持中立和客观,避免带有个人情感色彩的描述,不要添加评价或建议
|
|
1189
|
+
·仅给出画像内容,不要添加额外的描述、建议、评价、注解等
|
|
1190
|
+
·不使用markdown等标记语言,直接书写即可`
|
|
1191
|
+
}];
|
|
1192
|
+
}
|
|
1193
|
+
// 执行画像生成
|
|
1194
|
+
async generatePortrait(session, user, apiClient) {
|
|
1195
|
+
if (!this.config.enable_favorability)
|
|
1196
|
+
return;
|
|
1197
|
+
if (!this.config.enable_user_portrait)
|
|
1198
|
+
return;
|
|
1199
|
+
if (user.favorability < this.config.portrait_min_favorability)
|
|
1200
|
+
return;
|
|
1201
|
+
const dialogues = await this.getDialogues(user);
|
|
1202
|
+
const existing = this.readPortrait(user.userid);
|
|
1203
|
+
const userlevel = getFavorabilityLevel(user, this.getFavorabilityConfig());
|
|
1204
|
+
const messages = this.buildMessage(userlevel, dialogues.join("\n"), existing);
|
|
1205
|
+
logger5.info(`用户 ${user.userid} 画像生成中...`);
|
|
1206
|
+
logger5.info(`${messages[0].content}`);
|
|
1207
|
+
try {
|
|
1208
|
+
const response = await apiClient.generateUserPortrait(user, messages);
|
|
1209
|
+
if (response && !response.error) {
|
|
1210
|
+
this.savePortrait(user, response.content);
|
|
1211
|
+
if (user.usage > this.config.portrait_usage - 1)
|
|
1212
|
+
session.send("用户画像更新成功。");
|
|
1213
|
+
logger5.success(`用户 ${user.userid} 画像更新成功`);
|
|
1214
|
+
}
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
logger5.error(`画像生成失败:${error.message}`);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
savePortrait(user, portrait) {
|
|
1220
|
+
const filePath = this.getPortraitPath(user.userid);
|
|
1221
|
+
this.ensurePortraitFile(user.userid);
|
|
1222
|
+
fs5.writeFileSync(filePath, portrait);
|
|
1223
|
+
}
|
|
1224
|
+
ensurePortraitFile(userId) {
|
|
1225
|
+
const filePath = this.getPortraitPath(userId);
|
|
1226
|
+
if (!fs5.existsSync(filePath)) {
|
|
1227
|
+
fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
1228
|
+
fs5.writeFileSync(filePath, "");
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
// 获取用户画像
|
|
1232
|
+
getUserPortrait(session) {
|
|
1233
|
+
if (!this.config.enable_user_portrait)
|
|
1234
|
+
return "";
|
|
1235
|
+
const portrait = this.readPortrait(session.userId);
|
|
1236
|
+
return portrait ? `
|
|
1237
|
+
以下是当前用户的补充信息:{${portrait}
|
|
1238
|
+
}
|
|
1239
|
+
` : "";
|
|
1240
|
+
}
|
|
1241
|
+
getFavorabilityConfig() {
|
|
1242
|
+
return {
|
|
1243
|
+
enable_favorability: this.config.enable_favorability,
|
|
1244
|
+
dataDir: this.config.dataDir,
|
|
1245
|
+
max_favorability_perday: this.config.max_favorability_perday,
|
|
1246
|
+
input_censor_favorability: this.config.input_censor_favorability,
|
|
1247
|
+
value_of_input_favorability: this.config.value_of_input_favorability,
|
|
1248
|
+
output_censor_favorability: this.config.output_censor_favorability,
|
|
1249
|
+
value_of_output_favorability: this.config.value_of_output_favorability,
|
|
1250
|
+
enable_auxiliary_LLM: this.config.enable_auxiliary_LLM,
|
|
1251
|
+
offset_of_fafavorability: this.config.offset_of_fafavorability,
|
|
1252
|
+
prompt_0: this.config.prompt_0,
|
|
1253
|
+
favorability_div_1: this.config.favorability_div_1,
|
|
1254
|
+
prompt_1: this.config.prompt_1,
|
|
1255
|
+
favorability_div_2: this.config.favorability_div_2,
|
|
1256
|
+
prompt_2: this.config.prompt_2,
|
|
1257
|
+
favorability_div_3: this.config.favorability_div_3,
|
|
1258
|
+
prompt_3: this.config.prompt_3,
|
|
1259
|
+
favorability_div_4: this.config.favorability_div_4,
|
|
1260
|
+
prompt_4: this.config.prompt_4
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1093
1265
|
// src/index.tsx
|
|
1094
|
-
var
|
|
1266
|
+
var logger6 = new import_koishi7.Logger("satori-ai");
|
|
1095
1267
|
var SAT = class extends Sat {
|
|
1096
1268
|
// 重写构造函数
|
|
1097
1269
|
constructor(ctx, config) {
|
|
@@ -1100,7 +1272,8 @@ var SAT = class extends Sat {
|
|
|
1100
1272
|
ctx.i18n.define("zh", require_zh());
|
|
1101
1273
|
extendDatabase(ctx);
|
|
1102
1274
|
this.apiClient = new APIClient(ctx, this.getAPIConfig());
|
|
1103
|
-
this.memoryManager = new MemoryManager(this.getMemoryConfig());
|
|
1275
|
+
this.memoryManager = new MemoryManager(ctx, this.getMemoryConfig());
|
|
1276
|
+
this.portraitManager = new UserPortraitManager(ctx, config);
|
|
1104
1277
|
ensureCensorFileExists(this.config.dataDir);
|
|
1105
1278
|
ctx.middleware(createMiddleware(ctx, this, this.getMiddlewareConfig()));
|
|
1106
1279
|
this.registerCommands(ctx);
|
|
@@ -1110,7 +1283,7 @@ var SAT = class extends Sat {
|
|
|
1110
1283
|
}
|
|
1111
1284
|
apiClient;
|
|
1112
1285
|
memoryManager;
|
|
1113
|
-
|
|
1286
|
+
portraitManager;
|
|
1114
1287
|
ChannelParallelCount = /* @__PURE__ */ new Map();
|
|
1115
1288
|
onlineUsers = [];
|
|
1116
1289
|
getAPIConfig() {
|
|
@@ -1231,6 +1404,8 @@ var SAT = class extends Sat {
|
|
|
1231
1404
|
const response = await this.generateResponse(session, processedPrompt);
|
|
1232
1405
|
const auxiliaryResult = await this.handleAuxiliaryDialogue(session, processedPrompt, response);
|
|
1233
1406
|
await this.memoryManager.updateMemories(session, processedPrompt, this.getMemoryConfig(), response);
|
|
1407
|
+
if (user.usage == this.config.portrait_usage - 1)
|
|
1408
|
+
this.portraitManager.generatePortrait(session, user, this.apiClient);
|
|
1234
1409
|
return await this.formatResponse(session, response.content, auxiliaryResult);
|
|
1235
1410
|
}
|
|
1236
1411
|
// 处理辅助判断
|
|
@@ -1250,9 +1425,9 @@ var SAT = class extends Sat {
|
|
|
1250
1425
|
const messages = generateAuxiliaryPrompt(prompt, response.content, user, this.getFavorabilityConfig());
|
|
1251
1426
|
const result = await this.apiClient.auxiliaryChat(messages);
|
|
1252
1427
|
if (result.error) {
|
|
1253
|
-
|
|
1428
|
+
logger6.error(`辅助判断失败:${result.content}`);
|
|
1254
1429
|
} else {
|
|
1255
|
-
|
|
1430
|
+
logger6.info(`辅助判断:${result.content}`);
|
|
1256
1431
|
return handleAuxiliaryResult(this.ctx, session, this.getFavorabilityConfig(), result.content);
|
|
1257
1432
|
}
|
|
1258
1433
|
}
|
|
@@ -1266,7 +1441,7 @@ var SAT = class extends Sat {
|
|
|
1266
1441
|
}
|
|
1267
1442
|
// 前置检查
|
|
1268
1443
|
performPreChecks(session, prompt) {
|
|
1269
|
-
if (
|
|
1444
|
+
if (prompt === "")
|
|
1270
1445
|
return session.text("commands.sat.messages.no-prompt");
|
|
1271
1446
|
if (prompt.length > this.config.max_tokens)
|
|
1272
1447
|
return session.text("commands.sat.messages.tooLong");
|
|
@@ -1312,7 +1487,7 @@ var SAT = class extends Sat {
|
|
|
1312
1487
|
const usage = await updateUserUsage(this.ctx, user, adjustment);
|
|
1313
1488
|
if (user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description === "on")
|
|
1314
1489
|
return null;
|
|
1315
|
-
const level = user.userlevel
|
|
1490
|
+
const level = user.userlevel < 5 ? user.userlevel : 4;
|
|
1316
1491
|
const usageLimit = this.config.max_usage[level] || 0;
|
|
1317
1492
|
if (usage && usageLimit != 0 && usage > usageLimit) {
|
|
1318
1493
|
return session.text("commands.sat.messages.exceeds");
|
|
@@ -1339,13 +1514,13 @@ var SAT = class extends Sat {
|
|
|
1339
1514
|
await inputContentCheck(this.ctx, censored, session.userId, this.getFavorabilityConfig(), session);
|
|
1340
1515
|
}
|
|
1341
1516
|
if (this.config.log_ask_response)
|
|
1342
|
-
|
|
1517
|
+
logger6.info(`用户 ${session.username}:${processedPrompt}`);
|
|
1343
1518
|
return censored;
|
|
1344
1519
|
}
|
|
1345
1520
|
// 生成回复
|
|
1346
1521
|
async generateResponse(session, prompt) {
|
|
1347
1522
|
if (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
|
|
1348
|
-
|
|
1523
|
+
logger6.info(`频道 ${session.channelId} 并发数过高(${this.getChannelParallelCount(session)}),${session.username}等待中...`);
|
|
1349
1524
|
}
|
|
1350
1525
|
while (this.getChannelParallelCount(session) > this.config.max_parallel_count) {
|
|
1351
1526
|
this.updateChannelParallelCount(session, -1);
|
|
@@ -1353,11 +1528,11 @@ var SAT = class extends Sat {
|
|
|
1353
1528
|
this.updateChannelParallelCount(session, 1);
|
|
1354
1529
|
}
|
|
1355
1530
|
const messages = this.buildMessages(session, prompt);
|
|
1356
|
-
|
|
1531
|
+
logger6.info(`频道 ${session.channelId} 处理:${session.userId},剩余${this.getChannelParallelCount(session)}并发`);
|
|
1357
1532
|
const user = await getUser(this.ctx, session.userId);
|
|
1358
1533
|
const response = await this.apiClient.chat(user, await messages);
|
|
1359
1534
|
if (this.config.log_ask_response)
|
|
1360
|
-
|
|
1535
|
+
logger6.info(`Satori AI:${response.content}`);
|
|
1361
1536
|
return response;
|
|
1362
1537
|
}
|
|
1363
1538
|
// 构建消息
|
|
@@ -1382,7 +1557,7 @@ var SAT = class extends Sat {
|
|
|
1382
1557
|
});
|
|
1383
1558
|
let payload = messages.map((msg) => msg.role + ":" + msg.content).join("\n");
|
|
1384
1559
|
if (this.config.log_system_prompt)
|
|
1385
|
-
|
|
1560
|
+
logger6.info(`系统提示:
|
|
1386
1561
|
${payload}`);
|
|
1387
1562
|
return messages;
|
|
1388
1563
|
}
|
|
@@ -1396,6 +1571,7 @@ ${payload}`);
|
|
|
1396
1571
|
systemPrompt += channelDialogue;
|
|
1397
1572
|
systemPrompt += userMemory;
|
|
1398
1573
|
const user = await getUser(this.ctx, session.userId);
|
|
1574
|
+
systemPrompt += this.portraitManager.getUserPortrait(session);
|
|
1399
1575
|
const nickName = user.items["情侣合照"]?.metadata?.userNickName;
|
|
1400
1576
|
systemPrompt += `用户的名字是:${session.username}, id是:${session.userId}`;
|
|
1401
1577
|
if (nickName)
|
|
@@ -1430,7 +1606,7 @@ ${payload}`);
|
|
|
1430
1606
|
response = `@${session.username} ` + response;
|
|
1431
1607
|
}
|
|
1432
1608
|
if (this.config.sentences_divide && response.length > 10) {
|
|
1433
|
-
const sentences = splitSentences(response).map((text) =>
|
|
1609
|
+
const sentences = splitSentences(response).map((text) => import_koishi7.h.text(text));
|
|
1434
1610
|
for (const sentence of sentences) {
|
|
1435
1611
|
await session.send(sentence);
|
|
1436
1612
|
await new Promise((resolve2) => setTimeout(resolve2, this.config.time_interval));
|
|
@@ -1458,7 +1634,7 @@ ${payload}`);
|
|
|
1458
1634
|
async addCommonSense(session, content) {
|
|
1459
1635
|
if (!content)
|
|
1460
1636
|
return session.text("commands.sat.common_sense.messages.no-prompt");
|
|
1461
|
-
const filePath =
|
|
1637
|
+
const filePath = path6.join(this.config.dataDir, "common_sense.txt");
|
|
1462
1638
|
await this.memoryManager.saveLongTermMemory(session, [{
|
|
1463
1639
|
role: "user",
|
|
1464
1640
|
content
|
|
@@ -1470,6 +1646,9 @@ ${payload}`);
|
|
|
1470
1646
|
const userId = options.id || session.userId;
|
|
1471
1647
|
const level = options.level || 1;
|
|
1472
1648
|
const user = await ensureUserExists(this.ctx, userId, session.username);
|
|
1649
|
+
const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
|
|
1650
|
+
if (enableUserKey)
|
|
1651
|
+
await this.portraitManager.generatePortrait(session, user, this.apiClient);
|
|
1473
1652
|
await updateUserLevel(this.ctx, user, level);
|
|
1474
1653
|
return session.text("commands.sat.messages.update_level_succeed", [level]);
|
|
1475
1654
|
}
|
|
@@ -1478,7 +1657,18 @@ ${payload}`);
|
|
|
1478
1657
|
const user = await ensureUserExists(this.ctx, session.userId, session.username);
|
|
1479
1658
|
const userUsage = user.usage || 0;
|
|
1480
1659
|
const maxUsage = this.config.max_usage[user.userlevel || 0] || 0;
|
|
1481
|
-
|
|
1660
|
+
const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
|
|
1661
|
+
let result = "";
|
|
1662
|
+
result += "用户等级:" + user.userlevel + "\n";
|
|
1663
|
+
if (enableUserKey)
|
|
1664
|
+
result += session.text("commands.sat.messages.usage", [userUsage, "∞"]) + "\n地灵殿通行证生效中\n";
|
|
1665
|
+
else
|
|
1666
|
+
result += session.text("commands.sat.messages.usage", [userUsage, maxUsage]) + "\n";
|
|
1667
|
+
if (this.portraitManager.hasPortrait(user.userid))
|
|
1668
|
+
result += "用户画像生效中\n";
|
|
1669
|
+
else
|
|
1670
|
+
result += "用户画像未生效\n";
|
|
1671
|
+
return result;
|
|
1482
1672
|
}
|
|
1483
1673
|
// 随机中间件转接
|
|
1484
1674
|
async handleRandomMiddleware(session, prompt) {
|
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,10 @@ 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
|
+
max_portrait_dialogues: number;
|
|
203
|
+
portrait_usage: number;
|
|
204
|
+
portrait_min_favorability: number;
|
|
203
205
|
prompt_0: string;
|
|
204
206
|
favorability_div_1: number;
|
|
205
207
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-satori-ai",
|
|
3
|
-
"description": "基于 Satori
|
|
4
|
-
"version": "1.2.
|
|
3
|
+
"description": "基于 Satori 适配器的智能对话插件,具备用户画像、长期记忆、常识管理和好感度系统",
|
|
4
|
+
"version": "1.2.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"types": "lib/index.d.ts",
|