feat(planner): 重构动作规划器以支持组合动作和概率性表情

重写了 Planner 的主 Prompt,引导 LLM 将回复(reply)视为主动作,将表情(emoji)等视为辅助动作,从而更好地生成组合动作,使响应更生动。

- 移除了旧的“100%概率动作强制添加”逻辑,并替换为新的“概率性表情”机制。现在,当生成回复时,会根据配置的概率(emoji_chance)自动附加一个 emoji 动作。
- 改进了 emoji 动作的情感匹配逻辑,从精确匹配改为模糊匹配,提高了根据 LLM 输出找到合适表情的成功率。
- 修复了随机类型动作在激活概率计算时的一个边界条件问题。
This commit is contained in:
tt-P607
2025-09-09 18:50:37 +08:00
committed by Windpicker-owo
parent 97df1011d1
commit 47ee2ba693
3 changed files with 64 additions and 28 deletions

View File

@@ -205,9 +205,8 @@ class ActionModifier:
elif activation_type == ActionActivationType.RANDOM: elif activation_type == ActionActivationType.RANDOM:
probability = action_info.random_activation_probability probability = action_info.random_activation_probability
if probability >= 1.0: probability = action_info.random_activation_probability
continue # 概率为100%或更高,直接激活 if random.random() >= probability:
if random.random() > probability:
reason = f"RANDOM类型未触发概率{probability}" reason = f"RANDOM类型未触发概率{probability}"
deactivated_actions.append((action_name, reason)) deactivated_actions.append((action_name, reason))
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: {reason}") logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: {reason}")

View File

@@ -57,30 +57,64 @@ def init_prompt():
{moderation_prompt} {moderation_prompt}
现在请你根据聊天内容和用户的最新消息选择合适的action和触发action的消息: **任务: 构建一个完整的响应**
你的任务是根据当前的聊天内容,构建一个完整的、人性化的响应。一个完整的响应由两部分组成:
1. **主要动作**: 这是响应的核心,通常是 `reply`(文本回复)。
2. **辅助动作 (可选)**: 这是为了增强表达效果的附加动作,例如 `emoji`(发送表情包)或 `poke_user`(戳一戳)。
**决策流程:**
1. 首先,决定是否要进行 `reply`。
2. 然后,评估当前的对话气氛和用户情绪,判断是否需要一个**辅助动作**来让你的回应更生动、更符合你的性格。
3. 如果需要,选择一个最合适的辅助动作与 `reply` 组合。
4. 如果用户明确要求了某个动作,请务必优先满足。
**可用动作:**
{actions_before_now_block} {actions_before_now_block}
{no_action_block} {no_action_block}
动作reply 动作reply
动作描述:参与聊天回复,发送文本进行表达 动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附 - 你想要闲聊或者随便附
- {mentioned_bonus} - {mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应 - 如果你刚刚进行了回复,不要对同一个话题重复回应
- 不要回复自己发送的消息 - 不要回复自己发送的消息
{{ {{
"action": "reply", "action": "reply",
"target_message_id":"触发action的消息id", "target_message_id": "触发action的消息id",
"reason":"回复的原因" "reason": "回复的原因"
}} }}
{action_options_text} {action_options_text}
- 如果用户明确要求使用某个动作,请优先选择该动作。
- 当一个动作可以作为另一个动作的补充时你应该同时选择它们。例如在回复的同时可以发送表情包emoji
你必须从上面列出的可用action中选择一个或多个并说明触发action的消息id不是消息原文和选择该action的原因。消息id格式:m+数字
请根据动作示例,以严格的 JSON 格式输出返回一个包含所有选定动作的JSON列表。如果只选择一个动作也请将其包含在列表中。如果没有任何合适的动作返回一个空列表[]。不要输出markdown格式```json等内容直接输出且仅包含 JSON 列表内容: **输出格式:**
你必须以严格的 JSON 格式输出返回一个包含所有选定动作的JSON列表。如果没有任何合适的动作返回一个空列表[]。
**单动作示例 (仅回复):**
[
{{
"action": "reply",
"target_message_id": "m123",
"reason": "回答用户的问题"
}}
]
**组合动作示例 (回复 + 表情包):**
[
{{
"action": "reply",
"target_message_id": "m123",
"reason": "回答用户的问题"
}},
{{
"action": "emoji",
"target_message_id": "m123",
"reason": "用一个可爱的表情来缓和气氛"
}}
]
不要输出markdown格式```json等内容直接输出且仅包含 JSON 列表内容:
""", """,
"planner_prompt", "planner_prompt",
) )
@@ -148,9 +182,9 @@ def init_prompt():
动作描述:{action_description} 动作描述:{action_description}
{action_require} {action_require}
{{ {{
"action": "{action_name}",{action_parameters}, "action": "{action_name}",
"target_message_id":"触发action的消息id", "target_message_id": "触发action的消息id",
"reason":"触发action的原因" "reason": "触发action的原因"{action_parameters}
}} }}
""", """,
"action_prompt", "action_prompt",
@@ -409,18 +443,18 @@ class ActionPlanner:
# --- 3. 后处理 --- # --- 3. 后处理 ---
final_actions = self._filter_no_actions(final_actions) final_actions = self._filter_no_actions(final_actions)
# === 强制后处理确保100%概率的动作在回复时被附带 === # === 概率模式后处理:根据配置决定是否强制添加 emoji 动作 ===
if global_config.emoji.emoji_activate_type == 'random':
has_reply_action = any(a.get("action_type") == "reply" for a in final_actions) has_reply_action = any(a.get("action_type") == "reply" for a in final_actions)
if has_reply_action: if has_reply_action:
for action_name, action_info in available_actions.items():
if action_info.activation_type == ActionActivationType.RANDOM and action_info.random_activation_probability >= 1.0:
# 检查此动作是否已被选择 # 检查此动作是否已被选择
is_already_chosen = any(a.get("action_type") == action_name for a in final_actions) is_already_chosen = any(a.get("action_type") == 'emoji' for a in final_actions)
if not is_already_chosen: if not is_already_chosen:
logger.info(f"{self.log_prefix}强制添加100%概率动作: {action_name}") if random.random() < global_config.emoji.emoji_chance:
logger.info(f"{self.log_prefix}根据概率 '{global_config.emoji.emoji_chance}' 添加 emoji 动作")
final_actions.append({ final_actions.append({
"action_type": action_name, "action_type": 'emoji',
"reasoning": "根据100%概率设置强制添加", "reasoning": f"根据概率 {global_config.emoji.emoji_chance} 自动添加",
"action_data": {}, "action_data": {},
"action_message": self.get_latest_message(used_message_id_list), "action_message": self.get_latest_message(used_message_id_list),
"available_actions": available_actions, "available_actions": available_actions,

View File

@@ -143,9 +143,12 @@ class EmojiAction(BaseAction):
chosen_emotion = chosen_emotion.strip().replace('"', "").replace("'", "") chosen_emotion = chosen_emotion.strip().replace('"', "").replace("'", "")
logger.info(f"{self.log_prefix} LLM选择的情感: {chosen_emotion}") logger.info(f"{self.log_prefix} LLM选择的情感: {chosen_emotion}")
if chosen_emotion in emotion_map: # 使用模糊匹配来查找最相关的情感标签
emoji_base64, emoji_description = random.choice(emotion_map[chosen_emotion]) matched_key = next((key for key in emotion_map if chosen_emotion in key), None)
logger.info(f"{self.log_prefix} 找到匹配情感 '{chosen_emotion}' 的表情包: {emoji_description}")
if matched_key:
emoji_base64, emoji_description = random.choice(emotion_map[matched_key])
logger.info(f"{self.log_prefix} 找到匹配情感 '{chosen_emotion}' (匹配到: '{matched_key}') 的表情包: {emoji_description}")
else: else:
logger.warning( logger.warning(
f"{self.log_prefix} LLM选择的情感 '{chosen_emotion}' 不在可用列表中, 将随机选择一个表情包" f"{self.log_prefix} LLM选择的情感 '{chosen_emotion}' 不在可用列表中, 将随机选择一个表情包"