diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 4b5b711b8..97a7efdf1 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -17,7 +17,7 @@ from src.chat.planner_actions.action_manager import ActionManager from src.chat.chat_loop.hfc_utils import CycleDetail from src.person_info.relationship_builder_manager import relationship_builder_manager from src.chat.express.expression_learner import expression_learner_manager -from src.person_info.person_info import get_person_info_manager +from src.person_info.person_info import Person from src.person_info.group_relationship_manager import get_group_relationship_manager from src.plugin_system.base.component_types import ChatMode, EventType from src.plugin_system.core import events_manager @@ -306,20 +306,14 @@ class HeartFChatting: with Timer("回复发送", cycle_timers): reply_text = await self._send_response(response_set, action_message) - - # 存储reply action信息 - person_info_manager = get_person_info_manager() # 获取 platform,如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值 platform = action_message.get("chat_info_platform") if platform is None: platform = getattr(self.chat_stream, "platform", "unknown") - person_id = person_info_manager.get_person_id( - platform, - action_message.get("user_id", ""), - ) - person_name = await person_info_manager.get_value(person_id, "person_name") + person = Person(platform = platform ,user_id = action_message.get("user_id", "")) + person_name = person.person_name action_prompt_display = f"你对{person_name}进行了回复:{reply_text}" await database_api.store_action_info( diff --git a/src/chat/express/expression_learner.py b/src/chat/express/expression_learner.py index a45305203..ad75b565f 100644 --- a/src/chat/express/expression_learner.py +++ b/src/chat/express/expression_learner.py @@ -281,7 +281,7 @@ class ExpressionLearner: logger.info(f"在 {group_name} 学习到表达风格:\n{learnt_expressions_str}") if not learnt_expressions: - logger.info(f"没有学习到表达风格") + logger.info("没有学习到表达风格") return [] # 按chat_id分组 diff --git a/src/chat/express/expression_selector.py b/src/chat/express/expression_selector.py index 97026712e..6fb74d1d6 100644 --- a/src/chat/express/expression_selector.py +++ b/src/chat/express/expression_selector.py @@ -3,7 +3,7 @@ import time import random import hashlib -from typing import List, Dict, Tuple, Optional, Any +from typing import List, Dict, Optional, Any from json_repair import repair_json from src.llm_models.utils_model import LLMRequest @@ -22,16 +22,22 @@ def init_prompt(): 你的名字是{bot_name}{target_message} -你知道以下这些表达方式,梗和说话方式: +以下是可选的表达情境: {all_situations} -现在,请你根据聊天记录从中挑选合适的表达方式,梗和说话方式,组织一条回复风格指导,指导的目的是在组织回复的时候提供一些语言风格和梗上的参考。 -请在reply_style_guide中以平文本输出指导,不要浮夸,并在selected_expressions中说明在指导中你挑选了哪些表达方式,梗和说话方式,以json格式输出: -例子: +请你分析聊天内容的语境、情绪、话题类型,从上述情境中选择最适合当前聊天情境的,最多{max_num}个情境。 +考虑因素包括: +1. 聊天的情绪氛围(轻松、严肃、幽默等) +2. 话题类型(日常、技术、游戏、情感等) +3. 情境与当前语境的匹配度 +{target_message_extra_block} + +请以JSON格式输出,只需要输出选中的情境编号: +例如: {{ - "reply_style_guide": "...", - "selected_expressions": [2, 3, 4, 7] + "selected_situations": [2, 3, 5, 7, 19] }} + 请严格按照JSON格式输出,不要包含其他内容: """ Prompt(expression_evaluation_prompt, "expression_evaluation_prompt") @@ -190,14 +196,14 @@ class ExpressionSelector: chat_info: str, max_num: int = 10, target_message: Optional[str] = None, - ) -> Tuple[str, List[Dict[str, Any]]]: + ) -> List[Dict[str, Any]]: # sourcery skip: inline-variable, list-comprehension """使用LLM选择适合的表达方式""" # 检查是否允许在此聊天流中使用表达 if not self.can_use_expression_for_chat(chat_id): logger.debug(f"聊天流 {chat_id} 不允许使用表达,返回空列表") - return "", [] + return [] # 1. 获取20个随机表达方式(现在按权重抽取) style_exprs = self.get_random_expressions(chat_id, 10) @@ -216,7 +222,7 @@ class ExpressionSelector: if not all_expressions: logger.warning("没有找到可用的表达方式") - return "", [] + return [] all_situations_str = "\n".join(all_situations) @@ -255,24 +261,23 @@ class ExpressionSelector: if not content: logger.warning("LLM返回空结果") - return "", [] + return [] # 5. 解析结果 result = repair_json(content) if isinstance(result, str): result = json.loads(result) - if not isinstance(result, dict) or "reply_style_guide" not in result or "selected_expressions" not in result: + if not isinstance(result, dict) or "selected_situations" not in result: logger.error("LLM返回格式错误") logger.info(f"LLM返回结果: \n{content}") - return "", [] - - reply_style_guide = result["reply_style_guide"] - selected_expressions = result["selected_expressions"] + return [] + + selected_indices = result["selected_situations"] # 根据索引获取完整的表达方式 valid_expressions = [] - for idx in selected_expressions: + for idx in selected_indices: if isinstance(idx, int) and 1 <= idx <= len(all_expressions): expression = all_expressions[idx - 1] # 索引从1开始 valid_expressions.append(expression) @@ -282,11 +287,11 @@ class ExpressionSelector: self.update_expressions_count_batch(valid_expressions, 0.006) # logger.info(f"LLM从{len(all_expressions)}个情境中选择了{len(valid_expressions)}个") - return reply_style_guide, valid_expressions + return valid_expressions except Exception as e: logger.error(f"LLM处理表达方式选择时出错: {e}") - return "", [] + return [] diff --git a/src/chat/express/expression_selector_old.py b/src/chat/express/expression_selector_new.py similarity index 90% rename from src/chat/express/expression_selector_old.py rename to src/chat/express/expression_selector_new.py index bf85d6cbd..97026712e 100644 --- a/src/chat/express/expression_selector_old.py +++ b/src/chat/express/expression_selector_new.py @@ -22,22 +22,16 @@ def init_prompt(): 你的名字是{bot_name}{target_message} -以下是可选的表达情境: +你知道以下这些表达方式,梗和说话方式: {all_situations} -请你分析聊天内容的语境、情绪、话题类型,从上述情境中选择最适合当前聊天情境的,最多{max_num}个情境。 -考虑因素包括: -1. 聊天的情绪氛围(轻松、严肃、幽默等) -2. 话题类型(日常、技术、游戏、情感等) -3. 情境与当前语境的匹配度 -{target_message_extra_block} - -请以JSON格式输出,只需要输出选中的情境编号: -例如: +现在,请你根据聊天记录从中挑选合适的表达方式,梗和说话方式,组织一条回复风格指导,指导的目的是在组织回复的时候提供一些语言风格和梗上的参考。 +请在reply_style_guide中以平文本输出指导,不要浮夸,并在selected_expressions中说明在指导中你挑选了哪些表达方式,梗和说话方式,以json格式输出: +例子: {{ - "selected_situations": [2, 3, 5, 7, 19] + "reply_style_guide": "...", + "selected_expressions": [2, 3, 4, 7] }} - 请严格按照JSON格式输出,不要包含其他内容: """ Prompt(expression_evaluation_prompt, "expression_evaluation_prompt") @@ -196,14 +190,14 @@ class ExpressionSelector: chat_info: str, max_num: int = 10, target_message: Optional[str] = None, - ) -> List[Dict[str, Any]]: + ) -> Tuple[str, List[Dict[str, Any]]]: # sourcery skip: inline-variable, list-comprehension """使用LLM选择适合的表达方式""" # 检查是否允许在此聊天流中使用表达 if not self.can_use_expression_for_chat(chat_id): logger.debug(f"聊天流 {chat_id} 不允许使用表达,返回空列表") - return [] + return "", [] # 1. 获取20个随机表达方式(现在按权重抽取) style_exprs = self.get_random_expressions(chat_id, 10) @@ -222,7 +216,7 @@ class ExpressionSelector: if not all_expressions: logger.warning("没有找到可用的表达方式") - return [] + return "", [] all_situations_str = "\n".join(all_situations) @@ -261,23 +255,24 @@ class ExpressionSelector: if not content: logger.warning("LLM返回空结果") - return [] + return "", [] # 5. 解析结果 result = repair_json(content) if isinstance(result, str): result = json.loads(result) - if not isinstance(result, dict) or "selected_situations" not in result: + if not isinstance(result, dict) or "reply_style_guide" not in result or "selected_expressions" not in result: logger.error("LLM返回格式错误") logger.info(f"LLM返回结果: \n{content}") - return [] - - selected_indices = result["selected_situations"] + return "", [] + + reply_style_guide = result["reply_style_guide"] + selected_expressions = result["selected_expressions"] # 根据索引获取完整的表达方式 valid_expressions = [] - for idx in selected_indices: + for idx in selected_expressions: if isinstance(idx, int) and 1 <= idx <= len(all_expressions): expression = all_expressions[idx - 1] # 索引从1开始 valid_expressions.append(expression) @@ -287,11 +282,11 @@ class ExpressionSelector: self.update_expressions_count_batch(valid_expressions, 0.006) # logger.info(f"LLM从{len(all_expressions)}个情境中选择了{len(valid_expressions)}个") - return valid_expressions + return reply_style_guide, valid_expressions except Exception as e: logger.error(f"LLM处理表达方式选择时出错: {e}") - return [] + return "", [] diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index e750cfec3..4fcbae01b 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -14,34 +14,14 @@ from src.chat.utils.utils import is_mentioned_bot_in_message from src.chat.utils.timer_calculator import Timer from src.chat.utils.chat_message_builder import replace_user_references_sync from src.common.logger import get_logger -from src.person_info.relationship_manager import get_relationship_manager from src.mood.mood_manager import mood_manager +from src.person_info.person_info import Person if TYPE_CHECKING: from src.chat.heart_flow.sub_heartflow import SubHeartflow logger = get_logger("chat") - -async def _process_relationship(message: MessageRecv) -> None: - """处理用户关系逻辑 - - Args: - message: 消息对象,包含用户信息 - """ - platform = message.message_info.platform - user_id = message.message_info.user_info.user_id # type: ignore - nickname = message.message_info.user_info.user_nickname # type: ignore - cardname = message.message_info.user_info.user_cardname or nickname # type: ignore - - relationship_manager = get_relationship_manager() - is_known = await relationship_manager.is_known_some_one(platform, user_id) - - if not is_known: - logger.info(f"首次认识用户: {nickname}") - await relationship_manager.first_knowing_some_one(platform, user_id, nickname, cardname) # type: ignore - - async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool, list[str]]: """计算消息的兴趣度 @@ -165,7 +145,7 @@ class HeartFCMessageReceiver: # 4. 关系处理 if global_config.relationship.enable_relationship: - await _process_relationship(message) + person = Person(platform=message.message_info.platform, user_id=message.message_info.user_info.user_id,nickname=userinfo.user_nickname) except Exception as e: logger.error(f"消息处理失败: {e}") diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index f339b4b4c..b36ee8108 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -9,7 +9,6 @@ from datetime import datetime from src.mais4u.mai_think import mai_thinking_manager from src.common.logger import get_logger from src.config.config import global_config, model_config -from src.config.api_ada_configs import TaskConfig from src.individuality.individuality import get_individuality from src.llm_models.utils_model import LLMRequest from src.chat.message_receive.message import UserInfo, Seg, MessageRecv, MessageSending @@ -27,8 +26,7 @@ from src.chat.express.expression_selector import expression_selector from src.chat.memory_system.memory_activator import MemoryActivator from src.chat.memory_system.instant_memory import InstantMemory from src.mood.mood_manager import mood_manager -from src.person_info.relationship_fetcher import relationship_fetcher_manager -from src.person_info.person_info import get_person_info_manager +from src.person_info.person_info import Person from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.apis import llm_api @@ -302,16 +300,14 @@ class DefaultReplyer: if not global_config.relationship.enable_relationship: return "" - relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id) - # 获取用户ID - person_info_manager = get_person_info_manager() - person_id = person_info_manager.get_person_id_by_person_name(sender) + person = Person(platform=self.chat_stream.platform, user_id=sender) + person_id = person.person_id if not person_id: logger.warning(f"未找到用户 {sender} 的ID,跳过信息提取") return f"你完全不认识{sender},不理解ta的相关信息。" - return await relationship_fetcher.build_relation_info(person_id, points_num=5) + return person.build_relationship(points_num=5) async def build_expression_habits(self, chat_history: str, target: str) -> Tuple[str, str]: """构建表达习惯块 @@ -330,7 +326,7 @@ class DefaultReplyer: style_habits = [] # 使用从处理器传来的选中表达方式 # LLM模式:调用LLM选择5-10个,然后随机选5个 - reply_style_guide, selected_expressions = await expression_selector.select_suitable_expressions_llm( + selected_expressions = await expression_selector.select_suitable_expressions_llm( self.chat_stream.stream_id, chat_history, max_num=8, target_message=target ) @@ -354,7 +350,7 @@ class DefaultReplyer: ) expression_habits_block += f"{style_habits_str}\n" - return (f"{expression_habits_title}\n{expression_habits_block}", reply_style_guide) + return f"{expression_habits_title}\n{expression_habits_block}" async def build_memory_block(self, chat_history: str, target: str) -> str: """构建记忆块 @@ -659,18 +655,16 @@ class DefaultReplyer: available_actions = {} chat_stream = self.chat_stream chat_id = chat_stream.stream_id - person_info_manager = get_person_info_manager() is_group_chat = bool(chat_stream.group_info) platform = chat_stream.platform user_id = reply_message.get("user_id","") if user_id: - person_id = person_info_manager.get_person_id(platform,user_id) - person_name = await person_info_manager.get_value(person_id, "person_name") + person = Person(platform=platform, user_id=user_id) + person_name = person.person_name or user_id sender = person_name target = reply_message.get('processed_plain_text') else: - person_id = "" person_name = "用户" sender = "用户" target = "消息" @@ -746,7 +740,7 @@ class DefaultReplyer: logger.warning(f"回复生成前信息获取耗时过长: {chinese_name} 耗时: {duration:.1f}s,请使用更快的模型") logger.info(f"在回复前的步骤耗时: {'; '.join(timing_logs)}") - (expression_habits_block, reply_style_guide) = results_dict["expression_habits"] + expression_habits_block = results_dict["expression_habits"] relation_info = results_dict["relation_info"] memory_block = results_dict["memory_block"] tool_info = results_dict["tool_info"] @@ -802,7 +796,7 @@ class DefaultReplyer: if global_config.bot.qq_account == user_id and platform == global_config.bot.platform: return await global_prompt_manager.format_prompt( "replyer_self_prompt", - expression_habits_block=reply_style_guide, + expression_habits_block=expression_habits_block, tool_info_block=tool_info, knowledge_prompt=prompt_info, memory_block=memory_block, @@ -822,7 +816,7 @@ class DefaultReplyer: else: return await global_prompt_manager.format_prompt( "replyer_prompt", - expression_habits_block=reply_style_guide, + expression_habits_block=expression_habits_block, tool_info_block=tool_info, knowledge_prompt=prompt_info, memory_block=memory_block, @@ -885,7 +879,6 @@ class DefaultReplyer: self.build_relation_info(sender, target), ) - expression_habits_block, reply_style_guide = expression_habits_block keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target) diff --git a/src/chat/replyer/replyer_manager.py b/src/chat/replyer/replyer_manager.py index 2613e49a1..2f64ab07f 100644 --- a/src/chat/replyer/replyer_manager.py +++ b/src/chat/replyer/replyer_manager.py @@ -1,7 +1,6 @@ -from typing import Dict, Optional, List, Tuple +from typing import Dict, Optional from src.common.logger import get_logger -from src.config.api_ada_configs import TaskConfig from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager from src.chat.replyer.default_generator import DefaultReplyer diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 5a161f76f..04213a572 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -9,7 +9,7 @@ from src.config.config import global_config from src.common.message_repository import find_messages, count_messages from src.common.database.database_model import ActionRecords from src.common.database.database_model import Images -from src.person_info.person_info import PersonInfoManager, get_person_info_manager +from src.person_info.person_info import Person,get_person_id from src.chat.utils.utils import translate_timestamp_to_human_readable, assign_message_ids install(extra_lines=3) @@ -35,14 +35,12 @@ def replace_user_references_sync( str: 处理后的内容字符串 """ if name_resolver is None: - person_info_manager = get_person_info_manager() - def default_resolver(platform: str, user_id: str) -> str: # 检查是否是机器人自己 if replace_bot_name and user_id == global_config.bot.qq_account: return f"{global_config.bot.nickname}(你)" - person_id = PersonInfoManager.get_person_id(platform, user_id) - return person_info_manager.get_value_sync(person_id, "person_name") or user_id # type: ignore + person = Person(platform=platform, user_id=user_id) + return person.person_name or user_id # type: ignore name_resolver = default_resolver @@ -110,14 +108,12 @@ async def replace_user_references_async( str: 处理后的内容字符串 """ if name_resolver is None: - person_info_manager = get_person_info_manager() - async def default_resolver(platform: str, user_id: str) -> str: # 检查是否是机器人自己 if replace_bot_name and user_id == global_config.bot.qq_account: return f"{global_config.bot.nickname}(你)" - person_id = PersonInfoManager.get_person_id(platform, user_id) - return await person_info_manager.get_value(person_id, "person_name") or user_id # type: ignore + person = Person(platform=platform, user_id=user_id) + return person.person_name or user_id # type: ignore name_resolver = default_resolver @@ -506,14 +502,13 @@ def _build_readable_messages_internal( if not all([platform, user_id, timestamp is not None]): continue - person_id = PersonInfoManager.get_person_id(platform, user_id) - person_info_manager = get_person_info_manager() + person = Person(platform=platform, user_id=user_id) # 根据 replace_bot_name 参数决定是否替换机器人名称 person_name: str if replace_bot_name and user_id == global_config.bot.qq_account: person_name = f"{global_config.bot.nickname}(你)" else: - person_name = person_info_manager.get_value_sync(person_id, "person_name") # type: ignore + person_name = person.person_name or user_id # type: ignore # 如果 person_name 未设置,则使用消息中的 nickname 或默认名称 if not person_name: @@ -1009,7 +1004,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str: # print("SELF11111111111111") return "SELF" try: - person_id = PersonInfoManager.get_person_id(platform, user_id) + person_id = get_person_id(platform, user_id) except Exception as _e: person_id = None if not person_id: @@ -1102,7 +1097,7 @@ async def get_person_id_list(messages: List[Dict[str, Any]]) -> List[str]: if platform is None: platform = "unknown" - if person_id := PersonInfoManager.get_person_id(platform, user_id): + if person_id := get_person_id(platform, user_id): person_ids_set.add(person_id) return list(person_ids_set) # 将集合转换为列表返回 diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py index 0b9ec7798..9a91ca173 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -15,7 +15,7 @@ from src.config.config import global_config, model_config from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.chat_stream import get_chat_manager from src.llm_models.utils_model import LLMRequest -from src.person_info.person_info import PersonInfoManager, get_person_info_manager +from src.person_info.person_info import Person from .typo_generator import ChineseTypoGenerator logger = get_logger("chat_utils") @@ -639,12 +639,12 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]: # Try to fetch person info try: # Assume get_person_id is sync (as per original code), keep using to_thread - person_id = PersonInfoManager.get_person_id(platform, user_id) + person = Person(platform=platform, user_id=user_id) + person_id = person.person_id person_name = None if person_id: # get_value is async, so await it directly - person_info_manager = get_person_info_manager() - person_name = person_info_manager.get_value_sync(person_id, "person_name") + person_name = person.person_name target_info["person_id"] = person_id target_info["person_name"] = person_name diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index 3edb1509b..6055b7724 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -254,6 +254,7 @@ class PersonInfo(BaseModel): 用于存储个人信息数据的模型。 """ + is_known = BooleanField(default=False) # 是否已认识 person_id = TextField(unique=True, index=True) # 个人唯一ID person_name = TextField(null=True) # 个人名称 (允许为空) name_reason = TextField(null=True) # 名称设定的原因 @@ -261,15 +262,25 @@ class PersonInfo(BaseModel): user_id = TextField(index=True) # 用户ID nickname = TextField(null=True) # 用户昵称 points = TextField(null=True) # 个人印象的点 - attitude_to_me = TextField(null=True) # 对bot的态度 - rudeness = TextField(null=True) # 对bot的冒犯程度 - neuroticism = TextField(null=True) # 对bot的神经质程度 - conscientiousness = TextField(null=True) # 对bot的尽责程度 - likeness = TextField(null=True) # 对bot的相似程度 - know_times = FloatField(null=True) # 认识时间 (时间戳) know_since = FloatField(null=True) # 首次印象总结时间 last_know = FloatField(null=True) # 最后一次印象总结时间 + + + attitude_to_me = TextField(null=True) # 对bot的态度 + attitude_to_me_confidence = FloatField(null=True) # 对bot的态度置信度 + friendly_value = FloatField(null=True) # 对bot的友好程度 + friendly_value_confidence = FloatField(null=True) # 对bot的友好程度置信度 + rudeness = TextField(null=True) # 对bot的冒犯程度 + rudeness_confidence = FloatField(null=True) # 对bot的冒犯程度置信度 + neuroticism = TextField(null=True) # 对bot的神经质程度 + neuroticism_confidence = FloatField(null=True) # 对bot的神经质程度置信度 + conscientiousness = TextField(null=True) # 对bot的尽责程度 + conscientiousness_confidence = FloatField(null=True) # 对bot的尽责程度置信度 + likeness = TextField(null=True) # 对bot的相似程度 + likeness_confidence = FloatField(null=True) # 对bot的相似程度置信度 + + class Meta: # database = db # 继承自 BaseModel diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index c2655fba7..f63c88c59 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -6,7 +6,6 @@ import time from src.common.logger import get_logger from src.config.config import global_config, model_config from src.llm_models.utils_model import LLMRequest -from src.person_info.person_info import get_person_info_manager from rich.traceback import install install(extra_lines=3) @@ -19,7 +18,6 @@ class Individuality: def __init__(self): self.name = "" - self.bot_person_id = "" self.meta_info_file_path = "data/personality/meta.json" self.personality_data_file_path = "data/personality/personality_data.json" @@ -32,8 +30,6 @@ class Individuality: personality_side = global_config.personality.personality_side identity = global_config.personality.identity - person_info_manager = get_person_info_manager() - self.bot_person_id = person_info_manager.get_person_id("system", "bot_id") self.name = bot_nickname # 检查配置变化,如果变化则清空 @@ -64,16 +60,6 @@ class Individuality: else: logger.error("人设构建失败") - # 如果任何一个发生变化,都需要清空数据库中的info_list(因为这影响整体人设) - if personality_changed or identity_changed: - logger.info("将清空数据库中原有的关键词缓存") - update_data = { - "platform": "system", - "user_id": "bot_id", - "person_name": self.name, - "nickname": self.name, - } - await person_info_manager.update_one_field(self.bot_person_id, "info_list", [], data=update_data) async def get_personality_block(self) -> str: bot_name = global_config.bot.nickname @@ -130,7 +116,6 @@ class Individuality: Returns: tuple: (personality_changed, identity_changed) """ - person_info_manager = get_person_info_manager() current_personality_hash, current_identity_hash = self._get_config_hash( bot_nickname, personality_core, personality_side, identity ) @@ -148,17 +133,6 @@ class Individuality: if identity_changed: logger.info("检测到身份配置发生变化") - # 如果任何一个发生变化,都需要清空info_list(因为这影响整体人设) - if personality_changed or identity_changed: - logger.info("将清空原有的关键词缓存") - update_data = { - "platform": "system", - "user_id": "bot_id", - "person_name": self.name, - "nickname": self.name, - } - await person_info_manager.update_one_field(self.bot_person_id, "info_list", [], data=update_data) - # 更新元信息文件 new_meta_info = { "personality_hash": current_personality_hash, diff --git a/src/mais4u/mais4u_chat/s4u_chat.py b/src/mais4u/mais4u_chat/s4u_chat.py index 78df5e98a..80452d6e2 100644 --- a/src/mais4u/mais4u_chat/s4u_chat.py +++ b/src/mais4u/mais4u_chat/s4u_chat.py @@ -16,7 +16,7 @@ import json from .s4u_mood_manager import mood_manager from src.person_info.relationship_builder_manager import relationship_builder_manager from src.mais4u.s4u_config import s4u_config -from src.person_info.person_info import PersonInfoManager +from src.person_info.person_info import get_person_id from .super_chat_manager import get_super_chat_manager from .yes_or_no import yes_or_no_head from src.mais4u.constant_s4u import ENABLE_S4U @@ -262,7 +262,7 @@ class S4UChat: """根据VIP状态和中断逻辑将消息放入相应队列。""" user_id = message.message_info.user_info.user_id platform = message.message_info.platform - person_id = PersonInfoManager.get_person_id(platform, user_id) + person_id = get_person_id(platform, user_id) try: is_gift = message.is_gift diff --git a/src/mais4u/mais4u_chat/s4u_prompt.py b/src/mais4u/mais4u_chat/s4u_prompt.py index f0a0ade2a..8fb3eb155 100644 --- a/src/mais4u/mais4u_chat/s4u_prompt.py +++ b/src/mais4u/mais4u_chat/s4u_prompt.py @@ -10,8 +10,7 @@ from datetime import datetime import asyncio from src.mais4u.s4u_config import s4u_config from src.chat.message_receive.message import MessageRecvS4U -from src.person_info.relationship_fetcher import relationship_fetcher_manager -from src.person_info.person_info import PersonInfoManager, get_person_info_manager +from src.person_info.person_info import Person, get_person_id from src.chat.message_receive.chat_stream import ChatStream from src.mais4u.mais4u_chat.super_chat_manager import get_super_chat_manager from src.mais4u.mais4u_chat.screen_manager import screen_manager @@ -103,7 +102,7 @@ class PromptBuilder: # 使用从处理器传来的选中表达方式 # LLM模式:调用LLM选择5-10个,然后随机选5个 - _,selected_expressions = await expression_selector.select_suitable_expressions_llm( + selected_expressions = await expression_selector.select_suitable_expressions_llm( chat_stream.stream_id, chat_history, max_num=12, target_message=target ) @@ -142,18 +141,16 @@ class PromptBuilder: relation_prompt = "" if global_config.relationship.enable_relationship and who_chat_in_group: - relationship_fetcher = relationship_fetcher_manager.get_fetcher(chat_stream.stream_id) - # 将 (platform, user_id, nickname) 转换为 person_id person_ids = [] for person in who_chat_in_group: - person_id = PersonInfoManager.get_person_id(person[0], person[1]) + person_id = get_person_id(person[0], person[1]) person_ids.append(person_id) - # 使用 RelationshipFetcher 的 build_relation_info 方法,设置 points_num=3 保持与原来相同的行为 - relation_info_list = await asyncio.gather( - *[relationship_fetcher.build_relation_info(person_id, points_num=3) for person_id in person_ids] - ) + # 使用 Person 的 build_relationship 方法,设置 points_num=3 保持与原来相同的行为 + relation_info_list = [ + Person(person_id=person_id).build_relationship(points_num=3) for person_id in person_ids + ] if relation_info := "".join(relation_info_list): relation_prompt = await global_prompt_manager.format_prompt( "relation_prompt", relation_info=relation_info @@ -288,11 +285,8 @@ class PromptBuilder: chat_stream = message.chat_stream - person_id = PersonInfoManager.get_person_id( - message.chat_stream.user_info.platform, message.chat_stream.user_info.user_id - ) - person_info_manager = get_person_info_manager() - person_name = await person_info_manager.get_value(person_id, "person_name") + person = Person(platform=message.chat_stream.user_info.platform, user_id=message.chat_stream.user_info.user_id) + person_name = person.person_name if message.chat_stream.user_info.user_nickname: if person_name: diff --git a/src/mais4u/mais4u_chat/s4u_stream_generator.py b/src/mais4u/mais4u_chat/s4u_stream_generator.py index 04689f5e0..607470cd2 100644 --- a/src/mais4u/mais4u_chat/s4u_stream_generator.py +++ b/src/mais4u/mais4u_chat/s4u_stream_generator.py @@ -5,7 +5,6 @@ from src.config.config import model_config from src.chat.message_receive.message import MessageRecvS4U from src.mais4u.mais4u_chat.s4u_prompt import prompt_builder from src.common.logger import get_logger -import asyncio import re diff --git a/src/person_info/__init__.py b/src/person_info/__init__.py index 68d0e5519..e69de29bb 100644 --- a/src/person_info/__init__.py +++ b/src/person_info/__init__.py @@ -1,4 +0,0 @@ -from .person_info import get_person_info_manager -from .group_info import get_group_info_manager - -__all__ = ["get_person_info_manager", "get_group_info_manager"] diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index e3e92a05e..4cbbb0ffa 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -1,11 +1,11 @@ -import copy import hashlib -import datetime import asyncio import json +import time +import random from json_repair import repair_json -from typing import Any, Callable, Dict, Union, Optional +from typing import Union from src.common.logger import get_logger from src.common.database.database import db @@ -14,45 +14,276 @@ from src.llm_models.utils_model import LLMRequest from src.config.config import global_config, model_config -""" -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_logger("person_info") -JSON_SERIALIZED_FIELDS = ["points"] +def get_person_id(platform: str, user_id: Union[int, str]) -> str: + """获取唯一id""" + if "-" in platform: + platform = platform.split("-")[1] + components = [platform, str(user_id)] + key = "_".join(components) + return hashlib.md5(key.encode()).hexdigest() -person_info_default = { - "person_id": None, - "person_name": None, - "name_reason": None, # Corrected from person_name_reason to match common usage if intended - "platform": "unknown", - "user_id": "unknown", - "nickname": "Unknown", - "know_times": 0, - "know_since": None, - "last_know": None, - "attitude_to_me": "0,1", - "friendly_value": 50, - "rudeness":50, - "neuroticism":"5,1", - "conscientiousness": 50, - "likeness": 50, - "points": None, -} +def get_person_id_by_person_name(person_name: str) -> str: + """根据用户名获取用户ID""" + try: + record = PersonInfo.get_or_none(PersonInfo.person_name == person_name) + return record.person_id if record else "" + except Exception as e: + logger.error(f"根据用户名 {person_name} 获取用户ID时出错 (Peewee): {e}") + return "" + +class Person: + def __init__(self, platform: str = "", user_id: str = "",person_id: str = "",person_name: str = "",nickname: str = ""): + if person_id: + self.person_id = person_id + elif person_name: + self.person_id = get_person_id_by_person_name(person_name) + if not self.person_id: + logger.error(f"根据用户名 {person_name} 获取用户ID时出错,不存在用户{person_name}") + return "" + elif platform and user_id: + self.person_id = get_person_id(platform, user_id) + else: + logger.error("Person 初始化失败,缺少必要参数") + return "" + + self.is_known = False + self.platform = platform + self.user_id = user_id + + # 初始化默认值 + self.nickname = nickname + self.person_name = None + self.name_reason = None + self.know_times = 0 + self.know_since = None + self.last_know = None + self.points = [] + + # 初始化性格特征相关字段 + self.attitude_to_me:float = 0 + self.attitude_to_me_confidence:float = 1 + + self.neuroticism:float = 5 + self.neuroticism_confidence:float = 1 + + self.friendly_value:float = 50 + self.friendly_value_confidence:float = 1 + + self.rudeness:float = 50 + self.rudeness_confidence:float = 1 + + self.conscientiousness:float = 50 + self.conscientiousness_confidence:float = 1 + + self.likeness:float = 50 + self.likeness_confidence:float = 1 + + # 从数据库加载数据 + self.load_from_database() + + def load_from_database(self): + """从数据库加载个人信息数据""" + try: + # 查询数据库中的记录 + record = PersonInfo.get_or_none(PersonInfo.person_id == self.person_id) + + if record: + self.is_known = record.is_known if record.is_known else False + self.nickname = record.nickname if record.nickname else self.nickname + + if not self.is_known: + if self.nickname: + self.is_known = True + self.person_name = self.nickname + logger.info(f"用户 {self.person_id} 已认识,昵称:{self.nickname}") + else: + logger.warning(f"用户 {self.person_id} 尚未认识,昵称为空") + else: + self.person_name = record.person_name if record.person_name else self.nickname + self.name_reason = record.name_reason if record.name_reason else None + self.know_times = record.know_times if record.know_times else 0 + self.know_since = record.know_since if record.know_since else time.time() + self.last_know = record.last_know if record.last_know else time.time() + + # 处理points字段(JSON格式的列表) + if record.points: + try: + self.points = json.loads(record.points) + except (json.JSONDecodeError, TypeError): + logger.warning(f"解析用户 {self.person_id} 的points字段失败,使用默认值") + self.points = [] + else: + self.points = [] + + # 加载性格特征相关字段 + if record.attitude_to_me and not isinstance(record.attitude_to_me, str): + self.attitude_to_me = record.attitude_to_me + + if record.attitude_to_me_confidence is not None: + self.attitude_to_me_confidence = float(record.attitude_to_me_confidence) + + if record.friendly_value is not None: + self.friendly_value = float(record.friendly_value) + + if record.friendly_value_confidence is not None: + self.friendly_value_confidence = float(record.friendly_value_confidence) + + if record.rudeness is not None: + self.rudeness = float(record.rudeness) + + if record.rudeness_confidence is not None: + self.rudeness_confidence = float(record.rudeness_confidence) + + if record.neuroticism and not isinstance(record.neuroticism, str): + self.neuroticism = float(record.neuroticism) + + if record.neuroticism_confidence is not None: + self.neuroticism_confidence = float(record.neuroticism_confidence) + + if record.conscientiousness is not None: + self.conscientiousness = float(record.conscientiousness) + + if record.conscientiousness_confidence is not None: + self.conscientiousness_confidence = float(record.conscientiousness_confidence) + + if record.likeness is not None: + self.likeness = float(record.likeness) + + if record.likeness_confidence is not None: + self.likeness_confidence = float(record.likeness_confidence) + + logger.info(f"已从数据库加载用户 {self.person_id} 的信息") + else: + self.sync_to_database() + logger.info(f"用户 {self.person_id} 在数据库中不存在,使用默认值并创建") + + except Exception as e: + logger.error(f"从数据库加载用户 {self.person_id} 信息时出错: {e}") + # 出错时保持默认值 + + def sync_to_database(self): + """将所有属性同步回数据库""" + try: + # 准备数据 + data = { + 'person_id': self.person_id, + 'is_known': self.is_known, + 'platform': self.platform, + 'user_id': self.user_id, + 'nickname': self.nickname, + 'person_name': self.person_name, + 'name_reason': self.name_reason, + 'know_times': self.know_times, + 'know_since': self.know_since, + 'last_know': self.last_know, + 'points': json.dumps(self.points, ensure_ascii=False) if self.points else json.dumps([], ensure_ascii=False), + 'attitude_to_me': self.attitude_to_me, + 'attitude_to_me_confidence': self.attitude_to_me_confidence, + 'friendly_value': self.friendly_value, + 'friendly_value_confidence': self.friendly_value_confidence, + 'rudeness': self.rudeness, + 'rudeness_confidence': self.rudeness_confidence, + 'neuroticism': self.neuroticism, + 'neuroticism_confidence': self.neuroticism_confidence, + 'conscientiousness': self.conscientiousness, + 'conscientiousness_confidence': self.conscientiousness_confidence, + 'likeness': self.likeness, + 'likeness_confidence': self.likeness_confidence, + } + + # 检查记录是否存在 + record = PersonInfo.get_or_none(PersonInfo.person_id == self.person_id) + + if record: + # 更新现有记录 + for field, value in data.items(): + if hasattr(record, field): + setattr(record, field, value) + record.save() + logger.debug(f"已同步用户 {self.person_id} 的信息到数据库") + else: + # 创建新记录 + PersonInfo.create(**data) + logger.debug(f"已创建用户 {self.person_id} 的信息到数据库") + + except Exception as e: + logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}") + + def build_relationship(self,points_num=3): + if not self.is_known: + return "" + + # 按时间排序forgotten_points + current_points = self.points + current_points.sort(key=lambda x: x[2]) + # 按权重加权随机抽取最多3个不重复的points,point[1]的值在1-10之间,权重越高被抽到概率越大 + if len(current_points) > points_num: + # point[1] 取值范围1-10,直接作为权重 + weights = [max(1, min(10, int(point[1]))) for point in current_points] + # 使用加权采样不放回,保证不重复 + indices = list(range(len(current_points))) + points = [] + for _ in range(points_num): + if not indices: + break + sub_weights = [weights[i] for i in indices] + chosen_idx = random.choices(indices, weights=sub_weights, k=1)[0] + points.append(current_points[chosen_idx]) + indices.remove(chosen_idx) + else: + points = current_points + + # 构建points文本 + points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points]) + + nickname_str = "" + if self.person_name != nickname_str: + nickname_str = f"(ta在{self.platform}上的昵称是{nickname_str})" + + relation_info = "" + + attitude_info = "" + if self.attitude_to_me: + if self.attitude_to_me > 8: + attitude_info = f"{self.person_name}对你的态度十分好," + elif self.attitude_to_me > 5: + attitude_info = f"{self.person_name}对你的态度较好," + + + if self.attitude_to_me < -8: + attitude_info = f"{self.person_name}对你的态度十分恶劣," + elif self.attitude_to_me < -4: + attitude_info = f"{self.person_name}对你的态度不好," + elif self.attitude_to_me < 0: + attitude_info = f"{self.person_name}对你的态度一般," + + neuroticism_info = "" + if self.neuroticism: + if self.neuroticism > 8: + neuroticism_info = f"{self.person_name}的情绪十分活跃,容易情绪化," + elif self.neuroticism > 6: + neuroticism_info = f"{self.person_name}的情绪比较活跃," + elif self.neuroticism > 4: + neuroticism_info = "" + elif self.neuroticism > 2: + neuroticism_info = f"{self.person_name}的情绪比较稳定," + else: + neuroticism_info = f"{self.person_name}的情绪非常稳定,毫无波动" + + points_info = "" + if points_text: + points_info = f"你还记得ta最近做的事:{points_text}" + + relation_info = f"{self.person_name}:{nickname_str}{attitude_info}{neuroticism_info}{points_info}" + + return relation_info class PersonInfoManager: def __init__(self): + self.person_name_list = {} self.qv_name_llm = LLMRequest(model_set=model_config.model_task_config.utils, request_type="relation.qv_name") try: @@ -77,158 +308,11 @@ class PersonInfoManager: logger.debug(f"已加载 {len(self.person_name_list)} 个用户名称 (Peewee)") except Exception as e: logger.error(f"从 Peewee 加载 person_name_list 失败: {e}") + + def get_person(self, platform: str, user_id: Union[int, str]) -> Person: + person = Person(platform, user_id) + return person - @staticmethod - def get_person_id(platform: str, user_id: Union[int, str]) -> str: - """获取唯一id""" - # 添加空值检查,防止 platform 为 None 时出错 - if platform is None: - platform = "unknown" - elif "-" in platform: - platform = platform.split("-")[1] - - components = [platform, str(user_id)] - key = "_".join(components) - return hashlib.md5(key.encode()).hexdigest() - - async def is_person_known(self, platform: str, user_id: int): - """判断是否认识某人""" - person_id = self.get_person_id(platform, user_id) - - def _db_check_known_sync(p_id: str): - return PersonInfo.get_or_none(PersonInfo.person_id == p_id) is not None - - try: - return await asyncio.to_thread(_db_check_known_sync, person_id) - except Exception as e: - logger.error(f"检查用户 {person_id} 是否已知时出错 (Peewee): {e}") - return False - - def get_person_id_by_person_name(self, person_name: str) -> str: - """根据用户名获取用户ID""" - try: - record = PersonInfo.get_or_none(PersonInfo.person_name == person_name) - return record.person_id if record else "" - except Exception as e: - logger.error(f"根据用户名 {person_name} 获取用户ID时出错 (Peewee): {e}") - return "" - - async def _safe_create_person_info(self, person_id: str, data: Optional[dict] = None): - """安全地创建用户信息,处理竞态条件""" - if not person_id: - logger.debug("创建失败,person_id不存在") - return - - _person_info_default = copy.deepcopy(person_info_default) - model_fields = PersonInfo._meta.fields.keys() # type: ignore - - final_data = {"person_id": person_id} - - # Start with defaults for all model fields - for key, default_value in _person_info_default.items(): - if key in model_fields: - final_data[key] = default_value - - # Override with provided data - if data: - for key, value in data.items(): - if key in model_fields: - final_data[key] = value - - # Ensure person_id is correctly set from the argument - final_data["person_id"] = person_id - - # Serialize JSON fields - for key in JSON_SERIALIZED_FIELDS: - if key in final_data: - if isinstance(final_data[key], (list, dict)): - final_data[key] = json.dumps(final_data[key], ensure_ascii=False) - elif final_data[key] is None: # Default for lists is [], store as "[]" - final_data[key] = json.dumps([], ensure_ascii=False) - - def _db_safe_create_sync(p_data: dict): - try: - # 首先检查是否已存在 - existing = PersonInfo.get_or_none(PersonInfo.person_id == p_data["person_id"]) - if existing: - logger.debug(f"用户 {p_data['person_id']} 已存在,跳过创建") - return True - - # 尝试创建 - PersonInfo.create(**p_data) - return True - except Exception as e: - if "UNIQUE constraint failed" in str(e): - logger.debug(f"检测到并发创建用户 {p_data.get('person_id')},跳过错误") - return True # 其他协程已创建,视为成功 - else: - logger.error(f"创建 PersonInfo 记录 {p_data.get('person_id')} 失败 (Peewee): {e}") - return False - - await asyncio.to_thread(_db_safe_create_sync, final_data) - - async def update_one_field(self, person_id: str, field_name: str, value, data: Optional[Dict] = None): - """更新某一个字段,会补全""" - if field_name not in PersonInfo._meta.fields: # type: ignore - logger.debug(f"更新'{field_name}'失败,未在 PersonInfo Peewee 模型中定义的字段。") - return - - processed_value = value - if field_name in JSON_SERIALIZED_FIELDS: - if isinstance(value, (list, dict)): - processed_value = json.dumps(value, ensure_ascii=False, indent=None) - elif value is None: # Store None as "[]" for JSON list fields - processed_value = json.dumps([], ensure_ascii=False, indent=None) - - def _db_update_sync(p_id: str, f_name: str, val_to_set): - import time - - start_time = time.time() - try: - record = PersonInfo.get_or_none(PersonInfo.person_id == p_id) - query_time = time.time() - - if record: - setattr(record, f_name, val_to_set) - record.save() - save_time = time.time() - - total_time = save_time - start_time - if total_time > 0.5: # 如果超过500ms就记录日志 - logger.warning( - f"数据库更新操作耗时 {total_time:.3f}秒 (查询: {query_time - start_time:.3f}s, 保存: {save_time - query_time:.3f}s) person_id={p_id}, field={f_name}" - ) - - return True, False # Found and updated, no creation needed - else: - total_time = time.time() - start_time - if total_time > 0.5: - logger.warning(f"数据库查询操作耗时 {total_time:.3f}秒 person_id={p_id}, field={f_name}") - return False, True # Not found, needs creation - except Exception as e: - total_time = time.time() - start_time - logger.error(f"数据库操作异常,耗时 {total_time:.3f}秒: {e}") - raise - - found, needs_creation = await asyncio.to_thread(_db_update_sync, person_id, field_name, processed_value) - - if needs_creation: - logger.info(f"{person_id} 不存在,将新建。") - creation_data = data if data is not None else {} - # Ensure platform and user_id are present for context if available from 'data' - # but primarily, set the field that triggered the update. - # The create_person_info will handle defaults and serialization. - creation_data[field_name] = value # Pass original value to create_person_info - - # Ensure platform and user_id are in creation_data if available, - # otherwise create_person_info will use defaults. - if data and "platform" in data: - creation_data["platform"] = data["platform"] - if data and "user_id" in data: - creation_data["user_id"] = data["user_id"] - - # 使用安全的创建方法,处理竞态条件 - await self._safe_create_person_info(person_id, creation_data) @staticmethod def _extract_json_from_text(text: str) -> dict: @@ -279,8 +363,9 @@ class PersonInfoManager: logger.debug("取名失败:person_id不能为空") return None - old_name = await self.get_value(person_id, "person_name") - old_reason = await self.get_value(person_id, "name_reason") + person = Person(person_id=person_id) + old_name = person.person_name + old_reason = person.name_reason max_retries = 8 current_try = 0 @@ -338,8 +423,9 @@ class PersonInfoManager: current_name_set.add(generated_nickname) if not is_duplicate: - await self.update_one_field(person_id, "person_name", generated_nickname) - await self.update_one_field(person_id, "name_reason", result.get("reason", "未提供理由")) + person.person_name = generated_nickname + person.name_reason = result.get("reason", "未提供理由") + person.sync_to_database() logger.info( f"成功给用户{user_nickname} {person_id} 取名 {generated_nickname},理由:{result.get('reason', '未提供理由')}" @@ -357,186 +443,11 @@ class PersonInfoManager: # 如果多次尝试后仍未成功,使用唯一的 user_nickname 作为默认值 unique_nickname = await self._generate_unique_person_name(user_nickname) logger.warning(f"在{max_retries}次尝试后未能生成唯一昵称,使用默认昵称 {unique_nickname}") - await self.update_one_field(person_id, "person_name", unique_nickname) - await self.update_one_field(person_id, "name_reason", "使用用户原始昵称作为默认值") + person.person_name = unique_nickname + person.name_reason = "使用用户原始昵称作为默认值" + person.sync_to_database() self.person_name_list[person_id] = unique_nickname return {"nickname": unique_nickname, "reason": "使用用户原始昵称作为默认值"} - - - @staticmethod - async def get_value(person_id: str, field_name: str): - """获取指定用户指定字段的值""" - default_value_for_field = person_info_default.get(field_name) - if field_name in JSON_SERIALIZED_FIELDS and default_value_for_field is None: - default_value_for_field = [] # Ensure JSON fields default to [] if not in DB - - def _db_get_value_sync(p_id: str, f_name: str): - record = PersonInfo.get_or_none(PersonInfo.person_id == p_id) - if record: - val = getattr(record, f_name, None) - if f_name in JSON_SERIALIZED_FIELDS: - if isinstance(val, str): - try: - return json.loads(val) - except json.JSONDecodeError: - logger.warning(f"字段 {f_name} for {p_id} 包含无效JSON: {val}. 返回默认值.") - return [] # Default for JSON fields on error - elif val is None: # Field exists in DB but is None - return [] # Default for JSON fields - # If val is already a list/dict (e.g. if somehow set without serialization) - return val # Should ideally not happen if update_one_field is always used - return val - return None # Record not found - - try: - value_from_db = await asyncio.to_thread(_db_get_value_sync, person_id, field_name) - if value_from_db is not None: - return value_from_db - if field_name in person_info_default: - return default_value_for_field - logger.warning(f"字段 {field_name} 在 person_info_default 中未定义,且在数据库中未找到。") - return None # Ultimate fallback - except Exception as e: - logger.error(f"获取字段 {field_name} for {person_id} 时出错 (Peewee): {e}") - # Fallback to default in case of any error during DB access - return default_value_for_field if field_name in person_info_default else None - - @staticmethod - def get_value_sync(person_id: str, field_name: str): - """同步获取指定用户指定字段的值""" - default_value_for_field = person_info_default.get(field_name) - if field_name in JSON_SERIALIZED_FIELDS and default_value_for_field is None: - default_value_for_field = [] - - record = PersonInfo.get_or_none(PersonInfo.person_id == person_id) - if record: - val = getattr(record, field_name, None) - if field_name in JSON_SERIALIZED_FIELDS: - if isinstance(val, str): - try: - return json.loads(val) - except json.JSONDecodeError: - logger.warning(f"字段 {field_name} for {person_id} 包含无效JSON: {val}. 返回默认值.") - return [] - elif val is None: - return [] - return val - return val - - if field_name in person_info_default: - return default_value_for_field - logger.warning(f"字段 {field_name} 在 person_info_default 中未定义,且在数据库中未找到。") - return None - - @staticmethod - async def get_values(person_id: str, field_names: list) -> dict: - """获取指定person_id文档的多个字段值,若不存在该字段,则返回该字段的全局默认值""" - if not person_id: - logger.debug("get_values获取失败:person_id不能为空") - return {} - - result = {} - - def _db_get_record_sync(p_id: str): - return PersonInfo.get_or_none(PersonInfo.person_id == p_id) - - record = await asyncio.to_thread(_db_get_record_sync, person_id) - - for field_name in field_names: - if field_name not in PersonInfo._meta.fields: # type: ignore - if field_name in person_info_default: - result[field_name] = copy.deepcopy(person_info_default[field_name]) - logger.debug(f"字段'{field_name}'不在Peewee模型中,使用默认配置值。") - else: - logger.debug(f"get_values查询失败:字段'{field_name}'未在Peewee模型和默认配置中定义。") - result[field_name] = None - continue - - if record: - value = getattr(record, field_name) - if value is not None: - result[field_name] = value - else: - result[field_name] = copy.deepcopy(person_info_default.get(field_name)) - else: - result[field_name] = copy.deepcopy(person_info_default.get(field_name)) - - return result - - async def get_or_create_person( - self, platform: str, user_id: int, nickname: str, user_cardname: str, user_avatar: Optional[str] = None - ) -> str: - """ - 根据 platform 和 user_id 获取 person_id。 - 如果对应的用户不存在,则使用提供的可选信息创建新用户。 - 使用try-except处理竞态条件,避免重复创建错误。 - """ - person_id = self.get_person_id(platform, user_id) - - def _db_get_or_create_sync(p_id: str, init_data: dict): - """原子性的获取或创建操作""" - # 首先尝试获取现有记录 - record = PersonInfo.get_or_none(PersonInfo.person_id == p_id) - if record: - return record, False # 记录存在,未创建 - - # 记录不存在,尝试创建 - try: - PersonInfo.create(**init_data) - return PersonInfo.get(PersonInfo.person_id == p_id), True # 创建成功 - except Exception as e: - # 如果创建失败(可能是因为竞态条件),再次尝试获取 - if "UNIQUE constraint failed" in str(e): - logger.debug(f"检测到并发创建用户 {p_id},获取现有记录") - record = PersonInfo.get_or_none(PersonInfo.person_id == p_id) - if record: - return record, False # 其他协程已创建,返回现有记录 - # 如果仍然失败,重新抛出异常 - raise e - - unique_nickname = await self._generate_unique_person_name(nickname) - initial_data = { - "person_id": person_id, - "platform": platform, - "user_id": str(user_id), - "nickname": nickname, - "person_name": unique_nickname, # 使用群昵称作为person_name - "name_reason": "从群昵称获取", - "know_times": 0, - "know_since": int(datetime.datetime.now().timestamp()), - "last_know": int(datetime.datetime.now().timestamp()), - "impression": None, - "points": [], - "forgotten_points": [], - } - - # 序列化JSON字段 - for key in JSON_SERIALIZED_FIELDS: - if key in initial_data: - if isinstance(initial_data[key], (list, dict)): - initial_data[key] = json.dumps(initial_data[key], ensure_ascii=False) - elif initial_data[key] is None: - initial_data[key] = json.dumps([], ensure_ascii=False) - - model_fields = PersonInfo._meta.fields.keys() # type: ignore - filtered_initial_data = {k: v for k, v in initial_data.items() if v is not None and k in model_fields} - - record, was_created = await asyncio.to_thread(_db_get_or_create_sync, person_id, filtered_initial_data) - - if was_created: - logger.info(f"用户 {platform}:{user_id} (person_id: {person_id}) 不存在,将创建新记录 (Peewee)。") - logger.info(f"已为 {person_id} 创建新记录,初始数据 (filtered for model): {filtered_initial_data}") - else: - logger.debug(f"用户 {platform}:{user_id} (person_id: {person_id}) 已存在,返回现有记录。") - - return person_id - -person_info_manager = None - -def get_person_info_manager(): - global person_info_manager - if person_info_manager is None: - person_info_manager = PersonInfoManager() - return person_info_manager +person_info_manager = PersonInfoManager() diff --git a/src/person_info/relationship_builder.py b/src/person_info/relationship_builder.py index 5bf689910..fc9908b37 100644 --- a/src/person_info/relationship_builder.py +++ b/src/person_info/relationship_builder.py @@ -7,7 +7,7 @@ from typing import List, Dict, Any from src.config.config import global_config from src.common.logger import get_logger from src.person_info.relationship_manager import get_relationship_manager -from src.person_info.person_info import get_person_info_manager, PersonInfoManager +from src.person_info.person_info import Person,get_person_id from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.utils.chat_message_builder import ( get_raw_msg_by_timestamp_with_chat, @@ -15,6 +15,7 @@ from src.chat.utils.chat_message_builder import ( get_raw_msg_before_timestamp_with_chat, num_new_messages_since, ) +import asyncio logger = get_logger("relationship_builder") @@ -142,7 +143,8 @@ class RelationshipBuilder: } segments.append(new_segment) - person_name = get_person_info_manager().get_value_sync(person_id, "person_name") or person_id + person = Person(person_id=person_id) + person_name = person.person_name or person_id logger.debug( f"{self.log_prefix} 眼熟用户 {person_name} 在 {time.strftime('%H:%M:%S', time.localtime(potential_start_time))} - {time.strftime('%H:%M:%S', time.localtime(message_time))} 之间有 {new_segment['message_count']} 条消息" ) @@ -188,8 +190,8 @@ class RelationshipBuilder: "message_count": self._count_messages_in_timerange(potential_start_time, message_time), } segments.append(new_segment) - person_info_manager = get_person_info_manager() - person_name = person_info_manager.get_value_sync(person_id, "person_name") or person_id + person = Person(person_id=person_id) + person_name = person.person_name or person_id logger.debug( f"{self.log_prefix} 重新眼熟用户 {person_name} 创建新消息段(超过10条消息间隔): {new_segment}" ) @@ -375,7 +377,7 @@ class RelationshipBuilder: and user_id != global_config.bot.qq_account and msg_time > self.last_processed_message_time ): - person_id = PersonInfoManager.get_person_id(platform, user_id) + person_id = get_person_id(platform, user_id) self._update_message_segments(person_id, msg_time) logger.debug( f"{self.log_prefix} 更新用户 {person_id} 的消息段,消息时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(msg_time))}" @@ -386,7 +388,8 @@ class RelationshipBuilder: users_to_build_relationship = [] for person_id, segments in self.person_engaged_cache.items(): total_message_count = self._get_total_message_count(person_id) - person_name = get_person_info_manager().get_value_sync(person_id, "person_name") or person_id + person = Person(person_id=person_id) + person_name = person.person_name or person_id if total_message_count >= max_build_threshold or (total_message_count >= 5 and (immediate_build == person_id or immediate_build == "all")): users_to_build_relationship.append(person_id) @@ -403,9 +406,9 @@ class RelationshipBuilder: for person_id in users_to_build_relationship: segments = self.person_engaged_cache[person_id] # 异步执行关系构建 - import asyncio - - asyncio.create_task(self.update_impression_on_segments(person_id, self.chat_id, segments)) + person = Person(person_id=person_id) + if person.is_known: + asyncio.create_task(self.update_impression_on_segments(person_id, self.chat_id, segments)) # 移除已处理的用户缓存 del self.person_engaged_cache[person_id] self._save_cache() diff --git a/src/person_info/relationship_fetcher.py b/src/person_info/relationship_fetcher.py deleted file mode 100644 index 3db1e731e..000000000 --- a/src/person_info/relationship_fetcher.py +++ /dev/null @@ -1,468 +0,0 @@ -import time -import traceback -import json -import random - -from typing import List, Dict, Any -from json_repair import repair_json - -from src.common.logger import get_logger -from src.config.config import global_config, model_config -from src.llm_models.utils_model import LLMRequest -from src.chat.utils.prompt_builder import Prompt, global_prompt_manager -from src.chat.message_receive.chat_stream import get_chat_manager -from src.person_info.person_info import get_person_info_manager - - -logger = get_logger("relationship_fetcher") - - -def init_real_time_info_prompts(): - """初始化实时信息提取相关的提示词""" - relationship_prompt = """ -<聊天记录> -{chat_observe_info} - - -{name_block} -现在,你想要回复{person_name}的消息,消息内容是:{target_message}。请根据聊天记录和你要回复的消息,从你对{person_name}的了解中提取有关的信息: -1.你需要提供你想要提取的信息具体是哪方面的信息,例如:年龄,性别,你们之间的交流方式,最近发生的事等等。 -2.请注意,请不要重复调取相同的信息,已经调取的信息如下: -{info_cache_block} -3.如果当前聊天记录中没有需要查询的信息,或者现有信息已经足够回复,请返回{{"none": "不需要查询"}} - -请以json格式输出,例如: - -{{ - "info_type": "信息类型", -}} - -请严格按照json输出格式,不要输出多余内容: -""" - Prompt(relationship_prompt, "real_time_info_identify_prompt") - - fetch_info_prompt = """ - -{name_block} -以下是你在之前与{person_name}的交流中,产生的对{person_name}的了解: -{person_impression_block} -{points_text_block} - -请从中提取用户"{person_name}"的有关"{info_type}"信息 -请以json格式输出,例如: - -{{ - {info_json_str} -}} - -请严格按照json输出格式,不要输出多余内容: -""" - Prompt(fetch_info_prompt, "real_time_fetch_person_info_prompt") - - -class RelationshipFetcher: - def __init__(self, chat_id): - self.chat_id = chat_id - - # 信息获取缓存:记录正在获取的信息请求 - self.info_fetching_cache: List[Dict[str, Any]] = [] - - # 信息结果缓存:存储已获取的信息结果,带TTL - self.info_fetched_cache: Dict[str, Dict[str, Any]] = {} - # 结构:{person_id: {info_type: {"info": str, "ttl": int, "start_time": float, "person_name": str, "unknown": bool}}} - - # LLM模型配置 - self.llm_model = LLMRequest( - model_set=model_config.model_task_config.utils_small, request_type="relation.fetcher" - ) - - # 小模型用于即时信息提取 - self.instant_llm_model = LLMRequest( - model_set=model_config.model_task_config.utils_small, request_type="relation.fetch" - ) - - name = get_chat_manager().get_stream_name(self.chat_id) - self.log_prefix = f"[{name}] 实时信息" - - def _cleanup_expired_cache(self): - """清理过期的信息缓存""" - for person_id in list(self.info_fetched_cache.keys()): - for info_type in list(self.info_fetched_cache[person_id].keys()): - self.info_fetched_cache[person_id][info_type]["ttl"] -= 1 - if self.info_fetched_cache[person_id][info_type]["ttl"] <= 0: - del self.info_fetched_cache[person_id][info_type] - if not self.info_fetched_cache[person_id]: - del self.info_fetched_cache[person_id] - - async def build_relation_info(self, person_id, points_num=3): - # 清理过期的信息缓存 - self._cleanup_expired_cache() - - person_info_manager = get_person_info_manager() - person_name = await person_info_manager.get_value(person_id, "person_name") - attitude_to_me = await person_info_manager.get_value(person_id, "attitude_to_me") - neuroticism = await person_info_manager.get_value(person_id, "neuroticism") - conscientiousness = await person_info_manager.get_value(person_id, "conscientiousness") - likeness = await person_info_manager.get_value(person_id, "likeness") - - nickname_str = await person_info_manager.get_value(person_id, "nickname") - platform = await person_info_manager.get_value(person_id, "platform") - - current_points = await person_info_manager.get_value(person_id, "points") or [] - - # 按时间排序forgotten_points - current_points.sort(key=lambda x: x[2]) - # 按权重加权随机抽取最多3个不重复的points,point[1]的值在1-10之间,权重越高被抽到概率越大 - if len(current_points) > points_num: - # point[1] 取值范围1-10,直接作为权重 - weights = [max(1, min(10, int(point[1]))) for point in current_points] - # 使用加权采样不放回,保证不重复 - indices = list(range(len(current_points))) - points = [] - for _ in range(points_num): - if not indices: - break - sub_weights = [weights[i] for i in indices] - chosen_idx = random.choices(indices, weights=sub_weights, k=1)[0] - points.append(current_points[chosen_idx]) - indices.remove(chosen_idx) - else: - points = current_points - - # 构建points文本 - points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points]) - - nickname_str = "" - if person_name != nickname_str: - nickname_str = f"(ta在{platform}上的昵称是{nickname_str})" - - relation_info = "" - - attitude_info = "" - attitude_parts = attitude_to_me.split(',') - current_attitude_score = float(attitude_parts[0]) if len(attitude_parts) > 0 else 0.0 - total_confidence = float(attitude_parts[1]) if len(attitude_parts) > 1 else 1.0 - if attitude_to_me: - if current_attitude_score > 8: - attitude_info = f"{person_name}对你的态度十分好," - elif current_attitude_score > 5: - attitude_info = f"{person_name}对你的态度较好," - - - if current_attitude_score < -8: - attitude_info = f"{person_name}对你的态度十分恶劣," - elif current_attitude_score < -4: - attitude_info = f"{person_name}对你的态度不好," - elif current_attitude_score < 0: - attitude_info = f"{person_name}对你的态度一般," - - neuroticism_info = "" - neuroticism_parts = neuroticism.split(',') - current_neuroticism_score = float(neuroticism_parts[0]) if len(neuroticism_parts) > 0 else 0.0 - total_confidence = float(neuroticism_parts[1]) if len(neuroticism_parts) > 1 else 1.0 - if neuroticism: - if current_neuroticism_score > 8: - neuroticism_info = f"{person_name}的情绪十分活跃,容易情绪化," - elif current_neuroticism_score > 6: - neuroticism_info = f"{person_name}的情绪比较活跃," - elif current_neuroticism_score > 4: - neuroticism_info = "" - elif current_neuroticism_score > 2: - neuroticism_info = f"{person_name}的情绪比较稳定," - else: - neuroticism_info = f"{person_name}的情绪非常稳定,毫无波动" - - points_info = "" - if points_text: - points_info = f"你还记得ta最近做的事:{points_text}" - - - - relation_info = f"{person_name}:{nickname_str}{attitude_info}{neuroticism_info}{points_info}" - - - return relation_info - - async def _build_fetch_query(self, person_id, target_message, chat_history): - nickname_str = ",".join(global_config.bot.alias_names) - name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。" - person_info_manager = get_person_info_manager() - person_name: str = await person_info_manager.get_value(person_id, "person_name") # type: ignore - - info_cache_block = self._build_info_cache_block() - - prompt = (await global_prompt_manager.get_prompt_async("real_time_info_identify_prompt")).format( - chat_observe_info=chat_history, - name_block=name_block, - info_cache_block=info_cache_block, - person_name=person_name, - target_message=target_message, - ) - - try: - logger.debug(f"{self.log_prefix} 信息识别prompt: \n{prompt}\n") - content, _ = await self.llm_model.generate_response_async(prompt=prompt) - - if content: - content_json = json.loads(repair_json(content)) - - # 检查是否返回了不需要查询的标志 - if "none" in content_json: - logger.debug(f"{self.log_prefix} LLM判断当前不需要查询任何信息:{content_json.get('none', '')}") - return None - - if info_type := content_json.get("info_type"): - # 记录信息获取请求 - self.info_fetching_cache.append( - { - "person_id": get_person_info_manager().get_person_id_by_person_name(person_name), - "person_name": person_name, - "info_type": info_type, - "start_time": time.time(), - "forget": False, - } - ) - - # 限制缓存大小 - if len(self.info_fetching_cache) > 10: - self.info_fetching_cache.pop(0) - - logger.info(f"{self.log_prefix} 识别到需要调取用户 {person_name} 的[{info_type}]信息") - return info_type - else: - logger.warning(f"{self.log_prefix} LLM未返回有效的info_type。响应: {content}") - - except Exception as e: - logger.error(f"{self.log_prefix} 执行信息识别LLM请求时出错: {e}") - logger.error(traceback.format_exc()) - - return None - - def _build_info_cache_block(self) -> str: - """构建已获取信息的缓存块""" - info_cache_block = "" - if self.info_fetching_cache: - # 对于每个(person_id, info_type)组合,只保留最新的记录 - latest_records = {} - for info_fetching in self.info_fetching_cache: - key = (info_fetching["person_id"], info_fetching["info_type"]) - if key not in latest_records or info_fetching["start_time"] > latest_records[key]["start_time"]: - latest_records[key] = info_fetching - - # 按时间排序并生成显示文本 - sorted_records = sorted(latest_records.values(), key=lambda x: x["start_time"]) - for info_fetching in sorted_records: - info_cache_block += ( - f"你已经调取了[{info_fetching['person_name']}]的[{info_fetching['info_type']}]信息\n" - ) - return info_cache_block - - async def _extract_single_info(self, person_id: str, info_type: str, person_name: str): - """提取单个信息类型 - - Args: - person_id: 用户ID - info_type: 信息类型 - person_name: 用户名 - """ - start_time = time.time() - person_info_manager = get_person_info_manager() - - # 首先检查 info_list 缓存 - info_list = await person_info_manager.get_value(person_id, "info_list") or [] - cached_info = None - - # 查找对应的 info_type - for info_item in info_list: - if info_item.get("info_type") == info_type: - cached_info = info_item.get("info_content") - logger.debug(f"{self.log_prefix} 在info_list中找到 {person_name} 的 {info_type} 信息: {cached_info}") - break - - # 如果缓存中有信息,直接使用 - if cached_info: - if person_id not in self.info_fetched_cache: - self.info_fetched_cache[person_id] = {} - - self.info_fetched_cache[person_id][info_type] = { - "info": cached_info, - "ttl": 2, - "start_time": start_time, - "person_name": person_name, - "unknown": cached_info == "none", - } - logger.info(f"{self.log_prefix} 记得 {person_name} 的 {info_type}: {cached_info}") - return - - # 如果缓存中没有,尝试从用户档案中提取 - try: - person_impression = await person_info_manager.get_value(person_id, "impression") - points = await person_info_manager.get_value(person_id, "points") - - # 构建印象信息块 - if person_impression: - person_impression_block = ( - f"<对{person_name}的总体了解>\n{person_impression}\n" - ) - else: - person_impression_block = "" - - # 构建要点信息块 - if points: - points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points]) - points_text_block = f"<对{person_name}的近期了解>\n{points_text}\n" - else: - points_text_block = "" - - # 如果完全没有用户信息 - if not points_text_block and not person_impression_block: - if person_id not in self.info_fetched_cache: - self.info_fetched_cache[person_id] = {} - self.info_fetched_cache[person_id][info_type] = { - "info": "none", - "ttl": 2, - "start_time": start_time, - "person_name": person_name, - "unknown": True, - } - logger.info(f"{self.log_prefix} 完全不认识 {person_name}") - await self._save_info_to_cache(person_id, info_type, "none") - return - - # 使用LLM提取信息 - nickname_str = ",".join(global_config.bot.alias_names) - name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。" - - prompt = (await global_prompt_manager.get_prompt_async("real_time_fetch_person_info_prompt")).format( - name_block=name_block, - info_type=info_type, - person_impression_block=person_impression_block, - person_name=person_name, - info_json_str=f'"{info_type}": "有关{info_type}的信息内容"', - points_text_block=points_text_block, - ) - - # 使用小模型进行即时提取 - content, _ = await self.instant_llm_model.generate_response_async(prompt=prompt) - - if content: - content_json = json.loads(repair_json(content)) - if info_type in content_json: - info_content = content_json[info_type] - is_unknown = info_content == "none" or not info_content - - # 保存到运行时缓存 - if person_id not in self.info_fetched_cache: - self.info_fetched_cache[person_id] = {} - self.info_fetched_cache[person_id][info_type] = { - "info": "unknown" if is_unknown else info_content, - "ttl": 3, - "start_time": start_time, - "person_name": person_name, - "unknown": is_unknown, - } - - # 保存到持久化缓存 (info_list) - await self._save_info_to_cache(person_id, info_type, "none" if is_unknown else info_content) - - if not is_unknown: - logger.info(f"{self.log_prefix} 思考得到,{person_name} 的 {info_type}: {info_content}") - else: - logger.info(f"{self.log_prefix} 思考了也不知道{person_name} 的 {info_type} 信息") - else: - logger.warning(f"{self.log_prefix} 小模型返回空结果,获取 {person_name} 的 {info_type} 信息失败。") - - except Exception as e: - logger.error(f"{self.log_prefix} 执行信息提取时出错: {e}") - logger.error(traceback.format_exc()) - - async def _save_info_to_cache(self, person_id: str, info_type: str, info_content: str): - # sourcery skip: use-next - """将提取到的信息保存到 person_info 的 info_list 字段中 - - Args: - person_id: 用户ID - info_type: 信息类型 - info_content: 信息内容 - """ - try: - person_info_manager = get_person_info_manager() - - # 获取现有的 info_list - info_list = await person_info_manager.get_value(person_id, "info_list") or [] - - # 查找是否已存在相同 info_type 的记录 - found_index = -1 - for i, info_item in enumerate(info_list): - if isinstance(info_item, dict) and info_item.get("info_type") == info_type: - found_index = i - break - - # 创建新的信息记录 - new_info_item = { - "info_type": info_type, - "info_content": info_content, - } - - if found_index >= 0: - # 更新现有记录 - info_list[found_index] = new_info_item - logger.info(f"{self.log_prefix} [缓存更新] 更新 {person_id} 的 {info_type} 信息缓存") - else: - # 添加新记录 - info_list.append(new_info_item) - logger.info(f"{self.log_prefix} [缓存保存] 新增 {person_id} 的 {info_type} 信息缓存") - - # 保存更新后的 info_list - await person_info_manager.update_one_field(person_id, "info_list", info_list) - - except Exception as e: - logger.error(f"{self.log_prefix} [缓存保存] 保存信息到缓存失败: {e}") - logger.error(traceback.format_exc()) - - -class RelationshipFetcherManager: - """关系提取器管理器 - - 管理不同 chat_id 的 RelationshipFetcher 实例 - """ - - def __init__(self): - self._fetchers: Dict[str, RelationshipFetcher] = {} - - def get_fetcher(self, chat_id: str) -> RelationshipFetcher: - """获取或创建指定 chat_id 的 RelationshipFetcher - - Args: - chat_id: 聊天ID - - Returns: - RelationshipFetcher: 关系提取器实例 - """ - if chat_id not in self._fetchers: - self._fetchers[chat_id] = RelationshipFetcher(chat_id) - return self._fetchers[chat_id] - - def remove_fetcher(self, chat_id: str): - """移除指定 chat_id 的 RelationshipFetcher - - Args: - chat_id: 聊天ID - """ - if chat_id in self._fetchers: - del self._fetchers[chat_id] - - def clear_all(self): - """清空所有 RelationshipFetcher""" - self._fetchers.clear() - - def get_active_chat_ids(self) -> List[str]: - """获取所有活跃的 chat_id 列表""" - return list(self._fetchers.keys()) - - -# 全局管理器实例 -relationship_fetcher_manager = RelationshipFetcherManager() - - -init_real_time_info_prompts() diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index ae8e40592..0405e4d41 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -1,6 +1,5 @@ from src.common.logger import get_logger -from .person_info import PersonInfoManager, get_person_info_manager -import time +from .person_info import Person import random from src.llm_models.utils_model import LLMRequest from src.config.config import global_config, model_config @@ -8,11 +7,7 @@ from src.chat.utils.chat_message_builder import build_readable_messages import json from json_repair import repair_json from datetime import datetime -from difflib import SequenceMatcher -import jieba -from sklearn.feature_extraction.text import TfidfVectorizer -from sklearn.metrics.pairwise import cosine_similarity -from typing import List, Dict, Any, Tuple +from typing import List, Dict, Any from src.chat.utils.prompt_builder import Prompt, global_prompt_manager import traceback @@ -52,8 +47,7 @@ def init_prompt(): }} ] -如果没有,就输出none,或返回空数组: -[] +如果没有,就只输出空数组:[] """, "relation_points", ) @@ -83,7 +77,9 @@ def init_prompt(): "attitude": 0, "confidence": 0.5 }} -现在,请你输出json: +如果无法看出对方对你的态度,就只输出空数组:[] + +现在,请你输出: """, "attitude_to_me_prompt", ) @@ -115,7 +111,9 @@ def init_prompt(): "neuroticism": 0, "confidence": 0.5 }} -现在,请你输出json: +如果无法看出对方的神经质程度,就只输出空数组:[] + +现在,请你输出: """, "neuroticism_prompt", ) @@ -124,46 +122,15 @@ class RelationshipManager: def __init__(self): self.relationship_llm = LLMRequest( model_set=model_config.model_task_config.utils, request_type="relationship.person" - ) # 用于动作规划 - - @staticmethod - async def is_known_some_one(platform, user_id): - """判断是否认识某人""" - person_info_manager = get_person_info_manager() - return await person_info_manager.is_person_known(platform, user_id) - - @staticmethod - async def first_knowing_some_one(platform: str, user_id: str, user_nickname: str, user_cardname: str): - """判断是否认识某人""" - person_id = PersonInfoManager.get_person_id(platform, user_id) - # 生成唯一的 person_name - person_info_manager = get_person_info_manager() - unique_nickname = await person_info_manager._generate_unique_person_name(user_nickname) - data = { - "platform": platform, - "user_id": user_id, - "nickname": user_nickname, - "konw_time": int(time.time()), - "person_name": unique_nickname, # 使用唯一的 person_name - } - # 先创建用户基本信息,使用安全创建方法避免竞态条件 - await person_info_manager._safe_create_person_info(person_id=person_id, data=data) - # 更新昵称 - await person_info_manager.update_one_field( - person_id=person_id, field_name="nickname", value=user_nickname, data=data - ) - # 尝试生成更好的名字 - # await person_info_manager.qv_person_name( - # person_id=person_id, user_nickname=user_nickname, user_cardname=user_cardname, user_avatar=user_avatar - # ) + ) async def get_points(self, - person_name: str, - nickname: str, - readable_messages: str, - name_mapping: Dict[str, str], - timestamp: float, - current_points: List[Tuple[str, float, str]]): + person_name: str, + nickname: str, + readable_messages: str, + name_mapping: Dict[str, str], + timestamp: float, + person: Person): alias_str = ", ".join(global_config.bot.alias_names) current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") @@ -198,9 +165,7 @@ class RelationshipManager: points_data = json.loads(points) # 只处理正确的格式,错误格式直接跳过 - if points_data == "none" or not points_data: - points_list = [] - elif isinstance(points_data, str) and points_data.lower() == "none": + if points_data == "none" or not points_data or (isinstance(points_data, str) and points_data.lower() == "none") or (isinstance(points_data, list) and len(points_data) == 0): points_list = [] elif isinstance(points_data, list): points_list = [(item["point"], float(item["weight"]), current_time) for item in points_data] @@ -238,15 +203,15 @@ class RelationshipManager: return - current_points.extend(points_list) + person.points.extend(points_list) # 如果points超过10条,按权重随机选择多余的条目移动到forgotten_points - if len(current_points) > 20: + if len(person.points) > 20: # 计算当前时间 current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") # 计算每个点的最终权重(原始权重 * 时间权重) weighted_points = [] - for point in current_points: + for point in person.points: time_weight = self.calculate_time_weight(point[2], current_time) final_weight = point[1] * time_weight weighted_points.append((point, final_weight)) @@ -270,16 +235,15 @@ class RelationshipManager: idx_to_remove = random.randrange(len(remaining_points)) remaining_points[idx_to_remove] = point - return remaining_points - return current_points + person.points = remaining_points + return person - async def get_attitude_to_me(self, person_name, nickname, readable_messages, timestamp, current_attitude): + async def get_attitude_to_me(self, person_name, nickname, readable_messages, timestamp, person: Person): alias_str = ", ".join(global_config.bot.alias_names) current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") # 解析当前态度值 - attitude_parts = current_attitude.split(',') - current_attitude_score = float(attitude_parts[0]) if len(attitude_parts) > 0 else 0.0 - total_confidence = float(attitude_parts[1]) if len(attitude_parts) > 1 else 1.0 + current_attitude_score = person.attitude_to_me + total_confidence = person.attitude_to_me_confidence prompt = await global_prompt_manager.format_prompt( "attitude_to_me_prompt", @@ -301,23 +265,31 @@ class RelationshipManager: attitude = repair_json(attitude) attitude_data = json.loads(attitude) + if attitude_data == "none" or not attitude_data or (isinstance(attitude_data, str) and attitude_data.lower() == "none") or (isinstance(attitude_data, list) and len(attitude_data) == 0): + return "" + + # 确保 attitude_data 是字典格式 + if not isinstance(attitude_data, dict): + logger.warning(f"LLM返回了错误的JSON格式,跳过解析: {type(attitude_data)}, 内容: {attitude_data}") + return "" + attitude_score = attitude_data["attitude"] confidence = attitude_data["confidence"] new_confidence = total_confidence + confidence - new_attitude_score = (current_attitude_score * total_confidence + attitude_score * confidence)/new_confidence + person.attitude_to_me = new_attitude_score + person.attitude_to_me_confidence = new_confidence - return f"{new_attitude_score:.3f},{new_confidence:.3f}" + return person - async def get_neuroticism(self, person_name, nickname, readable_messages, timestamp, current_neuroticism): + async def get_neuroticism(self, person_name, nickname, readable_messages, timestamp, person: Person): alias_str = ", ".join(global_config.bot.alias_names) current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") # 解析当前态度值 - neuroticism_parts = current_neuroticism.split(',') - current_neuroticism_score = float(neuroticism_parts[0]) if len(neuroticism_parts) > 0 else 0.0 - total_confidence = float(neuroticism_parts[1]) if len(neuroticism_parts) > 1 else 1.0 + current_neuroticism_score = person.neuroticism + total_confidence = person.neuroticism_confidence prompt = await global_prompt_manager.format_prompt( "neuroticism_prompt", @@ -339,6 +311,14 @@ class RelationshipManager: neuroticism = repair_json(neuroticism) neuroticism_data = json.loads(neuroticism) + if neuroticism_data == "none" or not neuroticism_data or (isinstance(neuroticism_data, str) and neuroticism_data.lower() == "none") or (isinstance(neuroticism_data, list) and len(neuroticism_data) == 0): + return "" + + # 确保 neuroticism_data 是字典格式 + if not isinstance(neuroticism_data, dict): + logger.warning(f"LLM返回了错误的JSON格式,跳过解析: {type(neuroticism_data)}, 内容: {neuroticism_data}") + return "" + neuroticism_score = neuroticism_data["neuroticism"] confidence = neuroticism_data["confidence"] @@ -346,8 +326,10 @@ class RelationshipManager: new_neuroticism_score = (current_neuroticism_score * total_confidence + neuroticism_score * confidence)/new_confidence + person.neuroticism = new_neuroticism_score + person.neuroticism_confidence = new_confidence - return f"{new_neuroticism_score:.3f},{new_confidence:.3f}" + return person async def update_person_impression(self, person_id, timestamp, bot_engaged_messages: List[Dict[str, Any]]): @@ -360,21 +342,13 @@ class RelationshipManager: timestamp: 时间戳 (用于记录交互时间) bot_engaged_messages: bot参与的消息列表 """ - person_info_manager = get_person_info_manager() - person_name = await person_info_manager.get_value(person_id, "person_name") - nickname = await person_info_manager.get_value(person_id, "nickname") - know_times: float = await person_info_manager.get_value(person_id, "know_times") or 0 # type: ignore - current_points = await person_info_manager.get_value(person_id, "points") or [] - attitude_to_me = await person_info_manager.get_value(person_id, "attitude_to_me") or "0,1" - neuroticism = await person_info_manager.get_value(person_id, "neuroticism") or "5,1" - - # personality_block =get_individuality().get_personality_prompt(x_person=2, level=2) - # identity_block =get_individuality().get_identity_prompt(x_person=2, level=2) + person = Person(person_id=person_id) + person_name = person.person_name + nickname = person.nickname + know_times: float = person.know_times user_messages = bot_engaged_messages - current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") - # 匿名化消息 # 创建用户名称映射 name_mapping = {} @@ -383,33 +357,23 @@ class RelationshipManager: # 遍历消息,构建映射 for msg in user_messages: - await person_info_manager.get_or_create_person( - platform=msg.get("chat_info_platform"), # type: ignore - user_id=msg.get("user_id"), # type: ignore - nickname=msg.get("user_nickname"), # type: ignore - user_cardname=msg.get("user_cardname"), # type: ignore - ) - replace_user_id: str = msg.get("user_id") # type: ignore - replace_platform: str = msg.get("chat_info_platform") # type: ignore - replace_person_id = PersonInfoManager.get_person_id(replace_platform, replace_user_id) - replace_person_name = await person_info_manager.get_value(replace_person_id, "person_name") - + msg_person = Person(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")) # 跳过机器人自己 - if replace_user_id == global_config.bot.qq_account: + if msg_person.user_id == global_config.bot.qq_account: name_mapping[f"{global_config.bot.nickname}"] = f"{global_config.bot.nickname}" continue # 跳过目标用户 - if replace_person_name == person_name: - name_mapping[replace_person_name] = f"{person_name}" + if msg_person.person_name == person_name: + name_mapping[msg_person.person_name] = f"{person_name}" continue # 其他用户映射 - if replace_person_name not in name_mapping: + if msg_person.person_name not in name_mapping: if current_user > "Z": current_user = "A" user_count += 1 - name_mapping[replace_person_name] = f"用户{current_user}{user_count if user_count > 1 else ''}" + name_mapping[msg_person.person_name] = f"用户{current_user}{user_count if user_count > 1 else ''}" current_user = chr(ord(current_user) + 1) readable_messages = build_readable_messages( @@ -420,23 +384,16 @@ class RelationshipManager: # print(f"original_name: {original_name}, mapped_name: {mapped_name}") readable_messages = readable_messages.replace(f"{original_name}", f"{mapped_name}") - + print(name_mapping) - remaining_points = await self.get_points(person_name, nickname, readable_messages, name_mapping, timestamp, current_points) - attitude_to_me = await self.get_attitude_to_me(person_name, nickname, readable_messages, timestamp, attitude_to_me) - neuroticism = await self.get_neuroticism(person_name, nickname, readable_messages, timestamp, neuroticism) + person = await self.get_points(person_name, nickname, readable_messages, name_mapping, timestamp, person) + person = await self.get_attitude_to_me(person_name, nickname, readable_messages, timestamp, person) + person = await self.get_neuroticism(person_name, nickname, readable_messages, timestamp, person) - # 更新数据库 - await person_info_manager.update_one_field( - person_id, "points", json.dumps(remaining_points, ensure_ascii=False, indent=None) - ) - await person_info_manager.update_one_field(person_id, "neuroticism", neuroticism) - await person_info_manager.update_one_field(person_id, "attitude_to_me", attitude_to_me) - await person_info_manager.update_one_field(person_id, "know_times", know_times + 1) - await person_info_manager.update_one_field(person_id, "last_know", timestamp) - know_since = await person_info_manager.get_value(person_id, "know_since") or 0 - if know_since == 0: - await person_info_manager.update_one_field(person_id, "know_since", timestamp) + person.know_times = know_times + 1 + person.last_know = timestamp + + person.sync_to_database() diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index 2fc931a30..4e33595de 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -12,7 +12,6 @@ import traceback from typing import Tuple, Any, Dict, List, Optional from rich.traceback import install from src.common.logger import get_logger -from src.config.api_ada_configs import TaskConfig from src.chat.replyer.default_generator import DefaultReplyer from src.chat.message_receive.chat_stream import ChatStream from src.chat.utils.utils import process_llm_response diff --git a/src/plugin_system/apis/person_api.py b/src/plugin_system/apis/person_api.py index a84c5d2bb..c81e4747e 100644 --- a/src/plugin_system/apis/person_api.py +++ b/src/plugin_system/apis/person_api.py @@ -7,9 +7,9 @@ value = await person_api.get_person_value(person_id, "nickname") """ -from typing import Any, Optional +from typing import Any from src.common.logger import get_logger -from src.person_info.person_info import get_person_info_manager, PersonInfoManager +from src.person_info.person_info import Person logger = get_logger("person_api") @@ -33,7 +33,7 @@ def get_person_id(platform: str, user_id: int) -> str: person_id = person_api.get_person_id("qq", 123456) """ try: - return PersonInfoManager.get_person_id(platform, user_id) + return Person(platform=platform, user_id=user_id).person_id except Exception as e: logger.error(f"[PersonAPI] 获取person_id失败: platform={platform}, user_id={user_id}, error={e}") return "" @@ -55,85 +55,14 @@ async def get_person_value(person_id: str, field_name: str, default: Any = None) impression = await person_api.get_person_value(person_id, "impression") """ try: - person_info_manager = get_person_info_manager() - value = await person_info_manager.get_value(person_id, field_name) + person = Person(person_id=person_id) + value = getattr(person, field_name) return value if value is not None else default except Exception as e: logger.error(f"[PersonAPI] 获取用户信息失败: person_id={person_id}, field={field_name}, error={e}") return default -async def get_person_values(person_id: str, field_names: list, default_dict: Optional[dict] = None) -> dict: - """批量获取用户信息字段值 - - Args: - person_id: 用户的唯一标识ID - field_names: 要获取的字段名列表 - default_dict: 默认值字典,键为字段名,值为默认值 - - Returns: - dict: 字段名到值的映射字典 - - 示例: - values = await person_api.get_person_values( - person_id, - ["nickname", "impression", "know_times"], - {"nickname": "未知用户", "know_times": 0} - ) - """ - try: - person_info_manager = get_person_info_manager() - values = await person_info_manager.get_values(person_id, field_names) - - # 如果获取成功,返回结果 - if values: - return values - - # 如果获取失败,构建默认值字典 - result = {} - if default_dict: - for field in field_names: - result[field] = default_dict.get(field, None) - else: - for field in field_names: - result[field] = None - - return result - - except Exception as e: - logger.error(f"[PersonAPI] 批量获取用户信息失败: person_id={person_id}, fields={field_names}, error={e}") - # 返回默认值字典 - result = {} - if default_dict: - for field in field_names: - result[field] = default_dict.get(field, None) - else: - for field in field_names: - result[field] = None - return result - - -async def is_person_known(platform: str, user_id: int) -> bool: - """判断是否认识某个用户 - - Args: - platform: 平台名称 - user_id: 用户ID - - Returns: - bool: 是否认识该用户 - - 示例: - known = await person_api.is_person_known("qq", 123456) - """ - try: - person_info_manager = get_person_info_manager() - return await person_info_manager.is_person_known(platform, user_id) - except Exception as e: - logger.error(f"[PersonAPI] 检查用户是否已知失败: platform={platform}, user_id={user_id}, error={e}") - return False - - def get_person_id_by_name(person_name: str) -> str: """根据用户名获取person_id @@ -147,8 +76,8 @@ def get_person_id_by_name(person_name: str) -> str: person_id = person_api.get_person_id_by_name("张三") """ try: - person_info_manager = get_person_info_manager() - return person_info_manager.get_person_id_by_person_name(person_name) + person = Person(person_name=person_name) + return person.person_id except Exception as e: logger.error(f"[PersonAPI] 根据用户名获取person_id失败: person_name={person_name}, error={e}") return ""