Merge branch 'dev' into plugin

This commit is contained in:
UnCLAS-Prommer
2025-07-13 01:28:48 +08:00
46 changed files with 1350 additions and 2433 deletions

View File

@@ -27,8 +27,8 @@ services:
# image: infinitycat/maibot:dev # image: infinitycat/maibot:dev
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
# - EULA_AGREE=bda99dca873f5d8044e9987eac417e01 # 同意EULA # - EULA_AGREE=99f08e0cab0190de853cb6af7d64d4de # 同意EULA
# - PRIVACY_AGREE=42dddb3cbe2b784b45a2781407b298a1 # 同意EULA # - PRIVACY_AGREE=9943b855e72199d0f5016ea39052f1b6 # 同意EULA
# ports: # ports:
# - "8000:8000" # - "8000:8000"
volumes: volumes:

View File

@@ -10,8 +10,7 @@
"license": "GPL-v3.0-or-later", "license": "GPL-v3.0-or-later",
"host_application": { "host_application": {
"min_version": "0.8.0", "min_version": "0.8.0"
"max_version": "0.8.0"
}, },
"homepage_url": "https://github.com/MaiM-with-u/maibot", "homepage_url": "https://github.com/MaiM-with-u/maibot",
"repository_url": "https://github.com/MaiM-with-u/maibot", "repository_url": "https://github.com/MaiM-with-u/maibot",

View File

@@ -1,62 +0,0 @@
import asyncio
from src.common.logger import get_logger
logger = get_logger("MockAudio")
class MockAudioPlayer:
"""
一个模拟的音频播放器,它会根据音频数据的"长度"来模拟播放时间。
"""
def __init__(self, audio_data: bytes):
self._audio_data = audio_data
# 模拟音频时长:假设每 1024 字节代表 0.5 秒的音频
self._duration = (len(audio_data) / 1024.0) * 0.5
async def play(self):
"""模拟播放音频。该过程可以被中断。"""
if self._duration <= 0:
return
logger.info(f"开始播放模拟音频,预计时长: {self._duration:.2f} 秒...")
try:
await asyncio.sleep(self._duration)
logger.info("模拟音频播放完毕。")
except asyncio.CancelledError:
logger.info("音频播放被中断。")
raise # 重新抛出异常,以便上层逻辑可以捕获它
class MockAudioGenerator:
"""
一个模拟的文本到语音TTS生成器。
"""
def __init__(self):
# 模拟生成速度:每秒生成的字符数
self.chars_per_second = 25.0
async def generate(self, text: str) -> bytes:
"""
模拟从文本生成音频数据。该过程可以被中断。
Args:
text: 需要转换为音频的文本。
Returns:
模拟的音频数据bytes
"""
if not text:
return b""
generation_time = len(text) / self.chars_per_second
logger.info(f"模拟生成音频... 文本长度: {len(text)}, 预计耗时: {generation_time:.2f} 秒...")
try:
await asyncio.sleep(generation_time)
# 生成虚拟的音频数据,其长度与文本长度成正比
mock_audio_data = b"\x01\x02\x03" * (len(text) * 40)
logger.info(f"模拟音频生成完毕,数据大小: {len(mock_audio_data) / 1024:.2f} KB。")
return mock_audio_data
except asyncio.CancelledError:
logger.info("音频生成被中断。")
raise # 重新抛出异常

View File

@@ -5,11 +5,9 @@ MaiBot模块系统
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.emoji_system.emoji_manager import get_emoji_manager
from src.chat.normal_chat.willing.willing_manager import get_willing_manager
# 导出主要组件供外部使用 # 导出主要组件供外部使用
__all__ = [ __all__ = [
"get_chat_manager", "get_chat_manager",
"get_emoji_manager", "get_emoji_manager",
"get_willing_manager",
] ]

View File

@@ -1,91 +0,0 @@
# 定义了来自外部世界的信息
# 外部世界可以是某个聊天 不同平台的聊天 也可以是任意媒体
from datetime import datetime
from typing import List
from src.common.logger import get_logger
from src.chat.focus_chat.hfc_utils import CycleDetail
logger = get_logger("loop_info")
# 所有观察的基类
class FocusLoopInfo:
def __init__(self, observe_id):
self.observe_id = observe_id
self.last_observe_time = datetime.now().timestamp() # 初始化为当前时间
self.history_loop: List[CycleDetail] = []
def add_loop_info(self, loop_info: CycleDetail):
self.history_loop.append(loop_info)
async def observe(self):
recent_active_cycles: List[CycleDetail] = []
for cycle in reversed(self.history_loop):
# 只关心实际执行了动作的循环
# action_taken = cycle.loop_action_info["action_taken"]
# if action_taken:
recent_active_cycles.append(cycle)
if len(recent_active_cycles) == 5:
break
cycle_info_block = ""
action_detailed_str = ""
consecutive_text_replies = 0
responses_for_prompt = []
cycle_last_reason = ""
# 检查这最近的活动循环中有多少是连续的文本回复 (从最近的开始看)
for cycle in recent_active_cycles:
action_result = cycle.loop_plan_info.get("action_result", {})
action_type = action_result.get("action_type", "unknown")
action_reasoning = action_result.get("reasoning", "未提供理由")
is_taken = cycle.loop_action_info.get("action_taken", False)
action_taken_time = cycle.loop_action_info.get("taken_time", 0)
action_taken_time_str = (
datetime.fromtimestamp(action_taken_time).strftime("%H:%M:%S") if action_taken_time > 0 else "未知时间"
)
if action_reasoning != cycle_last_reason:
cycle_last_reason = action_reasoning
action_reasoning_str = f"你选择这个action的原因是:{action_reasoning}"
else:
action_reasoning_str = ""
if action_type == "reply":
consecutive_text_replies += 1
response_text = cycle.loop_action_info.get("reply_text", "")
responses_for_prompt.append(response_text)
if is_taken:
action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}')。{action_reasoning_str}\n"
else:
action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}'),但是动作失败了。{action_reasoning_str}\n"
elif action_type == "no_reply":
pass
else:
if is_taken:
action_detailed_str += (
f"{action_taken_time_str}时,你选择执行了(action:{action_type}){action_reasoning_str}\n"
)
else:
action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),但是动作失败了。{action_reasoning_str}\n"
if action_detailed_str:
cycle_info_block = f"\n你最近做的事:\n{action_detailed_str}\n"
else:
cycle_info_block = "\n"
# 获取history_loop中最新添加的
if self.history_loop:
last_loop = self.history_loop[0]
start_time = last_loop.start_time
end_time = last_loop.end_time
if start_time is not None and end_time is not None:
time_diff = int(end_time - start_time)
if time_diff > 60:
cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{int(time_diff / 60)}分钟\n"
else:
cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{time_diff}\n"
else:
cycle_info_block += "你还没看过消息\n"

View File

@@ -1,5 +1,4 @@
import asyncio import asyncio
import contextlib
import time import time
import traceback import traceback
from collections import deque from collections import deque
@@ -14,11 +13,44 @@ from src.chat.utils.timer_calculator import Timer
from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.planner import ActionPlanner
from src.chat.planner_actions.action_modifier import ActionModifier 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.focus_chat.focus_loop_info import FocusLoopInfo
from src.chat.focus_chat.hfc_performance_logger import HFCPerformanceLogger
from src.chat.focus_chat.hfc_utils import CycleDetail from src.chat.focus_chat.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.plugin_system.base.component_types import ChatMode from src.plugin_system.base.component_types import ChatMode
import random
from src.chat.focus_chat.hfc_utils import get_recent_message_stats
from src.person_info.person_info import get_person_info_manager
from src.plugin_system.apis import generator_api, send_api, message_api
from src.chat.willing.willing_manager import get_willing_manager
from .priority_manager import PriorityManager
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat
ERROR_LOOP_INFO = {
"loop_plan_info": {
"action_result": {
"action_type": "error",
"action_data": {},
"reasoning": "循环处理失败",
},
},
"loop_action_info": {
"action_taken": False,
"reply_text": "",
"command": "",
"taken_time": time.time(),
},
}
NO_ACTION = {
"action_result": {
"action_type": "no_action",
"action_data": {},
"reasoning": "规划器初始化默认",
"is_parallel": True,
},
"chat_context": "",
"action_prompt": "",
}
install(extra_lines=3) install(extra_lines=3)
@@ -37,7 +69,6 @@ class HeartFChatting:
def __init__( def __init__(
self, self,
chat_id: str, chat_id: str,
on_stop_focus_chat: Optional[Callable[[], Awaitable[None]]] = None,
): ):
""" """
HeartFChatting 初始化函数 HeartFChatting 初始化函数
@@ -56,6 +87,8 @@ class HeartFChatting:
self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id) self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id)
self.loop_mode = "normal"
# 新增:消息计数器和疲惫阈值 # 新增:消息计数器和疲惫阈值
self._message_count = 0 # 发送的消息计数 self._message_count = 0 # 发送的消息计数
# 基于exit_focus_threshold动态计算疲惫阈值 # 基于exit_focus_threshold动态计算疲惫阈值
@@ -63,73 +96,61 @@ class HeartFChatting:
self._message_threshold = max(10, int(30 * global_config.chat.exit_focus_threshold)) self._message_threshold = max(10, int(30 * global_config.chat.exit_focus_threshold))
self._fatigue_triggered = False # 是否已触发疲惫退出 self._fatigue_triggered = False # 是否已触发疲惫退出
self.loop_info: FocusLoopInfo = FocusLoopInfo(observe_id=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)
self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id) self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id)
self._processing_lock = asyncio.Lock()
# 循环控制内部状态 # 循环控制内部状态
self._loop_active: bool = False # 循环是否正在运行 self.running: bool = False
self._loop_task: Optional[asyncio.Task] = None # 主循环任务 self._loop_task: Optional[asyncio.Task] = None # 主循环任务
# 添加循环信息管理相关的属性 # 添加循环信息管理相关的属性
self.history_loop: List[CycleDetail] = []
self._cycle_counter = 0 self._cycle_counter = 0
self._cycle_history: Deque[CycleDetail] = deque(maxlen=10) # 保留最近10个循环的信息
self._current_cycle_detail: Optional[CycleDetail] = None self._current_cycle_detail: Optional[CycleDetail] = None
self._shutting_down: bool = False # 关闭标志位
# 存储回调函数
self.on_stop_focus_chat = on_stop_focus_chat
self.reply_timeout_count = 0 self.reply_timeout_count = 0
self.plan_timeout_count = 0 self.plan_timeout_count = 0
# 初始化性能记录器 self.last_read_time = time.time() - 1
# 如果没有指定版本号,则使用全局版本管理器的版本号
self.performance_logger = HFCPerformanceLogger(chat_id) self.willing_amplifier = 1
self.willing_manager = get_willing_manager()
self.reply_mode = self.chat_stream.context.get_priority_mode()
if self.reply_mode == "priority":
self.priority_manager = PriorityManager(
normal_queue_max_size=5,
)
self.loop_mode = "priority"
else:
self.priority_manager = None
logger.info( logger.info(
f"{self.log_prefix} HeartFChatting 初始化完成,消息疲惫阈值: {self._message_threshold}基于exit_focus_threshold={global_config.chat.exit_focus_threshold}计算仅在auto模式下生效" f"{self.log_prefix} HeartFChatting 初始化完成,消息疲惫阈值: {self._message_threshold}基于exit_focus_threshold={global_config.chat.exit_focus_threshold}计算仅在auto模式下生效"
) )
self.energy_value = 100
async def start(self): async def start(self):
"""检查是否需要启动主循环,如果未激活则启动。""" """检查是否需要启动主循环,如果未激活则启动。"""
# 如果循环已经激活,直接返回 # 如果循环已经激活,直接返回
if self._loop_active: if self.running:
logger.debug(f"{self.log_prefix} HeartFChatting 已激活,无需重复启动") logger.debug(f"{self.log_prefix} HeartFChatting 已激活,无需重复启动")
return return
try: try:
# 重置消息计数器开始新的focus会话
self.reset_message_count()
# 标记为活动状态,防止重复启动 # 标记为活动状态,防止重复启动
self._loop_active = True self.running = True
# 检查是否已有任务在运行(理论上不应该,因为 _loop_active=False self._loop_task = asyncio.create_task(self._main_chat_loop())
if self._loop_task and not self._loop_task.done():
logger.warning(f"{self.log_prefix} 发现之前的循环任务仍在运行(不符合预期)。取消旧任务。")
self._loop_task.cancel()
try:
# 等待旧任务确实被取消
await asyncio.wait_for(self._loop_task, timeout=5.0)
except Exception as e:
logger.warning(f"{self.log_prefix} 等待旧任务取消时出错: {e}")
self._loop_task = None # 清理旧任务引用
logger.debug(f"{self.log_prefix} 创建新的 HeartFChatting 主循环任务")
self._loop_task = asyncio.create_task(self._run_focus_chat())
self._loop_task.add_done_callback(self._handle_loop_completion) self._loop_task.add_done_callback(self._handle_loop_completion)
logger.debug(f"{self.log_prefix} HeartFChatting 启动完成") logger.info(f"{self.log_prefix} HeartFChatting 启动完成")
except Exception as e: except Exception as e:
# 启动失败时重置状态 # 启动失败时重置状态
self._loop_active = False self.running = False
self._loop_task = None self._loop_task = None
logger.error(f"{self.log_prefix} HeartFChatting 启动失败: {e}") logger.error(f"{self.log_prefix} HeartFChatting 启动失败: {e}")
raise raise
@@ -143,102 +164,22 @@ class HeartFChatting:
else: else:
logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天 (外部停止)") logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天 (外部停止)")
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天(任务取消)") logger.info(f"{self.log_prefix} HeartFChatting: 结束了聊天")
finally:
self._loop_active = False
self._loop_task = None
if self._processing_lock.locked():
logger.warning(f"{self.log_prefix} HeartFChatting: 处理锁在循环结束时仍被锁定,强制释放。")
self._processing_lock.release()
async def _run_focus_chat(self): def start_cycle(self):
"""主循环,持续进行计划并可能回复消息,直到被外部取消。"""
try:
while True: # 主循环
logger.debug(f"{self.log_prefix} 开始第{self._cycle_counter}次循环")
# 检查关闭标志
if self._shutting_down:
logger.info(f"{self.log_prefix} 检测到关闭标志,退出 Focus Chat 循环。")
break
# 创建新的循环信息
self._cycle_counter += 1 self._cycle_counter += 1
self._current_cycle_detail = CycleDetail(self._cycle_counter) self._current_cycle_detail = CycleDetail(self._cycle_counter)
self._current_cycle_detail.prefix = self.log_prefix self._current_cycle_detail.thinking_id = "tid" + str(round(time.time(), 2))
# 初始化周期状态
cycle_timers = {} cycle_timers = {}
return cycle_timers, self._current_cycle_detail.thinking_id
# 执行规划和处理阶段 def end_cycle(self, loop_info, cycle_timers):
try:
async with self._get_cycle_context():
thinking_id = f"tid{str(round(time.time(), 2))}"
self._current_cycle_detail.set_thinking_id(thinking_id)
# 使用异步上下文管理器处理消息
try:
async with global_prompt_manager.async_message_scope(
self.chat_stream.context.get_template_name()
):
# 在上下文内部检查关闭状态
if self._shutting_down:
logger.info(f"{self.log_prefix} 在处理上下文中检测到关闭信号,退出")
break
logger.debug(f"模板 {self.chat_stream.context.get_template_name()}")
loop_info = await self._observe_process_plan_action_loop(cycle_timers, thinking_id)
if loop_info["loop_action_info"]["command"] == "stop_focus_chat":
logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天")
# 如果设置了回调函数,则调用它
if self.on_stop_focus_chat:
try:
await self.on_stop_focus_chat()
logger.info(f"{self.log_prefix} 成功调用回调函数处理停止专注聊天")
except Exception as e:
logger.error(f"{self.log_prefix} 调用停止专注聊天回调函数时出错: {e}")
logger.error(traceback.format_exc())
break
except asyncio.CancelledError:
logger.info(f"{self.log_prefix} 处理上下文时任务被取消")
break
except Exception as e:
logger.error(f"{self.log_prefix} 处理上下文时出错: {e}")
# 为当前循环设置错误状态,防止后续重复报错
error_loop_info = {
"loop_plan_info": {
"action_result": {
"action_type": "error",
"action_data": {},
},
},
"loop_action_info": {
"action_taken": False,
"reply_text": "",
"command": "",
"taken_time": time.time(),
},
}
self._current_cycle_detail.set_loop_info(error_loop_info)
self._current_cycle_detail.complete_cycle()
# 上下文处理失败,跳过当前循环
await asyncio.sleep(1)
continue
self._current_cycle_detail.set_loop_info(loop_info) self._current_cycle_detail.set_loop_info(loop_info)
self.history_loop.append(self._current_cycle_detail)
self.loop_info.add_loop_info(self._current_cycle_detail)
self._current_cycle_detail.timers = cycle_timers self._current_cycle_detail.timers = cycle_timers
self._current_cycle_detail.end_time = time.time()
# 完成当前循环并保存历史 def print_cycle_info(self, cycle_timers):
self._current_cycle_detail.complete_cycle()
self._cycle_history.append(self._current_cycle_detail)
# 记录循环信息和计时器结果 # 记录循环信息和计时器结果
timer_strings = [] timer_strings = []
for name, elapsed in cycle_timers.items(): for name, elapsed in cycle_timers.items():
@@ -252,158 +193,175 @@ class HeartFChatting:
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
) )
# 记录性能数据 async def _loopbody(self):
try: if self.loop_mode == "focus":
action_result = self._current_cycle_detail.loop_plan_info.get("action_result", {}) self.energy_value -= 5 * (1 / global_config.chat.exit_focus_threshold)
cycle_performance_data = { if self.energy_value <= 0:
"cycle_id": self._current_cycle_detail.cycle_id, self.loop_mode = "normal"
"action_type": action_result.get("action_type", "unknown"), return True
"total_time": self._current_cycle_detail.end_time - self._current_cycle_detail.start_time, # type: ignore
"step_times": cycle_timers.copy(),
"reasoning": action_result.get("reasoning", ""),
"success": self._current_cycle_detail.loop_action_info.get("action_taken", False),
}
self.performance_logger.record_cycle(cycle_performance_data)
except Exception as perf_e:
logger.warning(f"{self.log_prefix} 记录性能数据失败: {perf_e}")
await asyncio.sleep(global_config.focus_chat.think_interval) return await self._observe()
elif self.loop_mode == "normal":
new_messages_data = get_raw_msg_by_timestamp_with_chat(
chat_id=self.stream_id,
timestamp_start=self.last_read_time,
timestamp_end=time.time(),
limit=10,
limit_mode="earliest",
fliter_bot=True,
)
except asyncio.CancelledError: if len(new_messages_data) > 4 * global_config.chat.auto_focus_threshold:
logger.info(f"{self.log_prefix} 循环处理时任务被取消") self.loop_mode = "focus"
break self.energy_value = 100
except Exception as e: return True
logger.error(f"{self.log_prefix} 循环处理时出错: {e}")
logger.error(traceback.format_exc())
# 如果_current_cycle_detail存在但未完成为其设置错误状态 if new_messages_data:
if self._current_cycle_detail and not hasattr(self._current_cycle_detail, "end_time"): earliest_messages_data = new_messages_data[0]
error_loop_info = { self.last_read_time = earliest_messages_data.get("time")
"loop_plan_info": {
"action_result": {
"action_type": "error",
"action_data": {},
"reasoning": f"循环处理失败: {e}",
},
},
"loop_action_info": {
"action_taken": False,
"reply_text": "",
"command": "",
"taken_time": time.time(),
},
}
try:
self._current_cycle_detail.set_loop_info(error_loop_info)
self._current_cycle_detail.complete_cycle()
except Exception as inner_e:
logger.error(f"{self.log_prefix} 设置错误状态时出错: {inner_e}")
await asyncio.sleep(1) # 出错后等待一秒再继续 await self.normal_response(earliest_messages_data)
return True
except asyncio.CancelledError: await asyncio.sleep(1)
# 设置了关闭标志位后被取消是正常流程
if not self._shutting_down:
logger.warning(f"{self.log_prefix} 麦麦Focus聊天模式意外被取消")
else:
logger.info(f"{self.log_prefix} 麦麦已离开Focus聊天模式")
except Exception as e:
logger.error(f"{self.log_prefix} 麦麦Focus聊天模式意外错误: {e}")
print(traceback.format_exc())
@contextlib.asynccontextmanager return True
async def _get_cycle_context(self):
"""
循环周期的上下文管理器
用于确保资源的正确获取和释放: async def build_reply_to_str(self, message_data: dict):
1. 获取处理锁 person_info_manager = get_person_info_manager()
2. 执行操作 person_id = person_info_manager.get_person_id(
3. 释放锁 message_data.get("chat_info_platform"), message_data.get("user_id")
""" )
acquired = False person_name = await person_info_manager.get_value(person_id, "person_name")
try: reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}"
await self._processing_lock.acquire() return reply_to_str
acquired = True
yield acquired
finally:
if acquired and self._processing_lock.locked():
self._processing_lock.release()
async def _observe_process_plan_action_loop(self, cycle_timers: dict, thinking_id: str) -> dict: async def _observe(self, message_data: dict = None):
try: # 创建新的循环信息
cycle_timers, thinking_id = self.start_cycle()
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]")
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()
await self.loop_info.observe() # await self.loop_info.observe()
await self.relationship_builder.build_relation() await self.relationship_builder.build_relation()
# 顺序执行调整动作和处理器阶段
# 第一步:动作修改 # 第一步:动作修改
with Timer("动作修改", cycle_timers): with Timer("动作修改", cycle_timers):
try: try:
# 调用完整的动作修改流程 await self.action_modifier.modify_actions()
await self.action_modifier.modify_actions( available_actions = self.action_manager.get_using_actions()
loop_info=self.loop_info,
mode=ChatMode.FOCUS,
)
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 动作修改失败: {e}") logger.error(f"{self.log_prefix} 动作修改失败: {e}")
# 继续执行,不中断流程
# 如果normal开始一个回复生成进程先准备好回复其实是和planer同时进行的
if self.loop_mode == "normal":
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))
with Timer("规划器", cycle_timers): with Timer("规划器", cycle_timers):
plan_result = await self.action_planner.plan() plan_result = await self.action_planner.plan(mode=self.loop_mode)
loop_plan_info = { action_result = plan_result.get("action_result", {})
"action_result": plan_result.get("action_result", {}), action_type, action_data, reasoning, is_parallel = (
} action_result.get("action_type", "error"),
action_result.get("action_data", {}),
action_type, action_data, reasoning = ( action_result.get("reasoning", "未提供理由"),
plan_result.get("action_result", {}).get("action_type", "error"), action_result.get("is_parallel", True),
plan_result.get("action_result", {}).get("action_data", {}),
plan_result.get("action_result", {}).get("reasoning", "未提供理由"),
) )
action_data["loop_start_time"] = loop_start_time action_data["loop_start_time"] = loop_start_time
if action_type == "reply": if self.loop_mode == "normal":
action_str = "回复" if action_type == "no_action":
elif action_type == "no_reply": logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复")
action_str = "不回复" elif is_parallel:
logger.info(
f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作"
)
else: else:
action_str = action_type logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定执行{action_type}动作")
logger.debug(f"{self.log_prefix} 麦麦想要:'{action_str}',理由是:{reasoning}") if action_type == "no_action":
# 等待回复生成完毕
gather_timeout = global_config.chat.thinking_timeout
try:
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
except asyncio.TimeoutError:
response_set = None
if response_set:
content = " ".join([item[1] for item in response_set if item[0] == "text"])
# 模型炸了,没有回复内容生成
if not response_set or (action_type not in ["no_action"] and not is_parallel):
if not response_set:
logger.warning(f"[{self.log_prefix}] 模型未生成回复内容")
elif action_type not in ["no_action"] and not is_parallel:
logger.info(
f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复"
)
return False
logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}")
# 发送回复 (不再需要传入 chat)
await self._send_response(response_set, reply_to_str, loop_start_time)
return True
else:
# 动作执行计时 # 动作执行计时
with Timer("动作执行", cycle_timers): with Timer("动作执行", cycle_timers):
success, reply_text, command = await self._handle_action( success, reply_text, command = await self._handle_action(
action_type, reasoning, action_data, cycle_timers, thinking_id action_type, reasoning, action_data, cycle_timers, thinking_id
) )
loop_action_info = { loop_info = {
"loop_plan_info": {
"action_result": plan_result.get("action_result", {}),
},
"loop_action_info": {
"action_taken": success, "action_taken": success,
"reply_text": reply_text, "reply_text": reply_text,
"command": command, "command": command,
"taken_time": time.time(), "taken_time": time.time(),
}
loop_info = {
"loop_plan_info": loop_plan_info,
"loop_action_info": loop_action_info,
}
return loop_info
except Exception as e:
logger.error(f"{self.log_prefix} FOCUS聊天处理失败: {e}")
logger.error(traceback.format_exc())
return {
"loop_plan_info": {
"action_result": {"action_type": "error", "action_data": {}, "reasoning": f"处理失败: {e}"},
}, },
"loop_action_info": {"action_taken": False, "reply_text": "", "command": "", "taken_time": time.time()},
} }
if loop_info["loop_action_info"]["command"] == "stop_focus_chat":
logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天")
return False
# 停止该聊天模式的循环
self.end_cycle(loop_info, cycle_timers)
self.print_cycle_info(cycle_timers)
if self.loop_mode == "normal":
await self.willing_manager.after_generate_reply_handle(message_data.get("message_id"))
return True
async def _main_chat_loop(self):
"""主循环,持续进行计划并可能回复消息,直到被外部取消。"""
try:
while self.running: # 主循环
success = await self._loopbody()
await asyncio.sleep(0.1)
if not success:
break
logger.info(f"{self.log_prefix} 麦麦已强制离开聊天")
except asyncio.CancelledError:
# 设置了关闭标志位后被取消是正常流程
logger.info(f"{self.log_prefix} 麦麦已关闭聊天")
except Exception:
logger.error(f"{self.log_prefix} 麦麦聊天意外错误")
print(traceback.format_exc())
# 理论上不能到这里
logger.error(f"{self.log_prefix} 麦麦聊天意外错误,结束了聊天循环")
async def _handle_action( async def _handle_action(
self, self,
action: str, action: str,
@@ -436,7 +394,6 @@ class HeartFChatting:
thinking_id=thinking_id, thinking_id=thinking_id,
chat_stream=self.chat_stream, chat_stream=self.chat_stream,
log_prefix=self.log_prefix, log_prefix=self.log_prefix,
shutting_down=self._shutting_down,
) )
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 创建动作处理器时出错: {e}") logger.error(f"{self.log_prefix} 创建动作处理器时出错: {e}")
@@ -452,32 +409,7 @@ class HeartFChatting:
success, reply_text = result success, reply_text = result
command = "" command = ""
# 检查action_data中是否有系统命令优先使用系统命令 if reply_text == "timeout":
if "_system_command" in action_data:
command = action_data["_system_command"]
logger.debug(f"{self.log_prefix} 从action_data中获取系统命令: {command}")
# 新增:消息计数和疲惫检查
if action == "reply" and success:
self._message_count += 1
current_threshold = self._get_current_fatigue_threshold()
logger.info(
f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold}"
)
# 检查是否达到疲惫阈值只有在auto模式下才会自动退出
if (
global_config.chat.chat_mode == "auto"
and self._message_count >= current_threshold
and not self._fatigue_triggered
):
self._fatigue_triggered = True
logger.info(
f"{self.log_prefix} [auto模式] 已发送 {self._message_count} 条消息,达到疲惫阈值 {current_threshold},麦麦感到疲惫了,准备退出专注聊天模式"
)
# 设置系统命令,在下次循环检查时触发退出
command = "stop_focus_chat"
elif reply_text == "timeout":
self.reply_timeout_count += 1 self.reply_timeout_count += 1
if self.reply_timeout_count > 5: if self.reply_timeout_count > 5:
logger.warning( logger.warning(
@@ -493,38 +425,10 @@ class HeartFChatting:
traceback.print_exc() traceback.print_exc()
return False, "", "" return False, "", ""
def _get_current_fatigue_threshold(self) -> int:
"""动态获取当前的疲惫阈值基于exit_focus_threshold配置
Returns:
int: 当前的疲惫阈值
"""
return max(10, int(30 / global_config.chat.exit_focus_threshold))
def get_message_count_info(self) -> dict:
"""获取消息计数信息
Returns:
dict: 包含消息计数信息的字典
"""
current_threshold = self._get_current_fatigue_threshold()
return {
"current_count": self._message_count,
"threshold": current_threshold,
"fatigue_triggered": self._fatigue_triggered,
"remaining": max(0, current_threshold - self._message_count),
}
def reset_message_count(self):
"""重置消息计数器用于重新启动focus模式时"""
self._message_count = 0
self._fatigue_triggered = False
logger.info(f"{self.log_prefix} 消息计数器已重置")
async def shutdown(self): async def shutdown(self):
"""优雅关闭HeartFChatting实例取消活动循环任务""" """优雅关闭HeartFChatting实例取消活动循环任务"""
logger.info(f"{self.log_prefix} 正在关闭HeartFChatting...") logger.info(f"{self.log_prefix} 正在关闭HeartFChatting...")
self._shutting_down = True # <-- 在开始关闭时设置标志位 self.running = False # <-- 在开始关闭时设置标志位
# 记录最终的消息统计 # 记录最终的消息统计
if self._message_count > 0: if self._message_count > 0:
@@ -547,34 +451,183 @@ class HeartFChatting:
logger.info(f"{self.log_prefix} 没有活动的HeartFChatting循环任务") logger.info(f"{self.log_prefix} 没有活动的HeartFChatting循环任务")
# 清理状态 # 清理状态
self._loop_active = False self.running = False
self._loop_task = None self._loop_task = None
if self._processing_lock.locked():
self._processing_lock.release()
logger.warning(f"{self.log_prefix} 已释放处理锁")
# 完成性能统计
try:
self.performance_logger.finalize_session()
logger.info(f"{self.log_prefix} 性能统计已完成")
except Exception as e:
logger.warning(f"{self.log_prefix} 完成性能统计时出错: {e}")
# 重置消息计数器,为下次启动做准备 # 重置消息计数器,为下次启动做准备
self.reset_message_count() self.reset_message_count()
logger.info(f"{self.log_prefix} HeartFChatting关闭完成") logger.info(f"{self.log_prefix} HeartFChatting关闭完成")
def get_cycle_history(self, last_n: Optional[int] = None) -> List[Dict[str, Any]]: def adjust_reply_frequency(self):
"""获取循环历史记录
参数:
last_n: 获取最近n个循环的信息如果为None则获取所有历史记录
返回:
List[Dict[str, Any]]: 循环历史记录列表
""" """
history = list(self._cycle_history) 根据预设规则动态调整回复意愿willing_amplifier
if last_n is not None: - 评估周期10分钟
history = history[-last_n:] - 目标频率:由 global_config.chat.talk_frequency 定义(例如 1条/分钟)
return [cycle.to_dict() for cycle in history] - 调整逻辑:
- 0条回复 -> 5.0x 意愿
- 达到目标回复数 -> 1.0x 意愿(基准)
- 达到目标2倍回复数 -> 0.2x 意愿
- 中间值线性变化
- 增益抑制如果最近5分钟回复过快则不增加意愿。
"""
# --- 1. 定义参数 ---
evaluation_minutes = 10.0
target_replies_per_min = global_config.chat.get_current_talk_frequency(
self.stream_id
) # 目标频率e.g. 1条/分钟
target_replies_in_window = target_replies_per_min * evaluation_minutes # 10分钟内的目标回复数
if target_replies_in_window <= 0:
logger.debug(f"[{self.log_prefix}] 目标回复频率为0或负数不调整意愿放大器。")
return
# --- 2. 获取近期统计数据 ---
stats_10_min = get_recent_message_stats(minutes=evaluation_minutes, chat_id=self.stream_id)
bot_reply_count_10_min = stats_10_min["bot_reply_count"]
# --- 3. 计算新的意愿放大器 (willing_amplifier) ---
# 基于回复数在 [0, target*2] 区间内进行分段线性映射
if bot_reply_count_10_min <= target_replies_in_window:
# 在 [0, 目标数] 区间,意愿从 5.0 线性下降到 1.0
new_amplifier = 5.0 + (bot_reply_count_10_min - 0) * (1.0 - 5.0) / (target_replies_in_window - 0)
elif bot_reply_count_10_min <= target_replies_in_window * 2:
# 在 [目标数, 目标数*2] 区间,意愿从 1.0 线性下降到 0.2
over_target_cap = target_replies_in_window * 2
new_amplifier = 1.0 + (bot_reply_count_10_min - target_replies_in_window) * (0.2 - 1.0) / (
over_target_cap - target_replies_in_window
)
else:
# 超过目标数2倍直接设为最小值
new_amplifier = 0.2
# --- 4. 检查是否需要抑制增益 ---
# "如果邻近5分钟内回复数量 > 频率/2就不再进行增益"
suppress_gain = False
if new_amplifier > self.willing_amplifier: # 仅在计算结果为增益时检查
suppression_minutes = 5.0
# 5分钟内目标回复数的一半
suppression_threshold = (target_replies_per_min / 2) * suppression_minutes # e.g., (1/2)*5 = 2.5
stats_5_min = get_recent_message_stats(minutes=suppression_minutes, chat_id=self.stream_id)
bot_reply_count_5_min = stats_5_min["bot_reply_count"]
if bot_reply_count_5_min > suppression_threshold:
suppress_gain = True
# --- 5. 更新意愿放大器 ---
if suppress_gain:
logger.debug(
f"[{self.log_prefix}] 回复增益被抑制。最近5分钟内回复数 ({bot_reply_count_5_min}) "
f"> 阈值 ({suppression_threshold:.1f})。意愿放大器保持在 {self.willing_amplifier:.2f}"
)
# 不做任何改动
else:
# 限制最终值在 [0.2, 5.0] 范围内
self.willing_amplifier = max(0.2, min(5.0, new_amplifier))
logger.debug(
f"[{self.log_prefix}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> "
f"意愿放大器更新为: {self.willing_amplifier:.2f}"
)
async def normal_response(self, message_data: dict) -> None:
"""
处理接收到的消息。
"兴趣"模式下,判断是否回复并生成内容。
"""
is_mentioned = message_data.get("is_mentioned", False)
interested_rate = message_data.get("interest_rate", 0.0) * self.willing_amplifier
reply_probability = (
1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0
) # 如果被提及且开启了提及必回复则基础概率为1否则需要意愿判断
# 意愿管理器设置当前message信息
self.willing_manager.setup(message_data, self.chat_stream)
# 获取回复概率
# 仅在未被提及或基础概率不为1时查询意愿概率
if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率
# is_willing = True
reply_probability = await self.willing_manager.get_reply_probability(message_data.get("message_id"))
additional_config = message_data.get("additional_config", {})
if additional_config and "maimcore_reply_probability_gain" in additional_config:
reply_probability += additional_config["maimcore_reply_probability_gain"]
reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间
# 处理表情包
if message_data.get("is_emoji") or message_data.get("is_picid"):
reply_probability = 0
# 打印消息信息
mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊"
if reply_probability > 0.1:
logger.info(
f"[{mes_name}]"
f"{message_data.get('user_nickname')}:"
f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]"
)
if random.random() < reply_probability:
await self.willing_manager.before_generate_reply_handle(message_data.get("message_id"))
await self._observe(message_data=message_data)
# 意愿管理器注销当前message信息 (无论是否回复,只要处理过就删除)
self.willing_manager.delete(message_data.get("message_id"))
return True
async def _generate_response(
self, message_data: dict, available_actions: Optional[list], reply_to: str
) -> Optional[list]:
"""生成普通回复"""
try:
success, reply_set = await generator_api.generate_reply(
chat_stream=self.chat_stream,
reply_to=reply_to,
available_actions=available_actions,
enable_tool=global_config.tool.enable_in_normal_chat,
request_type="normal.replyer",
)
if not success or not reply_set:
logger.info(f"{message_data.get('processed_plain_text')} 的回复生成失败")
return None
return reply_set
except Exception as e:
logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}")
return None
async def _send_response(self, reply_set, reply_to, thinking_start_time):
current_time = time.time()
new_message_count = message_api.count_new_messages(
chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time
)
need_reply = new_message_count >= random.randint(2, 4)
logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复"
)
reply_text = ""
first_replyed = False
for reply_seg in reply_set:
data = reply_seg[1]
if not first_replyed:
if need_reply:
await send_api.text_to_stream(
text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, typing=False
)
first_replyed = True
else:
await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=False)
first_replyed = True
else:
await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=True)
reply_text += data
return reply_text

View File

@@ -1,162 +0,0 @@
import json
from datetime import datetime
from typing import Dict, Any
from pathlib import Path
from src.common.logger import get_logger
logger = get_logger("hfc_performance")
class HFCPerformanceLogger:
"""HFC性能记录管理器"""
# 版本号常量,可在启动时修改
INTERNAL_VERSION = "v7.0.0"
def __init__(self, chat_id: str):
self.chat_id = chat_id
self.version = self.INTERNAL_VERSION
self.log_dir = Path("log/hfc_loop")
self.session_start_time = datetime.now()
# 确保目录存在
self.log_dir.mkdir(parents=True, exist_ok=True)
# 当前会话的日志文件,包含版本号
version_suffix = self.version.replace(".", "_")
self.session_file = (
self.log_dir / f"{chat_id}_{version_suffix}_{self.session_start_time.strftime('%Y%m%d_%H%M%S')}.json"
)
self.current_session_data = []
def record_cycle(self, cycle_data: Dict[str, Any]):
"""记录单次循环数据"""
try:
# 构建记录数据
record = {
"timestamp": datetime.now().isoformat(),
"version": self.version,
"cycle_id": cycle_data.get("cycle_id"),
"chat_id": self.chat_id,
"action_type": cycle_data.get("action_type", "unknown"),
"total_time": cycle_data.get("total_time", 0),
"step_times": cycle_data.get("step_times", {}),
"reasoning": cycle_data.get("reasoning", ""),
"success": cycle_data.get("success", False),
}
# 添加到当前会话数据
self.current_session_data.append(record)
# 立即写入文件(防止数据丢失)
self._write_session_data()
# 构建详细的日志信息
log_parts = [
f"cycle_id={record['cycle_id']}",
f"action={record['action_type']}",
f"time={record['total_time']:.2f}s",
]
logger.debug(f"记录HFC循环数据: {', '.join(log_parts)}")
except Exception as e:
logger.error(f"记录HFC循环数据失败: {e}")
def _write_session_data(self):
"""写入当前会话数据到文件"""
try:
with open(self.session_file, "w", encoding="utf-8") as f:
json.dump(self.current_session_data, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"写入会话数据失败: {e}")
def get_current_session_stats(self) -> Dict[str, Any]:
"""获取当前会话的基本信息"""
if not self.current_session_data:
return {}
return {
"chat_id": self.chat_id,
"version": self.version,
"session_file": str(self.session_file),
"record_count": len(self.current_session_data),
"start_time": self.session_start_time.isoformat(),
}
def finalize_session(self):
"""结束会话"""
try:
if self.current_session_data:
logger.info(f"完成会话,当前会话 {len(self.current_session_data)} 条记录")
except Exception as e:
logger.error(f"结束会话失败: {e}")
@classmethod
def cleanup_old_logs(cls, max_size_mb: float = 50.0):
"""
清理旧的HFC日志文件保持目录大小在指定限制内
Args:
max_size_mb: 最大目录大小限制MB
"""
log_dir = Path("log/hfc_loop")
if not log_dir.exists():
logger.info("HFC日志目录不存在跳过日志清理")
return
# 获取所有日志文件及其信息
log_files = []
total_size = 0
for log_file in log_dir.glob("*.json"):
try:
file_stat = log_file.stat()
log_files.append({"path": log_file, "size": file_stat.st_size, "mtime": file_stat.st_mtime})
total_size += file_stat.st_size
except Exception as e:
logger.warning(f"无法获取文件信息 {log_file}: {e}")
if not log_files:
logger.info("没有找到HFC日志文件")
return
max_size_bytes = max_size_mb * 1024 * 1024
current_size_mb = total_size / (1024 * 1024)
logger.info(f"HFC日志目录当前大小: {current_size_mb:.2f}MB限制: {max_size_mb}MB")
if total_size <= max_size_bytes:
logger.info("HFC日志目录大小在限制范围内无需清理")
return
# 按修改时间排序(最早的在前面)
log_files.sort(key=lambda x: x["mtime"])
deleted_count = 0
deleted_size = 0
for file_info in log_files:
if total_size <= max_size_bytes:
break
try:
file_size = file_info["size"]
file_path = file_info["path"]
file_path.unlink()
total_size -= file_size
deleted_size += file_size
deleted_count += 1
logger.info(f"删除旧日志文件: {file_path.name} ({file_size / 1024:.1f}KB)")
except Exception as e:
logger.error(f"删除日志文件失败 {file_info['path']}: {e}")
final_size_mb = total_size / (1024 * 1024)
deleted_size_mb = deleted_size / (1024 * 1024)
logger.info(f"HFC日志清理完成: 删除了{deleted_count}个文件,释放{deleted_size_mb:.2f}MB空间")
logger.info(f"清理后目录大小: {final_size_mb:.2f}MB")

View File

@@ -3,22 +3,21 @@ import json
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from src.config.config import global_config
from src.common.message_repository import count_messages
from src.common.logger import get_logger
from src.chat.message_receive.message import MessageRecv, BaseMessageInfo from src.chat.message_receive.message import MessageRecv, BaseMessageInfo
from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.chat_stream import ChatStream
from src.chat.message_receive.message import UserInfo from src.chat.message_receive.message import UserInfo
from src.common.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
log_dir = "log/log_cycle_debug/"
class CycleDetail: class CycleDetail:
"""循环信息记录类""" """循环信息记录类"""
def __init__(self, cycle_id: int): def __init__(self, cycle_id: int):
self.cycle_id = cycle_id self.cycle_id = cycle_id
self.prefix = ""
self.thinking_id = "" self.thinking_id = ""
self.start_time = time.time() self.start_time = time.time()
self.end_time: Optional[float] = None self.end_time: Optional[float] = None
@@ -80,85 +79,34 @@ class CycleDetail:
"loop_action_info": convert_to_serializable(self.loop_action_info), "loop_action_info": convert_to_serializable(self.loop_action_info),
} }
def complete_cycle(self):
"""完成循环,记录结束时间"""
self.end_time = time.time()
# 处理 prefix只保留中英文字符和基本标点
if not self.prefix:
self.prefix = "group"
else:
# 只保留中文、英文字母、数字和基本标点
allowed_chars = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
self.prefix = (
"".join(char for char in self.prefix if "\u4e00" <= char <= "\u9fff" or char in allowed_chars)
or "group"
)
def set_thinking_id(self, thinking_id: str):
"""设置思考消息ID"""
self.thinking_id = thinking_id
def set_loop_info(self, loop_info: Dict[str, Any]): def set_loop_info(self, loop_info: Dict[str, Any]):
"""设置循环信息""" """设置循环信息"""
self.loop_plan_info = loop_info["loop_plan_info"] self.loop_plan_info = loop_info["loop_plan_info"]
self.loop_action_info = loop_info["loop_action_info"] self.loop_action_info = loop_info["loop_action_info"]
async def create_empty_anchor_message( def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict:
platform: str, group_info: dict, chat_stream: ChatStream
) -> Optional[MessageRecv]:
""" """
重构观察到的最后一条消息作为回复的锚点, Args:
如果重构失败或观察为空,则创建一个占位符。 minutes (int): 检索的分钟数默认30分钟
chat_id (str, optional): 指定的chat_id仅统计该chat下的消息。为None时统计全部。
Returns:
dict: {"bot_reply_count": int, "total_message_count": int}
""" """
placeholder_id = f"mid_pf_{int(time.time() * 1000)}" now = time.time()
placeholder_user = UserInfo(user_id="system_trigger", user_nickname="System Trigger", platform=platform) start_time = now - minutes * 60
placeholder_msg_info = BaseMessageInfo( bot_id = global_config.bot.qq_account
message_id=placeholder_id,
platform=platform,
group_info=group_info, # type: ignore
user_info=placeholder_user,
time=time.time(),
)
placeholder_msg_dict = {
"message_info": placeholder_msg_info.to_dict(),
"processed_plain_text": "[System Trigger Context]",
"raw_message": "",
"time": placeholder_msg_info.time,
}
anchor_message = MessageRecv(placeholder_msg_dict)
anchor_message.update_chat_stream(chat_stream)
return anchor_message filter_base = {"time": {"$gte": start_time}}
if chat_id is not None:
filter_base["chat_id"] = chat_id
# 总消息数
total_message_count = count_messages(filter_base)
# bot自身回复数
bot_filter = filter_base.copy()
bot_filter["user_id"] = bot_id
bot_reply_count = count_messages(bot_filter)
def parse_thinking_id_to_timestamp(thinking_id: str) -> float: return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count}
"""
将形如 'tid<timestamp>' 的 thinking_id 解析回 float 时间戳
例如: 'tid1718251234.56' -> 1718251234.56
"""
if not thinking_id.startswith("tid"):
raise ValueError("thinking_id 格式不正确")
ts_str = thinking_id[3:]
return float(ts_str)
def get_keywords_from_json(json_str: str) -> list[str]:
# 提取JSON内容
start = json_str.find("{")
end = json_str.rfind("}") + 1
if start == -1 or end == 0:
logger.error("未找到有效的JSON内容")
return []
json_content = json_str[start:end]
# 解析JSON
try:
json_data = json.loads(json_content)
return json_data.get("keywords", [])
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e}")
return []

View File

@@ -1,9 +1,8 @@
import time import time
import heapq import heapq
import math import math
from typing import List, Dict, Optional import json
from typing import List, Optional
from src.chat.message_receive.message import MessageRecv
from src.common.logger import get_logger from src.common.logger import get_logger
logger = get_logger("normal_chat") logger = get_logger("normal_chat")
@@ -12,8 +11,8 @@ logger = get_logger("normal_chat")
class PrioritizedMessage: class PrioritizedMessage:
"""带有优先级的消息对象""" """带有优先级的消息对象"""
def __init__(self, message: MessageRecv, interest_scores: List[float], is_vip: bool = False): def __init__(self, message_data: dict, interest_scores: List[float], is_vip: bool = False):
self.message = message self.message_data = message_data
self.arrival_time = time.time() self.arrival_time = time.time()
self.interest_scores = interest_scores self.interest_scores = interest_scores
self.is_vip = is_vip self.is_vip = is_vip
@@ -39,25 +38,28 @@ class PriorityManager:
管理消息队列根据优先级选择消息进行处理 管理消息队列根据优先级选择消息进行处理
""" """
def __init__(self, interest_dict: Dict[str, float], normal_queue_max_size: int = 5): def __init__(self, normal_queue_max_size: int = 5):
self.vip_queue: List[PrioritizedMessage] = [] # VIP 消息队列 (最大堆) self.vip_queue: List[PrioritizedMessage] = [] # VIP 消息队列 (最大堆)
self.normal_queue: List[PrioritizedMessage] = [] # 普通消息队列 (最大堆) self.normal_queue: List[PrioritizedMessage] = [] # 普通消息队列 (最大堆)
self.interest_dict = interest_dict if interest_dict is not None else {}
self.normal_queue_max_size = normal_queue_max_size self.normal_queue_max_size = normal_queue_max_size
def _get_interest_score(self, user_id: str) -> float: def add_message(self, message_data: dict, interest_score: Optional[float] = None):
"""获取用户的兴趣分默认为1.0"""
return self.interest_dict.get("interests", {}).get(user_id, 1.0)
def add_message(self, message: MessageRecv, interest_score: Optional[float] = None):
""" """
添加新消息到合适的队列中 添加新消息到合适的队列中
""" """
user_id = message.message_info.user_info.user_id # type: ignore user_id = message_data.get("user_id")
is_vip = message.priority_info.get("message_type") == "vip" if message.priority_info else False
message_priority = message.priority_info.get("message_priority", 0.0) if message.priority_info else 0.0
p_message = PrioritizedMessage(message, [interest_score, message_priority], is_vip) priority_info_raw = message_data.get("priority_info")
priority_info = {}
if isinstance(priority_info_raw, str):
priority_info = json.loads(priority_info_raw)
elif isinstance(priority_info_raw, dict):
priority_info = priority_info_raw
is_vip = priority_info.get("message_type") == "vip"
message_priority = priority_info.get("message_priority", 0.0)
p_message = PrioritizedMessage(message_data, [interest_score, message_priority], is_vip)
if is_vip: if is_vip:
heapq.heappush(self.vip_queue, p_message) heapq.heappush(self.vip_queue, p_message)
@@ -76,7 +78,7 @@ class PriorityManager:
f"消息来自普通用户 {user_id}, 已添加到普通队列. 当前普通队列长度: {len(self.normal_queue)}" f"消息来自普通用户 {user_id}, 已添加到普通队列. 当前普通队列长度: {len(self.normal_queue)}"
) )
def get_highest_priority_message(self) -> Optional[MessageRecv]: def get_highest_priority_message(self) -> Optional[dict]:
""" """
从VIP和普通队列中获取当前最高优先级的消息 从VIP和普通队列中获取当前最高优先级的消息
""" """
@@ -94,9 +96,9 @@ class PriorityManager:
normal_msg = self.normal_queue[0] if self.normal_queue else None normal_msg = self.normal_queue[0] if self.normal_queue else None
if vip_msg: if vip_msg:
return heapq.heappop(self.vip_queue).message return heapq.heappop(self.vip_queue).message_data
elif normal_msg: elif normal_msg:
return heapq.heappop(self.normal_queue).message return heapq.heappop(self.normal_queue).message_data
else: else:
return None return None

View File

@@ -1,16 +0,0 @@
import enum
class ChatState(enum.Enum):
ABSENT = "没在看群"
NORMAL = "随便水群"
FOCUSED = "认真水群"
def __str__(self):
return self.name
class ChatStateInfo:
def __init__(self):
self.chat_status: ChatState = ChatState.NORMAL
self.current_state_time = 120

View File

@@ -1,7 +1,8 @@
import traceback
from typing import Any, Optional, Dict from typing import Any, Optional, Dict
from src.common.logger import get_logger from src.common.logger import get_logger
from src.chat.heart_flow.sub_heartflow import SubHeartflow, ChatState from src.chat.heart_flow.sub_heartflow import SubHeartflow
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
logger = get_logger("heartflow") logger = get_logger("heartflow")
@@ -27,27 +28,13 @@ class Heartflow:
# 注册子心流 # 注册子心流
self.subheartflows[subheartflow_id] = new_subflow self.subheartflows[subheartflow_id] = new_subflow
heartflow_name = get_chat_manager().get_stream_name(subheartflow_id) or subheartflow_id heartflow_name = get_chat_manager().get_stream_name(subheartflow_id) or subheartflow_id
logger.debug(f"[{heartflow_name}] 开始接收消息") logger.info(f"[{heartflow_name}] 开始接收消息")
return new_subflow return new_subflow
except Exception as e: except Exception as e:
logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True) logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True)
traceback.print_exc()
return None return None
async def force_change_subheartflow_status(self, subheartflow_id: str, status: ChatState) -> bool:
"""强制改变子心流的状态"""
# 这里的 message 是可选的,可能是一个消息对象,也可能是其他类型的数据
return await self.force_change_state(subheartflow_id, status)
async def force_change_state(self, subflow_id: Any, target_state: ChatState) -> bool:
"""强制改变指定子心流的状态"""
subflow = self.subheartflows.get(subflow_id)
if not subflow:
logger.warning(f"[强制状态转换]尝试转换不存在的子心流{subflow_id}{target_state.value}")
return False
await subflow.change_chat_state(target_state)
logger.info(f"[强制状态转换]子心流 {subflow_id} 已转换到 {target_state.value}")
return True
heartflow = Heartflow() heartflow = Heartflow()

View File

@@ -104,12 +104,13 @@ class HeartFCMessageReceiver:
# 2. 兴趣度计算与更新 # 2. 兴趣度计算与更新
interested_rate, is_mentioned = await _calculate_interest(message) interested_rate, is_mentioned = await _calculate_interest(message)
message.interest_value = interested_rate message.interest_value = interested_rate
message.is_mentioned = is_mentioned
await self.storage.store_message(message, chat) await self.storage.store_message(message, chat)
subheartflow: SubHeartflow = await heartflow.get_or_create_subheartflow(chat.stream_id) # type: ignore subheartflow: SubHeartflow = await heartflow.get_or_create_subheartflow(chat.stream_id) # type: ignore
subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) # subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned)
chat_mood = mood_manager.get_mood_by_chat_id(subheartflow.chat_id) # type: ignore chat_mood = mood_manager.get_mood_by_chat_id(subheartflow.chat_id) # type: ignore
asyncio.create_task(chat_mood.update_mood_by_message(message, interested_rate)) asyncio.create_task(chat_mood.update_mood_by_message(message, interested_rate))

View File

@@ -10,8 +10,6 @@ from src.config.config import global_config
from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.message import MessageRecv
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from src.chat.focus_chat.heartFC_chat import HeartFChatting from src.chat.focus_chat.heartFC_chat import HeartFChatting
from src.chat.normal_chat.normal_chat import NormalChat
from src.chat.heart_flow.chat_state_info import ChatState, ChatStateInfo
from src.chat.utils.utils import get_chat_type_and_target_info from src.chat.utils.utils import get_chat_type_and_target_info
logger = get_logger("sub_heartflow") logger = get_logger("sub_heartflow")
@@ -33,278 +31,60 @@ class SubHeartflow:
self.subheartflow_id = subheartflow_id self.subheartflow_id = subheartflow_id
self.chat_id = subheartflow_id self.chat_id = subheartflow_id
# 这个聊天流的状态
self.chat_state: ChatStateInfo = ChatStateInfo()
self.chat_state_changed_time: float = time.time()
self.chat_state_last_time: float = 0
self.history_chat_state: List[Tuple[ChatState, float]] = []
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id) self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id)
self.log_prefix = get_chat_manager().get_stream_name(self.subheartflow_id) or self.subheartflow_id self.log_prefix = get_chat_manager().get_stream_name(self.subheartflow_id) or self.subheartflow_id
# 兴趣消息集合
self.interest_dict: Dict[str, Tuple[MessageRecv, float, bool]] = {}
# focus模式退出冷却时间管理 # focus模式退出冷却时间管理
self.last_focus_exit_time: float = 0 # 上次退出focus模式的时间 self.last_focus_exit_time: float = 0 # 上次退出focus模式的时间
# 随便水群 normal_chat 和 认真水群 focus_chat 实例 # 随便水群 normal_chat 和 认真水群 focus_chat 实例
# CHAT模式激活 随便水群 FOCUS模式激活 认真水群 # CHAT模式激活 随便水群 FOCUS模式激活 认真水群
self.heart_fc_instance: Optional[HeartFChatting] = None # 该sub_heartflow的HeartFChatting实例 self.heart_fc_instance: Optional[HeartFChatting] = HeartFChatting(
self.normal_chat_instance: Optional[NormalChat] = None # 该sub_heartflow的NormalChat实例 chat_id=self.subheartflow_id,
) # 该sub_heartflow的HeartFChatting实例
async def initialize(self): async def initialize(self):
"""异步初始化方法,创建兴趣流并确定聊天类型""" """异步初始化方法,创建兴趣流并确定聊天类型"""
await self.heart_fc_instance.start()
# 根据配置决定初始状态
if not self.is_group_chat:
logger.debug(f"{self.log_prefix} 检测到是私聊,将直接尝试进入 FOCUSED 状态。")
await self.change_chat_state(ChatState.FOCUSED)
elif global_config.chat.chat_mode == "focus":
logger.debug(f"{self.log_prefix} 配置为 focus 模式,将直接尝试进入 FOCUSED 状态。")
await self.change_chat_state(ChatState.FOCUSED)
else: # "auto" 或其他模式保持原有逻辑或默认为 NORMAL
logger.debug(f"{self.log_prefix} 配置为 auto 或其他模式,将尝试进入 NORMAL 状态。")
await self.change_chat_state(ChatState.NORMAL)
def update_last_chat_state_time(self):
self.chat_state_last_time = time.time() - self.chat_state_changed_time
async def _stop_normal_chat(self):
"""
停止 NormalChat 实例
切出 CHAT 状态时使用
"""
if not self.normal_chat_instance:
return
logger.info(f"{self.log_prefix} 离开normal模式")
try:
logger.debug(f"{self.log_prefix} 开始调用 stop_chat()")
# 使用更短的超时时间,强制快速停止
await asyncio.wait_for(self.normal_chat_instance.stop_chat(), timeout=3.0)
logger.debug(f"{self.log_prefix} stop_chat() 调用完成")
except asyncio.TimeoutError:
logger.warning(f"{self.log_prefix} 停止 NormalChat 超时,强制清理")
# 超时时强制清理实例
self.normal_chat_instance = None
except Exception as e:
logger.error(f"{self.log_prefix} 停止 NormalChat 监控任务时出错: {e}")
# 出错时也要清理实例,避免状态不一致
self.normal_chat_instance = None
finally:
# 确保实例被清理
if self.normal_chat_instance:
logger.warning(f"{self.log_prefix} 强制清理 NormalChat 实例")
self.normal_chat_instance = None
logger.debug(f"{self.log_prefix} _stop_normal_chat 完成")
async def _start_normal_chat(self, rewind=False) -> bool:
"""
启动 NormalChat 实例,并进行异步初始化。
进入 CHAT 状态时使用。
确保 HeartFChatting 已停止。
"""
await self._stop_heart_fc_chat() # 确保 专注聊天已停止
self.interest_dict.clear()
log_prefix = self.log_prefix
try:
# 获取聊天流并创建 NormalChat 实例 (同步部分)
chat_stream = get_chat_manager().get_stream(self.chat_id)
if not chat_stream:
logger.error(f"{log_prefix} 无法获取 chat_stream无法启动 NormalChat。")
return False
# 在 rewind 为 True 或 NormalChat 实例尚未创建时,创建新实例
if rewind or not self.normal_chat_instance:
# 提供回调函数用于接收需要切换到focus模式的通知
self.normal_chat_instance = NormalChat(
chat_stream=chat_stream,
interest_dict=self.interest_dict,
on_switch_to_focus_callback=self._handle_switch_to_focus_request,
get_cooldown_progress_callback=self.get_cooldown_progress,
)
logger.info(f"{log_prefix} 开始普通聊天,随便水群...")
await self.normal_chat_instance.start_chat() # start_chat now ensures init is called again if needed
return True
except Exception as e:
logger.error(f"{log_prefix} 启动 NormalChat 或其初始化时出错: {e}")
logger.error(traceback.format_exc())
self.normal_chat_instance = None # 启动/初始化失败,清理实例
return False
async def _handle_switch_to_focus_request(self) -> bool:
"""
处理来自NormalChat的切换到focus模式的请求
Args:
stream_id: 请求切换的stream_id
Returns:
bool: 切换成功返回True失败返回False
"""
logger.info(f"{self.log_prefix} 收到NormalChat请求切换到focus模式")
# 检查是否在focus冷却期内
if self.is_in_focus_cooldown():
logger.info(f"{self.log_prefix} 正在focus冷却期内忽略切换到focus模式的请求")
return False
# 切换到focus模式
current_state = self.chat_state.chat_status
if current_state == ChatState.NORMAL:
await self.change_chat_state(ChatState.FOCUSED)
logger.info(f"{self.log_prefix} 已根据NormalChat请求从NORMAL切换到FOCUSED状态")
return True
else:
logger.warning(f"{self.log_prefix} 当前状态为{current_state.value}无法切换到FOCUSED状态")
return False
async def _handle_stop_focus_chat_request(self) -> None:
"""
处理来自HeartFChatting的停止focus模式的请求
当收到stop_focus_chat命令时被调用
"""
logger.info(f"{self.log_prefix} 收到HeartFChatting请求停止focus模式")
# 切换到normal模式
current_state = self.chat_state.chat_status
if current_state == ChatState.FOCUSED:
await self.change_chat_state(ChatState.NORMAL)
logger.info(f"{self.log_prefix} 已根据HeartFChatting请求从FOCUSED切换到NORMAL状态")
else:
logger.warning(f"{self.log_prefix} 当前状态为{current_state.value}无法切换到NORMAL状态")
async def _stop_heart_fc_chat(self): async def _stop_heart_fc_chat(self):
"""停止并清理 HeartFChatting 实例""" """停止并清理 HeartFChatting 实例"""
if self.heart_fc_instance: if self.heart_fc_instance.running:
logger.debug(f"{self.log_prefix} 结束专注聊天...") logger.info(f"{self.log_prefix} 结束专注聊天...")
try: try:
await self.heart_fc_instance.shutdown() await self.heart_fc_instance.shutdown()
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 关闭 HeartFChatting 实例时出错: {e}") logger.error(f"{self.log_prefix} 关闭 HeartFChatting 实例时出错: {e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
finally: else:
# 无论是否成功关闭,都清理引用 logger.info(f"{self.log_prefix} 没有专注聊天实例,无需停止专注聊天")
self.heart_fc_instance = None
async def _start_heart_fc_chat(self) -> bool: async def _start_heart_fc_chat(self) -> bool:
"""启动 HeartFChatting 实例,确保 NormalChat 已停止""" """启动 HeartFChatting 实例,确保 NormalChat 已停止"""
logger.debug(f"{self.log_prefix} 开始启动 HeartFChatting")
try: try:
# 确保普通聊天监控已停止
await self._stop_normal_chat()
self.interest_dict.clear()
log_prefix = self.log_prefix
# 如果实例已存在,检查其循环任务状态
if self.heart_fc_instance:
logger.debug(f"{log_prefix} HeartFChatting 实例已存在,检查状态")
# 如果任务已完成或不存在,则尝试重新启动 # 如果任务已完成或不存在,则尝试重新启动
if self.heart_fc_instance._loop_task is None or self.heart_fc_instance._loop_task.done(): if self.heart_fc_instance._loop_task is None or self.heart_fc_instance._loop_task.done():
logger.info(f"{log_prefix} HeartFChatting 实例存在但循环未运行,尝试启动...") logger.info(f"{self.log_prefix} HeartFChatting 实例存在但循环未运行,尝试启动...")
try: try:
# 添加超时保护 # 添加超时保护
await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0) await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0)
logger.info(f"{log_prefix} HeartFChatting 循环已启动。") logger.info(f"{self.log_prefix} HeartFChatting 循环已启动。")
return True return True
except Exception as e: except Exception as e:
logger.error(f"{log_prefix} 尝试启动现有 HeartFChatting 循环时出错: {e}") logger.error(f"{self.log_prefix} 尝试启动现有 HeartFChatting 循环时出错: {e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
# 出错时清理实例,准备重新创建 # 出错时清理实例,准备重新创建
self.heart_fc_instance = None self.heart_fc_instance = None
else: else:
# 任务正在运行 # 任务正在运行
logger.debug(f"{log_prefix} HeartFChatting 已在运行中。") logger.debug(f"{self.log_prefix} HeartFChatting 已在运行中。")
return True # 已经在运行 return True # 已经在运行
# 如果实例不存在,则创建并启动
logger.info(f"{log_prefix} 麦麦准备开始专注聊天...")
try:
logger.debug(f"{log_prefix} 创建新的 HeartFChatting 实例")
self.heart_fc_instance = HeartFChatting(
chat_id=self.subheartflow_id,
on_stop_focus_chat=self._handle_stop_focus_chat_request,
)
logger.debug(f"{log_prefix} 启动 HeartFChatting 实例")
# 添加超时保护
await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0)
logger.debug(f"{log_prefix} 麦麦已成功进入专注聊天模式 (新实例已启动)。")
return True
except Exception as e:
logger.error(f"{log_prefix} 创建或启动 HeartFChatting 实例时出错: {e}")
logger.error(traceback.format_exc())
self.heart_fc_instance = None # 创建或初始化异常,清理实例
return False
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} _start_heart_fc_chat 执行时出错: {e}") logger.error(f"{self.log_prefix} _start_heart_fc_chat 执行时出错: {e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return False return False
async def change_chat_state(self, new_state: ChatState) -> None:
"""
改变聊天状态。
如果转换到CHAT或FOCUSED状态时超过限制会保持当前状态。
"""
current_state = self.chat_state.chat_status
state_changed = False
log_prefix = f"[{self.log_prefix}]"
if new_state == ChatState.NORMAL:
logger.debug(f"{log_prefix} 准备进入 normal聊天 状态")
if await self._start_normal_chat():
logger.debug(f"{log_prefix} 成功进入或保持 NormalChat 状态。")
state_changed = True
else:
logger.error(f"{log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。")
# 启动失败时,保持当前状态
return
elif new_state == ChatState.FOCUSED:
logger.debug(f"{log_prefix} 准备进入 focus聊天 状态")
if await self._start_heart_fc_chat():
logger.debug(f"{log_prefix} 成功进入或保持 HeartFChatting 状态。")
state_changed = True
else:
logger.error(f"{log_prefix} 启动 HeartFChatting 失败,无法进入 FOCUSED 状态。")
# 启动失败时,保持当前状态
return
elif new_state == ChatState.ABSENT:
logger.info(f"{log_prefix} 进入 ABSENT 状态,停止所有聊天活动...")
self.interest_dict.clear()
await self._stop_normal_chat()
await self._stop_heart_fc_chat()
state_changed = True
# --- 记录focus模式退出时间 ---
if state_changed and current_state == ChatState.FOCUSED and new_state != ChatState.FOCUSED:
self.last_focus_exit_time = time.time()
logger.debug(f"{log_prefix} 记录focus模式退出时间: {self.last_focus_exit_time}")
# --- 更新状态和最后活动时间 ---
if state_changed:
self.update_last_chat_state_time()
self.history_chat_state.append((current_state, self.chat_state_last_time))
self.chat_state.chat_status = new_state
self.chat_state_last_time = 0
self.chat_state_changed_time = time.time()
else:
logger.debug(
f"{log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。"
)
def add_message_to_normal_chat_cache(self, message: MessageRecv, interest_value: float, is_mentioned: bool):
self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned) # type: ignore
# 如果字典长度超过10删除最旧的消息
if len(self.interest_dict) > 30:
oldest_key = next(iter(self.interest_dict))
self.interest_dict.pop(oldest_key)
def is_in_focus_cooldown(self) -> bool: def is_in_focus_cooldown(self) -> bool:
"""检查是否在focus模式的冷却期内 """检查是否在focus模式的冷却期内

View File

@@ -1,12 +1,10 @@
from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.emoji_system.emoji_manager import get_emoji_manager
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from src.chat.message_receive.normal_message_sender import message_manager
from src.chat.message_receive.storage import MessageStorage from src.chat.message_receive.storage import MessageStorage
__all__ = [ __all__ = [
"get_emoji_manager", "get_emoji_manager",
"get_chat_manager", "get_chat_manager",
"message_manager",
"MessageStorage", "MessageStorage",
] ]

View File

@@ -84,7 +84,6 @@ class ChatBot:
# 创建初始化PFC管理器的任务会在_ensure_started时执行 # 创建初始化PFC管理器的任务会在_ensure_started时执行
self.only_process_chat = MessageProcessor() self.only_process_chat = MessageProcessor()
self.pfc_manager = PFCManager.get_instance()
self.s4u_message_processor = S4UMessageProcessor() self.s4u_message_processor = S4UMessageProcessor()
async def _ensure_started(self): async def _ensure_started(self):

View File

@@ -438,3 +438,52 @@ class MessageSet:
def __len__(self) -> int: def __len__(self) -> int:
return len(self.messages) return len(self.messages)
def message_recv_from_dict(message_dict: dict) -> MessageRecv:
return MessageRecv(message_dict)
def message_from_db_dict(db_dict: dict) -> MessageRecv:
"""从数据库字典创建MessageRecv实例"""
# 转换扁平的数据库字典为嵌套结构
message_info_dict = {
"platform": db_dict.get("chat_info_platform"),
"message_id": db_dict.get("message_id"),
"time": db_dict.get("time"),
"group_info": {
"platform": db_dict.get("chat_info_group_platform"),
"group_id": db_dict.get("chat_info_group_id"),
"group_name": db_dict.get("chat_info_group_name"),
},
"user_info": {
"platform": db_dict.get("user_platform"),
"user_id": db_dict.get("user_id"),
"user_nickname": db_dict.get("user_nickname"),
"user_cardname": db_dict.get("user_cardname"),
},
}
processed_text = db_dict.get("processed_plain_text", "")
# 构建 MessageRecv 需要的字典
recv_dict = {
"message_info": message_info_dict,
"message_segment": {"type": "text", "data": processed_text}, # 从纯文本重建消息段
"raw_message": None, # 数据库中未存储原始消息
"processed_plain_text": processed_text,
"detailed_plain_text": db_dict.get("detailed_plain_text", ""),
}
# 创建 MessageRecv 实例
msg = MessageRecv(recv_dict)
# 从数据库字典中填充其他可选字段
msg.interest_value = db_dict.get("interest_value")
msg.is_mentioned = db_dict.get("is_mentioned")
msg.priority_mode = db_dict.get("priority_mode", "interest")
msg.priority_info = db_dict.get("priority_info")
msg.is_emoji = db_dict.get("is_emoji", False)
msg.is_picid = db_dict.get("is_picid", False)
return msg

View File

@@ -1,297 +0,0 @@
import asyncio
import time
from asyncio import Task
from typing import Union
from rich.traceback import install
from src.common.logger import get_logger
from src.common.message.api import get_global_api
from src.chat.message_receive.storage import MessageStorage
from src.chat.utils.utils import truncate_message, calculate_typing_time, count_messages_between
from .message import MessageSending, MessageThinking, MessageSet
install(extra_lines=3)
logger = get_logger("sender")
async def send_via_ws(message: MessageSending) -> None:
"""通过 WebSocket 发送消息"""
try:
await get_global_api().send_message(message)
except Exception as e:
logger.error(f"WS发送失败: {e}")
raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置请检查配置文件") from e
async def send_message(
message: MessageSending,
) -> None:
"""发送消息(核心发送逻辑)"""
# --- 添加计算打字和延迟的逻辑 (从 heartflow_message_sender 移动并调整) ---
typing_time = calculate_typing_time(
input_string=message.processed_plain_text,
thinking_start_time=message.thinking_start_time,
is_emoji=message.is_emoji,
)
# logger.debug(f"{message.processed_plain_text},{typing_time},计算输入时间结束") # 减少日志
await asyncio.sleep(typing_time)
# logger.debug(f"{message.processed_plain_text},{typing_time},等待输入时间结束") # 减少日志
# --- 结束打字延迟 ---
message_preview = truncate_message(message.processed_plain_text)
try:
await send_via_ws(message)
logger.info(f"发送消息 '{message_preview}' 成功") # 调整日志格式
except Exception as e:
logger.error(f"发送消息 '{message_preview}' 失败: {str(e)}")
class MessageSender:
"""发送器 (不再是单例)"""
def __init__(self):
self.message_interval = (0.5, 1) # 消息间隔时间范围(秒)
self.last_send_time = 0
self._current_bot = None
def set_bot(self, bot):
"""设置当前bot实例"""
pass
class MessageContainer:
"""单个聊天流的发送/思考消息容器"""
def __init__(self, chat_id: str, max_size: int = 100):
self.chat_id = chat_id
self.max_size = max_size
self.messages: list[MessageThinking | MessageSending] = [] # 明确类型
self.last_send_time = 0
self.thinking_wait_timeout = 20 # 思考等待超时时间(秒) - 从旧 sender 合并
def count_thinking_messages(self) -> int:
"""计算当前容器中思考消息的数量"""
return sum(isinstance(msg, MessageThinking) for msg in self.messages)
def get_timeout_sending_messages(self) -> list[MessageSending]:
# sourcery skip: merge-nested-ifs
"""获取所有超时的MessageSending对象思考时间超过20秒按thinking_start_time排序 - 从旧 sender 合并"""
current_time = time.time()
timeout_messages = []
for msg in self.messages:
# 只检查 MessageSending 类型
if isinstance(msg, MessageSending):
# 确保 thinking_start_time 有效
if msg.thinking_start_time and current_time - msg.thinking_start_time > self.thinking_wait_timeout:
timeout_messages.append(msg)
# 按thinking_start_time排序时间早的在前面
timeout_messages.sort(key=lambda x: x.thinking_start_time)
return timeout_messages
def get_earliest_message(self):
"""获取thinking_start_time最早的消息对象"""
if not self.messages:
return None
earliest_time = float("inf")
earliest_message = None
for msg in self.messages:
# 确保消息有 thinking_start_time 属性
msg_time = getattr(msg, "thinking_start_time", float("inf"))
if msg_time < earliest_time:
earliest_time = msg_time
earliest_message = msg
return earliest_message
def add_message(self, message: Union[MessageThinking, MessageSending, MessageSet]):
"""添加消息到队列"""
if isinstance(message, MessageSet):
for single_message in message.messages:
self.messages.append(single_message)
else:
self.messages.append(message)
def remove_message(self, message_to_remove: Union[MessageThinking, MessageSending]):
"""移除指定的消息对象如果消息存在则返回True否则返回False"""
try:
_initial_len = len(self.messages)
# 使用列表推导式或 message_filter 创建新列表,排除要删除的元素
# self.messages = [msg for msg in self.messages if msg is not message_to_remove]
# 或者直接 remove (如果确定对象唯一性)
if message_to_remove in self.messages:
self.messages.remove(message_to_remove)
return True
# logger.debug(f"Removed message {getattr(message_to_remove, 'message_info', {}).get('message_id', 'UNKNOWN')}. Old len: {initial_len}, New len: {len(self.messages)}")
# return len(self.messages) < initial_len
return False
except Exception as e:
logger.exception(f"移除消息时发生错误: {e}")
return False
def has_messages(self) -> bool:
"""检查是否有待发送的消息"""
return bool(self.messages)
def get_all_messages(self) -> list[MessageThinking | MessageSending]:
"""获取所有消息"""
return list(self.messages) # 返回副本
class MessageManager:
"""管理所有聊天流的消息容器 (不再是单例)"""
def __init__(self):
self._processor_task: Task | None = None
self.containers: dict[str, MessageContainer] = {}
self.storage = MessageStorage() # 添加 storage 实例
self._running = True # 处理器运行状态
self._container_lock = asyncio.Lock() # 保护 containers 字典的锁
# self.message_sender = MessageSender() # 创建发送器实例 (改为全局实例)
async def start(self):
"""启动后台处理器任务。"""
# 检查是否已有任务在运行,避免重复启动
if self._processor_task is not None and not self._processor_task.done():
logger.warning("Processor task already running.")
return
self._processor_task = asyncio.create_task(self._start_processor_loop())
logger.debug("MessageManager processor task started.")
def stop(self):
"""停止后台处理器任务。"""
self._running = False
if self._processor_task is not None and not self._processor_task.done():
self._processor_task.cancel()
logger.debug("MessageManager processor task stopping.")
else:
logger.debug("MessageManager processor task not running or already stopped.")
async def get_container(self, chat_id: str) -> MessageContainer:
"""获取或创建聊天流的消息容器 (异步,使用锁)"""
async with self._container_lock:
if chat_id not in self.containers:
self.containers[chat_id] = MessageContainer(chat_id)
return self.containers[chat_id]
async def add_message(self, message: Union[MessageThinking, MessageSending, MessageSet]) -> None:
"""添加消息到对应容器"""
chat_stream = message.chat_stream
if not chat_stream:
logger.error("消息缺少 chat_stream无法添加到容器")
return # 或者抛出异常
container = await self.get_container(chat_stream.stream_id)
container.add_message(message)
async def _handle_sending_message(self, container: MessageContainer, message: MessageSending):
"""处理单个 MessageSending 消息 (包含 set_reply 逻辑)"""
try:
_ = message.update_thinking_time() # 更新思考时间
thinking_start_time = message.thinking_start_time
now_time = time.time()
# logger.debug(f"thinking_start_time:{thinking_start_time},now_time:{now_time}")
thinking_messages_count, thinking_messages_length = count_messages_between(
start_time=thinking_start_time, end_time=now_time, stream_id=message.chat_stream.stream_id
)
if (
message.is_head
and (thinking_messages_count > 3 or thinking_messages_length > 200)
and not message.is_private_message()
):
logger.debug(
f"[{message.chat_stream.stream_id}] 应用 set_reply 逻辑: {message.processed_plain_text[:20]}..."
)
message.build_reply()
# --- 结束条件 set_reply ---
await message.process() # 预处理消息内容
# logger.debug(f"{message}")
# 使用全局 message_sender 实例
await send_message(message)
await self.storage.store_message(message, message.chat_stream)
# 移除消息要在发送 *之后*
container.remove_message(message)
# logger.debug(f"[{message.chat_stream.stream_id}] Sent and removed message: {message.message_info.message_id}")
except Exception as e:
logger.error(
f"[{message.chat_stream.stream_id}] 处理发送消息 {getattr(message.message_info, 'message_id', 'N/A')} 时出错: {e}"
)
logger.exception("详细错误信息:")
if container.remove_message(message):
logger.warning(f"[{message.chat_stream.stream_id}] 已移除处理出错的消息。")
async def _process_chat_messages(self, chat_id: str):
"""处理单个聊天流消息 (合并后的逻辑)"""
container = await self.get_container(chat_id) # 获取容器是异步的了
if container.has_messages():
message_earliest = container.get_earliest_message()
if not message_earliest: # 如果最早消息为空,则退出
return
if isinstance(message_earliest, MessageThinking):
# --- 处理思考消息 (来自旧 sender) ---
message_earliest.update_thinking_time()
thinking_time = message_earliest.thinking_time
# 减少控制台刷新频率或只在时间显著变化时打印
if int(thinking_time) % 5 == 0: # 每5秒打印一次
print(
f"消息 {message_earliest.message_info.message_id} 正在思考中,已思考 {int(thinking_time)}\r",
end="",
flush=True,
)
elif isinstance(message_earliest, MessageSending):
# --- 处理发送消息 ---
await self._handle_sending_message(container, message_earliest)
if timeout_sending_messages := container.get_timeout_sending_messages():
logger.debug(f"[{chat_id}] 发现 {len(timeout_sending_messages)} 条超时的发送消息")
for msg in timeout_sending_messages:
# 确保不是刚刚处理过的最早消息 (虽然理论上应该已被移除,但以防万一)
if msg is message_earliest:
continue
logger.info(f"[{chat_id}] 处理超时发送消息: {msg.message_info.message_id}")
await self._handle_sending_message(container, msg) # 复用处理逻辑
async def _start_processor_loop(self):
# sourcery skip: list-comprehension, move-assign-in-block, use-named-expression
"""消息处理器主循环"""
while self._running:
tasks = []
# 使用异步锁保护迭代器创建过程
async with self._container_lock:
# 创建 keys 的快照以安全迭代
chat_ids = list(self.containers.keys())
tasks.extend(asyncio.create_task(self._process_chat_messages(chat_id)) for chat_id in chat_ids)
if tasks:
try:
# 等待当前批次的所有任务完成
await asyncio.gather(*tasks)
except Exception as e:
logger.error(f"消息处理循环 gather 出错: {e}")
# 等待一小段时间避免CPU空转
try:
await asyncio.sleep(0.1) # 稍微降低轮询频率
except asyncio.CancelledError:
logger.info("Processor loop sleep cancelled.")
break # 退出循环
logger.info("MessageManager processor loop finished.")
# --- 创建全局实例 ---
message_manager = MessageManager()
message_sender = MessageSender()
# --- 结束全局实例 ---

View File

@@ -37,11 +37,21 @@ class MessageStorage:
else: else:
filtered_display_message = "" filtered_display_message = ""
interest_value = 0 interest_value = 0
is_mentioned = False
reply_to = message.reply_to reply_to = message.reply_to
priority_mode = ""
priority_info = {}
is_emoji = False
is_picid = False
else: else:
filtered_display_message = "" filtered_display_message = ""
interest_value = message.interest_value interest_value = message.interest_value
is_mentioned = message.is_mentioned
reply_to = "" reply_to = ""
priority_mode = message.priority_mode
priority_info = message.priority_info
is_emoji = message.is_emoji
is_picid = message.is_picid
chat_info_dict = chat_stream.to_dict() chat_info_dict = chat_stream.to_dict()
user_info_dict = message.message_info.user_info.to_dict() # type: ignore user_info_dict = message.message_info.user_info.to_dict() # type: ignore
@@ -60,6 +70,7 @@ class MessageStorage:
chat_id=chat_stream.stream_id, chat_id=chat_stream.stream_id,
# Flattened chat_info # Flattened chat_info
reply_to=reply_to, reply_to=reply_to,
is_mentioned=is_mentioned,
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"),
@@ -81,6 +92,10 @@ class MessageStorage:
display_message=filtered_display_message, display_message=filtered_display_message,
memorized_times=message.memorized_times, memorized_times=message.memorized_times,
interest_value=interest_value, interest_value=interest_value,
priority_mode=priority_mode,
priority_info=priority_info,
is_emoji=is_emoji,
is_picid=is_picid,
) )
except Exception: except Exception:
logger.exception("存储消息失败") logger.exception("存储消息失败")

View File

@@ -1,12 +1,11 @@
import asyncio import asyncio
import traceback import traceback
from typing import Dict, Optional
from rich.traceback import install from rich.traceback import install
from src.common.message.api import get_global_api from src.common.message.api import get_global_api
from src.common.logger import get_logger from src.common.logger import get_logger
from src.chat.message_receive.message import MessageSending, MessageThinking from src.chat.message_receive.message import MessageSending
from src.chat.message_receive.storage import MessageStorage from src.chat.message_receive.storage import MessageStorage
from src.chat.utils.utils import truncate_message from src.chat.utils.utils import truncate_message
from src.chat.utils.utils import calculate_typing_time from src.chat.utils.utils import calculate_typing_time
@@ -37,42 +36,6 @@ class HeartFCSender:
def __init__(self): def __init__(self):
self.storage = MessageStorage() self.storage = MessageStorage()
# 用于存储活跃的思考消息
self.thinking_messages: Dict[str, Dict[str, MessageThinking]] = {}
self._thinking_lock = asyncio.Lock() # 保护 thinking_messages 的锁
async def register_thinking(self, thinking_message: MessageThinking):
"""注册一个思考中的消息。"""
if not thinking_message.chat_stream or not thinking_message.message_info.message_id:
logger.error("无法注册缺少 chat_stream 或 message_id 的思考消息")
return
chat_id = thinking_message.chat_stream.stream_id
message_id = thinking_message.message_info.message_id
async with self._thinking_lock:
if chat_id not in self.thinking_messages:
self.thinking_messages[chat_id] = {}
if message_id in self.thinking_messages[chat_id]:
logger.warning(f"[{chat_id}] 尝试注册已存在的思考消息 ID: {message_id}")
self.thinking_messages[chat_id][message_id] = thinking_message
logger.debug(f"[{chat_id}] Registered thinking message: {message_id}")
async def complete_thinking(self, chat_id: str, message_id: str):
"""完成并移除一个思考中的消息记录。"""
async with self._thinking_lock:
if chat_id in self.thinking_messages and message_id in self.thinking_messages[chat_id]:
del self.thinking_messages[chat_id][message_id]
logger.debug(f"[{chat_id}] Completed thinking message: {message_id}")
if not self.thinking_messages[chat_id]:
del self.thinking_messages[chat_id]
logger.debug(f"[{chat_id}] Removed empty thinking message container.")
async def get_thinking_start_time(self, chat_id: str, message_id: str) -> Optional[float]:
"""获取已注册思考消息的开始时间。"""
async with self._thinking_lock:
thinking_message = self.thinking_messages.get(chat_id, {}).get(message_id)
return thinking_message.thinking_start_time if thinking_message else None
async def send_message(self, message: MessageSending, typing=False, set_reply=False, storage_message=True): async def send_message(self, message: MessageSending, typing=False, set_reply=False, storage_message=True):
""" """
@@ -122,5 +85,3 @@ class HeartFCSender:
except Exception as e: except Exception as e:
logger.error(f"[{chat_id}] 处理或存储消息 {message_id} 时出错: {e}") logger.error(f"[{chat_id}] 处理或存储消息 {message_id} 时出错: {e}")
raise e raise e
finally:
await self.complete_thinking(chat_id, message_id)

File diff suppressed because it is too large Load Diff

View File

@@ -145,29 +145,6 @@ class ActionManager:
"""获取当前正在使用的动作集合""" """获取当前正在使用的动作集合"""
return self._using_actions.copy() return self._using_actions.copy()
def get_using_actions_for_mode(self, mode: ChatMode) -> Dict[str, ActionInfo]:
"""
根据聊天模式获取可用的动作集合
Args:
mode: 聊天模式 (ChatMode.FOCUS, ChatMode.NORMAL, ChatMode.ALL)
Returns:
Dict[str, ActionInfo]: 在指定模式下可用的动作集合
"""
enabled_actions = {}
for action_name, action_info in self._using_actions.items():
action_mode = action_info.mode_enable
# 检查动作是否在当前模式下启用
if action_mode in [ChatMode.ALL, mode]:
enabled_actions[action_name] = action_info
logger.debug(f"动作 {action_name} 在模式 {mode} 下可用 (mode_enable: {action_mode})")
logger.debug(f"模式 {mode} 下可用动作: {list(enabled_actions.keys())}")
return enabled_actions
def add_action_to_using(self, action_name: str) -> bool: def add_action_to_using(self, action_name: str) -> bool:
""" """
添加已注册的动作到当前使用的动作集 添加已注册的动作到当前使用的动作集

View File

@@ -7,7 +7,7 @@ from typing import List, Any, Dict, TYPE_CHECKING
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import global_config from src.config.config import global_config
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest
from src.chat.focus_chat.focus_loop_info import FocusLoopInfo from src.chat.focus_chat.hfc_utils import CycleDetail
from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext
from src.chat.planner_actions.action_manager import ActionManager from src.chat.planner_actions.action_manager import ActionManager
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages
@@ -48,8 +48,7 @@ class ActionModifier:
async def modify_actions( async def modify_actions(
self, self,
loop_info=None, history_loop=None,
mode: ChatMode = ChatMode.FOCUS,
message_content: str = "", message_content: str = "",
): # sourcery skip: use-named-expression ): # sourcery skip: use-named-expression
""" """
@@ -67,7 +66,7 @@ class ActionModifier:
removals_s2 = [] removals_s2 = []
self.action_manager.restore_actions() self.action_manager.restore_actions()
all_actions = self.action_manager.get_using_actions_for_mode(mode) all_actions = self.action_manager.get_using_actions()
message_list_before_now_half = get_raw_msg_before_timestamp_with_chat( message_list_before_now_half = get_raw_msg_before_timestamp_with_chat(
chat_id=self.chat_stream.stream_id, chat_id=self.chat_stream.stream_id,
@@ -87,10 +86,10 @@ class ActionModifier:
chat_content = chat_content + "\n" + f"现在,最新的消息是:{message_content}" chat_content = chat_content + "\n" + f"现在,最新的消息是:{message_content}"
# === 第一阶段:传统观察处理 === # === 第一阶段:传统观察处理 ===
if loop_info: # if history_loop:
removals_from_loop = await self.analyze_loop_actions(loop_info) # removals_from_loop = await self.analyze_loop_actions(history_loop)
if removals_from_loop: # if removals_from_loop:
removals_s1.extend(removals_from_loop) # removals_s1.extend(removals_from_loop)
# 检查动作的关联类型 # 检查动作的关联类型
chat_context = self.chat_stream.context chat_context = self.chat_stream.context
@@ -109,12 +108,11 @@ class ActionModifier:
logger.debug(f"{self.log_prefix}开始激活类型判定阶段") logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
# 获取当前使用的动作集(经过第一阶段处理) # 获取当前使用的动作集(经过第一阶段处理)
current_using_actions = self.action_manager.get_using_actions_for_mode(mode) current_using_actions = self.action_manager.get_using_actions()
# 获取因激活类型判定而需要移除的动作 # 获取因激活类型判定而需要移除的动作
removals_s2 = await self._get_deactivated_actions_by_type( removals_s2 = await self._get_deactivated_actions_by_type(
current_using_actions, current_using_actions,
mode,
chat_content, chat_content,
) )
@@ -129,7 +127,7 @@ class ActionModifier:
removals_summary = " | ".join([f"{name}({reason})" for name, reason in all_removals]) removals_summary = " | ".join([f"{name}({reason})" for name, reason in all_removals])
logger.info( logger.info(
f"{self.log_prefix}{mode}模式动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions_for_mode(mode).keys())}||移除记录: {removals_summary}" f"{self.log_prefix} 动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions().keys())}||移除记录: {removals_summary}"
) )
def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext): def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext):
@@ -144,8 +142,7 @@ class ActionModifier:
async def _get_deactivated_actions_by_type( async def _get_deactivated_actions_by_type(
self, self,
actions_with_info: Dict[str, ActionInfo], actions_with_info: Dict[str, Any],
mode: ChatMode = ChatMode.FOCUS,
chat_content: str = "", chat_content: str = "",
) -> List[tuple[str, str]]: ) -> List[tuple[str, str]]:
""" """
@@ -167,9 +164,11 @@ class ActionModifier:
random.shuffle(actions_to_check) random.shuffle(actions_to_check)
for action_name, action_info in actions_to_check: for action_name, action_info in actions_to_check:
mode_activation_type = f"{mode}_activation_type" activation_type = action_info.get("activation_type", "")
activation_type = getattr(action_info, mode_activation_type, ActionActivationType.ALWAYS) if not activation_type:
if activation_type == ActionActivationType.ALWAYS: activation_type = action_info.get("focus_activation_type", "")
if activation_type == "always":
continue # 总是激活,无需处理 continue # 总是激活,无需处理
elif activation_type == ActionActivationType.RANDOM: elif activation_type == ActionActivationType.RANDOM:
@@ -189,6 +188,11 @@ class ActionModifier:
elif activation_type == ActionActivationType.LLM_JUDGE: elif activation_type == ActionActivationType.LLM_JUDGE:
llm_judge_actions[action_name] = action_info llm_judge_actions[action_name] = action_info
elif activation_type == "never":
reason = "激活类型为never"
deactivated_actions.append((action_name, reason))
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: 激活类型为never")
else: else:
logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理") logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理")
@@ -434,7 +438,7 @@ class ActionModifier:
logger.debug(f"{self.log_prefix}动作 {action_name} 未匹配到任何关键词: {activation_keywords}") logger.debug(f"{self.log_prefix}动作 {action_name} 未匹配到任何关键词: {activation_keywords}")
return False return False
async def analyze_loop_actions(self, obs: FocusLoopInfo) -> List[tuple[str, str]]: async def analyze_loop_actions(self, history_loop: List[CycleDetail]) -> List[tuple[str, str]]:
"""分析最近的循环内容并决定动作的移除 """分析最近的循环内容并决定动作的移除
Returns: Returns:
@@ -444,7 +448,7 @@ class ActionModifier:
removals = [] removals = []
# 获取最近10次循环 # 获取最近10次循环
recent_cycles = obs.history_loop[-10:] if len(obs.history_loop) > 10 else obs.history_loop recent_cycles = history_loop[-10:] if len(history_loop) > 10 else history_loop
if not recent_cycles: if not recent_cycles:
return removals return removals
@@ -501,16 +505,24 @@ class ActionModifier:
return removals return removals
def get_available_actions_count(self) -> int: def get_available_actions_count(self, mode: str = "focus") -> int:
"""获取当前可用动作数量排除默认的no_action""" """获取当前可用动作数量排除默认的no_action"""
current_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL) current_actions = self.action_manager.get_using_actions_for_mode(mode)
# 排除no_action如果存在 # 排除no_action如果存在
filtered_actions = {k: v for k, v in current_actions.items() if k != "no_action"} filtered_actions = {k: v for k, v in current_actions.items() if k != "no_action"}
return len(filtered_actions) return len(filtered_actions)
def should_skip_planning(self) -> bool: def should_skip_planning_for_no_reply(self) -> bool:
"""判断是否应该跳过规划过程""" """判断是否应该跳过规划过程"""
available_count = self.get_available_actions_count() current_actions = self.action_manager.get_using_actions_for_mode("focus")
# 排除no_action如果存在
if len(current_actions) == 1 and "no_reply" in current_actions:
return True
return False
def should_skip_planning_for_no_action(self) -> bool:
"""判断是否应该跳过规划过程"""
available_count = self.action_manager.get_using_actions_for_mode("normal")
if available_count == 0: if available_count == 0:
logger.debug(f"{self.log_prefix} 没有可用动作,跳过规划") logger.debug(f"{self.log_prefix} 没有可用动作,跳过规划")
return True return True

View File

@@ -67,20 +67,19 @@ def init_prompt():
class ActionPlanner: class ActionPlanner:
def __init__(self, chat_id: str, action_manager: ActionManager, mode: ChatMode = ChatMode.FOCUS): def __init__(self, chat_id: str, action_manager: ActionManager):
self.chat_id = chat_id self.chat_id = chat_id
self.log_prefix = f"[{get_chat_manager().get_stream_name(chat_id) or chat_id}]" self.log_prefix = f"[{get_chat_manager().get_stream_name(chat_id) or chat_id}]"
self.mode = mode
self.action_manager = action_manager self.action_manager = action_manager
# LLM规划器配置 # LLM规划器配置
self.planner_llm = LLMRequest( self.planner_llm = LLMRequest(
model=global_config.model.planner, model=global_config.model.planner,
request_type=f"{self.mode.value}.planner", # 用于动作规划 request_type="planner", # 用于动作规划
) )
self.last_obs_time_mark = 0.0 self.last_obs_time_mark = 0.0
async def plan(self) -> Dict[str, Any]: # sourcery skip: dict-comprehension async def plan(self, mode: str = "focus") -> Dict[str, Any]: # sourcery skip: dict-comprehension
""" """
规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。
""" """
@@ -96,7 +95,7 @@ class ActionPlanner:
is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id) is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id)
logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}") logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}")
current_available_actions_dict = self.action_manager.get_using_actions_for_mode(self.mode) current_available_actions_dict = self.action_manager.get_using_actions()
# 获取完整的动作信息 # 获取完整的动作信息
all_registered_actions = self.action_manager.get_registered_actions() all_registered_actions = self.action_manager.get_registered_actions()
@@ -130,6 +129,7 @@ class ActionPlanner:
is_group_chat=is_group_chat, # <-- Pass HFC state is_group_chat=is_group_chat, # <-- Pass HFC state
chat_target_info=chat_target_info, # <-- 传递获取到的聊天目标信息 chat_target_info=chat_target_info, # <-- 传递获取到的聊天目标信息
current_available_actions=current_available_actions, # <-- Pass determined actions current_available_actions=current_available_actions, # <-- Pass determined actions
mode=mode,
) )
# --- 调用 LLM (普通文本生成) --- # --- 调用 LLM (普通文本生成) ---
@@ -178,7 +178,7 @@ class ActionPlanner:
if action == "no_action": if action == "no_action":
reasoning = "normal决定不使用额外动作" reasoning = "normal决定不使用额外动作"
elif action not in current_available_actions: elif action != "no_reply" and action != "reply" and action not in current_available_actions:
logger.warning( logger.warning(
f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'" f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'"
) )
@@ -219,6 +219,7 @@ class ActionPlanner:
is_group_chat: bool, # Now passed as argument is_group_chat: bool, # Now passed as argument
chat_target_info: Optional[dict], # Now passed as argument chat_target_info: Optional[dict], # Now passed as argument
current_available_actions: Dict[str, ActionInfo], current_available_actions: Dict[str, ActionInfo],
mode: str = "focus",
) -> str: # sourcery skip: use-join ) -> str: # sourcery skip: use-join
"""构建 Planner LLM 的提示词 (获取模板并填充数据)""" """构建 Planner LLM 的提示词 (获取模板并填充数据)"""
try: try:
@@ -248,9 +249,25 @@ class ActionPlanner:
self.last_obs_time_mark = time.time() self.last_obs_time_mark = time.time()
if self.mode == ChatMode.FOCUS: if mode == "focus":
by_what = "聊天内容" by_what = "聊天内容"
no_action_block = "" no_action_block = """重要说明1
- 'no_reply' 表示只进行不进行回复,等待合适的回复时机
- 当你刚刚发送了消息没有人回复时选择no_reply
- 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply
动作reply
动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附和
- 有人提到你
- 如果你刚刚进行了回复,不要对同一个话题重复回应
{
"action": "reply",
"reply_to":"你要回复的对方的发言内容,格式:(用户名:发言内容可以为none"
"reason":"回复的原因"
}
"""
else: else:
by_what = "聊天内容和用户的最新消息" by_what = "聊天内容和用户的最新消息"
no_action_block = """重要说明: no_action_block = """重要说明:

View File

@@ -18,7 +18,6 @@ from src.chat.utils.timer_calculator import Timer # <--- Import Timer
from src.chat.utils.utils import get_chat_type_and_target_info from src.chat.utils.utils import get_chat_type_and_target_info
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp
from src.chat.express.expression_selector import expression_selector from src.chat.express.expression_selector import expression_selector
from src.chat.knowledge.knowledge_lib import qa_manager from src.chat.knowledge.knowledge_lib import qa_manager
from src.chat.memory_system.memory_activator import MemoryActivator from src.chat.memory_system.memory_activator import MemoryActivator
@@ -30,6 +29,8 @@ from src.plugin_system.base.component_types import ActionInfo
logger = get_logger("replyer") logger = get_logger("replyer")
ENABLE_S2S_MODE = True
def init_prompt(): def init_prompt():
Prompt("你正在qq群里聊天下面是群里在聊的内容", "chat_target_group1") Prompt("你正在qq群里聊天下面是群里在聊的内容", "chat_target_group1")
@@ -133,33 +134,6 @@ class DefaultReplyer:
return random.choices(population=configs, weights=weights, k=1)[0] return random.choices(population=configs, weights=weights, k=1)[0]
async def _create_thinking_message(self, anchor_message: Optional[MessageRecv], thinking_id: str):
"""创建思考消息 (尝试锚定到 anchor_message)"""
if not anchor_message or not anchor_message.chat_stream:
logger.error(f"{self.log_prefix} 无法创建思考消息,缺少有效的锚点消息或聊天流。")
return None
chat = anchor_message.chat_stream
message_info = anchor_message.message_info
thinking_time_point = parse_thinking_id_to_timestamp(thinking_id)
bot_user_info = UserInfo(
user_id=global_config.bot.qq_account,
user_nickname=global_config.bot.nickname,
platform=message_info.platform,
)
thinking_message = MessageThinking(
message_id=thinking_id,
chat_stream=chat,
bot_user_info=bot_user_info,
reply=anchor_message, # 回复的是锚点消息
thinking_start_time=thinking_time_point,
)
# logger.debug(f"创建思考消息thinking_message{thinking_message}")
await self.heart_fc_sender.register_thinking(thinking_message)
return None
async def generate_reply_with_context( async def generate_reply_with_context(
self, self,
reply_data: Optional[Dict[str, Any]] = None, reply_data: Optional[Dict[str, Any]] = None,
@@ -526,13 +500,13 @@ class DefaultReplyer:
show_actions=True, show_actions=True,
) )
message_list_before_now_half = 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(),
limit=int(global_config.chat.max_context_size * 0.5), limit=int(global_config.chat.max_context_size * 0.33),
) )
chat_talking_prompt_half = build_readable_messages( chat_talking_prompt_short = build_readable_messages(
message_list_before_now_half, message_list_before_short,
replace_bot_name=True, replace_bot_name=True,
merge_messages=False, merge_messages=False,
timestamp_mode="relative", timestamp_mode="relative",
@@ -543,14 +517,14 @@ class DefaultReplyer:
# 并行执行四个构建任务 # 并行执行四个构建任务
task_results = await asyncio.gather( task_results = await asyncio.gather(
self._time_and_run_task( self._time_and_run_task(
self.build_expression_habits(chat_talking_prompt_half, target), "build_expression_habits" self.build_expression_habits(chat_talking_prompt_short, target), "build_expression_habits"
), ),
self._time_and_run_task( self._time_and_run_task(
self.build_relation_info(reply_data, chat_talking_prompt_half), "build_relation_info" self.build_relation_info(reply_data, chat_talking_prompt_short), "build_relation_info"
), ),
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_half, target), "build_memory_block"), self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "build_memory_block"),
self._time_and_run_task( self._time_and_run_task(
self.build_tool_info(chat_talking_prompt_half, reply_data, enable_tool=enable_tool), "build_tool_info" self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "build_tool_info"
), ),
) )
@@ -801,108 +775,6 @@ class DefaultReplyer:
moderation_prompt=moderation_prompt_block, moderation_prompt=moderation_prompt_block,
) )
async def send_response_messages(
self,
anchor_message: Optional[MessageRecv],
response_set: List[Tuple[str, str]],
thinking_id: str = "",
display_message: str = "",
) -> Optional[List[Tuple[str, bool]]]:
# sourcery skip: assign-if-exp, boolean-if-exp-identity, remove-unnecessary-cast
"""发送回复消息 (尝试锚定到 anchor_message),使用 HeartFCSender"""
chat = self.chat_stream
chat_id = self.chat_stream.stream_id
if chat is None:
logger.error(f"{self.log_prefix} 无法发送回复chat_stream 为空。")
return None
if not anchor_message:
logger.error(f"{self.log_prefix} 无法发送回复anchor_message 为空。")
return None
stream_name = get_chat_manager().get_stream_name(chat_id) or chat_id # 获取流名称用于日志
# 检查思考过程是否仍在进行,并获取开始时间
if thinking_id:
# print(f"thinking_id: {thinking_id}")
thinking_start_time = await self.heart_fc_sender.get_thinking_start_time(chat_id, thinking_id)
else:
print("thinking_id is None")
# thinking_id = "ds" + str(round(time.time(), 2))
thinking_start_time = time.time()
if thinking_start_time is None:
logger.error(f"[{stream_name}]replyer思考过程未找到或已结束无法发送回复。")
return None
mark_head = False
# first_bot_msg: Optional[MessageSending] = None
reply_message_ids = [] # 记录实际发送的消息ID
sent_msg_list = []
for i, msg_text in enumerate(response_set):
# 为每个消息片段生成唯一ID
msg_type = msg_text[0]
data = msg_text[1]
if global_config.debug.debug_show_chat_mode and msg_type == "text":
data += ""
part_message_id = f"{thinking_id}_{i}"
message_segment = Seg(type=msg_type, data=data)
if msg_type == "emoji":
is_emoji = True
else:
is_emoji = False
reply_to = not mark_head
bot_message: MessageSending = await self._build_single_sending_message(
anchor_message=anchor_message,
message_id=part_message_id,
message_segment=message_segment,
display_message=display_message,
reply_to=reply_to,
is_emoji=is_emoji,
thinking_start_time=thinking_start_time,
)
try:
if (
bot_message.is_private_message()
or bot_message.reply.processed_plain_text != "[System Trigger Context]" # type: ignore
or mark_head
):
set_reply = False
else:
set_reply = True
if not mark_head:
mark_head = True
typing = False
else:
typing = True
sent_msg = await self.heart_fc_sender.send_message(bot_message, typing=typing, set_reply=set_reply)
reply_message_ids.append(part_message_id) # 记录我们生成的ID
sent_msg_list.append((msg_type, sent_msg))
except Exception as e:
logger.error(f"{self.log_prefix}发送回复片段 {i} ({part_message_id}) 时失败: {e}")
traceback.print_exc()
# 这里可以选择是继续发送下一个片段还是中止
# 在尝试发送完所有片段后,完成原始的 thinking_id 状态
try:
await self.heart_fc_sender.complete_thinking(chat_id, thinking_id)
except Exception as e:
logger.error(f"{self.log_prefix}完成思考状态 {thinking_id} 时出错: {e}")
return sent_msg_list
async def _build_single_sending_message( async def _build_single_sending_message(
self, self,
message_id: str, message_id: str,

View File

@@ -30,7 +30,12 @@ def get_raw_msg_by_timestamp(
def get_raw_msg_by_timestamp_with_chat( def get_raw_msg_by_timestamp_with_chat(
chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" chat_id: str,
timestamp_start: float,
timestamp_end: float,
limit: int = 0,
limit_mode: str = "latest",
fliter_bot=False,
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""获取在特定聊天从指定时间戳到指定时间戳的消息,按时间升序排序,返回消息列表 """获取在特定聊天从指定时间戳到指定时间戳的消息,按时间升序排序,返回消息列表
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
@@ -40,11 +45,18 @@ def get_raw_msg_by_timestamp_with_chat(
# 只有当 limit 为 0 时才应用外部 sort # 只有当 limit 为 0 时才应用外部 sort
sort_order = [("time", 1)] if limit == 0 else None sort_order = [("time", 1)] if limit == 0 else None
# 直接将 limit_mode 传递给 find_messages # 直接将 limit_mode 传递给 find_messages
return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode) return find_messages(
message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot
)
def get_raw_msg_by_timestamp_with_chat_inclusive( def get_raw_msg_by_timestamp_with_chat_inclusive(
chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" chat_id: str,
timestamp_start: float,
timestamp_end: float,
limit: int = 0,
limit_mode: str = "latest",
fliter_bot=False,
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""获取在特定聊天从指定时间戳到指定时间戳的消息(包含边界),按时间升序排序,返回消息列表 """获取在特定聊天从指定时间戳到指定时间戳的消息(包含边界),按时间升序排序,返回消息列表
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
@@ -54,7 +66,10 @@ def get_raw_msg_by_timestamp_with_chat_inclusive(
# 只有当 limit 为 0 时才应用外部 sort # 只有当 limit 为 0 时才应用外部 sort
sort_order = [("time", 1)] if limit == 0 else None sort_order = [("time", 1)] if limit == 0 else None
# 直接将 limit_mode 传递给 find_messages # 直接将 limit_mode 传递给 find_messages
return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode)
return find_messages(
message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot
)
def get_raw_msg_by_timestamp_with_chat_users( def get_raw_msg_by_timestamp_with_chat_users(
@@ -580,6 +595,9 @@ def build_readable_actions(actions: List[Dict[str, Any]]) -> str:
for action in actions: for action in actions:
action_time = action.get("time", current_time) action_time = action.get("time", current_time)
action_name = action.get("action_name", "未知动作") action_name = action.get("action_name", "未知动作")
if action_name == "no_action" or action_name == "no_reply":
continue
action_prompt_display = action.get("action_prompt_display", "无具体内容") action_prompt_display = action.get("action_prompt_display", "无具体内容")
time_diff_seconds = current_time - action_time time_diff_seconds = current_time - action_time

View File

@@ -93,7 +93,7 @@ class BaseWillingManager(ABC):
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
self.logger = logger self.logger = logger
def setup(self, message: MessageRecv, chat: ChatStream, is_mentioned_bot: bool, interested_rate: float): def setup(self, message: dict, chat: ChatStream):
person_id = PersonInfoManager.get_person_id(chat.platform, chat.user_info.user_id) # type: ignore person_id = PersonInfoManager.get_person_id(chat.platform, chat.user_info.user_id) # type: ignore
self.ongoing_messages[message.message_info.message_id] = WillingInfo( # type: ignore self.ongoing_messages[message.message_info.message_id] = WillingInfo( # type: ignore
message=message, message=message,
@@ -102,10 +102,10 @@ class BaseWillingManager(ABC):
chat_id=chat.stream_id, chat_id=chat.stream_id,
person_id=person_id, person_id=person_id,
group_info=chat.group_info, group_info=chat.group_info,
is_mentioned_bot=is_mentioned_bot, is_mentioned_bot=message.get("is_mentioned_bot", False),
is_emoji=message.is_emoji, is_emoji=message.get("is_emoji", False),
is_picid=message.is_picid, is_picid=message.get("is_picid", False),
interested_rate=interested_rate, interested_rate=message.get("interested_rate", 0),
) )
def delete(self, message_id: str): def delete(self, message_id: str):

View File

@@ -130,6 +130,7 @@ class Messages(BaseModel):
reply_to = TextField(null=True) reply_to = TextField(null=True)
interest_value = DoubleField(null=True) interest_value = DoubleField(null=True)
is_mentioned = BooleanField(null=True)
# 从 chat_info 扁平化而来的字段 # 从 chat_info 扁平化而来的字段
chat_info_stream_id = TextField() chat_info_stream_id = TextField()
@@ -155,6 +156,13 @@ class Messages(BaseModel):
detailed_plain_text = TextField(null=True) # 详细的纯文本消息 detailed_plain_text = TextField(null=True) # 详细的纯文本消息
memorized_times = IntegerField(default=0) # 被记忆的次数 memorized_times = IntegerField(default=0) # 被记忆的次数
priority_mode = TextField(null=True)
priority_info = TextField(null=True)
additional_config = TextField(null=True)
is_emoji = BooleanField(default=False)
is_picid = BooleanField(default=False)
class Meta: class Meta:
# database = db # 继承自 BaseModel # database = db # 继承自 BaseModel
table_name = "messages" table_name = "messages"

View File

@@ -2,6 +2,7 @@ import traceback
from typing import List, Any, Optional from typing import List, Any, Optional
from peewee import Model # 添加 Peewee Model 导入 from peewee import Model # 添加 Peewee Model 导入
from src.config.config import global_config
from src.common.database.database_model import Messages from src.common.database.database_model import Messages
from src.common.logger import get_logger from src.common.logger import get_logger
@@ -21,6 +22,7 @@ def find_messages(
sort: Optional[List[tuple[str, int]]] = None, sort: Optional[List[tuple[str, int]]] = None,
limit: int = 0, limit: int = 0,
limit_mode: str = "latest", limit_mode: str = "latest",
fliter_bot=False,
) -> List[dict[str, Any]]: ) -> List[dict[str, Any]]:
""" """
根据提供的过滤器、排序和限制条件查找消息。 根据提供的过滤器、排序和限制条件查找消息。
@@ -70,6 +72,9 @@ def find_messages(
if conditions: if conditions:
query = query.where(*conditions) query = query.where(*conditions)
if fliter_bot:
query = query.where(Messages.user_id != global_config.bot.qq_account)
if limit > 0: if limit > 0:
if limit_mode == "earliest": if limit_mode == "earliest":
# 获取时间最早的 limit 条记录,已经是正序 # 获取时间最早的 limit 条记录,已经是正序

View File

@@ -49,7 +49,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.8.2-snapshot.1" MMC_VERSION = "0.9.0-snapshot.1"
def update_config(): def update_config():

View File

@@ -49,7 +49,7 @@ class IdentityConfig(ConfigBase):
identity_detail: list[str] = field(default_factory=lambda: []) identity_detail: list[str] = field(default_factory=lambda: [])
"""身份特征""" """身份特征"""
compress_indentity: bool = True compress_identity: bool = True
"""是否压缩身份压缩后会精简身份信息节省token消耗并提高回复性能但是会丢失一些信息如果不长可以关闭""" """是否压缩身份压缩后会精简身份信息节省token消耗并提高回复性能但是会丢失一些信息如果不长可以关闭"""
@@ -68,9 +68,6 @@ class RelationshipConfig(ConfigBase):
class ChatConfig(ConfigBase): class ChatConfig(ConfigBase):
"""聊天配置类""" """聊天配置类"""
chat_mode: str = "normal"
"""聊天模式"""
max_context_size: int = 18 max_context_size: int = 18
"""上下文长度""" """上下文长度"""
@@ -281,17 +278,11 @@ class NormalChatConfig(ConfigBase):
at_bot_inevitable_reply: bool = False at_bot_inevitable_reply: bool = False
"""@bot 必然回复""" """@bot 必然回复"""
enable_planner: bool = False
"""是否启用动作规划器"""
@dataclass @dataclass
class FocusChatConfig(ConfigBase): class FocusChatConfig(ConfigBase):
"""专注聊天配置类""" """专注聊天配置类"""
think_interval: float = 1
"""思考间隔(秒)"""
consecutive_replies: float = 1 consecutive_replies: float = 1
"""连续回复能力,值越高,麦麦连续回复的概率越高""" """连续回复能力,值越高,麦麦连续回复的概率越高"""
@@ -530,9 +521,6 @@ class TelemetryConfig(ConfigBase):
class DebugConfig(ConfigBase): class DebugConfig(ConfigBase):
"""调试配置类""" """调试配置类"""
debug_show_chat_mode: bool = False
"""是否在回复后显示当前聊天模式"""
show_prompt: bool = False show_prompt: bool = False
"""是否显示prompt""" """是否显示prompt"""

View File

@@ -10,7 +10,6 @@ from typing import Dict, Any, Optional
from src.chat.message_receive.message import Message from src.chat.message_receive.message import Message
from .pfc_types import ConversationState from .pfc_types import ConversationState
from .pfc import ChatObserver, GoalAnalyzer from .pfc import ChatObserver, GoalAnalyzer
from .message_sender import DirectMessageSender
from src.common.logger import get_logger from src.common.logger import get_logger
from .action_planner import ActionPlanner from .action_planner import ActionPlanner
from .observation_info import ObservationInfo from .observation_info import ObservationInfo

View File

@@ -1,81 +0,0 @@
import time
from typing import Optional
from src.common.logger import get_logger
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.message_receive.message import Message
from maim_message import UserInfo, Seg
from src.chat.message_receive.message import MessageSending, MessageSet
from src.chat.message_receive.normal_message_sender import message_manager
from src.chat.message_receive.storage import MessageStorage
from src.config.config import global_config
from rich.traceback import install
install(extra_lines=3)
logger = get_logger("message_sender")
class DirectMessageSender:
"""直接消息发送器"""
def __init__(self, private_name: str):
self.private_name = private_name
self.storage = MessageStorage()
async def send_message(
self,
chat_stream: ChatStream,
content: str,
reply_to_message: Optional[Message] = None,
) -> None:
"""发送消息到聊天流
Args:
chat_stream: 聊天流
content: 消息内容
reply_to_message: 要回复的消息(可选)
"""
try:
# 创建消息内容
segments = Seg(type="seglist", data=[Seg(type="text", data=content)])
# 获取麦麦的信息
bot_user_info = UserInfo(
user_id=global_config.bot.qq_account,
user_nickname=global_config.bot.nickname,
platform=chat_stream.platform,
)
# 用当前时间作为message_id和之前那套sender一样
message_id = f"dm{round(time.time(), 2)}"
# 构建消息对象
message = MessageSending(
message_id=message_id,
chat_stream=chat_stream,
bot_user_info=bot_user_info,
sender_info=reply_to_message.message_info.user_info if reply_to_message else None,
message_segment=segments,
reply=reply_to_message,
is_head=True,
is_emoji=False,
thinking_start_time=time.time(),
)
# 处理消息
await message.process()
# 不知道有什么用先留下来了和之前那套sender一样
_message_json = message.to_dict()
# 发送消息
message_set = MessageSet(chat_stream, message_id)
message_set.add_message(message)
await message_manager.add_message(message_set)
await self.storage.store_message(message, chat_stream)
logger.info(f"[私聊][{self.private_name}]PFC消息已发送: {content}")
except Exception as e:
logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}")
raise

View File

@@ -337,7 +337,7 @@ class Individuality:
# 身份配置哈希 # 身份配置哈希
identity_config = { identity_config = {
"identity_detail": sorted(identity_detail), "identity_detail": sorted(identity_detail),
"compress_identity": global_config.identity.compress_indentity, "compress_identity": global_config.identity.compress_identity,
} }
identity_str = json.dumps(identity_config, sort_keys=True) identity_str = json.dumps(identity_config, sort_keys=True)
identity_hash = hashlib.md5(identity_str.encode("utf-8")).hexdigest() identity_hash = hashlib.md5(identity_str.encode("utf-8")).hexdigest()
@@ -507,7 +507,7 @@ class Individuality:
"""使用LLM创建压缩版本的impression""" """使用LLM创建压缩版本的impression"""
logger.info("正在构建身份.........") logger.info("正在构建身份.........")
if global_config.identity.compress_indentity: if global_config.identity.compress_identity:
identity_to_compress = [] identity_to_compress = []
if identity_detail: if identity_detail:
identity_to_compress.append(f"身份背景: {''.join(identity_detail)}") identity_to_compress.append(f"身份背景: {''.join(identity_detail)}")

View File

@@ -7,9 +7,8 @@ from src.common.remote import TelemetryHeartBeatTask
from src.manager.async_task_manager import async_task_manager from src.manager.async_task_manager import async_task_manager
from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask
from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.emoji_system.emoji_manager import get_emoji_manager
from src.chat.normal_chat.willing.willing_manager import get_willing_manager from src.chat.willing.willing_manager import get_willing_manager
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from src.chat.message_receive.normal_message_sender import message_manager
from src.chat.message_receive.storage import MessageStorage from src.chat.message_receive.storage import MessageStorage
from src.config.config import global_config from src.config.config import global_config
from src.chat.message_receive.bot import chat_bot from src.chat.message_receive.bot import chat_bot
@@ -23,9 +22,6 @@ from rich.traceback import install
# 导入新的插件管理器 # 导入新的插件管理器
from src.plugin_system.core.plugin_manager import plugin_manager from src.plugin_system.core.plugin_manager import plugin_manager
# 导入HFC性能记录器用于日志清理
from src.chat.focus_chat.hfc_performance_logger import HFCPerformanceLogger
# 导入消息API和traceback模块 # 导入消息API和traceback模块
from src.common.message import get_global_api from src.common.message import get_global_api
@@ -69,11 +65,6 @@ class MainSystem:
"""初始化其他组件""" """初始化其他组件"""
init_start_time = time.time() init_start_time = time.time()
# 清理HFC旧日志文件保持目录大小在50MB以内
logger.info("开始清理HFC旧日志文件...")
HFCPerformanceLogger.cleanup_old_logs(max_size_mb=50.0)
logger.info("HFC日志清理完成")
# 添加在线时间统计任务 # 添加在线时间统计任务
await async_task_manager.add_task(OnlineTimeRecordTask()) await async_task_manager.add_task(OnlineTimeRecordTask())
@@ -134,10 +125,6 @@ class MainSystem:
logger.info("个体特征初始化成功") logger.info("个体特征初始化成功")
try: try:
# 启动全局消息管理器 (负责消息发送/排队)
await message_manager.start()
logger.info("全局消息管理器启动成功")
init_time = int(1000 * (time.time() - init_start_time)) init_time = int(1000 * (time.time() - init_start_time))
logger.info(f"初始化完成,神经元放电{init_time}") logger.info(f"初始化完成,神经元放电{init_time}")
except Exception as e: except Exception as e:

View File

@@ -38,7 +38,7 @@ def init_prompt():
现在,发送了消息,引起了你的注意,你对其进行了阅读和思考,请你输出一句话描述你新的情绪状态,不要输出任何其他内容 现在,发送了消息,引起了你的注意,你对其进行了阅读和思考,请你输出一句话描述你新的情绪状态,不要输出任何其他内容
请只输出情绪状态,不要输出其他内容: 请只输出情绪状态,不要输出其他内容:
""", """,
"change_mood_prompt", "change_mood_prompt_vtb",
) )
Prompt( Prompt(
""" """
@@ -51,7 +51,7 @@ def init_prompt():
距离你上次关注直播间消息已经过去了一段时间,你冷静了下来,请你输出一句话描述你现在的情绪状态 距离你上次关注直播间消息已经过去了一段时间,你冷静了下来,请你输出一句话描述你现在的情绪状态
请只输出情绪状态,不要输出其他内容: 请只输出情绪状态,不要输出其他内容:
""", """,
"regress_mood_prompt", "regress_mood_prompt_vtb",
) )
Prompt( Prompt(
""" """
@@ -183,7 +183,7 @@ class ChatMood:
async def _update_text_mood(): async def _update_text_mood():
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
"change_mood_prompt", "change_mood_prompt_vtb",
chat_talking_prompt=chat_talking_prompt, chat_talking_prompt=chat_talking_prompt,
indentify_block=indentify_block, indentify_block=indentify_block,
mood_state=self.mood_state, mood_state=self.mood_state,
@@ -257,7 +257,7 @@ class ChatMood:
async def _regress_text_mood(): async def _regress_text_mood():
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
"regress_mood_prompt", "regress_mood_prompt_vtb",
chat_talking_prompt=chat_talking_prompt, chat_talking_prompt=chat_talking_prompt,
indentify_block=indentify_block, indentify_block=indentify_block,
mood_state=self.mood_state, mood_state=self.mood_state,

View File

@@ -70,7 +70,7 @@ class ChatMood:
else: else:
interest_multiplier = 3 * math.pow(interested_rate, 0.25) interest_multiplier = 3 * math.pow(interested_rate, 0.25)
logger.info( logger.debug(
f"base_probability: {base_probability}, time_multiplier: {time_multiplier}, interest_multiplier: {interest_multiplier}" f"base_probability: {base_probability}, time_multiplier: {time_multiplier}, interest_multiplier: {interest_multiplier}"
) )
update_probability = min(1.0, base_probability * time_multiplier * interest_multiplier) update_probability = min(1.0, base_probability * time_multiplier * interest_multiplier)
@@ -78,6 +78,8 @@ class ChatMood:
if random.random() > update_probability: if random.random() > update_probability:
return return
logger.info(f"更新情绪状态,感兴趣度: {interested_rate}, 更新概率: {update_probability}")
message_time: float = message.message_info.time # type: ignore message_time: float = message.message_info.time # type: ignore
message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive( message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive(
chat_id=self.chat_id, chat_id=self.chat_id,

View File

@@ -9,6 +9,7 @@
""" """
from typing import List, Dict, Any, Tuple, Optional from typing import List, Dict, Any, Tuple, Optional
from src.config.config import global_config
import time import time
from src.chat.utils.chat_message_builder import ( from src.chat.utils.chat_message_builder import (
get_raw_msg_by_timestamp, get_raw_msg_by_timestamp,
@@ -34,7 +35,7 @@ from src.chat.utils.chat_message_builder import (
def get_messages_by_time( def get_messages_by_time(
start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
获取指定时间范围内的消息 获取指定时间范围内的消息
@@ -44,15 +45,23 @@ def get_messages_by_time(
end_time: 结束时间戳 end_time: 结束时间戳
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录 limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录
filter_mai: 是否过滤麦麦自身的消息默认为False
Returns: Returns:
消息列表 消息列表
""" """
if filter_mai:
return filter_mai_messages(get_raw_msg_by_timestamp(start_time, end_time, limit, limit_mode))
return get_raw_msg_by_timestamp(start_time, end_time, limit, limit_mode) return get_raw_msg_by_timestamp(start_time, end_time, limit, limit_mode)
def get_messages_by_time_in_chat( def get_messages_by_time_in_chat(
chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" chat_id: str,
start_time: float,
end_time: float,
limit: int = 0,
limit_mode: str = "latest",
filter_mai: bool = False,
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
获取指定聊天中指定时间范围内的消息 获取指定聊天中指定时间范围内的消息
@@ -63,15 +72,23 @@ def get_messages_by_time_in_chat(
end_time: 结束时间戳 end_time: 结束时间戳
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录 limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录
filter_mai: 是否过滤麦麦自身的消息默认为False
Returns: Returns:
消息列表 消息列表
""" """
if filter_mai:
return filter_mai_messages(get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time, limit, limit_mode))
return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time, limit, limit_mode) return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time, limit, limit_mode)
def get_messages_by_time_in_chat_inclusive( def get_messages_by_time_in_chat_inclusive(
chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" chat_id: str,
start_time: float,
end_time: float,
limit: int = 0,
limit_mode: str = "latest",
filter_mai: bool = False,
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
获取指定聊天中指定时间范围内的消息(包含边界) 获取指定聊天中指定时间范围内的消息(包含边界)
@@ -82,10 +99,15 @@ def get_messages_by_time_in_chat_inclusive(
end_time: 结束时间戳(包含) end_time: 结束时间戳(包含)
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录 limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录
filter_mai: 是否过滤麦麦自身的消息默认为False
Returns: Returns:
消息列表 消息列表
""" """
if filter_mai:
return filter_mai_messages(
get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode)
)
return get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode) return get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode)
@@ -115,7 +137,7 @@ def get_messages_by_time_in_chat_for_users(
def get_random_chat_messages( def get_random_chat_messages(
start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
随机选择一个聊天,返回该聊天在指定时间范围内的消息 随机选择一个聊天,返回该聊天在指定时间范围内的消息
@@ -125,10 +147,13 @@ def get_random_chat_messages(
end_time: 结束时间戳 end_time: 结束时间戳
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录 limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录
filter_mai: 是否过滤麦麦自身的消息默认为False
Returns: Returns:
消息列表 消息列表
""" """
if filter_mai:
return filter_mai_messages(get_raw_msg_by_timestamp_random(start_time, end_time, limit, limit_mode))
return get_raw_msg_by_timestamp_random(start_time, end_time, limit, limit_mode) return get_raw_msg_by_timestamp_random(start_time, end_time, limit, limit_mode)
@@ -151,21 +176,26 @@ def get_messages_by_time_for_users(
return get_raw_msg_by_timestamp_with_users(start_time, end_time, person_ids, limit, limit_mode) return get_raw_msg_by_timestamp_with_users(start_time, end_time, person_ids, limit, limit_mode)
def get_messages_before_time(timestamp: float, limit: int = 0) -> List[Dict[str, Any]]: def get_messages_before_time(timestamp: float, limit: int = 0, filter_mai: bool = False) -> List[Dict[str, Any]]:
""" """
获取指定时间戳之前的消息 获取指定时间戳之前的消息
Args: Args:
timestamp: 时间戳 timestamp: 时间戳
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
filter_mai: 是否过滤麦麦自身的消息默认为False
Returns: Returns:
消息列表 消息列表
""" """
if filter_mai:
return filter_mai_messages(get_raw_msg_before_timestamp(timestamp, limit))
return get_raw_msg_before_timestamp(timestamp, limit) return get_raw_msg_before_timestamp(timestamp, limit)
def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int = 0) -> List[Dict[str, Any]]: def get_messages_before_time_in_chat(
chat_id: str, timestamp: float, limit: int = 0, filter_mai: bool = False
) -> List[Dict[str, Any]]:
""" """
获取指定聊天中指定时间戳之前的消息 获取指定聊天中指定时间戳之前的消息
@@ -173,10 +203,13 @@ def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int
chat_id: 聊天ID chat_id: 聊天ID
timestamp: 时间戳 timestamp: 时间戳
limit: 限制返回的消息数量0为不限制 limit: 限制返回的消息数量0为不限制
filter_mai: 是否过滤麦麦自身的消息默认为False
Returns: Returns:
消息列表 消息列表
""" """
if filter_mai:
return filter_mai_messages(get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit))
return get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit) return get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit)
@@ -196,7 +229,7 @@ def get_messages_before_time_for_users(timestamp: float, person_ids: list, limit
def get_recent_messages( def get_recent_messages(
chat_id: str, hours: float = 24.0, limit: int = 100, limit_mode: str = "latest" chat_id: str, hours: float = 24.0, limit: int = 100, limit_mode: str = "latest", filter_mai: bool = False
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
获取指定聊天中最近一段时间的消息 获取指定聊天中最近一段时间的消息
@@ -206,12 +239,15 @@ def get_recent_messages(
hours: 最近多少小时默认24小时 hours: 最近多少小时默认24小时
limit: 限制返回的消息数量默认100条 limit: 限制返回的消息数量默认100条
limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录 limit_mode: 当limit>0时生效'earliest'表示获取最早的记录,'latest'表示获取最新的记录
filter_mai: 是否过滤麦麦自身的消息默认为False
Returns: Returns:
消息列表 消息列表
""" """
now = time.time() now = time.time()
start_time = now - hours * 3600 start_time = now - hours * 3600
if filter_mai:
return filter_mai_messages(get_raw_msg_by_timestamp_with_chat(chat_id, start_time, now, limit, limit_mode))
return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, now, limit, limit_mode) return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, now, limit, limit_mode)
@@ -319,3 +355,19 @@ async def get_person_ids_from_messages(messages: List[Dict[str, Any]]) -> List[s
用户ID列表 用户ID列表
""" """
return await get_person_id_list(messages) return await get_person_id_list(messages)
# =============================================================================
# 消息过滤函数
# =============================================================================
def filter_mai_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
从消息列表中移除麦麦的消息
Args:
messages: 消息列表,每个元素是消息字典
Returns:
过滤后的消息列表
"""
return [msg for msg in messages if msg.get("user_id") != str(global_config.bot.qq_account)]

View File

@@ -34,7 +34,6 @@ class BaseAction(ABC):
thinking_id: str, thinking_id: str,
chat_stream: ChatStream, chat_stream: ChatStream,
log_prefix: str = "", log_prefix: str = "",
shutting_down: bool = False,
plugin_config: Optional[dict] = None, plugin_config: Optional[dict] = None,
**kwargs, **kwargs,
): ):
@@ -60,7 +59,6 @@ class BaseAction(ABC):
self.cycle_timers = cycle_timers self.cycle_timers = cycle_timers
self.thinking_id = thinking_id self.thinking_id = thinking_id
self.log_prefix = log_prefix self.log_prefix = log_prefix
self.shutting_down = shutting_down
# 保存插件配置 # 保存插件配置
self.plugin_config = plugin_config or {} self.plugin_config = plugin_config or {}

View File

@@ -24,7 +24,7 @@ class NoReplyAction(BaseAction):
2. 累计新消息数量达到随机阈值 (默认5-10条) 则结束等待 2. 累计新消息数量达到随机阈值 (默认5-10条) 则结束等待
""" """
focus_activation_type = ActionActivationType.ALWAYS focus_activation_type = ActionActivationType.NEVER
normal_activation_type = ActionActivationType.NEVER normal_activation_type = ActionActivationType.NEVER
mode_enable = ChatMode.FOCUS mode_enable = ChatMode.FOCUS
parallel_action = False parallel_action = False
@@ -61,8 +61,8 @@ class NoReplyAction(BaseAction):
count = NoReplyAction._consecutive_count count = NoReplyAction._consecutive_count
reason = self.action_data.get("reason", "") reason = self.action_data.get("reason", "")
start_time = time.time() start_time = self.action_data.get("loop_start_time", time.time())
check_interval = 1.0 # 每秒检查一次 check_interval = 0.6 # 每秒检查一次
# 随机生成本次等待需要的新消息数量阈值 # 随机生成本次等待需要的新消息数量阈值
exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count) exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count)

View File

@@ -34,7 +34,7 @@ class ReplyAction(BaseAction):
"""回复动作 - 参与聊天回复""" """回复动作 - 参与聊天回复"""
# 激活设置 # 激活设置
focus_activation_type = ActionActivationType.ALWAYS focus_activation_type = ActionActivationType.NEVER
normal_activation_type = ActionActivationType.NEVER normal_activation_type = ActionActivationType.NEVER
mode_enable = ChatMode.FOCUS mode_enable = ChatMode.FOCUS
parallel_action = False parallel_action = False
@@ -98,7 +98,7 @@ class ReplyAction(BaseAction):
) )
# 根据新消息数量决定是否使用reply_to # 根据新消息数量决定是否使用reply_to
need_reply = new_message_count >= random.randint(2, 5) need_reply = new_message_count >= random.randint(2, 4)
logger.info( logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复" f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复"
) )

View File

@@ -10,8 +10,7 @@
"license": "GPL-v3.0-or-later", "license": "GPL-v3.0-or-later",
"host_application": { "host_application": {
"min_version": "0.8.0", "min_version": "0.8.0"
"max_version": "0.8.10"
}, },
"homepage_url": "https://github.com/MaiM-with-u/maibot", "homepage_url": "https://github.com/MaiM-with-u/maibot",
"repository_url": "https://github.com/MaiM-with-u/maibot", "repository_url": "https://github.com/MaiM-with-u/maibot",

View File

@@ -9,8 +9,7 @@
}, },
"license": "GPL-v3.0-or-later", "license": "GPL-v3.0-or-later",
"host_application": { "host_application": {
"min_version": "0.8.0", "min_version": "0.8.0"
"max_version": "0.8.10"
}, },
"keywords": ["vtb", "vtuber", "emotion", "expression", "virtual", "streamer"], "keywords": ["vtb", "vtuber", "emotion", "expression", "virtual", "streamer"],
"categories": ["Entertainment", "Virtual Assistant", "Emotion"], "categories": ["Entertainment", "Virtual Assistant", "Emotion"],

View File

@@ -1,5 +1,5 @@
[inner] [inner]
version = "3.7.0" version = "4.0.1"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请在修改后将version的值进行变更 #如果你想要修改配置文件请在修改后将version的值进行变更
@@ -40,7 +40,7 @@ identity_detail = [
"有橙色的短发", "有橙色的短发",
] ]
compress_indentity = true # 是否压缩身份压缩后会精简身份信息节省token消耗并提高回复性能但是会丢失一些信息如果不长可以关闭 compress_identity = true # 是否压缩身份压缩后会精简身份信息节省token消耗并提高回复性能但是会丢失一些信息如果不长可以关闭
[expression] [expression]
# 表达方式 # 表达方式
@@ -62,14 +62,13 @@ enable_relationship = true # 是否启用关系系统
relation_frequency = 1 # 关系频率,麦麦构建关系的频率 relation_frequency = 1 # 关系频率,麦麦构建关系的频率
[chat] #麦麦的聊天通用设置 [chat] #麦麦的聊天通用设置
chat_mode = "normal" # 聊天模式 —— 普通模式normal专注模式focusauto模式在普通模式和专注模式之间自动切换
auto_focus_threshold = 1 # 自动切换到专注聊天的阈值,越低越容易进入专注聊天 auto_focus_threshold = 1 # 自动切换到专注聊天的阈值,越低越容易进入专注聊天
exit_focus_threshold = 1 # 自动退出专注聊天的阈值,越低越容易退出专注聊天 exit_focus_threshold = 1 # 自动退出专注聊天的阈值,越低越容易退出专注聊天
# 普通模式下麦麦会针对感兴趣的消息进行回复token消耗量较低 # 普通模式下麦麦会针对感兴趣的消息进行回复token消耗量较低
# 专注模式下麦麦会进行主动的观察并给出回复token消耗量略高但是回复时机更准确 # 专注模式下麦麦会进行主动的观察并给出回复token消耗量略高但是回复时机更准确
# 自动模式下,麦麦会根据消息内容自动切换到专注模式或普通模式 # 自动模式下,麦麦会根据消息内容自动切换到专注模式或普通模式
max_context_size = 18 # 上下文长度 max_context_size = 25 # 上下文长度
thinking_timeout = 20 # 麦麦一次回复最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢 thinking_timeout = 20 # 麦麦一次回复最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
@@ -119,10 +118,8 @@ willing_mode = "classical" # 回复意愿模式 —— 经典模式classical
response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数 response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数
mentioned_bot_inevitable_reply = true # 提及 bot 必然回复 mentioned_bot_inevitable_reply = true # 提及 bot 必然回复
at_bot_inevitable_reply = true # @bot 必然回复(包含提及) at_bot_inevitable_reply = true # @bot 必然回复(包含提及)
enable_planner = true # 是否启用动作规划器与focus_chat共享actions
[focus_chat] #专注聊天 [focus_chat] #专注聊天
think_interval = 3 # 思考间隔 单位秒,可以有效减少消耗
consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高 consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高
[tool] [tool]
@@ -234,7 +231,6 @@ library_log_levels = { "aiohttp" = "WARNING"} # 设置特定库的日志级别
[debug] [debug]
show_prompt = false # 是否显示prompt show_prompt = false # 是否显示prompt
debug_show_chat_mode = false # 是否在回复后显示当前聊天模式
[model] [model]