重构整个插件系统,尝试恢复可启动性,新增插件系统maibot-plugin-sdk依赖
This commit is contained in:
@@ -1,34 +1,38 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Emoji插件 (Emoji Actions)",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"description": "可以发送和管理Emoji",
|
||||
"author": {
|
||||
"name": "SengokuCola",
|
||||
"url": "https://github.com/MaiM-with-u"
|
||||
},
|
||||
"license": "GPL-v3.0-or-later",
|
||||
|
||||
"host_application": {
|
||||
"min_version": "0.10.0"
|
||||
"min_version": "1.0.0"
|
||||
},
|
||||
"homepage_url": "https://github.com/MaiM-with-u/maibot",
|
||||
"repository_url": "https://github.com/MaiM-with-u/maibot",
|
||||
"keywords": ["emoji", "action", "built-in"],
|
||||
"categories": ["Emoji"],
|
||||
|
||||
"default_locale": "zh-CN",
|
||||
"locales_path": "_locales",
|
||||
|
||||
"plugin_info": {
|
||||
"is_built_in": true,
|
||||
"plugin_type": "action_provider",
|
||||
"components": [
|
||||
{
|
||||
"type": "action",
|
||||
"name": "emoji",
|
||||
"name": "emoji",
|
||||
"description": "发送表情包辅助表达情绪"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"capabilities": [
|
||||
"emoji.get_random",
|
||||
"message.get_recent",
|
||||
"message.build_readable",
|
||||
"llm.generate",
|
||||
"send.emoji",
|
||||
"config.get"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import random
|
||||
from typing import Tuple
|
||||
|
||||
# 导入新插件系统
|
||||
from src.plugin_system import BaseAction, ActionActivationType
|
||||
|
||||
# 导入依赖的系统组件
|
||||
from src.common.logger import get_logger
|
||||
|
||||
# 导入API模块 - 标准Python包方式
|
||||
from src.plugin_system.apis import emoji_api, llm_api, message_api
|
||||
|
||||
# NoReplyAction已集成到heartFC_chat.py中,不再需要导入
|
||||
from src.config.config import global_config
|
||||
|
||||
|
||||
logger = get_logger("emoji")
|
||||
|
||||
|
||||
class EmojiAction(BaseAction):
|
||||
"""表情动作 - 发送表情包"""
|
||||
|
||||
activation_type = ActionActivationType.RANDOM
|
||||
random_activation_probability = global_config.emoji.emoji_chance
|
||||
parallel_action = True
|
||||
|
||||
# 动作基本信息
|
||||
action_name = "emoji"
|
||||
action_description = "发送表情包辅助表达情绪"
|
||||
|
||||
# 动作参数定义
|
||||
action_parameters = {}
|
||||
|
||||
# 动作使用场景
|
||||
action_require = [
|
||||
"发送表情包辅助表达情绪",
|
||||
"表达情绪时可以选择使用",
|
||||
"不要连续发送,如果你已经发过[表情包],就不要选择此动作",
|
||||
]
|
||||
|
||||
# 关联类型
|
||||
associated_types = ["emoji"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# sourcery skip: assign-if-exp, introduce-default-else, swap-if-else-branches, use-named-expression
|
||||
"""执行表情动作"""
|
||||
try:
|
||||
# 1. 获取发送表情的原因
|
||||
# reason = self.action_data.get("reason", "表达当前情绪")
|
||||
reason = self.action_reasoning
|
||||
|
||||
# 2. 随机获取20个表情包
|
||||
sampled_emojis = await emoji_api.get_random(30)
|
||||
if not sampled_emojis:
|
||||
logger.warning(f"{self.log_prefix} 无法获取随机表情包")
|
||||
return False, "无法获取随机表情包"
|
||||
|
||||
# 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))
|
||||
|
||||
available_emotions = list(emotion_map.keys())
|
||||
available_emotions_str = ""
|
||||
for emotion in available_emotions:
|
||||
available_emotions_str += f"{emotion}\n"
|
||||
|
||||
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"""你正在进行QQ聊天,你需要根据聊天记录,选出一个合适的情感标签。
|
||||
请你根据以下原因和聊天记录进行选择
|
||||
原因:{reason}
|
||||
聊天记录:
|
||||
{messages_text}
|
||||
|
||||
这里是可用的情感标签:
|
||||
{available_emotions_str}
|
||||
请直接返回最匹配的那个情感标签,不要进行任何解释或添加其他多余的文字。
|
||||
"""
|
||||
|
||||
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("utils") # 使用字典访问方式
|
||||
if not chat_model_config:
|
||||
logger.error(f"{self.log_prefix} 未找到'utils'模型配置,无法调用LLM")
|
||||
return False, "未找到'utils'模型配置"
|
||||
|
||||
success, chosen_emotion, _, _ = await llm_api.generate_with_model(
|
||||
prompt, model_config=chat_model_config, request_type="emoji.select"
|
||||
)
|
||||
|
||||
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}],原因: {reason}")
|
||||
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 success:
|
||||
# 存储动作信息
|
||||
await self.store_action_info(
|
||||
action_build_into_prompt=True,
|
||||
action_prompt_display=f"你发送了表情包,原因:{reason}",
|
||||
action_done=True,
|
||||
)
|
||||
return True, f"成功发送表情包:[表情包:{chosen_emotion}]"
|
||||
else:
|
||||
error_msg = "发送表情包失败"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
|
||||
await self.send_text("执行表情包动作失败")
|
||||
return False, error_msg
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 表情动作执行失败: {e}", exc_info=True)
|
||||
return False, f"表情发送失败: {str(e)}"
|
||||
@@ -1,66 +1,116 @@
|
||||
"""
|
||||
核心动作插件
|
||||
"""Emoji 插件 — 新 SDK 版本
|
||||
|
||||
将系统核心动作(reply、no_reply、emoji)转换为新插件系统格式
|
||||
这是系统的内置插件,提供基础的聊天交互功能
|
||||
根据聊天上下文的情感,使用 LLM 选择并发送合适的表情包。
|
||||
"""
|
||||
|
||||
from typing import List, Tuple, Type
|
||||
import random
|
||||
|
||||
# 导入新插件系统
|
||||
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
|
||||
from src.plugin_system.base.config_types import ConfigField
|
||||
|
||||
# 导入依赖的系统组件
|
||||
from src.common.logger import get_logger
|
||||
|
||||
from src.plugins.built_in.emoji_plugin.emoji import EmojiAction
|
||||
|
||||
logger = get_logger("core_actions")
|
||||
from maibot_sdk import MaiBotPlugin, Action
|
||||
from maibot_sdk.types import ActivationType
|
||||
|
||||
|
||||
@register_plugin
|
||||
class CoreActionsPlugin(BasePlugin):
|
||||
"""核心动作插件
|
||||
class EmojiPlugin(MaiBotPlugin):
|
||||
"""表情包插件"""
|
||||
|
||||
系统内置插件,提供基础的聊天交互功能:
|
||||
- Reply: 回复动作
|
||||
- NoReply: 不回复动作
|
||||
- Emoji: 表情动作
|
||||
@Action(
|
||||
"emoji",
|
||||
description="发送表情包辅助表达情绪",
|
||||
activation_type=ActivationType.RANDOM,
|
||||
activation_probability=0.3,
|
||||
parallel_action=True,
|
||||
action_require=[
|
||||
"发送表情包辅助表达情绪",
|
||||
"表达情绪时可以选择使用",
|
||||
"不要连续发送,如果你已经发过[表情包],就不要选择此动作",
|
||||
],
|
||||
associated_types=["emoji"],
|
||||
)
|
||||
async def handle_emoji(self, stream_id: str = "", reasoning: str = "", chat_id: str = "", **kwargs):
|
||||
"""执行表情动作"""
|
||||
reason = reasoning or "表达当前情绪"
|
||||
|
||||
注意:插件基本信息优先从_manifest.json文件中读取
|
||||
"""
|
||||
# 1. 随机获取30个表情包
|
||||
result = await self.ctx.emoji.get_random(30)
|
||||
if not result or not result.get("success"):
|
||||
return False, "无法获取随机表情包"
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name: str = "core_actions" # 内部标识符
|
||||
enable_plugin: bool = True
|
||||
dependencies: list[str] = [] # 插件依赖列表
|
||||
python_dependencies: list[str] = [] # Python包依赖列表
|
||||
config_file_name: str = "config.toml"
|
||||
sampled_emojis = result.get("emojis", [])
|
||||
if not sampled_emojis:
|
||||
return False, "无法获取随机表情包"
|
||||
|
||||
# 配置节描述
|
||||
config_section_descriptions = {
|
||||
"plugin": "插件启用配置",
|
||||
"components": "核心组件启用配置",
|
||||
}
|
||||
# 2. 按情感分组
|
||||
emotion_map: dict[str, list] = {}
|
||||
for emoji in sampled_emojis:
|
||||
emo = emoji.get("emotion", "")
|
||||
if emo not in emotion_map:
|
||||
emotion_map[emo] = []
|
||||
emotion_map[emo].append(emoji)
|
||||
|
||||
# 配置Schema定义
|
||||
config_schema: dict = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="0.6.0", description="配置文件版本"),
|
||||
},
|
||||
"components": {
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
|
||||
},
|
||||
}
|
||||
available_emotions = list(emotion_map.keys())
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""返回插件包含的组件列表"""
|
||||
if not available_emotions:
|
||||
# 无情感标签,随机发送
|
||||
chosen = random.choice(sampled_emojis)
|
||||
await self.ctx.send.emoji(chosen["base64"], stream_id)
|
||||
return True, "随机发送了表情包"
|
||||
|
||||
# --- 根据配置注册组件 ---
|
||||
components = []
|
||||
if self.get_config("components.enable_emoji", True):
|
||||
components.append((EmojiAction.get_action_info(), EmojiAction))
|
||||
# 3. 获取最近消息作为上下文
|
||||
messages_text = ""
|
||||
if chat_id:
|
||||
recent_result = await self.ctx.message.get_recent(chat_id=chat_id, limit=5)
|
||||
if recent_result and recent_result.get("success"):
|
||||
readable_result = await self.ctx.call_capability(
|
||||
"message.build_readable",
|
||||
chat_id=chat_id,
|
||||
start_time=0,
|
||||
end_time=0,
|
||||
limit=5,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
truncate=False,
|
||||
)
|
||||
if readable_result and readable_result.get("success"):
|
||||
messages_text = readable_result.get("text", "")
|
||||
|
||||
return components
|
||||
# 4. 构建 prompt 让 LLM 选择情感
|
||||
available_emotions_str = "\n".join(available_emotions)
|
||||
prompt = f"""你正在进行QQ聊天,你需要根据聊天记录,选出一个合适的情感标签。
|
||||
请你根据以下原因和聊天记录进行选择
|
||||
原因:{reason}
|
||||
聊天记录:
|
||||
{messages_text}
|
||||
|
||||
这里是可用的情感标签:
|
||||
{available_emotions_str}
|
||||
请直接返回最匹配的那个情感标签,不要进行任何解释或添加其他多余的文字。
|
||||
"""
|
||||
|
||||
# 5. 调用 LLM
|
||||
llm_result = await self.ctx.llm.generate(prompt=prompt, model_name="utils")
|
||||
if not llm_result or not llm_result.get("success"):
|
||||
chosen = random.choice(sampled_emojis)
|
||||
await self.ctx.send.emoji(chosen["base64"], stream_id)
|
||||
return True, "LLM调用失败,随机发送了表情包"
|
||||
|
||||
chosen_emotion = llm_result.get("response", "").strip().replace('"', "").replace("'", "")
|
||||
|
||||
# 6. 根据选择的情感匹配表情包
|
||||
if chosen_emotion in emotion_map:
|
||||
chosen = random.choice(emotion_map[chosen_emotion])
|
||||
else:
|
||||
chosen = random.choice(sampled_emojis)
|
||||
|
||||
# 7. 发送
|
||||
send_result = await self.ctx.send.emoji(chosen["base64"], stream_id)
|
||||
if send_result and send_result.get("success"):
|
||||
return True, f"成功发送表情包:[表情包:{chosen_emotion}]"
|
||||
return False, "发送表情包失败"
|
||||
|
||||
async def on_load(self):
|
||||
# 从插件配置读取 emoji_chance 来覆盖默认概率
|
||||
config_result = await self.ctx.config.get("emoji.emoji_chance")
|
||||
if config_result and isinstance(config_result, dict) and config_result.get("success"):
|
||||
pass # 配置已在宿主端管理
|
||||
|
||||
|
||||
def create_plugin():
|
||||
return EmojiPlugin()
|
||||
|
||||
Reference in New Issue
Block a user