refc:重构插件api,补全文档,合并expressor和replyer,分离reply和sender,新log浏览器
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
# 核心动作插件配置文件
|
||||
|
||||
[plugin]
|
||||
name = "core_actions"
|
||||
description = "系统核心动作插件"
|
||||
version = "0.2"
|
||||
author = "built-in"
|
||||
enabled = true
|
||||
|
||||
[no_reply]
|
||||
# 等待新消息的超时时间(秒)
|
||||
waiting_timeout = 1200
|
||||
|
||||
[emoji]
|
||||
# 表情动作配置
|
||||
enabled = true
|
||||
# 在Normal模式下的随机激活概率
|
||||
random_probability = 0.1
|
||||
# 是否启用智能表情选择
|
||||
smart_selection = true
|
||||
|
||||
# LLM判断相关配置
|
||||
[emoji.llm_judge]
|
||||
# 是否启用LLM智能判断
|
||||
enabled = true
|
||||
# 自定义判断提示词(可选)
|
||||
custom_prompt = ""
|
||||
@@ -5,18 +5,18 @@
|
||||
这是系统的内置插件,提供基础的聊天交互功能
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import List, Tuple, Type, Optional
|
||||
import time
|
||||
from typing import List, Tuple, Type
|
||||
|
||||
# 导入新插件系统
|
||||
from src.plugin_system import BasePlugin, register_plugin, BaseAction, ComponentInfo, ActionActivationType, ChatMode
|
||||
from src.plugin_system.base.base_command import BaseCommand
|
||||
from src.plugin_system.base.config_types import ConfigField
|
||||
|
||||
# 导入依赖的系统组件
|
||||
from src.common.logger import get_logger
|
||||
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
|
||||
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
|
||||
|
||||
# 导入API模块 - 标准Python包方式
|
||||
from src.plugin_system.apis import emoji_api, generator_api, message_api
|
||||
|
||||
logger = get_logger("core_actions")
|
||||
|
||||
@@ -35,11 +35,11 @@ class ReplyAction(BaseAction):
|
||||
|
||||
# 动作基本信息
|
||||
action_name = "reply"
|
||||
action_description = "参与聊天回复,处理文本和表情的发送"
|
||||
action_description = "参与聊天回复,发送文本进行表达"
|
||||
|
||||
# 动作参数定义
|
||||
action_parameters = {
|
||||
"reply_to": "如果是明确回复某个人的发言,请在reply_to参数中指定,格式:(用户名:发言内容),如果不是,reply_to的值设为none"
|
||||
"reply_to": "你要回复的对方的发言内容,格式:(用户名:发言内容),可以为none"
|
||||
}
|
||||
|
||||
# 动作使用场景
|
||||
@@ -52,40 +52,52 @@ class ReplyAction(BaseAction):
|
||||
"""执行回复动作"""
|
||||
logger.info(f"{self.log_prefix} 决定回复: {self.reasoning}")
|
||||
|
||||
start_time = self.action_data.get("loop_start_time", time.time())
|
||||
|
||||
try:
|
||||
# 获取聊天观察
|
||||
chatting_observation = self._get_chatting_observation()
|
||||
if not chatting_observation:
|
||||
return False, "未找到聊天观察"
|
||||
|
||||
# 处理回复目标
|
||||
anchor_message = await self._resolve_reply_target(chatting_observation)
|
||||
|
||||
# 获取回复器服务
|
||||
replyer = self.api.get_service("replyer")
|
||||
if not replyer:
|
||||
logger.error(f"{self.log_prefix} 未找到回复器服务")
|
||||
return False, "回复器服务不可用"
|
||||
|
||||
# 执行回复
|
||||
success, reply_set = await replyer.deal_reply(
|
||||
cycle_timers=self.cycle_timers,
|
||||
|
||||
success, reply_set = await generator_api.generate_reply(
|
||||
chat_stream=self.chat_stream,
|
||||
action_data=self.action_data,
|
||||
anchor_message=anchor_message,
|
||||
reasoning=self.reasoning,
|
||||
thinking_id=self.thinking_id,
|
||||
platform=self.platform,
|
||||
chat_id=self.chat_id,
|
||||
is_group=self.is_group
|
||||
)
|
||||
|
||||
# 检查从start_time以来的新消息数量
|
||||
# 获取动作触发时间或使用默认值
|
||||
current_time = time.time()
|
||||
new_message_count = message_api.count_new_messages(
|
||||
chat_id=self.chat_id,
|
||||
start_time=start_time,
|
||||
end_time=current_time
|
||||
)
|
||||
|
||||
# 根据新消息数量决定是否使用reply_to
|
||||
need_reply = new_message_count >= 4
|
||||
logger.info(f"{self.log_prefix} 从{start_time}到{current_time}共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}reply_to")
|
||||
|
||||
# 构建回复文本
|
||||
reply_text = self._build_reply_text(reply_set)
|
||||
reply_text = ""
|
||||
first_reply = False
|
||||
for reply_seg in reply_set:
|
||||
data = reply_seg[1]
|
||||
if not first_reply and need_reply:
|
||||
await self.send_text(
|
||||
content=data,
|
||||
reply_to=self.action_data.get("reply_to", "")
|
||||
)
|
||||
else:
|
||||
await self.send_text(content=data)
|
||||
first_reply = True
|
||||
reply_text += data
|
||||
|
||||
|
||||
# 存储动作记录
|
||||
await self.api.store_action_info(
|
||||
await self.store_action_info(
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=reply_text,
|
||||
action_done=True,
|
||||
thinking_id=self.thinking_id,
|
||||
action_data=self.action_data,
|
||||
)
|
||||
|
||||
# 重置NoReplyAction的连续计数器
|
||||
@@ -97,47 +109,6 @@ class ReplyAction(BaseAction):
|
||||
logger.error(f"{self.log_prefix} 回复动作执行失败: {e}")
|
||||
return False, f"回复失败: {str(e)}"
|
||||
|
||||
def _get_chatting_observation(self) -> Optional[ChattingObservation]:
|
||||
"""获取聊天观察对象"""
|
||||
observations = self.api.get_service("observations") or []
|
||||
for obs in observations:
|
||||
if isinstance(obs, ChattingObservation):
|
||||
return obs
|
||||
return None
|
||||
|
||||
async def _resolve_reply_target(self, chatting_observation: ChattingObservation):
|
||||
"""解析回复目标消息"""
|
||||
reply_to = self.action_data.get("reply_to", "none")
|
||||
|
||||
if ":" in reply_to or ":" in reply_to:
|
||||
# 解析回复目标格式:用户名:消息内容
|
||||
parts = re.split(pattern=r"[::]", string=reply_to, maxsplit=1)
|
||||
if len(parts) == 2:
|
||||
target = parts[1].strip()
|
||||
anchor_message = chatting_observation.search_message_by_text(target)
|
||||
if anchor_message:
|
||||
chat_stream = self.api.get_service("chat_stream")
|
||||
if chat_stream:
|
||||
anchor_message.update_chat_stream(chat_stream)
|
||||
return anchor_message
|
||||
|
||||
# 创建空锚点消息
|
||||
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符")
|
||||
chat_stream = self.api.get_service("chat_stream")
|
||||
if chat_stream:
|
||||
return await create_empty_anchor_message(chat_stream.platform, chat_stream.group_info, chat_stream)
|
||||
return None
|
||||
|
||||
def _build_reply_text(self, reply_set) -> str:
|
||||
"""构建回复文本"""
|
||||
reply_text = ""
|
||||
if reply_set:
|
||||
for reply in reply_set:
|
||||
reply_type = reply[0]
|
||||
data = reply[1]
|
||||
if reply_type in ["text", "emoji"]:
|
||||
reply_text += data
|
||||
return reply_text
|
||||
|
||||
|
||||
class NoReplyAction(BaseAction):
|
||||
@@ -178,30 +149,26 @@ class NoReplyAction(BaseAction):
|
||||
count = NoReplyAction._consecutive_count
|
||||
|
||||
# 计算本次等待时间
|
||||
timeout = self._calculate_waiting_time(count)
|
||||
if count <= len(self._waiting_stages):
|
||||
# 前3次使用预设时间
|
||||
stage_time = self._waiting_stages[count - 1]
|
||||
# 如果WAITING_TIME_THRESHOLD更小,则使用它
|
||||
timeout = min(stage_time, self.waiting_timeout)
|
||||
else:
|
||||
# 第4次及以后使用WAITING_TIME_THRESHOLD
|
||||
timeout = self.waiting_timeout
|
||||
|
||||
logger.info(f"{self.log_prefix} 选择不回复(第{count}次连续),等待新消息中... (超时: {timeout}秒)")
|
||||
|
||||
# 等待新消息或达到时间上限
|
||||
result = await self.api.wait_for_new_message(timeout)
|
||||
result = await self.wait_for_new_message(timeout)
|
||||
|
||||
# 如果有新消息或者超时,都不重置计数器,因为可能还会继续no_reply
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}")
|
||||
return False, f"不回复动作执行失败: {e}"
|
||||
|
||||
def _calculate_waiting_time(self, consecutive_count: int) -> int:
|
||||
"""根据连续次数计算等待时间"""
|
||||
if consecutive_count <= len(self._waiting_stages):
|
||||
# 前3次使用预设时间
|
||||
stage_time = self._waiting_stages[consecutive_count - 1]
|
||||
# 如果WAITING_TIME_THRESHOLD更小,则使用它
|
||||
return min(stage_time, self.waiting_timeout)
|
||||
else:
|
||||
# 第4次及以后使用WAITING_TIME_THRESHOLD
|
||||
return self.waiting_timeout
|
||||
return False, f"不回复动作执行失败: {e}"
|
||||
|
||||
@classmethod
|
||||
def reset_consecutive_count(cls):
|
||||
@@ -248,56 +215,33 @@ class EmojiAction(BaseAction):
|
||||
logger.info(f"{self.log_prefix} 决定发送表情")
|
||||
|
||||
try:
|
||||
# 创建空锚点消息
|
||||
anchor_message = await self._create_anchor_message()
|
||||
if not anchor_message:
|
||||
return False, "无法创建锚点消息"
|
||||
|
||||
# 获取回复器服务
|
||||
replyer = self.api.get_service("replyer")
|
||||
if not replyer:
|
||||
logger.error(f"{self.log_prefix} 未找到回复器服务")
|
||||
return False, "回复器服务不可用"
|
||||
|
||||
# 执行表情处理
|
||||
success, reply_set = await replyer.deal_emoji(
|
||||
cycle_timers=self.cycle_timers,
|
||||
action_data=self.action_data,
|
||||
anchor_message=anchor_message,
|
||||
thinking_id=self.thinking_id,
|
||||
)
|
||||
|
||||
# 构建回复文本
|
||||
reply_text = self._build_reply_text(reply_set)
|
||||
# 1. 根据描述选择表情包
|
||||
description = self.action_data.get("description", "")
|
||||
emoji_result = await emoji_api.get_by_description(description)
|
||||
|
||||
if not emoji_result:
|
||||
logger.warning(f"{self.log_prefix} 未找到匹配描述 '{description}' 的表情包")
|
||||
return False, f"未找到匹配 '{description}' 的表情包"
|
||||
|
||||
emoji_base64, emoji_description, matched_emotion = emoji_result
|
||||
logger.info(f"{self.log_prefix} 找到表情包: {emoji_description}, 匹配情感: {matched_emotion}")
|
||||
|
||||
# 使用BaseAction的便捷方法发送表情包
|
||||
success = await self.send_emoji(emoji_base64)
|
||||
|
||||
if not success:
|
||||
logger.error(f"{self.log_prefix} 表情包发送失败")
|
||||
return False, "表情包发送失败"
|
||||
|
||||
# 重置NoReplyAction的连续计数器
|
||||
NoReplyAction.reset_consecutive_count()
|
||||
|
||||
return success, reply_text
|
||||
return True, f"发送表情包: {emoji_description}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 表情动作执行失败: {e}")
|
||||
return False, f"表情发送失败: {str(e)}"
|
||||
|
||||
async def _create_anchor_message(self):
|
||||
"""创建锚点消息"""
|
||||
chat_stream = self.api.get_service("chat_stream")
|
||||
if chat_stream:
|
||||
logger.info(f"{self.log_prefix} 为表情包创建占位符")
|
||||
return await create_empty_anchor_message(chat_stream.platform, chat_stream.group_info, chat_stream)
|
||||
return None
|
||||
|
||||
def _build_reply_text(self, reply_set) -> str:
|
||||
"""构建回复文本"""
|
||||
reply_text = ""
|
||||
if reply_set:
|
||||
for reply in reply_set:
|
||||
reply_type = reply[0]
|
||||
data = reply[1]
|
||||
if reply_type in ["text", "emoji"]:
|
||||
reply_text += data
|
||||
return reply_text
|
||||
|
||||
|
||||
class ChangeToFocusChatAction(BaseAction):
|
||||
"""切换到专注聊天动作 - 从普通模式切换到专注模式"""
|
||||
@@ -314,6 +258,7 @@ class ChangeToFocusChatAction(BaseAction):
|
||||
# 动作参数定义
|
||||
action_parameters = {}
|
||||
|
||||
apex = 111
|
||||
# 动作使用场景
|
||||
action_require = [
|
||||
"你想要进入专注聊天模式",
|
||||
@@ -437,8 +382,6 @@ class CoreActionsPlugin(BasePlugin):
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用'表情'动作"),
|
||||
"enable_change_to_focus": ConfigField(type=bool, default=True, description="是否启用'切换到专注模式'动作"),
|
||||
"enable_exit_focus": ConfigField(type=bool, default=True, description="是否启用'退出专注模式'动作"),
|
||||
"enable_ping_command": ConfigField(type=bool, default=True, description="是否启用'/ping'测试命令"),
|
||||
"enable_log_command": ConfigField(type=bool, default=True, description="是否启用'/log'日志命令"),
|
||||
},
|
||||
"no_reply": {
|
||||
"waiting_timeout": ConfigField(
|
||||
@@ -482,73 +425,137 @@ class CoreActionsPlugin(BasePlugin):
|
||||
components.append((ExitFocusChatAction.get_action_info(), ExitFocusChatAction))
|
||||
if self.get_config("components.enable_change_to_focus", True):
|
||||
components.append((ChangeToFocusChatAction.get_action_info(), ChangeToFocusChatAction))
|
||||
if self.get_config("components.enable_ping_command", True):
|
||||
components.append(
|
||||
(PingCommand.get_command_info(name="ping", description="测试机器人响应,拦截后续处理"), PingCommand)
|
||||
)
|
||||
if self.get_config("components.enable_log_command", True):
|
||||
components.append(
|
||||
(LogCommand.get_command_info(name="log", description="记录消息到日志,不拦截后续处理"), LogCommand)
|
||||
)
|
||||
# components.append((DeepReplyAction.get_action_info(), DeepReplyAction))
|
||||
|
||||
return components
|
||||
|
||||
|
||||
# ===== 示例Command组件 =====
|
||||
|
||||
|
||||
class PingCommand(BaseCommand):
|
||||
"""Ping命令 - 测试响应,拦截消息处理"""
|
||||
|
||||
command_pattern = r"^/ping(\s+(?P<message>.+))?$"
|
||||
command_help = "测试机器人响应 - 拦截后续处理"
|
||||
command_examples = ["/ping", "/ping 测试消息"]
|
||||
intercept_message = True # 拦截消息,不继续处理
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
"""执行ping命令"""
|
||||
try:
|
||||
message = self.matched_groups.get("message", "")
|
||||
reply_text = f"🏓 Pong! {message}" if message else "🏓 Pong!"
|
||||
|
||||
await self.send_text(reply_text)
|
||||
return True, f"发送ping响应: {reply_text}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ping命令执行失败: {e}")
|
||||
return False, f"执行失败: {str(e)}"
|
||||
|
||||
|
||||
class LogCommand(BaseCommand):
|
||||
"""日志命令 - 记录消息但不拦截后续处理"""
|
||||
# class DeepReplyAction(BaseAction):
|
||||
# """回复动作 - 参与聊天回复"""
|
||||
|
||||
command_pattern = r"^/log(\s+(?P<level>debug|info|warn|error))?$"
|
||||
command_help = "记录当前消息到日志 - 不拦截后续处理"
|
||||
command_examples = ["/log", "/log info", "/log debug"]
|
||||
intercept_message = False # 不拦截消息,继续后续处理
|
||||
# # 激活设置
|
||||
# focus_activation_type = ActionActivationType.ALWAYS
|
||||
# normal_activation_type = ActionActivationType.NEVER
|
||||
# mode_enable = ChatMode.FOCUS
|
||||
# parallel_action = False
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
"""执行日志命令"""
|
||||
try:
|
||||
level = self.matched_groups.get("level", "info")
|
||||
user_nickname = self.message.message_info.user_info.user_nickname
|
||||
content = self.message.processed_plain_text
|
||||
# # 动作基本信息
|
||||
# action_name = "deep_reply"
|
||||
# action_description = "参与聊天回复,关注某个话题,对聊天内容进行深度思考,给出回复"
|
||||
|
||||
log_message = f"[{level.upper()}] 用户 {user_nickname}: {content}"
|
||||
# # 动作参数定义
|
||||
# action_parameters = {
|
||||
# "topic": "想要思考的话题"
|
||||
# }
|
||||
|
||||
# 根据级别记录日志
|
||||
if level == "debug":
|
||||
logger.debug(log_message)
|
||||
elif level == "warn":
|
||||
logger.warning(log_message)
|
||||
elif level == "error":
|
||||
logger.error(log_message)
|
||||
else:
|
||||
logger.info(log_message)
|
||||
# # 动作使用场景
|
||||
# action_require = ["有些问题需要深度思考", "某个问题可能涉及多个方面", "某个问题涉及专业领域或者需要专业知识","这个问题讨论的很激烈,需要深度思考"]
|
||||
|
||||
# 不发送回复,让消息继续处理
|
||||
return True, f"已记录到{level}级别日志"
|
||||
# # 关联类型
|
||||
# associated_types = ["text"]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Log命令执行失败: {e}")
|
||||
return False, f"执行失败: {str(e)}"
|
||||
# async def execute(self) -> Tuple[bool, str]:
|
||||
# """执行回复动作"""
|
||||
# logger.info(f"{self.log_prefix} 决定深度思考")
|
||||
|
||||
# try:
|
||||
# # 获取聊天观察
|
||||
# chatting_observation = self._get_chatting_observation()
|
||||
# if not chatting_observation:
|
||||
# return False, "未找到聊天观察"
|
||||
|
||||
# talking_message_str = chatting_observation.talking_message_str
|
||||
|
||||
# # 处理回复目标
|
||||
# chat_stream = self.api.get_service("chat_stream")
|
||||
# anchor_message = await create_empty_anchor_message(chat_stream.platform, chat_stream.group_info, chat_stream)
|
||||
|
||||
|
||||
|
||||
# llm_model = self.api.get_available_models().replyer_1
|
||||
|
||||
# prompt = f"""
|
||||
# {talking_message_str}
|
||||
|
||||
# 在上面的聊天中,你对{self.action_data.get("topic", "")}感兴趣,形成深刻观点,请你思考,总结成一份学术论文,APA标准格式
|
||||
# """
|
||||
|
||||
# success, response, reasoning, model_name = await self.api.generate_with_model(prompt, llm_model)
|
||||
|
||||
# print(prompt)
|
||||
# print(f"DeepReplyAction: {response}")
|
||||
|
||||
# # prompt = f"""
|
||||
# # {talking_message_str}
|
||||
|
||||
# # 在上面的聊天中,你对{self.action_data.get("topic", "")}感兴趣,请你思考
|
||||
# # """
|
||||
|
||||
# extra_info_block = self.action_data.get("extra_info_block", "")
|
||||
# extra_info_block += response
|
||||
# # extra_info_block += f"\n--------------------------------\n注意,这是最重要的内容!!!!!你现在可以用比较长的篇幅来表达你的观点,不要只回复一个字或者几个字\n由于你进入了深度思考模式,所以下方聊天记录的回复要求不再适用,请你自由的表达,不论字数长短限制\n\n--------------------------------\n注意,你现在可以用比较长的篇幅来表达你的观点,不要只回复一个字或者几个字\n由于你进入了深度思考模式,所以下方聊天记录的回复要求不再适用,请你自由的表达,不论字数长短限制\n"
|
||||
# # extra_info_block += f"\n--------------------------------\n注意,优先关注这句!!!!你现在可以用比较长的篇幅来表达你的观点,不要只回复一个字或者几个字\n由于你进入了深度思考模式,所以下方聊天记录的回复要求不再适用,请你自由的表达,不论字数长短限制\n\n--------------------------------\n注意,你现在可以用比较长的篇幅来表达你的观点,不要只回复一个字或者几个字\n由于你进入了深度思考模式,所以其他的回复要求不再适用,请你自由的表达,不论字数长短限制\n"
|
||||
# self.action_data["extra_info_block"] = extra_info_block
|
||||
|
||||
|
||||
|
||||
|
||||
# # 获取回复器服务
|
||||
# # replyer = self.api.get_service("replyer")
|
||||
# # if not replyer:
|
||||
# # logger.error(f"{self.log_prefix} 未找到回复器服务")
|
||||
# # return False, "回复器服务不可用"
|
||||
|
||||
# # await self.send_message_by_expressor(extra_info_block)
|
||||
# await self.send_text(extra_info_block)
|
||||
# # 执行回复
|
||||
# # success, reply_set = await replyer.deal_reply(
|
||||
# # cycle_timers=self.cycle_timers,
|
||||
# # action_data=self.action_data,
|
||||
# # anchor_message=anchor_message,
|
||||
# # reasoning=self.reasoning,
|
||||
# # thinking_id=self.thinking_id,
|
||||
# # )
|
||||
|
||||
# # 构建回复文本
|
||||
# reply_text = "self._build_reply_text(reply_set)"
|
||||
|
||||
# # 存储动作记录
|
||||
# await self.api.store_action_info(
|
||||
# action_build_into_prompt=False,
|
||||
# action_prompt_display=reply_text,
|
||||
# action_done=True,
|
||||
# thinking_id=self.thinking_id,
|
||||
# action_data=self.action_data,
|
||||
# )
|
||||
|
||||
# # 重置NoReplyAction的连续计数器
|
||||
# NoReplyAction.reset_consecutive_count()
|
||||
|
||||
# return success, reply_text
|
||||
|
||||
# except Exception as e:
|
||||
# logger.error(f"{self.log_prefix} 回复动作执行失败: {e}")
|
||||
# return False, f"回复失败: {str(e)}"
|
||||
|
||||
# def _get_chatting_observation(self) -> Optional[ChattingObservation]:
|
||||
# """获取聊天观察对象"""
|
||||
# observations = self.api.get_service("observations") or []
|
||||
# for obs in observations:
|
||||
# if isinstance(obs, ChattingObservation):
|
||||
# return obs
|
||||
# return None
|
||||
|
||||
|
||||
# def _build_reply_text(self, reply_set) -> str:
|
||||
# """构建回复文本"""
|
||||
# reply_text = ""
|
||||
# if reply_set:
|
||||
# for reply in reply_set:
|
||||
# data = reply[1]
|
||||
# reply_text += data
|
||||
# return reply_text
|
||||
Reference in New Issue
Block a user