feat:大幅优化聊天流控制,更精准简洁

pull/1224/head
SengokuCola 2025-08-31 12:35:01 +08:00
parent 4bee6002ff
commit a11e65f794
18 changed files with 458 additions and 313 deletions

View File

@ -64,6 +64,11 @@
> - QQ 机器人存在被限制风险,请自行了解,谨慎使用。 > - QQ 机器人存在被限制风险,请自行了解,谨慎使用。
> - 由于程序处于开发中,可能消耗较多 token。 > - 由于程序处于开发中,可能消耗较多 token。
## 麦麦MC项目早期开发
[让麦麦玩MC](https://github.com/MaiM-with-u/Maicraft)
交流群1058573197
## 💬 讨论 ## 💬 讨论
**技术交流群:** **技术交流群:**

View File

@ -1,15 +1,15 @@
# Changelog # Changelog
TODO回复频率动态控制 ## [0.10.2] - 2025-8-31
## [0.10.2] - 2025-8-24
### 🌟 主要功能更改 ### 🌟 主要功能更改
- 大幅优化了聊天逻辑,更易配置
- 记忆系统重新启用,更好更优秀 - 记忆系统重新启用,更好更优秀
- 更好的event系统 - 更好的event系统
- 为空回复添加重试机制 - 现在支持提及100%回复
### 细节功能更改 ### 细节功能更改
- 为空回复添加重试机制
- 修复tts插件可能的复读问题 - 修复tts插件可能的复读问题

View File

@ -3,26 +3,7 @@ from src.config.config import global_config
from src.chat.frequency_control.utils import parse_stream_config_to_chat_id from src.chat.frequency_control.utils import parse_stream_config_to_chat_id
class FocusValueControl: def get_config_base_focus_value(chat_id: Optional[str] = None) -> float:
def __init__(self, chat_id: str):
self.chat_id = chat_id
self.focus_value_adjust: float = 1
def get_current_focus_value(self) -> float:
return get_current_focus_value(self.chat_id) * self.focus_value_adjust
class FocusValueControlManager:
def __init__(self):
self.focus_value_controls: dict[str, FocusValueControl] = {}
def get_focus_value_control(self, chat_id: str) -> FocusValueControl:
if chat_id not in self.focus_value_controls:
self.focus_value_controls[chat_id] = FocusValueControl(chat_id)
return self.focus_value_controls[chat_id]
def get_current_focus_value(chat_id: Optional[str] = None) -> float:
""" """
根据当前时间和聊天流获取对应的 focus_value 根据当前时间和聊天流获取对应的 focus_value
""" """
@ -139,5 +120,3 @@ def get_global_focus_value() -> Optional[float]:
return None return None
focus_value_control = FocusValueControlManager()

View File

@ -0,0 +1,275 @@
import time
from typing import Optional, Dict, List
from src.plugin_system.apis import message_api
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
from src.common.logger import get_logger
from src.config.config import global_config
from src.chat.frequency_control.talk_frequency_control import get_config_base_talk_frequency
from src.chat.frequency_control.focus_value_control import get_config_base_focus_value
logger = get_logger("frequency_control")
class FrequencyControl:
"""
频率控制类可以根据最近时间段的发言数量和发言人数动态调整频率
"""
def __init__(self, chat_id: str):
self.chat_id = chat_id
self.chat_stream: ChatStream = get_chat_manager().get_stream(self.chat_id)
if not self.chat_stream:
raise ValueError(f"无法找到聊天流: {self.chat_id}")
self.log_prefix = f"[{get_chat_manager().get_stream_name(self.chat_id) or self.chat_id}]"
# 发言频率调整值
self.talk_frequency_adjust: float = 1.0
self.talk_frequency_external_adjust: float = 1.0
# 专注度调整值
self.focus_value_adjust: float = 1.0
self.focus_value_external_adjust: float = 1.0
# 动态调整相关参数
self.last_update_time = time.time()
self.update_interval = 60 # 每60秒更新一次
# 历史数据缓存
self._message_count_cache = 0
self._user_count_cache = 0
self._last_cache_time = 0
self._cache_duration = 30 # 缓存30秒
# 调整参数
self.min_adjust = 0.3 # 最小调整值
self.max_adjust = 2.0 # 最大调整值
# 基准值(可根据实际情况调整)
self.base_message_count = 5 # 基准消息数量
self.base_user_count = 3 # 基准用户数量
# 平滑因子
self.smoothing_factor = 0.3
def get_dynamic_talk_frequency_adjust(self) -> float:
"""
获取纯动态调整值不包含配置文件基础值
Returns:
float: 动态调整值
"""
self._update_talk_frequency_adjust()
return self.talk_frequency_adjust
def get_dynamic_focus_value_adjust(self) -> float:
"""
获取纯动态调整值不包含配置文件基础值
Returns:
float: 动态调整值
"""
self._update_focus_value_adjust()
return self.focus_value_adjust
def _update_talk_frequency_adjust(self):
"""
更新发言频率调整值
适合人少话多的时候人少但消息多提高回复频率
"""
current_time = time.time()
# 检查是否需要更新
if current_time - self.last_update_time < self.update_interval:
return
try:
# 获取最近30分钟的数据发言频率更敏感
recent_messages = message_api.get_messages_by_time_in_chat(
chat_id=self.chat_stream.stream_id,
start_time=current_time - 1800, # 30分钟前
end_time=current_time,
filter_mai=True,
filter_command=True
)
# 计算消息数量和用户数量
message_count = len(recent_messages)
user_ids = set()
for msg in recent_messages:
if msg.user_info and msg.user_info.user_id:
user_ids.add(msg.user_info.user_id)
user_count = len(user_ids)
# 发言频率调整逻辑:人少话多时提高回复频率
if user_count > 0:
# 计算人均消息数
messages_per_user = message_count / user_count
# 基准人均消息数
base_messages_per_user = self.base_message_count / self.base_user_count if self.base_user_count > 0 else 1.0
# 如果人均消息数高,说明活跃度高,提高回复频率
if messages_per_user > base_messages_per_user:
# 人少话多:提高回复频率
target_talk_adjust = min(self.max_adjust, messages_per_user / base_messages_per_user)
else:
# 活跃度一般:保持正常
target_talk_adjust = 1.0
else:
target_talk_adjust = 1.0
# 限制调整范围
target_talk_adjust = max(self.min_adjust, min(self.max_adjust, target_talk_adjust))
# 平滑调整
self.talk_frequency_adjust = (
self.talk_frequency_adjust * (1 - self.smoothing_factor) +
target_talk_adjust * self.smoothing_factor
)
logger.info(
f"{self.log_prefix} 发言频率调整更新: "
f"消息数={message_count}, 用户数={user_count}, "
f"人均消息数={message_count/user_count if user_count > 0 else 0:.2f}, "
f"调整值={self.talk_frequency_adjust:.2f}"
)
except Exception as e:
logger.error(f"{self.log_prefix} 更新发言频率调整值时出错: {e}")
def _update_focus_value_adjust(self):
"""
更新专注度调整值
适合人多话多的时候人多且消息多提高专注度LLM消耗更多但回复更精准
"""
current_time = time.time()
# 检查是否需要更新
if current_time - self.last_update_time < self.update_interval:
return
try:
# 获取最近1小时的数据
recent_messages = message_api.get_messages_by_time_in_chat(
chat_id=self.chat_stream.stream_id,
start_time=current_time - 3600, # 1小时前
end_time=current_time,
filter_mai=True,
filter_command=True
)
# 计算消息数量和用户数量
message_count = len(recent_messages)
user_ids = set()
for msg in recent_messages:
if msg.user_info and msg.user_info.user_id:
user_ids.add(msg.user_info.user_id)
user_count = len(user_ids)
# 专注度调整逻辑:人多话多时提高专注度
if user_count > 0 and self.base_user_count > 0:
# 计算用户活跃度比率
user_ratio = user_count / self.base_user_count
# 计算消息活跃度比率
message_ratio = message_count / self.base_message_count if self.base_message_count > 0 else 1.0
# 如果用户多且消息多,提高专注度
if user_ratio > 1.2 and message_ratio > 1.2:
# 人多话多提高专注度消耗更多LLM资源但回复更精准
target_focus_adjust = min(self.max_adjust, (user_ratio + message_ratio) / 2)
elif user_ratio > 1.5:
# 用户特别多:适度提高专注度
target_focus_adjust = min(self.max_adjust, 1.0 + (user_ratio - 1.0) * 0.3)
else:
# 正常情况:保持默认专注度
target_focus_adjust = 1.0
else:
target_focus_adjust = 1.0
# 限制调整范围
target_focus_adjust = max(self.min_adjust, min(self.max_adjust, target_focus_adjust))
# 平滑调整
self.focus_value_adjust = (
self.focus_value_adjust * (1 - self.smoothing_factor) +
target_focus_adjust * self.smoothing_factor
)
logger.info(
f"{self.log_prefix} 专注度调整更新: "
f"消息数={message_count}, 用户数={user_count}, "
f"用户比率={user_count/self.base_user_count if self.base_user_count > 0 else 0:.2f}, "
f"消息比率={message_count/self.base_message_count if self.base_message_count > 0 else 0:.2f}, "
f"调整值={self.focus_value_adjust:.2f}"
)
except Exception as e:
logger.error(f"{self.log_prefix} 更新专注度调整值时出错: {e}")
def get_final_talk_frequency(self) -> float:
return get_config_base_talk_frequency(self.chat_stream.stream_id) * self.get_dynamic_talk_frequency_adjust() * self.talk_frequency_external_adjust
def get_final_focus_value(self) -> float:
return get_config_base_focus_value(self.chat_stream.stream_id) * self.get_dynamic_focus_value_adjust() * self.focus_value_external_adjust
def set_adjustment_parameters(
self,
min_adjust: Optional[float] = None,
max_adjust: Optional[float] = None,
base_message_count: Optional[int] = None,
base_user_count: Optional[int] = None,
smoothing_factor: Optional[float] = None,
update_interval: Optional[int] = None
):
"""
设置调整参数
Args:
min_adjust: 最小调整值
max_adjust: 最大调整值
base_message_count: 基准消息数量
base_user_count: 基准用户数量
smoothing_factor: 平滑因子
update_interval: 更新间隔
"""
if min_adjust is not None:
self.min_adjust = max(0.1, min_adjust)
if max_adjust is not None:
self.max_adjust = max(1.0, max_adjust)
if base_message_count is not None:
self.base_message_count = max(1, base_message_count)
if base_user_count is not None:
self.base_user_count = max(1, base_user_count)
if smoothing_factor is not None:
self.smoothing_factor = max(0.0, min(1.0, smoothing_factor))
if update_interval is not None:
self.update_interval = max(10, update_interval)
class FrequencyControlManager:
"""
频率控制管理器管理多个聊天流的频率控制实例
"""
def __init__(self):
self.frequency_control_dict: Dict[str, FrequencyControl] = {}
def get_or_create_frequency_control(self, chat_id: str) -> FrequencyControl:
"""
获取或创建指定聊天流的频率控制实例
Args:
chat_id: 聊天流ID
Returns:
FrequencyControl: 频率控制实例
"""
if chat_id not in self.frequency_control_dict:
self.frequency_control_dict[chat_id] = FrequencyControl(chat_id)
return self.frequency_control_dict[chat_id]
# 创建全局实例
frequency_control_manager = FrequencyControlManager()

View File

@ -3,26 +3,7 @@ from src.config.config import global_config
from src.chat.frequency_control.utils import parse_stream_config_to_chat_id from src.chat.frequency_control.utils import parse_stream_config_to_chat_id
class TalkFrequencyControl: def get_config_base_talk_frequency(chat_id: Optional[str] = None) -> float:
def __init__(self, chat_id: str):
self.chat_id = chat_id
self.talk_frequency_adjust: float = 1
def get_current_talk_frequency(self) -> float:
return get_current_talk_frequency(self.chat_id) * self.talk_frequency_adjust
class TalkFrequencyControlManager:
def __init__(self):
self.talk_frequency_controls = {}
def get_talk_frequency_control(self, chat_id: str) -> TalkFrequencyControl:
if chat_id not in self.talk_frequency_controls:
self.talk_frequency_controls[chat_id] = TalkFrequencyControl(chat_id)
return self.talk_frequency_controls[chat_id]
def get_current_talk_frequency(chat_id: Optional[str] = None) -> float:
""" """
根据当前时间和聊天流获取对应的 talk_frequency 根据当前时间和聊天流获取对应的 talk_frequency
@ -145,4 +126,3 @@ def get_global_frequency() -> Optional[float]:
return None return None
talk_frequency_control = TalkFrequencyControlManager()

View File

@ -18,8 +18,7 @@ from src.chat.planner_actions.action_modifier import ActionModifier
from src.chat.planner_actions.action_manager import ActionManager from src.chat.planner_actions.action_manager import ActionManager
from src.chat.heart_flow.hfc_utils import CycleDetail from src.chat.heart_flow.hfc_utils import CycleDetail
from src.chat.heart_flow.hfc_utils import send_typing, stop_typing from src.chat.heart_flow.hfc_utils import send_typing, stop_typing
from src.chat.frequency_control.talk_frequency_control import talk_frequency_control from src.chat.frequency_control.frequency_control import frequency_control_manager
from src.chat.frequency_control.focus_value_control import focus_value_control
from src.chat.express.expression_learner import expression_learner_manager from src.chat.express.expression_learner import expression_learner_manager
from src.person_info.person_info import Person from src.person_info.person_info import Person
from src.plugin_system.base.component_types import ChatMode, EventType, ActionInfo from src.plugin_system.base.component_types import ChatMode, EventType, ActionInfo
@ -85,8 +84,7 @@ class HeartFChatting:
self.expression_learner = expression_learner_manager.get_expression_learner(self.stream_id) self.expression_learner = expression_learner_manager.get_expression_learner(self.stream_id)
self.talk_frequency_control = talk_frequency_control.get_talk_frequency_control(self.stream_id) self.frequency_control = frequency_control_manager.get_or_create_frequency_control(self.stream_id)
self.focus_value_control = focus_value_control.get_focus_value_control(self.stream_id)
self.action_manager = ActionManager() self.action_manager = ActionManager()
self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager) self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager)
@ -101,15 +99,8 @@ class HeartFChatting:
self._cycle_counter = 0 self._cycle_counter = 0
self._current_cycle_detail: CycleDetail = None # type: ignore self._current_cycle_detail: CycleDetail = None # type: ignore
self.reply_timeout_count = 0
self.plan_timeout_count = 0
self.last_read_time = time.time() - 10 self.last_read_time = time.time() - 10
self.focus_energy = 1
self.no_action_consecutive = 0
# 最近三次no_action的新消息兴趣度记录
self.recent_interest_records: deque = deque(maxlen=3)
async def start(self): async def start(self):
"""检查是否需要启动主循环,如果未激活则启动。""" """检查是否需要启动主循环,如果未激活则启动。"""
@ -187,87 +178,14 @@ class HeartFChatting:
f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " # type: ignore f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " # type: ignore
f"选择动作: {action_type}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") f"选择动作: {action_type}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
) )
def _determine_form_type(self) -> None: async def caculate_interest_value(self, recent_messages_list: List["DatabaseMessages"]) -> float:
"""判断使用哪种形式的no_action"""
# 如果连续no_action次数少于3次使用waiting形式
if self.no_action_consecutive <= 3:
self.focus_energy = 1
else:
# 计算最近三次记录的兴趣度总和
total_recent_interest = sum(self.recent_interest_records)
# 计算调整后的阈值
adjusted_threshold = 1 / self.talk_frequency_control.get_current_talk_frequency()
logger.info(
f"{self.log_prefix} 最近三次兴趣度总和: {total_recent_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}"
)
# 如果兴趣度总和小于阈值进入breaking形式
if total_recent_interest < adjusted_threshold:
logger.info(f"{self.log_prefix} 兴趣度不足,进入休息")
self.focus_energy = random.randint(3, 6)
else:
logger.info(f"{self.log_prefix} 兴趣度充足,等待新消息")
self.focus_energy = 1
async def _should_process_messages(self, new_message: List["DatabaseMessages"]) -> tuple[bool, float]:
"""
判断是否应该处理消息
Args:
new_message: 新消息列表
mode: 当前聊天模式
Returns:
bool: 是否应该处理消息
"""
new_message_count = len(new_message)
talk_frequency = self.talk_frequency_control.get_current_talk_frequency()
modified_exit_count_threshold = self.focus_energy * 0.5 / talk_frequency
modified_exit_interest_threshold = 1.5 / talk_frequency
total_interest = 0.0 total_interest = 0.0
for msg in new_message: for msg in recent_messages_list:
interest_value = msg.interest_value interest_value = msg.interest_value
if interest_value is not None and msg.processed_plain_text: if interest_value is not None and msg.processed_plain_text:
total_interest += float(interest_value) total_interest += float(interest_value)
return total_interest / len(recent_messages_list)
if new_message_count >= modified_exit_count_threshold:
self.recent_interest_records.append(total_interest)
logger.info(
f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold:.1f}),结束等待"
)
# logger.info(self.last_read_time)
# logger.info(new_message)
return True, total_interest / new_message_count if new_message_count > 0 else 0.0
# 检查累计兴趣值
if new_message_count > 0:
# 只在兴趣值变化时输出log
if not hasattr(self, "_last_accumulated_interest") or total_interest != self._last_accumulated_interest:
logger.info(
f"{self.log_prefix} 休息中,新消息:{new_message_count}条,累计兴趣值: {total_interest:.2f}, 活跃度: {talk_frequency:.1f}"
)
self._last_accumulated_interest = total_interest
if total_interest >= modified_exit_interest_threshold:
# 记录兴趣度到列表
self.recent_interest_records.append(total_interest)
logger.info(
f"{self.log_prefix} 累计兴趣值达到{total_interest:.2f}(>{modified_exit_interest_threshold:.1f}),结束等待"
)
return True, total_interest / new_message_count if new_message_count > 0 else 0.0
# 每10秒输出一次等待状态
if int(time.time() - self.last_read_time) > 0 and int(time.time() - self.last_read_time) % 15 == 0:
logger.debug(
f"{self.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,累计兴趣{total_interest:.1f},继续等待..."
)
await asyncio.sleep(0.5)
return False, 0.0
async def _loopbody(self): async def _loopbody(self):
recent_messages_list = message_api.get_messages_by_time_in_chat( recent_messages_list = message_api.get_messages_by_time_in_chat(
@ -279,16 +197,13 @@ class HeartFChatting:
filter_mai=True, filter_mai=True,
filter_command=True, filter_command=True,
) )
# 统一的消息处理逻辑
should_process, interest_value = await self._should_process_messages(recent_messages_list) if recent_messages_list:
if should_process:
self.last_read_time = time.time() self.last_read_time = time.time()
await self._observe(interest_value=interest_value) await self._observe(interest_value=await self.caculate_interest_value(recent_messages_list),recent_messages_list=recent_messages_list)
else: else:
# Normal模式消息数量不足等待 # Normal模式消息数量不足等待
await asyncio.sleep(0.5) await asyncio.sleep(0.2)
return True return True
return True return True
@ -342,8 +257,7 @@ class HeartFChatting:
return loop_info, reply_text, cycle_timers return loop_info, reply_text, cycle_timers
async def _observe(self, interest_value: float = 0.0) -> bool: async def _observe(self, interest_value: float = 0.0,recent_messages_list: List["DatabaseMessages"] = []) -> bool:
action_type = "no_action"
reply_text = "" # 初始化reply_text变量避免UnboundLocalError reply_text = "" # 初始化reply_text变量避免UnboundLocalError
# 使用sigmoid函数将interest_value转换为概率 # 使用sigmoid函数将interest_value转换为概率
@ -362,22 +276,28 @@ class HeartFChatting:
normal_mode_probability = ( normal_mode_probability = (
calculate_normal_mode_probability(interest_value) calculate_normal_mode_probability(interest_value)
* 2 * 2
* self.talk_frequency_control.get_current_talk_frequency() * self.frequency_control.get_final_talk_frequency()
) )
#对呼唤名字进行增幅
for msg in recent_messages_list:
if msg.reply_probability_boost is not None and msg.reply_probability_boost > 0.0:
normal_mode_probability += msg.reply_probability_boost
if global_config.chat.mentioned_bot_reply and msg.is_mentioned:
normal_mode_probability += global_config.chat.mentioned_bot_reply
if global_config.chat.at_bot_inevitable_reply and msg.is_at:
normal_mode_probability += global_config.chat.at_bot_inevitable_reply
# 根据概率决定使用哪种模式 # 根据概率决定使用直接回复
interest_triggerd = False
focus_triggerd = False
if random.random() < normal_mode_probability: if random.random() < normal_mode_probability:
mode = ChatMode.NORMAL interest_triggerd = True
logger.info( logger.info(
f"{self.log_prefix} 有兴趣({interest_value:.2f}),在{normal_mode_probability * 100:.0f}%概率下选择回复" f"{self.log_prefix}新消息,在{normal_mode_probability * 100:.0f}%概率下选择回复"
) )
else:
mode = ChatMode.FOCUS
# 创建新的循环信息
cycle_timers, thinking_id = self.start_cycle()
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考")
if s4u_config.enable_s4u: if s4u_config.enable_s4u:
await send_typing() await send_typing()
@ -386,16 +306,21 @@ class HeartFChatting:
await self.expression_learner.trigger_learning_for_chat() await self.expression_learner.trigger_learning_for_chat()
available_actions: Dict[str, ActionInfo] = {} available_actions: Dict[str, ActionInfo] = {}
if random.random() > self.focus_value_control.get_current_focus_value() and mode == ChatMode.FOCUS:
# 如果激活度没有激活并且聊天活跃度低有可能不进行plan相当于不在电脑前不进行认真思考 #如果兴趣度不足以激活
action_to_use_info = [ if not interest_triggerd:
ActionPlannerInfo( #看看专注值够不够
action_type="no_action", if random.random() < self.frequency_control.get_final_focus_value():
reasoning="专注不足", #专注值足够,仍然进入正式思考
action_data={}, focus_triggerd = True #都没触发,路边
)
]
else: # 任意一种触发都行
if interest_triggerd or focus_triggerd:
# 进入正式思考模式
cycle_timers, thinking_id = self.start_cycle()
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考")
# 第一步:动作检查 # 第一步:动作检查
with Timer("动作检查", cycle_timers): with Timer("动作检查", cycle_timers):
try: try:
@ -433,103 +358,93 @@ class HeartFChatting:
): ):
return False return False
with Timer("规划器", cycle_timers): with Timer("规划器", cycle_timers):
# 根据不同触发进入不同plan
if focus_triggerd:
mode = ChatMode.FOCUS
else:
mode = ChatMode.NORMAL
action_to_use_info, _ = await self.action_planner.plan( action_to_use_info, _ = await self.action_planner.plan(
mode=mode, mode=mode,
loop_start_time=self.last_read_time, loop_start_time=self.last_read_time,
available_actions=available_actions, available_actions=available_actions,
) )
# for action in action_to_use_info: # 3. 并行执行所有动作
# print(action.action_type) action_tasks = [
asyncio.create_task(
self._execute_action(action, action_to_use_info, thinking_id, available_actions, cycle_timers)
)
for action in action_to_use_info
]
# 3. 并行执行所有动作 # 并行执行所有任务
action_tasks = [ results = await asyncio.gather(*action_tasks, return_exceptions=True)
asyncio.create_task(
self._execute_action(action, action_to_use_info, thinking_id, available_actions, cycle_timers)
)
for action in action_to_use_info
]
# 并行执行所有任务 # 处理执行结果
results = await asyncio.gather(*action_tasks, return_exceptions=True) reply_loop_info = None
reply_text_from_reply = ""
action_success = False
action_reply_text = ""
action_command = ""
# 处理执行结果 for i, result in enumerate(results):
reply_loop_info = None if isinstance(result, BaseException):
reply_text_from_reply = "" logger.error(f"{self.log_prefix} 动作执行异常: {result}")
action_success = False continue
action_reply_text = ""
action_command = ""
for i, result in enumerate(results): _cur_action = action_to_use_info[i]
if isinstance(result, BaseException): if result["action_type"] != "reply":
logger.error(f"{self.log_prefix} 动作执行异常: {result}") action_success = result["success"]
continue action_reply_text = result["reply_text"]
action_command = result.get("command", "")
elif result["action_type"] == "reply":
if result["success"]:
reply_loop_info = result["loop_info"]
reply_text_from_reply = result["reply_text"]
else:
logger.warning(f"{self.log_prefix} 回复动作执行失败")
_cur_action = action_to_use_info[i] # 构建最终的循环信息
if result["action_type"] != "reply": if reply_loop_info:
action_success = result["success"] # 如果有回复信息使用回复的loop_info作为基础
action_reply_text = result["reply_text"] loop_info = reply_loop_info
action_command = result.get("command", "") # 更新动作执行信息
elif result["action_type"] == "reply": loop_info["loop_action_info"].update(
if result["success"]: {
reply_loop_info = result["loop_info"] "action_taken": action_success,
reply_text_from_reply = result["reply_text"] "command": action_command,
else: "taken_time": time.time(),
logger.warning(f"{self.log_prefix} 回复动作执行失败") }
)
# 构建最终的循环信息 reply_text = reply_text_from_reply
if reply_loop_info: else:
# 如果有回复信息使用回复的loop_info作为基础 # 没有回复信息构建纯动作的loop_info
loop_info = reply_loop_info loop_info = {
# 更新动作执行信息 "loop_plan_info": {
loop_info["loop_action_info"].update( "action_result": action_to_use_info,
{ },
"action_taken": action_success, "loop_action_info": {
"command": action_command, "action_taken": action_success,
"taken_time": time.time(), "reply_text": action_reply_text,
"command": action_command,
"taken_time": time.time(),
},
} }
) reply_text = action_reply_text
reply_text = reply_text_from_reply
else:
# 没有回复信息构建纯动作的loop_info self.end_cycle(loop_info, cycle_timers)
loop_info = { self.print_cycle_info(cycle_timers)
"loop_plan_info": {
"action_result": action_to_use_info,
},
"loop_action_info": {
"action_taken": action_success,
"reply_text": action_reply_text,
"command": action_command,
"taken_time": time.time(),
},
}
reply_text = action_reply_text
if s4u_config.enable_s4u: """S4U内容暂时保留"""
await stop_typing() if s4u_config.enable_s4u:
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text) await stop_typing()
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
"""S4U内容暂时保留"""
self.end_cycle(loop_info, cycle_timers)
self.print_cycle_info(cycle_timers)
# await self.willing_manager.after_generate_reply_handle(message_data.get("message_id", ""))
action_type = action_to_use_info[0].action_type if action_to_use_info else "no_action"
# 管理no_action计数器当执行了非no_action动作时重置计数器
if action_type != "no_action":
# no_action逻辑已集成到heartFC_chat.py中直接重置计数器
self.recent_interest_records.clear()
self.no_action_consecutive = 0
logger.debug(f"{self.log_prefix} 执行了{action_type}动作重置no_action计数器")
return True return True
if action_type == "no_action":
self.no_action_consecutive += 1
self._determine_form_type()
return True
async def _main_chat_loop(self): async def _main_chat_loop(self):
"""主循环,持续进行计划并可能回复消息,直到被外部取消。""" """主循环,持续进行计划并可能回复消息,直到被外部取消。"""
try: try:

View File

@ -32,10 +32,10 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, list[str]]:
Returns: Returns:
Tuple[float, bool, list[str]]: (兴趣度, 是否被提及, 关键词) Tuple[float, bool, list[str]]: (兴趣度, 是否被提及, 关键词)
""" """
if message.is_picid: if message.is_picid or message.is_emoji:
return 0.0, [] return 0.0, []
is_mentioned, _ = is_mentioned_bot_in_message(message) is_mentioned,is_at,reply_probability_boost = is_mentioned_bot_in_message(message)
interested_rate = 0.0 interested_rate = 0.0
with Timer("记忆激活"): with Timer("记忆激活"):
@ -79,17 +79,13 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, list[str]]:
# 确保在范围内 # 确保在范围内
base_interest = min(max(base_interest, 0.01), 0.3) base_interest = min(max(base_interest, 0.01), 0.3)
interested_rate += base_interest
if is_mentioned:
interest_increase_on_mention = 2
interested_rate += interest_increase_on_mention
message.interest_value = base_interest
message.interest_value = interested_rate
message.is_mentioned = is_mentioned message.is_mentioned = is_mentioned
message.is_at = is_at
return interested_rate, keywords message.reply_probability_boost = reply_probability_boost
return base_interest, keywords
class HeartFCMessageReceiver: class HeartFCMessageReceiver:

View File

@ -108,6 +108,8 @@ class MessageRecv(Message):
self.has_picid = False self.has_picid = False
self.is_voice = False self.is_voice = False
self.is_mentioned = None self.is_mentioned = None
self.is_at = False
self.reply_probability_boost = 0.0
self.is_notify = False self.is_notify = False
self.is_command = False self.is_command = False

View File

@ -56,6 +56,8 @@ class MessageStorage:
filtered_display_message = "" filtered_display_message = ""
interest_value = 0 interest_value = 0
is_mentioned = False is_mentioned = False
is_at = False
reply_probability_boost = 0.0
reply_to = message.reply_to reply_to = message.reply_to
priority_mode = "" priority_mode = ""
priority_info = {} priority_info = {}
@ -70,6 +72,8 @@ class MessageStorage:
filtered_display_message = "" filtered_display_message = ""
interest_value = message.interest_value interest_value = message.interest_value
is_mentioned = message.is_mentioned is_mentioned = message.is_mentioned
is_at = message.is_at
reply_probability_boost = message.reply_probability_boost
reply_to = "" reply_to = ""
priority_mode = message.priority_mode priority_mode = message.priority_mode
priority_info = message.priority_info priority_info = message.priority_info
@ -100,6 +104,8 @@ class MessageStorage:
# Flattened chat_info # Flattened chat_info
reply_to=reply_to, reply_to=reply_to,
is_mentioned=is_mentioned, is_mentioned=is_mentioned,
is_at=is_at,
reply_probability_boost=reply_probability_boost,
chat_info_stream_id=chat_info_dict.get("stream_id"), chat_info_stream_id=chat_info_dict.get("stream_id"),
chat_info_platform=chat_info_dict.get("platform"), chat_info_platform=chat_info_dict.get("platform"),
chat_info_user_platform=user_info_from_chat.get("platform"), chat_info_user_platform=user_info_from_chat.get("platform"),

View File

@ -65,7 +65,6 @@ def init_prompt():
动作描述参与聊天回复发送文本进行表达 动作描述参与聊天回复发送文本进行表达
- 你想要闲聊或者随便附和 - 你想要闲聊或者随便附和
- 有人提到了你但是你还没有回应 - 有人提到了你但是你还没有回应
- {mentioned_bonus}
- 如果你刚刚进行了回复不要对同一个话题重复回应 - 如果你刚刚进行了回复不要对同一个话题重复回应
{{ {{
"action": "reply", "action": "reply",
@ -93,7 +92,6 @@ def init_prompt():
现在最新的聊天消息引起了你的兴趣你想要对其中的消息进行回复回复标准如下 现在最新的聊天消息引起了你的兴趣你想要对其中的消息进行回复回复标准如下
- 你想要闲聊或者随便附和 - 你想要闲聊或者随便附和
- 有人提到了你但是你还没有回应 - 有人提到了你但是你还没有回应
- {mentioned_bonus}
- 如果你刚刚进行了回复不要对同一个话题重复回应 - 如果你刚刚进行了回复不要对同一个话题重复回应
你之前的动作记录 你之前的动作记录
@ -465,7 +463,7 @@ class ActionPlanner:
) )
) )
logger.info(f"{self.log_prefix}副规划器返回了{len(action_planner_infos)}个action") logger.debug(f"{self.log_prefix}副规划器返回了{len(action_planner_infos)}个action")
return action_planner_infos return action_planner_infos
async def plan( async def plan(
@ -553,7 +551,7 @@ class ActionPlanner:
for i, (action_name, action_info) in enumerate(action_items): for i, (action_name, action_info) in enumerate(action_items):
sub_planner_lists[i % sub_planner_num].append((action_name, action_info)) sub_planner_lists[i % sub_planner_num].append((action_name, action_info))
logger.info( logger.debug(
f"{self.log_prefix}成功将{sub_planner_actions_num}个actions分配到{sub_planner_num}个子列表中" f"{self.log_prefix}成功将{sub_planner_actions_num}个actions分配到{sub_planner_num}个子列表中"
) )
for i, action_list in enumerate(sub_planner_lists): for i, action_list in enumerate(sub_planner_lists):
@ -585,7 +583,7 @@ class ActionPlanner:
for sub_result in sub_plan_results: for sub_result in sub_plan_results:
all_sub_planner_results.extend(sub_result) all_sub_planner_results.extend(sub_result)
logger.info(f"{self.log_prefix}所有副规划器共返回了{len(all_sub_planner_results)}个action") logger.info(f"{self.log_prefix}小脑决定执行{len(all_sub_planner_results)}个动作")
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) --- # --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
prompt, message_id_list = await self.build_planner_prompt( prompt, message_id_list = await self.build_planner_prompt(
@ -777,12 +775,6 @@ class ActionPlanner:
else: else:
actions_before_now_block = "" actions_before_now_block = ""
mentioned_bonus = ""
if global_config.chat.mentioned_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你"
if global_config.chat.at_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你或者at你"
chat_context_description = "你现在正在一个群聊中" chat_context_description = "你现在正在一个群聊中"
chat_target_name = None chat_target_name = None
if not is_group_chat and chat_target_info: if not is_group_chat and chat_target_info:
@ -838,7 +830,6 @@ class ActionPlanner:
chat_context_description=chat_context_description, chat_context_description=chat_context_description,
chat_content_block=chat_content_block, chat_content_block=chat_content_block,
actions_before_now_block=actions_before_now_block, actions_before_now_block=actions_before_now_block,
mentioned_bonus=mentioned_bonus,
# action_options_text=action_options_block, # action_options_text=action_options_block,
moderation_prompt=moderation_prompt_block, moderation_prompt=moderation_prompt_block,
name_block=name_block, name_block=name_block,
@ -850,7 +841,6 @@ class ActionPlanner:
time_block=time_block, time_block=time_block,
chat_context_description=chat_context_description, chat_context_description=chat_context_description,
chat_content_block=chat_content_block, chat_content_block=chat_content_block,
mentioned_bonus=mentioned_bonus,
moderation_prompt=moderation_prompt_block, moderation_prompt=moderation_prompt_block,
name_block=name_block, name_block=name_block,
actions_before_now_block=actions_before_now_block, actions_before_now_block=actions_before_now_block,

View File

@ -43,15 +43,15 @@ def db_message_to_str(message_dict: dict) -> str:
return result return result
def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]: def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, bool, float]:
"""检查消息是否提到了机器人""" """检查消息是否提到了机器人"""
keywords = [global_config.bot.nickname] keywords = [global_config.bot.nickname] + list(global_config.bot.alias_names)
nicknames = global_config.bot.alias_names
reply_probability = 0.0 reply_probability = 0.0
is_at = False is_at = False
is_mentioned = False is_mentioned = False
if message.is_mentioned is not None:
return bool(message.is_mentioned), message.is_mentioned # 这部分怎么处理啊啊啊啊
#我觉得可以给消息加一个 reply_probability_boost字段
if ( if (
message.message_info.additional_config is not None message.message_info.additional_config is not None
and message.message_info.additional_config.get("is_mentioned") is not None and message.message_info.additional_config.get("is_mentioned") is not None
@ -59,18 +59,15 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
try: try:
reply_probability = float(message.message_info.additional_config.get("is_mentioned")) # type: ignore reply_probability = float(message.message_info.additional_config.get("is_mentioned")) # type: ignore
is_mentioned = True is_mentioned = True
return is_mentioned, reply_probability return is_mentioned, is_at, reply_probability
except Exception as e: except Exception as e:
logger.warning(str(e)) logger.warning(str(e))
logger.warning( logger.warning(
f"消息中包含不合理的设置 is_mentioned: {message.message_info.additional_config.get('is_mentioned')}" f"消息中包含不合理的设置 is_mentioned: {message.message_info.additional_config.get('is_mentioned')}"
) )
if global_config.bot.nickname in message.processed_plain_text: for keyword in keywords:
is_mentioned = True if keyword in message.processed_plain_text:
for alias_name in global_config.bot.alias_names:
if alias_name in message.processed_plain_text:
is_mentioned = True is_mentioned = True
# 判断是否被@ # 判断是否被@
@ -78,10 +75,6 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
is_at = True is_at = True
is_mentioned = True is_mentioned = True
# print(f"message.processed_plain_text: {message.processed_plain_text}")
# print(f"is_mentioned: {is_mentioned}")
# print(f"is_at: {is_at}")
if is_at and global_config.chat.at_bot_inevitable_reply: if is_at and global_config.chat.at_bot_inevitable_reply:
reply_probability = 1.0 reply_probability = 1.0
logger.debug("被@回复概率设置为100%") logger.debug("被@回复概率设置为100%")
@ -104,13 +97,10 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
for keyword in keywords: for keyword in keywords:
if keyword in message_content: if keyword in message_content:
is_mentioned = True is_mentioned = True
for nickname in nicknames: if is_mentioned and global_config.chat.mentioned_bot_reply:
if nickname in message_content:
is_mentioned = True
if is_mentioned and global_config.chat.mentioned_bot_inevitable_reply:
reply_probability = 1.0 reply_probability = 1.0
logger.debug("被提及回复概率设置为100%") logger.debug("被提及回复概率设置为100%")
return is_mentioned, reply_probability return is_mentioned, is_at, reply_probability
async def get_embedding(text, request_type="embedding") -> Optional[List[float]]: async def get_embedding(text, request_type="embedding") -> Optional[List[float]]:

View File

@ -67,6 +67,8 @@ class DatabaseMessages(BaseDataModel):
key_words: Optional[str] = None, key_words: Optional[str] = None,
key_words_lite: Optional[str] = None, key_words_lite: Optional[str] = None,
is_mentioned: Optional[bool] = None, is_mentioned: Optional[bool] = None,
is_at: Optional[bool] = None,
reply_probability_boost: Optional[float] = None,
processed_plain_text: Optional[str] = None, processed_plain_text: Optional[str] = None,
display_message: Optional[str] = None, display_message: Optional[str] = None,
priority_mode: Optional[str] = None, priority_mode: Optional[str] = None,
@ -104,6 +106,9 @@ class DatabaseMessages(BaseDataModel):
self.key_words_lite = key_words_lite self.key_words_lite = key_words_lite
self.is_mentioned = is_mentioned self.is_mentioned = is_mentioned
self.is_at = is_at
self.reply_probability_boost = reply_probability_boost
self.processed_plain_text = processed_plain_text self.processed_plain_text = processed_plain_text
self.display_message = display_message self.display_message = display_message
@ -171,6 +176,8 @@ class DatabaseMessages(BaseDataModel):
"key_words": self.key_words, "key_words": self.key_words,
"key_words_lite": self.key_words_lite, "key_words_lite": self.key_words_lite,
"is_mentioned": self.is_mentioned, "is_mentioned": self.is_mentioned,
"is_at": self.is_at,
"reply_probability_boost": self.reply_probability_boost,
"processed_plain_text": self.processed_plain_text, "processed_plain_text": self.processed_plain_text,
"display_message": self.display_message, "display_message": self.display_message,
"priority_mode": self.priority_mode, "priority_mode": self.priority_mode,

View File

@ -137,7 +137,8 @@ class Messages(BaseModel):
key_words_lite = TextField(null=True) key_words_lite = TextField(null=True)
is_mentioned = BooleanField(null=True) is_mentioned = BooleanField(null=True)
is_at = BooleanField(null=True)
reply_probability_boost = DoubleField(null=True)
# 从 chat_info 扁平化而来的字段 # 从 chat_info 扁平化而来的字段
chat_info_stream_id = TextField() chat_info_stream_id = TextField()
chat_info_platform = TextField() chat_info_platform = TextField()

View File

@ -355,6 +355,7 @@ MODULE_COLORS = {
# 核心模块 # 核心模块
"main": "\033[1;97m", # 亮白色+粗体 (主程序) "main": "\033[1;97m", # 亮白色+粗体 (主程序)
"memory": "\033[38;5;34m", # 天蓝色
"config": "\033[93m", # 亮黄色 "config": "\033[93m", # 亮黄色
"common": "\033[95m", # 亮紫色 "common": "\033[95m", # 亮紫色
@ -366,10 +367,9 @@ MODULE_COLORS = {
"llm_models": "\033[36m", # 青色 "llm_models": "\033[36m", # 青色
"remote": "\033[38;5;242m", # 深灰色,更不显眼 "remote": "\033[38;5;242m", # 深灰色,更不显眼
"planner": "\033[36m", "planner": "\033[36m",
"memory": "\033[38;5;117m", # 天蓝色
"hfc": "\033[38;5;81m", # 稍微暗一些的青色,保持可读
"action_manager": "\033[38;5;208m", # 橙色不与replyer重复
# 关系系统
"relation": "\033[38;5;139m", # 柔和的紫色,不刺眼 "relation": "\033[38;5;139m", # 柔和的紫色,不刺眼
# 聊天相关模块 # 聊天相关模块
"normal_chat": "\033[38;5;81m", # 亮蓝绿色 "normal_chat": "\033[38;5;81m", # 亮蓝绿色

View File

@ -56,7 +56,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template")
# 考虑到实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 考虑到实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码
# 对该字段的更新请严格参照语义化版本规范https://semver.org/lang/zh-CN/ # 对该字段的更新请严格参照语义化版本规范https://semver.org/lang/zh-CN/
MMC_VERSION = "0.10.2-snapshot.1" MMC_VERSION = "0.10.2-snapshot.2"
def get_key_comment(toml_table, key): def get_key_comment(toml_table, key):

View File

@ -71,14 +71,14 @@ class ChatConfig(ConfigBase):
interest_rate_mode: Literal["fast", "accurate"] = "fast" interest_rate_mode: Literal["fast", "accurate"] = "fast"
"""兴趣值计算模式fast为快速计算accurate为精确计算""" """兴趣值计算模式fast为快速计算accurate为精确计算"""
mentioned_bot_inevitable_reply: bool = False mentioned_bot_reply: float = 1
"""提及 bot 必然回复""" """提及 bot 必然回复1为100%回复0为不额外增幅"""
planner_size: float = 1.5 planner_size: float = 1.5
"""副规划器大小越小麦麦的动作执行能力越精细但是消耗更多token调大可以缓解429类错误""" """副规划器大小越小麦麦的动作执行能力越精细但是消耗更多token调大可以缓解429类错误"""
at_bot_inevitable_reply: bool = False at_bot_inevitable_reply: float = 1
"""@bot 必然回复""" """@bot 必然回复1为100%回复0为不额外增幅"""
talk_frequency: float = 0.5 talk_frequency: float = 0.5
"""回复频率阈值""" """回复频率阈值"""

View File

@ -1,27 +1,26 @@
from src.common.logger import get_logger from src.common.logger import get_logger
from src.chat.frequency_control.focus_value_control import focus_value_control from src.chat.frequency_control.frequency_control import frequency_control_manager
from src.chat.frequency_control.talk_frequency_control import talk_frequency_control
logger = get_logger("frequency_api") logger = get_logger("frequency_api")
def get_current_focus_value(chat_id: str) -> float: def get_current_focus_value(chat_id: str) -> float:
return focus_value_control.get_focus_value_control(chat_id).get_current_focus_value() return frequency_control_manager.get_or_create_frequency_control(chat_id).get_final_focus_value()
def get_current_talk_frequency(chat_id: str) -> float: def get_current_talk_frequency(chat_id: str) -> float:
return talk_frequency_control.get_talk_frequency_control(chat_id).get_current_talk_frequency() return frequency_control_manager.get_or_create_frequency_control(chat_id).get_final_talk_frequency()
def set_focus_value_adjust(chat_id: str, focus_value_adjust: float) -> None: def set_focus_value_adjust(chat_id: str, focus_value_adjust: float) -> None:
focus_value_control.get_focus_value_control(chat_id).focus_value_adjust = focus_value_adjust frequency_control_manager.get_or_create_frequency_control(chat_id).focus_value_external_adjust = focus_value_adjust
def set_talk_frequency_adjust(chat_id: str, talk_frequency_adjust: float) -> None: def set_talk_frequency_adjust(chat_id: str, talk_frequency_adjust: float) -> None:
talk_frequency_control.get_talk_frequency_control(chat_id).talk_frequency_adjust = talk_frequency_adjust frequency_control_manager.get_or_create_frequency_control(chat_id).talk_frequency_external_adjust = talk_frequency_adjust
def get_focus_value_adjust(chat_id: str) -> float: def get_focus_value_adjust(chat_id: str) -> float:
return focus_value_control.get_focus_value_control(chat_id).focus_value_adjust return frequency_control_manager.get_or_create_frequency_control(chat_id).focus_value_external_adjust
def get_talk_frequency_adjust(chat_id: str) -> float: def get_talk_frequency_adjust(chat_id: str) -> float:
return talk_frequency_control.get_talk_frequency_control(chat_id).talk_frequency_adjust return frequency_control_manager.get_or_create_frequency_control(chat_id).talk_frequency_external_adjust

View File

@ -1,5 +1,5 @@
[inner] [inner]
version = "6.7.2" version = "6.8.0"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请递增version的值 #如果你想要修改配置文件请递增version的值
@ -59,17 +59,17 @@ expression_groups = [
[chat] #麦麦的聊天设置 [chat] #麦麦的聊天设置
talk_frequency = 0.5 talk_frequency = 0.5
# 麦麦活跃度,越高,麦麦回复越多范围0-1 # 麦麦活跃度,越高,麦麦越容易回复范围0-1
focus_value = 0.5 focus_value = 0.5
# 麦麦的专注度越高越容易持续连续对话可能消耗更多token, 范围0-1 # 麦麦的专注度越高越容易持续连续对话可能消耗更多token, 范围0-1
mentioned_bot_reply = 1 # 提及时,回复概率增幅1为100%回复0为不额外增幅
at_bot_inevitable_reply = 1 # at时,回复概率增幅1为100%回复0为不额外增幅
max_context_size = 20 # 上下文长度 max_context_size = 20 # 上下文长度
planner_size = 2.5 # 副规划器大小越小麦麦的动作执行能力越精细但是消耗更多token调大可以缓解429类错误 planner_size = 2.5 # 副规划器大小越小麦麦的动作执行能力越精细但是消耗更多token调大可以缓解429类错误
mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复
focus_value_adjust = [ focus_value_adjust = [
["", "8:00,1", "12:00,0.8", "18:00,1", "01:00,0.3"], ["", "8:00,1", "12:00,0.8", "18:00,1", "01:00,0.3"],
["qq:114514:group", "12:20,0.6", "16:10,0.5", "20:10,0.8", "00:10,0.3"], ["qq:114514:group", "12:20,0.6", "16:10,0.5", "20:10,0.8", "00:10,0.3"],