From f17adee4bafb7ab9f3f324f5a66f7aec7fca15fd Mon Sep 17 00:00:00 2001 From: jie65535 Date: Fri, 22 May 2026 14:05:13 +0800 Subject: [PATCH] Extend favorability into user profile system --- src/main/kotlin/JChatGPT.kt | 37 ++++-- src/main/kotlin/PluginData.kt | 17 ++- .../tools/AdjustUserFavorabilityAgent.kt | 107 ++++++++++++------ 3 files changed, 112 insertions(+), 49 deletions(-) diff --git a/src/main/kotlin/JChatGPT.kt b/src/main/kotlin/JChatGPT.kt index 4225d03..3f2f20a 100644 --- a/src/main/kotlin/JChatGPT.kt +++ b/src/main/kotlin/JChatGPT.kt @@ -318,17 +318,27 @@ object JChatGPT : KotlinPlugin( var lastId = 0L if (event is GroupMessageEvent) { if (PluginConfig.enableFavorabilitySystem) { - val favorabilityInfos = history.map { it.fromId } + val knownUsers = history.asSequence() + .map { it.fromId } .filter { it != event.bot.id } .distinct() .mapNotNull { PluginData.userFavorability[it] } - if (favorabilityInfos.isNotEmpty()) { - historyText.appendLine("## 相关成员的好感信息") - for (info in favorabilityInfos) { - historyText.append(getNameCard(event.group, info.userId)).append('\t') - .appendLine(info).appendLine() + .filter { it.name.isNotEmpty() || it.tags.isNotEmpty() || it.impression.isNotEmpty() } + .sortedBy { it.userId } + .toList() + if (knownUsers.isNotEmpty()) { + historyText.appendLine("【你认识的群友】") + for (info in knownUsers) { + val displayName = if (info.name.isNotEmpty()) info.name + else getNameCard(event.subject, info.userId) + historyText.append("- ").append(displayName) + .append("(${info.userId})") + .append(" 好感度${if (info.value >= 0) "+" else ""}${info.value}") + if (info.tags.isNotEmpty()) historyText.append(" [${info.tags.joinToString(", ")}]") + if (info.impression.isNotEmpty()) historyText.append(" ${info.impression}") + historyText.appendLine() } - historyText.appendLine("---").appendLine() + historyText.appendLine() } } @@ -341,10 +351,15 @@ object JChatGPT : KotlinPlugin( } else { if (PluginConfig.enableFavorabilitySystem) { val favorabilityInfo = PluginData.userFavorability[event.sender.id] - if (favorabilityInfo != null) { - historyText.append("你对\"").append(event.senderName).append("\"的好感信息如下: ") - .appendLine(favorabilityInfo).appendLine() - historyText.appendLine("---").appendLine() + if (favorabilityInfo != null && (favorabilityInfo.name.isNotEmpty() || favorabilityInfo.tags.isNotEmpty() || favorabilityInfo.impression.isNotEmpty())) { + val displayName = if (favorabilityInfo.name.isNotEmpty()) favorabilityInfo.name else event.senderName + historyText.appendLine("【你认识的对方】") + historyText.append("- ").append(displayName) + .append("(${event.sender.id})") + .append(" 好感度${if (favorabilityInfo.value >= 0) "+" else ""}${favorabilityInfo.value}") + if (favorabilityInfo.tags.isNotEmpty()) historyText.append(" [${favorabilityInfo.tags.joinToString(", ")}]") + if (favorabilityInfo.impression.isNotEmpty()) historyText.append(" ${favorabilityInfo.impression}") + historyText.appendLine().appendLine() } } diff --git a/src/main/kotlin/PluginData.kt b/src/main/kotlin/PluginData.kt index 7da3ffc..7583c6b 100644 --- a/src/main/kotlin/PluginData.kt +++ b/src/main/kotlin/PluginData.kt @@ -10,22 +10,27 @@ import net.mamoe.mirai.console.data.value * @param value 好感度值 (-100 ~ 100) * @param reasons 调整原因列表,用于溯源 * @param impression 对用户的印象/画像 + * @param name Bot给此人起的代号 + * @param tags 标签列表,最多5个 */ @Serializable data class FavorabilityInfo( val userId: Long, val value: Int = 0, val reasons: List = emptyList(), - val impression: String = "" + val impression: String = "", + val name: String = "", + val tags: List = emptyList() ) { override fun toString(): String { return buildString { append("好感度:$value") - if (impression.isNotEmpty()) { - append("\t印象:$impression") - } + if (name.isNotEmpty()) append(",代号:$name") + if (tags.isNotEmpty()) append(",标签:[${tags.joinToString(", ")}]") + if (impression.isNotEmpty()) append(",印象:$impression") if (reasons.isNotEmpty()) { - appendLine("\t调整原因:") + appendLine() + appendLine("调整原因:") reasons.forEach { reason -> appendLine("* $reason") } @@ -99,4 +104,4 @@ object PluginData : AutoSavePluginData("data") { .replace("\n\n", "\n") } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/tools/AdjustUserFavorabilityAgent.kt b/src/main/kotlin/tools/AdjustUserFavorabilityAgent.kt index e52e90d..2130480 100644 --- a/src/main/kotlin/tools/AdjustUserFavorabilityAgent.kt +++ b/src/main/kotlin/tools/AdjustUserFavorabilityAgent.kt @@ -2,6 +2,7 @@ package top.jie65535.mirai.tools import com.aallam.openai.api.chat.Tool import com.aallam.openai.api.core.Parameters +import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonObject @@ -11,6 +12,7 @@ import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.longOrNull import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject import net.mamoe.mirai.event.events.MessageEvent import top.jie65535.mirai.JChatGPT import top.jie65535.mirai.PluginData @@ -21,9 +23,20 @@ import java.time.format.DateTimeFormatter class AdjustUserFavorabilityAgent : BaseAgent( tool = Tool.function( name = "adjustUserFavorability", - description = "可根据网友行为调整对其好感度,范围从-100到100。" + - "默认为0表示陌生人,100表示非常好的朋友,-100表示已拉黑。" + - "当好感度低于0时,有一定概率忽略该用户的消息,-100则100%忽略其消息。", + description = """ + 维护你对群友的认识(好感度、印象、标签、代号)。 + 每次和某人有实质交流后建议调用一次,可与发言工具在同一轮发出,几乎不产生额外成本。 + + 触发场景: + - 首次有像样的对话(建立初始印象和代号) + - 对方透露身份/职业/偏好/技术栈(加 tag) + - 互动产生明显情绪变化——开心/被逗/被冒犯(调 change) + - 已有印象明显不准(更新 impression) + + change 默认为 0,只更新标签/印象时不用填 reason。 + tags 上限 5 个,满了须先 tags_remove 旧标签才能继续添加。 + 好感度范围 -100 到 100,低于 0 时有概率忽略其消息,-100 则 100% 忽略。 + """.trimIndent(), parameters = Parameters.buildJsonObject { put("type", "object") put("properties", buildJsonObject { @@ -33,21 +46,33 @@ class AdjustUserFavorabilityAgent : BaseAgent( }) put("change", buildJsonObject { put("type", "integer") - put("description", "好感度变化值(可正可负)") + put("description", "好感度变化值(可正可负),默认为0") }) put("reason", buildJsonObject { put("type", "string") - put("description", "调整原因(供日志记录和溯源)") + put("description", "调整原因(change!=0时建议填写,供溯源)") }) put("impression", buildJsonObject { put("type", "string") - put("description", "对用户的印象或称呼(可选)") + put("description", "对用户的印象描述,覆盖旧值(上限200字符)") + }) + put("name", buildJsonObject { + put("type", "string") + put("description", "Bot给此人起的代号(非QQ昵称,上限20字符)") + }) + put("tags_add", buildJsonObject { + put("type", "array") + putJsonObject("items") { put("type", "string") } + put("description", "追加标签(自动去重,总数超5项返错,单项上限20字符)") + }) + put("tags_remove", buildJsonObject { + put("type", "array") + putJsonObject("items") { put("type", "string") } + put("description", "删除标签(不存在的项静默忽略)") }) }) putJsonArray("required") { add("userId") - add("change") - add("reason") } } ) @@ -56,48 +81,66 @@ class AdjustUserFavorabilityAgent : BaseAgent( requireNotNull(args) val userId = args["userId"]?.jsonPrimitive?.longOrNull - val change = args["change"]?.jsonPrimitive?.intOrNull + ?: return "错误:userId参数不能为空" + + val change = args["change"]?.jsonPrimitive?.intOrNull ?: 0 val reason = args["reason"]?.jsonPrimitive?.contentOrNull val impression = args["impression"]?.jsonPrimitive?.contentOrNull + val name = args["name"]?.jsonPrimitive?.contentOrNull + val tagsAdd = (args["tags_add"] as? JsonArray)?.mapNotNull { it.jsonPrimitive.contentOrNull } + val tagsRemove = (args["tags_remove"] as? JsonArray)?.mapNotNull { it.jsonPrimitive.contentOrNull } - if (userId == null || change == null || reason == null) { - return "错误:userId、change和reason参数不能为空" + // 字段长度校验 + if (name != null && name.length > 20) return "错误:name不能超过20字符(当前${name.length}字符)" + if (impression != null && impression.length > 200) return "错误:impression不能超过200字符(当前${impression.length}字符)" + tagsAdd?.forEach { tag -> + if (tag.length > 20) return "错误:tag「$tag」不能超过20字符" } - // 获取当前好感度信息 val currentInfo = PluginData.userFavorability[userId] ?: FavorabilityInfo(userId) val currentValue = currentInfo.value - // 计算新的好感度值,限制在-100~100范围内 val newValue = (currentValue + change).coerceIn(-100, 100) - // 更新原因列表 - val timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") - val newReason = "${timeFormatter.format(OffsetDateTime.now())}: $reason" - val newReasons = if (currentInfo.reasons.size >= 10) { - // 保留最近的10条原因记录 - (currentInfo.reasons.drop(1) + newReason) - } else { - (currentInfo.reasons + newReason) + // 只在 change != 0 时记录原因 + val newReasons = if (change != 0 && reason != null) { + val timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + val newReason = "${timeFormatter.format(OffsetDateTime.now())}: $reason" + if (currentInfo.reasons.size >= 10) { + currentInfo.reasons.drop(1) + newReason + } else { + currentInfo.reasons + newReason + } + } else currentInfo.reasons + + // 处理标签 + val newTags = currentInfo.tags.toMutableList() + tagsRemove?.forEach { tag -> newTags.remove(tag) } + if (tagsAdd != null) { + val toAdd = tagsAdd.filter { it !in newTags } + if (newTags.size + toAdd.size > 5) { + return "错误:标签已满(当前${newTags.size}项),须先用tags_remove删除旧标签。当前标签:[${newTags.joinToString(", ")}]" + } + newTags.addAll(toAdd) } - // 更新印象/画像 - val newImpression = impression ?: currentInfo.impression - - // 创建新的好感度信息 val newInfo = FavorabilityInfo( userId = userId, value = newValue, reasons = newReasons, - impression = newImpression + impression = impression ?: currentInfo.impression, + name = name ?: currentInfo.name, + tags = newTags ) - // 更新好感度 PluginData.userFavorability[userId] = newInfo + JChatGPT.logger.info("用户 $userId 画像已更新:好感度($currentValue -> $newValue),原因:$reason") - // 记录日志 - JChatGPT.logger.info("用户 $userId 的好感度 ($currentValue -> $newValue),原因:$reason") - - return "用户 $userId 的好感度已更新为 $newValue" + return buildString { + append("用户 $userId 画像已更新:好感度=$newValue") + if (newTags.isNotEmpty()) append(",标签=[${newTags.joinToString(", ")}]") + if (newInfo.name.isNotEmpty()) append(",代号=${newInfo.name}") + if (newInfo.impression.isNotEmpty()) append(",印象=${newInfo.impression}") + } } -} \ No newline at end of file +}