Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox_Bot into dev
This commit is contained in:
@@ -7,7 +7,6 @@ from . import event_types, CONSTS, event_handlers
|
||||
from typing import List
|
||||
|
||||
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.common.logger import get_logger
|
||||
@@ -37,6 +36,7 @@ def get_classes_in_module(module):
|
||||
classes.append(member)
|
||||
return classes
|
||||
|
||||
|
||||
async def message_recv(server_connection: Server.ServerConnection):
|
||||
await message_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.debug(f"原始消息: {raw_message[:500]}...")
|
||||
|
||||
|
||||
async def message_process():
|
||||
"""消息处理主循环"""
|
||||
logger.info("消息处理器已启动")
|
||||
@@ -132,6 +133,7 @@ async def message_process():
|
||||
except Exception as e:
|
||||
logger.debug(f"清理消息队列时出错: {e}")
|
||||
|
||||
|
||||
async def napcat_server():
|
||||
"""启动 Napcat WebSocket 连接(支持正向和反向连接)"""
|
||||
mode = global_config.napcat_server.mode
|
||||
@@ -143,6 +145,7 @@ async def napcat_server():
|
||||
logger.error(f"启动 WebSocket 连接失败: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def graceful_shutdown():
|
||||
"""优雅关闭所有组件"""
|
||||
try:
|
||||
@@ -189,10 +192,7 @@ async def graceful_shutdown():
|
||||
|
||||
# 等待任务取消完成,忽略 CancelledError
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.gather(*tasks, return_exceptions=True),
|
||||
timeout=10
|
||||
)
|
||||
await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=10)
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("部分任务取消超时")
|
||||
except Exception as e:
|
||||
@@ -214,6 +214,7 @@ async def graceful_shutdown():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class LauchNapcatAdapterHandler(BaseEventHandler):
|
||||
"""自动启动Adapter"""
|
||||
|
||||
@@ -245,6 +246,7 @@ class LauchNapcatAdapterHandler(BaseEventHandler):
|
||||
asyncio.create_task(message_process())
|
||||
asyncio.create_task(check_timeout_response())
|
||||
|
||||
|
||||
class StopNapcatAdapterHandler(BaseEventHandler):
|
||||
"""关闭Adapter"""
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ class VoiceConfig(ConfigBase):
|
||||
use_tts: bool = False
|
||||
"""是否启用TTS功能"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class SlicingConfig(ConfigBase):
|
||||
max_frame_size: int = 64
|
||||
@@ -66,6 +67,7 @@ class SlicingConfig(ConfigBase):
|
||||
delay_ms: int = 10
|
||||
"""切片发送间隔时间,单位为毫秒"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class DebugConfig(ConfigBase):
|
||||
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
用于在 Ada 发送给 MMC 时进行消息切片,利用 WebSocket 协议的自动重组特性
|
||||
仅在 Ada -> MMC 方向进行切片,其他方向(MMC -> Ada,Ada <-> Napcat)不切片
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import asyncio
|
||||
@@ -15,7 +16,6 @@ from src.common.logger import get_logger
|
||||
logger = get_logger("napcat_adapter")
|
||||
|
||||
|
||||
|
||||
class MessageChunker:
|
||||
"""消息切片器,用于处理大消息的分片发送"""
|
||||
|
||||
@@ -29,12 +29,14 @@ class MessageChunker:
|
||||
message_str = json.dumps(message, ensure_ascii=False)
|
||||
else:
|
||||
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:
|
||||
logger.error(f"检查消息大小时出错: {e}")
|
||||
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:
|
||||
chunk_id = str(uuid.uuid4())
|
||||
|
||||
message_bytes = message_str.encode('utf-8')
|
||||
message_bytes = message_str.encode("utf-8")
|
||||
total_size = len(message_bytes)
|
||||
|
||||
# 计算需要多少个切片
|
||||
@@ -83,10 +85,10 @@ class MessageChunker:
|
||||
"total_chunks": num_chunks,
|
||||
"chunk_size": len(chunk_data),
|
||||
"total_size": total_size,
|
||||
"timestamp": time.time()
|
||||
"timestamp": time.time(),
|
||||
},
|
||||
"__mmc_chunk_data__": chunk_data.decode('utf-8', errors='ignore'),
|
||||
"__mmc_is_chunked__": True
|
||||
"__mmc_chunk_data__": chunk_data.decode("utf-8", errors="ignore"),
|
||||
"__mmc_is_chunked__": True,
|
||||
}
|
||||
|
||||
chunks.append(chunk_message)
|
||||
@@ -111,10 +113,10 @@ class MessageChunker:
|
||||
data = message
|
||||
|
||||
return (
|
||||
isinstance(data, dict) and
|
||||
"__mmc_chunk_info__" in data and
|
||||
"__mmc_chunk_data__" in data and
|
||||
"__mmc_is_chunked__" in data
|
||||
isinstance(data, dict)
|
||||
and "__mmc_chunk_info__" in data
|
||||
and "__mmc_chunk_data__" in data
|
||||
and "__mmc_is_chunked__" in data
|
||||
)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return False
|
||||
@@ -152,7 +154,7 @@ class MessageReassembler:
|
||||
|
||||
expired_chunks = []
|
||||
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)
|
||||
|
||||
for chunk_id in expired_chunks:
|
||||
@@ -207,7 +209,7 @@ class MessageReassembler:
|
||||
"chunks": {},
|
||||
"total_chunks": total_chunks,
|
||||
"received_chunks": 0,
|
||||
"timestamp": chunk_timestamp
|
||||
"timestamp": chunk_timestamp,
|
||||
}
|
||||
|
||||
buffer = self.chunk_buffers[chunk_id]
|
||||
@@ -260,7 +262,7 @@ class MessageReassembler:
|
||||
"received": buffer["received_chunks"],
|
||||
"total": 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
|
||||
|
||||
|
||||
@@ -800,7 +800,10 @@ class MessageHandler:
|
||||
content_parts.append(f"链接: {extracted_info['short_url']}")
|
||||
|
||||
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
|
||||
return None
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
from src.common.logger import get_logger
|
||||
@@ -30,14 +29,14 @@ class MessageSending:
|
||||
message_dict = message_base.to_dict()
|
||||
|
||||
if chunker.should_chunk_message(message_dict):
|
||||
logger.info(f"消息过大,进行切片发送到 MaiBot")
|
||||
logger.info("消息过大,进行切片发送到 MaiBot")
|
||||
|
||||
# 切片消息
|
||||
chunks = chunker.chunk_message(message_dict)
|
||||
|
||||
# 逐个发送切片
|
||||
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
|
||||
@@ -49,7 +48,7 @@ class MessageSending:
|
||||
send_status = await client.send_message(chunk)
|
||||
|
||||
if not send_status:
|
||||
logger.error(f"发送切片 {i+1}/{len(chunks)} 失败")
|
||||
logger.error(f"发送切片 {i + 1}/{len(chunks)} 失败")
|
||||
return False
|
||||
|
||||
# 使用配置中的延迟时间
|
||||
|
||||
@@ -5,7 +5,6 @@ import math
|
||||
import random
|
||||
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.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
@@ -95,7 +94,7 @@ class CycleProcessor:
|
||||
|
||||
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 # 控制曲线中心点
|
||||
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:
|
||||
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:
|
||||
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()
|
||||
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 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():
|
||||
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成")
|
||||
|
||||
with Timer("规划器", cycle_timers):
|
||||
actions, _= await self.action_planner.plan(
|
||||
actions, _ = await self.action_planner.plan(
|
||||
mode=mode,
|
||||
loop_start_time=loop_start_time,
|
||||
available_actions=available_actions,
|
||||
@@ -195,12 +204,7 @@ class CycleProcessor:
|
||||
action_name="no_reply",
|
||||
)
|
||||
|
||||
return {
|
||||
"action_type": "no_reply",
|
||||
"success": True,
|
||||
"reply_text": "",
|
||||
"command": ""
|
||||
}
|
||||
return {"action_type": "no_reply", "success": True, "reply_text": "", "command": ""}
|
||||
elif action_info["action_type"] != "reply":
|
||||
# 执行普通动作
|
||||
with Timer("动作执行", cycle_timers):
|
||||
@@ -210,40 +214,32 @@ class CycleProcessor:
|
||||
action_info["action_data"],
|
||||
cycle_timers,
|
||||
thinking_id,
|
||||
action_info["action_message"]
|
||||
action_info["action_message"],
|
||||
)
|
||||
return {
|
||||
"action_type": action_info["action_type"],
|
||||
"success": success,
|
||||
"reply_text": reply_text,
|
||||
"command": command
|
||||
"command": command,
|
||||
}
|
||||
else:
|
||||
try:
|
||||
success, response_set, _ = await generator_api.generate_reply(
|
||||
chat_stream=self.context.chat_stream,
|
||||
reply_message = action_info["action_message"],
|
||||
reply_message=action_info["action_message"],
|
||||
available_actions=available_actions,
|
||||
enable_tool=global_config.tool.enable_tool,
|
||||
request_type="chat.replyer",
|
||||
from_plugin=False,
|
||||
)
|
||||
if not success or not response_set:
|
||||
logger.info(f"对 {action_info['action_message'].get('processed_plain_text')} 的回复生成失败")
|
||||
return {
|
||||
"action_type": "reply",
|
||||
"success": False,
|
||||
"reply_text": "",
|
||||
"loop_info": None
|
||||
}
|
||||
logger.info(
|
||||
f"对 {action_info['action_message'].get('processed_plain_text')} 的回复生成失败"
|
||||
)
|
||||
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
|
||||
except asyncio.CancelledError:
|
||||
logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消")
|
||||
return {
|
||||
"action_type": "reply",
|
||||
"success": False,
|
||||
"reply_text": "",
|
||||
"loop_info": None
|
||||
}
|
||||
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
|
||||
|
||||
loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply(
|
||||
response_set,
|
||||
@@ -253,12 +249,7 @@ class CycleProcessor:
|
||||
thinking_id,
|
||||
actions,
|
||||
)
|
||||
return {
|
||||
"action_type": "reply",
|
||||
"success": True,
|
||||
"reply_text": reply_text,
|
||||
"loop_info": loop_info
|
||||
}
|
||||
return {"action_type": "reply", "success": True, "reply_text": reply_text, "loop_info": loop_info}
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 执行动作时出错: {e}")
|
||||
logger.error(f"{self.log_prefix} 错误信息: {traceback.format_exc()}")
|
||||
@@ -267,7 +258,7 @@ class CycleProcessor:
|
||||
"success": False,
|
||||
"reply_text": "",
|
||||
"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)
|
||||
|
||||
action_type = actions[0]["action_type"] if actions else "no_action"
|
||||
# 管理no_reply计数器:当执行了非no_reply动作时,重置计数器
|
||||
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
|
||||
)
|
||||
return action_type
|
||||
|
||||
async def _handle_action(
|
||||
self, action, reasoning, action_data, cycle_timers, thinking_id, action_message
|
||||
@@ -603,12 +375,12 @@ class CycleProcessor:
|
||||
if "reply" in available_actions:
|
||||
fallback_action = "reply"
|
||||
elif available_actions:
|
||||
fallback_action = list(available_actions.keys())[0]
|
||||
fallback_action = list(available_actions.keys())
|
||||
|
||||
if fallback_action and fallback_action != action:
|
||||
logger.info(f"{self.context.log_prefix} 使用回退动作: {fallback_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,
|
||||
reasoning=f"原动作'{action}'不可用,自动回退。{reasoning}",
|
||||
cycle_timers=cycle_timers,
|
||||
@@ -628,43 +400,3 @@ class CycleProcessor:
|
||||
logger.error(f"{self.context.log_prefix} 处理{action}时出错: {e}")
|
||||
traceback.print_exc()
|
||||
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
|
||||
|
||||
@@ -91,25 +91,24 @@ class CycleTracker:
|
||||
|
||||
# 获取动作类型,兼容新旧格式
|
||||
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
|
||||
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):
|
||||
# 旧格式:action_result是字典
|
||||
action_type = action_result.get('action_type', '未知动作')
|
||||
action_type = action_result.get("action_type", "未知动作")
|
||||
elif isinstance(action_result, list) and action_result:
|
||||
# 新格式: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:
|
||||
# 直接是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:
|
||||
duration = self.context.current_cycle_detail.end_time - self.context.current_cycle_detail.start_time
|
||||
logger.info(
|
||||
f"{self.context.log_prefix} 第{self.context.current_cycle_detail.cycle_id}次思考,"
|
||||
f"耗时: {duration:.1f}秒, "
|
||||
f"选择动作: {action_type}"
|
||||
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
|
||||
f"选择动作: {action_type}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ import time
|
||||
from typing import Optional
|
||||
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
|
||||
from src.schedule.schedule_manager import schedule_manager
|
||||
|
||||
|
||||
@@ -2,24 +2,24 @@ import asyncio
|
||||
import time
|
||||
import traceback
|
||||
import random
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
from typing import Optional, List, Dict, Any
|
||||
from collections import deque
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from src.person_info.relationship_builder_manager import relationship_builder_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.plugin_system.apis import message_api
|
||||
|
||||
from .hfc_context import HfcContext
|
||||
from .energy_manager import EnergyManager
|
||||
from .proactive_thinker import ProactiveThinker
|
||||
from .proactive.proactive_thinker import ProactiveThinker
|
||||
from .cycle_processor import CycleProcessor
|
||||
from .response_handler import ResponseHandler
|
||||
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")
|
||||
|
||||
@@ -54,6 +54,7 @@ class HeartFChatting:
|
||||
self.context.chat_instance = self
|
||||
|
||||
self._loop_task: Optional[asyncio.Task] = None
|
||||
self._proactive_monitor_task: Optional[asyncio.Task] = None
|
||||
|
||||
# 记录最近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.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()
|
||||
|
||||
self._loop_task = asyncio.create_task(self._main_chat_loop())
|
||||
@@ -116,8 +121,12 @@ class HeartFChatting:
|
||||
return
|
||||
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()
|
||||
|
||||
if self._loop_task and not self._loop_task.done():
|
||||
@@ -147,6 +156,151 @@ class HeartFChatting:
|
||||
except asyncio.CancelledError:
|
||||
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):
|
||||
"""
|
||||
主聊天循环
|
||||
@@ -238,30 +392,36 @@ class HeartFChatting:
|
||||
logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。")
|
||||
|
||||
# 根据聊天模式处理新消息
|
||||
# 统一使用 _should_process_messages 判断是否应该处理
|
||||
should_process,interest_value = await self._should_process_messages(recent_messages if has_new_messages else None)
|
||||
if should_process:
|
||||
self.context.last_read_time = time.time()
|
||||
await self.cycle_processor.observe(interest_value = interest_value)
|
||||
else:
|
||||
# Normal模式:消息数量不足,等待
|
||||
should_process, interest_value = await self._should_process_messages(recent_messages)
|
||||
if not should_process:
|
||||
# 消息数量不足或兴趣不够,等待
|
||||
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):
|
||||
return has_new_messages
|
||||
# Messages should be processed
|
||||
action_type = await self.cycle_processor.observe(interest_value=interest_value)
|
||||
|
||||
# 处理新消息
|
||||
for message in recent_messages:
|
||||
await self.cycle_processor.observe(interest_value = interest_value)
|
||||
# 管理no_reply计数器
|
||||
if action_type != "no_reply":
|
||||
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.breaking_accumulated_interest = 0.0
|
||||
logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f},重置累积兴趣值")
|
||||
|
||||
self.context.energy_value += 1 / global_config.chat.focus_value
|
||||
# 重置累积兴趣值,因为消息已经被成功处理
|
||||
self.context.breaking_accumulated_interest = 0.0
|
||||
logger.info(
|
||||
f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f},重置累积兴趣值"
|
||||
)
|
||||
|
||||
# 更新上一帧的睡眠状态
|
||||
self.context.was_sleeping = is_sleeping
|
||||
@@ -282,7 +442,6 @@ class HeartFChatting:
|
||||
|
||||
return has_new_messages
|
||||
|
||||
|
||||
def _handle_wakeup_messages(self, messages):
|
||||
"""
|
||||
处理休眠状态下的消息,累积唤醒度
|
||||
@@ -321,14 +480,15 @@ class HeartFChatting:
|
||||
def _determine_form_type(self) -> str:
|
||||
"""判断使用哪种形式的no_reply"""
|
||||
# 检查是否启用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形式")
|
||||
self.context.focus_energy = 1
|
||||
return
|
||||
return "waiting"
|
||||
|
||||
# 如果连续no_reply次数少于3次,使用waiting形式
|
||||
if self.context.no_reply_consecutive <= 3:
|
||||
self.context.focus_energy = 1
|
||||
return "waiting"
|
||||
else:
|
||||
# 使用累积兴趣值而不是最近3次的记录
|
||||
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)
|
||||
|
||||
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形式
|
||||
if total_interest < adjusted_threshold:
|
||||
logger.info(f"{self.context.log_prefix} 累积兴趣度不足,进入breaking形式")
|
||||
self.context.focus_energy = random.randint(3, 6)
|
||||
return "breaking"
|
||||
else:
|
||||
logger.info(f"{self.context.log_prefix} 累积兴趣度充足,使用waiting形式")
|
||||
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)
|
||||
|
||||
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_interest_threshold = 1.5 / talk_frequency
|
||||
@@ -381,13 +548,15 @@ class HeartFChatting:
|
||||
logger.info(
|
||||
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:
|
||||
# 只在兴趣值变化时输出log
|
||||
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
|
||||
if total_interest >= modified_exit_interest_threshold:
|
||||
# 记录兴趣度到列表
|
||||
@@ -397,13 +566,16 @@ class HeartFChatting:
|
||||
logger.info(
|
||||
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秒输出一次等待状态
|
||||
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(
|
||||
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)
|
||||
|
||||
return False,0.0
|
||||
return False, 0.0
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
import time
|
||||
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.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.chat_loop.hfc_utils import CycleDetail
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .wakeup_manager import WakeUpManager
|
||||
from .sleep_manager.wakeup_manager import WakeUpManager
|
||||
from .energy_manager import EnergyManager
|
||||
from .heartFC_chat import HeartFChatting
|
||||
|
||||
|
||||
class HfcContext:
|
||||
@@ -43,13 +42,13 @@ class HfcContext:
|
||||
|
||||
self.energy_value = self.chat_stream.energy_value
|
||||
self.sleep_pressure = self.chat_stream.sleep_pressure
|
||||
self.was_sleeping = False # 用于检测睡眠状态的切换
|
||||
self.was_sleeping = False # 用于检测睡眠状态的切换
|
||||
|
||||
self.last_message_time = time.time()
|
||||
self.last_read_time = time.time() - 10
|
||||
|
||||
# 从聊天流恢复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()
|
||||
|
||||
@@ -69,7 +68,7 @@ class HfcContext:
|
||||
# breaking形式下的累积兴趣值
|
||||
self.breaking_accumulated_interest = 0.0
|
||||
# 引用HeartFChatting实例,以便其他组件可以调用其方法
|
||||
self.chat_instance = None
|
||||
self.chat_instance: Optional["HeartFChatting"] = None
|
||||
|
||||
def save_context_state(self):
|
||||
"""将当前状态保存到聊天流"""
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import time
|
||||
from typing import Optional, Dict, Any, Union
|
||||
|
||||
from src.config.config import global_config
|
||||
from src.common.logger import get_logger
|
||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||
from src.plugin_system.apis import send_api
|
||||
from maim_message.message_base import GroupInfo
|
||||
|
||||
from src.common.message_repository import count_messages
|
||||
|
||||
logger = get_logger("hfc")
|
||||
|
||||
@@ -122,6 +120,7 @@ class CycleDetail:
|
||||
self.loop_plan_info = loop_info["loop_plan_info"]
|
||||
self.loop_action_info = loop_info["loop_action_info"]
|
||||
|
||||
|
||||
async def send_typing():
|
||||
"""
|
||||
发送打字状态指示
|
||||
|
||||
13
src/chat/chat_loop/proactive/events.py
Normal file
13
src/chat/chat_loop/proactive/events.py
Normal 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) # 可选的元数据,用于传递额外信息
|
||||
125
src/chat/chat_loop/proactive/proactive_thinker.py
Normal file
125
src/chat/chat_loop/proactive/proactive_thinker.py
Normal 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())
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
|
||||
@@ -157,7 +157,7 @@ class ResponseHandler:
|
||||
await send_api.text_to_stream(
|
||||
text=data,
|
||||
stream_id=self.context.stream_id,
|
||||
reply_to_message = message_data,
|
||||
reply_to_message=message_data,
|
||||
set_reply=need_reply,
|
||||
typing=False,
|
||||
)
|
||||
@@ -166,107 +166,9 @@ class ResponseHandler:
|
||||
await send_api.text_to_stream(
|
||||
text=data,
|
||||
stream_id=self.context.stream_id,
|
||||
reply_to_message = message_data,
|
||||
set_reply=need_reply,
|
||||
reply_to_message=None,
|
||||
set_reply=False,
|
||||
typing=True,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@@ -10,7 +10,7 @@ from src.manager.local_store_manager import local_storage
|
||||
from src.plugin_system.apis import send_api, generator_api
|
||||
|
||||
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")
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Optional
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from src.manager.local_store_manager import local_storage
|
||||
from .hfc_context import HfcContext
|
||||
from ..hfc_context import HfcContext
|
||||
|
||||
logger = get_logger("wakeup")
|
||||
|
||||
@@ -139,7 +139,7 @@ class WakeUpManager:
|
||||
|
||||
# 只有在休眠且非失眠状态下才累积唤醒度
|
||||
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()
|
||||
if current_sleep_state != SleepState.SLEEPING:
|
||||
@@ -85,6 +85,7 @@ class ChatStream:
|
||||
self.context: ChatMessageContext = None # type: ignore # 用于存储该聊天的上下文信息
|
||||
self.focus_energy = 1
|
||||
self.no_reply_consecutive = 0
|
||||
self.breaking_accumulated_interest = 0.0
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""转换为字典格式"""
|
||||
@@ -97,6 +98,7 @@ class ChatStream:
|
||||
"last_active_time": self.last_active_time,
|
||||
"energy_value": self.energy_value,
|
||||
"sleep_pressure": self.sleep_pressure,
|
||||
"breaking_accumulated_interest": self.breaking_accumulated_interest,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -257,7 +259,7 @@ class ChatManager:
|
||||
"user_cardname": model_instance.user_cardname or "",
|
||||
}
|
||||
group_info_data = None
|
||||
if model_instance.group_id:
|
||||
if model_instance and getattr(model_instance, "group_id", None):
|
||||
group_info_data = {
|
||||
"platform": model_instance.group_platform,
|
||||
"group_id": model_instance.group_id,
|
||||
@@ -403,7 +405,7 @@ class ChatManager:
|
||||
"user_cardname": model_instance.user_cardname or "",
|
||||
}
|
||||
group_info_data = None
|
||||
if model_instance.group_id:
|
||||
if model_instance and getattr(model_instance, "group_id", None):
|
||||
group_info_data = {
|
||||
"platform": model_instance.group_platform,
|
||||
"group_id": model_instance.group_id,
|
||||
|
||||
@@ -162,9 +162,7 @@ class ActionModifier:
|
||||
available_actions = list(self.action_manager.get_using_actions().keys())
|
||||
available_actions_text = "、".join(available_actions) if available_actions else "无"
|
||||
|
||||
logger.info(
|
||||
f"{self.log_prefix} 当前可用动作: {available_actions_text}||移除: {removals_summary}"
|
||||
)
|
||||
logger.info(f"{self.log_prefix} 当前可用动作: {available_actions_text}||移除: {removals_summary}")
|
||||
|
||||
def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext):
|
||||
type_mismatched_actions: List[Tuple[str, str]] = []
|
||||
|
||||
@@ -188,15 +188,12 @@ class ActionPlanner:
|
||||
param_text = ""
|
||||
if action_info.action_parameters:
|
||||
param_text = "\n" + "\n".join(
|
||||
f' "{p_name}":"{p_desc}"'
|
||||
for p_name, p_desc in action_info.action_parameters.items()
|
||||
f' "{p_name}":"{p_desc}"' for p_name, p_desc in action_info.action_parameters.items()
|
||||
)
|
||||
|
||||
require_text = "\n".join(f"- {req}" for req in action_info.action_require)
|
||||
|
||||
using_action_prompt = await global_prompt_manager.get_prompt_async(
|
||||
"action_prompt"
|
||||
)
|
||||
using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt")
|
||||
action_options_block += using_action_prompt.format(
|
||||
action_name=action_name,
|
||||
action_description=action_info.description,
|
||||
@@ -205,9 +202,7 @@ class ActionPlanner:
|
||||
)
|
||||
return action_options_block
|
||||
|
||||
def find_message_by_id(
|
||||
self, message_id: str, message_id_list: list
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
def find_message_by_id(self, message_id: str, message_id_list: list) -> Optional[Dict[str, Any]]:
|
||||
# sourcery skip: use-next
|
||||
"""
|
||||
根据message_id从message_id_list中查找对应的原始消息
|
||||
@@ -245,7 +240,7 @@ class ActionPlanner:
|
||||
async def plan(
|
||||
self,
|
||||
mode: ChatMode = ChatMode.FOCUS,
|
||||
loop_start_time:float = 0.0,
|
||||
loop_start_time: float = 0.0,
|
||||
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||||
) -> Tuple[List[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
||||
"""
|
||||
@@ -323,11 +318,15 @@ class ActionPlanner:
|
||||
# 如果获取的target_message为None,输出warning并重新plan
|
||||
if target_message is None:
|
||||
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并选取最新消息
|
||||
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)
|
||||
self.plan_retry_count = 0 # 重置计数器
|
||||
else:
|
||||
@@ -339,7 +338,6 @@ class ActionPlanner:
|
||||
else:
|
||||
logger.warning(f"{self.log_prefix}动作'{action}'缺少target_message_id")
|
||||
|
||||
|
||||
if action != "no_reply" and action != "reply" and action not in current_available_actions:
|
||||
logger.warning(
|
||||
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:
|
||||
is_parallel = current_available_actions[action].parallel_action
|
||||
|
||||
|
||||
action_data["loop_start_time"] = loop_start_time
|
||||
|
||||
actions = []
|
||||
|
||||
# 1. 添加Planner取得的动作
|
||||
actions.append({
|
||||
"action_type": action,
|
||||
"reasoning": reasoning,
|
||||
"action_data": action_data,
|
||||
"action_message": target_message,
|
||||
"available_actions": available_actions # 添加这个字段
|
||||
})
|
||||
actions.append(
|
||||
{
|
||||
"action_type": action,
|
||||
"reasoning": reasoning,
|
||||
"action_data": action_data,
|
||||
"action_message": target_message,
|
||||
"available_actions": available_actions, # 添加这个字段
|
||||
}
|
||||
)
|
||||
|
||||
if action != "reply" and is_parallel:
|
||||
actions.append({
|
||||
"action_type": "reply",
|
||||
"action_message": target_message,
|
||||
"available_actions": available_actions
|
||||
})
|
||||
actions.append(
|
||||
{"action_type": "reply", "action_message": target_message, "available_actions": available_actions}
|
||||
)
|
||||
|
||||
return actions,target_message
|
||||
return actions, target_message
|
||||
|
||||
async def build_planner_prompt(
|
||||
self,
|
||||
is_group_chat: bool, # Now passed as argument
|
||||
chat_target_info: Optional[dict], # Now passed as argument
|
||||
current_available_actions: Dict[str, ActionInfo],
|
||||
refresh_time :bool = False,
|
||||
refresh_time: bool = False,
|
||||
mode: ChatMode = ChatMode.FOCUS,
|
||||
) -> tuple[str, list]: # sourcery skip: use-join
|
||||
"""构建 Planner LLM 的提示词 (获取模板并填充数据)"""
|
||||
@@ -400,21 +397,15 @@ class ActionPlanner:
|
||||
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
bot_name = global_config.bot.nickname
|
||||
bot_nickname = (
|
||||
f",也有人叫你{','.join(global_config.bot.alias_names)}"
|
||||
if global_config.bot.alias_names
|
||||
else ""
|
||||
f",也有人叫你{','.join(global_config.bot.alias_names)}" if global_config.bot.alias_names else ""
|
||||
)
|
||||
bot_core_personality = global_config.personality.personality_core
|
||||
identity_block = (
|
||||
f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:"
|
||||
)
|
||||
identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:"
|
||||
|
||||
schedule_block = ""
|
||||
if global_config.schedule.enable:
|
||||
if current_activity := schedule_manager.get_current_activity():
|
||||
schedule_block = (
|
||||
f"你当前正在:{current_activity},但注意它与群聊的聊天无关。"
|
||||
)
|
||||
schedule_block = f"你当前正在:{current_activity},但注意它与群聊的聊天无关。"
|
||||
|
||||
mood_block = ""
|
||||
if global_config.mood.enable_mood:
|
||||
@@ -424,13 +415,9 @@ class ActionPlanner:
|
||||
# --- 根据模式构建不同的Prompt ---
|
||||
if mode == ChatMode.PROACTIVE:
|
||||
long_term_memory_block = await self._get_long_term_memory_context()
|
||||
action_options_text = await self._build_action_options(
|
||||
current_available_actions, mode
|
||||
)
|
||||
action_options_text = await self._build_action_options(current_available_actions, mode)
|
||||
|
||||
prompt_template = await global_prompt_manager.get_prompt_async(
|
||||
"proactive_planner_prompt"
|
||||
)
|
||||
prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt")
|
||||
prompt = prompt_template.format(
|
||||
time_block=time_block,
|
||||
identity_block=identity_block,
|
||||
@@ -463,12 +450,8 @@ class ActionPlanner:
|
||||
limit=5,
|
||||
)
|
||||
|
||||
actions_before_now_block = build_readable_actions(
|
||||
actions=actions_before_now
|
||||
)
|
||||
actions_before_now_block = (
|
||||
f"你刚刚选择并执行过的action是:\n{actions_before_now_block}"
|
||||
)
|
||||
actions_before_now_block = build_readable_actions(actions=actions_before_now)
|
||||
actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}"
|
||||
|
||||
if refresh_time:
|
||||
self.last_obs_time_mark = time.time()
|
||||
@@ -507,27 +490,19 @@ class ActionPlanner:
|
||||
chat_target_name = None
|
||||
if not is_group_chat and chat_target_info:
|
||||
chat_target_name = (
|
||||
chat_target_info.get("person_name")
|
||||
or chat_target_info.get("user_nickname")
|
||||
or "对方"
|
||||
chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or "对方"
|
||||
)
|
||||
chat_context_description = f"你正在和 {chat_target_name} 私聊"
|
||||
|
||||
action_options_block = await self._build_action_options(
|
||||
current_available_actions, mode
|
||||
)
|
||||
action_options_block = await self._build_action_options(current_available_actions, mode)
|
||||
|
||||
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
|
||||
|
||||
custom_prompt_block = ""
|
||||
if global_config.custom_prompt.planner_custom_prompt_content:
|
||||
custom_prompt_block = (
|
||||
global_config.custom_prompt.planner_custom_prompt_content
|
||||
)
|
||||
custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content
|
||||
|
||||
planner_prompt_template = await global_prompt_manager.get_prompt_async(
|
||||
"planner_prompt"
|
||||
)
|
||||
planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt")
|
||||
prompt = planner_prompt_template.format(
|
||||
schedule_block=schedule_block,
|
||||
mood_block=mood_block,
|
||||
@@ -555,9 +530,7 @@ class ActionPlanner:
|
||||
"""
|
||||
is_group_chat = True
|
||||
is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id)
|
||||
logger.debug(
|
||||
f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}"
|
||||
)
|
||||
logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}")
|
||||
|
||||
current_available_actions_dict = self.action_manager.get_using_actions()
|
||||
|
||||
@@ -568,13 +541,9 @@ class ActionPlanner:
|
||||
current_available_actions = {}
|
||||
for action_name in current_available_actions_dict:
|
||||
if action_name in all_registered_actions:
|
||||
current_available_actions[action_name] = all_registered_actions[
|
||||
action_name
|
||||
]
|
||||
current_available_actions[action_name] = all_registered_actions[action_name]
|
||||
else:
|
||||
logger.warning(
|
||||
f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到"
|
||||
)
|
||||
logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到")
|
||||
|
||||
# 将no_reply作为系统级特殊动作添加到可用动作中
|
||||
# no_reply虽然是系统级决策,但需要让规划器认为它是可用的
|
||||
|
||||
@@ -15,7 +15,6 @@ from src.chat.utils.prompt_utils import PromptUtils
|
||||
from src.mais4u.mai_think import mai_thinking_manager
|
||||
from src.common.logger import get_logger
|
||||
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.llm_models.utils_model import LLMRequest
|
||||
from src.chat.message_receive.message import UserInfo, Seg, MessageRecv, MessageSending
|
||||
@@ -819,7 +818,7 @@ class DefaultReplyer:
|
||||
mood_prompt = ""
|
||||
|
||||
if reply_to:
|
||||
#兼容旧的reply_to
|
||||
# 兼容旧的reply_to
|
||||
sender, target = self._parse_reply_target(reply_to)
|
||||
else:
|
||||
# 获取 platform,如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值
|
||||
@@ -830,7 +829,7 @@ class DefaultReplyer:
|
||||
)
|
||||
person_name = await person_info_manager.get_value(person_id, "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_id = person_info_manager.get_person_id_by_person_name(sender)
|
||||
@@ -1178,7 +1177,9 @@ class DefaultReplyer:
|
||||
else:
|
||||
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}")
|
||||
return content, reasoning_content, model_name, tool_calls
|
||||
|
||||
@@ -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.config.api_ada_configs import TaskConfig
|
||||
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
|
||||
from src.chat.replyer.default_generator import DefaultReplyer
|
||||
|
||||
|
||||
@@ -4,17 +4,13 @@
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
from typing import Dict, Any, Optional, Tuple
|
||||
|
||||
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
|
||||
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")
|
||||
|
||||
@@ -88,108 +84,24 @@ class PromptUtils:
|
||||
) -> str:
|
||||
"""
|
||||
构建跨群聊上下文 - 统一实现,完全继承DefaultReplyer功能
|
||||
|
||||
Args:
|
||||
chat_id: 当前聊天ID
|
||||
target_user_info: 目标用户信息
|
||||
current_prompt_mode: 当前提示模式
|
||||
|
||||
Returns:
|
||||
str: 跨群上下文块
|
||||
"""
|
||||
if not global_config.cross_context.enable:
|
||||
return ""
|
||||
|
||||
# 找到当前群聊所在的共享组
|
||||
target_group = None
|
||||
current_stream = get_chat_manager().get_stream(chat_id)
|
||||
if not current_stream or not current_stream.group_info:
|
||||
other_chat_raw_ids = cross_context_api.get_context_groups(chat_id)
|
||||
if not other_chat_raw_ids:
|
||||
return ""
|
||||
|
||||
try:
|
||||
current_chat_raw_id = current_stream.group_info.group_id
|
||||
except Exception as e:
|
||||
logger.error(f"获取群聊ID失败: {e}")
|
||||
chat_stream = get_chat_manager().get_stream(chat_id)
|
||||
if not chat_stream:
|
||||
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":
|
||||
# normal模式:获取其他群聊的最近N条消息
|
||||
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
|
||||
|
||||
return await cross_context_api.build_cross_context_normal(chat_stream, other_chat_raw_ids)
|
||||
elif current_prompt_mode == "s4u":
|
||||
# s4u模式:获取当前发言用户在其他群聊的消息
|
||||
if target_user_info:
|
||||
user_id = target_user_info.get("user_id")
|
||||
return await cross_context_api.build_cross_context_s4u(chat_stream, other_chat_raw_ids, target_user_info)
|
||||
|
||||
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 "# 跨群上下文参考\n" + "\n\n".join(cross_context_messages) + "\n"
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def parse_reply_target_id(reply_to: str) -> str:
|
||||
|
||||
@@ -4,7 +4,6 @@ import time
|
||||
import hashlib
|
||||
import uuid
|
||||
import io
|
||||
import asyncio
|
||||
import numpy as np
|
||||
|
||||
from typing import Optional, Tuple, Dict, Any
|
||||
@@ -35,8 +34,7 @@ def is_image_message(message: Dict[str, Any]) -> bool:
|
||||
bool: 是否为图片消息
|
||||
"""
|
||||
return message.get("type") == "image" or (
|
||||
isinstance(message.get("content"), dict) and
|
||||
message["content"].get("type") == "image"
|
||||
isinstance(message.get("content"), dict) and message["content"].get("type") == "image"
|
||||
)
|
||||
|
||||
|
||||
@@ -596,7 +594,6 @@ class ImageManager:
|
||||
return "", "[图片]"
|
||||
|
||||
|
||||
|
||||
# 创建全局单例
|
||||
image_manager = None
|
||||
|
||||
|
||||
@@ -62,10 +62,12 @@ def get_active_plans_for_month(month: str) -> List[MonthlyPlan]:
|
||||
"""
|
||||
with get_db_session() as session:
|
||||
try:
|
||||
plans = session.query(MonthlyPlan).filter(
|
||||
MonthlyPlan.target_month == month,
|
||||
MonthlyPlan.status == 'active'
|
||||
).order_by(MonthlyPlan.created_at.desc()).all()
|
||||
plans = (
|
||||
session.query(MonthlyPlan)
|
||||
.filter(MonthlyPlan.target_month == month, MonthlyPlan.status == "active")
|
||||
.order_by(MonthlyPlan.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
return plans
|
||||
except Exception as e:
|
||||
logger.error(f"查询 {month} 的有效月度计划时发生错误: {e}")
|
||||
|
||||
@@ -81,8 +81,8 @@ def get_key_comment(toml_table, key):
|
||||
return item.trivia.comment
|
||||
if hasattr(toml_table, "keys"):
|
||||
for k in toml_table.keys():
|
||||
if isinstance(k, KeyType) and k.key == key: # type: ignore
|
||||
return k.trivia.comment # type: ignore
|
||||
if isinstance(k, KeyType) and k.key == key: # type: ignore
|
||||
return k.trivia.comment # type: ignore
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -259,7 +259,6 @@ class NormalChatConfig(ValidatedConfigBase):
|
||||
"""普通聊天配置类"""
|
||||
|
||||
|
||||
|
||||
class ExpressionRule(ValidatedConfigBase):
|
||||
"""表达学习规则"""
|
||||
|
||||
@@ -652,7 +651,10 @@ class ContextGroup(ValidatedConfigBase):
|
||||
"""上下文共享组配置"""
|
||||
|
||||
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):
|
||||
|
||||
105
src/main.py
105
src/main.py
@@ -29,9 +29,54 @@ from src.plugin_system.core.plugin_hot_reload import hot_reload_manager
|
||||
# 导入消息API和traceback模块
|
||||
from src.common.message import get_global_api
|
||||
|
||||
# 条件导入记忆系统
|
||||
if global_config.memory.enable_memory:
|
||||
from src.chat.memory_system.Hippocampus import hippocampus_manager
|
||||
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:
|
||||
def __init__(self):
|
||||
# 根据配置条件性地初始化记忆系统
|
||||
if global_config.memory.enable_memory:
|
||||
self.hippocampus_manager = hippocampus_manager
|
||||
else:
|
||||
self.hippocampus_manager = None
|
||||
self.hippocampus_manager = hippocampus_manager
|
||||
|
||||
self.individuality: Individuality = get_individuality()
|
||||
|
||||
@@ -103,8 +144,6 @@ class MainSystem:
|
||||
else:
|
||||
loop.run_until_complete(async_memory_manager.shutdown())
|
||||
logger.info("🛑 记忆管理器已停止")
|
||||
except ImportError:
|
||||
pass # 异步记忆优化器不存在
|
||||
except Exception as e:
|
||||
logger.error(f"停止记忆管理器时出错: {e}")
|
||||
|
||||
@@ -189,7 +228,6 @@ MoFox_Bot(第三方修改版)
|
||||
get_emoji_manager().initialize()
|
||||
logger.info("表情包管理器初始化成功")
|
||||
|
||||
|
||||
# 启动情绪管理器
|
||||
await mood_manager.start()
|
||||
logger.info("情绪管理器初始化成功")
|
||||
@@ -201,22 +239,18 @@ MoFox_Bot(第三方修改版)
|
||||
|
||||
logger.info("聊天管理器初始化成功")
|
||||
|
||||
# 根据配置条件性地初始化记忆系统
|
||||
if global_config.memory.enable_memory:
|
||||
if self.hippocampus_manager:
|
||||
self.hippocampus_manager.initialize()
|
||||
logger.info("记忆系统初始化成功")
|
||||
# 初始化记忆系统
|
||||
self.hippocampus_manager.initialize()
|
||||
logger.info("记忆系统初始化成功")
|
||||
|
||||
# 初始化异步记忆管理器
|
||||
try:
|
||||
from src.chat.memory_system.async_memory_optimizer import async_memory_manager
|
||||
# 初始化异步记忆管理器
|
||||
try:
|
||||
from src.chat.memory_system.async_memory_optimizer import async_memory_manager
|
||||
|
||||
await async_memory_manager.initialize()
|
||||
logger.info("记忆管理器初始化成功")
|
||||
except Exception as e:
|
||||
logger.error(f"记忆管理器初始化失败: {e}")
|
||||
else:
|
||||
logger.info("记忆系统已禁用,跳过初始化")
|
||||
await async_memory_manager.initialize()
|
||||
logger.info("记忆管理器初始化成功")
|
||||
except Exception as e:
|
||||
logger.error(f"记忆管理器初始化失败: {e}")
|
||||
|
||||
# await asyncio.sleep(0.5) #防止logger输出飞了
|
||||
|
||||
@@ -265,15 +299,14 @@ MoFox_Bot(第三方修改版)
|
||||
self.server.run(),
|
||||
]
|
||||
|
||||
# 根据配置条件性地添加记忆系统相关任务
|
||||
if global_config.memory.enable_memory and self.hippocampus_manager:
|
||||
tasks.extend(
|
||||
[
|
||||
self.build_memory_task(),
|
||||
self.forget_memory_task(),
|
||||
self.consolidate_memory_task(),
|
||||
]
|
||||
)
|
||||
# 添加记忆系统相关任务
|
||||
tasks.extend(
|
||||
[
|
||||
self.build_memory_task(),
|
||||
self.forget_memory_task(),
|
||||
self.consolidate_memory_task(),
|
||||
]
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
@@ -305,10 +338,6 @@ MoFox_Bot(第三方修改版)
|
||||
|
||||
def sync_build_memory():
|
||||
"""在线程池中执行同步记忆构建"""
|
||||
if not self.hippocampus_manager:
|
||||
logger.error("尝试在禁用记忆系统时构建记忆,操作已取消。")
|
||||
return
|
||||
|
||||
try:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
180
src/plugin_system/apis/cross_context_api.py
Normal file
180
src/plugin_system/apis/cross_context_api.py
Normal 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"
|
||||
@@ -12,7 +12,6 @@ import traceback
|
||||
from typing import Tuple, Any, Dict, List, Optional
|
||||
from rich.traceback import install
|
||||
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.message_receive.chat_stream import ChatStream
|
||||
from src.chat.utils.utils import process_llm_response
|
||||
@@ -107,9 +106,7 @@ async def generate_reply(
|
||||
"""
|
||||
try:
|
||||
# 获取回复器
|
||||
replyer = get_replyer(
|
||||
chat_stream, chat_id, request_type=request_type
|
||||
)
|
||||
replyer = get_replyer(chat_stream, chat_id, request_type=request_type)
|
||||
if not replyer:
|
||||
logger.error("[GeneratorAPI] 无法获取回复器")
|
||||
return False, [], None
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
import traceback
|
||||
import time
|
||||
import difflib
|
||||
import asyncio
|
||||
from typing import Optional, Union, Dict, Any
|
||||
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.uni_message_sender import HeartFCSender
|
||||
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 src.config.config import global_config
|
||||
|
||||
@@ -51,6 +48,7 @@ logger = get_logger("send_api")
|
||||
# 适配器命令响应等待池
|
||||
_adapter_response_pool: Dict[str, asyncio.Future] = {}
|
||||
|
||||
|
||||
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', '')}")
|
||||
return message_recv
|
||||
|
||||
|
||||
def put_adapter_response(request_id: str, response_data: dict) -> None:
|
||||
"""将适配器响应放入响应池"""
|
||||
if request_id in _adapter_response_pool:
|
||||
@@ -234,7 +233,6 @@ async def _send_to_target(
|
||||
return False
|
||||
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 公共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:
|
||||
@@ -285,10 +285,14 @@ async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bo
|
||||
Returns:
|
||||
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:
|
||||
@@ -299,11 +303,17 @@ async def image_to_stream(image_base64: str, stream_id: str, storage_message: bo
|
||||
Returns:
|
||||
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(
|
||||
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:
|
||||
"""向指定流发送命令
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ class PluginManager:
|
||||
|
||||
# 检查并调用 on_plugin_loaded 钩子(如果存在)
|
||||
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 钩子")
|
||||
try:
|
||||
|
||||
@@ -53,7 +53,9 @@ class MaiZoneRefactoredPlugin(BasePlugin):
|
||||
"enable_reply": ConfigField(type=bool, default=True, description="完成后是否回复"),
|
||||
"ai_image_number": ConfigField(type=int, default=1, description="AI生成图片数量"),
|
||||
"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": {
|
||||
"permission": ConfigField(type=list, default=[], description="阅读权限QQ号列表"),
|
||||
@@ -75,7 +77,9 @@ class MaiZoneRefactoredPlugin(BasePlugin):
|
||||
"forbidden_hours_end": ConfigField(type=int, default=6, description="禁止发送的结束小时(24小时制)"),
|
||||
},
|
||||
"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获取服务的端口"),
|
||||
"napcat_token": ConfigField(type=str, default="", description="Napcat服务的认证Token(可选)"),
|
||||
},
|
||||
@@ -102,7 +106,7 @@ class MaiZoneRefactoredPlugin(BasePlugin):
|
||||
content_service,
|
||||
image_service,
|
||||
cookie_service,
|
||||
reply_tracker_service # 传入已创建的实例
|
||||
reply_tracker_service, # 传入已创建的实例
|
||||
)
|
||||
scheduler_service = SchedulerService(self.get_config, qzone_service)
|
||||
monitor_service = MonitorService(self.get_config, qzone_service)
|
||||
|
||||
@@ -13,6 +13,7 @@ from src.common.logger import get_logger
|
||||
import imghdr
|
||||
import asyncio
|
||||
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 maim_message import UserInfo
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
@@ -87,6 +88,11 @@ class ContentService:
|
||||
if context:
|
||||
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"
|
||||
history_block = await get_send_history(qq_account)
|
||||
@@ -232,7 +238,7 @@ class ContentService:
|
||||
for i in range(3): # 重试3次
|
||||
try:
|
||||
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:
|
||||
logger.error(f"下载图片失败: {image_url}, status: {resp.status}")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
@@ -272,8 +272,10 @@ class QZoneService:
|
||||
# 检查是否已经在持久化记录中标记为已回复
|
||||
if not self.reply_tracker.has_replied(fid, comment_tid):
|
||||
# 记录日志以便追踪
|
||||
logger.debug(f"发现新评论需要回复 - 说说ID: {fid}, 评论ID: {comment_tid}, "
|
||||
f"评论人: {comment.get('nickname', '')}, 内容: {comment.get('content', '')}")
|
||||
logger.debug(
|
||||
f"发现新评论需要回复 - 说说ID: {fid}, 评论ID: {comment_tid}, "
|
||||
f"评论人: {comment.get('nickname', '')}, 内容: {comment.get('content', '')}"
|
||||
)
|
||||
comments_to_reply.append(comment)
|
||||
|
||||
if not comments_to_reply:
|
||||
|
||||
@@ -74,8 +74,10 @@ class ReplyTrackerService:
|
||||
data = json.loads(file_content)
|
||||
if self._validate_data(data):
|
||||
self.replied_comments = data
|
||||
logger.info(f"已加载 {len(self.replied_comments)} 条说说的回复记录,"
|
||||
f"总计 {sum(len(comments) for comments in self.replied_comments.values())} 条评论")
|
||||
logger.info(
|
||||
f"已加载 {len(self.replied_comments)} 条说说的回复记录,"
|
||||
f"总计 {sum(len(comments) for comments in self.replied_comments.values())} 条评论"
|
||||
)
|
||||
else:
|
||||
logger.error("加载的数据格式无效,将创建新的记录")
|
||||
self.replied_comments = {}
|
||||
@@ -112,7 +114,7 @@ class ReplyTrackerService:
|
||||
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:
|
||||
|
||||
@@ -76,7 +76,7 @@ class MonthlyPlanManager:
|
||||
if 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_plans_by_ids(delete_ids)
|
||||
# 重新获取计划列表
|
||||
@@ -291,6 +291,8 @@ class MonthlyPlanManager:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}")
|
||||
|
||||
|
||||
class MonthlyPlanGenerationTask(AsyncTask):
|
||||
"""每月初自动生成新月度计划的任务"""
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ from src.llm_models.utils_model import LLMRequest
|
||||
from src.common.logger import get_logger
|
||||
from json_repair import repair_json
|
||||
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:
|
||||
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")
|
||||
@@ -165,7 +165,9 @@ class ScheduleManager:
|
||||
schedule_str = f"已成功加载今天的日程 ({today_str}):\n"
|
||||
if 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)
|
||||
return # 成功加载,直接返回
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[inner]
|
||||
version = "6.7.1"
|
||||
version = "6.7.2"
|
||||
|
||||
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
||||
#如果你想要修改配置文件,请递增version的值
|
||||
@@ -476,28 +476,25 @@ pre_sleep_notification_groups = []
|
||||
# 用于生成睡前消息的提示。AI会根据这个提示生成一句晚安问候。
|
||||
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]]
|
||||
name = "项目A技术讨论组"
|
||||
chat_ids = [
|
||||
"111111", # 假设这是“开发群”的ID
|
||||
"222222" # 假设这是“产品群”的ID
|
||||
["group", "169850076"], # 假设这是“开发群”的ID
|
||||
["group", "1025509724"], # 假设这是“产品群”的ID
|
||||
["private", "123456789"] # 假设这是某个用户的私聊
|
||||
]
|
||||
|
||||
[maizone_intercom]
|
||||
# QQ空间互通组配置
|
||||
# 启用后,发布说说时会读取指定互通组的上下文
|
||||
enable = false
|
||||
# 定义QQ空间互通组
|
||||
# 同一个组的chat_id会共享上下文,用于生成更相关的说说
|
||||
[[maizone_intercom.groups]]
|
||||
[[cross_context.maizone_context_group]]
|
||||
name = "Maizone默认互通组"
|
||||
chat_ids = [
|
||||
"111111", # 示例群聊1
|
||||
"222222" # 示例群聊2
|
||||
["group", "111111"], # 示例群聊1
|
||||
["private", "222222"] # 示例私聊2
|
||||
]
|
||||
Reference in New Issue
Block a user