feat:移除reply动作,合并tool配置项

This commit is contained in:
SengokuCola
2025-07-26 17:29:41 +08:00
parent bffc372d95
commit 7d2cef9a9c
11 changed files with 153 additions and 285 deletions

View File

@@ -18,11 +18,12 @@ from src.chat.chat_loop.hfc_utils import CycleDetail
from src.person_info.relationship_builder_manager import relationship_builder_manager from src.person_info.relationship_builder_manager import relationship_builder_manager
from src.person_info.person_info import get_person_info_manager from src.person_info.person_info import get_person_info_manager
from src.plugin_system.base.component_types import ActionInfo, ChatMode from src.plugin_system.base.component_types import ActionInfo, ChatMode
from src.plugin_system.apis import generator_api, send_api, message_api from src.plugin_system.apis import generator_api, send_api, message_api, database_api
from src.chat.willing.willing_manager import get_willing_manager from src.chat.willing.willing_manager import get_willing_manager
from src.mais4u.mai_think import mai_thinking_manager from src.mais4u.mai_think import mai_thinking_manager
from maim_message.message_base import GroupInfo
from src.mais4u.constant_s4u import ENABLE_S4U from src.mais4u.constant_s4u import ENABLE_S4U
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
from src.chat.chat_loop.hfc_utils import send_typing, stop_typing
ERROR_LOOP_INFO = { ERROR_LOOP_INFO = {
"loop_plan_info": { "loop_plan_info": {
@@ -254,44 +255,19 @@ class HeartFChatting:
person_name = await person_info_manager.get_value(person_id, "person_name") person_name = await person_info_manager.get_value(person_id, "person_name")
return f"{person_name}:{message_data.get('processed_plain_text')}" return f"{person_name}:{message_data.get('processed_plain_text')}"
async def send_typing(self):
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
chat = await get_chat_manager().get_or_create_stream(
platform="amaidesu_default",
user_info=None,
group_info=group_info,
)
await send_api.custom_to_stream(
message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False
)
async def stop_typing(self):
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
chat = await get_chat_manager().get_or_create_stream(
platform="amaidesu_default",
user_info=None,
group_info=group_info,
)
await send_api.custom_to_stream(
message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False
)
async def _observe(self, message_data: Optional[Dict[str, Any]] = None): async def _observe(self, message_data: Optional[Dict[str, Any]] = None):
# sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else # sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else
if not message_data: if not message_data:
message_data = {} message_data = {}
action_type = "no_action" action_type = "no_action"
reply_text = "" # 初始化reply_text变量避免UnboundLocalError
# 创建新的循环信息 # 创建新的循环信息
cycle_timers, thinking_id = self.start_cycle() cycle_timers, thinking_id = self.start_cycle()
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]")
if ENABLE_S4U: if ENABLE_S4U:
await self.send_typing() await send_typing()
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
loop_start_time = time.time() loop_start_time = time.time()
@@ -310,7 +286,7 @@ class HeartFChatting:
# 如果normal开始一个回复生成进程先准备好回复其实是和planer同时进行的 # 如果normal开始一个回复生成进程先准备好回复其实是和planer同时进行的
if self.loop_mode == ChatMode.NORMAL: if self.loop_mode == ChatMode.NORMAL:
reply_to_str = await self.build_reply_to_str(message_data) reply_to_str = await self.build_reply_to_str(message_data)
gen_task = asyncio.create_task(self._generate_response(message_data, available_actions, reply_to_str)) gen_task = asyncio.create_task(self._generate_response(message_data, available_actions, reply_to_str, "chat.replyer.normal"))
with Timer("规划器", cycle_timers): with Timer("规划器", cycle_timers):
plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode) plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode)
@@ -326,7 +302,7 @@ class HeartFChatting:
action_data["loop_start_time"] = loop_start_time action_data["loop_start_time"] = loop_start_time
if self.loop_mode == ChatMode.NORMAL: if self.loop_mode == ChatMode.NORMAL:
if action_type == "no_action": if action_type == "reply":
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复") logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复")
elif is_parallel: elif is_parallel:
logger.info( logger.info(
@@ -335,45 +311,86 @@ class HeartFChatting:
else: else:
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定执行{action_type}动作") logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定执行{action_type}动作")
if action_type == "no_action": action_message: Dict[str, Any] = message_data or target_message # type: ignore
if action_type == "no_action" or (self.loop_mode == ChatMode.FOCUS and action_type == "reply"):
# 等待回复生成完毕 # 等待回复生成完毕
gather_timeout = global_config.chat.thinking_timeout if action_type == "no_action":
try: gather_timeout = global_config.chat.thinking_timeout
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) try:
except asyncio.TimeoutError: response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
response_set = None except asyncio.TimeoutError:
logger.warning(f"{self.log_prefix} 回复生成超时>{global_config.chat.thinking_timeout}s已跳过")
response_set = None
if response_set: if response_set:
content = " ".join([item[1] for item in response_set if item[0] == "text"]) content = " ".join([item[1] for item in response_set if item[0] == "text"])
# 模型炸了,没有回复内容生成 # 模型炸了或超时,没有回复内容生成
if not response_set: if not response_set:
logger.warning(f"{self.log_prefix}模型未生成回复内容") logger.warning(f"{self.log_prefix}模型未生成回复内容")
return False return False
elif action_type not in ["no_action"] and not is_parallel: elif action_type not in ["no_action"] and not is_parallel:
logger.info( logger.info(
f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复"
) )
return False return False
else:
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定的回复内容: {content}") logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复 (focus模式)")
# 发送回复 (不再需要传入 chat) # 构建reply_to字符串
reply_text = await self._send_response(response_set, reply_to_str, loop_start_time,message_data) reply_to_str = await self.build_reply_to_str(action_message)
# 生成回复
with Timer("回复生成", cycle_timers):
response_set = await self._generate_response(action_message, available_actions, reply_to_str, request_type="chat.replyer.focus")
if not response_set:
logger.warning(f"{self.log_prefix}模型未生成回复内容")
return False
# 发送回复
with Timer("回复发送", cycle_timers):
reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, action_message)
# 存储reply action信息 (focus模式)
person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id(
action_message.get("chat_info_platform", ""),
action_message.get("user_id", ""),
)
person_name = await person_info_manager.get_value(person_id, "person_name")
action_prompt_display = f"你对{person_name}进行了回复:{reply_text}"
await database_api.store_action_info(
chat_stream=self.chat_stream,
action_build_into_prompt=False,
action_prompt_display=action_prompt_display,
action_done=True,
thinking_id=thinking_id,
action_data={"reply_text": reply_text, "reply_to": reply_to_str},
action_name="reply",
)
# 构建循环信息
loop_info = {
"loop_plan_info": {
"action_result": plan_result.get("action_result", {}),
},
"loop_action_info": {
"action_taken": True,
"reply_text": reply_text,
"command": "",
"taken_time": time.time(),
},
}
success = True
command = ""
if ENABLE_S4U:
await self.stop_typing()
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
return True return True
else: else:
action_message: Dict[str, Any] = message_data or target_message # type: ignore
# 动作执行计时 # 动作执行计时
with Timer("动作执行", cycle_timers): with Timer("动作执行", cycle_timers):
success, reply_text, command = await self._handle_action( success, reply_text, command = await self._handle_action(
@@ -392,11 +409,11 @@ class HeartFChatting:
}, },
} }
if loop_info["loop_action_info"]["command"] == "stop_focus_chat":
logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天")
return False
# 停止该聊天模式的循环
if ENABLE_S4U:
await stop_typing()
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
self.end_cycle(loop_info, cycle_timers) self.end_cycle(loop_info, cycle_timers)
self.print_cycle_info(cycle_timers) self.print_cycle_info(cycle_timers)
@@ -406,13 +423,11 @@ class HeartFChatting:
# 管理no_reply计数器当执行了非no_reply动作时重置计数器 # 管理no_reply计数器当执行了非no_reply动作时重置计数器
if action_type != "no_reply" and action_type != "no_action": if action_type != "no_reply" and action_type != "no_action":
# 导入NoReplyAction并重置计数器 # 导入NoReplyAction并重置计数器
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
NoReplyAction.reset_consecutive_count() NoReplyAction.reset_consecutive_count()
logger.info(f"{self.log_prefix} 执行了{action_type}动作重置no_reply计数器") logger.info(f"{self.log_prefix} 执行了{action_type}动作重置no_reply计数器")
return True return True
elif action_type == "no_action": elif action_type == "no_action":
# 当执行回复动作时也重置no_reply计数器 # 当执行回复动作时也重置no_reply计数器s
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
NoReplyAction.reset_consecutive_count() NoReplyAction.reset_consecutive_count()
logger.info(f"{self.log_prefix} 执行了回复动作重置no_reply计数器") logger.info(f"{self.log_prefix} 执行了回复动作重置no_reply计数器")
@@ -551,7 +566,7 @@ class HeartFChatting:
return False return False
async def _generate_response( async def _generate_response(
self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str, request_type: str = "chat.replyer.normal"
) -> Optional[list]: ) -> Optional[list]:
"""生成普通回复""" """生成普通回复"""
try: try:
@@ -559,8 +574,8 @@ class HeartFChatting:
chat_stream=self.chat_stream, chat_stream=self.chat_stream,
reply_to=reply_to, reply_to=reply_to,
available_actions=available_actions, available_actions=available_actions,
enable_tool=global_config.tool.enable_in_normal_chat, enable_tool=global_config.tool.enable_tool,
request_type="chat.replyer.normal", request_type=request_type,
) )
if not success or not reply_set: if not success or not reply_set:
@@ -589,7 +604,7 @@ class HeartFChatting:
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复" f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
) )
else: else:
logger.debug( logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复" f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
) )

View File

@@ -1,10 +1,13 @@
import time import time
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from src.config.config import global_config from src.config.config import global_config
from src.common.message_repository import count_messages
from src.common.logger import get_logger from src.common.logger import get_logger
from src.chat.message_receive.chat_stream import get_chat_manager
from src.plugin_system.apis import send_api
from maim_message.message_base import GroupInfo
from src.common.message_repository import count_messages
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -106,3 +109,30 @@ def get_recent_message_stats(minutes: float = 30, chat_id: Optional[str] = None)
bot_reply_count = count_messages(bot_filter) bot_reply_count = count_messages(bot_filter)
return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count} return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count}
async def send_typing():
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
chat = await get_chat_manager().get_or_create_stream(
platform="amaidesu_default",
user_info=None,
group_info=group_info,
)
await send_api.custom_to_stream(
message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False
)
async def stop_typing():
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
chat = await get_chat_manager().get_or_create_stream(
platform="amaidesu_default",
user_info=None,
group_info=group_info,
)
await send_api.custom_to_stream(
message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False
)

View File

@@ -312,7 +312,7 @@ class ActionPlanner:
by_what = "聊天内容和用户的最新消息" by_what = "聊天内容和用户的最新消息"
target_prompt = "" target_prompt = ""
no_action_block = """重要说明: no_action_block = """重要说明:
- 'no_action' 表示只进行普通聊天回复,不执行任何额外动作 - 'reply' 表示只进行普通聊天回复,不执行任何额外动作
- 其他action表示在普通回复的基础上执行相应的额外动作""" - 其他action表示在普通回复的基础上执行相应的额外动作"""
chat_context_description = "你现在正在一个群聊中" chat_context_description = "你现在正在一个群聊中"

View File

@@ -151,7 +151,6 @@ class DefaultReplyer:
async def generate_reply_with_context( async def generate_reply_with_context(
self, self,
reply_data: Optional[Dict[str, Any]] = None,
reply_to: str = "", reply_to: str = "",
extra_info: str = "", extra_info: str = "",
available_actions: Optional[Dict[str, ActionInfo]] = None, available_actions: Optional[Dict[str, ActionInfo]] = None,
@@ -160,29 +159,24 @@ class DefaultReplyer:
) -> Tuple[bool, Optional[str], Optional[str]]: ) -> Tuple[bool, Optional[str], Optional[str]]:
""" """
回复器 (Replier): 核心逻辑,负责生成回复文本。 回复器 (Replier): 核心逻辑,负责生成回复文本。
(已整合原 HeartFCGenerator 的功能)
""" """
prompt = None prompt = None
if available_actions is None: if available_actions is None:
available_actions = {} available_actions = {}
try: try:
if not reply_data:
reply_data = {
"reply_to": reply_to,
"extra_info": extra_info,
}
for key, value in reply_data.items():
if not value:
logger.debug(f"回复数据跳过{key},生成回复时将忽略。")
# 3. 构建 Prompt # 3. 构建 Prompt
with Timer("构建Prompt", {}): # 内部计时器,可选保留 with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await self.build_prompt_reply_context( prompt = await self.build_prompt_reply_context(
reply_data=reply_data, # 传递action_data reply_to = reply_to,
extra_info=extra_info,
available_actions=available_actions, available_actions=available_actions,
enable_timeout=enable_timeout, enable_timeout=enable_timeout,
enable_tool=enable_tool, enable_tool=enable_tool,
) )
if not prompt:
logger.warning("构建prompt失败跳过回复生成")
return False, None, None
# 4. 调用 LLM 生成回复 # 4. 调用 LLM 生成回复
content = None content = None
@@ -282,14 +276,13 @@ class DefaultReplyer:
traceback.print_exc() traceback.print_exc()
return False, None return False, None
async def build_relation_info(self, reply_data=None): async def build_relation_info(self, reply_to: str = ""):
if not global_config.relationship.enable_relationship: if not global_config.relationship.enable_relationship:
return "" return ""
relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id) relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id)
if not reply_data: if not reply_to:
return "" return ""
reply_to = reply_data.get("reply_to", "")
sender, text = self._parse_reply_target(reply_to) sender, text = self._parse_reply_target(reply_to)
if not sender or not text: if not sender or not text:
return "" return ""
@@ -381,7 +374,7 @@ class DefaultReplyer:
return memory_str return memory_str
async def build_tool_info(self, chat_history, reply_data: Optional[Dict], enable_tool: bool = True): async def build_tool_info(self, chat_history, reply_to: str = "", enable_tool: bool = True):
"""构建工具信息块 """构建工具信息块
Args: Args:
@@ -395,10 +388,9 @@ class DefaultReplyer:
if not enable_tool: if not enable_tool:
return "" return ""
if not reply_data: if not reply_to:
return "" return ""
reply_to = reply_data.get("reply_to", "")
sender, text = self._parse_reply_target(reply_to) sender, text = self._parse_reply_target(reply_to)
if not text: if not text:
@@ -577,7 +569,8 @@ class DefaultReplyer:
async def build_prompt_reply_context( async def build_prompt_reply_context(
self, self,
reply_data: Dict[str, Any], reply_to: str,
extra_info: str = "",
available_actions: Optional[Dict[str, ActionInfo]] = None, available_actions: Optional[Dict[str, ActionInfo]] = None,
enable_timeout: bool = False, enable_timeout: bool = False,
enable_tool: bool = True, enable_tool: bool = True,
@@ -602,8 +595,6 @@ class DefaultReplyer:
chat_id = chat_stream.stream_id chat_id = chat_stream.stream_id
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
is_group_chat = bool(chat_stream.group_info) is_group_chat = bool(chat_stream.group_info)
reply_to = reply_data.get("reply_to", "none")
extra_info_block = reply_data.get("extra_info", "") or reply_data.get("extra_info_block", "")
if global_config.mood.enable_mood: if global_config.mood.enable_mood:
chat_mood = mood_manager.get_mood_by_chat_id(chat_id) chat_mood = mood_manager.get_mood_by_chat_id(chat_id)
@@ -612,6 +603,13 @@ class DefaultReplyer:
mood_prompt = "" mood_prompt = ""
sender, target = self._parse_reply_target(reply_to) sender, target = self._parse_reply_target(reply_to)
person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id_by_person_name(sender)
user_id = person_info_manager.get_value_sync(person_id, "user_id")
platform = chat_stream.platform
if user_id == global_config.bot.qq_account and platform == global_config.bot.platform:
logger.warning("选取了自身作为回复对象跳过构建prompt")
return ""
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True) target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
@@ -630,21 +628,6 @@ class DefaultReplyer:
limit=global_config.chat.max_context_size * 2, limit=global_config.chat.max_context_size * 2,
) )
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_id,
timestamp=time.time(),
limit=global_config.chat.max_context_size,
)
chat_talking_prompt = build_readable_messages(
message_list_before_now,
replace_bot_name=True,
merge_messages=False,
timestamp_mode="normal_no_YMD",
read_mark=0.0,
truncate=True,
show_actions=True,
)
message_list_before_short = get_raw_msg_before_timestamp_with_chat( message_list_before_short = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_id, chat_id=chat_id,
timestamp=time.time(), timestamp=time.time(),
@@ -664,10 +647,10 @@ class DefaultReplyer:
self._time_and_run_task( self._time_and_run_task(
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits" self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
), ),
self._time_and_run_task(self.build_relation_info(reply_data), "relation_info"), self._time_and_run_task(self.build_relation_info(reply_to), "relation_info"),
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"), self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
self._time_and_run_task( self._time_and_run_task(
self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "tool_info" self.build_tool_info(chat_talking_prompt_short, reply_to, enable_tool=enable_tool), "tool_info"
), ),
self._time_and_run_task(get_prompt_info(target, threshold=0.38), "prompt_info"), self._time_and_run_task(get_prompt_info(target, threshold=0.38), "prompt_info"),
) )
@@ -700,8 +683,8 @@ class DefaultReplyer:
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target) keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
if extra_info_block: if extra_info:
extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info_block}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策" extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策"
else: else:
extra_info_block = "" extra_info_block = ""

View File

@@ -77,7 +77,7 @@ class ChatConfig(ConfigBase):
选择普通模型的概率为 1 - reasoning_normal_model_probability 选择普通模型的概率为 1 - reasoning_normal_model_probability
""" """
thinking_timeout: int = 30 thinking_timeout: int = 40
"""麦麦最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢""" """麦麦最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢"""
talk_frequency: float = 1 talk_frequency: float = 1
@@ -299,11 +299,8 @@ class ExpressionConfig(ConfigBase):
class ToolConfig(ConfigBase): class ToolConfig(ConfigBase):
"""工具配置类""" """工具配置类"""
enable_in_normal_chat: bool = False enable_tool: bool = False
"""是否在普通聊天中启用工具""" """是否在聊天中启用工具"""
enable_in_focus_chat: bool = True
"""是否在专注聊天中启用工具"""
@dataclass @dataclass
class VoiceConfig(ConfigBase): class VoiceConfig(ConfigBase):

View File

@@ -107,10 +107,14 @@ async def generate_reply(
return False, [], None return False, [], None
logger.debug("[GeneratorAPI] 开始生成回复") logger.debug("[GeneratorAPI] 开始生成回复")
if not reply_to:
reply_to = action_data.get("reply_to", "")
if not extra_info and action_data:
extra_info = action_data.get("extra_info", "")
# 调用回复器生成回复 # 调用回复器生成回复
success, content, prompt = await replyer.generate_reply_with_context( success, content, prompt = await replyer.generate_reply_with_context(
reply_data=action_data or {},
reply_to=reply_to, reply_to=reply_to,
extra_info=extra_info, extra_info=extra_info,
available_actions=available_actions, available_actions=available_actions,

View File

@@ -24,11 +24,6 @@
"is_built_in": true, "is_built_in": true,
"plugin_type": "action_provider", "plugin_type": "action_provider",
"components": [ "components": [
{
"type": "action",
"name": "reply",
"description": "参与聊天回复,发送文本进行表达"
},
{ {
"type": "action", "type": "action",
"name": "no_reply", "name": "no_reply",

View File

@@ -61,7 +61,6 @@ class NoReplyAction(BaseAction):
async def execute(self) -> Tuple[bool, str]: async def execute(self) -> Tuple[bool, str]:
"""执行不回复动作""" """执行不回复动作"""
import asyncio
try: try:
reason = self.action_data.get("reason", "") reason = self.action_data.get("reason", "")

View File

@@ -18,7 +18,6 @@ from src.common.logger import get_logger
# 导入API模块 - 标准Python包方式 # 导入API模块 - 标准Python包方式
from src.plugins.built_in.core_actions.no_reply import NoReplyAction from src.plugins.built_in.core_actions.no_reply import NoReplyAction
from src.plugins.built_in.core_actions.emoji import EmojiAction from src.plugins.built_in.core_actions.emoji import EmojiAction
from src.plugins.built_in.core_actions.reply import ReplyAction
logger = get_logger("core_actions") logger = get_logger("core_actions")
@@ -52,10 +51,9 @@ class CoreActionsPlugin(BasePlugin):
config_schema: dict = { config_schema: dict = {
"plugin": { "plugin": {
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"config_version": ConfigField(type=str, default="0.4.0", description="配置文件版本"), "config_version": ConfigField(type=str, default="0.5.0", description="配置文件版本"),
}, },
"components": { "components": {
"enable_reply": ConfigField(type=bool, default=True, description="是否启用回复动作"),
"enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"), "enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"),
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"), "enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
}, },
@@ -74,8 +72,6 @@ class CoreActionsPlugin(BasePlugin):
# --- 根据配置注册组件 --- # --- 根据配置注册组件 ---
components = [] components = []
if self.get_config("components.enable_reply", True):
components.append((ReplyAction.get_action_info(), ReplyAction))
if self.get_config("components.enable_no_reply", True): if self.get_config("components.enable_no_reply", True):
components.append((NoReplyAction.get_action_info(), NoReplyAction)) components.append((NoReplyAction.get_action_info(), NoReplyAction))
if self.get_config("components.enable_emoji", True): if self.get_config("components.enable_emoji", True):

View File

@@ -1,150 +0,0 @@
# 导入新插件系统
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
from src.config.config import global_config
import random
import time
from typing import Tuple
import asyncio
import re
import traceback
# 导入依赖的系统组件
from src.common.logger import get_logger
# 导入API模块 - 标准Python包方式
from src.plugin_system.apis import generator_api, message_api
# 注释不再需要导入NoReplyAction因为计数器管理已移至heartFC_chat.py
# from src.plugins.built_in.core_actions.no_reply import NoReplyAction
from src.person_info.person_info import get_person_info_manager
from src.mais4u.mai_think import mai_thinking_manager
from src.mais4u.constant_s4u import ENABLE_S4U
logger = get_logger("reply_action")
class ReplyAction(BaseAction):
"""回复动作 - 参与聊天回复"""
# 激活设置
focus_activation_type = ActionActivationType.NEVER
normal_activation_type = ActionActivationType.NEVER
mode_enable = ChatMode.FOCUS
parallel_action = False
# 动作基本信息
action_name = "reply"
action_description = ""
# 动作参数定义
action_parameters = {}
# 动作使用场景
action_require = [""]
# 关联类型
associated_types = ["text"]
def _parse_reply_target(self, target_message: str) -> tuple:
sender = ""
target = ""
# 添加None检查防止NoneType错误
if target_message is None:
return sender, target
if ":" in target_message or "" in target_message:
# 使用正则表达式匹配中文或英文冒号
parts = re.split(pattern=r"[:]", string=target_message, maxsplit=1)
if len(parts) == 2:
sender = parts[0].strip()
target = parts[1].strip()
return sender, target
async def execute(self) -> Tuple[bool, str]:
"""执行回复动作"""
logger.debug(f"{self.log_prefix} 决定进行回复")
start_time = self.action_data.get("loop_start_time", time.time())
user_id = self.user_id
platform = self.platform
# logger.info(f"{self.log_prefix} 用户ID: {user_id}, 平台: {platform}")
person_id = get_person_info_manager().get_person_id(platform, user_id) # type: ignore
# logger.info(f"{self.log_prefix} 人物ID: {person_id}")
person_name = get_person_info_manager().get_value_sync(person_id, "person_name")
reply_to = f"{person_name}:{self.action_message.get('processed_plain_text', '')}" # type: ignore
logger.info(f"{self.log_prefix} 决定进行回复,目标: {reply_to}")
try:
if prepared_reply := self.action_data.get("prepared_reply", ""):
reply_text = prepared_reply
else:
try:
success, reply_set, _ = await asyncio.wait_for(
generator_api.generate_reply(
extra_info="",
reply_to=reply_to,
chat_id=self.chat_id,
request_type="chat.replyer.focus",
enable_tool=global_config.tool.enable_in_focus_chat,
),
timeout=global_config.chat.thinking_timeout,
)
except asyncio.TimeoutError:
logger.warning(f"{self.log_prefix} 回复生成超时 ({global_config.chat.thinking_timeout}s)")
return False, "timeout"
# 检查从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 >= random.randint(2, 4)
if need_reply:
logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
)
else:
logger.debug(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
)
# 构建回复文本
reply_text = ""
first_replied = False
reply_to_platform_id = f"{platform}:{user_id}"
for reply_seg in reply_set:
data = reply_seg[1]
if not first_replied:
if need_reply:
await self.send_text(
content=data, reply_to=reply_to, reply_to_platform_id=reply_to_platform_id, typing=False
)
else:
await self.send_text(content=data, reply_to_platform_id=reply_to_platform_id, typing=False)
first_replied = True
else:
await self.send_text(content=data, reply_to_platform_id=reply_to_platform_id, typing=True)
reply_text += data
# 存储动作记录
reply_text = f"你对{person_name}进行了回复:{reply_text}"
if ENABLE_S4U:
await mai_thinking_manager.get_mai_think(self.chat_id).do_think_after_response(reply_text)
await self.store_action_info(
action_build_into_prompt=False,
action_prompt_display=reply_text,
action_done=True,
)
# 注释重置NoReplyAction的连续计数器现在由heartFC_chat.py统一管理
# NoReplyAction.reset_consecutive_count()
return success, reply_text
except Exception as e:
logger.error(f"{self.log_prefix} 回复动作执行失败: {e}")
traceback.print_exc()
return False, f"回复失败: {str(e)}"

View File

@@ -58,7 +58,7 @@ focus_value = 1
willing_amplifier = 1 # 麦麦回复意愿 willing_amplifier = 1 # 麦麦回复意愿
max_context_size = 25 # 上下文长度 max_context_size = 25 # 上下文长度
thinking_timeout = 20 # 麦麦一次回复最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢 thinking_timeout = 40 # 麦麦一次回复最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复 mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
@@ -107,8 +107,7 @@ ban_msgs_regex = [
willing_mode = "classical" # 回复意愿模式 —— 经典模式classicalmxp模式mxp自定义模式custom需要你自己实现 willing_mode = "classical" # 回复意愿模式 —— 经典模式classicalmxp模式mxp自定义模式custom需要你自己实现
[tool] [tool]
enable_in_normal_chat = false # 是否在普通聊天中启用工具 enable_tool = false # 是否在普通聊天中启用工具
enable_in_focus_chat = true # 是否在专注聊天中启用工具
[emoji] [emoji]
emoji_chance = 0.6 # 麦麦激活表情包动作的概率 emoji_chance = 0.6 # 麦麦激活表情包动作的概率