diff --git a/.gitignore b/.gitignore index 2b6f89dcc..37b1b82b1 100644 --- a/.gitignore +++ b/.gitignore @@ -317,4 +317,4 @@ run_pet.bat !/plugins/take_picture_plugin config.toml -备忘录.txt \ No newline at end of file +interested_rates.txt \ No newline at end of file diff --git a/src/plugin_system/apis/emoji_api.py b/src/plugin_system/apis/emoji_api.py index 33c0f23d7..4f1d03521 100644 --- a/src/plugin_system/apis/emoji_api.py +++ b/src/plugin_system/apis/emoji_api.py @@ -8,7 +8,7 @@ count = emoji_api.get_count() """ -from typing import Optional, Tuple +from typing import Optional, Tuple, List from src.common.logger import get_logger from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.utils.utils_image import image_path_to_base64 @@ -55,14 +55,20 @@ async def get_by_description(description: str) -> Optional[Tuple[str, str, str]] return None -async def get_random() -> Optional[Tuple[str, str, str]]: - """随机获取表情包 +async def get_random(count: int = 1) -> Optional[List[Tuple[str, str, str]]]: + """随机获取指定数量的表情包 + + Args: + count: 要获取的表情包数量,默认为1 Returns: - Optional[Tuple[str, str, str]]: (base64编码, 表情包描述, 随机情感标签) 或 None + Optional[List[Tuple[str, str, str]]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,如果失败则为None """ + if count <= 0: + return [] + try: - logger.info("[EmojiAPI] 随机获取表情包") + logger.info(f"[EmojiAPI] 随机获取 {count} 个表情包") emoji_manager = get_emoji_manager() all_emojis = emoji_manager.emoji_objects @@ -77,23 +83,37 @@ async def get_random() -> Optional[Tuple[str, str, str]]: logger.warning("[EmojiAPI] 没有有效的表情包") return None + if len(valid_emojis) < count: + logger.warning( + f"[EmojiAPI] 有效表情包数量 ({len(valid_emojis)}) 少于请求的数量 ({count}),将返回所有有效表情包" + ) + count = len(valid_emojis) + # 随机选择 import random - selected_emoji = random.choice(valid_emojis) - emoji_base64 = image_path_to_base64(selected_emoji.full_path) + selected_emojis = random.sample(valid_emojis, count) - if not emoji_base64: - logger.error(f"[EmojiAPI] 无法转换表情包为base64: {selected_emoji.full_path}") + results = [] + for selected_emoji in selected_emojis: + emoji_base64 = image_path_to_base64(selected_emoji.full_path) + + if not emoji_base64: + logger.error(f"[EmojiAPI] 无法转换表情包为base64: {selected_emoji.full_path}") + continue + + matched_emotion = random.choice(selected_emoji.emotion) if selected_emoji.emotion else "随机表情" + + # 记录使用次数 + emoji_manager.record_usage(selected_emoji.hash) + results.append((emoji_base64, selected_emoji.description, matched_emotion)) + + if not results and count > 0: + logger.warning("[EmojiAPI] 随机获取表情包失败,没有一个可以成功处理") return None - matched_emotion = random.choice(selected_emoji.emotion) if selected_emoji.emotion else "随机表情" - - # 记录使用次数 - emoji_manager.record_usage(selected_emoji.hash) - - logger.info(f"[EmojiAPI] 成功获取随机表情包: {selected_emoji.description}") - return emoji_base64, selected_emoji.description, matched_emotion + logger.info(f"[EmojiAPI] 成功获取 {len(results)} 个随机表情包") + return results except Exception as e: logger.error(f"[EmojiAPI] 获取随机表情包失败: {e}") diff --git a/src/plugins/built_in/core_actions/emoji.py b/src/plugins/built_in/core_actions/emoji.py index cb429dd4c..efd285f9d 100644 --- a/src/plugins/built_in/core_actions/emoji.py +++ b/src/plugins/built_in/core_actions/emoji.py @@ -1,3 +1,4 @@ +import random from typing import Tuple # 导入新插件系统 @@ -7,7 +8,7 @@ from src.plugin_system import BaseAction, ActionActivationType, ChatMode from src.common.logger import get_logger # 导入API模块 - 标准Python包方式 -from src.plugin_system.apis import emoji_api +from src.plugin_system.apis import emoji_api, llm_api, message_api from src.plugins.built_in.core_actions.no_reply import NoReplyAction @@ -39,7 +40,7 @@ class EmojiAction(BaseAction): """ # 动作参数定义 - action_parameters = {"description": "文字描述你想要发送的表情包内容"} + action_parameters = {"reason": "文字描述你想要发送的表情包原因"} # 动作使用场景 action_require = [ @@ -56,18 +57,82 @@ class EmojiAction(BaseAction): logger.info(f"{self.log_prefix} 决定发送表情") try: - # 1. 根据描述选择表情包 - description = self.action_data.get("description", "") - emoji_result = await emoji_api.get_by_description(description) + # 1. 获取发送表情的原因 + reason = self.action_data.get("reason", "表达当前情绪") + logger.info(f"{self.log_prefix} 发送表情原因: {reason}") - if not emoji_result: - logger.warning(f"{self.log_prefix} 未找到匹配描述 '{description}' 的表情包") - return False, f"未找到匹配 '{description}' 的表情包" + # 2. 随机获取20个表情包 + sampled_emojis = await emoji_api.get_random(30) + if not sampled_emojis: + logger.warning(f"{self.log_prefix} 无法获取随机表情包") + return False, "无法获取随机表情包" - emoji_base64, emoji_description, matched_emotion = emoji_result - logger.info(f"{self.log_prefix} 找到表达{matched_emotion}的表情包") + # 3. 准备情感数据 + emotion_map = {} + for b64, desc, emo in sampled_emojis: + if emo not in emotion_map: + emotion_map[emo] = [] + emotion_map[emo].append((b64, desc)) - # 使用BaseAction的便捷方法发送表情包 + available_emotions = list(emotion_map.keys()) + + if not available_emotions: + logger.warning(f"{self.log_prefix} 获取到的表情包均无情感标签, 将随机发送") + emoji_base64, emoji_description, _ = random.choice(sampled_emojis) + else: + # 获取最近的5条消息内容用于判断 + recent_messages = message_api.get_recent_messages(chat_id=self.chat_id, limit=5) + messages_text = "" + if recent_messages: + # 使用message_api构建可读的消息字符串 + messages_text = message_api.build_readable_messages( + messages=recent_messages, + timestamp_mode="normal_no_YMD", + truncate=False, + show_actions=False, + ) + + # 4. 构建prompt让LLM选择情感 + prompt = f""" + 你是一个正在进行聊天的网友,你需要根据一个理由和最近的聊天记录,从一个情感标签列表中选择最匹配的一个。 + 这是最近的聊天记录: + {messages_text} + + 这是理由:“{reason}” + 这里是可用的情感标签:{available_emotions} + 请直接返回最匹配的那个情感标签,不要进行任何解释或添加其他多余的文字。 + """ + logger.info(f"{self.log_prefix} 生成的LLM Prompt: {prompt}") + + # 5. 调用LLM + models = llm_api.get_available_models() + chat_model_config = getattr(models, "utils_small", None) # 默认使用chat模型 + if not chat_model_config: + logger.error(f"{self.log_prefix} 未找到'chat'模型配置,无法调用LLM") + return False, "未找到'chat'模型配置" + + success, chosen_emotion, _, _ = await llm_api.generate_with_model( + prompt, model_config=chat_model_config, request_type="emoji" + ) + + if not success: + logger.error(f"{self.log_prefix} LLM调用失败: {chosen_emotion}") + return False, f"LLM调用失败: {chosen_emotion}" + + chosen_emotion = chosen_emotion.strip().replace('"', "").replace("'", "") + logger.info(f"{self.log_prefix} LLM选择的情感: {chosen_emotion}") + + # 6. 根据选择的情感匹配表情包 + if chosen_emotion in emotion_map: + emoji_base64, emoji_description = random.choice(emotion_map[chosen_emotion]) + logger.info(f"{self.log_prefix} 找到匹配情感 '{chosen_emotion}' 的表情包: {emoji_description}") + else: + logger.warning( + f"{self.log_prefix} LLM选择的情感 '{chosen_emotion}' 不在可用列表中, 将随机选择一个表情包" + ) + emoji_base64, emoji_description, _ = random.choice(sampled_emojis) + + # 7. 发送表情包 success = await self.send_emoji(emoji_base64) if not success: @@ -80,5 +145,5 @@ class EmojiAction(BaseAction): return True, f"发送表情包: {emoji_description}" except Exception as e: - logger.error(f"{self.log_prefix} 表情动作执行失败: {e}") + logger.error(f"{self.log_prefix} 表情动作执行失败: {e}", exc_info=True) return False, f"表情发送失败: {str(e)}"