Merge branch 'MaiM-with-u:dev' into dev

This commit is contained in:
UnCLAS-Prommer
2025-04-21 22:55:24 +08:00
committed by GitHub
19 changed files with 1369 additions and 229 deletions

View File

@@ -98,7 +98,7 @@
<div align="left"> <div align="left">
<h2>📚 文档</h2> <h2>📚 文档 </h2>
</div> </div>
### (部分内容可能过时,请注意版本对应) ### (部分内容可能过时,请注意版本对应)

View File

@@ -186,12 +186,18 @@ class BotConfig:
ban_words = set() ban_words = set()
ban_msgs_regex = set() ban_msgs_regex = set()
# heartflow # [heartflow] # 启用启用heart_flowC(心流聊天)模式时生效, 需要填写token消耗量巨大的相关模型
# enable_heartflow: bool = False # 是否启用心流 # 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间), 进行长时间高质量的聊天
sub_heart_flow_update_interval: int = 60 # 子心流更新频率,间隔 单位秒 enable_heart_flowC: bool = True # 是否启用heart_flowC(心流聊天, HFC)模式
sub_heart_flow_freeze_time: int = 120 # 心流冻结时间,超过这个时间没有回复,子心流会冻结,间隔 单位秒 reply_trigger_threshold: float = 3.0 # 心流聊天触发阈值,越低越容易触发
probability_decay_factor_per_second: float = 0.2 # 概率衰减因子,越大衰减越快
default_decay_rate_per_second: float = 0.98 # 默认衰减率,越大衰减越慢
initial_duration: int = 60 # 初始持续时间,越大心流聊天持续的时间越长
# sub_heart_flow_update_interval: int = 60 # 子心流更新频率,间隔 单位秒
# sub_heart_flow_freeze_time: int = 120 # 子心流冻结时间,超过这个时间没有回复,子心流会冻结,间隔 单位秒
sub_heart_flow_stop_time: int = 600 # 子心流停止时间,超过这个时间没有回复,子心流会停止,间隔 单位秒 sub_heart_flow_stop_time: int = 600 # 子心流停止时间,超过这个时间没有回复,子心流会停止,间隔 单位秒
heart_flow_update_interval: int = 300 # 心流更新频率,间隔 单位秒 # heart_flow_update_interval: int = 300 # 心流更新频率,间隔 单位秒
observation_context_size: int = 20 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩 observation_context_size: int = 20 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩
compressed_length: int = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5 compressed_length: int = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5
compress_length_limit: int = 5 # 最多压缩份数,超过该数值的压缩上下文会被删除 compress_length_limit: int = 5 # 最多压缩份数,超过该数值的压缩上下文会被删除
@@ -207,8 +213,8 @@ class BotConfig:
# response # response
response_mode: str = "heart_flow" # 回复策略 response_mode: str = "heart_flow" # 回复策略
MODEL_R1_PROBABILITY: float = 0.8 # R1模型概率 model_reasoning_probability: float = 0.7 # 麦麦回答时选择推理模型(主要)模型概率
MODEL_V3_PROBABILITY: float = 0.1 # V3模型概率 model_normal_probability: float = 0.3 # 麦麦回答时选择一般模型(次要)模型概率
# MODEL_R1_DISTILL_PROBABILITY: float = 0.1 # R1蒸馏模型概率 # MODEL_R1_DISTILL_PROBABILITY: float = 0.1 # R1蒸馏模型概率
# emoji # emoji
@@ -401,29 +407,34 @@ class BotConfig:
def response(parent: dict): def response(parent: dict):
response_config = parent["response"] response_config = parent["response"]
config.MODEL_R1_PROBABILITY = response_config.get("model_r1_probability", config.MODEL_R1_PROBABILITY) config.model_reasoning_probability = response_config.get(
config.MODEL_V3_PROBABILITY = response_config.get("model_v3_probability", config.MODEL_V3_PROBABILITY) "model_reasoning_probability", config.model_reasoning_probability
# config.MODEL_R1_DISTILL_PROBABILITY = response_config.get( )
# "model_r1_distill_probability", config.MODEL_R1_DISTILL_PROBABILITY config.model_normal_probability = response_config.get(
# ) "model_normal_probability", config.model_normal_probability
config.max_response_length = response_config.get("max_response_length", config.max_response_length) )
if config.INNER_VERSION in SpecifierSet(">=1.0.4"):
config.response_mode = response_config.get("response_mode", config.response_mode) # 添加 enable_heart_flowC 的加载逻辑 (假设它在 [response] 部分)
if config.INNER_VERSION in SpecifierSet(">=1.4.0"):
config.enable_heart_flowC = response_config.get("enable_heart_flowC", config.enable_heart_flowC)
def heartflow(parent: dict): def heartflow(parent: dict):
heartflow_config = parent["heartflow"] heartflow_config = parent["heartflow"]
config.sub_heart_flow_update_interval = heartflow_config.get( # 加载新增的 heartflowC 参数
"sub_heart_flow_update_interval", config.sub_heart_flow_update_interval
) # 加载原有的 heartflow 参数
config.sub_heart_flow_freeze_time = heartflow_config.get( # config.sub_heart_flow_update_interval = heartflow_config.get(
"sub_heart_flow_freeze_time", config.sub_heart_flow_freeze_time # "sub_heart_flow_update_interval", config.sub_heart_flow_update_interval
) # )
# config.sub_heart_flow_freeze_time = heartflow_config.get(
# "sub_heart_flow_freeze_time", config.sub_heart_flow_freeze_time
# )
config.sub_heart_flow_stop_time = heartflow_config.get( config.sub_heart_flow_stop_time = heartflow_config.get(
"sub_heart_flow_stop_time", config.sub_heart_flow_stop_time "sub_heart_flow_stop_time", config.sub_heart_flow_stop_time
) )
config.heart_flow_update_interval = heartflow_config.get( # config.heart_flow_update_interval = heartflow_config.get(
"heart_flow_update_interval", config.heart_flow_update_interval # "heart_flow_update_interval", config.heart_flow_update_interval
) # )
if config.INNER_VERSION in SpecifierSet(">=1.3.0"): if config.INNER_VERSION in SpecifierSet(">=1.3.0"):
config.observation_context_size = heartflow_config.get( config.observation_context_size = heartflow_config.get(
"observation_context_size", config.observation_context_size "observation_context_size", config.observation_context_size
@@ -432,6 +443,17 @@ class BotConfig:
config.compress_length_limit = heartflow_config.get( config.compress_length_limit = heartflow_config.get(
"compress_length_limit", config.compress_length_limit "compress_length_limit", config.compress_length_limit
) )
if config.INNER_VERSION in SpecifierSet(">=1.4.0"):
config.reply_trigger_threshold = heartflow_config.get(
"reply_trigger_threshold", config.reply_trigger_threshold
)
config.probability_decay_factor_per_second = heartflow_config.get(
"probability_decay_factor_per_second", config.probability_decay_factor_per_second
)
config.default_decay_rate_per_second = heartflow_config.get(
"default_decay_rate_per_second", config.default_decay_rate_per_second
)
config.initial_duration = heartflow_config.get("initial_duration", config.initial_duration)
def willing(parent: dict): def willing(parent: dict):
willing_config = parent["willing"] willing_config = parent["willing"]

View File

@@ -1,5 +1,4 @@
from .sub_heartflow import SubHeartflow from .sub_heartflow import SubHeartflow, ChattingObservation
from .observation import ChattingObservation
from src.plugins.moods.moods import MoodManager from src.plugins.moods.moods import MoodManager
from src.plugins.models.utils_model import LLMRequest from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
@@ -10,7 +9,8 @@ from src.common.logger import get_module_logger, LogConfig, HEARTFLOW_STYLE_CONF
from src.individuality.individuality import Individuality from src.individuality.individuality import Individuality
import time import time
import random import random
from typing import Dict, Any from typing import Dict, Any, Optional
import traceback
heartflow_config = LogConfig( heartflow_config = LogConfig(
# 使用海马体专用样式 # 使用海马体专用样式
@@ -45,6 +45,8 @@ class CurrentState:
def __init__(self): def __init__(self):
self.current_state_info = "" self.current_state_info = ""
self.chat_status = "IDLE"
self.mood_manager = MoodManager() self.mood_manager = MoodManager()
self.mood = self.mood_manager.get_prompt() self.mood = self.mood_manager.get_prompt()
@@ -70,20 +72,27 @@ class Heartflow:
"""定期清理不活跃的子心流""" """定期清理不活跃的子心流"""
while True: while True:
current_time = time.time() current_time = time.time()
inactive_subheartflows = [] inactive_subheartflows_ids = [] # 修改变量名以清晰表示存储的是ID
# 检查所有子心流 # 检查所有子心流
for subheartflow_id, subheartflow in self._subheartflows.items(): # 使用 list(self._subheartflows.items()) 避免在迭代时修改字典
for subheartflow_id, subheartflow in list(self._subheartflows.items()):
if ( if (
current_time - subheartflow.last_active_time > global_config.sub_heart_flow_stop_time current_time - subheartflow.last_active_time > global_config.sub_heart_flow_stop_time
): # 10分钟 = 600秒 ): # 10分钟 = 600秒
inactive_subheartflows.append(subheartflow_id) logger.info(f"发现不活跃的子心流: {subheartflow_id}, 准备清理。")
logger.info(f"发现不活跃的子心流: {subheartflow_id}") # 1. 标记子心流让其后台任务停止
subheartflow.should_stop = True
# 2. 将ID添加到待清理列表
inactive_subheartflows_ids.append(subheartflow_id)
# 清理不活跃的子心流 # 清理不活跃的子心流 (从字典中移除)
for subheartflow_id in inactive_subheartflows: for subheartflow_id in inactive_subheartflows_ids:
del self._subheartflows[subheartflow_id] if subheartflow_id in self._subheartflows:
logger.info(f"已清理不活跃的子心流: {subheartflow_id}") del self._subheartflows[subheartflow_id]
logger.info(f"已从主心流移除子心流: {subheartflow_id}")
else:
logger.warning(f"尝试移除子心流 {subheartflow_id} 时发现其已被移除。")
await asyncio.sleep(30) # 每分钟检查一次 await asyncio.sleep(30) # 每分钟检查一次
@@ -95,8 +104,10 @@ class Heartflow:
await asyncio.sleep(30) # 每分钟检查一次是否有新的子心流 await asyncio.sleep(30) # 每分钟检查一次是否有新的子心流
continue continue
await self.do_a_thinking() # await self.do_a_thinking()
await asyncio.sleep(global_config.heart_flow_update_interval) # 5分钟思考一次 # await asyncio.sleep(global_config.heart_flow_update_interval * 3) # 5分钟思考一次
await asyncio.sleep(300)
async def heartflow_start_working(self): async def heartflow_start_working(self):
# 启动清理任务 # 启动清理任务
@@ -110,7 +121,7 @@ class Heartflow:
print("TODO") print("TODO")
async def do_a_thinking(self): async def do_a_thinking(self):
logger.debug("麦麦大脑袋转起来了") # logger.debug("麦麦大脑袋转起来了")
self.current_state.update_current_state_info() self.current_state.update_current_state_info()
# 开始构建prompt # 开始构建prompt
@@ -216,33 +227,55 @@ class Heartflow:
return response return response
async def create_subheartflow(self, subheartflow_id): async def create_subheartflow(self, subheartflow_id: Any) -> Optional[SubHeartflow]:
"""
创建一个新的SubHeartflow实例
添加一个SubHeartflow实例到self._subheartflows字典中
并根据subheartflow_id为子心流创建一个观察对象
""" """
获取或创建一个新的SubHeartflow实例。
如果实例已存在,则直接返回。
如果不存在,则创建实例、观察对象、启动后台任务,并返回新实例。
创建过程中发生任何错误将返回 None。
Args:
subheartflow_id: 用于标识子心流的ID (例如群聊ID)。
Returns:
对应的 SubHeartflow 实例,如果创建失败则返回 None。
"""
# 检查是否已存在
existing_subheartflow = self._subheartflows.get(subheartflow_id)
if existing_subheartflow:
logger.debug(f"返回已存在的 subheartflow: {subheartflow_id}")
return existing_subheartflow
# 如果不存在,则创建新的
logger.info(f"尝试创建新的 subheartflow: {subheartflow_id}")
try: try:
if subheartflow_id not in self._subheartflows: subheartflow = SubHeartflow(subheartflow_id)
subheartflow = SubHeartflow(subheartflow_id)
# 创建一个观察对象目前只可以用chat_id创建观察对象 # 创建并初始化观察对象
logger.debug(f"创建 observation: {subheartflow_id}") logger.debug(f" {subheartflow_id} 创建 observation")
observation = ChattingObservation(subheartflow_id) observation = ChattingObservation(subheartflow_id)
await observation.initialize() await observation.initialize() # 等待初始化完成
subheartflow.add_observation(observation) subheartflow.add_observation(observation)
logger.debug("添加 observation 成功") logger.debug(f"{subheartflow_id} 添加 observation 成功")
# 创建异步任务
asyncio.create_task(subheartflow.subheartflow_start_working()) # 创建并存储后台任务
logger.debug("创建异步任务 成功") subheartflow.task = asyncio.create_task(subheartflow.subheartflow_start_working())
self._subheartflows[subheartflow_id] = subheartflow logger.debug(f"{subheartflow_id} 创建后台任务成功")
logger.info("添加 subheartflow 成功")
return self._subheartflows[subheartflow_id] # 添加到管理字典
self._subheartflows[subheartflow_id] = subheartflow
logger.info(f"添加 subheartflow {subheartflow_id} 成功")
return subheartflow
except Exception as e: except Exception as e:
logger.error(f"创建 subheartflow 失败: {e}") # 记录详细错误信息
logger.error(f"创建 subheartflow {subheartflow_id} 失败: {e}")
logger.error(traceback.format_exc()) # 记录完整的 traceback
# 考虑是否需要更具体的错误处理或资源清理逻辑
return None return None
def get_subheartflow(self, observe_chat_id) -> SubHeartflow: def get_subheartflow(self, observe_chat_id: Any) -> Optional[SubHeartflow]:
"""获取指定ID的SubHeartflow实例""" """获取指定ID的SubHeartflow实例"""
return self._subheartflows.get(observe_chat_id) return self._subheartflows.get(observe_chat_id)

View File

@@ -139,7 +139,7 @@ class ChattingObservation(Observation):
# traceback.print_exc() # 记录详细堆栈 # traceback.print_exc() # 记录详细堆栈
# print(f"处理后self.talking_message{self.talking_message}") # print(f"处理后self.talking_message{self.talking_message}")
self.talking_message_str = await build_readable_messages(self.talking_message) self.talking_message_str = await build_readable_messages(messages=self.talking_message, timestamp_mode="normal")
logger.trace( logger.trace(
f"Chat {self.chat_id} - 压缩早期记忆:{self.mid_memory_info}\n现在聊天内容:{self.talking_message_str}" f"Chat {self.chat_id} - 压缩早期记忆:{self.mid_memory_info}\n现在聊天内容:{self.talking_message_str}"

View File

@@ -4,8 +4,7 @@ from src.plugins.moods.moods import MoodManager
from src.plugins.models.utils_model import LLMRequest from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
import time import time
from typing import Optional from typing import Optional, List
from datetime import datetime
import traceback import traceback
from src.plugins.chat.utils import parse_text_timestamps from src.plugins.chat.utils import parse_text_timestamps
@@ -65,7 +64,7 @@ class SubHeartflow:
def __init__(self, subheartflow_id): def __init__(self, subheartflow_id):
self.subheartflow_id = subheartflow_id self.subheartflow_id = subheartflow_id
self.current_mind = "" self.current_mind = "你什么也没想"
self.past_mind = [] self.past_mind = []
self.current_state: CurrentState = CurrentState() self.current_state: CurrentState = CurrentState()
self.llm_model = LLMRequest( self.llm_model = LLMRequest(
@@ -77,15 +76,13 @@ class SubHeartflow:
self.main_heartflow_info = "" self.main_heartflow_info = ""
self.last_reply_time = time.time()
self.last_active_time = time.time() # 添加最后激活时间 self.last_active_time = time.time() # 添加最后激活时间
self.should_stop = False # 添加停止标志
if not self.current_mind: self.task: Optional[asyncio.Task] = None # 添加 task 属性
self.current_mind = "你什么也没想"
self.is_active = False self.is_active = False
self.observations: list[ChattingObservation] = [] self.observations: List[ChattingObservation] = [] # 使用 List 类型提示
self.running_knowledges = [] self.running_knowledges = []
@@ -93,19 +90,13 @@ class SubHeartflow:
async def subheartflow_start_working(self): async def subheartflow_start_working(self):
while True: while True:
current_time = time.time()
# --- 调整后台任务逻辑 --- # # --- 调整后台任务逻辑 --- #
# 这个后台循环现在主要负责检查是否需要自我销毁 # 这个后台循环现在主要负责检查是否需要自我销毁
# 不再主动进行思考或状态更新,这些由 HeartFC_Chat 驱动 # 不再主动进行思考或状态更新,这些由 HeartFC_Chat 驱动
# 检查是否超过指定时间没有激活 (例如,没有被调用进行思考) # 检查是否被主心流标记为停止
if current_time - self.last_active_time > global_config.sub_heart_flow_stop_time: # 例如 5 分钟 if self.should_stop:
logger.info( logger.info(f"子心流 {self.subheartflow_id} 被标记为停止,正在退出后台任务...")
f"子心流 {self.subheartflow_id} 超过 {global_config.sub_heart_flow_stop_time} 秒没有激活,正在销毁..."
f" (Last active: {datetime.fromtimestamp(self.last_active_time).strftime('%Y-%m-%d %H:%M:%S')})"
)
# 在这里添加实际的销毁逻辑,例如从主 Heartflow 管理器中移除自身
# heartflow.remove_subheartflow(self.subheartflow_id) # 假设有这样的方法
break # 退出循环以停止任务 break # 退出循环以停止任务
await asyncio.sleep(global_config.sub_heart_flow_update_interval) # 定期检查销毁条件 await asyncio.sleep(global_config.sub_heart_flow_update_interval) # 定期检查销毁条件

View File

@@ -117,7 +117,7 @@ class MainSystem:
await interest_manager.start_background_tasks() await interest_manager.start_background_tasks()
logger.success("兴趣管理器后台任务启动成功") logger.success("兴趣管理器后台任务启动成功")
# 初始化并独立启动 HeartFC_Chat # 初始化并独立启动 HeartFCController
HeartFCController() HeartFCController()
heartfc_chat_instance = HeartFCController.get_instance() heartfc_chat_instance = HeartFCController.get_instance()
if heartfc_chat_instance: if heartfc_chat_instance:

View File

@@ -105,53 +105,24 @@ class ChatBot:
template_group_name = None template_group_name = None
async def preprocess(): async def preprocess():
if global_config.enable_pfc_chatting: if groupinfo is None:
try: if global_config.enable_friend_chat:
if groupinfo is None: if global_config.enable_pfc_chatting:
if global_config.enable_friend_chat: userinfo = message.message_info.user_info
userinfo = message.message_info.user_info messageinfo = message.message_info
messageinfo = message.message_info # 创建聊天流
# 创建聊天流 chat = await chat_manager.get_or_create_stream(
chat = await chat_manager.get_or_create_stream( platform=messageinfo.platform,
platform=messageinfo.platform, user_info=userinfo,
user_info=userinfo, group_info=groupinfo,
group_info=groupinfo, )
) message.update_chat_stream(chat)
message.update_chat_stream(chat) await self.only_process_chat.process_message(message)
await self.only_process_chat.process_message(message) await self._create_pfc_chat(message)
await self._create_pfc_chat(message)
else: else:
if groupinfo.group_id in global_config.talk_allowed_groups: await self.heartFC_processor.process_message(message_data)
# logger.debug(f"开始群聊模式{str(message_data)[:50]}...")
if global_config.response_mode == "heart_flow":
# logger.info(f"启动最新最好的思维流FC模式{str(message_data)[:50]}...")
await self.heartFC_processor.process_message(message_data)
elif global_config.response_mode == "reasoning":
# logger.debug(f"开始推理模式{str(message_data)[:50]}...")
await self.reasoning_chat.process_message(message_data)
else:
logger.error(f"未知的回复模式,请检查配置文件!!: {global_config.response_mode}")
except Exception as e:
logger.error(f"处理PFC消息失败: {e}")
else: else:
if groupinfo is None: await self.heartFC_processor.process_message(message_data)
if global_config.enable_friend_chat:
# 私聊处理流程
# await self._handle_private_chat(message)
if global_config.response_mode == "heart_flow":
await self.heartFC_processor.process_message(message_data)
elif global_config.response_mode == "reasoning":
await self.reasoning_chat.process_message(message_data)
else:
logger.error(f"未知的回复模式,请检查配置文件!!: {global_config.response_mode}")
else: # 群聊处理
if groupinfo.group_id in global_config.talk_allowed_groups:
if global_config.response_mode == "heart_flow":
await self.heartFC_processor.process_message(message_data)
elif global_config.response_mode == "reasoning":
await self.reasoning_chat.process_message(message_data)
else:
logger.error(f"未知的回复模式,请检查配置文件!!: {global_config.response_mode}")
if template_group_name: if template_group_name:
async with global_prompt_manager.async_message_scope(template_group_name): async with global_prompt_manager.async_message_scope(template_group_name):

View File

@@ -1,7 +1,7 @@
import traceback import traceback
from typing import Optional, Dict from typing import Optional, Dict
import asyncio import asyncio
from asyncio import Lock import threading # 导入 threading
from ...moods.moods import MoodManager from ...moods.moods import MoodManager
from ...chat.emoji_manager import emoji_manager from ...chat.emoji_manager import emoji_manager
from .heartFC_generator import ResponseGenerator from .heartFC_generator import ResponseGenerator
@@ -14,6 +14,7 @@ from .interest import InterestManager
from src.plugins.chat.chat_stream import chat_manager from src.plugins.chat.chat_stream import chat_manager
from .pf_chatting import PFChatting from .pf_chatting import PFChatting
# 定义日志配置 # 定义日志配置
chat_config = LogConfig( chat_config = LogConfig(
console_format=CHAT_STYLE_CONFIG["console_format"], console_format=CHAT_STYLE_CONFIG["console_format"],
@@ -26,44 +27,81 @@ logger = get_module_logger("HeartFCController", config=chat_config)
INTEREST_MONITOR_INTERVAL_SECONDS = 1 INTEREST_MONITOR_INTERVAL_SECONDS = 1
# 合并后的版本:使用 __new__ + threading.Lock 实现线程安全单例,类名为 HeartFCController
class HeartFCController: class HeartFCController:
_instance = None # For potential singleton access if needed by MessageManager _instance = None
_lock = threading.Lock() # 使用 threading.Lock 保证 __new__ 线程安全
_initialized = False
def __init__(self): def __new__(cls, *args, **kwargs):
# --- Updated Init ---
if HeartFCController._instance is not None:
# Prevent re-initialization if used as a singleton
return
self.gpt = ResponseGenerator()
self.mood_manager = MoodManager.get_instance()
self.mood_manager.start_mood_update()
self.tool_user = ToolUser()
self.interest_manager = InterestManager()
self._interest_monitor_task: Optional[asyncio.Task] = None
# --- New PFChatting Management ---
self.pf_chatting_instances: Dict[str, PFChatting] = {}
self._pf_chatting_lock = Lock()
# --- End New PFChatting Management ---
HeartFCController._instance = self # Register instance
# --- End Updated Init ---
# --- Make dependencies accessible for PFChatting ---
# These are accessed via the passed instance in PFChatting
self.emoji_manager = emoji_manager
self.relationship_manager = relationship_manager
self.MessageManager = MessageManager # Pass the class/singleton access
# --- End dependencies ---
# --- Added Class Method for Singleton Access ---
@classmethod
def get_instance(cls):
if cls._instance is None: if cls._instance is None:
# This might indicate an issue if called before initialization with cls._lock:
logger.warning("HeartFCController get_instance called before initialization.") # Double-checked locking
# Optionally, initialize here if a strict singleton pattern is desired if cls._instance is None:
# cls._instance = cls() logger.debug("创建 HeartFCController 单例实例...")
cls._instance = super().__new__(cls)
return cls._instance return cls._instance
# --- End Added Class Method --- def __init__(self):
# 使用 _initialized 标志确保 __init__ 只执行一次
if self._initialized:
return
# 虽然 __new__ 保证了只有一个实例,但为了防止意外重入或多线程下的初始化竞争,
# 再次使用类锁保护初始化过程是更严谨的做法。
# 如果确定 __init__ 逻辑本身是幂等的或非关键的,可以省略这里的锁。
# 但为了保持原始逻辑的意图(防止重复初始化),这里保留检查。
with self.__class__._lock: # 确保初始化逻辑线程安全
if self._initialized: # 再次检查,防止锁等待期间其他线程已完成初始化
return
logger.info("正在初始化 HeartFCController 单例...")
self.gpt = ResponseGenerator()
self.mood_manager = MoodManager.get_instance()
# 注意mood_manager 的 start_mood_update 可能需要在应用主循环启动后调用,
# 或者确保其内部实现是安全的。这里保持原状。
self.mood_manager.start_mood_update()
self.tool_user = ToolUser()
# 注意InterestManager() 可能是另一个单例或需要特定初始化。
# 假设 InterestManager() 返回的是正确配置的实例。
self.interest_manager = InterestManager()
self._interest_monitor_task: Optional[asyncio.Task] = None
self.pf_chatting_instances: Dict[str, PFChatting] = {}
# _pf_chatting_lock 用于保护 pf_chatting_instances 的异步操作
self._pf_chatting_lock = asyncio.Lock() # 这个是 asyncio.Lock用于异步上下文
self.emoji_manager = emoji_manager # 假设是全局或已初始化的实例
self.relationship_manager = relationship_manager # 假设是全局或已初始化的实例
# MessageManager 可能是类本身或单例实例,根据其设计确定
self.MessageManager = MessageManager
self._initialized = True
logger.info("HeartFCController 单例初始化完成。")
@classmethod
def get_instance(cls):
"""获取 HeartFCController 的单例实例。"""
# 如果实例尚未创建,调用构造函数(这将触发 __new__ 和 __init__
if cls._instance is None:
# 在首次调用 get_instance 时创建实例。
# __new__ 中的锁会确保线程安全。
cls()
# 添加日志记录,说明实例是在 get_instance 调用时创建的
logger.info("HeartFCController 实例在首次 get_instance 时创建。")
elif not cls._initialized:
# 实例已创建但可能未初始化完成(理论上不太可能发生,除非 __init__ 异常)
logger.warning("HeartFCController 实例存在但尚未完成初始化。")
return cls._instance
# --- 新增:检查 PFChatting 状态的方法 --- #
def is_pf_chatting_active(self, stream_id: str) -> bool:
"""检查指定 stream_id 的 PFChatting 循环是否处于活动状态。"""
# 注意:这里直接访问字典,不加锁,因为读取通常是安全的,
# 并且 PFChatting 实例的 _loop_active 状态由其自身的异步循环管理。
# 如果需要更强的保证,可以在访问 pf_instance 前获取 _pf_chatting_lock
pf_instance = self.pf_chatting_instances.get(stream_id)
if pf_instance and pf_instance._loop_active: # 直接检查 PFChatting 实例的 _loop_active 属性
return True
return False
# --- 结束新增 --- #
async def start(self): async def start(self):
"""启动异步任务,如回复启动器""" """启动异步任务,如回复启动器"""

View File

@@ -13,6 +13,7 @@ from ...chat.message_buffer import message_buffer
from ...utils.timer_calculater import Timer from ...utils.timer_calculater import Timer
from .interest import InterestManager from .interest import InterestManager
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from .reasoning_chat import ReasoningChat
# 定义日志配置 # 定义日志配置
processor_config = LogConfig( processor_config = LogConfig(
@@ -29,7 +30,7 @@ class HeartFCProcessor:
def __init__(self): def __init__(self):
self.storage = MessageStorage() self.storage = MessageStorage()
self.interest_manager = InterestManager() self.interest_manager = InterestManager()
# self.chat_instance = chat_instance # 持有 HeartFC_Chat 实例 self.reasoning_chat = ReasoningChat.get_instance()
async def process_message(self, message_data: str) -> None: async def process_message(self, message_data: str) -> None:
"""处理接收到的原始消息数据,完成消息解析、缓冲、过滤、存储、兴趣度计算与更新等核心流程。 """处理接收到的原始消息数据,完成消息解析、缓冲、过滤、存储、兴趣度计算与更新等核心流程。
@@ -72,11 +73,11 @@ class HeartFCProcessor:
user_info=userinfo, user_info=userinfo,
group_info=groupinfo, group_info=groupinfo,
) )
if not chat:
logger.error( # --- 添加兴趣追踪启动 ---
f"无法为消息创建或获取聊天流: user {userinfo.user_id}, group {groupinfo.group_id if groupinfo else 'None'}" # 在获取到 chat 对象后,启动对该聊天流的兴趣监控
) await self.reasoning_chat.start_monitoring_interest(chat)
return # --- 结束添加 ---
message.update_chat_stream(chat) message.update_chat_stream(chat)
@@ -90,7 +91,6 @@ class HeartFCProcessor:
message.raw_message, chat, userinfo message.raw_message, chat, userinfo
): ):
return return
logger.trace(f"过滤词/正则表达式过滤成功: {message.processed_plain_text}")
# 查询缓冲器结果 # 查询缓冲器结果
buffer_result = await message_buffer.query_buffer_result(message) buffer_result = await message_buffer.query_buffer_result(message)
@@ -152,6 +152,8 @@ class HeartFCProcessor:
f"使用激活率 {interested_rate:.2f} 更新后 (通过缓冲后),当前兴趣度: {current_interest:.2f}" f"使用激活率 {interested_rate:.2f} 更新后 (通过缓冲后),当前兴趣度: {current_interest:.2f}"
) )
self.interest_manager.add_interest_dict(message, interested_rate, is_mentioned)
except Exception as e: except Exception as e:
logger.error(f"更新兴趣度失败: {e}") # 调整日志消息 logger.error(f"更新兴趣度失败: {e}") # 调整日志消息
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())

View File

@@ -6,6 +6,7 @@ import json # 引入 json
import os # 引入 os import os # 引入 os
from typing import Optional # <--- 添加导入 from typing import Optional # <--- 添加导入
import random # <--- 添加导入 random import random # <--- 添加导入 random
from src.plugins.chat.message import MessageRecv
from src.common.logger import get_module_logger, LogConfig, DEFAULT_CONFIG # 引入 DEFAULT_CONFIG from src.common.logger import get_module_logger, LogConfig, DEFAULT_CONFIG # 引入 DEFAULT_CONFIG
from src.plugins.chat.chat_stream import chat_manager # *** Import ChatManager *** from src.plugins.chat.chat_stream import chat_manager # *** Import ChatManager ***
@@ -66,6 +67,13 @@ class InterestChatting:
self.is_above_threshold: bool = False # 标记兴趣值是否高于阈值 self.is_above_threshold: bool = False # 标记兴趣值是否高于阈值
# --- 结束:概率回复相关属性 --- # --- 结束:概率回复相关属性 ---
# 记录激发兴趣对(消息id,激活值)
self.interest_dict = {}
def add_interest_dict(self, message: MessageRecv, interest_value: float, is_mentioned: bool):
# Store the MessageRecv object and the interest value as a tuple
self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned)
def _calculate_decay(self, current_time: float): def _calculate_decay(self, current_time: float):
"""计算从上次更新到现在的衰减""" """计算从上次更新到现在的衰减"""
time_delta = current_time - self.last_update_time time_delta = current_time - self.last_update_time
@@ -445,6 +453,10 @@ class InterestManager:
stream_name = chat_manager.get_stream_name(stream_id) or stream_id # 获取流名称 stream_name = chat_manager.get_stream_name(stream_id) or stream_id # 获取流名称
logger.warning(f"尝试降低不存在的聊天流 {stream_name} 的兴趣度") logger.warning(f"尝试降低不存在的聊天流 {stream_name} 的兴趣度")
def add_interest_dict(self, message: MessageRecv, interest_value: float, is_mentioned: bool):
interest_chatting = self._get_or_create_interest_chatting(message.chat_stream.stream_id)
interest_chatting.add_interest_dict(message, interest_value, is_mentioned)
def cleanup_inactive_chats(self, max_age_seconds=INACTIVE_THRESHOLD_SECONDS): def cleanup_inactive_chats(self, max_age_seconds=INACTIVE_THRESHOLD_SECONDS):
""" """
清理长时间不活跃的聊天流记录 清理长时间不活跃的聊天流记录

View File

@@ -220,9 +220,8 @@ class MessageManager:
await asyncio.sleep(typing_time) await asyncio.sleep(typing_time)
logger.debug(f"\n{message_earliest.processed_plain_text},{typing_time},等待输入时间结束\n") logger.debug(f"\n{message_earliest.processed_plain_text},{typing_time},等待输入时间结束\n")
await self.storage.store_message(message_earliest, message_earliest.chat_stream)
await MessageSender().send_message(message_earliest) await MessageSender().send_message(message_earliest)
await self.storage.store_message(message_earliest, message_earliest.chat_stream)
container.remove_message(message_earliest) container.remove_message(message_earliest)

View File

@@ -15,6 +15,9 @@ from src.config.config import global_config
from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move
from src.plugins.utils.timer_calculater import Timer # <--- Import Timer from src.plugins.utils.timer_calculater import Timer # <--- Import Timer
INITIAL_DURATION = 60.0
# 定义日志配置 (使用 loguru 格式) # 定义日志配置 (使用 loguru 格式)
interest_log_config = LogConfig( interest_log_config = LogConfig(
console_format=PFC_STYLE_CONFIG["console_format"], # 使用默认控制台格式 console_format=PFC_STYLE_CONFIG["console_format"], # 使用默认控制台格式
@@ -67,7 +70,7 @@ class PFChatting:
Args: Args:
chat_id: The identifier for the chat stream (e.g., stream_id). chat_id: The identifier for the chat stream (e.g., stream_id).
heartfc_controller_instance: 访问共享资源和方法的主HeartFC_Controller实例。 heartfc_controller_instance: 访问共享资源和方法的主HeartFCController实例。
""" """
self.heartfc_controller = heartfc_controller_instance # Store the controller instance self.heartfc_controller = heartfc_controller_instance # Store the controller instance
self.stream_id: str = chat_id self.stream_id: str = chat_id
@@ -91,7 +94,7 @@ class PFChatting:
self._loop_active: bool = False # Is the loop currently running? self._loop_active: bool = False # Is the loop currently running?
self._loop_task: Optional[asyncio.Task] = None # Stores the main loop task self._loop_task: Optional[asyncio.Task] = None # Stores the main loop task
self._trigger_count_this_activation: int = 0 # Counts triggers within an active period self._trigger_count_this_activation: int = 0 # Counts triggers within an active period
self._initial_duration: float = 60.0 # 首次触发增加的时间 self._initial_duration: float = INITIAL_DURATION # 首次触发增加的时间
self._last_added_duration: float = self._initial_duration # <--- 新增:存储上次增加的时间 self._last_added_duration: float = self._initial_duration # <--- 新增:存储上次增加的时间
def _get_log_prefix(self) -> str: def _get_log_prefix(self) -> str:

View File

@@ -0,0 +1,412 @@
import time
import threading # 导入 threading
from random import random
import traceback
import asyncio
from typing import List, Dict
from ...moods.moods import MoodManager
from ....config.config import global_config
from ...chat.emoji_manager import emoji_manager
from .reasoning_generator import ResponseGenerator
from ...chat.message import MessageSending, MessageRecv, MessageThinking, MessageSet
from ...chat.messagesender import message_manager
from ...storage.storage import MessageStorage
from ...chat.utils import is_mentioned_bot_in_message
from ...chat.utils_image import image_path_to_base64
from ...willing.willing_manager import willing_manager
from ...message import UserInfo, Seg
from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig
from src.plugins.chat.chat_stream import ChatStream
from src.plugins.person_info.relationship_manager import relationship_manager
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from src.plugins.utils.timer_calculater import Timer
from .interest import InterestManager
from .heartFC_controler import HeartFCController # 导入 HeartFCController
# 定义日志配置
chat_config = LogConfig(
console_format=CHAT_STYLE_CONFIG["console_format"],
file_format=CHAT_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("reasoning_chat", config=chat_config)
class ReasoningChat:
_instance = None
_lock = threading.Lock()
_initialized = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
# Double-check locking
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
# 防止重复初始化
if self._initialized:
return
with self.__class__._lock: # 使用类锁确保线程安全
if self._initialized:
return
logger.info("正在初始化 ReasoningChat 单例...") # 添加日志
self.storage = MessageStorage()
self.gpt = ResponseGenerator()
self.mood_manager = MoodManager.get_instance()
self.mood_manager.start_mood_update()
# 用于存储每个 chat stream 的兴趣监控任务
self._interest_monitoring_tasks: Dict[str, asyncio.Task] = {}
self._initialized = True
self.interest_manager = InterestManager()
logger.info("ReasoningChat 单例初始化完成。") # 添加日志
@classmethod
def get_instance(cls):
"""获取 ReasoningChat 的单例实例。"""
if cls._instance is None:
# 如果实例还未创建(理论上应该在 main 中初始化,但作为备用)
logger.warning("ReasoningChat 实例在首次 get_instance 时创建。")
cls() # 调用构造函数来创建实例
return cls._instance
@staticmethod
async def _create_thinking_message(message, chat, userinfo, messageinfo):
"""创建思考消息"""
bot_user_info = UserInfo(
user_id=global_config.BOT_QQ,
user_nickname=global_config.BOT_NICKNAME,
platform=messageinfo.platform,
)
thinking_time_point = round(time.time(), 2)
thinking_id = "mt" + str(thinking_time_point)
thinking_message = MessageThinking(
message_id=thinking_id,
chat_stream=chat,
bot_user_info=bot_user_info,
reply=message,
thinking_start_time=thinking_time_point,
)
message_manager.add_message(thinking_message)
return thinking_id
@staticmethod
async def _send_response_messages(message, chat, response_set: List[str], thinking_id) -> MessageSending:
"""发送回复消息"""
container = message_manager.get_container(chat.stream_id)
thinking_message = None
for msg in container.messages:
if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id:
thinking_message = msg
container.messages.remove(msg)
break
if not thinking_message:
logger.warning("未找到对应的思考消息,可能已超时被移除")
return
thinking_start_time = thinking_message.thinking_start_time
message_set = MessageSet(chat, thinking_id)
mark_head = False
first_bot_msg = None
for msg in response_set:
message_segment = Seg(type="text", data=msg)
bot_message = MessageSending(
message_id=thinking_id,
chat_stream=chat,
bot_user_info=UserInfo(
user_id=global_config.BOT_QQ,
user_nickname=global_config.BOT_NICKNAME,
platform=message.message_info.platform,
),
sender_info=message.message_info.user_info,
message_segment=message_segment,
reply=message,
is_head=not mark_head,
is_emoji=False,
thinking_start_time=thinking_start_time,
)
if not mark_head:
mark_head = True
first_bot_msg = bot_message
message_set.add_message(bot_message)
message_manager.add_message(message_set)
return first_bot_msg
@staticmethod
async def _handle_emoji(message, chat, response):
"""处理表情包"""
if random() < global_config.emoji_chance:
emoji_raw = await emoji_manager.get_emoji_for_text(response)
if emoji_raw:
emoji_path, description = emoji_raw
emoji_cq = image_path_to_base64(emoji_path)
thinking_time_point = round(message.message_info.time, 2)
message_segment = Seg(type="emoji", data=emoji_cq)
bot_message = MessageSending(
message_id="mt" + str(thinking_time_point),
chat_stream=chat,
bot_user_info=UserInfo(
user_id=global_config.BOT_QQ,
user_nickname=global_config.BOT_NICKNAME,
platform=message.message_info.platform,
),
sender_info=message.message_info.user_info,
message_segment=message_segment,
reply=message,
is_head=False,
is_emoji=True,
)
message_manager.add_message(bot_message)
async def _update_relationship(self, message: MessageRecv, response_set):
"""更新关系情绪"""
ori_response = ",".join(response_set)
stance, emotion = await self.gpt._get_emotion_tags(ori_response, message.processed_plain_text)
await relationship_manager.calculate_update_relationship_value(
chat_stream=message.chat_stream, label=emotion, stance=stance
)
self.mood_manager.update_mood_from_emotion(emotion, global_config.mood_intensity_factor)
async def _find_interested_message(self, chat: ChatStream) -> None:
# 此函数设计为后台任务,轮询指定 chat 的兴趣消息。
# 它通常由外部代码在 chat 流活跃时启动。
controller = HeartFCController.get_instance() # 获取控制器实例
if not controller:
logger.error(f"无法获取 HeartFCController 实例,无法检查 PFChatting 状态。stream: {chat.stream_id}")
# 在没有控制器的情况下可能需要决定是继续处理还是完全停止?这里暂时假设继续
pass # 或者 return?
while True:
await asyncio.sleep(1) # 每秒检查一次
interest_chatting = self.interest_manager.get_interest_chatting(chat.stream_id)
if not interest_chatting:
continue
interest_dict = interest_chatting.interest_dict if interest_chatting.interest_dict else {}
items_to_process = list(interest_dict.items())
if not items_to_process:
continue
for msg_id, (message, interest_value, is_mentioned) in items_to_process:
# --- 检查 PFChatting 是否活跃 --- #
pf_active = False
if controller:
pf_active = controller.is_pf_chatting_active(chat.stream_id)
if pf_active:
# 如果 PFChatting 活跃,则跳过处理,直接移除消息
removed_item = interest_dict.pop(msg_id, None)
if removed_item:
logger.debug(f"PFChatting 活跃,已跳过并移除兴趣消息 {msg_id} for stream: {chat.stream_id}")
continue # 处理下一条消息
# --- 结束检查 --- #
# 只有当 PFChatting 不活跃时才执行以下处理逻辑
try:
# logger.debug(f"正在处理消息 {msg_id} for stream: {chat.stream_id}") # 可选调试信息
await self.normal_reasoning_chat(
message=message,
chat=chat,
is_mentioned=is_mentioned,
interested_rate=interest_value,
)
# logger.debug(f"处理完成消息 {msg_id}") # 可选调试信息
except Exception as e:
logger.error(f"处理兴趣消息 {msg_id} 时出错: {e}\n{traceback.format_exc()}")
finally:
# 无论处理成功与否且PFChatting不活跃都尝试从原始字典中移除该消息
removed_item = interest_dict.pop(msg_id, None)
if removed_item:
logger.debug(f"已从兴趣字典中移除消息 {msg_id}")
async def normal_reasoning_chat(
self, message: MessageRecv, chat: ChatStream, is_mentioned: bool, interested_rate: float
) -> None:
timing_results = {}
userinfo = message.message_info.user_info
messageinfo = message.message_info
is_mentioned, reply_probability = is_mentioned_bot_in_message(message)
# 意愿管理器设置当前message信息
willing_manager.setup(message, chat, is_mentioned, interested_rate)
# 获取回复概率
is_willing = False
if reply_probability != 1:
is_willing = True
reply_probability = await willing_manager.get_reply_probability(message.message_info.message_id)
if message.message_info.additional_config:
if "maimcore_reply_probability_gain" in message.message_info.additional_config.keys():
reply_probability += message.message_info.additional_config["maimcore_reply_probability_gain"]
# 打印消息信息
mes_name = chat.group_info.group_name if chat.group_info else "私聊"
current_time = time.strftime("%H:%M:%S", time.localtime(message.message_info.time))
willing_log = f"[回复意愿:{await willing_manager.get_willing(chat.stream_id):.2f}]" if is_willing else ""
logger.info(
f"[{current_time}][{mes_name}]"
f"{chat.user_info.user_nickname}:"
f"{message.processed_plain_text}{willing_log}[概率:{reply_probability * 100:.1f}%]"
)
do_reply = False
if random() < reply_probability:
do_reply = True
# 回复前处理
await willing_manager.before_generate_reply_handle(message.message_info.message_id)
# 创建思考消息
with Timer("创建思考消息", timing_results):
thinking_id = await self._create_thinking_message(message, chat, userinfo, messageinfo)
logger.debug(f"创建捕捉器thinking_id:{thinking_id}")
info_catcher = info_catcher_manager.get_info_catcher(thinking_id)
info_catcher.catch_decide_to_response(message)
# 生成回复
try:
with Timer("生成回复", timing_results):
response_set = await self.gpt.generate_response(message, thinking_id)
info_catcher.catch_after_generate_response(timing_results["生成回复"])
except Exception as e:
logger.error(f"回复生成出现错误:{str(e)} {traceback.format_exc()}")
response_set = None
if not response_set:
logger.info("为什么生成回复失败?")
return
# 发送消息
with Timer("发送消息", timing_results):
first_bot_msg = await self._send_response_messages(message, chat, response_set, thinking_id)
info_catcher.catch_after_response(timing_results["发送消息"], response_set, first_bot_msg)
info_catcher.done_catch()
# 处理表情包
with Timer("处理表情包", timing_results):
await self._handle_emoji(message, chat, response_set)
# 更新关系情绪
with Timer("更新关系情绪", timing_results):
await self._update_relationship(message, response_set)
# 回复后处理
await willing_manager.after_generate_reply_handle(message.message_info.message_id)
# 输出性能计时结果
if do_reply:
timing_str = " | ".join([f"{step}: {duration:.2f}" for step, duration in timing_results.items()])
trigger_msg = message.processed_plain_text
response_msg = " ".join(response_set) if response_set else "无回复"
logger.info(f"触发消息: {trigger_msg[:20]}... | 推理消息: {response_msg[:20]}... | 性能计时: {timing_str}")
else:
# 不回复处理
await willing_manager.not_reply_handle(message.message_info.message_id)
# 意愿管理器注销当前message信息
willing_manager.delete(message.message_info.message_id)
@staticmethod
def _check_ban_words(text: str, chat, userinfo) -> bool:
"""检查消息中是否包含过滤词"""
for word in global_config.ban_words:
if word in text:
logger.info(
f"[{chat.group_info.group_name if chat.group_info else '私聊'}]{userinfo.user_nickname}:{text}"
)
logger.info(f"[过滤词识别]消息中含有{word}filtered")
return True
return False
@staticmethod
def _check_ban_regex(text: str, chat, userinfo) -> bool:
"""检查消息是否匹配过滤正则表达式"""
for pattern in global_config.ban_msgs_regex:
if pattern.search(text):
logger.info(
f"[{chat.group_info.group_name if chat.group_info else '私聊'}]{userinfo.user_nickname}:{text}"
)
logger.info(f"[正则表达式过滤]消息匹配到{pattern}filtered")
return True
return False
async def start_monitoring_interest(self, chat: ChatStream):
"""为指定的 ChatStream 启动后台兴趣消息监控任务。"""
stream_id = chat.stream_id
# 检查任务是否已在运行
if stream_id in self._interest_monitoring_tasks and not self._interest_monitoring_tasks[stream_id].done():
task = self._interest_monitoring_tasks[stream_id]
if not task.cancelled(): # 确保任务未被取消
logger.info(f"兴趣监控任务已在运行 stream: {stream_id}")
return
else:
logger.info(f"发现已取消的任务,重新创建 stream: {stream_id}")
# 如果任务被取消了,允许重新创建
logger.info(f"启动兴趣监控任务 stream: {stream_id}...")
# 创建新的后台任务来运行 _find_interested_message
task = asyncio.create_task(self._find_interested_message(chat))
self._interest_monitoring_tasks[stream_id] = task
# 添加回调,当任务完成(或被取消)时,自动从字典中移除
task.add_done_callback(lambda t: self._handle_task_completion(stream_id, t))
def _handle_task_completion(self, stream_id: str, task: asyncio.Task):
"""处理监控任务完成的回调。"""
try:
# 检查任务是否因异常而结束
exception = task.exception()
if exception:
logger.error(f"兴趣监控任务 stream {stream_id} 异常结束: {exception}", exc_info=exception)
elif task.cancelled():
logger.info(f"兴趣监控任务 stream {stream_id} 已被取消。")
else:
logger.info(f"兴趣监控任务 stream {stream_id} 正常结束。") # 理论上 while True 不会正常结束
except asyncio.CancelledError:
logger.info(f"兴趣监控任务 stream {stream_id} 在完成处理期间被取消。")
finally:
# 无论如何都从字典中移除
removed_task = self._interest_monitoring_tasks.pop(stream_id, None)
if removed_task:
logger.debug(f"已从监控任务字典移除 stream: {stream_id}")
async def stop_monitoring_interest(self, stream_id: str):
"""停止指定 stream_id 的兴趣消息监控任务。"""
if stream_id in self._interest_monitoring_tasks:
task = self._interest_monitoring_tasks[stream_id]
if not task.done():
logger.info(f"正在停止兴趣监控任务 stream: {stream_id}...")
task.cancel() # 请求取消任务
try:
# 等待任务实际被取消(可选,提供更明确的停止)
# 设置超时以防万一
await asyncio.wait_for(task, timeout=5.0)
except asyncio.CancelledError:
logger.info(f"兴趣监控任务 stream {stream_id} 已确认取消。")
except asyncio.TimeoutError:
logger.warning(f"停止兴趣监控任务 stream {stream_id} 超时。任务可能仍在运行。")
except Exception as e:
# 捕获 task.exception() 可能在取消期间重新引发的错误
logger.error(f"停止兴趣监控任务 stream {stream_id} 时发生错误: {e}")
# 任务最终会由 done_callback 移除,或在这里再次确认移除
self._interest_monitoring_tasks.pop(stream_id, None)
else:
logger.warning(f"尝试停止不存在或已停止的监控任务 stream: {stream_id}")

View File

@@ -0,0 +1,199 @@
from typing import List, Optional, Tuple, Union
import random
from ...models.utils_model import LLMRequest
from ....config.config import global_config
from ...chat.message import MessageThinking
from .reasoning_prompt_builder import prompt_builder
from ...chat.utils import process_llm_response
from ...utils.timer_calculater import Timer
from src.common.logger import get_module_logger, LogConfig, LLM_STYLE_CONFIG
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
# 定义日志配置
llm_config = LogConfig(
# 使用消息发送专用样式
console_format=LLM_STYLE_CONFIG["console_format"],
file_format=LLM_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("llm_generator", config=llm_config)
class ResponseGenerator:
def __init__(self):
self.model_reasoning = LLMRequest(
model=global_config.llm_reasoning,
temperature=0.7,
max_tokens=3000,
request_type="response_reasoning",
)
self.model_normal = LLMRequest(
model=global_config.llm_normal,
temperature=global_config.llm_normal["temp"],
max_tokens=256,
request_type="response_reasoning",
)
self.model_sum = LLMRequest(
model=global_config.llm_summary_by_topic, temperature=0.7, max_tokens=3000, request_type="relation"
)
self.current_model_type = "r1" # 默认使用 R1
self.current_model_name = "unknown model"
async def generate_response(self, message: MessageThinking, thinking_id: str) -> Optional[Union[str, List[str]]]:
"""根据当前模型类型选择对应的生成函数"""
# 从global_config中获取模型概率值并选择模型
if random.random() < global_config.model_reasoning_probability:
self.current_model_type = "深深地"
current_model = self.model_reasoning
else:
self.current_model_type = "浅浅的"
current_model = self.model_normal
logger.info(
f"{self.current_model_type}思考:{message.processed_plain_text[:30] + '...' if len(message.processed_plain_text) > 30 else message.processed_plain_text}"
) # noqa: E501
model_response = await self._generate_response_with_model(message, current_model, thinking_id)
# print(f"raw_content: {model_response}")
if model_response:
logger.info(f"{global_config.BOT_NICKNAME}的回复是:{model_response}")
model_response = await self._process_response(model_response)
return model_response
else:
logger.info(f"{self.current_model_type}思考,失败")
return None
async def _generate_response_with_model(self, message: MessageThinking, model: LLMRequest, thinking_id: str):
info_catcher = info_catcher_manager.get_info_catcher(thinking_id)
if message.chat_stream.user_info.user_cardname and message.chat_stream.user_info.user_nickname:
sender_name = (
f"[({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}]"
f"{message.chat_stream.user_info.user_cardname}"
)
elif message.chat_stream.user_info.user_nickname:
sender_name = f"({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}"
else:
sender_name = f"用户({message.chat_stream.user_info.user_id})"
logger.debug("开始使用生成回复-2")
# 构建prompt
with Timer() as t_build_prompt:
prompt = await prompt_builder._build_prompt(
message.chat_stream,
message_txt=message.processed_plain_text,
sender_name=sender_name,
stream_id=message.chat_stream.stream_id,
)
logger.info(f"构建prompt时间: {t_build_prompt.human_readable}")
try:
content, reasoning_content, self.current_model_name = await model.generate_response(prompt)
info_catcher.catch_after_llm_generated(
prompt=prompt, response=content, reasoning_content=reasoning_content, model_name=self.current_model_name
)
except Exception:
logger.exception("生成回复时出错")
return None
# 保存到数据库
# self._save_to_db(
# message=message,
# sender_name=sender_name,
# prompt=prompt,
# content=content,
# reasoning_content=reasoning_content,
# # reasoning_content_check=reasoning_content_check if global_config.enable_kuuki_read else ""
# )
return content
# def _save_to_db(
# self,
# message: MessageRecv,
# sender_name: str,
# prompt: str,
# content: str,
# reasoning_content: str,
# ):
# """保存对话记录到数据库"""
# db.reasoning_logs.insert_one(
# {
# "time": time.time(),
# "chat_id": message.chat_stream.stream_id,
# "user": sender_name,
# "message": message.processed_plain_text,
# "model": self.current_model_name,
# "reasoning": reasoning_content,
# "response": content,
# "prompt": prompt,
# }
# )
async def _get_emotion_tags(self, content: str, processed_plain_text: str):
"""提取情感标签,结合立场和情绪"""
try:
# 构建提示词,结合回复内容、被回复的内容以及立场分析
prompt = f"""
请严格根据以下对话内容,完成以下任务:
1. 判断回复者对被回复者观点的直接立场:
- "支持":明确同意或强化被回复者观点
- "反对":明确反驳或否定被回复者观点
- "中立":不表达明确立场或无关回应
2. 从"开心,愤怒,悲伤,惊讶,平静,害羞,恐惧,厌恶,困惑"中选出最匹配的1个情感标签
3. 按照"立场-情绪"的格式直接输出结果,例如:"反对-愤怒"
4. 考虑回复者的人格设定为{global_config.personality_core}
对话示例:
被回复「A就是笨」
回复「A明明很聪明」 → 反对-愤怒
当前对话:
被回复:「{processed_plain_text}
回复:「{content}
输出要求:
- 只需输出"立场-情绪"结果,不要解释
- 严格基于文字直接表达的对立关系判断
"""
# 调用模型生成结果
result, _, _ = await self.model_sum.generate_response(prompt)
result = result.strip()
# 解析模型输出的结果
if "-" in result:
stance, emotion = result.split("-", 1)
valid_stances = ["支持", "反对", "中立"]
valid_emotions = ["开心", "愤怒", "悲伤", "惊讶", "害羞", "平静", "恐惧", "厌恶", "困惑"]
if stance in valid_stances and emotion in valid_emotions:
return stance, emotion # 返回有效的立场-情绪组合
else:
logger.debug(f"无效立场-情感组合:{result}")
return "中立", "平静" # 默认返回中立-平静
else:
logger.debug(f"立场-情感格式错误:{result}")
return "中立", "平静" # 格式错误时返回默认值
except Exception as e:
logger.debug(f"获取情感标签时出错: {e}")
return "中立", "平静" # 出错时返回默认值
@staticmethod
async def _process_response(content: str) -> Tuple[List[str], List[str]]:
"""处理响应内容,返回处理后的内容和情感标签"""
if not content:
return None, []
processed_response = process_llm_response(content)
# print(f"得到了处理后的llm返回{processed_response}")
return processed_response

View File

@@ -0,0 +1,445 @@
import random
import time
from typing import Optional, Union
from ....common.database import db
from ...chat.utils import get_embedding, get_recent_group_detailed_plain_text, get_recent_group_speaker
from ...chat.chat_stream import chat_manager
from ...moods.moods import MoodManager
from ....individuality.individuality import Individuality
from ...memory_system.Hippocampus import HippocampusManager
from ...schedule.schedule_generator import bot_schedule
from ....config.config import global_config
from ...person_info.relationship_manager import relationship_manager
from src.common.logger import get_module_logger
from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager
logger = get_module_logger("prompt")
def init_prompt():
Prompt(
"""
{relation_prompt_all}
{memory_prompt}
{prompt_info}
{schedule_prompt}
{chat_target}
{chat_talking_prompt}
现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n
你的网名叫{bot_name},有人也叫你{bot_other_names}{prompt_personality}
你正在{chat_target_2},现在请你读读之前的聊天记录,{mood_prompt},然后给出日常且口语化的回复,平淡一些,
尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger}
请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景,尽量不要说你说过的话
请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。
{moderation_prompt}不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )。""",
"reasoning_prompt_main",
)
Prompt(
"{relation_prompt}关系等级越大,关系越好,请分析聊天记录,根据你和说话者{sender_name}的关系和态度进行回复,明确你的立场和情感。",
"relationship_prompt",
)
Prompt(
"你想起你之前见过的事情:{related_memory_info}\n以上是你的回忆,不一定是目前聊天里的人说的,也不一定是现在发生的事情,请记住。\n",
"memory_prompt",
)
Prompt("你现在正在做的事情是:{schedule_info}", "schedule_prompt")
Prompt("\n你有以下这些**知识**\n{prompt_info}\n请你**记住上面的知识**,之后可能会用到。\n", "knowledge_prompt")
class PromptBuilder:
def __init__(self):
self.prompt_built = ""
self.activate_messages = ""
async def _build_prompt(
self, chat_stream, message_txt: str, sender_name: str = "某人", stream_id: Optional[int] = None
) -> tuple[str, str]:
# 开始构建prompt
prompt_personality = ""
# person
individuality = Individuality.get_instance()
personality_core = individuality.personality.personality_core
prompt_personality += personality_core
personality_sides = individuality.personality.personality_sides
random.shuffle(personality_sides)
prompt_personality += f",{personality_sides[0]}"
identity_detail = individuality.identity.identity_detail
random.shuffle(identity_detail)
prompt_personality += f",{identity_detail[0]}"
# 关系
who_chat_in_group = [
(chat_stream.user_info.platform, chat_stream.user_info.user_id, chat_stream.user_info.user_nickname)
]
who_chat_in_group += get_recent_group_speaker(
stream_id,
(chat_stream.user_info.platform, chat_stream.user_info.user_id),
limit=global_config.MAX_CONTEXT_SIZE,
)
relation_prompt = ""
for person in who_chat_in_group:
relation_prompt += await relationship_manager.build_relationship_info(person)
# relation_prompt_all = (
# f"{relation_prompt}关系等级越大,关系越好,请分析聊天记录,"
# f"根据你和说话者{sender_name}的关系和态度进行回复,明确你的立场和情感。"
# )
# 心情
mood_manager = MoodManager.get_instance()
mood_prompt = mood_manager.get_prompt()
# logger.info(f"心情prompt: {mood_prompt}")
# 调取记忆
memory_prompt = ""
related_memory = await HippocampusManager.get_instance().get_memory_from_text(
text=message_txt, max_memory_num=2, max_memory_length=2, max_depth=3, fast_retrieval=False
)
related_memory_info = ""
if related_memory:
for memory in related_memory:
related_memory_info += memory[1]
# memory_prompt = f"你想起你之前见过的事情:{related_memory_info}。\n以上是你的回忆不一定是目前聊天里的人说的也不一定是现在发生的事情请记住。\n"
memory_prompt = await global_prompt_manager.format_prompt(
"memory_prompt", related_memory_info=related_memory_info
)
# print(f"相关记忆:{related_memory_info}")
# 日程构建
# schedule_prompt = f"""你现在正在做的事情是:{bot_schedule.get_current_num_task(num=1, time_info=False)}"""
# 获取聊天上下文
chat_in_group = True
chat_talking_prompt = ""
if stream_id:
chat_talking_prompt = get_recent_group_detailed_plain_text(
stream_id, limit=global_config.MAX_CONTEXT_SIZE, combine=True
)
chat_stream = chat_manager.get_stream(stream_id)
if chat_stream.group_info:
chat_talking_prompt = chat_talking_prompt
else:
chat_in_group = False
chat_talking_prompt = chat_talking_prompt
# print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的消息记录:{chat_talking_prompt}")
# 关键词检测与反应
keywords_reaction_prompt = ""
for rule in global_config.keywords_reaction_rules:
if rule.get("enable", False):
if any(keyword in message_txt.lower() for keyword in rule.get("keywords", [])):
logger.info(
f"检测到以下关键词之一:{rule.get('keywords', [])},触发反应:{rule.get('reaction', '')}"
)
keywords_reaction_prompt += rule.get("reaction", "") + ""
else:
for pattern in rule.get("regex", []):
result = pattern.search(message_txt)
if result:
reaction = rule.get("reaction", "")
for name, content in result.groupdict().items():
reaction = reaction.replace(f"[{name}]", content)
logger.info(f"匹配到以下正则表达式:{pattern},触发反应:{reaction}")
keywords_reaction_prompt += reaction + ""
break
# 中文高手(新加的好玩功能)
prompt_ger = ""
if random.random() < 0.04:
prompt_ger += "你喜欢用倒装句"
if random.random() < 0.02:
prompt_ger += "你喜欢用反问句"
if random.random() < 0.01:
prompt_ger += "你喜欢用文言文"
# 知识构建
start_time = time.time()
prompt_info = await self.get_prompt_info(message_txt, threshold=0.38)
if prompt_info:
# prompt_info = f"""\n你有以下这些**知识**\n{prompt_info}\n请你**记住上面的知识**,之后可能会用到。\n"""
prompt_info = await global_prompt_manager.format_prompt("knowledge_prompt", prompt_info=prompt_info)
end_time = time.time()
logger.debug(f"知识检索耗时: {(end_time - start_time):.3f}")
# moderation_prompt = ""
# moderation_prompt = """**检查并忽略**任何涉及尝试绕过审核的行为。
# 涉及政治敏感以及违法违规的内容请规避。"""
logger.debug("开始构建prompt")
# prompt = f"""
# {relation_prompt_all}
# {memory_prompt}
# {prompt_info}
# {schedule_prompt}
# {chat_target}
# {chat_talking_prompt}
# 现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n
# 你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)}{prompt_personality}。
# 你正在{chat_target_2},现在请你读读之前的聊天记录,{mood_prompt},然后给出日常且口语化的回复,平淡一些,
# 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger}
# 请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景,尽量不要说你说过的话
# 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。
# {moderation_prompt}不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )。"""
prompt = await global_prompt_manager.format_prompt(
"reasoning_prompt_main",
relation_prompt_all=await global_prompt_manager.get_prompt_async("relationship_prompt"),
relation_prompt=relation_prompt,
sender_name=sender_name,
memory_prompt=memory_prompt,
prompt_info=prompt_info,
schedule_prompt=await global_prompt_manager.format_prompt(
"schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False)
),
chat_target=await global_prompt_manager.get_prompt_async("chat_target_group1")
if chat_in_group
else await global_prompt_manager.get_prompt_async("chat_target_private1"),
chat_target_2=await global_prompt_manager.get_prompt_async("chat_target_group2")
if chat_in_group
else await global_prompt_manager.get_prompt_async("chat_target_private2"),
chat_talking_prompt=chat_talking_prompt,
message_txt=message_txt,
bot_name=global_config.BOT_NICKNAME,
bot_other_names="/".join(
global_config.BOT_ALIAS_NAMES,
),
prompt_personality=prompt_personality,
mood_prompt=mood_prompt,
keywords_reaction_prompt=keywords_reaction_prompt,
prompt_ger=prompt_ger,
moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"),
)
return prompt
async def get_prompt_info(self, message: str, threshold: float):
start_time = time.time()
related_info = ""
logger.debug(f"获取知识库内容,元消息:{message[:30]}...,消息长度: {len(message)}")
# 1. 先从LLM获取主题类似于记忆系统的做法
topics = []
# try:
# # 先尝试使用记忆系统的方法获取主题
# hippocampus = HippocampusManager.get_instance()._hippocampus
# topic_num = min(5, max(1, int(len(message) * 0.1)))
# topics_response = await hippocampus.llm_topic_judge.generate_response(hippocampus.find_topic_llm(message, topic_num))
# # 提取关键词
# topics = re.findall(r"<([^>]+)>", topics_response[0])
# if not topics:
# topics = []
# else:
# topics = [
# topic.strip()
# for topic in ",".join(topics).replace("", ",").replace("、", ",").replace(" ", ",").split(",")
# if topic.strip()
# ]
# logger.info(f"从LLM提取的主题: {', '.join(topics)}")
# except Exception as e:
# logger.error(f"从LLM提取主题失败: {str(e)}")
# # 如果LLM提取失败使用jieba分词提取关键词作为备选
# words = jieba.cut(message)
# topics = [word for word in words if len(word) > 1][:5]
# logger.info(f"使用jieba提取的主题: {', '.join(topics)}")
# 如果无法提取到主题,直接使用整个消息
if not topics:
logger.info("未能提取到任何主题,使用整个消息进行查询")
embedding = await get_embedding(message, request_type="prompt_build")
if not embedding:
logger.error("获取消息嵌入向量失败")
return ""
related_info = self.get_info_from_db(embedding, limit=3, threshold=threshold)
logger.info(f"知识库检索完成,总耗时: {time.time() - start_time:.3f}")
return related_info
# 2. 对每个主题进行知识库查询
logger.info(f"开始处理{len(topics)}个主题的知识库查询")
# 优化批量获取嵌入向量减少API调用
embeddings = {}
topics_batch = [topic for topic in topics if len(topic) > 0]
if message: # 确保消息非空
topics_batch.append(message)
# 批量获取嵌入向量
embed_start_time = time.time()
for text in topics_batch:
if not text or len(text.strip()) == 0:
continue
try:
embedding = await get_embedding(text, request_type="prompt_build")
if embedding:
embeddings[text] = embedding
else:
logger.warning(f"获取'{text}'的嵌入向量失败")
except Exception as e:
logger.error(f"获取'{text}'的嵌入向量时发生错误: {str(e)}")
logger.info(f"批量获取嵌入向量完成,耗时: {time.time() - embed_start_time:.3f}")
if not embeddings:
logger.error("所有嵌入向量获取失败")
return ""
# 3. 对每个主题进行知识库查询
all_results = []
query_start_time = time.time()
# 首先添加原始消息的查询结果
if message in embeddings:
original_results = self.get_info_from_db(embeddings[message], limit=3, threshold=threshold, return_raw=True)
if original_results:
for result in original_results:
result["topic"] = "原始消息"
all_results.extend(original_results)
logger.info(f"原始消息查询到{len(original_results)}条结果")
# 然后添加每个主题的查询结果
for topic in topics:
if not topic or topic not in embeddings:
continue
try:
topic_results = self.get_info_from_db(embeddings[topic], limit=3, threshold=threshold, return_raw=True)
if topic_results:
# 添加主题标记
for result in topic_results:
result["topic"] = topic
all_results.extend(topic_results)
logger.info(f"主题'{topic}'查询到{len(topic_results)}条结果")
except Exception as e:
logger.error(f"查询主题'{topic}'时发生错误: {str(e)}")
logger.info(f"知识库查询完成,耗时: {time.time() - query_start_time:.3f}秒,共获取{len(all_results)}条结果")
# 4. 去重和过滤
process_start_time = time.time()
unique_contents = set()
filtered_results = []
for result in all_results:
content = result["content"]
if content not in unique_contents:
unique_contents.add(content)
filtered_results.append(result)
# 5. 按相似度排序
filtered_results.sort(key=lambda x: x["similarity"], reverse=True)
# 6. 限制总数量最多10条
filtered_results = filtered_results[:10]
logger.info(
f"结果处理完成,耗时: {time.time() - process_start_time:.3f}秒,过滤后剩余{len(filtered_results)}条结果"
)
# 7. 格式化输出
if filtered_results:
format_start_time = time.time()
grouped_results = {}
for result in filtered_results:
topic = result["topic"]
if topic not in grouped_results:
grouped_results[topic] = []
grouped_results[topic].append(result)
# 按主题组织输出
for topic, results in grouped_results.items():
related_info += f"【主题: {topic}\n"
for _i, result in enumerate(results, 1):
_similarity = result["similarity"]
content = result["content"].strip()
# 调试:为内容添加序号和相似度信息
# related_info += f"{i}. [{similarity:.2f}] {content}\n"
related_info += f"{content}\n"
related_info += "\n"
logger.info(f"格式化输出完成,耗时: {time.time() - format_start_time:.3f}")
logger.info(f"知识库检索总耗时: {time.time() - start_time:.3f}")
return related_info
@staticmethod
def get_info_from_db(
query_embedding: list, limit: int = 1, threshold: float = 0.5, return_raw: bool = False
) -> Union[str, list]:
if not query_embedding:
return "" if not return_raw else []
# 使用余弦相似度计算
pipeline = [
{
"$addFields": {
"dotProduct": {
"$reduce": {
"input": {"$range": [0, {"$size": "$embedding"}]},
"initialValue": 0,
"in": {
"$add": [
"$$value",
{
"$multiply": [
{"$arrayElemAt": ["$embedding", "$$this"]},
{"$arrayElemAt": [query_embedding, "$$this"]},
]
},
]
},
}
},
"magnitude1": {
"$sqrt": {
"$reduce": {
"input": "$embedding",
"initialValue": 0,
"in": {"$add": ["$$value", {"$multiply": ["$$this", "$$this"]}]},
}
}
},
"magnitude2": {
"$sqrt": {
"$reduce": {
"input": query_embedding,
"initialValue": 0,
"in": {"$add": ["$$value", {"$multiply": ["$$this", "$$this"]}]},
}
}
},
}
},
{"$addFields": {"similarity": {"$divide": ["$dotProduct", {"$multiply": ["$magnitude1", "$magnitude2"]}]}}},
{
"$match": {
"similarity": {"$gte": threshold} # 只保留相似度大于等于阈值的结果
}
},
{"$sort": {"similarity": -1}},
{"$limit": limit},
{"$project": {"content": 1, "similarity": 1}},
]
results = list(db.knowledges.aggregate(pipeline))
logger.debug(f"知识库查询结果数量: {len(results)}")
if not results:
return "" if not return_raw else []
if return_raw:
return results
else:
# 返回所有找到的内容,用换行分隔
return "\n".join(str(result["content"]) for result in results)
init_prompt()
prompt_builder = PromptBuilder()

View File

@@ -157,17 +157,17 @@ class ReasoningChat:
# 消息加入缓冲池 # 消息加入缓冲池
await message_buffer.start_caching_messages(message) await message_buffer.start_caching_messages(message)
# logger.info("使用推理聊天模式")
# 创建聊天流 # 创建聊天流
chat = await chat_manager.get_or_create_stream( chat = await chat_manager.get_or_create_stream(
platform=messageinfo.platform, platform=messageinfo.platform,
user_info=userinfo, user_info=userinfo,
group_info=groupinfo, group_info=groupinfo,
) )
message.update_chat_stream(chat) message.update_chat_stream(chat)
await message.process() await message.process()
logger.trace(f"消息处理成功: {message.processed_plain_text}")
# 过滤词/正则表达式过滤 # 过滤词/正则表达式过滤
if self._check_ban_words(message.processed_plain_text, chat, userinfo) or self._check_ban_regex( if self._check_ban_words(message.processed_plain_text, chat, userinfo) or self._check_ban_regex(
@@ -175,27 +175,13 @@ class ReasoningChat:
): ):
return return
await self.storage.store_message(message, chat)
# 记忆激活
with Timer("记忆激活", timing_results):
interested_rate = await HippocampusManager.get_instance().get_activate_from_text(
message.processed_plain_text, fast_retrieval=True
)
# 查询缓冲器结果会整合前面跳过的消息改变processed_plain_text # 查询缓冲器结果会整合前面跳过的消息改变processed_plain_text
buffer_result = await message_buffer.query_buffer_result(message) buffer_result = await message_buffer.query_buffer_result(message)
# 处理提及
is_mentioned, reply_probability = is_mentioned_bot_in_message(message)
# 意愿管理器设置当前message信息
willing_manager.setup(message, chat, is_mentioned, interested_rate)
# 处理缓冲器结果 # 处理缓冲器结果
if not buffer_result: if not buffer_result:
await willing_manager.bombing_buffer_message_handle(message.message_info.message_id) # await willing_manager.bombing_buffer_message_handle(message.message_info.message_id)
willing_manager.delete(message.message_info.message_id) # willing_manager.delete(message.message_info.message_id)
f_type = "seglist" f_type = "seglist"
if message.message_segment.type != "seglist": if message.message_segment.type != "seglist":
f_type = message.message_segment.type f_type = message.message_segment.type
@@ -214,6 +200,27 @@ class ReasoningChat:
logger.info("触发缓冲,已炸飞消息列") logger.info("触发缓冲,已炸飞消息列")
return return
try:
await self.storage.store_message(message, chat)
logger.trace(f"存储成功 (通过缓冲后): {message.processed_plain_text}")
except Exception as e:
logger.error(f"存储消息失败: {e}")
logger.error(traceback.format_exc())
# 存储失败可能仍需考虑是否继续,暂时返回
return
is_mentioned, reply_probability = is_mentioned_bot_in_message(message)
# 记忆激活
with Timer("记忆激活", timing_results):
interested_rate = await HippocampusManager.get_instance().get_activate_from_text(
message.processed_plain_text, fast_retrieval=True
)
# 处理提及
# 意愿管理器设置当前message信息
willing_manager.setup(message, chat, is_mentioned, interested_rate)
# 获取回复概率 # 获取回复概率
is_willing = False is_willing = False
if reply_probability != 1: if reply_probability != 1:

View File

@@ -44,7 +44,7 @@ class ResponseGenerator:
async def generate_response(self, message: MessageThinking, thinking_id: str) -> Optional[Union[str, List[str]]]: async def generate_response(self, message: MessageThinking, thinking_id: str) -> Optional[Union[str, List[str]]]:
"""根据当前模型类型选择对应的生成函数""" """根据当前模型类型选择对应的生成函数"""
# 从global_config中获取模型概率值并选择模型 # 从global_config中获取模型概率值并选择模型
if random.random() < global_config.MODEL_R1_PROBABILITY: if random.random() < global_config.model_reasoning_probability:
self.current_model_type = "深深地" self.current_model_type = "深深地"
current_model = self.model_reasoning current_model = self.model_reasoning
else: else:

View File

@@ -1509,19 +1509,14 @@ class HippocampusManager:
return response return response
async def get_memory_from_topic( async def get_memory_from_topic(
self, self, valid_keywords: list[str], max_memory_num: int = 3, max_memory_length: int = 2, max_depth: int = 3
valid_keywords: list[str],
max_memory_num: int = 3,
max_memory_length: int = 2,
max_depth: int = 3,
fast_retrieval: bool = False,
) -> list: ) -> list:
"""从文本中获取相关记忆的公共接口""" """从文本中获取相关记忆的公共接口"""
if not self._initialized: if not self._initialized:
raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法") raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法")
try: try:
response = await self._hippocampus.get_memory_from_topic( response = await self._hippocampus.get_memory_from_topic(
valid_keywords, max_memory_num, max_memory_length, max_depth, fast_retrieval valid_keywords, max_memory_num, max_memory_length, max_depth
) )
except Exception as e: except Exception as e:
logger.error(f"文本激活记忆失败: {e}") logger.error(f"文本激活记忆失败: {e}")

View File

@@ -1,8 +1,7 @@
[inner] [inner]
version = "1.3.1" version = "1.4.0"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#以下是给开发人员阅读的,一般用户不需要阅读
#如果你想要修改配置文件请在修改后将version的值进行变更 #如果你想要修改配置文件请在修改后将version的值进行变更
#如果新增项目请在BotConfig类下新增相应的变量 #如果新增项目请在BotConfig类下新增相应的变量
#1.如果你修改的是[]层级项目,例如你新增了 [memory],那么请在config.py的 load_config函数中的include_configs字典中新增"内容":{ #1.如果你修改的是[]层级项目,例如你新增了 [memory],那么请在config.py的 load_config函数中的include_configs字典中新增"内容":{
@@ -19,11 +18,12 @@ version = "1.3.1"
# 次版本号:当你做了向下兼容的功能性新增, # 次版本号:当你做了向下兼容的功能性新增,
# 修订号:当你做了向下兼容的问题修正。 # 修订号:当你做了向下兼容的问题修正。
# 先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。 # 先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
#----以上是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
[bot] [bot]
qq = 114514 qq = 1145141919810
nickname = "麦麦" nickname = "麦麦"
alias_names = ["麦叠", "牢麦"] alias_names = ["麦叠", "牢麦"] #该选项还在调试中,暂时未生效
[groups] [groups]
talk_allowed = [ talk_allowed = [
@@ -41,23 +41,24 @@ personality_sides = [
"用一句话或几句话描述人格的一些细节", "用一句话或几句话描述人格的一些细节",
"用一句话或几句话描述人格的一些细节", "用一句话或几句话描述人格的一些细节",
"用一句话或几句话描述人格的一些细节", "用一句话或几句话描述人格的一些细节",
]# 条数任意 ]# 条数任意不能为0, 该选项还在调试中,可能未完全生效
[identity] #アイデンティティがない 生まれないらららら [identity] #アイデンティティがない 生まれないらららら
# 兴趣爱好 未完善,有些条目未使用 # 兴趣爱好 未完善,有些条目未使用
identity_detail = [ identity_detail = [
"身份特点", "身份特点",
"身份特点", "身份特点",
]# 条数任意 ]# 条数任意不能为0, 该选项还在调试中,可能未完全生效
#外貌特征 #外貌特征
height = 170 # 身高 单位厘米 height = 170 # 身高 单位厘米 该选项还在调试中,暂时未生效
weight = 50 # 体重 单位千克 weight = 50 # 体重 单位千克 该选项还在调试中,暂时未生效
age = 20 # 年龄 单位岁 age = 20 # 年龄 单位岁 该选项还在调试中,暂时未生效
gender = "男" # 性别 gender = "男" # 性别 该选项还在调试中,暂时未生效
appearance = "用几句话描述外貌特征" # 外貌特征 appearance = "用几句话描述外貌特征" # 外貌特征 该选项还在调试中,暂时未生效
[schedule] [schedule]
enable_schedule_gen = true # 是否启用日程表(尚未完成) enable_schedule_gen = true # 是否启用日程表
enable_schedule_interaction = true # 日程表是否影响回复模式
prompt_schedule_gen = "用几句话描述描述性格特点或行动规律,这个特征会用来生成日程表" prompt_schedule_gen = "用几句话描述描述性格特点或行动规律,这个特征会用来生成日程表"
schedule_doing_update_interval = 900 # 日程表更新间隔 单位秒 schedule_doing_update_interval = 900 # 日程表更新间隔 单位秒
schedule_temperature = 0.1 # 日程表温度建议0.1-0.5 schedule_temperature = 0.1 # 日程表温度建议0.1-0.5
@@ -67,19 +68,25 @@ time_zone = "Asia/Shanghai" # 给你的机器人设置时区,可以解决运
nonebot-qq="http://127.0.0.1:18002/api/message" nonebot-qq="http://127.0.0.1:18002/api/message"
[response] #群聊的回复策略 [response] #群聊的回复策略
#reasoning推理模式麦麦会根据上下文进行推理并给出回复 enable_heart_flowC = true
#heart_flow结合了PFC模式和心流模式麦麦会进行主动的观察和回复并给出回复 # 该功能还在完善中
response_mode = "heart_flow" # 回复策略可选值heart_flow心流reasoning推理) # 是否启用heart_flowC(心流聊天,HFC)模式
# 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间进行主动的观察和回复并给出回复比较消耗token
#推理回复参数 #一般回复参数
model_r1_probability = 0.7 # 麦麦回答时选择主要回复模型1 模型的概率 model_reasoning_probability = 0.7 # 麦麦回答时选择推理模型 模型的概率
model_v3_probability = 0.3 # 麦麦回答时选择次要回复模型2 模型的概率 model_normal_probability = 0.3 # 麦麦回答时选择一般模型 模型的概率
[heartflow] #启用启用heart_flowC(心流聊天)模式时生效,需要填写以下参数
reply_trigger_threshold = 3.0 # 心流聊天触发阈值,越低越容易进入心流聊天
probability_decay_factor_per_second = 0.2 # 概率衰减因子,越大衰减越快,越高越容易退出心流聊天
default_decay_rate_per_second = 0.98 # 默认衰减率,越大衰减越快,越高越难进入心流聊天
initial_duration = 60 # 初始持续时间,越大心流聊天持续的时间越长
[heartflow] # 注意可能会消耗大量token请谨慎开启仅会使用v3模型
sub_heart_flow_update_interval = 60 # 子心流更新频率,间隔 单位秒
sub_heart_flow_freeze_time = 100 # 子心流冻结时间,超过这个时间没有回复,子心流会冻结,间隔 单位秒
sub_heart_flow_stop_time = 500 # 子心流停止时间,超过这个时间没有回复,子心流会停止,间隔 单位秒 sub_heart_flow_stop_time = 500 # 子心流停止时间,超过这个时间没有回复,子心流会停止,间隔 单位秒
heart_flow_update_interval = 600 # 心流更新频率,间隔 单位秒 # sub_heart_flow_update_interval = 60
# sub_heart_flow_freeze_time = 100
# heart_flow_update_interval = 600
observation_context_size = 20 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩 observation_context_size = 20 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩
compressed_length = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5 compressed_length = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5
@@ -87,11 +94,13 @@ compress_length_limit = 5 #最多压缩份数,超过该数值的压缩上下
[message] [message]
max_context_size = 12 # 麦麦获得的上文数量建议12太短太长都会导致脑袋尖尖 max_context_size = 12 # 麦麦回复时获得的上文数量建议12太短太长都会导致脑袋尖尖
emoji_chance = 0.2 # 麦麦使用表情包的概率设置为1让麦麦自己决定发不发 emoji_chance = 0.2 # 麦麦一般回复时使用表情包的概率设置为1让麦麦自己决定发不发
thinking_timeout = 60 # 麦麦最长思考时间,超过这个时间的思考会放弃 thinking_timeout = 100 # 麦麦最长思考时间,超过这个时间的思考会放弃往往是api反应太慢
max_response_length = 256 # 麦麦回答的最大token数 max_response_length = 256 # 麦麦单次回答的最大token数
message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟 message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟
# 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息
ban_words = [ ban_words = [
# "403","张三" # "403","张三"
] ]
@@ -103,22 +112,23 @@ ban_msgs_regex = [
# "\\[CQ:at,qq=\\d+\\]" # 匹配@ # "\\[CQ:at,qq=\\d+\\]" # 匹配@
] ]
[willing] [willing] # 一般回复模式的回复意愿设置
willing_mode = "classical" # 回复意愿模式 —— 经典模式classical动态模式dynamicmxp模式mxp自定义模式custom需要你自己实现 willing_mode = "classical" # 回复意愿模式 —— 经典模式classical动态模式dynamicmxp模式mxp自定义模式custom需要你自己实现
response_willing_amplifier = 1 # 麦麦回复意愿放大系数一般为1 response_willing_amplifier = 1 # 麦麦回复意愿放大系数一般为1
response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数
down_frequency_rate = 3 # 降低回复频率的群组回复意愿降低系数 除法 down_frequency_rate = 3 # 降低回复频率的群组回复意愿降低系数 除法
emoji_response_penalty = 0.1 # 表情包回复惩罚系数设为0为不回复单个表情包减少单独回复表情包的概率 emoji_response_penalty = 0 # 表情包回复惩罚系数设为0为不回复单个表情包减少单独回复表情包的概率
mentioned_bot_inevitable_reply = false # 提及 bot 必然回复 mentioned_bot_inevitable_reply = false # 提及 bot 必然回复
at_bot_inevitable_reply = false # @bot 必然回复 at_bot_inevitable_reply = false # @bot 必然回复
[emoji] [emoji]
max_emoji_num = 120 # 表情包最大数量 max_emoji_num = 90 # 表情包最大数量
max_reach_deletion = true # 开启则在达到最大数量时删除表情包,关闭则达到最大数量时不删除,只是不会继续收集表情包 max_reach_deletion = true # 开启则在达到最大数量时删除表情包,关闭则达到最大数量时不删除,只是不会继续收集表情包
check_interval = 30 # 检查表情包(注册,破损,删除)的时间间隔(分钟) check_interval = 30 # 检查表情包(注册,破损,删除)的时间间隔(分钟)
auto_save = true # 是否保存表情包和图片 auto_save = true # 是否保存表情包和图片
enable_check = false # 是否启用表情包过滤
check_prompt = "符合公序良俗" # 表情包过滤要求 enable_check = false # 是否启用表情包过滤,只有符合该要求的表情包才会被保存
check_prompt = "符合公序良俗" # 表情包过滤要求,只有符合该要求的表情包才会被保存
[memory] [memory]
build_memory_interval = 2000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多 build_memory_interval = 2000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多
@@ -131,7 +141,8 @@ forget_memory_interval = 1000 # 记忆遗忘间隔 单位秒 间隔越低,
memory_forget_time = 24 #多长时间后的记忆会被遗忘 单位小时 memory_forget_time = 24 #多长时间后的记忆会被遗忘 单位小时
memory_forget_percentage = 0.01 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认 memory_forget_percentage = 0.01 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认
memory_ban_words = [ #不希望记忆的词 #不希望记忆的词,已经记忆的不会受到影响
memory_ban_words = [
# "403","张三" # "403","张三"
] ]
@@ -167,7 +178,7 @@ word_replace_rate=0.006 # 整词替换概率
[response_splitter] [response_splitter]
enable_response_splitter = true # 是否启用回复分割器 enable_response_splitter = true # 是否启用回复分割器
response_max_length = 100 # 回复允许的最大长度 response_max_length = 256 # 回复允许的最大长度
response_max_sentence_num = 4 # 回复允许的最大句子数 response_max_sentence_num = 4 # 回复允许的最大句子数
[remote] #发送统计信息,主要是看全球有多少只麦麦 [remote] #发送统计信息,主要是看全球有多少只麦麦