diff --git a/src/main.py b/src/main.py index fc0a757e5..e3bbf38d1 100644 --- a/src/main.py +++ b/src/main.py @@ -4,7 +4,7 @@ from .plugins.utils.statistic import LLMStatistics from .plugins.moods.moods import MoodManager from .plugins.schedule.schedule_generator import bot_schedule from .plugins.chat.emoji_manager import emoji_manager -from .plugins.relationship.relationship_manager import relationship_manager +from .plugins.person_info.person_info import person_info_manager from .plugins.willing.willing_manager import willing_manager from .plugins.chat.chat_stream import chat_manager from .heart_flow.heartflow import heartflow @@ -50,14 +50,14 @@ class MainSystem: # 初始化表情管理器 emoji_manager.initialize() + logger.success("表情包管理器初始化成功") # 启动情绪管理器 self.mood_manager.start_mood_update(update_interval=global_config.mood_update_interval) logger.success("情绪管理器启动成功") - # 加载用户关系 - await relationship_manager.load_all_relationships() - asyncio.create_task(relationship_manager._start_relationship_manager()) + # 检查并清除person_info冗余字段 + await person_info_manager.del_all_undefined_field() # 启动愿望管理器 await willing_manager.ensure_started() @@ -107,6 +107,7 @@ class MainSystem: self.print_mood_task(), self.remove_recalled_message_task(), emoji_manager.start_periodic_check(), + emoji_manager.start_periodic_register(), self.app.run(), ] await asyncio.gather(*tasks) diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py index 186245417..1bc844939 100644 --- a/src/plugins/__init__.py +++ b/src/plugins/__init__.py @@ -5,7 +5,7 @@ MaiMBot插件系统 from .chat.chat_stream import chat_manager from .chat.emoji_manager import emoji_manager -from .relationship.relationship_manager import relationship_manager +from .person_info.relationship_manager import relationship_manager from .moods.moods import MoodManager from .willing.willing_manager import willing_manager from .schedule.schedule_generator import bot_schedule diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index 0f4dada44..e5cef56a5 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -1,5 +1,5 @@ from .emoji_manager import emoji_manager -from ..relationship.relationship_manager import relationship_manager +from ..person_info.relationship_manager import relationship_manager from .chat_stream import chat_manager from .message_sender import message_manager from ..storage.storage import MessageStorage diff --git a/src/plugins/chat/emoji_manager.py b/src/plugins/chat/emoji_manager.py index 18a54b1ec..279dfb464 100644 --- a/src/plugins/chat/emoji_manager.py +++ b/src/plugins/chat/emoji_manager.py @@ -38,6 +38,8 @@ class EmojiManager: self.llm_emotion_judge = LLM_request( model=global_config.llm_emotion_judge, max_tokens=600, temperature=0.8, request_type="emoji" ) # 更高的温度,更少的token(后续可以根据情绪来调整温度) + + logger.info("启动表情包管理器") def _ensure_emoji_dir(self): """确保表情存储目录存在""" @@ -338,7 +340,7 @@ class EmojiManager: except Exception: logger.exception("[错误] 扫描表情包失败") - async def _periodic_scan(self): + async def start_periodic_register(self): """定期扫描新表情包""" while True: logger.info("[扫描] 开始扫描新表情包...") diff --git a/src/plugins/chat/message_sender.py b/src/plugins/chat/message_sender.py index a12f7320b..daba61552 100644 --- a/src/plugins/chat/message_sender.py +++ b/src/plugins/chat/message_sender.py @@ -67,6 +67,8 @@ class Message_Sender: try: end_point = global_config.api_urls.get(message.message_info.platform, None) if end_point: + # logger.info(f"发送消息到{end_point}") + # logger.info(message_json) await global_api.send_message(end_point, message_json) else: raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置,请检查配置文件") diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index c3c1e1fa8..c575eea88 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -149,7 +149,6 @@ def get_recent_group_speaker(chat_stream_id: int, sender, limit: int = 12) -> li db.messages.find( {"chat_id": chat_stream_id}, { - "chat_info": 1, "user_info": 1, }, ) @@ -160,20 +159,17 @@ def get_recent_group_speaker(chat_stream_id: int, sender, limit: int = 12) -> li if not recent_messages: return [] - who_chat_in_group = [] # ChatStream列表 - - duplicate_removal = [] + who_chat_in_group = [] for msg_db_data in recent_messages: user_info = UserInfo.from_dict(msg_db_data["user_info"]) if ( - (user_info.user_id, user_info.platform) != sender - and (user_info.user_id, user_info.platform) != (global_config.BOT_QQ, "qq") - and (user_info.user_id, user_info.platform) not in duplicate_removal - and len(duplicate_removal) < 5 - ): # 排除重复,排除消息发送者,排除bot(此处bot的平台强制为了qq,可能需要更改),限制加载的关系数目 - duplicate_removal.append((user_info.user_id, user_info.platform)) - chat_info = msg_db_data.get("chat_info", {}) - who_chat_in_group.append(ChatStream.from_dict(chat_info)) + (user_info.platform, user_info.user_id) != sender + and user_info.user_id != global_config.BOT_QQ + and (user_info.platform, user_info.user_id, user_info.user_nickname) not in who_chat_in_group + and len(who_chat_in_group) < 5 + ): # 排除重复,排除消息发送者,排除bot,限制加载的关系数目 + who_chat_in_group.append((user_info.platform, user_info.user_id, user_info.user_nickname)) + return who_chat_in_group @@ -349,6 +345,15 @@ def calculate_typing_time(input_string: str, chinese_time: float = 0.2, english_ - 如果只有一个中文字符,将使用3倍的中文输入时间 - 在所有输入结束后,额外加上回车时间0.3秒 """ + + # 如果输入是列表,将其连接成字符串 + if isinstance(input_string, list): + input_string = ''.join(input_string) + + # 确保现在是字符串类型 + if not isinstance(input_string, str): + input_string = str(input_string) + mood_manager = MoodManager.get_instance() # 将0-1的唤醒度映射到-1到1 mood_arousal = mood_manager.current_mood.arousal diff --git a/src/plugins/chat_module/reasoning_chat/reasoning_chat.py b/src/plugins/chat_module/reasoning_chat/reasoning_chat.py index be62d964c..0163a306e 100644 --- a/src/plugins/chat_module/reasoning_chat/reasoning_chat.py +++ b/src/plugins/chat_module/reasoning_chat/reasoning_chat.py @@ -16,6 +16,7 @@ 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 ...chat.chat_stream import chat_manager +from ...person_info.relationship_manager import relationship_manager # 定义日志配置 chat_config = LogConfig( @@ -123,6 +124,15 @@ class ReasoningChat: ) message_manager.add_message(bot_message) + async def _update_relationship(self, message, 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 process_message(self, message_data: str) -> None: """处理消息并生成回复""" timing_results = {} @@ -226,6 +236,12 @@ class ReasoningChat: timer2 = time.time() timing_results["处理表情包"] = timer2 - timer1 + # 更新关系情绪 + timer1 = time.time() + await self._update_relationship(message, response_set) + timer2 = time.time() + timing_results["更新关系情绪"] = timer2 - timer1 + # 输出性能计时结果 if do_reply: timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) diff --git a/src/plugins/chat_module/reasoning_chat/reasoning_prompt_builder.py b/src/plugins/chat_module/reasoning_chat/reasoning_prompt_builder.py index 508febec8..e3015fe1e 100644 --- a/src/plugins/chat_module/reasoning_chat/reasoning_prompt_builder.py +++ b/src/plugins/chat_module/reasoning_chat/reasoning_prompt_builder.py @@ -7,9 +7,10 @@ from ...memory_system.Hippocampus import HippocampusManager from ...moods.moods import MoodManager from ...schedule.schedule_generator import bot_schedule from ...config.config import global_config -from ...chat.utils import get_embedding, get_recent_group_detailed_plain_text +from ...chat.utils import get_embedding, get_recent_group_detailed_plain_text, get_recent_group_speaker from ...chat.chat_stream import chat_manager from src.common.logger import get_module_logger +from ...person_info.relationship_manager import relationship_manager logger = get_module_logger("prompt") @@ -25,6 +26,25 @@ class PromptBuilder: # 开始构建prompt + # 关系 + 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() @@ -127,7 +147,7 @@ class PromptBuilder: {schedule_prompt} {chat_target} {chat_talking_prompt} -现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n +现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。{relation_prompt_all}\n 你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)},{prompt_personality}。 你正在{chat_target_2},现在请你读读之前的聊天记录,{mood_prompt},然后给出日常且口语化的回复,平淡一些, 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger} diff --git a/src/plugins/chat_module/think_flow_chat/think_flow_chat.py b/src/plugins/chat_module/think_flow_chat/think_flow_chat.py index 7e5eef53b..87ea1575a 100644 --- a/src/plugins/chat_module/think_flow_chat/think_flow_chat.py +++ b/src/plugins/chat_module/think_flow_chat/think_flow_chat.py @@ -17,6 +17,7 @@ from ...message import UserInfo, Seg from src.heart_flow.heartflow import heartflow from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig from ...chat.chat_stream import chat_manager +from ...person_info.relationship_manager import relationship_manager # 定义日志配置 chat_config = LogConfig( @@ -101,9 +102,13 @@ class ThinkFlowChat: """处理表情包""" if random() < global_config.emoji_chance: emoji_raw = await emoji_manager.get_emoji_for_text(response) + # print("11111111111111") + # logger.info(emoji_raw) if emoji_raw: emoji_path, description = emoji_raw emoji_cq = image_path_to_base64(emoji_path) + + # logger.info(emoji_cq) thinking_time_point = round(message.message_info.time, 2) @@ -122,6 +127,8 @@ class ThinkFlowChat: is_head=False, is_emoji=True, ) + + # logger.info("22222222222222") message_manager.add_message(bot_message) async def _update_using_response(self, message, response_set): @@ -135,6 +142,15 @@ class ThinkFlowChat: await heartflow.get_subheartflow(stream_id).do_thinking_after_reply(response_set, chat_talking_prompt) + async def _update_relationship(self, message, 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 process_message(self, message_data: str) -> None: """处理消息并生成回复""" timing_results = {} @@ -180,8 +196,10 @@ class ThinkFlowChat: # 计算回复意愿 current_willing_old = willing_manager.get_willing(chat_stream=chat) - current_willing_new = (heartflow.get_subheartflow(chat.stream_id).current_state.willing - 5) / 4 - current_willing = (current_willing_old + current_willing_new) / 2 + # current_willing_new = (heartflow.get_subheartflow(chat.stream_id).current_state.willing - 5) / 4 + # current_willing = (current_willing_old + current_willing_new) / 2 + # 有点bug + current_willing = current_willing_old willing_manager.set_willing(chat.stream_id, current_willing) @@ -263,6 +281,12 @@ class ThinkFlowChat: timer2 = time.time() timing_results["更新心流"] = timer2 - timer1 + # 更新关系情绪 + timer1 = time.time() + await self._update_relationship(message, response_set) + timer2 = time.time() + timing_results["更新关系情绪"] = timer2 - timer1 + # 输出性能计时结果 if do_reply: timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) diff --git a/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py b/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py index cba03d234..3cd6096e7 100644 --- a/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py +++ b/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py @@ -6,9 +6,10 @@ from ...memory_system.Hippocampus import HippocampusManager from ...moods.moods import MoodManager from ...schedule.schedule_generator import bot_schedule from ...config.config import global_config -from ...chat.utils import get_recent_group_detailed_plain_text +from ...chat.utils import get_recent_group_detailed_plain_text, get_recent_group_speaker from ...chat.chat_stream import chat_manager from src.common.logger import get_module_logger +from ...person_info.relationship_manager import relationship_manager from src.heart_flow.heartflow import heartflow @@ -28,6 +29,25 @@ class PromptBuilder: # 开始构建prompt + # 关系 + 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() @@ -98,18 +118,19 @@ class PromptBuilder: logger.info("开始构建prompt") prompt = f""" + {relation_prompt_all}\n {chat_target} {chat_talking_prompt} 你刚刚脑子里在想: {current_mind_info} -现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n +现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。{relation_prompt_all}\n 你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)},{prompt_personality}。 你正在{chat_target_2},现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些, 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger} 请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景,尽量不要说你说过的话 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 {moderation_prompt}不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。""" - + return prompt def _build_initiative_prompt_select(self, group_id, probability_1=0.8, probability_2=0.1): diff --git a/src/plugins/moods/moods.py b/src/plugins/moods/moods.py index 8115ee1b9..98fd61952 100644 --- a/src/plugins/moods/moods.py +++ b/src/plugins/moods/moods.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from ..config.config import global_config from src.common.logger import get_module_logger, LogConfig, MOOD_STYLE_CONFIG +from ..person_info.relationship_manager import relationship_manager mood_config = LogConfig( # 使用海马体专用样式 @@ -55,15 +56,15 @@ class MoodManager: # 情绪词映射表 (valence, arousal) self.emotion_map = { - "开心": (0.8, 0.6), # 高愉悦度,中等唤醒度 - "愤怒": (-0.7, 0.7), # 负愉悦度,高唤醒度 - "悲伤": (-0.6, 0.3), # 负愉悦度,低唤醒度 - "惊讶": (0.2, 0.8), # 中等愉悦度,高唤醒度 - "害羞": (0.5, 0.2), # 中等愉悦度,低唤醒度 - "平静": (0.0, 0.5), # 中性愉悦度,中等唤醒度 - "恐惧": (-0.7, 0.6), # 负愉悦度,高唤醒度 - "厌恶": (-0.4, 0.4), # 负愉悦度,低唤醒度 - "困惑": (0.0, 0.6), # 中性愉悦度,高唤醒度 + "开心": (0.21, 0.6), + "害羞": (0.15, 0.2), + "愤怒": (-0.24, 0.8), + "恐惧": (-0.21, 0.7), + "悲伤": (-0.21, 0.3), + "厌恶": (-0.12, 0.4), + "惊讶": (0.06, 0.7), + "困惑": (0.0, 0.6), + "平静": (0.03, 0.5), } # 情绪文本映射表 @@ -93,7 +94,7 @@ class MoodManager: cls._instance = MoodManager() return cls._instance - def start_mood_update(self, update_interval: float = 1.0) -> None: + def start_mood_update(self, update_interval: float = 5.0) -> None: """ 启动情绪更新线程 :param update_interval: 更新间隔(秒) @@ -228,9 +229,15 @@ class MoodManager: :param intensity: 情绪强度(0.0-1.0) """ if emotion not in self.emotion_map: + logger.debug(f"[情绪更新] 未知情绪词: {emotion}") return valence_change, arousal_change = self.emotion_map[emotion] + old_valence = self.current_mood.valence + old_arousal = self.current_mood.arousal + old_mood = self.current_mood.text + + valence_change *= relationship_manager.gain_coefficient[relationship_manager.positive_feedback_value] # 应用情绪强度 valence_change *= intensity @@ -243,5 +250,8 @@ class MoodManager: # 限制范围 self.current_mood.valence = max(-1.0, min(1.0, self.current_mood.valence)) self.current_mood.arousal = max(0.0, min(1.0, self.current_mood.arousal)) - + self._update_mood_text() + + logger.info(f"[情绪变化] {emotion}(强度:{intensity:.2f}) | 愉悦度:{old_valence:.2f}->{self.current_mood.valence:.2f}, 唤醒度:{old_arousal:.2f}->{self.current_mood.arousal:.2f} | 心情:{old_mood}->{self.current_mood.text}") + diff --git a/src/plugins/person_info/person_info.py b/src/plugins/person_info/person_info.py new file mode 100644 index 000000000..f940c0fca --- /dev/null +++ b/src/plugins/person_info/person_info.py @@ -0,0 +1,213 @@ +from src.common.logger import get_module_logger +from ...common.database import db +import copy +import hashlib +from typing import Any, Callable, Dict, TypeVar +T = TypeVar('T') # 泛型类型 + +""" +PersonInfoManager 类方法功能摘要: +1. get_person_id - 根据平台和用户ID生成MD5哈希的唯一person_id +2. create_person_info - 创建新个人信息文档(自动合并默认值) +3. update_one_field - 更新单个字段值(若文档不存在则创建) +4. del_one_document - 删除指定person_id的文档 +5. get_value - 获取单个字段值(返回实际值或默认值) +6. get_values - 批量获取字段值(任一字段无效则返回空字典) +7. del_all_undefined_field - 清理全集合中未定义的字段 +8. get_specific_value_list - 根据指定条件,返回person_id,value字典 +""" + +logger = get_module_logger("person_info") + +person_info_default = { + "person_id" : None, + "platform" : None, + "user_id" : None, + "nickname" : None, + # "age" : 0, + "relationship_value" : 0, + # "saved" : True, + # "impression" : None, + # "gender" : Unkown, + "konw_time" : 0, +} # 个人信息的各项与默认值在此定义,以下处理会自动创建/补全每一项 + +class PersonInfoManager: + def __init__(self): + if "person_info" not in db.list_collection_names(): + db.create_collection("person_info") + db.person_info.create_index("person_id", unique=True) + + def get_person_id(self, platform:str, user_id:int): + """获取唯一id""" + components = [platform, str(user_id)] + key = "_".join(components) + return hashlib.md5(key.encode()).hexdigest() + + async def create_person_info(self, person_id:str, data:dict = None): + """创建一个项""" + if not person_id: + logger.debug("创建失败,personid不存在") + return + + _person_info_default = copy.deepcopy(person_info_default) + _person_info_default["person_id"] = person_id + + if data: + for key in _person_info_default: + if key != "person_id" and key in data: + _person_info_default[key] = data[key] + + db.person_info.insert_one(_person_info_default) + + async def update_one_field(self, person_id:str, field_name:str, value, Data:dict = None): + """更新某一个字段,会补全""" + if field_name not in person_info_default.keys(): + logger.debug(f"更新'{field_name}'失败,未定义的字段") + return + + document = db.person_info.find_one({"person_id": person_id}) + + if document: + db.person_info.update_one( + {"person_id": person_id}, + {"$set": {field_name: value}} + ) + else: + Data[field_name] = value + logger.debug(f"更新时{person_id}不存在,已新建") + await self.create_person_info(person_id, Data) + + async def del_one_document(self, person_id: str): + """删除指定 person_id 的文档""" + if not person_id: + logger.debug("删除失败:person_id 不能为空") + return + + result = db.person_info.delete_one({"person_id": person_id}) + if result.deleted_count > 0: + logger.debug(f"删除成功:person_id={person_id}") + else: + logger.debug(f"删除失败:未找到 person_id={person_id}") + + async def get_value(self, person_id: str, field_name: str): + """获取指定person_id文档的字段值,若不存在该字段,则返回该字段的全局默认值""" + if not person_id: + logger.debug("get_value获取失败:person_id不能为空") + return None + + if field_name not in person_info_default: + logger.debug(f"get_value获取失败:字段'{field_name}'未定义") + return None + + document = db.person_info.find_one( + {"person_id": person_id}, + {field_name: 1} + ) + + if document and field_name in document: + return document[field_name] + else: + logger.debug(f"获取{person_id}的{field_name}失败,已返回默认值{person_info_default[field_name]}") + return person_info_default[field_name] + + async def get_values(self, person_id: str, field_names: list) -> dict: + """获取指定person_id文档的多个字段值,若不存在该字段,则返回该字段的全局默认值""" + if not person_id: + logger.debug("get_values获取失败:person_id不能为空") + return {} + + # 检查所有字段是否有效 + for field in field_names: + if field not in person_info_default: + logger.debug(f"get_values获取失败:字段'{field}'未定义") + return {} + + # 构建查询投影(所有字段都有效才会执行到这里) + projection = {field: 1 for field in field_names} + + document = db.person_info.find_one( + {"person_id": person_id}, + projection + ) + + result = {} + for field in field_names: + result[field] = document.get(field, person_info_default[field]) if document else person_info_default[field] + + return result + + async def del_all_undefined_field(self): + """删除所有项里的未定义字段""" + # 获取所有已定义的字段名 + defined_fields = set(person_info_default.keys()) + + try: + # 遍历集合中的所有文档 + for document in db.person_info.find({}): + # 找出文档中未定义的字段 + undefined_fields = set(document.keys()) - defined_fields - {'_id'} + + if undefined_fields: + # 构建更新操作,使用$unset删除未定义字段 + update_result = db.person_info.update_one( + {'_id': document['_id']}, + {'$unset': {field: 1 for field in undefined_fields}} + ) + + if update_result.modified_count > 0: + logger.debug(f"已清理文档 {document['_id']} 的未定义字段: {undefined_fields}") + + return + + except Exception as e: + logger.error(f"清理未定义字段时出错: {e}") + return + + async def get_specific_value_list( + self, + field_name: str, + way: Callable[[Any], bool], # 接受任意类型值 +) ->Dict[str, Any]: + """ + 获取满足条件的字段值字典 + + Args: + field_name: 目标字段名 + way: 判断函数 (value: Any) -> bool + + Returns: + {person_id: value} | {} + + Example: + # 查找所有nickname包含"admin"的用户 + result = manager.specific_value_list( + "nickname", + lambda x: "admin" in x.lower() + ) + """ + if field_name not in person_info_default: + logger.error(f"字段检查失败:'{field_name}'未定义") + return {} + + try: + result = {} + for doc in db.person_info.find( + {field_name: {"$exists": True}}, + {"person_id": 1, field_name: 1, "_id": 0} + ): + try: + value = doc[field_name] + if way(value): + result[doc["person_id"]] = value + except (KeyError, TypeError, ValueError) as e: + logger.debug(f"记录{doc.get('person_id')}处理失败: {str(e)}") + continue + + return result + + except Exception as e: + logger.error(f"数据库查询失败: {str(e)}", exc_info=True) + return {} + +person_info_manager = PersonInfoManager() \ No newline at end of file diff --git a/src/plugins/person_info/relationship_manager.py b/src/plugins/person_info/relationship_manager.py new file mode 100644 index 000000000..707dbbe51 --- /dev/null +++ b/src/plugins/person_info/relationship_manager.py @@ -0,0 +1,195 @@ +from src.common.logger import get_module_logger, LogConfig, RELATION_STYLE_CONFIG +from ..chat.chat_stream import ChatStream +import math +from bson.decimal128 import Decimal128 +from .person_info import person_info_manager +import time + +relationship_config = LogConfig( + # 使用关系专用样式 + console_format=RELATION_STYLE_CONFIG["console_format"], + file_format=RELATION_STYLE_CONFIG["file_format"], +) +logger = get_module_logger("rel_manager", config=relationship_config) + +class RelationshipManager: + def __init__(self): + self.positive_feedback_value = 0 # 正反馈系统 + self.gain_coefficient = [1.0, 1.0, 1.1, 1.2, 1.4, 1.7, 1.9, 2.0] + self._mood_manager = None + + @property + def mood_manager(self): + if self._mood_manager is None: + from ..moods.moods import MoodManager # 延迟导入 + self._mood_manager = MoodManager.get_instance() + return self._mood_manager + + def positive_feedback_sys(self, label: str, stance: str): + """正反馈系统,通过正反馈系数增益情绪变化,根据情绪再影响关系变更""" + + positive_list = [ + "开心", + "惊讶", + "害羞", + ] + + negative_list = [ + "愤怒", + "悲伤", + "恐惧", + "厌恶", + ] + + if label in positive_list and stance != "反对": + if 7 > self.positive_feedback_value >= 0: + self.positive_feedback_value += 1 + elif self.positive_feedback_value < 0: + self.positive_feedback_value = 0 + elif label in negative_list and stance != "支持": + if -7 < self.positive_feedback_value <= 0: + self.positive_feedback_value -= 1 + elif self.positive_feedback_value > 0: + self.positive_feedback_value = 0 + + if abs(self.positive_feedback_value) > 1: + logger.info(f"触发mood变更增益,当前增益系数:{self.gain_coefficient[abs(self.positive_feedback_value)]}") + + def mood_feedback(self, value): + """情绪反馈""" + mood_manager = self.mood_manager + mood_gain = (mood_manager.get_current_mood().valence) ** 2 \ + * math.copysign(1, value * mood_manager.get_current_mood().valence) + value += value * mood_gain + logger.info(f"当前relationship增益系数:{mood_gain:.3f}") + return value + + + async def calculate_update_relationship_value(self, chat_stream: ChatStream, label: str, stance: str) -> None: + """计算并变更关系值 + 新的关系值变更计算方式: + 将关系值限定在-1000到1000 + 对于关系值的变更,期望: + 1.向两端逼近时会逐渐减缓 + 2.关系越差,改善越难,关系越好,恶化越容易 + 3.人维护关系的精力往往有限,所以当高关系值用户越多,对于中高关系值用户增长越慢 + 4.连续正面或负面情感会正反馈 + """ + stancedict = { + "支持": 0, + "中立": 1, + "反对": 2, + } + + valuedict = { + "开心": 1.5, + "愤怒": -2.0, + "悲伤": -0.5, + "惊讶": 0.6, + "害羞": 2.0, + "平静": 0.3, + "恐惧": -1.5, + "厌恶": -1.0, + "困惑": 0.5, + } + + person_id = person_info_manager.get_person_id(chat_stream.user_info.platform, chat_stream.user_info.user_id) + data = { + "platform" : chat_stream.user_info.platform, + "user_id" : chat_stream.user_info.user_id, + "nickname" : chat_stream.user_info.user_nickname, + "konw_time" : int(time.time()) + } + old_value = await person_info_manager.get_value(person_id, "relationship_value") + old_value = self.ensure_float(old_value, person_id) + + if old_value > 1000: + old_value = 1000 + elif old_value < -1000: + old_value = -1000 + + value = valuedict[label] + if old_value >= 0: + if valuedict[label] >= 0 and stancedict[stance] != 2: + value = value * math.cos(math.pi * old_value / 2000) + if old_value > 500: + rdict = await person_info_manager.get_specific_value_list("relationship_value", lambda x: x > 700) + high_value_count = len(rdict) + if old_value > 700: + value *= 3 / (high_value_count + 2) # 排除自己 + else: + value *= 3 / (high_value_count + 3) + elif valuedict[label] < 0 and stancedict[stance] != 0: + value = value * math.exp(old_value / 2000) + else: + value = 0 + elif old_value < 0: + if valuedict[label] >= 0 and stancedict[stance] != 2: + value = value * math.exp(old_value / 2000) + elif valuedict[label] < 0 and stancedict[stance] != 0: + value = value * math.cos(math.pi * old_value / 2000) + else: + value = 0 + + self.positive_feedback_sys(label, stance) + value = self.mood_feedback(value) + + level_num = self.calculate_level_num(old_value + value) + relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"] + logger.info( + f"当前关系: {relationship_level[level_num]}, " + f"关系值: {old_value:.2f}, " + f"当前立场情感: {stance}-{label}, " + f"变更: {value:+.5f}" + ) + + await person_info_manager.update_one_field(person_id, "relationship_value", old_value + value, data) + + async def build_relationship_info(self, person) -> str: + person_id = person_info_manager.get_person_id(person[0], person[1]) + relationship_value = await person_info_manager.get_value(person_id, "relationship_value") + level_num = self.calculate_level_num(relationship_value) + relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"] + relation_prompt2_list = [ + "厌恶回应", + "冷淡回复", + "保持理性", + "愿意回复", + "积极回复", + "无条件支持", + ] + + return ( + f"你对昵称为'({person[1]}){person[2]}'的用户的态度为{relationship_level[level_num]}," + f"回复态度为{relation_prompt2_list[level_num]},关系等级为{level_num}。" + ) + + def calculate_level_num(self, relationship_value) -> int: + """关系等级计算""" + if -1000 <= relationship_value < -227: + level_num = 0 + elif -227 <= relationship_value < -73: + level_num = 1 + elif -73 <= relationship_value < 227: + level_num = 2 + elif 227 <= relationship_value < 587: + level_num = 3 + elif 587 <= relationship_value < 900: + level_num = 4 + elif 900 <= relationship_value <= 1000: + level_num = 5 + else: + level_num = 5 if relationship_value > 1000 else 0 + return level_num + + def ensure_float(self, value, person_id): + """确保返回浮点数,转换失败返回0.0""" + if isinstance(value, float): + return value + try: + return float(value.to_decimal() if isinstance(value, Decimal128) else value) + except (ValueError, TypeError, AttributeError): + logger.warning(f"[关系管理] {person_id}值转换失败(原始值:{value}),已重置为0") + return 0.0 + +relationship_manager = RelationshipManager() diff --git a/src/plugins/relationship/relationship_manager.py b/src/plugins/relationship/relationship_manager.py deleted file mode 100644 index f8a850cab..000000000 --- a/src/plugins/relationship/relationship_manager.py +++ /dev/null @@ -1,383 +0,0 @@ -import asyncio -from typing import Optional -from src.common.logger import get_module_logger, LogConfig, RELATION_STYLE_CONFIG - -from ...common.database import db -from ..message.message_base import UserInfo -from ..chat.chat_stream import ChatStream -import math -from bson.decimal128 import Decimal128 - -relationship_config = LogConfig( - # 使用关系专用样式 - console_format=RELATION_STYLE_CONFIG["console_format"], - file_format=RELATION_STYLE_CONFIG["file_format"], -) -logger = get_module_logger("rel_manager", config=relationship_config) - - -class Impression: - traits: str = None - called: str = None - know_time: float = None - - relationship_value: float = None - - -class Relationship: - user_id: int = None - platform: str = None - gender: str = None - age: int = None - nickname: str = None - relationship_value: float = None - saved = False - - def __init__(self, chat: ChatStream = None, data: dict = None): - self.user_id = chat.user_info.user_id if chat else data.get("user_id", 0) - self.platform = chat.platform if chat else data.get("platform", "") - self.nickname = chat.user_info.user_nickname if chat else data.get("nickname", "") - self.relationship_value = data.get("relationship_value", 0) if data else 0 - self.age = data.get("age", 0) if data else 0 - self.gender = data.get("gender", "") if data else "" - - -class RelationshipManager: - def __init__(self): - self.relationships: dict[tuple[int, str], Relationship] = {} # 修改为使用(user_id, platform)作为键 - - async def update_relationship(self, chat_stream: ChatStream, data: dict = None, **kwargs) -> Optional[Relationship]: - """更新或创建关系 - Args: - chat_stream: 聊天流对象 - data: 字典格式的数据(可选) - **kwargs: 其他参数 - Returns: - Relationship: 关系对象 - """ - # 确定user_id和platform - if chat_stream.user_info is not None: - user_id = chat_stream.user_info.user_id - platform = chat_stream.user_info.platform or "qq" - else: - platform = platform or "qq" - - if user_id is None: - raise ValueError("必须提供user_id或user_info") - - # 使用(user_id, platform)作为键 - key = (user_id, platform) - - # 检查是否在内存中已存在 - relationship = self.relationships.get(key) - if relationship: - # 如果存在,更新现有对象 - if isinstance(data, dict): - for k, value in data.items(): - if hasattr(relationship, k) and value is not None: - setattr(relationship, k, value) - else: - # 如果不存在,创建新对象 - if chat_stream.user_info is not None: - relationship = Relationship(chat=chat_stream, **kwargs) - else: - raise ValueError("必须提供user_id或user_info") - self.relationships[key] = relationship - - # 保存到数据库 - await self.storage_relationship(relationship) - relationship.saved = True - - return relationship - - async def update_relationship_value(self, chat_stream: ChatStream, **kwargs) -> Optional[Relationship]: - """更新关系值 - Args: - user_id: 用户ID(可选,如果提供user_info则不需要) - platform: 平台(可选,如果提供user_info则不需要) - user_info: 用户信息对象(可选) - **kwargs: 其他参数 - Returns: - Relationship: 关系对象 - """ - # 确定user_id和platform - user_info = chat_stream.user_info - if user_info is not None: - user_id = user_info.user_id - platform = user_info.platform or "qq" - else: - platform = platform or "qq" - - if user_id is None: - raise ValueError("必须提供user_id或user_info") - - # 使用(user_id, platform)作为键 - key = (user_id, platform) - - # 检查是否在内存中已存在 - relationship = self.relationships.get(key) - if relationship: - for k, value in kwargs.items(): - if k == "relationship_value": - # 检查relationship.relationship_value是否为double类型 - if not isinstance(relationship.relationship_value, float): - try: - # 处理 Decimal128 类型 - if isinstance(relationship.relationship_value, Decimal128): - relationship.relationship_value = float(relationship.relationship_value.to_decimal()) - else: - relationship.relationship_value = float(relationship.relationship_value) - logger.info( - f"[关系管理] 用户 {user_id}({platform}) 的关系值已转换为double类型: {relationship.relationship_value}" - ) # noqa: E501 - except (ValueError, TypeError): - # 如果不能解析/强转则将relationship.relationship_value设置为double类型的0 - relationship.relationship_value = 0.0 - logger.warning(f"[关系管理] 用户 {user_id}({platform}) 的无法转换为double类型,已设置为0") - relationship.relationship_value += value - await self.storage_relationship(relationship) - relationship.saved = True - return relationship - else: - # 如果不存在且提供了user_info,则创建新的关系 - if user_info is not None: - return await self.update_relationship(chat_stream=chat_stream, **kwargs) - logger.warning(f"[关系管理] 用户 {user_id}({platform}) 不存在,无法更新") - return None - - def get_relationship(self, chat_stream: ChatStream) -> Optional[Relationship]: - """获取用户关系对象 - Args: - user_id: 用户ID(可选,如果提供user_info则不需要) - platform: 平台(可选,如果提供user_info则不需要) - user_info: 用户信息对象(可选) - Returns: - Relationship: 关系对象 - """ - # 确定user_id和platform - user_info = chat_stream.user_info - platform = chat_stream.user_info.platform or "qq" - if user_info is not None: - user_id = user_info.user_id - platform = user_info.platform or "qq" - else: - platform = platform or "qq" - - if user_id is None: - raise ValueError("必须提供user_id或user_info") - - key = (user_id, platform) - if key in self.relationships: - return self.relationships[key] - else: - return None - - async def load_relationship(self, data: dict) -> Relationship: - """从数据库加载或创建新的关系对象""" - # 确保data中有platform字段,如果没有则默认为'qq' - if "platform" not in data: - data["platform"] = "qq" - - rela = Relationship(data=data) - rela.saved = True - key = (rela.user_id, rela.platform) - self.relationships[key] = rela - return rela - - async def load_all_relationships(self): - """加载所有关系对象""" - all_relationships = db.relationships.find({}) - for data in all_relationships: - await self.load_relationship(data) - - async def _start_relationship_manager(self): - """每5分钟自动保存一次关系数据""" - # 获取所有关系记录 - all_relationships = db.relationships.find({}) - # 依次加载每条记录 - for data in all_relationships: - await self.load_relationship(data) - logger.debug(f"[关系管理] 已加载 {len(self.relationships)} 条关系记录") - - while True: - logger.debug("正在自动保存关系") - await asyncio.sleep(300) # 等待300秒(5分钟) - await self._save_all_relationships() - - async def _save_all_relationships(self): - """将所有关系数据保存到数据库""" - # 保存所有关系数据 - for _, relationship in self.relationships.items(): - if not relationship.saved: - relationship.saved = True - await self.storage_relationship(relationship) - - async def storage_relationship(self, relationship: Relationship): - """将关系记录存储到数据库中""" - user_id = relationship.user_id - platform = relationship.platform - nickname = relationship.nickname - relationship_value = relationship.relationship_value - gender = relationship.gender - age = relationship.age - saved = relationship.saved - - db.relationships.update_one( - {"user_id": user_id, "platform": platform}, - { - "$set": { - "platform": platform, - "nickname": nickname, - "relationship_value": relationship_value, - "gender": gender, - "age": age, - "saved": saved, - } - }, - upsert=True, - ) - - def get_name(self, user_id: int = None, platform: str = None, user_info: UserInfo = None) -> str: - """获取用户昵称 - Args: - user_id: 用户ID(可选,如果提供user_info则不需要) - platform: 平台(可选,如果提供user_info则不需要) - user_info: 用户信息对象(可选) - Returns: - str: 用户昵称 - """ - # 确定user_id和platform - if user_info is not None: - user_id = user_info.user_id - platform = user_info.platform or "qq" - else: - platform = platform or "qq" - - if user_id is None: - raise ValueError("必须提供user_id或user_info") - - # 确保user_id是整数类型 - user_id = int(user_id) - key = (user_id, platform) - if key in self.relationships: - return self.relationships[key].nickname - elif user_info is not None: - return user_info.user_nickname or user_info.user_cardname or "某人" - else: - return "某人" - - async def calculate_update_relationship_value(self, chat_stream: ChatStream, label: str, stance: str) -> None: - """计算变更关系值 - 新的关系值变更计算方式: - 将关系值限定在-1000到1000 - 对于关系值的变更,期望: - 1.向两端逼近时会逐渐减缓 - 2.关系越差,改善越难,关系越好,恶化越容易 - 3.人维护关系的精力往往有限,所以当高关系值用户越多,对于中高关系值用户增长越慢 - """ - stancedict = { - "支持": 0, - "中立": 1, - "反对": 2, - } - - valuedict = { - "开心": 1.5, - "愤怒": -3.5, - "悲伤": -1.5, - "惊讶": 0.6, - "害羞": 2.0, - "平静": 0.3, - "恐惧": -2, - "厌恶": -2.5, - "困惑": 0.5, - } - if self.get_relationship(chat_stream): - old_value = self.get_relationship(chat_stream).relationship_value - else: - return - - if old_value > 1000: - old_value = 1000 - elif old_value < -1000: - old_value = -1000 - - value = valuedict[label] - if old_value >= 0: - if valuedict[label] >= 0 and stancedict[stance] != 2: - value = value * math.cos(math.pi * old_value / 2000) - if old_value > 500: - high_value_count = 0 - for _, relationship in self.relationships.items(): - if relationship.relationship_value >= 700: - high_value_count += 1 - if old_value >= 700: - value *= 3 / (high_value_count + 2) # 排除自己 - else: - value *= 3 / (high_value_count + 3) - elif valuedict[label] < 0 and stancedict[stance] != 0: - value = value * math.exp(old_value / 1000) - else: - value = 0 - elif old_value < 0: - if valuedict[label] >= 0 and stancedict[stance] != 2: - value = value * math.exp(old_value / 1000) - elif valuedict[label] < 0 and stancedict[stance] != 0: - value = value * math.cos(math.pi * old_value / 2000) - else: - value = 0 - - level_num = self.calculate_level_num(old_value + value) - relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"] - logger.info( - f"当前关系: {relationship_level[level_num]}, " - f"关系值: {old_value:.2f}, " - f"当前立场情感: {stance}-{label}, " - f"变更: {value:+.5f}" - ) - - await self.update_relationship_value(chat_stream=chat_stream, relationship_value=value) - - def build_relationship_info(self, person) -> str: - relationship_value = relationship_manager.get_relationship(person).relationship_value - level_num = self.calculate_level_num(relationship_value) - relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"] - relation_prompt2_list = [ - "冷漠回应", - "冷淡回复", - "保持理性", - "愿意回复", - "积极回复", - "无条件支持", - ] - if person.user_info.user_cardname: - return ( - f"你对昵称为'[({person.user_info.user_id}){person.user_info.user_nickname}]{person.user_info.user_cardname}'的用户的态度为{relationship_level[level_num]}," - f"回复态度为{relation_prompt2_list[level_num]},关系等级为{level_num}。" - ) - else: - return ( - f"你对昵称为'({person.user_info.user_id}){person.user_info.user_nickname}'的用户的态度为{relationship_level[level_num]}," - f"回复态度为{relation_prompt2_list[level_num]},关系等级为{level_num}。" - ) - - def calculate_level_num(self, relationship_value) -> int: - """关系等级计算""" - if -1000 <= relationship_value < -227: - level_num = 0 - elif -227 <= relationship_value < -73: - level_num = 1 - elif -73 <= relationship_value < 227: - level_num = 2 - elif 227 <= relationship_value < 587: - level_num = 3 - elif 587 <= relationship_value < 900: - level_num = 4 - elif 900 <= relationship_value <= 1000: - level_num = 5 - else: - level_num = 5 if relationship_value > 1000 else 0 - return level_num - - -relationship_manager = RelationshipManager()