This commit is contained in:
Windpicker-owo
2025-09-06 00:11:31 +08:00
44 changed files with 965 additions and 1257 deletions

View File

@@ -7,7 +7,6 @@ from . import event_types, CONSTS, event_handlers
from typing import List from typing import List
from src.plugin_system import BasePlugin, BaseEventHandler, register_plugin, EventType, ConfigField from src.plugin_system import BasePlugin, BaseEventHandler, register_plugin, EventType, ConfigField
from src.plugin_system.base.base_event import HandlerResult
from src.plugin_system.core.event_manager import event_manager from src.plugin_system.core.event_manager import event_manager
from src.common.logger import get_logger from src.common.logger import get_logger
@@ -37,6 +36,7 @@ def get_classes_in_module(module):
classes.append(member) classes.append(member)
return classes return classes
async def message_recv(server_connection: Server.ServerConnection): async def message_recv(server_connection: Server.ServerConnection):
await message_handler.set_server_connection(server_connection) await message_handler.set_server_connection(server_connection)
asyncio.create_task(notice_handler.set_server_connection(server_connection)) asyncio.create_task(notice_handler.set_server_connection(server_connection))
@@ -76,6 +76,7 @@ async def message_recv(server_connection: Server.ServerConnection):
logger.error(f"处理消息时出错: {e}") logger.error(f"处理消息时出错: {e}")
logger.debug(f"原始消息: {raw_message[:500]}...") logger.debug(f"原始消息: {raw_message[:500]}...")
async def message_process(): async def message_process():
"""消息处理主循环""" """消息处理主循环"""
logger.info("消息处理器已启动") logger.info("消息处理器已启动")
@@ -132,6 +133,7 @@ async def message_process():
except Exception as e: except Exception as e:
logger.debug(f"清理消息队列时出错: {e}") logger.debug(f"清理消息队列时出错: {e}")
async def napcat_server(): async def napcat_server():
"""启动 Napcat WebSocket 连接(支持正向和反向连接)""" """启动 Napcat WebSocket 连接(支持正向和反向连接)"""
mode = global_config.napcat_server.mode mode = global_config.napcat_server.mode
@@ -143,6 +145,7 @@ async def napcat_server():
logger.error(f"启动 WebSocket 连接失败: {e}") logger.error(f"启动 WebSocket 连接失败: {e}")
raise raise
async def graceful_shutdown(): async def graceful_shutdown():
"""优雅关闭所有组件""" """优雅关闭所有组件"""
try: try:
@@ -189,10 +192,7 @@ async def graceful_shutdown():
# 等待任务取消完成,忽略 CancelledError # 等待任务取消完成,忽略 CancelledError
try: try:
await asyncio.wait_for( await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=10)
asyncio.gather(*tasks, return_exceptions=True),
timeout=10
)
except asyncio.TimeoutError: except asyncio.TimeoutError:
logger.warning("部分任务取消超时") logger.warning("部分任务取消超时")
except Exception as e: except Exception as e:
@@ -214,6 +214,7 @@ async def graceful_shutdown():
except Exception: except Exception:
pass pass
class LauchNapcatAdapterHandler(BaseEventHandler): class LauchNapcatAdapterHandler(BaseEventHandler):
"""自动启动Adapter""" """自动启动Adapter"""
@@ -245,6 +246,7 @@ class LauchNapcatAdapterHandler(BaseEventHandler):
asyncio.create_task(message_process()) asyncio.create_task(message_process())
asyncio.create_task(check_timeout_response()) asyncio.create_task(check_timeout_response())
class StopNapcatAdapterHandler(BaseEventHandler): class StopNapcatAdapterHandler(BaseEventHandler):
"""关闭Adapter""" """关闭Adapter"""

View File

@@ -58,6 +58,7 @@ class VoiceConfig(ConfigBase):
use_tts: bool = False use_tts: bool = False
"""是否启用TTS功能""" """是否启用TTS功能"""
@dataclass @dataclass
class SlicingConfig(ConfigBase): class SlicingConfig(ConfigBase):
max_frame_size: int = 64 max_frame_size: int = 64
@@ -66,6 +67,7 @@ class SlicingConfig(ConfigBase):
delay_ms: int = 10 delay_ms: int = 10
"""切片发送间隔时间,单位为毫秒""" """切片发送间隔时间,单位为毫秒"""
@dataclass @dataclass
class DebugConfig(ConfigBase): class DebugConfig(ConfigBase):
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO" level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"

View File

@@ -3,6 +3,7 @@
用于在 Ada 发送给 MMC 时进行消息切片,利用 WebSocket 协议的自动重组特性 用于在 Ada 发送给 MMC 时进行消息切片,利用 WebSocket 协议的自动重组特性
仅在 Ada -> MMC 方向进行切片其他方向MMC -> AdaAda <-> Napcat不切片 仅在 Ada -> MMC 方向进行切片其他方向MMC -> AdaAda <-> Napcat不切片
""" """
import json import json
import uuid import uuid
import asyncio import asyncio
@@ -15,7 +16,6 @@ from src.common.logger import get_logger
logger = get_logger("napcat_adapter") logger = get_logger("napcat_adapter")
class MessageChunker: class MessageChunker:
"""消息切片器,用于处理大消息的分片发送""" """消息切片器,用于处理大消息的分片发送"""
@@ -29,12 +29,14 @@ class MessageChunker:
message_str = json.dumps(message, ensure_ascii=False) message_str = json.dumps(message, ensure_ascii=False)
else: else:
message_str = message message_str = message
return len(message_str.encode('utf-8')) > self.max_chunk_size return len(message_str.encode("utf-8")) > self.max_chunk_size
except Exception as e: except Exception as e:
logger.error(f"检查消息大小时出错: {e}") logger.error(f"检查消息大小时出错: {e}")
return False return False
def chunk_message(self, message: Union[str, Dict[str, Any]], chunk_id: Optional[str] = None) -> List[Dict[str, Any]]: def chunk_message(
self, message: Union[str, Dict[str, Any]], chunk_id: Optional[str] = None
) -> List[Dict[str, Any]]:
""" """
将消息切片 将消息切片
@@ -62,7 +64,7 @@ class MessageChunker:
if chunk_id is None: if chunk_id is None:
chunk_id = str(uuid.uuid4()) chunk_id = str(uuid.uuid4())
message_bytes = message_str.encode('utf-8') message_bytes = message_str.encode("utf-8")
total_size = len(message_bytes) total_size = len(message_bytes)
# 计算需要多少个切片 # 计算需要多少个切片
@@ -83,10 +85,10 @@ class MessageChunker:
"total_chunks": num_chunks, "total_chunks": num_chunks,
"chunk_size": len(chunk_data), "chunk_size": len(chunk_data),
"total_size": total_size, "total_size": total_size,
"timestamp": time.time() "timestamp": time.time(),
}, },
"__mmc_chunk_data__": chunk_data.decode('utf-8', errors='ignore'), "__mmc_chunk_data__": chunk_data.decode("utf-8", errors="ignore"),
"__mmc_is_chunked__": True "__mmc_is_chunked__": True,
} }
chunks.append(chunk_message) chunks.append(chunk_message)
@@ -111,10 +113,10 @@ class MessageChunker:
data = message data = message
return ( return (
isinstance(data, dict) and isinstance(data, dict)
"__mmc_chunk_info__" in data and and "__mmc_chunk_info__" in data
"__mmc_chunk_data__" in data and and "__mmc_chunk_data__" in data
"__mmc_is_chunked__" in data and "__mmc_is_chunked__" in data
) )
except (json.JSONDecodeError, TypeError): except (json.JSONDecodeError, TypeError):
return False return False
@@ -152,7 +154,7 @@ class MessageReassembler:
expired_chunks = [] expired_chunks = []
for chunk_id, buffer_info in self.chunk_buffers.items(): for chunk_id, buffer_info in self.chunk_buffers.items():
if current_time - buffer_info['timestamp'] > self.timeout: if current_time - buffer_info["timestamp"] > self.timeout:
expired_chunks.append(chunk_id) expired_chunks.append(chunk_id)
for chunk_id in expired_chunks: for chunk_id in expired_chunks:
@@ -207,7 +209,7 @@ class MessageReassembler:
"chunks": {}, "chunks": {},
"total_chunks": total_chunks, "total_chunks": total_chunks,
"received_chunks": 0, "received_chunks": 0,
"timestamp": chunk_timestamp "timestamp": chunk_timestamp,
} }
buffer = self.chunk_buffers[chunk_id] buffer = self.chunk_buffers[chunk_id]
@@ -260,7 +262,7 @@ class MessageReassembler:
"received": buffer["received_chunks"], "received": buffer["received_chunks"],
"total": buffer["total_chunks"], "total": buffer["total_chunks"],
"progress": f"{buffer['received_chunks']}/{buffer['total_chunks']}", "progress": f"{buffer['received_chunks']}/{buffer['total_chunks']}",
"age_seconds": time.time() - buffer["timestamp"] "age_seconds": time.time() - buffer["timestamp"],
} }
return info return info

View File

@@ -800,7 +800,10 @@ class MessageHandler:
content_parts.append(f"链接: {extracted_info['short_url']}") content_parts.append(f"链接: {extracted_info['short_url']}")
formatted_content = "\n".join(content_parts) formatted_content = "\n".join(content_parts)
return Seg(type="text", data=f"这是一条小程序分享消息,可以根据来源,考虑使用对应解析工具\n{formatted_content}") return Seg(
type="text",
data=f"这是一条小程序分享消息,可以根据来源,考虑使用对应解析工具\n{formatted_content}",
)
# 如果没有提取到关键信息返回None # 如果没有提取到关键信息返回None
return None return None

View File

@@ -1,4 +1,3 @@
import json
import asyncio import asyncio
from src.common.logger import get_logger from src.common.logger import get_logger
@@ -30,14 +29,14 @@ class MessageSending:
message_dict = message_base.to_dict() message_dict = message_base.to_dict()
if chunker.should_chunk_message(message_dict): if chunker.should_chunk_message(message_dict):
logger.info(f"消息过大,进行切片发送到 MaiBot") logger.info("消息过大,进行切片发送到 MaiBot")
# 切片消息 # 切片消息
chunks = chunker.chunk_message(message_dict) chunks = chunker.chunk_message(message_dict)
# 逐个发送切片 # 逐个发送切片
for i, chunk in enumerate(chunks): for i, chunk in enumerate(chunks):
logger.debug(f"发送切片 {i+1}/{len(chunks)} 到 MaiBot") logger.debug(f"发送切片 {i + 1}/{len(chunks)} 到 MaiBot")
# 获取对应的客户端并发送切片 # 获取对应的客户端并发送切片
platform = message_base.message_info.platform platform = message_base.message_info.platform
@@ -49,7 +48,7 @@ class MessageSending:
send_status = await client.send_message(chunk) send_status = await client.send_message(chunk)
if not send_status: if not send_status:
logger.error(f"发送切片 {i+1}/{len(chunks)} 失败") logger.error(f"发送切片 {i + 1}/{len(chunks)} 失败")
return False return False
# 使用配置中的延迟时间 # 使用配置中的延迟时间

View File

@@ -5,7 +5,6 @@ import math
import random import random
from typing import Optional, Dict, Any, Tuple from typing import Optional, Dict, Any, Tuple
from src.chat.message_receive.chat_stream import get_chat_manager
from src.chat.utils.timer_calculator import Timer from src.chat.utils.timer_calculator import Timer
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
@@ -95,7 +94,7 @@ class CycleProcessor:
return loop_info, reply_text, cycle_timers return loop_info, reply_text, cycle_timers
async def observe(self,interest_value:float = 0.0) -> bool: async def observe(self, interest_value: float = 0.0) -> str:
""" """
观察和处理单次思考循环的核心方法 观察和处理单次思考循环的核心方法
@@ -128,15 +127,23 @@ class CycleProcessor:
x0 = 1.0 # 控制曲线中心点 x0 = 1.0 # 控制曲线中心点
return 1.0 / (1.0 + math.exp(-k * (interest_val - x0))) return 1.0 / (1.0 + math.exp(-k * (interest_val - x0)))
normal_mode_probability = calculate_normal_mode_probability(interest_value) * 0.5 / global_config.chat.get_current_talk_frequency(self.context.stream_id) normal_mode_probability = (
calculate_normal_mode_probability(interest_value)
* 0.5
/ global_config.chat.get_current_talk_frequency(self.context.stream_id)
)
# 根据概率决定使用哪种模式 # 根据概率决定使用哪种模式
if random.random() < normal_mode_probability: if random.random() < normal_mode_probability:
mode = ChatMode.NORMAL mode = ChatMode.NORMAL
logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Normal planner模式") logger.info(
f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Normal planner模式"
)
else: else:
mode = ChatMode.FOCUS mode = ChatMode.FOCUS
logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Focus planner模式") logger.info(
f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Focus planner模式"
)
cycle_timers, thinking_id = self.cycle_tracker.start_cycle() cycle_timers, thinking_id = self.cycle_tracker.start_cycle()
logger.info(f"{self.log_prefix} 开始第{self.context.cycle_counter}次思考") logger.info(f"{self.log_prefix} 开始第{self.context.cycle_counter}次思考")
@@ -165,12 +172,14 @@ class CycleProcessor:
from src.plugin_system.core.event_manager import event_manager from src.plugin_system.core.event_manager import event_manager
from src.plugin_system import EventType from src.plugin_system import EventType
result = await event_manager.trigger_event(EventType.ON_PLAN,plugin_name="SYSTEM", stream_id=self.context.chat_stream) result = await event_manager.trigger_event(
EventType.ON_PLAN, plugin_name="SYSTEM", stream_id=self.context.chat_stream
)
if not result.all_continue_process(): if not result.all_continue_process():
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成") raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成")
with Timer("规划器", cycle_timers): with Timer("规划器", cycle_timers):
actions, _= await self.action_planner.plan( actions, _ = await self.action_planner.plan(
mode=mode, mode=mode,
loop_start_time=loop_start_time, loop_start_time=loop_start_time,
available_actions=available_actions, available_actions=available_actions,
@@ -195,12 +204,7 @@ class CycleProcessor:
action_name="no_reply", action_name="no_reply",
) )
return { return {"action_type": "no_reply", "success": True, "reply_text": "", "command": ""}
"action_type": "no_reply",
"success": True,
"reply_text": "",
"command": ""
}
elif action_info["action_type"] != "reply": elif action_info["action_type"] != "reply":
# 执行普通动作 # 执行普通动作
with Timer("动作执行", cycle_timers): with Timer("动作执行", cycle_timers):
@@ -210,40 +214,32 @@ class CycleProcessor:
action_info["action_data"], action_info["action_data"],
cycle_timers, cycle_timers,
thinking_id, thinking_id,
action_info["action_message"] action_info["action_message"],
) )
return { return {
"action_type": action_info["action_type"], "action_type": action_info["action_type"],
"success": success, "success": success,
"reply_text": reply_text, "reply_text": reply_text,
"command": command "command": command,
} }
else: else:
try: try:
success, response_set, _ = await generator_api.generate_reply( success, response_set, _ = await generator_api.generate_reply(
chat_stream=self.context.chat_stream, chat_stream=self.context.chat_stream,
reply_message = action_info["action_message"], reply_message=action_info["action_message"],
available_actions=available_actions, available_actions=available_actions,
enable_tool=global_config.tool.enable_tool, enable_tool=global_config.tool.enable_tool,
request_type="chat.replyer", request_type="chat.replyer",
from_plugin=False, from_plugin=False,
) )
if not success or not response_set: if not success or not response_set:
logger.info(f"{action_info['action_message'].get('processed_plain_text')} 的回复生成失败") logger.info(
return { f"{action_info['action_message'].get('processed_plain_text')} 的回复生成失败"
"action_type": "reply", )
"success": False, return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
"reply_text": "",
"loop_info": None
}
except asyncio.CancelledError: except asyncio.CancelledError:
logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消") logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消")
return { return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
"action_type": "reply",
"success": False,
"reply_text": "",
"loop_info": None
}
loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply( loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply(
response_set, response_set,
@@ -253,12 +249,7 @@ class CycleProcessor:
thinking_id, thinking_id,
actions, actions,
) )
return { return {"action_type": "reply", "success": True, "reply_text": reply_text, "loop_info": loop_info}
"action_type": "reply",
"success": True,
"reply_text": reply_text,
"loop_info": loop_info
}
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 执行动作时出错: {e}") logger.error(f"{self.log_prefix} 执行动作时出错: {e}")
logger.error(f"{self.log_prefix} 错误信息: {traceback.format_exc()}") logger.error(f"{self.log_prefix} 错误信息: {traceback.format_exc()}")
@@ -267,7 +258,7 @@ class CycleProcessor:
"success": False, "success": False,
"reply_text": "", "reply_text": "",
"loop_info": None, "loop_info": None,
"error": str(e) "error": str(e),
} }
# 创建所有动作的后台任务 # 创建所有动作的后台任务
@@ -335,226 +326,7 @@ class CycleProcessor:
self.context.chat_instance.cycle_tracker.print_cycle_info(cycle_timers) self.context.chat_instance.cycle_tracker.print_cycle_info(cycle_timers)
action_type = actions[0]["action_type"] if actions else "no_action" action_type = actions[0]["action_type"] if actions else "no_action"
# 管理no_reply计数器当执行了非no_reply动作时重置计数器 return action_type
if action_type != "no_reply":
# no_reply逻辑已集成到heartFC_chat.py中直接重置计数器
self.context.chat_instance.recent_interest_records.clear()
self.context.no_reply_consecutive = 0
logger.debug(f"{self.log_prefix} 执行了{action_type}动作重置no_reply计数器")
return True
if action_type == "no_reply":
self.context.no_reply_consecutive += 1
self.context.chat_instance._determine_form_type()
# 在一轮动作执行完毕后,增加睡眠压力
if self.context.energy_manager and global_config.sleep_system.enable_insomnia_system:
if action_type not in ["no_reply", "no_action"]:
self.context.energy_manager.increase_sleep_pressure()
return True
async def execute_plan(self, action_result: Dict[str, Any], target_message: Optional[Dict[str, Any]]):
"""
执行一个已经制定好的计划
"""
action_type = action_result.get("action_type", "error")
# 这里我们需要为执行计划创建一个新的循环追踪
cycle_timers, thinking_id = self.cycle_tracker.start_cycle(is_proactive=True)
loop_start_time = time.time()
if action_type == "reply":
# 主动思考不应该直接触发简单回复但为了逻辑完整性我们假设它会调用response_handler
# 注意:这里的 available_actions 和 plan_result 是缺失的,需要根据实际情况处理
await self._handle_reply_action(
target_message, {}, None, loop_start_time, cycle_timers, thinking_id, {"action_result": action_result}
)
else:
await self._handle_other_actions(
action_type,
action_result.get("reasoning", ""),
action_result.get("action_data", {}),
action_result.get("is_parallel", False),
None,
target_message,
cycle_timers,
thinking_id,
{"action_result": action_result},
loop_start_time,
)
async def _handle_reply_action(
self, message_data, available_actions, gen_task, loop_start_time, cycle_timers, thinking_id, plan_result
):
"""
处理回复类型的动作
Args:
message_data: 消息数据
available_actions: 可用动作列表
gen_task: 预先创建的生成任务可能为None
loop_start_time: 循环开始时间
cycle_timers: 循环计时器
thinking_id: 思考ID
plan_result: 规划结果
功能说明:
- 根据聊天模式决定是否使用预生成的回复或实时生成
- 在NORMAL模式下使用异步生成提高效率
- 在FOCUS模式下同步生成确保及时响应
- 发送生成的回复并结束循环
"""
# 初始化reply_to_str以避免UnboundLocalError
reply_to_str = None
if self.context.loop_mode == ChatMode.NORMAL:
if not gen_task:
reply_to_str = await self._build_reply_to_str(message_data)
gen_task = asyncio.create_task(
self.response_handler.generate_response(
message_data=message_data,
available_actions=available_actions,
reply_to=reply_to_str,
request_type="chat.replyer.normal",
)
)
else:
# 如果gen_task已存在但reply_to_str还未构建需要构建它
if reply_to_str is None:
reply_to_str = await self._build_reply_to_str(message_data)
try:
response_set = await asyncio.wait_for(gen_task, timeout=global_config.chat.thinking_timeout)
except asyncio.TimeoutError:
response_set = None
else:
reply_to_str = await self._build_reply_to_str(message_data)
response_set = await self.response_handler.generate_response(
message_data=message_data,
available_actions=available_actions,
reply_to=reply_to_str,
request_type="chat.replyer.focus",
)
if response_set:
loop_info, _, _ = await self.response_handler.generate_and_send_reply(
response_set, reply_to_str, loop_start_time, message_data, cycle_timers, thinking_id, plan_result
)
self.cycle_tracker.end_cycle(loop_info, cycle_timers)
async def _handle_other_actions(
self,
action_type,
reasoning,
action_data,
is_parallel,
gen_task,
action_message,
cycle_timers,
thinking_id,
plan_result,
loop_start_time,
):
"""
处理非回复类型的动作如no_reply、自定义动作等
Args:
action_type: 动作类型
reasoning: 动作理由
action_data: 动作数据
is_parallel: 是否并行执行
gen_task: 生成任务
action_message: 动作消息
cycle_timers: 循环计时器
thinking_id: 思考ID
plan_result: 规划结果
loop_start_time: 循环开始时间
功能说明:
- 在NORMAL模式下可能并行执行回复生成和动作处理
- 等待所有异步任务完成
- 整合回复和动作的执行结果
- 构建最终循环信息并结束循环
"""
background_reply_task = None
if self.context.loop_mode == ChatMode.NORMAL and is_parallel and gen_task:
background_reply_task = asyncio.create_task(
self._handle_parallel_reply(
gen_task, loop_start_time, action_message, cycle_timers, thinking_id, plan_result
)
)
background_action_task = asyncio.create_task(
self._handle_action(action_type, reasoning, action_data, cycle_timers, thinking_id, action_message)
)
reply_loop_info, action_success, action_reply_text, action_command = None, False, "", ""
if background_reply_task:
results = await asyncio.gather(background_reply_task, background_action_task, return_exceptions=True)
reply_result, action_result_val = results
if not isinstance(reply_result, BaseException) and reply_result is not None:
reply_loop_info, _, _ = reply_result
else:
reply_loop_info = None
if not isinstance(action_result_val, BaseException) and action_result_val is not None:
action_success, action_reply_text, action_command = action_result_val
else:
action_success, action_reply_text, action_command = False, "", ""
else:
results = await asyncio.gather(background_action_task, return_exceptions=True)
if results and len(results) > 0:
action_result_val = results[0] # Get the actual result from the tuple
else:
action_result_val = (False, "", "")
if not isinstance(action_result_val, BaseException) and action_result_val is not None:
action_success, action_reply_text, action_command = action_result_val
else:
action_success, action_reply_text, action_command = False, "", ""
loop_info = self._build_final_loop_info(
reply_loop_info, action_success, action_reply_text, action_command, plan_result
)
self.cycle_tracker.end_cycle(loop_info, cycle_timers)
async def _handle_parallel_reply(
self, gen_task, loop_start_time, action_message, cycle_timers, thinking_id, plan_result
):
"""
处理并行回复生成
Args:
gen_task: 回复生成任务
loop_start_time: 循环开始时间
action_message: 动作消息
cycle_timers: 循环计时器
thinking_id: 思考ID
plan_result: 规划结果
Returns:
tuple: (循环信息, 回复文本, 计时器信息) 或 None
功能说明:
- 等待并行回复生成任务完成(带超时)
- 构建回复目标字符串
- 发送生成的回复
- 返回循环信息供上级方法使用
"""
try:
response_set = await asyncio.wait_for(gen_task, timeout=global_config.chat.thinking_timeout)
except asyncio.TimeoutError:
return None, "", {}
if not response_set:
return None, "", {}
reply_to_str = await self._build_reply_to_str(action_message)
return await self.response_handler.generate_and_send_reply(
response_set, reply_to_str, loop_start_time, action_message, cycle_timers, thinking_id, plan_result
)
async def _handle_action( async def _handle_action(
self, action, reasoning, action_data, cycle_timers, thinking_id, action_message self, action, reasoning, action_data, cycle_timers, thinking_id, action_message
@@ -603,12 +375,12 @@ class CycleProcessor:
if "reply" in available_actions: if "reply" in available_actions:
fallback_action = "reply" fallback_action = "reply"
elif available_actions: elif available_actions:
fallback_action = list(available_actions.keys())[0] fallback_action = list(available_actions.keys())
if fallback_action and fallback_action != action: if fallback_action and fallback_action != action:
logger.info(f"{self.context.log_prefix} 使用回退动作: {fallback_action}") logger.info(f"{self.context.log_prefix} 使用回退动作: {fallback_action}")
action_handler = self.context.action_manager.create_action( action_handler = self.context.action_manager.create_action(
action_name=fallback_action, action_name=fallback_action if isinstance(fallback_action, list) else fallback_action,
action_data=action_data, action_data=action_data,
reasoning=f"原动作'{action}'不可用,自动回退。{reasoning}", reasoning=f"原动作'{action}'不可用,自动回退。{reasoning}",
cycle_timers=cycle_timers, cycle_timers=cycle_timers,
@@ -628,43 +400,3 @@ class CycleProcessor:
logger.error(f"{self.context.log_prefix} 处理{action}时出错: {e}") logger.error(f"{self.context.log_prefix} 处理{action}时出错: {e}")
traceback.print_exc() traceback.print_exc()
return False, "", "" return False, "", ""
def _build_final_loop_info(self, reply_loop_info, action_success, action_reply_text, action_command, plan_result):
"""
构建最终的循环信息
Args:
reply_loop_info: 回复循环信息可能为None
action_success: 动作执行是否成功
action_reply_text: 动作回复文本
action_command: 动作命令
plan_result: 规划结果
Returns:
dict: 完整的循环信息,包含规划信息和动作信息
功能说明:
- 如果有回复循环信息,则在其基础上添加动作信息
- 如果没有回复信息,则创建新的循环信息结构
- 整合所有执行结果供循环跟踪器记录
"""
if reply_loop_info:
loop_info = reply_loop_info
loop_info["loop_action_info"].update(
{
"action_taken": action_success,
"command": action_command,
"taken_time": time.time(),
}
)
else:
loop_info = {
"loop_plan_info": {"action_result": plan_result.get("action_result", {})},
"loop_action_info": {
"action_taken": action_success,
"reply_text": action_reply_text,
"command": action_command,
"taken_time": time.time(),
},
}
return loop_info

View File

@@ -91,25 +91,24 @@ class CycleTracker:
# 获取动作类型,兼容新旧格式 # 获取动作类型,兼容新旧格式
action_type = "未知动作" action_type = "未知动作"
if hasattr(self, '_current_cycle_detail') and self._current_cycle_detail: if hasattr(self, "_current_cycle_detail") and self._current_cycle_detail:
loop_plan_info = self._current_cycle_detail.loop_plan_info loop_plan_info = self._current_cycle_detail.loop_plan_info
if isinstance(loop_plan_info, dict): if isinstance(loop_plan_info, dict):
action_result = loop_plan_info.get('action_result', {}) action_result = loop_plan_info.get("action_result", {})
if isinstance(action_result, dict): if isinstance(action_result, dict):
# 旧格式action_result是字典 # 旧格式action_result是字典
action_type = action_result.get('action_type', '未知动作') action_type = action_result.get("action_type", "未知动作")
elif isinstance(action_result, list) and action_result: elif isinstance(action_result, list) and action_result:
# 新格式action_result是actions列表 # 新格式action_result是actions列表
action_type = action_result[0].get('action_type', '未知动作') action_type = action_result[0].get("action_type", "未知动作")
elif isinstance(loop_plan_info, list) and loop_plan_info: elif isinstance(loop_plan_info, list) and loop_plan_info:
# 直接是actions列表的情况 # 直接是actions列表的情况
action_type = loop_plan_info[0].get('action_type', '未知动作') action_type = loop_plan_info[0].get("action_type", "未知动作")
if self.context.current_cycle_detail.end_time and self.context.current_cycle_detail.start_time: if self.context.current_cycle_detail.end_time and self.context.current_cycle_detail.start_time:
duration = self.context.current_cycle_detail.end_time - self.context.current_cycle_detail.start_time duration = self.context.current_cycle_detail.end_time - self.context.current_cycle_detail.start_time
logger.info( logger.info(
f"{self.context.log_prefix}{self.context.current_cycle_detail.cycle_id}次思考," f"{self.context.log_prefix}{self.context.current_cycle_detail.cycle_id}次思考,"
f"耗时: {duration:.1f}秒, " f"耗时: {duration:.1f}秒, "
f"选择动作: {action_type}" f"选择动作: {action_type}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
) )

View File

@@ -3,7 +3,6 @@ import time
from typing import Optional from typing import Optional
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.plugin_system.base.component_types import ChatMode
from .hfc_context import HfcContext from .hfc_context import HfcContext
from src.schedule.schedule_manager import schedule_manager from src.schedule.schedule_manager import schedule_manager

View File

@@ -2,24 +2,24 @@ import asyncio
import time import time
import traceback import traceback
import random import random
from typing import Optional, List, Dict, Any, Tuple from typing import Optional, List, Dict, Any
from collections import deque from collections import deque
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.person_info.relationship_builder_manager import relationship_builder_manager from src.person_info.relationship_builder_manager import relationship_builder_manager
from src.chat.express.expression_learner import expression_learner_manager from src.chat.express.expression_learner import expression_learner_manager
from src.plugin_system.base.component_types import ChatMode
from src.schedule.schedule_manager import schedule_manager, SleepState from src.schedule.schedule_manager import schedule_manager, SleepState
from src.plugin_system.apis import message_api from src.plugin_system.apis import message_api
from .hfc_context import HfcContext from .hfc_context import HfcContext
from .energy_manager import EnergyManager from .energy_manager import EnergyManager
from .proactive_thinker import ProactiveThinker from .proactive.proactive_thinker import ProactiveThinker
from .cycle_processor import CycleProcessor from .cycle_processor import CycleProcessor
from .response_handler import ResponseHandler from .response_handler import ResponseHandler
from .cycle_tracker import CycleTracker from .cycle_tracker import CycleTracker
from .wakeup_manager import WakeUpManager from .sleep_manager.wakeup_manager import WakeUpManager
from .proactive.events import ProactiveTriggerEvent
logger = get_logger("hfc") logger = get_logger("hfc")
@@ -54,6 +54,7 @@ class HeartFChatting:
self.context.chat_instance = self self.context.chat_instance = self
self._loop_task: Optional[asyncio.Task] = None self._loop_task: Optional[asyncio.Task] = None
self._proactive_monitor_task: Optional[asyncio.Task] = None
# 记录最近3次的兴趣度 # 记录最近3次的兴趣度
self.recent_interest_records: deque = deque(maxlen=3) self.recent_interest_records: deque = deque(maxlen=3)
@@ -93,8 +94,12 @@ class HeartFChatting:
self.context.relationship_builder = relationship_builder_manager.get_or_create_builder(self.context.stream_id) self.context.relationship_builder = relationship_builder_manager.get_or_create_builder(self.context.stream_id)
self.context.expression_learner = expression_learner_manager.get_expression_learner(self.context.stream_id) self.context.expression_learner = expression_learner_manager.get_expression_learner(self.context.stream_id)
#await self.energy_manager.start() # 启动主动思考监视器
await self.proactive_thinker.start() if global_config.chat.enable_proactive_thinking:
self._proactive_monitor_task = asyncio.create_task(self._proactive_monitor_loop())
self._proactive_monitor_task.add_done_callback(self._handle_proactive_monitor_completion)
logger.info(f"{self.context.log_prefix} 主动思考监视器已启动")
await self.wakeup_manager.start() await self.wakeup_manager.start()
self._loop_task = asyncio.create_task(self._main_chat_loop()) self._loop_task = asyncio.create_task(self._main_chat_loop())
@@ -116,8 +121,12 @@ class HeartFChatting:
return return
self.context.running = False self.context.running = False
#await self.energy_manager.stop() # 停止主动思考监视器
await self.proactive_thinker.stop() if self._proactive_monitor_task and not self._proactive_monitor_task.done():
self._proactive_monitor_task.cancel()
await asyncio.sleep(0)
logger.info(f"{self.context.log_prefix} 主动思考监视器已停止")
await self.wakeup_manager.stop() await self.wakeup_manager.stop()
if self._loop_task and not self._loop_task.done(): if self._loop_task and not self._loop_task.done():
@@ -147,6 +156,151 @@ class HeartFChatting:
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"{self.context.log_prefix} HeartFChatting: 结束了聊天") logger.info(f"{self.context.log_prefix} HeartFChatting: 结束了聊天")
def _handle_proactive_monitor_completion(self, task: asyncio.Task):
"""
处理主动思考监视器任务完成
Args:
task: 完成的异步任务对象
功能说明:
- 处理任务异常完成的情况
- 记录任务正常结束或被取消的日志
"""
try:
if exception := task.exception():
logger.error(f"{self.context.log_prefix} 主动思考监视器异常: {exception}")
else:
logger.info(f"{self.context.log_prefix} 主动思考监视器正常结束")
except asyncio.CancelledError:
logger.info(f"{self.context.log_prefix} 主动思考监视器被取消")
async def _proactive_monitor_loop(self):
"""
主动思考监视器循环
功能说明:
- 定期检查是否需要进行主动思考
- 计算聊天沉默时间,并与动态思考间隔比较
- 当沉默时间超过阈值时,触发主动思考
- 处理思考过程中的异常
"""
while self.context.running:
await asyncio.sleep(15)
if not self._should_enable_proactive_thinking():
continue
current_time = time.time()
silence_duration = current_time - self.context.last_message_time
target_interval = self._get_dynamic_thinking_interval()
if silence_duration >= target_interval:
try:
formatted_time = self._format_duration(silence_duration)
event = ProactiveTriggerEvent(
source="silence_monitor",
reason=f"聊天已沉默 {formatted_time}",
metadata={"silence_duration": silence_duration},
)
await self.proactive_thinker.think(event)
self.context.last_message_time = current_time
except Exception as e:
logger.error(f"{self.context.log_prefix} 主动思考触发执行出错: {e}")
logger.error(traceback.format_exc())
def _should_enable_proactive_thinking(self) -> bool:
"""
判断是否应启用主动思考
Returns:
bool: 如果应启用主动思考则返回True否则返回False
功能说明:
- 检查全局配置和特定聊天设置
- 支持按群聊和私聊分别配置
- 支持白名单模式,只在特定聊天中启用
"""
if not self.context.chat_stream:
return False
is_group_chat = self.context.chat_stream.group_info is not None
if is_group_chat and not global_config.chat.proactive_thinking_in_group:
return False
if not is_group_chat and not global_config.chat.proactive_thinking_in_private:
return False
stream_parts = self.context.stream_id.split(":")
current_chat_identifier = f"{stream_parts}:{stream_parts}" if len(stream_parts) >= 2 else self.context.stream_id
enable_list = getattr(
global_config.chat,
"proactive_thinking_enable_in_groups" if is_group_chat else "proactive_thinking_enable_in_private",
[],
)
return not enable_list or current_chat_identifier in enable_list
def _get_dynamic_thinking_interval(self) -> float:
"""
获取动态思考间隔时间
Returns:
float: 思考间隔秒数
功能说明:
- 尝试从timing_utils导入正态分布间隔函数
- 根据配置计算动态间隔,增加随机性
- 在无法导入或计算出错时,回退到固定的间隔
"""
try:
from src.utils.timing_utils import get_normal_distributed_interval
base_interval = global_config.chat.proactive_thinking_interval
delta_sigma = getattr(global_config.chat, "delta_sigma", 120)
if base_interval <= 0:
base_interval = abs(base_interval)
if delta_sigma < 0:
delta_sigma = abs(delta_sigma)
if base_interval == 0 and delta_sigma == 0:
return 300
if delta_sigma == 0:
return base_interval
sigma_percentage = delta_sigma / base_interval if base_interval > 0 else delta_sigma / 1000
return get_normal_distributed_interval(base_interval, sigma_percentage, 1, 86400, use_3sigma_rule=True)
except ImportError:
logger.warning(f"{self.context.log_prefix} timing_utils不可用使用固定间隔")
return max(300, abs(global_config.chat.proactive_thinking_interval))
except Exception as e:
logger.error(f"{self.context.log_prefix} 动态间隔计算出错: {e},使用固定间隔")
return max(300, abs(global_config.chat.proactive_thinking_interval))
def _format_duration(self, seconds: float) -> str:
"""
格式化时长为可读字符串
Args:
seconds: 时长秒数
Returns:
str: 格式化后的字符串 (例如 "1小时2分3秒")
"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
parts = []
if hours > 0:
parts.append(f"{hours}小时")
if minutes > 0:
parts.append(f"{minutes}")
if secs > 0 or not parts:
parts.append(f"{secs}")
return "".join(parts)
async def _main_chat_loop(self): async def _main_chat_loop(self):
""" """
主聊天循环 主聊天循环
@@ -238,30 +392,36 @@ class HeartFChatting:
logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。") logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。")
# 根据聊天模式处理新消息 # 根据聊天模式处理新消息
# 统一使用 _should_process_messages 判断是否应该处理 should_process, interest_value = await self._should_process_messages(recent_messages)
should_process,interest_value = await self._should_process_messages(recent_messages if has_new_messages else None) if not should_process:
if should_process: # 消息数量不足或兴趣不够,等待
self.context.last_read_time = time.time()
await self.cycle_processor.observe(interest_value = interest_value)
else:
# Normal模式消息数量不足等待
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
return True return True # Skip rest of the logic for this iteration
if not await self._should_process_messages(recent_messages if has_new_messages else None): # Messages should be processed
return has_new_messages action_type = await self.cycle_processor.observe(interest_value=interest_value)
# 处理新消息 # 管理no_reply计数器
for message in recent_messages: if action_type != "no_reply":
await self.cycle_processor.observe(interest_value = interest_value) self.recent_interest_records.clear()
self.context.no_reply_consecutive = 0
logger.debug(f"{self.context.log_prefix} 执行了{action_type}动作重置no_reply计数器")
else: # action_type == "no_reply"
self.context.no_reply_consecutive += 1
self._determine_form_type()
# 在一轮动作执行完毕后,增加睡眠压力
if self.context.energy_manager and global_config.sleep_system.enable_insomnia_system:
if action_type not in ["no_reply", "no_action"]:
self.context.energy_manager.increase_sleep_pressure()
# 如果成功观察,增加能量值并重置累积兴趣值 # 如果成功观察,增加能量值并重置累积兴趣值
if has_new_messages:
self.context.energy_value += 1 / global_config.chat.focus_value self.context.energy_value += 1 / global_config.chat.focus_value
# 重置累积兴趣值,因为消息已经被成功处理 # 重置累积兴趣值,因为消息已经被成功处理
self.context.breaking_accumulated_interest = 0.0 self.context.breaking_accumulated_interest = 0.0
logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f},重置累积兴趣值") logger.info(
f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f},重置累积兴趣值"
)
# 更新上一帧的睡眠状态 # 更新上一帧的睡眠状态
self.context.was_sleeping = is_sleeping self.context.was_sleeping = is_sleeping
@@ -282,7 +442,6 @@ class HeartFChatting:
return has_new_messages return has_new_messages
def _handle_wakeup_messages(self, messages): def _handle_wakeup_messages(self, messages):
""" """
处理休眠状态下的消息,累积唤醒度 处理休眠状态下的消息,累积唤醒度
@@ -321,14 +480,15 @@ class HeartFChatting:
def _determine_form_type(self) -> str: def _determine_form_type(self) -> str:
"""判断使用哪种形式的no_reply""" """判断使用哪种形式的no_reply"""
# 检查是否启用breaking模式 # 检查是否启用breaking模式
if not global_config.chat.enable_breaking_mode: if not getattr(global_config.chat, "enable_breaking_mode", False):
logger.info(f"{self.context.log_prefix} breaking模式已禁用使用waiting形式") logger.info(f"{self.context.log_prefix} breaking模式已禁用使用waiting形式")
self.context.focus_energy = 1 self.context.focus_energy = 1
return return "waiting"
# 如果连续no_reply次数少于3次使用waiting形式 # 如果连续no_reply次数少于3次使用waiting形式
if self.context.no_reply_consecutive <= 3: if self.context.no_reply_consecutive <= 3:
self.context.focus_energy = 1 self.context.focus_energy = 1
return "waiting"
else: else:
# 使用累积兴趣值而不是最近3次的记录 # 使用累积兴趣值而不是最近3次的记录
total_interest = self.context.breaking_accumulated_interest total_interest = self.context.breaking_accumulated_interest
@@ -336,24 +496,31 @@ class HeartFChatting:
# 计算调整后的阈值 # 计算调整后的阈值
adjusted_threshold = 1 / global_config.chat.get_current_talk_frequency(self.context.stream_id) adjusted_threshold = 1 / global_config.chat.get_current_talk_frequency(self.context.stream_id)
logger.info(f"{self.context.log_prefix} 累积兴趣值: {total_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}") logger.info(
f"{self.context.log_prefix} 累积兴趣值: {total_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}"
)
# 如果累积兴趣值小于阈值进入breaking形式 # 如果累积兴趣值小于阈值进入breaking形式
if total_interest < adjusted_threshold: if total_interest < adjusted_threshold:
logger.info(f"{self.context.log_prefix} 累积兴趣度不足进入breaking形式") logger.info(f"{self.context.log_prefix} 累积兴趣度不足进入breaking形式")
self.context.focus_energy = random.randint(3, 6) self.context.focus_energy = random.randint(3, 6)
return "breaking"
else: else:
logger.info(f"{self.context.log_prefix} 累积兴趣度充足使用waiting形式") logger.info(f"{self.context.log_prefix} 累积兴趣度充足使用waiting形式")
self.context.focus_energy = 1 self.context.focus_energy = 1
return "waiting"
async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool,float]: async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool, float]:
""" """
统一判断是否应该处理消息的函数 统一判断是否应该处理消息的函数
根据当前循环模式和消息内容决定是否继续处理 根据当前循环模式和消息内容决定是否继续处理
""" """
if not new_message:
return False, 0.0
new_message_count = len(new_message) new_message_count = len(new_message)
talk_frequency = global_config.chat.get_current_talk_frequency(self.context.chat_stream.stream_id) talk_frequency = global_config.chat.get_current_talk_frequency(self.context.stream_id)
modified_exit_count_threshold = self.context.focus_energy * 0.5 / talk_frequency modified_exit_count_threshold = self.context.focus_energy * 0.5 / talk_frequency
modified_exit_interest_threshold = 1.5 / talk_frequency modified_exit_interest_threshold = 1.5 / talk_frequency
@@ -381,13 +548,15 @@ class HeartFChatting:
logger.info( logger.info(
f"{self.context.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold:.1f}),结束等待,累积兴趣值: {total_interest:.2f}" f"{self.context.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold:.1f}),结束等待,累积兴趣值: {total_interest:.2f}"
) )
return True,total_interest/new_message_count return True, total_interest / new_message_count
# 检查累计兴趣值 # 检查累计兴趣值
if new_message_count > 0: if new_message_count > 0:
# 只在兴趣值变化时输出log # 只在兴趣值变化时输出log
if not hasattr(self, "_last_accumulated_interest") or total_interest != self._last_accumulated_interest: if not hasattr(self, "_last_accumulated_interest") or total_interest != self._last_accumulated_interest:
logger.info(f"{self.context.log_prefix} breaking形式当前累积兴趣值: {total_interest:.2f}, 专注度: {global_config.chat.focus_value:.1f}") logger.info(
f"{self.context.log_prefix} breaking形式当前累积兴趣值: {total_interest:.2f}, 专注度: {global_config.chat.focus_value:.1f}"
)
self._last_accumulated_interest = total_interest self._last_accumulated_interest = total_interest
if total_interest >= modified_exit_interest_threshold: if total_interest >= modified_exit_interest_threshold:
# 记录兴趣度到列表 # 记录兴趣度到列表
@@ -397,13 +566,16 @@ class HeartFChatting:
logger.info( logger.info(
f"{self.context.log_prefix} 累计兴趣值达到{total_interest:.2f}(>{modified_exit_interest_threshold:.1f}),结束等待" f"{self.context.log_prefix} 累计兴趣值达到{total_interest:.2f}(>{modified_exit_interest_threshold:.1f}),结束等待"
) )
return True,total_interest/new_message_count return True, total_interest / new_message_count
# 每10秒输出一次等待状态 # 每10秒输出一次等待状态
if int(time.time() - self.context.last_read_time) > 0 and int(time.time() - self.context.last_read_time) % 10 == 0: if (
int(time.time() - self.context.last_read_time) > 0
and int(time.time() - self.context.last_read_time) % 10 == 0
):
logger.info( logger.info(
f"{self.context.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,累积兴趣{total_interest:.1f},继续等待..." f"{self.context.log_prefix} 已等待{time.time() - self.context.last_read_time:.0f}秒,累计{new_message_count}条消息,累积兴趣{total_interest:.1f},继续等待..."
) )
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
return False,0.0 return False, 0.0

View File

@@ -1,16 +1,15 @@
from typing import List, Optional, TYPE_CHECKING from typing import List, Optional, TYPE_CHECKING
import time import time
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
from src.common.logger import get_logger
from src.person_info.relationship_builder_manager import RelationshipBuilder from src.person_info.relationship_builder_manager import RelationshipBuilder
from src.chat.express.expression_learner import ExpressionLearner from src.chat.express.expression_learner import ExpressionLearner
from src.plugin_system.base.component_types import ChatMode
from src.chat.planner_actions.action_manager import ActionManager from src.chat.planner_actions.action_manager import ActionManager
from src.chat.chat_loop.hfc_utils import CycleDetail from src.chat.chat_loop.hfc_utils import CycleDetail
if TYPE_CHECKING: if TYPE_CHECKING:
from .wakeup_manager import WakeUpManager from .sleep_manager.wakeup_manager import WakeUpManager
from .energy_manager import EnergyManager from .energy_manager import EnergyManager
from .heartFC_chat import HeartFChatting
class HfcContext: class HfcContext:
@@ -49,7 +48,7 @@ class HfcContext:
self.last_read_time = time.time() - 10 self.last_read_time = time.time() - 10
# 从聊天流恢复breaking累积兴趣值 # 从聊天流恢复breaking累积兴趣值
self.breaking_accumulated_interest = getattr(self.chat_stream, 'breaking_accumulated_interest', 0.0) self.breaking_accumulated_interest = getattr(self.chat_stream, "breaking_accumulated_interest", 0.0)
self.action_manager = ActionManager() self.action_manager = ActionManager()
@@ -69,7 +68,7 @@ class HfcContext:
# breaking形式下的累积兴趣值 # breaking形式下的累积兴趣值
self.breaking_accumulated_interest = 0.0 self.breaking_accumulated_interest = 0.0
# 引用HeartFChatting实例以便其他组件可以调用其方法 # 引用HeartFChatting实例以便其他组件可以调用其方法
self.chat_instance = None self.chat_instance: Optional["HeartFChatting"] = None
def save_context_state(self): def save_context_state(self):
"""将当前状态保存到聊天流""" """将当前状态保存到聊天流"""

View File

@@ -1,13 +1,11 @@
import time import time
from typing import Optional, Dict, Any, Union from typing import Optional, Dict, Any, Union
from src.config.config import global_config
from src.common.logger import get_logger from src.common.logger import get_logger
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from src.plugin_system.apis import send_api from src.plugin_system.apis import send_api
from maim_message.message_base import GroupInfo from maim_message.message_base import GroupInfo
from src.common.message_repository import count_messages
logger = get_logger("hfc") logger = get_logger("hfc")
@@ -122,6 +120,7 @@ class CycleDetail:
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 send_typing(): async def send_typing():
""" """
发送打字状态指示 发送打字状态指示

View File

@@ -0,0 +1,13 @@
from dataclasses import dataclass, field
from typing import Optional, Dict, Any
@dataclass
class ProactiveTriggerEvent:
"""
主动思考触发事件的数据类
"""
source: str # 触发源的标识,例如 "silence_monitor", "insomnia_manager"
reason: str # 触发的具体原因,例如 "聊天已沉默10分钟", "深夜emo"
metadata: Optional[Dict[str, Any]] = field(default_factory=dict) # 可选的元数据,用于传递额外信息

View File

@@ -0,0 +1,125 @@
import time
import traceback
from typing import TYPE_CHECKING
from src.common.logger import get_logger
from src.plugin_system.base.component_types import ChatMode
from ..hfc_context import HfcContext
from .events import ProactiveTriggerEvent
from src.plugin_system.apis import generator_api
if TYPE_CHECKING:
from ..cycle_processor import CycleProcessor
logger = get_logger("hfc")
class ProactiveThinker:
def __init__(self, context: HfcContext, cycle_processor: "CycleProcessor"):
"""
初始化主动思考器
Args:
context: HFC聊天上下文对象
cycle_processor: 循环处理器,用于执行主动思考的结果
功能说明:
- 接收主动思考事件并执行思考流程
- 根据事件类型执行不同的前置操作(如修改情绪)
- 调用planner进行决策并由cycle_processor执行
"""
self.context = context
self.cycle_processor = cycle_processor
async def think(self, trigger_event: ProactiveTriggerEvent):
"""
统一的API入口用于触发主动思考
Args:
trigger_event: 描述触发上下文的事件对象
"""
logger.info(
f"{self.context.log_prefix} 接收到主动思考事件: "
f"来源='{trigger_event.source}', 原因='{trigger_event.reason}'"
)
try:
# 1. 根据事件类型执行前置操作
await self._prepare_for_thinking(trigger_event)
# 2. 执行核心思考逻辑
await self._execute_proactive_thinking(trigger_event)
except Exception as e:
logger.error(f"{self.context.log_prefix} 主动思考 think 方法执行异常: {e}")
logger.error(traceback.format_exc())
async def _prepare_for_thinking(self, trigger_event: ProactiveTriggerEvent):
"""
根据事件类型,执行思考前的准备工作,例如修改情绪
Args:
trigger_event: 触发事件
"""
if trigger_event.source != "insomnia_manager":
return
try:
from src.mood.mood_manager import mood_manager
mood_obj = mood_manager.get_mood_by_chat_id(self.context.stream_id)
new_mood = None
if trigger_event.reason == "low_pressure":
new_mood = "精力过剩,毫无睡意"
elif trigger_event.reason == "random":
new_mood = "深夜emo胡思乱想"
elif trigger_event.reason == "goodnight":
new_mood = "有点困了,准备睡觉了"
if new_mood:
mood_obj.mood_state = new_mood
mood_obj.last_change_time = time.time()
logger.info(
f"{self.context.log_prefix}'{trigger_event.reason}'"
f"情绪状态被强制更新为: {mood_obj.mood_state}"
)
except Exception as e:
logger.error(f"{self.context.log_prefix} 设置失眠情绪时出错: {e}")
async def _execute_proactive_thinking(self, trigger_event: ProactiveTriggerEvent):
"""
执行主动思考的核心逻辑
Args:
trigger_event: 触发事件
"""
try:
# 直接调用 planner 的 PROACTIVE 模式
actions, target_message = await self.cycle_processor.action_planner.plan(mode=ChatMode.PROACTIVE)
# 获取第一个规划出的动作作为主要决策
action_result = actions[0] if actions else {}
# 如果决策不是 do_nothing则执行
if action_result and action_result.get("action_type") != "do_nothing":
if action_result.get("action_type") == "reply":
success, response_set, _ = await generator_api.generate_reply(
chat_stream=self.context.chat_stream,
reply_message=action_result["action_message"],
available_actions={},
enable_tool=False,
request_type="chat.replyer.proactive",
from_plugin=False,
)
if success and response_set:
await self.cycle_processor.response_handler.send_response(
response_set, time.time(), action_result["action_message"]
)
else:
logger.info(f"{self.context.log_prefix} 主动思考决策: 保持沉默")
except Exception as e:
logger.error(f"{self.context.log_prefix} 主动思考执行异常: {e}")
logger.error(traceback.format_exc())

View File

@@ -1,353 +0,0 @@
import asyncio
import time
import traceback
from typing import Optional, TYPE_CHECKING
from src.common.logger import get_logger
from src.config.config import global_config
from src.plugin_system.base.component_types import ChatMode
from .hfc_context import HfcContext
if TYPE_CHECKING:
from .cycle_processor import CycleProcessor
logger = get_logger("hfc")
class ProactiveThinker:
def __init__(self, context: HfcContext, cycle_processor: "CycleProcessor"):
"""
初始化主动思考器
Args:
context: HFC聊天上下文对象
cycle_processor: 循环处理器,用于执行主动思考的结果
功能说明:
- 管理机器人的主动发言功能
- 根据沉默时间和配置触发主动思考
- 提供私聊和群聊不同的思考提示模板
- 使用3-sigma规则计算动态思考间隔
"""
self.context = context
self.cycle_processor = cycle_processor
self._proactive_thinking_task: Optional[asyncio.Task] = None
self.proactive_thinking_prompts = {
"private": """现在你和你朋友的私聊里面已经隔了{time}没有发送消息了,请你结合上下文以及你和你朋友之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择:
1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时)
2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时)
请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请只回复"沉默"(注意:这个词不会被发送到群聊中)。""",
"group": """现在群里面已经隔了{time}没有人发送消息了,请你结合上下文以及群聊里面之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择:
1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时)
2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时)
请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请只回复"沉默"(注意:这个词不会被发送到群聊中)。""",
}
async def start(self):
"""
启动主动思考器
功能说明:
- 检查运行状态和配置,避免重复启动
- 只有在启用主动思考功能时才启动
- 创建主动思考循环异步任务
- 设置任务完成回调处理
- 记录启动日志
"""
if self.context.running and not self._proactive_thinking_task and global_config.chat.enable_proactive_thinking:
self._proactive_thinking_task = asyncio.create_task(self._proactive_thinking_loop())
self._proactive_thinking_task.add_done_callback(self._handle_proactive_thinking_completion)
logger.info(f"{self.context.log_prefix} 主动思考器已启动")
async def stop(self):
"""
停止主动思考器
功能说明:
- 取消正在运行的主动思考任务
- 等待任务完全停止
- 记录停止日志
"""
if self._proactive_thinking_task and not self._proactive_thinking_task.done():
self._proactive_thinking_task.cancel()
await asyncio.sleep(0)
logger.info(f"{self.context.log_prefix} 主动思考器已停止")
def _handle_proactive_thinking_completion(self, task: asyncio.Task):
"""
处理主动思考任务完成
Args:
task: 完成的异步任务对象
功能说明:
- 处理任务正常完成或异常情况
- 记录相应的日志信息
- 区分取消和异常终止的情况
"""
try:
if exception := task.exception():
logger.error(f"{self.context.log_prefix} 主动思考循环异常: {exception}")
else:
logger.info(f"{self.context.log_prefix} 主动思考循环正常结束")
except asyncio.CancelledError:
logger.info(f"{self.context.log_prefix} 主动思考循环被取消")
async def _proactive_thinking_loop(self):
"""
主动思考的主循环
功能说明:
- 每15秒检查一次是否需要主动思考
- 只在FOCUS模式下进行主动思考
- 检查是否启用主动思考功能
- 计算沉默时间并与动态间隔比较
- 达到条件时执行主动思考并更新最后消息时间
- 处理执行过程中的异常
"""
while self.context.running:
await asyncio.sleep(15)
if self.context.loop_mode != ChatMode.FOCUS:
continue
if not self._should_enable_proactive_thinking():
continue
current_time = time.time()
silence_duration = current_time - self.context.last_message_time
target_interval = self._get_dynamic_thinking_interval()
if silence_duration >= target_interval:
try:
await self._execute_proactive_thinking(silence_duration)
self.context.last_message_time = current_time
except Exception as e:
logger.error(f"{self.context.log_prefix} 主动思考执行出错: {e}")
logger.error(traceback.format_exc())
def _should_enable_proactive_thinking(self) -> bool:
"""
检查是否应该启用主动思考
Returns:
bool: 如果应该启用主动思考则返回True
功能说明:
- 检查聊天流是否存在
- 检查当前聊天是否在启用列表中(按平台和类型分别检查)
- 根据聊天类型(群聊/私聊)和配置决定是否启用
- 群聊需要proactive_thinking_in_group为True
- 私聊需要proactive_thinking_in_private为True
"""
if not self.context.chat_stream:
return False
is_group_chat = self.context.chat_stream.group_info is not None
# 检查基础开关
if is_group_chat and not global_config.chat.proactive_thinking_in_group:
return False
if not is_group_chat and not global_config.chat.proactive_thinking_in_private:
return False
# 获取当前聊天的完整标识 (platform:chat_id)
stream_parts = self.context.stream_id.split(":")
if len(stream_parts) >= 2:
platform = stream_parts[0]
chat_id = stream_parts[1]
current_chat_identifier = f"{platform}:{chat_id}"
else:
# 如果无法解析则使用原始stream_id
current_chat_identifier = self.context.stream_id
# 检查是否在启用列表中
if is_group_chat:
# 群聊检查
enable_list = getattr(global_config.chat, "proactive_thinking_enable_in_groups", [])
if enable_list and current_chat_identifier not in enable_list:
return False
else:
# 私聊检查
enable_list = getattr(global_config.chat, "proactive_thinking_enable_in_private", [])
if enable_list and current_chat_identifier not in enable_list:
return False
return True
def _get_dynamic_thinking_interval(self) -> float:
"""
获取动态思考间隔
Returns:
float: 计算得出的思考间隔时间(秒)
功能说明:
- 使用3-sigma规则计算正态分布的思考间隔
- 基于base_interval和delta_sigma配置计算
- 处理特殊情况为0或负数的配置
- 如果timing_utils不可用则使用固定间隔
- 间隔范围被限制在1秒到86400秒1天之间
"""
try:
from src.utils.timing_utils import get_normal_distributed_interval
base_interval = global_config.chat.proactive_thinking_interval
delta_sigma = getattr(global_config.chat, "delta_sigma", 120)
if base_interval < 0:
base_interval = abs(base_interval)
if delta_sigma < 0:
delta_sigma = abs(delta_sigma)
if base_interval == 0 and delta_sigma == 0:
return 300
elif base_interval == 0:
sigma_percentage = delta_sigma / 1000
return get_normal_distributed_interval(0, sigma_percentage, 1, 86400, use_3sigma_rule=True)
elif delta_sigma == 0:
return base_interval
sigma_percentage = delta_sigma / base_interval
return get_normal_distributed_interval(base_interval, sigma_percentage, 1, 86400, use_3sigma_rule=True)
except ImportError:
logger.warning(f"{self.context.log_prefix} timing_utils不可用使用固定间隔")
return max(300, abs(global_config.chat.proactive_thinking_interval))
except Exception as e:
logger.error(f"{self.context.log_prefix} 动态间隔计算出错: {e},使用固定间隔")
return max(300, abs(global_config.chat.proactive_thinking_interval))
def _format_duration(self, seconds: float) -> str:
"""
格式化持续时间为中文描述
Args:
seconds: 持续时间(秒)
Returns:
str: 格式化后的时间字符串,如"1小时30分45秒"
功能说明:
- 将秒数转换为小时、分钟、秒的组合
- 只显示非零的时间单位
- 如果所有单位都为0则显示"0秒"
- 用于主动思考日志的时间显示
"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
parts = []
if hours > 0:
parts.append(f"{hours}小时")
if minutes > 0:
parts.append(f"{minutes}")
if secs > 0 or not parts:
parts.append(f"{secs}")
return "".join(parts)
async def _execute_proactive_thinking(self, silence_duration: float):
"""
执行主动思考
Args:
silence_duration: 沉默持续时间(秒)
"""
formatted_time = self._format_duration(silence_duration)
logger.info(f"{self.context.log_prefix} 触发主动思考,已沉默{formatted_time}")
try:
# 直接调用 planner 的 PROACTIVE 模式
action_result_tuple, target_message = await self.cycle_processor.action_planner.plan(
mode=ChatMode.PROACTIVE
)
action_result = action_result_tuple.get("action_result")
# 如果决策不是 do_nothing则执行
if action_result and action_result.get("action_type") != "do_nothing":
logger.info(f"{self.context.log_prefix} 主动思考决策: {action_result.get('action_type')}, 原因: {action_result.get('reasoning')}")
# 在主动思考时,如果 target_message 为 None则默认选取最新 message 作为 target_message
if target_message is None and self.context.chat_stream and self.context.chat_stream.context:
from src.chat.message_receive.message import MessageRecv
latest_message = self.context.chat_stream.context.get_last_message()
if isinstance(latest_message, MessageRecv):
user_info = latest_message.message_info.user_info
target_message = {
"chat_info_platform": latest_message.message_info.platform,
"user_platform": user_info.platform if user_info else None,
"user_id": user_info.user_id if user_info else None,
"processed_plain_text": latest_message.processed_plain_text,
"is_mentioned": latest_message.is_mentioned,
}
# 将决策结果交给 cycle_processor 的后续流程处理
await self.cycle_processor.execute_plan(action_result, target_message)
else:
logger.info(f"{self.context.log_prefix} 主动思考决策: 保持沉默")
except Exception as e:
logger.error(f"{self.context.log_prefix} 主动思考执行异常: {e}")
logger.error(traceback.format_exc())
async def trigger_insomnia_thinking(self, reason: str):
"""
由外部事件(如失眠)触发的一次性主动思考
Args:
reason: 触发的原因 (e.g., "low_pressure", "random")
"""
logger.info(f"{self.context.log_prefix} 因“{reason}”触发失眠,开始深夜思考...")
# 1. 根据原因修改情绪
try:
from src.mood.mood_manager import mood_manager
mood_obj = mood_manager.get_mood_by_chat_id(self.context.stream_id)
if reason == "low_pressure":
mood_obj.mood_state = "精力过剩,毫无睡意"
elif reason == "random":
mood_obj.mood_state = "深夜emo胡思乱想"
mood_obj.last_change_time = time.time() # 更新时间戳以允许后续的情绪回归
logger.info(f"{self.context.log_prefix} 因失眠,情绪状态被强制更新为: {mood_obj.mood_state}")
except Exception as e:
logger.error(f"{self.context.log_prefix} 设置失眠情绪时出错: {e}")
# 2. 直接执行主动思考逻辑
try:
# 传入一个象征性的silence_duration因为它在这里不重要
await self._execute_proactive_thinking(silence_duration=1)
except Exception as e:
logger.error(f"{self.context.log_prefix} 失眠思考执行出错: {e}")
logger.error(traceback.format_exc())
async def trigger_goodnight_thinking(self):
"""
在失眠状态结束后,触发一次准备睡觉的主动思考
"""
logger.info(f"{self.context.log_prefix} 失眠状态结束,准备睡觉,触发告别思考...")
# 1. 设置一个准备睡觉的特定情绪
try:
from src.mood.mood_manager import mood_manager
mood_obj = mood_manager.get_mood_by_chat_id(self.context.stream_id)
mood_obj.mood_state = "有点困了,准备睡觉了"
mood_obj.last_change_time = time.time()
logger.info(f"{self.context.log_prefix} 情绪状态更新为: {mood_obj.mood_state}")
except Exception as e:
logger.error(f"{self.context.log_prefix} 设置睡前情绪时出错: {e}")
# 2. 直接执行主动思考逻辑
try:
await self._execute_proactive_thinking(silence_duration=1)
except Exception as e:
logger.error(f"{self.context.log_prefix} 睡前告别思考执行出错: {e}")
logger.error(traceback.format_exc())

View File

@@ -64,7 +64,7 @@ class ResponseHandler:
- 构建并返回完整的循环信息 - 构建并返回完整的循环信息
- 用于上级方法的状态跟踪 - 用于上级方法的状态跟踪
""" """
reply_text = await self.send_response(response_set, reply_to_str, loop_start_time, action_message) reply_text = await self.send_response(response_set, loop_start_time, action_message)
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
@@ -157,7 +157,7 @@ class ResponseHandler:
await send_api.text_to_stream( await send_api.text_to_stream(
text=data, text=data,
stream_id=self.context.stream_id, stream_id=self.context.stream_id,
reply_to_message = message_data, reply_to_message=message_data,
set_reply=need_reply, set_reply=need_reply,
typing=False, typing=False,
) )
@@ -166,107 +166,9 @@ class ResponseHandler:
await send_api.text_to_stream( await send_api.text_to_stream(
text=data, text=data,
stream_id=self.context.stream_id, stream_id=self.context.stream_id,
reply_to_message = message_data, reply_to_message=None,
set_reply=need_reply, set_reply=False,
typing=True, typing=True,
) )
return reply_text return reply_text
# TODO: 已废弃
async def generate_response(
self,
message_data: dict,
available_actions: Optional[Dict[str, Any]],
reply_to: str,
request_type: str = "chat.replyer.normal",
) -> Optional[list]:
"""
生成回复内容
Args:
message_data: 消息数据
available_actions: 可用动作列表
reply_to: 回复目标
request_type: 请求类型,默认为普通回复
Returns:
list: 生成的回复内容列表失败时返回None
功能说明:
- 在生成回复前进行反注入检测(提高效率)
- 调用生成器API生成回复
- 根据配置启用或禁用工具功能
- 处理生成失败的情况
- 记录生成过程中的错误和异常
"""
try:
# === 反注入检测(仅在需要生成回复时) ===
# 执行反注入检测(直接使用字典格式)
anti_injector = get_anti_injector()
result, modified_content, reason = await anti_injector.process_message(
message_data, self.context.chat_stream
)
# 根据反注入结果处理消息数据
await anti_injector.handle_message_storage(result, modified_content, reason, message_data)
if result == ProcessResult.BLOCKED_BAN:
# 用户被封禁 - 直接阻止回复生成
anti_injector_logger.warning(f"用户被反注入系统封禁,阻止回复生成: {reason}")
return None
elif result == ProcessResult.BLOCKED_INJECTION:
# 消息被阻止(危险内容等) - 直接阻止回复生成
anti_injector_logger.warning(f"消息被反注入系统阻止,阻止回复生成: {reason}")
return None
elif result == ProcessResult.COUNTER_ATTACK:
# 反击模式:生成反击消息作为回复
anti_injector_logger.info(f"反击模式启动,生成反击回复: {reason}")
if modified_content:
# 返回反击消息作为回复内容
return [("text", modified_content)]
else:
# 没有反击内容时阻止回复生成
return None
# 检查是否需要加盾处理
safety_prompt = None
if result == ProcessResult.SHIELDED:
# 获取安全系统提示词并注入
shield = anti_injector.shield
safety_prompt = shield.get_safety_system_prompt()
await Prompt.create_async(safety_prompt, "anti_injection_safety_prompt")
anti_injector_logger.info(f"消息已被反注入系统加盾处理,已注入安全提示词: {reason}")
# 处理被修改的消息内容(用于生成回复)
modified_reply_to = reply_to
if modified_content:
# 更新消息内容用于生成回复
anti_injector_logger.info(f"消息内容已被反注入系统修改,使用修改后内容生成回复: {reason}")
# 解析原始reply_to格式"发送者:消息内容"
if ":" in reply_to:
sender_part, _ = reply_to.split(":", 1)
modified_reply_to = f"{sender_part}:{modified_content}"
else:
# 如果格式不标准,直接使用修改后的内容
modified_reply_to = modified_content
# === 正常的回复生成流程 ===
success, reply_set, _ = await generator_api.generate_reply(
chat_stream=self.context.chat_stream,
reply_to=modified_reply_to, # 使用可能被修改的内容
available_actions=available_actions,
enable_tool=global_config.tool.enable_tool,
request_type=request_type,
from_plugin=False,
)
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.context.log_prefix}回复生成出现错误:{str(e)} {traceback.format_exc()}")
return None

View File

@@ -10,7 +10,7 @@ from src.manager.local_store_manager import local_storage
from src.plugin_system.apis import send_api, generator_api from src.plugin_system.apis import send_api, generator_api
if TYPE_CHECKING: if TYPE_CHECKING:
from src.chat.chat_loop.wakeup_manager import WakeUpManager from mmc.src.chat.chat_loop.sleep_manager.wakeup_manager import WakeUpManager
logger = get_logger("sleep_manager") logger = get_logger("sleep_manager")

View File

@@ -4,7 +4,7 @@ from typing import Optional
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.manager.local_store_manager import local_storage from src.manager.local_store_manager import local_storage
from .hfc_context import HfcContext from ..hfc_context import HfcContext
logger = get_logger("wakeup") logger = get_logger("wakeup")
@@ -139,7 +139,7 @@ class WakeUpManager:
# 只有在休眠且非失眠状态下才累积唤醒度 # 只有在休眠且非失眠状态下才累积唤醒度
from src.schedule.schedule_manager import schedule_manager from src.schedule.schedule_manager import schedule_manager
from src.schedule.sleep_manager import SleepState from mmc.src.chat.chat_loop.sleep_manager.sleep_manager import SleepState
current_sleep_state = schedule_manager.get_current_sleep_state() current_sleep_state = schedule_manager.get_current_sleep_state()
if current_sleep_state != SleepState.SLEEPING: if current_sleep_state != SleepState.SLEEPING:

View File

@@ -85,6 +85,7 @@ class ChatStream:
self.context: ChatMessageContext = None # type: ignore # 用于存储该聊天的上下文信息 self.context: ChatMessageContext = None # type: ignore # 用于存储该聊天的上下文信息
self.focus_energy = 1 self.focus_energy = 1
self.no_reply_consecutive = 0 self.no_reply_consecutive = 0
self.breaking_accumulated_interest = 0.0
def to_dict(self) -> dict: def to_dict(self) -> dict:
"""转换为字典格式""" """转换为字典格式"""
@@ -97,6 +98,7 @@ class ChatStream:
"last_active_time": self.last_active_time, "last_active_time": self.last_active_time,
"energy_value": self.energy_value, "energy_value": self.energy_value,
"sleep_pressure": self.sleep_pressure, "sleep_pressure": self.sleep_pressure,
"breaking_accumulated_interest": self.breaking_accumulated_interest,
} }
@classmethod @classmethod
@@ -257,7 +259,7 @@ class ChatManager:
"user_cardname": model_instance.user_cardname or "", "user_cardname": model_instance.user_cardname or "",
} }
group_info_data = None group_info_data = None
if model_instance.group_id: if model_instance and getattr(model_instance, "group_id", None):
group_info_data = { group_info_data = {
"platform": model_instance.group_platform, "platform": model_instance.group_platform,
"group_id": model_instance.group_id, "group_id": model_instance.group_id,
@@ -403,7 +405,7 @@ class ChatManager:
"user_cardname": model_instance.user_cardname or "", "user_cardname": model_instance.user_cardname or "",
} }
group_info_data = None group_info_data = None
if model_instance.group_id: if model_instance and getattr(model_instance, "group_id", None):
group_info_data = { group_info_data = {
"platform": model_instance.group_platform, "platform": model_instance.group_platform,
"group_id": model_instance.group_id, "group_id": model_instance.group_id,

View File

@@ -162,9 +162,7 @@ class ActionModifier:
available_actions = list(self.action_manager.get_using_actions().keys()) available_actions = list(self.action_manager.get_using_actions().keys())
available_actions_text = "".join(available_actions) if available_actions else "" available_actions_text = "".join(available_actions) if available_actions else ""
logger.info( logger.info(f"{self.log_prefix} 当前可用动作: {available_actions_text}||移除: {removals_summary}")
f"{self.log_prefix} 当前可用动作: {available_actions_text}||移除: {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):
type_mismatched_actions: List[Tuple[str, str]] = [] type_mismatched_actions: List[Tuple[str, str]] = []

View File

@@ -188,15 +188,12 @@ class ActionPlanner:
param_text = "" param_text = ""
if action_info.action_parameters: if action_info.action_parameters:
param_text = "\n" + "\n".join( param_text = "\n" + "\n".join(
f' "{p_name}":"{p_desc}"' f' "{p_name}":"{p_desc}"' for p_name, p_desc in action_info.action_parameters.items()
for p_name, p_desc in action_info.action_parameters.items()
) )
require_text = "\n".join(f"- {req}" for req in action_info.action_require) require_text = "\n".join(f"- {req}" for req in action_info.action_require)
using_action_prompt = await global_prompt_manager.get_prompt_async( using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt")
"action_prompt"
)
action_options_block += using_action_prompt.format( action_options_block += using_action_prompt.format(
action_name=action_name, action_name=action_name,
action_description=action_info.description, action_description=action_info.description,
@@ -205,9 +202,7 @@ class ActionPlanner:
) )
return action_options_block return action_options_block
def find_message_by_id( def find_message_by_id(self, message_id: str, message_id_list: list) -> Optional[Dict[str, Any]]:
self, message_id: str, message_id_list: list
) -> Optional[Dict[str, Any]]:
# sourcery skip: use-next # sourcery skip: use-next
""" """
根据message_id从message_id_list中查找对应的原始消息 根据message_id从message_id_list中查找对应的原始消息
@@ -245,7 +240,7 @@ class ActionPlanner:
async def plan( async def plan(
self, self,
mode: ChatMode = ChatMode.FOCUS, mode: ChatMode = ChatMode.FOCUS,
loop_start_time:float = 0.0, loop_start_time: float = 0.0,
available_actions: Optional[Dict[str, ActionInfo]] = None, available_actions: Optional[Dict[str, ActionInfo]] = None,
) -> Tuple[List[Dict[str, Any]], Optional[Dict[str, Any]]]: ) -> Tuple[List[Dict[str, Any]], Optional[Dict[str, Any]]]:
""" """
@@ -323,11 +318,15 @@ class ActionPlanner:
# 如果获取的target_message为None输出warning并重新plan # 如果获取的target_message为None输出warning并重新plan
if target_message is None: if target_message is None:
self.plan_retry_count += 1 self.plan_retry_count += 1
logger.warning(f"{self.log_prefix}无法找到target_message_id '{target_message_id}' 对应的消息,重试次数: {self.plan_retry_count}/{self.max_plan_retries}") logger.warning(
f"{self.log_prefix}无法找到target_message_id '{target_message_id}' 对应的消息,重试次数: {self.plan_retry_count}/{self.max_plan_retries}"
)
# 如果连续三次plan均为None输出error并选取最新消息 # 如果连续三次plan均为None输出error并选取最新消息
if self.plan_retry_count >= self.max_plan_retries: if self.plan_retry_count >= self.max_plan_retries:
logger.error(f"{self.log_prefix}连续{self.max_plan_retries}次plan获取target_message失败选择最新消息作为target_message") logger.error(
f"{self.log_prefix}连续{self.max_plan_retries}次plan获取target_message失败选择最新消息作为target_message"
)
target_message = self.get_latest_message(message_id_list) target_message = self.get_latest_message(message_id_list)
self.plan_retry_count = 0 # 重置计数器 self.plan_retry_count = 0 # 重置计数器
else: else:
@@ -339,7 +338,6 @@ class ActionPlanner:
else: else:
logger.warning(f"{self.log_prefix}动作'{action}'缺少target_message_id") logger.warning(f"{self.log_prefix}动作'{action}'缺少target_message_id")
if action != "no_reply" and action != "reply" and action not in current_available_actions: if 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'"
@@ -363,35 +361,34 @@ class ActionPlanner:
if mode == ChatMode.NORMAL and action in current_available_actions: if mode == ChatMode.NORMAL and action in current_available_actions:
is_parallel = current_available_actions[action].parallel_action is_parallel = current_available_actions[action].parallel_action
action_data["loop_start_time"] = loop_start_time action_data["loop_start_time"] = loop_start_time
actions = [] actions = []
# 1. 添加Planner取得的动作 # 1. 添加Planner取得的动作
actions.append({ actions.append(
{
"action_type": action, "action_type": action,
"reasoning": reasoning, "reasoning": reasoning,
"action_data": action_data, "action_data": action_data,
"action_message": target_message, "action_message": target_message,
"available_actions": available_actions # 添加这个字段 "available_actions": available_actions, # 添加这个字段
}) }
)
if action != "reply" and is_parallel: if action != "reply" and is_parallel:
actions.append({ actions.append(
"action_type": "reply", {"action_type": "reply", "action_message": target_message, "available_actions": available_actions}
"action_message": target_message, )
"available_actions": available_actions
})
return actions,target_message return actions, target_message
async def build_planner_prompt( async def build_planner_prompt(
self, self,
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],
refresh_time :bool = False, refresh_time: bool = False,
mode: ChatMode = ChatMode.FOCUS, mode: ChatMode = ChatMode.FOCUS,
) -> tuple[str, list]: # sourcery skip: use-join ) -> tuple[str, list]: # sourcery skip: use-join
"""构建 Planner LLM 的提示词 (获取模板并填充数据)""" """构建 Planner LLM 的提示词 (获取模板并填充数据)"""
@@ -400,21 +397,15 @@ class ActionPlanner:
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
bot_name = global_config.bot.nickname bot_name = global_config.bot.nickname
bot_nickname = ( bot_nickname = (
f",也有人叫你{','.join(global_config.bot.alias_names)}" f",也有人叫你{','.join(global_config.bot.alias_names)}" if global_config.bot.alias_names else ""
if global_config.bot.alias_names
else ""
) )
bot_core_personality = global_config.personality.personality_core bot_core_personality = global_config.personality.personality_core
identity_block = ( identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}"
f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}"
)
schedule_block = "" schedule_block = ""
if global_config.schedule.enable: if global_config.schedule.enable:
if current_activity := schedule_manager.get_current_activity(): if current_activity := schedule_manager.get_current_activity():
schedule_block = ( schedule_block = f"你当前正在:{current_activity},但注意它与群聊的聊天无关。"
f"你当前正在:{current_activity},但注意它与群聊的聊天无关。"
)
mood_block = "" mood_block = ""
if global_config.mood.enable_mood: if global_config.mood.enable_mood:
@@ -424,13 +415,9 @@ class ActionPlanner:
# --- 根据模式构建不同的Prompt --- # --- 根据模式构建不同的Prompt ---
if mode == ChatMode.PROACTIVE: if mode == ChatMode.PROACTIVE:
long_term_memory_block = await self._get_long_term_memory_context() long_term_memory_block = await self._get_long_term_memory_context()
action_options_text = await self._build_action_options( action_options_text = await self._build_action_options(current_available_actions, mode)
current_available_actions, mode
)
prompt_template = await global_prompt_manager.get_prompt_async( prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt")
"proactive_planner_prompt"
)
prompt = prompt_template.format( prompt = prompt_template.format(
time_block=time_block, time_block=time_block,
identity_block=identity_block, identity_block=identity_block,
@@ -463,12 +450,8 @@ class ActionPlanner:
limit=5, limit=5,
) )
actions_before_now_block = build_readable_actions( actions_before_now_block = build_readable_actions(actions=actions_before_now)
actions=actions_before_now actions_before_now_block = f"你刚刚选择并执行过的action是\n{actions_before_now_block}"
)
actions_before_now_block = (
f"你刚刚选择并执行过的action是\n{actions_before_now_block}"
)
if refresh_time: if refresh_time:
self.last_obs_time_mark = time.time() self.last_obs_time_mark = time.time()
@@ -507,27 +490,19 @@ class ActionPlanner:
chat_target_name = None chat_target_name = None
if not is_group_chat and chat_target_info: if not is_group_chat and chat_target_info:
chat_target_name = ( chat_target_name = (
chat_target_info.get("person_name") chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or "对方"
or chat_target_info.get("user_nickname")
or "对方"
) )
chat_context_description = f"你正在和 {chat_target_name} 私聊" chat_context_description = f"你正在和 {chat_target_name} 私聊"
action_options_block = await self._build_action_options( action_options_block = await self._build_action_options(current_available_actions, mode)
current_available_actions, mode
)
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。" moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
custom_prompt_block = "" custom_prompt_block = ""
if global_config.custom_prompt.planner_custom_prompt_content: if global_config.custom_prompt.planner_custom_prompt_content:
custom_prompt_block = ( custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content
global_config.custom_prompt.planner_custom_prompt_content
)
planner_prompt_template = await global_prompt_manager.get_prompt_async( planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt")
"planner_prompt"
)
prompt = planner_prompt_template.format( prompt = planner_prompt_template.format(
schedule_block=schedule_block, schedule_block=schedule_block,
mood_block=mood_block, mood_block=mood_block,
@@ -555,9 +530,7 @@ class ActionPlanner:
""" """
is_group_chat = True is_group_chat = True
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( logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}")
f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}"
)
current_available_actions_dict = self.action_manager.get_using_actions() current_available_actions_dict = self.action_manager.get_using_actions()
@@ -568,13 +541,9 @@ class ActionPlanner:
current_available_actions = {} current_available_actions = {}
for action_name in current_available_actions_dict: for action_name in current_available_actions_dict:
if action_name in all_registered_actions: if action_name in all_registered_actions:
current_available_actions[action_name] = all_registered_actions[ current_available_actions[action_name] = all_registered_actions[action_name]
action_name
]
else: else:
logger.warning( logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到")
f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到"
)
# 将no_reply作为系统级特殊动作添加到可用动作中 # 将no_reply作为系统级特殊动作添加到可用动作中
# no_reply虽然是系统级决策但需要让规划器认为它是可用的 # no_reply虽然是系统级决策但需要让规划器认为它是可用的

View File

@@ -15,7 +15,6 @@ from src.chat.utils.prompt_utils import PromptUtils
from src.mais4u.mai_think import mai_thinking_manager from src.mais4u.mai_think import mai_thinking_manager
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import global_config, model_config from src.config.config import global_config, model_config
from src.config.api_ada_configs import TaskConfig
from src.individuality.individuality import get_individuality from src.individuality.individuality import get_individuality
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest
from src.chat.message_receive.message import UserInfo, Seg, MessageRecv, MessageSending from src.chat.message_receive.message import UserInfo, Seg, MessageRecv, MessageSending
@@ -819,7 +818,7 @@ class DefaultReplyer:
mood_prompt = "" mood_prompt = ""
if reply_to: if reply_to:
#兼容旧的reply_to # 兼容旧的reply_to
sender, target = self._parse_reply_target(reply_to) sender, target = self._parse_reply_target(reply_to)
else: else:
# 获取 platform如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值 # 获取 platform如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值
@@ -830,7 +829,7 @@ class DefaultReplyer:
) )
person_name = await person_info_manager.get_value(person_id, "person_name") person_name = await person_info_manager.get_value(person_id, "person_name")
sender = person_name sender = person_name
target = reply_message.get('processed_plain_text') target = reply_message.get("processed_plain_text")
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id_by_person_name(sender) person_id = person_info_manager.get_person_id_by_person_name(sender)
@@ -1178,7 +1177,9 @@ class DefaultReplyer:
else: else:
logger.debug(f"\n{prompt}\n") logger.debug(f"\n{prompt}\n")
content, (reasoning_content, model_name, tool_calls) = await self.express_model.generate_response_async(prompt) content, (reasoning_content, model_name, tool_calls) = await self.express_model.generate_response_async(
prompt
)
logger.debug(f"replyer生成内容: {content}") logger.debug(f"replyer生成内容: {content}")
return content, reasoning_content, model_name, tool_calls return content, reasoning_content, model_name, tool_calls

View File

@@ -1,7 +1,6 @@
from typing import Dict, Optional, List, Tuple from typing import Dict, Optional
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.api_ada_configs import TaskConfig
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
from src.chat.replyer.default_generator import DefaultReplyer from src.chat.replyer.default_generator import DefaultReplyer

View File

@@ -4,17 +4,13 @@
""" """
import re import re
import time
from typing import Dict, Any, Optional, Tuple from typing import Dict, Any, Optional, Tuple
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.chat.utils.chat_message_builder import (
get_raw_msg_before_timestamp_with_chat,
build_readable_messages_with_id,
)
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from src.person_info.person_info import get_person_info_manager from src.person_info.person_info import get_person_info_manager
from src.plugin_system.apis import cross_context_api
logger = get_logger("prompt_utils") logger = get_logger("prompt_utils")
@@ -88,109 +84,25 @@ class PromptUtils:
) -> str: ) -> str:
""" """
构建跨群聊上下文 - 统一实现完全继承DefaultReplyer功能 构建跨群聊上下文 - 统一实现完全继承DefaultReplyer功能
Args:
chat_id: 当前聊天ID
target_user_info: 目标用户信息
current_prompt_mode: 当前提示模式
Returns:
str: 跨群上下文块
""" """
if not global_config.cross_context.enable: if not global_config.cross_context.enable:
return "" return ""
# 找到当前群聊所在的共享组 other_chat_raw_ids = cross_context_api.get_context_groups(chat_id)
target_group = None if not other_chat_raw_ids:
current_stream = get_chat_manager().get_stream(chat_id)
if not current_stream or not current_stream.group_info:
return "" return ""
try: chat_stream = get_chat_manager().get_stream(chat_id)
current_chat_raw_id = current_stream.group_info.group_id if not chat_stream:
except Exception as e:
logger.error(f"获取群聊ID失败: {e}")
return "" return ""
for group in global_config.cross_context.groups:
if str(current_chat_raw_id) in group.chat_ids:
target_group = group
break
if not target_group:
return ""
# 根据prompt_mode选择策略
other_chat_raw_ids = [chat_id for chat_id in target_group.chat_ids if chat_id != str(current_chat_raw_id)]
cross_context_messages = []
if current_prompt_mode == "normal": if current_prompt_mode == "normal":
# normal模式获取其他群聊的最近N条消息 return await cross_context_api.build_cross_context_normal(chat_stream, other_chat_raw_ids)
for chat_raw_id in other_chat_raw_ids:
stream_id = get_chat_manager().get_stream_id(current_stream.platform, chat_raw_id, is_group=True)
if not stream_id:
continue
try:
messages = get_raw_msg_before_timestamp_with_chat(
chat_id=stream_id,
timestamp=time.time(),
limit=5, # 可配置
)
if messages:
chat_name = get_chat_manager().get_stream_name(stream_id) or stream_id
formatted_messages, _ = build_readable_messages_with_id(messages, timestamp_mode="relative")
cross_context_messages.append(f'[以下是来自"{chat_name}"的近期消息]\n{formatted_messages}')
except Exception as e:
logger.error(f"获取群聊{chat_raw_id}的消息失败: {e}")
continue
elif current_prompt_mode == "s4u": elif current_prompt_mode == "s4u":
# s4u模式获取当前发言用户在其他群聊的消息 return await cross_context_api.build_cross_context_s4u(chat_stream, other_chat_raw_ids, target_user_info)
if target_user_info:
user_id = target_user_info.get("user_id")
if user_id:
for chat_raw_id in other_chat_raw_ids:
stream_id = get_chat_manager().get_stream_id(
current_stream.platform, chat_raw_id, is_group=True
)
if not stream_id:
continue
try:
messages = get_raw_msg_before_timestamp_with_chat(
chat_id=stream_id,
timestamp=time.time(),
limit=20, # 获取更多消息以供筛选
)
user_messages = [msg for msg in messages if msg.get("user_id") == user_id][
-5:
] # 筛选并取最近5条
if user_messages:
chat_name = get_chat_manager().get_stream_name(stream_id) or stream_id
user_name = (
target_user_info.get("person_name")
or target_user_info.get("user_nickname")
or user_id
)
formatted_messages, _ = build_readable_messages_with_id(
user_messages, timestamp_mode="relative"
)
cross_context_messages.append(
f'[以下是"{user_name}""{chat_name}"的近期发言]\n{formatted_messages}'
)
except Exception as e:
logger.error(f"获取用户{user_id}在群聊{chat_raw_id}的消息失败: {e}")
continue
if not cross_context_messages:
return "" return ""
return "# 跨群上下文参考\n" + "\n\n".join(cross_context_messages) + "\n"
@staticmethod @staticmethod
def parse_reply_target_id(reply_to: str) -> str: def parse_reply_target_id(reply_to: str) -> str:
""" """

View File

@@ -4,7 +4,6 @@ import time
import hashlib import hashlib
import uuid import uuid
import io import io
import asyncio
import numpy as np import numpy as np
from typing import Optional, Tuple, Dict, Any from typing import Optional, Tuple, Dict, Any
@@ -35,8 +34,7 @@ def is_image_message(message: Dict[str, Any]) -> bool:
bool: 是否为图片消息 bool: 是否为图片消息
""" """
return message.get("type") == "image" or ( return message.get("type") == "image" or (
isinstance(message.get("content"), dict) and isinstance(message.get("content"), dict) and message["content"].get("type") == "image"
message["content"].get("type") == "image"
) )
@@ -596,7 +594,6 @@ class ImageManager:
return "", "[图片]" return "", "[图片]"
# 创建全局单例 # 创建全局单例
image_manager = None image_manager = None

View File

@@ -62,10 +62,12 @@ def get_active_plans_for_month(month: str) -> List[MonthlyPlan]:
""" """
with get_db_session() as session: with get_db_session() as session:
try: try:
plans = session.query(MonthlyPlan).filter( plans = (
MonthlyPlan.target_month == month, session.query(MonthlyPlan)
MonthlyPlan.status == 'active' .filter(MonthlyPlan.target_month == month, MonthlyPlan.status == "active")
).order_by(MonthlyPlan.created_at.desc()).all() .order_by(MonthlyPlan.created_at.desc())
.all()
)
return plans return plans
except Exception as e: except Exception as e:
logger.error(f"查询 {month} 的有效月度计划时发生错误: {e}") logger.error(f"查询 {month} 的有效月度计划时发生错误: {e}")

View File

@@ -259,7 +259,6 @@ class NormalChatConfig(ValidatedConfigBase):
"""普通聊天配置类""" """普通聊天配置类"""
class ExpressionRule(ValidatedConfigBase): class ExpressionRule(ValidatedConfigBase):
"""表达学习规则""" """表达学习规则"""
@@ -652,7 +651,10 @@ class ContextGroup(ValidatedConfigBase):
"""上下文共享组配置""" """上下文共享组配置"""
name: str = Field(..., description="共享组的名称") name: str = Field(..., description="共享组的名称")
chat_ids: List[str] = Field(..., description="属于该组的聊天ID列表") chat_ids: List[List[str]] = Field(
...,
description='属于该组的聊天ID列表格式为 [["type", "chat_id"], ...],例如 [["group", "123456"], ["private", "789012"]]',
)
class CrossContextConfig(ValidatedConfigBase): class CrossContextConfig(ValidatedConfigBase):

View File

@@ -29,9 +29,54 @@ from src.plugin_system.core.plugin_hot_reload import hot_reload_manager
# 导入消息API和traceback模块 # 导入消息API和traceback模块
from src.common.message import get_global_api from src.common.message import get_global_api
# 条件导入记忆系统 from src.chat.memory_system.Hippocampus import hippocampus_manager
if global_config.memory.enable_memory:
from src.chat.memory_system.Hippocampus import hippocampus_manager if not global_config.memory.enable_memory:
import src.chat.memory_system.Hippocampus as hippocampus_module
class MockHippocampusManager:
def initialize(self):
pass
def get_hippocampus(self):
return None
async def build_memory(self):
pass
async def forget_memory(self, percentage: float = 0.005):
pass
async def consolidate_memory(self):
pass
async def get_memory_from_text(
self,
text: str,
max_memory_num: int = 3,
max_memory_length: int = 2,
max_depth: int = 3,
fast_retrieval: bool = False,
) -> list:
return []
async def get_memory_from_topic(
self, valid_keywords: list[str], max_memory_num: int = 3, max_memory_length: int = 2, max_depth: int = 3
) -> list:
return []
async def get_activate_from_text(
self, text: str, max_depth: int = 3, fast_retrieval: bool = False
) -> tuple[float, list[str]]:
return 0.0, []
def get_memory_from_keyword(self, keyword: str, max_depth: int = 2) -> list:
return []
def get_all_node_names(self) -> list:
return []
hippocampus_module.hippocampus_manager = MockHippocampusManager()
# 插件系统现在使用统一的插件加载器 # 插件系统现在使用统一的插件加载器
@@ -42,11 +87,7 @@ logger = get_logger("main")
class MainSystem: class MainSystem:
def __init__(self): def __init__(self):
# 根据配置条件性地初始化记忆系统
if global_config.memory.enable_memory:
self.hippocampus_manager = hippocampus_manager self.hippocampus_manager = hippocampus_manager
else:
self.hippocampus_manager = None
self.individuality: Individuality = get_individuality() self.individuality: Individuality = get_individuality()
@@ -103,8 +144,6 @@ class MainSystem:
else: else:
loop.run_until_complete(async_memory_manager.shutdown()) loop.run_until_complete(async_memory_manager.shutdown())
logger.info("🛑 记忆管理器已停止") logger.info("🛑 记忆管理器已停止")
except ImportError:
pass # 异步记忆优化器不存在
except Exception as e: except Exception as e:
logger.error(f"停止记忆管理器时出错: {e}") logger.error(f"停止记忆管理器时出错: {e}")
@@ -189,7 +228,6 @@ MoFox_Bot(第三方修改版)
get_emoji_manager().initialize() get_emoji_manager().initialize()
logger.info("表情包管理器初始化成功") logger.info("表情包管理器初始化成功")
# 启动情绪管理器 # 启动情绪管理器
await mood_manager.start() await mood_manager.start()
logger.info("情绪管理器初始化成功") logger.info("情绪管理器初始化成功")
@@ -201,9 +239,7 @@ MoFox_Bot(第三方修改版)
logger.info("聊天管理器初始化成功") logger.info("聊天管理器初始化成功")
# 根据配置条件性地初始化记忆系统 # 初始化记忆系统
if global_config.memory.enable_memory:
if self.hippocampus_manager:
self.hippocampus_manager.initialize() self.hippocampus_manager.initialize()
logger.info("记忆系统初始化成功") logger.info("记忆系统初始化成功")
@@ -215,8 +251,6 @@ MoFox_Bot(第三方修改版)
logger.info("记忆管理器初始化成功") logger.info("记忆管理器初始化成功")
except Exception as e: except Exception as e:
logger.error(f"记忆管理器初始化失败: {e}") logger.error(f"记忆管理器初始化失败: {e}")
else:
logger.info("记忆系统已禁用,跳过初始化")
# await asyncio.sleep(0.5) #防止logger输出飞了 # await asyncio.sleep(0.5) #防止logger输出飞了
@@ -265,8 +299,7 @@ MoFox_Bot(第三方修改版)
self.server.run(), self.server.run(),
] ]
# 根据配置条件性地添加记忆系统相关任务 # 添加记忆系统相关任务
if global_config.memory.enable_memory and self.hippocampus_manager:
tasks.extend( tasks.extend(
[ [
self.build_memory_task(), self.build_memory_task(),
@@ -305,10 +338,6 @@ MoFox_Bot(第三方修改版)
def sync_build_memory(): def sync_build_memory():
"""在线程池中执行同步记忆构建""" """在线程池中执行同步记忆构建"""
if not self.hippocampus_manager:
logger.error("尝试在禁用记忆系统时构建记忆,操作已取消。")
return
try: try:
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)

View File

@@ -0,0 +1,180 @@
"""
跨群聊上下文API
"""
import time
from typing import Dict, Any, Optional, List
from src.common.logger import get_logger
from src.config.config import global_config
from src.chat.utils.chat_message_builder import (
get_raw_msg_before_timestamp_with_chat,
build_readable_messages_with_id,
)
from src.chat.message_receive.chat_stream import get_chat_manager, ChatStream
logger = get_logger("cross_context_api")
def get_context_groups(chat_id: str) -> Optional[List[List[str]]]:
"""
获取当前聊天所在的共享组的其他聊天ID
"""
current_stream = get_chat_manager().get_stream(chat_id)
if not current_stream:
return None
is_group = current_stream.group_info is not None
if is_group:
assert current_stream.group_info is not None
current_chat_raw_id = current_stream.group_info.group_id
else:
current_chat_raw_id = current_stream.user_info.user_id
current_type = "group" if is_group else "private"
for group in global_config.cross_context.groups:
# 检查当前聊天的ID和类型是否在组的chat_ids中
if [current_type, str(current_chat_raw_id)] in group.chat_ids:
# 返回组内其他聊天的 [type, id] 列表
return [chat_info for chat_info in group.chat_ids if chat_info != [current_type, str(current_chat_raw_id)]]
return None
async def build_cross_context_normal(chat_stream: ChatStream, other_chat_infos: List[List[str]]) -> str:
"""
构建跨群聊/私聊上下文 (Normal模式)
"""
cross_context_messages = []
for chat_type, chat_raw_id in other_chat_infos:
is_group = chat_type == "group"
stream_id = get_chat_manager().get_stream_id(chat_stream.platform, chat_raw_id, is_group=is_group)
if not stream_id:
continue
try:
messages = get_raw_msg_before_timestamp_with_chat(
chat_id=stream_id,
timestamp=time.time(),
limit=5, # 可配置
)
if messages:
chat_name = get_chat_manager().get_stream_name(stream_id) or chat_raw_id
formatted_messages, _ = build_readable_messages_with_id(messages, timestamp_mode="relative")
cross_context_messages.append(f'[以下是来自"{chat_name}"的近期消息]\n{formatted_messages}')
except Exception as e:
logger.error(f"获取聊天 {chat_raw_id} 的消息失败: {e}")
continue
if not cross_context_messages:
return ""
return "# 跨上下文参考\n" + "\n\n".join(cross_context_messages) + "\n"
async def build_cross_context_s4u(
chat_stream: ChatStream,
other_chat_infos: List[List[str]],
target_user_info: Optional[Dict[str, Any]],
) -> str:
"""
构建跨群聊/私聊上下文 (S4U模式)
"""
cross_context_messages = []
if target_user_info:
user_id = target_user_info.get("user_id")
if user_id:
for chat_type, chat_raw_id in other_chat_infos:
is_group = chat_type == "group"
stream_id = get_chat_manager().get_stream_id(chat_stream.platform, chat_raw_id, is_group=is_group)
if not stream_id:
continue
try:
messages = get_raw_msg_before_timestamp_with_chat(
chat_id=stream_id,
timestamp=time.time(),
limit=20, # 获取更多消息以供筛选
)
user_messages = [msg for msg in messages if msg.get("user_id") == user_id][-5:]
if user_messages:
chat_name = get_chat_manager().get_stream_name(stream_id) or chat_raw_id
user_name = (
target_user_info.get("person_name") or target_user_info.get("user_nickname") or user_id
)
formatted_messages, _ = build_readable_messages_with_id(
user_messages, timestamp_mode="relative"
)
cross_context_messages.append(
f'[以下是"{user_name}""{chat_name}"的近期发言]\n{formatted_messages}'
)
except Exception as e:
logger.error(f"获取用户 {user_id} 在聊天 {chat_raw_id} 的消息失败: {e}")
continue
if not cross_context_messages:
return ""
return "# 跨上下文参考\n" + "\n\n".join(cross_context_messages) + "\n"
async def get_chat_history_by_group_name(group_name: str) -> str:
"""
根据互通组名字获取聊天记录
"""
target_group = None
for group in global_config.cross_context.groups:
if group.name == group_name:
target_group = group
break
if not target_group:
return f"找不到名为 {group_name} 的互通组。"
if not target_group.chat_ids:
return f"互通组 {group_name} 中没有配置任何聊天。"
chat_infos = target_group.chat_ids
chat_manager = get_chat_manager()
cross_context_messages = []
for chat_type, chat_raw_id in chat_infos:
is_group = chat_type == "group"
found_stream = None
for stream in chat_manager.streams.values():
if is_group:
if stream.group_info and stream.group_info.group_id == chat_raw_id:
found_stream = stream
break
else: # private
if stream.user_info and stream.user_info.user_id == chat_raw_id and not stream.group_info:
found_stream = stream
break
if not found_stream:
logger.warning(f"在已加载的聊天流中找不到ID为 {chat_raw_id} 的聊天。")
continue
stream_id = found_stream.stream_id
try:
messages = get_raw_msg_before_timestamp_with_chat(
chat_id=stream_id,
timestamp=time.time(),
limit=5, # 可配置
)
if messages:
chat_name = get_chat_manager().get_stream_name(stream_id) or chat_raw_id
formatted_messages, _ = build_readable_messages_with_id(messages, timestamp_mode="relative")
cross_context_messages.append(f'[以下是来自"{chat_name}"的近期消息]\n{formatted_messages}')
except Exception as e:
logger.error(f"获取聊天 {chat_raw_id} 的消息失败: {e}")
continue
if not cross_context_messages:
return f"无法从互通组 {group_name} 中获取任何聊天记录。"
return "# 跨上下文参考\n" + "\n\n".join(cross_context_messages) + "\n"

View File

@@ -12,7 +12,6 @@ import traceback
from typing import Tuple, Any, Dict, List, Optional from typing import Tuple, Any, Dict, List, Optional
from rich.traceback import install from rich.traceback import install
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.api_ada_configs import TaskConfig
from src.chat.replyer.default_generator import DefaultReplyer from src.chat.replyer.default_generator import DefaultReplyer
from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.chat_stream import ChatStream
from src.chat.utils.utils import process_llm_response from src.chat.utils.utils import process_llm_response
@@ -107,9 +106,7 @@ async def generate_reply(
""" """
try: try:
# 获取回复器 # 获取回复器
replyer = get_replyer( replyer = get_replyer(chat_stream, chat_id, request_type=request_type)
chat_stream, chat_id, request_type=request_type
)
if not replyer: if not replyer:
logger.error("[GeneratorAPI] 无法获取回复器") logger.error("[GeneratorAPI] 无法获取回复器")
return False, [], None return False, [], None

View File

@@ -30,7 +30,6 @@
import traceback import traceback
import time import time
import difflib
import asyncio import asyncio
from typing import Optional, Union, Dict, Any from typing import Optional, Union, Dict, Any
from src.common.logger import get_logger from src.common.logger import get_logger
@@ -41,8 +40,6 @@ from maim_message import UserInfo
from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.chat_stream import ChatStream
from src.chat.message_receive.uni_message_sender import HeartFCSender from src.chat.message_receive.uni_message_sender import HeartFCSender
from src.chat.message_receive.message import MessageSending, MessageRecv from src.chat.message_receive.message import MessageSending, MessageRecv
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, replace_user_references_async
from src.person_info.person_info import get_person_info_manager
from maim_message import Seg from maim_message import Seg
from src.config.config import global_config from src.config.config import global_config
@@ -51,6 +48,7 @@ logger = get_logger("send_api")
# 适配器命令响应等待池 # 适配器命令响应等待池
_adapter_response_pool: Dict[str, asyncio.Future] = {} _adapter_response_pool: Dict[str, asyncio.Future] = {}
def message_dict_to_message_recv(message_dict: Dict[str, Any]) -> Optional[MessageRecv]: def message_dict_to_message_recv(message_dict: Dict[str, Any]) -> Optional[MessageRecv]:
"""查找要回复的消息 """查找要回复的消息
@@ -101,6 +99,7 @@ def message_dict_to_message_recv(message_dict: Dict[str, Any]) -> Optional[Messa
logger.info(f"[SendAPI] 找到匹配的回复消息,发送者: {message_dict.get('user_nickname', '')}") logger.info(f"[SendAPI] 找到匹配的回复消息,发送者: {message_dict.get('user_nickname', '')}")
return message_recv return message_recv
def put_adapter_response(request_id: str, response_data: dict) -> None: def put_adapter_response(request_id: str, response_data: dict) -> None:
"""将适配器响应放入响应池""" """将适配器响应放入响应池"""
if request_id in _adapter_response_pool: if request_id in _adapter_response_pool:
@@ -234,7 +233,6 @@ async def _send_to_target(
return False return False
# ============================================================================= # =============================================================================
# 公共API函数 - 预定义类型的发送函数 # 公共API函数 - 预定义类型的发送函数
# ============================================================================= # =============================================================================
@@ -274,7 +272,9 @@ async def text_to_stream(
) )
async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True, set_reply: bool = False) -> bool: async def emoji_to_stream(
emoji_base64: str, stream_id: str, storage_message: bool = True, set_reply: bool = False
) -> bool:
"""向指定流发送表情包 """向指定流发送表情包
Args: Args:
@@ -285,10 +285,14 @@ async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bo
Returns: Returns:
bool: 是否发送成功 bool: 是否发送成功
""" """
return await _send_to_target("emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message, set_reply=set_reply) return await _send_to_target(
"emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message, set_reply=set_reply
)
async def image_to_stream(image_base64: str, stream_id: str, storage_message: bool = True, set_reply: bool = False) -> bool: async def image_to_stream(
image_base64: str, stream_id: str, storage_message: bool = True, set_reply: bool = False
) -> bool:
"""向指定流发送图片 """向指定流发送图片
Args: Args:
@@ -299,11 +303,17 @@ async def image_to_stream(image_base64: str, stream_id: str, storage_message: bo
Returns: Returns:
bool: 是否发送成功 bool: 是否发送成功
""" """
return await _send_to_target("image", image_base64, stream_id, "", typing=False, storage_message=storage_message, set_reply=set_reply) return await _send_to_target(
"image", image_base64, stream_id, "", typing=False, storage_message=storage_message, set_reply=set_reply
)
async def command_to_stream( async def command_to_stream(
command: Union[str, dict], stream_id: str, storage_message: bool = True, display_message: str = "", set_reply: bool = False command: Union[str, dict],
stream_id: str,
storage_message: bool = True,
display_message: str = "",
set_reply: bool = False,
) -> bool: ) -> bool:
"""向指定流发送命令 """向指定流发送命令

View File

@@ -200,7 +200,7 @@ class PluginManager:
# 检查并调用 on_plugin_loaded 钩子(如果存在) # 检查并调用 on_plugin_loaded 钩子(如果存在)
if hasattr(plugin_instance, "on_plugin_loaded") and callable( if hasattr(plugin_instance, "on_plugin_loaded") and callable(
getattr(plugin_instance, "on_plugin_loaded") plugin_instance.on_plugin_loaded
): ):
logger.debug(f"为插件 '{plugin_name}' 调用 on_plugin_loaded 钩子") logger.debug(f"为插件 '{plugin_name}' 调用 on_plugin_loaded 钩子")
try: try:

View File

@@ -53,7 +53,9 @@ class MaiZoneRefactoredPlugin(BasePlugin):
"enable_reply": ConfigField(type=bool, default=True, description="完成后是否回复"), "enable_reply": ConfigField(type=bool, default=True, description="完成后是否回复"),
"ai_image_number": ConfigField(type=int, default=1, description="AI生成图片数量"), "ai_image_number": ConfigField(type=int, default=1, description="AI生成图片数量"),
"image_number": ConfigField(type=int, default=1, description="本地配图数量1-9张"), "image_number": ConfigField(type=int, default=1, description="本地配图数量1-9张"),
"image_directory": ConfigField(type=str, default=(Path(__file__).parent / "images").as_posix(), description="图片存储目录") "image_directory": ConfigField(
type=str, default=(Path(__file__).parent / "images").as_posix(), description="图片存储目录"
),
}, },
"read": { "read": {
"permission": ConfigField(type=list, default=[], description="阅读权限QQ号列表"), "permission": ConfigField(type=list, default=[], description="阅读权限QQ号列表"),
@@ -75,7 +77,9 @@ class MaiZoneRefactoredPlugin(BasePlugin):
"forbidden_hours_end": ConfigField(type=int, default=6, description="禁止发送的结束小时(24小时制)"), "forbidden_hours_end": ConfigField(type=int, default=6, description="禁止发送的结束小时(24小时制)"),
}, },
"cookie": { "cookie": {
"http_fallback_host": ConfigField(type=str, default="127.0.0.1", description="备用Cookie获取服务的主机地址"), "http_fallback_host": ConfigField(
type=str, default="127.0.0.1", description="备用Cookie获取服务的主机地址"
),
"http_fallback_port": ConfigField(type=int, default=9999, description="备用Cookie获取服务的端口"), "http_fallback_port": ConfigField(type=int, default=9999, description="备用Cookie获取服务的端口"),
"napcat_token": ConfigField(type=str, default="", description="Napcat服务的认证Token可选"), "napcat_token": ConfigField(type=str, default="", description="Napcat服务的认证Token可选"),
}, },
@@ -102,7 +106,7 @@ class MaiZoneRefactoredPlugin(BasePlugin):
content_service, content_service,
image_service, image_service,
cookie_service, cookie_service,
reply_tracker_service # 传入已创建的实例 reply_tracker_service, # 传入已创建的实例
) )
scheduler_service = SchedulerService(self.get_config, qzone_service) scheduler_service = SchedulerService(self.get_config, qzone_service)
monitor_service = MonitorService(self.get_config, qzone_service) monitor_service = MonitorService(self.get_config, qzone_service)

View File

@@ -13,6 +13,7 @@ from src.common.logger import get_logger
import imghdr import imghdr
import asyncio import asyncio
from src.plugin_system.apis import llm_api, config_api, generator_api from src.plugin_system.apis import llm_api, config_api, generator_api
from src.plugin_system.apis.cross_context_api import get_chat_history_by_group_name
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from maim_message import UserInfo from maim_message import UserInfo
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest
@@ -87,6 +88,11 @@ class ContentService:
if context: if context:
prompt += f"\n作为参考,这里有一些最近的聊天记录:\n---\n{context}\n---" prompt += f"\n作为参考,这里有一些最近的聊天记录:\n---\n{context}\n---"
# 添加跨群聊上下文
cross_context = await get_chat_history_by_group_name("maizone_context_group")
if cross_context and "找不到名为" not in cross_context:
prompt += f"\n\n---跨群聊参考---\n{cross_context}\n---"
# 添加历史记录以避免重复 # 添加历史记录以避免重复
prompt += "\n\n---历史说说记录---\n" prompt += "\n\n---历史说说记录---\n"
history_block = await get_send_history(qq_account) history_block = await get_send_history(qq_account)
@@ -232,7 +238,7 @@ class ContentService:
for i in range(3): # 重试3次 for i in range(3): # 重试3次
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(image_url, timeout=30) as resp: async with session.get(image_url, timeout=aiohttp.ClientTimeout(total=30)) as resp:
if resp.status != 200: if resp.status != 200:
logger.error(f"下载图片失败: {image_url}, status: {resp.status}") logger.error(f"下载图片失败: {image_url}, status: {resp.status}")
await asyncio.sleep(2) await asyncio.sleep(2)

View File

@@ -272,8 +272,10 @@ class QZoneService:
# 检查是否已经在持久化记录中标记为已回复 # 检查是否已经在持久化记录中标记为已回复
if not self.reply_tracker.has_replied(fid, comment_tid): if not self.reply_tracker.has_replied(fid, comment_tid):
# 记录日志以便追踪 # 记录日志以便追踪
logger.debug(f"发现新评论需要回复 - 说说ID: {fid}, 评论ID: {comment_tid}, " logger.debug(
f"评论人: {comment.get('nickname', '')}, 内容: {comment.get('content', '')}") f"发现新评论需要回复 - 说说ID: {fid}, 评论ID: {comment_tid}, "
f"评论人: {comment.get('nickname', '')}, 内容: {comment.get('content', '')}"
)
comments_to_reply.append(comment) comments_to_reply.append(comment)
if not comments_to_reply: if not comments_to_reply:

View File

@@ -74,8 +74,10 @@ class ReplyTrackerService:
data = json.loads(file_content) data = json.loads(file_content)
if self._validate_data(data): if self._validate_data(data):
self.replied_comments = data self.replied_comments = data
logger.info(f"已加载 {len(self.replied_comments)} 条说说的回复记录," logger.info(
f"总计 {sum(len(comments) for comments in self.replied_comments.values())}评论") f"已加载 {len(self.replied_comments)}说说的回复记录,"
f"总计 {sum(len(comments) for comments in self.replied_comments.values())} 条评论"
)
else: else:
logger.error("加载的数据格式无效,将创建新的记录") logger.error("加载的数据格式无效,将创建新的记录")
self.replied_comments = {} self.replied_comments = {}
@@ -112,7 +114,7 @@ class ReplyTrackerService:
self._cleanup_old_records() self._cleanup_old_records()
# 创建临时文件 # 创建临时文件
temp_file = self.reply_record_file.with_suffix('.tmp') temp_file = self.reply_record_file.with_suffix(".tmp")
# 先写入临时文件 # 先写入临时文件
with open(temp_file, "w", encoding="utf-8") as f: with open(temp_file, "w", encoding="utf-8") as f:

View File

@@ -76,7 +76,7 @@ class MonthlyPlanManager:
if len(plans) > max_plans: if len(plans) > max_plans:
logger.warning(f"当前月度计划数量 ({len(plans)}) 超出上限 ({max_plans}),将自动删除多余的计划。") logger.warning(f"当前月度计划数量 ({len(plans)}) 超出上限 ({max_plans}),将自动删除多余的计划。")
# 数据库查询结果已按创建时间降序排序(新的在前),直接截取超出上限的部分进行删除 # 数据库查询结果已按创建时间降序排序(新的在前),直接截取超出上限的部分进行删除
plans_to_delete = plans[:len(plans)-max_plans] plans_to_delete = plans[: len(plans) - max_plans]
delete_ids = [p.id for p in plans_to_delete] delete_ids = [p.id for p in plans_to_delete]
delete_plans_by_ids(delete_ids) delete_plans_by_ids(delete_ids)
# 重新获取计划列表 # 重新获取计划列表
@@ -291,6 +291,8 @@ class MonthlyPlanManager:
except Exception as e: except Exception as e:
logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}") logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}")
class MonthlyPlanGenerationTask(AsyncTask): class MonthlyPlanGenerationTask(AsyncTask):
"""每月初自动生成新月度计划的任务""" """每月初自动生成新月度计划的任务"""

View File

@@ -15,10 +15,10 @@ from src.llm_models.utils_model import LLMRequest
from src.common.logger import get_logger from src.common.logger import get_logger
from json_repair import repair_json from json_repair import repair_json
from src.manager.async_task_manager import AsyncTask, async_task_manager from src.manager.async_task_manager import AsyncTask, async_task_manager
from .sleep_manager import SleepManager, SleepState from ..chat.chat_loop.sleep_manager.sleep_manager import SleepManager, SleepState
if TYPE_CHECKING: if TYPE_CHECKING:
from src.chat.chat_loop.wakeup_manager import WakeUpManager from src.chat.chat_loop.sleep_manager.wakeup_manager import WakeUpManager
logger = get_logger("schedule_manager") logger = get_logger("schedule_manager")
@@ -165,7 +165,9 @@ class ScheduleManager:
schedule_str = f"已成功加载今天的日程 ({today_str})\n" schedule_str = f"已成功加载今天的日程 ({today_str})\n"
if self.today_schedule: if self.today_schedule:
for item in self.today_schedule: for item in self.today_schedule:
schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n" schedule_str += (
f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
)
logger.info(schedule_str) logger.info(schedule_str)
return # 成功加载,直接返回 return # 成功加载,直接返回
else: else:

View File

@@ -1,5 +1,5 @@
[inner] [inner]
version = "6.7.1" version = "6.7.2"
#----以下是给开发人员阅读的如果你只是部署了MoFox-Bot不需要阅读---- #----以下是给开发人员阅读的如果你只是部署了MoFox-Bot不需要阅读----
#如果你想要修改配置文件请递增version的值 #如果你想要修改配置文件请递增version的值
@@ -476,28 +476,25 @@ pre_sleep_notification_groups = []
# 用于生成睡前消息的提示。AI会根据这个提示生成一句晚安问候。 # 用于生成睡前消息的提示。AI会根据这个提示生成一句晚安问候。
pre_sleep_prompt = "我准备睡觉了,请生成一句简短自然的晚安问候。" pre_sleep_prompt = "我准备睡觉了,请生成一句简短自然的晚安问候。"
[cross_context] # 跨群聊上下文共享配置 [cross_context] # 跨群聊/私聊上下文共享配置
# 这是总开关,用于一键启用或禁用此功能 # 这是总开关,用于一键启用或禁用此功能
enable = false enable = true
# 在这里定义您的“共享组” # 在这里定义您的“共享组”
# 只有在同一个组内的聊才会共享上下文 # 只有在同一个组内的聊才会共享上下文
# 注意:这里的chat_ids需要填写群号 # 格式:chat_ids = [["type", "id"], ["type", "id"], ...]
# type 可选 "group" 或 "private"
[[cross_context.groups]] [[cross_context.groups]]
name = "项目A技术讨论组" name = "项目A技术讨论组"
chat_ids = [ chat_ids = [
"111111", # 假设这是“开发群”的ID ["group", "169850076"], # 假设这是“开发群”的ID
"222222" # 假设这是“产品群”的ID ["group", "1025509724"], # 假设这是“产品群”的ID
["private", "123456789"] # 假设这是某个用户的私聊
] ]
[maizone_intercom]
# QQ空间互通组配置
# 启用后,发布说说时会读取指定互通组的上下文
enable = false
# 定义QQ空间互通组 # 定义QQ空间互通组
# 同一个组的chat_id会共享上下文用于生成更相关的说说 # 同一个组的chat_id会共享上下文用于生成更相关的说说
[[maizone_intercom.groups]] [[cross_context.maizone_context_group]]
name = "Maizone默认互通组" name = "Maizone默认互通组"
chat_ids = [ chat_ids = [
"111111", # 示例群聊1 ["group", "111111"], # 示例群聊1
"222222" # 示例聊2 ["private", "222222"] # 示例聊2
] ]