refactor(emoji): 重构表情发送逻辑为基于情感标签选择
重构了`emoji`动作的实现,将原有的LLM选择表情描述的逻辑,改为先由LLM根据聊天内容和理由选择一个最匹配的“情感标签”,然后再从该标签下的表情库中随机选择一个进行发送。 主要变更: - 移除原有的表情抽样、编号和LLM选择编号的复杂流程。 - 引入基于`emotion`元数据的情感标签映射。 - 更新LLM的Prompt,使其专注于选择情感标签而非具体表情。 - 简化了代码逻辑,移除了不再需要的历史记录队列和相关配置项。 - 如果表情没有情感标签或LLM调用失败,则回退到随机发送。
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
import random
|
import random
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from collections import deque
|
|
||||||
import json
|
|
||||||
|
|
||||||
# 导入新插件系统
|
# 导入新插件系统
|
||||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
||||||
@@ -22,7 +20,6 @@ logger = get_logger("emoji")
|
|||||||
class EmojiAction(BaseAction):
|
class EmojiAction(BaseAction):
|
||||||
"""表情动作 - 发送表情包"""
|
"""表情动作 - 发送表情包"""
|
||||||
|
|
||||||
# --- 类级别属性 ---
|
|
||||||
# 激活设置
|
# 激活设置
|
||||||
if global_config.emoji.emoji_activate_type == "llm":
|
if global_config.emoji.emoji_activate_type == "llm":
|
||||||
activation_type = ActionActivationType.LLM_JUDGE
|
activation_type = ActionActivationType.LLM_JUDGE
|
||||||
@@ -37,9 +34,6 @@ class EmojiAction(BaseAction):
|
|||||||
action_name = "emoji"
|
action_name = "emoji"
|
||||||
action_description = "发送表情包辅助表达情绪"
|
action_description = "发送表情包辅助表达情绪"
|
||||||
|
|
||||||
# 最近发送表情的历史记录
|
|
||||||
_sent_emoji_history = deque(maxlen=4)
|
|
||||||
|
|
||||||
# LLM判断提示词
|
# LLM判断提示词
|
||||||
llm_judge_prompt = """
|
llm_judge_prompt = """
|
||||||
判定是否需要使用表情动作的条件:
|
判定是否需要使用表情动作的条件:
|
||||||
@@ -80,22 +74,36 @@ class EmojiAction(BaseAction):
|
|||||||
logger.warning(f"{self.log_prefix} 无法获取任何带有描述的有效表情包")
|
logger.warning(f"{self.log_prefix} 无法获取任何带有描述的有效表情包")
|
||||||
return False, "无法获取任何带有描述的有效表情包"
|
return False, "无法获取任何带有描述的有效表情包"
|
||||||
|
|
||||||
# 3. 根据新配置项决定抽样数量
|
# 3. 准备情感数据和后备列表
|
||||||
sample_size = global_config.emoji.max_context_emojis
|
emotion_map = {}
|
||||||
if sample_size > 0 and len(all_emojis_obj) > sample_size:
|
all_emojis_data = []
|
||||||
sampled_emojis = random.sample(all_emojis_obj, sample_size)
|
|
||||||
|
for emoji in all_emojis_obj:
|
||||||
|
b64 = image_path_to_base64(emoji.full_path)
|
||||||
|
if not b64:
|
||||||
|
continue
|
||||||
|
|
||||||
|
desc = emoji.description
|
||||||
|
emotions = emoji.emotion
|
||||||
|
all_emojis_data.append((b64, desc))
|
||||||
|
|
||||||
|
for emo in emotions:
|
||||||
|
if emo not in emotion_map:
|
||||||
|
emotion_map[emo] = []
|
||||||
|
emotion_map[emo].append((b64, desc))
|
||||||
|
|
||||||
|
if not all_emojis_data:
|
||||||
|
logger.warning(f"{self.log_prefix} 无法加载任何有效的表情包数据")
|
||||||
|
return False, "无法加载任何有效的表情包数据"
|
||||||
|
|
||||||
|
available_emotions = list(emotion_map.keys())
|
||||||
|
emoji_base64, emoji_description = "", ""
|
||||||
|
|
||||||
|
if not available_emotions:
|
||||||
|
logger.warning(f"{self.log_prefix} 获取到的表情包均无情感标签, 将随机发送")
|
||||||
|
emoji_base64, emoji_description = random.choice(all_emojis_data)
|
||||||
else:
|
else:
|
||||||
sampled_emojis = all_emojis_obj # 0表示全部
|
# 获取最近的5条消息内容用于判断
|
||||||
|
|
||||||
# 4. 为抽样的表情包创建带编号的描述列表
|
|
||||||
prompt_emoji_list = []
|
|
||||||
for i, emoji in enumerate(sampled_emojis):
|
|
||||||
prompt_emoji_list.append(f"{i + 1}. {emoji.description}")
|
|
||||||
|
|
||||||
prompt_emoji_str = "\n".join(prompt_emoji_list)
|
|
||||||
chosen_emoji_obj: MaiEmoji = None
|
|
||||||
|
|
||||||
# 5. 获取最近的5条消息内容用于判断
|
|
||||||
recent_messages = message_api.get_recent_messages(chat_id=self.chat_id, limit=5)
|
recent_messages = message_api.get_recent_messages(chat_id=self.chat_id, limit=5)
|
||||||
messages_text = ""
|
messages_text = ""
|
||||||
if recent_messages:
|
if recent_messages:
|
||||||
@@ -106,74 +114,61 @@ class EmojiAction(BaseAction):
|
|||||||
show_actions=False,
|
show_actions=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 6. 构建prompt让LLM选择编号
|
# 4. 构建prompt让LLM选择情感
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
你是一个正在进行聊天的网友,你需要根据一个理由和最近的聊天记录,从一个带编号的表情包描述列表中选择最匹配的 **3个** 表情包,并按匹配度从高到低返回它们的编号。
|
你是一个正在进行聊天的网友,你需要根据一个理由和最近的聊天记录,从一个情感标签列表中选择最匹配的一个。
|
||||||
这是最近的聊天记录:
|
这是最近的聊天记录:
|
||||||
{messages_text}
|
{messages_text}
|
||||||
|
|
||||||
这是理由:“{reason}”
|
这是理由:“{reason}”
|
||||||
这里是可用的表情包详细描述列表:
|
这里是可用的情感标签:{available_emotions}
|
||||||
{prompt_emoji_str}
|
请直接返回最匹配的那个情感标签,不要进行任何解释或添加其他多余的文字。
|
||||||
请直接返回一个包含3个最匹配表情包编号的有序JSON列表,例如:[10, 2, 5],不要进行任何解释或添加其他多余的文字。
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 7. 调用LLM
|
if global_config.debug.show_prompt:
|
||||||
|
logger.info(f"{self.log_prefix} 生成的LLM Prompt: {prompt}")
|
||||||
|
else:
|
||||||
|
logger.debug(f"{self.log_prefix} 生成的LLM Prompt: {prompt}")
|
||||||
|
|
||||||
|
# 5. 调用LLM
|
||||||
models = llm_api.get_available_models()
|
models = llm_api.get_available_models()
|
||||||
chat_model_config = models.get("planner")
|
chat_model_config = models.get("planner")
|
||||||
if not chat_model_config:
|
if not chat_model_config:
|
||||||
logger.error(f"{self.log_prefix} 未找到 'planner' 模型配置,无法调用LLM")
|
logger.error(f"{self.log_prefix} 未找到'utils_small'模型配置,无法调用LLM")
|
||||||
return False, "未找到 'planner' 模型配置"
|
return False, "未找到'utils_small'模型配置"
|
||||||
|
|
||||||
success, chosen_indices_str, _, _ = await llm_api.generate_with_model(
|
success, chosen_emotion, _, _ = await llm_api.generate_with_model(
|
||||||
prompt, model_config=chat_model_config, request_type="emoji_selection"
|
prompt, model_config=chat_model_config, request_type="emoji"
|
||||||
)
|
)
|
||||||
|
|
||||||
selected_emoji_obj = None
|
|
||||||
if success:
|
|
||||||
try:
|
|
||||||
chosen_indices = json.loads(chosen_indices_str)
|
|
||||||
if isinstance(chosen_indices, list):
|
|
||||||
logger.info(f"{self.log_prefix} LLM选择的表情编号候选项: {chosen_indices}")
|
|
||||||
for index in chosen_indices:
|
|
||||||
if isinstance(index, int) and 1 <= index <= len(sampled_emojis):
|
|
||||||
candidate_emoji = sampled_emojis[index - 1]
|
|
||||||
if candidate_emoji.hash not in self._sent_emoji_history:
|
|
||||||
selected_emoji_obj = candidate_emoji
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.warning(f"{self.log_prefix} LLM返回的不是一个列表: {chosen_indices_str}")
|
|
||||||
except (json.JSONDecodeError, TypeError):
|
|
||||||
logger.warning(f"{self.log_prefix} 解析LLM返回的编号列表失败: {chosen_indices_str}")
|
|
||||||
|
|
||||||
if selected_emoji_obj:
|
|
||||||
chosen_emoji_obj = selected_emoji_obj
|
|
||||||
logger.info(f"{self.log_prefix} 从候选项中选择表情: {chosen_emoji_obj.description}")
|
|
||||||
else:
|
|
||||||
if not success:
|
if not success:
|
||||||
logger.warning(f"{self.log_prefix} LLM调用失败, 将随机选择一个表情包")
|
logger.warning(f"{self.log_prefix} LLM调用失败: {chosen_emotion}, 将随机选择一个表情包")
|
||||||
|
emoji_base64, emoji_description = random.choice(all_emojis_data)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"{self.log_prefix} 所有候选项均在最近发送历史中, 将随机选择")
|
chosen_emotion = chosen_emotion.strip().replace('"', "").replace("'", "")
|
||||||
|
logger.info(f"{self.log_prefix} LLM选择的情感: {chosen_emotion}")
|
||||||
|
|
||||||
selectable_emojis = [e for e in all_emojis_obj if e.hash not in self._sent_emoji_history]
|
# 使用模糊匹配来查找最相关的情感标签
|
||||||
if not selectable_emojis:
|
matched_key = next((key for key in emotion_map if chosen_emotion in key), None)
|
||||||
selectable_emojis = all_emojis_obj
|
|
||||||
chosen_emoji_obj = random.choice(selectable_emojis)
|
|
||||||
|
|
||||||
# 8. 发送表情包并更新历史记录
|
if matched_key:
|
||||||
if chosen_emoji_obj:
|
emoji_base64, emoji_description = random.choice(emotion_map[matched_key])
|
||||||
emoji_base64 = image_path_to_base64(chosen_emoji_obj.full_path)
|
logger.info(f"{self.log_prefix} 找到匹配情感 '{chosen_emotion}' (匹配到: '{matched_key}') 的表情包: {emoji_description}")
|
||||||
if emoji_base64:
|
else:
|
||||||
send_success = await self.send_emoji(emoji_base64)
|
logger.warning(
|
||||||
if send_success:
|
f"{self.log_prefix} LLM选择的情感 '{chosen_emotion}' 不在可用列表中, 将随机选择一个表情包"
|
||||||
self._sent_emoji_history.append(chosen_emoji_obj.hash)
|
)
|
||||||
logger.info(f"{self.log_prefix} 表情包发送成功: {chosen_emoji_obj.description}")
|
emoji_base64, emoji_description = random.choice(all_emojis_data)
|
||||||
logger.debug(f"{self.log_prefix} 最近表情历史: {list(self._sent_emoji_history)}")
|
|
||||||
return True, f"发送表情包: {chosen_emoji_obj.description}"
|
|
||||||
|
|
||||||
|
# 7. 发送表情包
|
||||||
|
success = await self.send_emoji(emoji_base64)
|
||||||
|
|
||||||
|
if not success:
|
||||||
logger.error(f"{self.log_prefix} 表情包发送失败")
|
logger.error(f"{self.log_prefix} 表情包发送失败")
|
||||||
return False, "表情包发送失败"
|
return False, "表情包发送失败"
|
||||||
|
|
||||||
|
return True, f"发送表情包: {emoji_description}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self.log_prefix} 表情动作执行失败: {e}", exc_info=True)
|
logger.error(f"{self.log_prefix} 表情动作执行失败: {e}", exc_info=True)
|
||||||
return False, f"表情发送失败: {str(e)}"
|
return False, f"表情发送失败: {str(e)}"
|
||||||
|
|||||||
Reference in New Issue
Block a user