feat(emoji): 引入表情发送历史以避免重复发送
为了提升表情发送的自然性和多样性,本次更新引入了发送历史记录机制,并优化了选择逻辑。 - 新增一个长度为4的双端队列,用于存储最近发送过的表情哈希,以避免在短期内重复。 - 修改LLM提示,要求其返回一个包含3个最匹配情感的有序列表,以提供更多候选表情。 - 重构选择逻辑:在LLM推荐或随机选择时,会优先选取未在最近历史中出现过的表情。 - 仅当所有候选表情都已在近期发送过时,才会退回至在完整表情库中进行选择。
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import random
|
||||
from typing import Tuple
|
||||
from collections import deque
|
||||
|
||||
# 导入新插件系统
|
||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
||||
@@ -20,14 +21,23 @@ logger = get_logger("emoji")
|
||||
class EmojiAction(BaseAction):
|
||||
"""表情动作 - 发送表情包"""
|
||||
|
||||
activation_type = ActionActivationType.RANDOM
|
||||
random_activation_probability = global_config.emoji.emoji_chance
|
||||
# --- 类级别属性 ---
|
||||
# 激活设置
|
||||
if global_config.emoji.emoji_activate_type == "llm":
|
||||
activation_type = ActionActivationType.LLM_JUDGE
|
||||
random_activation_probability = 0
|
||||
else:
|
||||
activation_type = ActionActivationType.RANDOM
|
||||
random_activation_probability = global_config.emoji.emoji_chance
|
||||
mode_enable = ChatMode.ALL
|
||||
parallel_action = True
|
||||
|
||||
# 动作基本信息
|
||||
action_name = "emoji"
|
||||
action_description = "发送表情包辅助表达情绪"
|
||||
|
||||
# 最近发送表情的历史记录
|
||||
_sent_emoji_history = deque(maxlen=4)
|
||||
|
||||
# LLM判断提示词
|
||||
llm_judge_prompt = """
|
||||
@@ -80,23 +90,29 @@ class EmojiAction(BaseAction):
|
||||
|
||||
desc = emoji.description
|
||||
emotions = emoji.emotion
|
||||
all_emojis_data.append((b64, desc))
|
||||
# 使用 emoji 对象的 hash 作为唯一标识符
|
||||
all_emojis_data.append((b64, desc, emoji.hash))
|
||||
|
||||
for emo in emotions:
|
||||
if emo not in emotion_map:
|
||||
emotion_map[emo] = []
|
||||
emotion_map[emo].append((b64, desc))
|
||||
emotion_map[emo].append((b64, desc, emoji.hash))
|
||||
|
||||
if not all_emojis_data:
|
||||
logger.warning(f"{self.log_prefix} 无法加载任何有效的表情包数据")
|
||||
return False, "无法加载任何有效的表情包数据"
|
||||
|
||||
available_emotions = list(emotion_map.keys())
|
||||
emoji_base64, emoji_description = "", ""
|
||||
|
||||
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = None, None, None
|
||||
|
||||
if not available_emotions:
|
||||
logger.warning(f"{self.log_prefix} 获取到的表情包均无情感标签, 将随机发送")
|
||||
emoji_base64, emoji_description = random.choice(all_emojis_data)
|
||||
# 随机选择一个不在历史记录中的表情
|
||||
selectable_emojis = [e for e in all_emojis_data if e[2] not in self._sent_emoji_history]
|
||||
if not selectable_emojis: # 如果都发过了,就从全部里面随机选
|
||||
selectable_emojis = all_emojis_data
|
||||
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = random.choice(selectable_emojis)
|
||||
else:
|
||||
# 获取最近的5条消息内容用于判断
|
||||
recent_messages = message_api.get_recent_messages(chat_id=self.chat_id, limit=5)
|
||||
@@ -109,60 +125,75 @@ class EmojiAction(BaseAction):
|
||||
show_actions=False,
|
||||
)
|
||||
|
||||
# 4. 构建prompt让LLM选择情感
|
||||
# 4. 构建prompt让LLM选择多个情感
|
||||
prompt = f"""
|
||||
你是一个正在进行聊天的网友,你需要根据一个理由和最近的聊天记录,从一个情感标签列表中选择最匹配的一个。
|
||||
你是一个正在进行聊天的网友,你需要根据一个理由和最近的聊天记录,从一个情感标签列表中选择最匹配的 **3个** 情感标签,并按匹配度从高到低排序。
|
||||
这是最近的聊天记录:
|
||||
{messages_text}
|
||||
|
||||
这是理由:“{reason}”
|
||||
这里是可用的情感标签:{available_emotions}
|
||||
请直接返回最匹配的那个情感标签,不要进行任何解释或添加其他多余的文字。
|
||||
请直接返回一个包含3个最匹配情感标签的有序列表,例如:["开心", "激动", "有趣"],不要进行任何解释或添加其他多余的文字。
|
||||
"""
|
||||
|
||||
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()
|
||||
chat_model_config = models.get("planner")
|
||||
if not chat_model_config:
|
||||
logger.error(f"{self.log_prefix} 未找到'utils_small'模型配置,无法调用LLM")
|
||||
return False, "未找到'utils_small'模型配置"
|
||||
logger.error(f"{self.log_prefix} 未找到 'planner' 模型配置,无法调用LLM")
|
||||
return False, "未找到 'planner' 模型配置"
|
||||
|
||||
success, chosen_emotion, _, _ = await llm_api.generate_with_model(
|
||||
prompt, model_config=chat_model_config, request_type="emoji"
|
||||
success, chosen_emotions_str, _, _ = await llm_api.generate_with_model(
|
||||
prompt, model_config=chat_model_config, request_type="emoji_selection"
|
||||
)
|
||||
|
||||
if not success:
|
||||
logger.warning(f"{self.log_prefix} LLM调用失败: {chosen_emotion}, 将随机选择一个表情包")
|
||||
emoji_base64, emoji_description = random.choice(all_emojis_data)
|
||||
selected_emoji_info = None
|
||||
if success:
|
||||
try:
|
||||
# 解析LLM返回的列表
|
||||
import json
|
||||
chosen_emotions = json.loads(chosen_emotions_str)
|
||||
if isinstance(chosen_emotions, list):
|
||||
logger.info(f"{self.log_prefix} LLM选择的情感候选项: {chosen_emotions}")
|
||||
# 遍历候选项,找到第一个不在历史记录中的表情
|
||||
for emotion in chosen_emotions:
|
||||
matched_key = next((key for key in emotion_map if emotion in key), None)
|
||||
if matched_key:
|
||||
# 从匹配到的表情中,随机选一个不在历史记录的
|
||||
candidate_emojis = [e for e in emotion_map[matched_key] if e[2] not in self._sent_emoji_history]
|
||||
if candidate_emojis:
|
||||
selected_emoji_info = random.choice(candidate_emojis)
|
||||
break # 找到后立即跳出循环
|
||||
else:
|
||||
logger.warning(f"{self.log_prefix} LLM返回的不是一个列表: {chosen_emotions_str}")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
logger.warning(f"{self.log_prefix} 解析LLM返回的情感列表失败: {chosen_emotions_str}")
|
||||
|
||||
if selected_emoji_info:
|
||||
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = selected_emoji_info
|
||||
logger.info(f"{self.log_prefix} 从候选项中选择表情: {chosen_emoji_desc}")
|
||||
else:
|
||||
chosen_emotion = chosen_emotion.strip().replace('"', "").replace("'", "")
|
||||
logger.info(f"{self.log_prefix} LLM选择的情感: {chosen_emotion}")
|
||||
|
||||
# 使用模糊匹配来查找最相关的情感标签
|
||||
matched_key = next((key for key in emotion_map if chosen_emotion in key), None)
|
||||
|
||||
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}")
|
||||
if not success:
|
||||
logger.warning(f"{self.log_prefix} LLM调用失败, 将随机选择一个表情包")
|
||||
else:
|
||||
logger.warning(
|
||||
f"{self.log_prefix} LLM选择的情感 '{chosen_emotion}' 不在可用列表中, 将随机选择一个表情包"
|
||||
)
|
||||
emoji_base64, emoji_description = random.choice(all_emojis_data)
|
||||
logger.warning(f"{self.log_prefix} 所有候选项均在最近发送历史中, 将随机选择")
|
||||
|
||||
selectable_emojis = [e for e in all_emojis_data if e[2] not in self._sent_emoji_history]
|
||||
if not selectable_emojis:
|
||||
selectable_emojis = all_emojis_data
|
||||
chosen_emoji_b64, chosen_emoji_desc, chosen_emoji_hash = random.choice(selectable_emojis)
|
||||
|
||||
# 7. 发送表情包
|
||||
success = await self.send_emoji(emoji_base64)
|
||||
# 7. 发送表情包并更新历史记录
|
||||
if chosen_emoji_b64 and chosen_emoji_hash:
|
||||
success = await self.send_emoji(chosen_emoji_b64)
|
||||
if success:
|
||||
self._sent_emoji_history.append(chosen_emoji_hash)
|
||||
logger.info(f"{self.log_prefix} 表情包发送成功: {chosen_emoji_desc}")
|
||||
logger.debug(f"{self.log_prefix} 最近表情历史: {list(self._sent_emoji_history)}")
|
||||
return True, f"发送表情包: {chosen_emoji_desc}"
|
||||
|
||||
if not success:
|
||||
logger.error(f"{self.log_prefix} 表情包发送失败")
|
||||
return False, "表情包发送失败"
|
||||
|
||||
return True, f"发送表情包: {emoji_description}"
|
||||
logger.error(f"{self.log_prefix} 表情包发送失败")
|
||||
return False, "表情包发送失败"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 表情动作执行失败: {e}", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user