From c1d5c3d9e8ff3eecc94c63a956c9d1cbe46b4a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E7=A9=BA?= <3103908461@qq.com> Date: Tue, 12 Aug 2025 02:41:38 +0800 Subject: [PATCH 01/14] =?UTF-8?q?fix(stream):=20=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E7=A9=BA=20choices=20=E7=9A=84=20SSE=20=E5=B8=A7=E5=B9=B6?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=20usage=EF=BC=8C=E9=81=BF=E5=85=8D=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E8=A7=A3=E6=9E=90=E8=B6=8A=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/llm_models/model_client/openai_client.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/llm_models/model_client/openai_client.py b/src/llm_models/model_client/openai_client.py index 0b4f1e709..6902889c5 100644 --- a/src/llm_models/model_client/openai_client.py +++ b/src/llm_models/model_client/openai_client.py @@ -270,7 +270,15 @@ async def _default_stream_response_handler( # 如果中断量被设置,则抛出ReqAbortException _insure_buffer_closed() raise ReqAbortException("请求被外部信号中断") - + # 空 choices / usage-only 帧的防御 + if not getattr(event, "choices", None) or len(event.choices) == 0: + if getattr(event, "usage", None): + _usage_record = ( + event.usage.prompt_tokens or 0, + event.usage.completion_tokens or 0, + event.usage.total_tokens or 0, + ) + continue # 跳过本帧,避免访问 choices[0] delta = event.choices[0].delta # 获取当前块的delta内容 if hasattr(delta, "reasoning_content") and delta.reasoning_content: # type: ignore From ae254de49421cff6224246fd6add605924a43f2c Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 14:33:13 +0800 Subject: [PATCH 02/14] =?UTF-8?q?better=EF=BC=9A=E9=87=8D=E6=9E=84personin?= =?UTF-8?q?fo=EF=BC=8C=E4=BD=BF=E7=94=A8Person=E7=B1=BB=E5=92=8C=E7=B1=BB?= =?UTF-8?q?=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_loop/heartFC_chat.py | 12 +- src/chat/express/expression_learner.py | 2 +- src/chat/express/expression_selector.py | 43 +- ...ctor_old.py => expression_selector_new.py} | 41 +- .../heart_flow/heartflow_message_processor.py | 24 +- src/chat/replyer/default_generator.py | 29 +- src/chat/replyer/replyer_manager.py | 3 +- src/chat/utils/chat_message_builder.py | 23 +- src/chat/utils/utils.py | 8 +- src/common/database/database_model.py | 23 +- src/individuality/individuality.py | 26 - src/mais4u/mais4u_chat/s4u_chat.py | 4 +- src/mais4u/mais4u_chat/s4u_prompt.py | 24 +- .../mais4u_chat/s4u_stream_generator.py | 1 - src/person_info/__init__.py | 4 - src/person_info/person_info.py | 649 ++++++++---------- src/person_info/relationship_builder.py | 21 +- src/person_info/relationship_fetcher.py | 468 ------------- src/person_info/relationship_manager.py | 179 ++--- src/plugin_system/apis/generator_api.py | 1 - src/plugin_system/apis/person_api.py | 85 +-- 21 files changed, 468 insertions(+), 1202 deletions(-) rename src/chat/express/{expression_selector_old.py => expression_selector_new.py} (90%) delete mode 100644 src/person_info/relationship_fetcher.py 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 "" From 99135f5e013c39827dff82a4c12b8f8b979f79ce Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 14:41:55 +0800 Subject: [PATCH 03/14] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update --- src/chat/replyer/default_generator.py | 6 ++-- src/person_info/person_info.py | 7 ++--- src/person_info/relationship_manager.py | 37 +++++++++++++------------ 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index b36ee8108..b9670452d 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -26,7 +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.person_info import Person +from src.person_info.person_info import Person, get_person_id_by_person_name from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.apis import llm_api @@ -301,8 +301,8 @@ class DefaultReplyer: return "" # 获取用户ID - person = Person(platform=self.chat_stream.platform, user_id=sender) - person_id = person.person_id + person_id = get_person_id_by_person_name(sender) + person = Person(person_id=person_id) if not person_id: logger.warning(f"未找到用户 {sender} 的ID,跳过信息提取") return f"你完全不认识{sender},不理解ta的相关信息。" diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index 4cbbb0ffa..786206d23 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -154,7 +154,7 @@ class Person: if record.likeness_confidence is not None: self.likeness_confidence = float(record.likeness_confidence) - logger.info(f"已从数据库加载用户 {self.person_id} 的信息") + logger.debug(f"已从数据库加载用户 {self.person_id} 的信息") else: self.sync_to_database() logger.info(f"用户 {self.person_id} 在数据库中不存在,使用默认值并创建") @@ -308,10 +308,7 @@ 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 diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index 0405e4d41..bc3e8d28c 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -125,8 +125,6 @@ class RelationshipManager: ) async def get_points(self, - person_name: str, - nickname: str, readable_messages: str, name_mapping: Dict[str, str], timestamp: float, @@ -138,8 +136,8 @@ class RelationshipManager: "relation_points", bot_name = global_config.bot.nickname, alias_str = alias_str, - person_name = person_name, - nickname = nickname, + person_name = person.person_name, + nickname = person.nickname, current_time = current_time, readable_messages = readable_messages) @@ -156,7 +154,7 @@ class RelationshipManager: logger.info(f"points: {points}") if not points: - logger.info(f"对 {person_name} 没啥新印象") + logger.info(f"对 {person.person_name} 没啥新印象") return # 解析JSON并转换为元组列表 @@ -190,7 +188,7 @@ class RelationshipManager: points_list.append(point) if points_list or discarded_count > 0: - logger_str = f"了解了有关{person_name}的新印象:\n" + logger_str = f"了解了有关{person.person_name}的新印象:\n" for point in points_list: logger_str += f"{point[0]},重要性:{point[1]}\n" if discarded_count > 0: @@ -238,7 +236,7 @@ class RelationshipManager: person.points = remaining_points return person - async def get_attitude_to_me(self, person_name, nickname, readable_messages, timestamp, person: Person): + async def get_attitude_to_me(self, 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") # 解析当前态度值 @@ -249,8 +247,8 @@ class RelationshipManager: "attitude_to_me_prompt", bot_name = global_config.bot.nickname, alias_str = alias_str, - person_name = person_name, - nickname = nickname, + person_name = person.person_name, + nickname = person.nickname, readable_messages = readable_messages, current_time = current_time, ) @@ -284,7 +282,7 @@ class RelationshipManager: return person - async def get_neuroticism(self, person_name, nickname, readable_messages, timestamp, person: Person): + async def get_neuroticism(self, 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") # 解析当前态度值 @@ -295,8 +293,8 @@ class RelationshipManager: "neuroticism_prompt", bot_name = global_config.bot.nickname, alias_str = alias_str, - person_name = person_name, - nickname = nickname, + person_name = person.person_name, + nickname = person.nickname, readable_messages = readable_messages, current_time = current_time, ) @@ -364,12 +362,12 @@ class RelationshipManager: continue # 跳过目标用户 - if msg_person.person_name == person_name: + if msg_person.person_name == person_name and msg_person.person_name is not None: name_mapping[msg_person.person_name] = f"{person_name}" continue # 其他用户映射 - if msg_person.person_name not in name_mapping: + if msg_person.person_name not in name_mapping and msg_person.person_name is not None: if current_user > "Z": current_user = "A" user_count += 1 @@ -382,13 +380,16 @@ class RelationshipManager: for original_name, mapped_name in name_mapping.items(): # print(f"original_name: {original_name}, mapped_name: {mapped_name}") - readable_messages = readable_messages.replace(f"{original_name}", f"{mapped_name}") + # 确保 original_name 和 mapped_name 都不为 None + if original_name is not None and mapped_name is not None: + readable_messages = readable_messages.replace(f"{original_name}", f"{mapped_name}") print(name_mapping) - 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) + person = await self.get_points( + readable_messages=readable_messages, name_mapping=name_mapping, timestamp=timestamp, person=person) + person = await self.get_attitude_to_me(readable_messages=readable_messages, timestamp=timestamp, person=person) + person = await self.get_neuroticism(readable_messages=readable_messages, timestamp=timestamp, person=person) person.know_times = know_times + 1 person.last_know = timestamp From f0fff5a03920143c0505164f244f896b60dda871 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 15:15:50 +0800 Subject: [PATCH 04/14] =?UTF-8?q?fix=EF=BC=9Aperson=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=92=8C=E8=B0=83=E7=94=A8=E5=8C=BA=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/express/expression_selector.py | 4 + .../heart_flow/heartflow_message_processor.py | 2 +- src/chat/message_receive/bot.py | 3 + src/person_info/person_info.py | 210 ++++++++++++------ 4 files changed, 152 insertions(+), 67 deletions(-) diff --git a/src/chat/express/expression_selector.py b/src/chat/express/expression_selector.py index 6fb74d1d6..f24f794b9 100644 --- a/src/chat/express/expression_selector.py +++ b/src/chat/express/expression_selector.py @@ -207,6 +207,10 @@ class ExpressionSelector: # 1. 获取20个随机表达方式(现在按权重抽取) style_exprs = self.get_random_expressions(chat_id, 10) + + if len(style_exprs) < 20: + logger.info(f"聊天流 {chat_id} 表达方式正在积累中") + return [] # 2. 构建所有表达方式的索引和情境列表 all_expressions = [] diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index 4fcbae01b..cc63e62c0 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -145,7 +145,7 @@ class HeartFCMessageReceiver: # 4. 关系处理 if global_config.relationship.enable_relationship: - person = Person(platform=message.message_info.platform, user_id=message.message_info.user_info.user_id,nickname=userinfo.user_nickname) + person = Person.register_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/message_receive/bot.py b/src/chat/message_receive/bot.py index 9a8c1b630..fd50035e6 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -16,6 +16,7 @@ from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.plugin_system.core import component_registry, events_manager, global_announcement_manager from src.plugin_system.base import BaseCommand, EventType from src.mais4u.mais4u_chat.s4u_msg_processor import S4UMessageProcessor +from src.person_info.person_info import Person # 定义日志配置 @@ -168,6 +169,8 @@ class ChatBot: # 处理消息内容 await message.process() + + person = Person.register_person(platform=message.message_info.platform, user_id=message.message_info.user_info.user_id,nickname=user_info.user_nickname) await self.s4u_message_processor.process_message(message) diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index 786206d23..6d5de429d 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -33,27 +33,116 @@ def get_person_id_by_person_name(person_name: str) -> str: logger.error(f"根据用户名 {person_name} 获取用户ID时出错 (Peewee): {e}") return "" +def is_person_known(person_id: str = None,user_id: str = None,platform: str = None,person_name: str = None) -> bool: + if person_id: + person = PersonInfo.get_or_none(PersonInfo.person_id == person_id) is not None + return person.is_known if person else False + elif user_id and platform: + person_id = get_person_id(platform, user_id) + person = PersonInfo.get_or_none(PersonInfo.person_id == person_id) + return person.is_known if person else False + elif person_name: + person_id = get_person_id_by_person_name(person_name) + person = PersonInfo.get_or_none(PersonInfo.person_id == person_id) + return person.is_known if person else False + else: + return False + class Person: - def __init__(self, platform: str = "", user_id: str = "",person_id: str = "",person_name: str = "",nickname: str = ""): + @classmethod + def register_person(cls, platform: str, user_id: str, nickname: str): + """ + 注册新用户的类方法 + 必须输入 platform、user_id 和 nickname 参数 + + Args: + platform: 平台名称 + user_id: 用户ID + nickname: 用户昵称 + + Returns: + Person: 新注册的Person实例 + """ + if not platform or not user_id or not nickname: + logger.error("注册用户失败:platform、user_id 和 nickname 都是必需参数") + return None + + # 生成唯一的person_id + person_id = get_person_id(platform, user_id) + + if is_person_known(person_id=person_id): + logger.info(f"用户 {nickname} 已存在") + return Person(person_id=person_id) + + # 创建Person实例 + person = cls.__new__(cls) + + # 设置基本属性 + person.person_id = person_id + person.platform = platform + person.user_id = user_id + person.nickname = nickname + + # 初始化默认值 + person.is_known = True # 注册后立即标记为已认识 + person.person_name = nickname # 使用nickname作为初始person_name + person.name_reason = "用户注册时设置的昵称" + person.know_times = 1 + person.know_since = time.time() + person.last_know = time.time() + person.points = [] + + # 初始化性格特征相关字段 + person.attitude_to_me = 0 + person.attitude_to_me_confidence = 1 + + person.neuroticism = 5 + person.neuroticism_confidence = 1 + + person.friendly_value = 50 + person.friendly_value_confidence = 1 + + person.rudeness = 50 + person.rudeness_confidence = 1 + + person.conscientiousness = 50 + person.conscientiousness_confidence = 1 + + person.likeness = 50 + person.likeness_confidence = 1 + + # 同步到数据库 + person.sync_to_database() + + logger.info(f"成功注册新用户:{person_id},平台:{platform},昵称:{nickname}") + + return person + + def __init__(self, platform: str = "", user_id: str = "",person_id: str = "",person_name: str = ""): + if not is_person_known(person_id=person_id): + logger.warning(f"用户 {person_name} 尚未认识") + return + + 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 "" + return elif platform and user_id: self.person_id = get_person_id(platform, user_id) else: logger.error("Person 初始化失败,缺少必要参数") - return "" + return self.is_known = False self.platform = platform self.user_id = user_id # 初始化默认值 - self.nickname = nickname + self.nickname = "" self.person_name = None self.name_reason = None self.know_times = 0 @@ -91,70 +180,59 @@ class Person: if record: self.is_known = record.is_known if record.is_known else False - self.nickname = record.nickname if record.nickname else self.nickname + self.nickname = record.nickname if record.nickname 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 - 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: + # 处理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 = [] - - # 加载性格特征相关字段 - 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.debug(f"已从数据库加载用户 {self.person_id} 的信息") + 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.debug(f"已从数据库加载用户 {self.person_id} 的信息") else: self.sync_to_database() logger.info(f"用户 {self.person_id} 在数据库中不存在,使用默认值并创建") From ca69e9af1505f9ea1dff97e8072143c82333ad63 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 15:35:18 +0800 Subject: [PATCH 05/14] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E9=A1=BA=E5=BA=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update person_info.py Update relationship_manager.py --- .../heart_flow/heartflow_message_processor.py | 5 +---- src/chat/utils/utils.py | 3 +++ src/person_info/person_info.py | 16 +++++++++++++--- src/person_info/relationship_manager.py | 8 +++++--- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index cc63e62c0..57f60dba5 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -142,10 +142,7 @@ class HeartFCMessageReceiver: else: logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}[兴趣度:{interested_rate:.2f}]") # type: ignore - - # 4. 关系处理 - if global_config.relationship.enable_relationship: - person = Person.register_person(platform=message.message_info.platform, user_id=message.message_info.user_info.user_id,nickname=userinfo.user_nickname) + person = Person.register_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/utils/utils.py b/src/chat/utils/utils.py index 9a91ca173..aefc694e5 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -640,6 +640,9 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]: try: # Assume get_person_id is sync (as per original code), keep using to_thread person = Person(platform=platform, user_id=user_id) + if not person.is_known: + logger.warning(f"用户 {user_info.user_nickname} 尚未认识") + return False, None person_id = person.person_id person_name = None if person_id: diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index 6d5de429d..ebc02b9b9 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -35,7 +35,7 @@ def get_person_id_by_person_name(person_name: str) -> str: def is_person_known(person_id: str = None,user_id: str = None,platform: str = None,person_name: str = None) -> bool: if person_id: - person = PersonInfo.get_or_none(PersonInfo.person_id == person_id) is not None + person = PersonInfo.get_or_none(PersonInfo.person_id == person_id) return person.is_known if person else False elif user_id and platform: person_id = get_person_id(platform, user_id) @@ -119,8 +119,13 @@ class Person: return person def __init__(self, platform: str = "", user_id: str = "",person_id: str = "",person_name: str = ""): - if not is_person_known(person_id=person_id): - logger.warning(f"用户 {person_name} 尚未认识") + if platform == global_config.bot.platform and user_id == global_config.bot.qq_account: + self.is_known = True + self.person_id = get_person_id(platform, user_id) + self.user_id = user_id + self.platform = platform + self.nickname = global_config.bot.nickname + self.person_name = global_config.bot.nickname return @@ -137,6 +142,11 @@ class Person: logger.error("Person 初始化失败,缺少必要参数") return + if not is_person_known(person_id=self.person_id): + self.is_known = False + logger.warning(f"用户 {platform}:{user_id}:{person_name}:{person_id} 尚未认识") + return + self.is_known = False self.platform = platform self.user_id = user_id diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index bc3e8d28c..b3c327a30 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -355,6 +355,8 @@ class RelationshipManager: # 遍历消息,构建映射 for msg in user_messages: + if msg.get("user_id") == "system": + continue msg_person = Person(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")) # 跳过机器人自己 if msg_person.user_id == global_config.bot.qq_account: @@ -386,10 +388,10 @@ class RelationshipManager: print(name_mapping) - person = await self.get_points( + await self.get_points( readable_messages=readable_messages, name_mapping=name_mapping, timestamp=timestamp, person=person) - person = await self.get_attitude_to_me(readable_messages=readable_messages, timestamp=timestamp, person=person) - person = await self.get_neuroticism(readable_messages=readable_messages, timestamp=timestamp, person=person) + await self.get_attitude_to_me(readable_messages=readable_messages, timestamp=timestamp, person=person) + await self.get_neuroticism(readable_messages=readable_messages, timestamp=timestamp, person=person) person.know_times = know_times + 1 person.last_know = timestamp From 1efea7304e45f3802cf81abe95bbfda8924c3014 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 16:36:43 +0800 Subject: [PATCH 06/14] =?UTF-8?q?fix=EF=BC=9A=E6=B7=BB=E5=8A=A0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/replyer/default_generator.py | 7 +++---- src/person_info/person_info.py | 15 +++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index b9670452d..84342c094 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -26,7 +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.person_info import Person, get_person_id_by_person_name +from src.person_info.person_info import Person, get_person_id_by_person_name,is_person_known from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.apis import llm_api @@ -301,9 +301,8 @@ class DefaultReplyer: return "" # 获取用户ID - person_id = get_person_id_by_person_name(sender) - person = Person(person_id=person_id) - if not person_id: + person = Person(person_name = sender) + if not is_person_known(person_name=sender): logger.warning(f"未找到用户 {sender} 的ID,跳过信息提取") return f"你完全不认识{sender},不理解ta的相关信息。" diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index ebc02b9b9..a55a9ef19 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -128,6 +128,8 @@ class Person: self.person_name = global_config.bot.nickname return + self.user_id = "" + self.platform = "" if person_id: self.person_id = person_id @@ -138,6 +140,8 @@ class Person: return elif platform and user_id: self.person_id = get_person_id(platform, user_id) + self.user_id = user_id + self.platform = platform else: logger.error("Person 初始化失败,缺少必要参数") return @@ -148,8 +152,6 @@ class Person: return self.is_known = False - self.platform = platform - self.user_id = user_id # 初始化默认值 self.nickname = "" @@ -189,6 +191,8 @@ class Person: record = PersonInfo.get_or_none(PersonInfo.person_id == self.person_id) if record: + self.user_id = record.user_id if record.user_id else "" + self.platform = record.platform if record.platform else "" self.is_known = record.is_known if record.is_known else False self.nickname = record.nickname if record.nickname else "" self.person_name = record.person_name if record.person_name else self.nickname @@ -300,6 +304,9 @@ class Person: logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}") def build_relationship(self,points_num=3): + print(self.person_name,self.nickname,self.platform,self.is_known) + + if not self.is_known: return "" @@ -327,8 +334,8 @@ class Person: 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})" + if self.person_name != self.nickname: + nickname_str = f"(ta在{self.platform}上的昵称是{self.nickname})" relation_info = "" From 527f2973979faa83f1df4b75cc0cced46ac15f62 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 16:46:29 +0800 Subject: [PATCH 07/14] =?UTF-8?q?fix:=E4=B8=8D=E8=AE=A4=E8=AF=86=E7=9A=84?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9E=84=E5=BB=BA=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/person_info/person_info.py | 4 +++- src/person_info/relationship_manager.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index a55a9ef19..639beaab8 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -144,7 +144,7 @@ class Person: self.platform = platform else: logger.error("Person 初始化失败,缺少必要参数") - return + raise ValueError("Person 初始化失败,缺少必要参数") if not is_person_known(person_id=self.person_id): self.is_known = False @@ -257,6 +257,8 @@ class Person: def sync_to_database(self): """将所有属性同步回数据库""" + if not self.is_known: + return try: # 准备数据 data = { diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index b3c327a30..9f95ba85c 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -1,5 +1,5 @@ from src.common.logger import get_logger -from .person_info import Person +from .person_info import Person,is_person_known import random from src.llm_models.utils_model import LLMRequest from src.config.config import global_config, model_config @@ -357,7 +357,14 @@ class RelationshipManager: for msg in user_messages: if msg.get("user_id") == "system": continue - msg_person = Person(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")) + try: + if not is_person_known(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")): + continue + msg_person = Person(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")) + except Exception as e: + logger.error(f"初始化Person失败: {msg}") + traceback.print_exc() + continue # 跳过机器人自己 if msg_person.user_id == global_config.bot.qq_account: name_mapping[f"{global_config.bot.nickname}"] = f"{global_config.bot.nickname}" From 918429605a2c0394e3bf6b831077f4757bbaec34 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 16:57:08 +0800 Subject: [PATCH 08/14] =?UTF-8?q?fix:=E4=B8=8D=E8=AE=A4=E8=AF=86=E7=9A=84?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9E=84=E5=BB=BA=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/person_info/person_info.py | 1 + src/person_info/relationship_builder.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index 639beaab8..e297f1cc6 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -149,6 +149,7 @@ class Person: if not is_person_known(person_id=self.person_id): self.is_known = False logger.warning(f"用户 {platform}:{user_id}:{person_name}:{person_id} 尚未认识") + self.person_name = f"未知用户{self.person_id[:4]}" return self.is_known = False diff --git a/src/person_info/relationship_builder.py b/src/person_info/relationship_builder.py index fc9908b37..f52bb8d32 100644 --- a/src/person_info/relationship_builder.py +++ b/src/person_info/relationship_builder.py @@ -389,6 +389,8 @@ class RelationshipBuilder: for person_id, segments in self.person_engaged_cache.items(): total_message_count = self._get_total_message_count(person_id) person = Person(person_id=person_id) + if not person.is_known: + continue 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")): From fb63e4d6965fdce3489914fee66023f1cbd4eb89 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 12 Aug 2025 17:03:34 +0800 Subject: [PATCH 09/14] typing fix --- src/chat/express/expression_learner.py | 50 +++++++++---------- .../heart_flow/heartflow_message_processor.py | 2 +- src/chat/replyer/default_generator.py | 39 ++++++++------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/chat/express/expression_learner.py b/src/chat/express/expression_learner.py index ad75b565f..c1233cab3 100644 --- a/src/chat/express/expression_learner.py +++ b/src/chat/express/expression_learner.py @@ -106,7 +106,7 @@ class ExpressionLearner: # 获取该聊天流的学习强度 try: - use_expression, enable_learning, learning_intensity = global_config.expression.get_expression_config_for_chat(self.chat_id) + _, enable_learning, learning_intensity = global_config.expression.get_expression_config_for_chat(self.chat_id) except Exception as e: logger.error(f"获取聊天流 {self.chat_id} 的学习配置失败: {e}") return False @@ -129,7 +129,7 @@ class ExpressionLearner: timestamp_start=self.last_learning_time, timestamp_end=time.time(), ) - + if not recent_messages or len(recent_messages) < self.min_messages_for_learning: return False @@ -168,30 +168,30 @@ class ExpressionLearner: logger.error(f"为聊天流 {self.chat_name} 触发学习失败: {e}") return False - def get_expression_by_chat_id(self) -> Tuple[List[Dict[str, float]], List[Dict[str, float]]]: - """ - 获取指定chat_id的style表达方式(已禁用grammar的获取) - 返回的每个表达方式字典中都包含了source_id, 用于后续的更新操作 - """ - learnt_style_expressions = [] + # def get_expression_by_chat_id(self) -> Tuple[List[Dict[str, float]], List[Dict[str, float]]]: + # """ + # 获取指定chat_id的style表达方式(已禁用grammar的获取) + # 返回的每个表达方式字典中都包含了source_id, 用于后续的更新操作 + # """ + # learnt_style_expressions = [] - # 直接从数据库查询 - style_query = Expression.select().where((Expression.chat_id == self.chat_id) & (Expression.type == "style")) - for expr in style_query: - # 确保create_date存在,如果不存在则使用last_active_time - create_date = expr.create_date if expr.create_date is not None else expr.last_active_time - learnt_style_expressions.append( - { - "situation": expr.situation, - "style": expr.style, - "count": expr.count, - "last_active_time": expr.last_active_time, - "source_id": self.chat_id, - "type": "style", - "create_date": create_date, - } - ) - return learnt_style_expressions + # # 直接从数据库查询 + # style_query = Expression.select().where((Expression.chat_id == self.chat_id) & (Expression.type == "style")) + # for expr in style_query: + # # 确保create_date存在,如果不存在则使用last_active_time + # create_date = expr.create_date if expr.create_date is not None else expr.last_active_time + # learnt_style_expressions.append( + # { + # "situation": expr.situation, + # "style": expr.style, + # "count": expr.count, + # "last_active_time": expr.last_active_time, + # "source_id": self.chat_id, + # "type": "style", + # "create_date": create_date, + # } + # ) + # return learnt_style_expressions diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index 57f60dba5..10bf80929 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -142,7 +142,7 @@ class HeartFCMessageReceiver: else: logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}[兴趣度:{interested_rate:.2f}]") # type: ignore - person = Person.register_person(platform=message.message_info.platform, user_id=message.message_info.user_info.user_id,nickname=userinfo.user_nickname) + _ = Person.register_person(platform=message.message_info.platform, user_id=message.message_info.user_info.user_id,nickname=userinfo.user_nickname) # type: ignore 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 84342c094..522323b53 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -26,7 +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.person_info import Person, get_person_id_by_person_name,is_person_known +from src.person_info.person_info import Person, is_person_known from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.apis import llm_api @@ -173,6 +173,7 @@ class DefaultReplyer: stream_id: Optional[str] = None, reply_message: Optional[Dict[str, Any]] = None, ) -> Tuple[bool, Optional[Dict[str, Any]], Optional[str]]: + # sourcery skip: merge-nested-ifs """ 回复器 (Replier): 负责生成回复文本的核心逻辑。 @@ -308,7 +309,7 @@ class DefaultReplyer: return person.build_relationship(points_num=5) - async def build_expression_habits(self, chat_history: str, target: str) -> Tuple[str, str]: + async def build_expression_habits(self, chat_history: str, target: str) -> str: """构建表达习惯块 Args: @@ -770,21 +771,21 @@ class DefaultReplyer: else: reply_target_block = "" - if is_group_chat: - chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1") - chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2") - else: - chat_target_name = "对方" - if self.chat_target_info: - chat_target_name = ( - self.chat_target_info.get("person_name") or self.chat_target_info.get("user_nickname") or "对方" - ) - chat_target_1 = await global_prompt_manager.format_prompt( - "chat_target_private1", sender_name=chat_target_name - ) - chat_target_2 = await global_prompt_manager.format_prompt( - "chat_target_private2", sender_name=chat_target_name - ) + # if is_group_chat: + # chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1") + # chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2") + # else: + # chat_target_name = "对方" + # if self.chat_target_info: + # chat_target_name = ( + # self.chat_target_info.get("person_name") or self.chat_target_info.get("user_nickname") or "对方" + # ) + # chat_target_1 = await global_prompt_manager.format_prompt( + # "chat_target_private1", sender_name=chat_target_name + # ) + # chat_target_2 = await global_prompt_manager.format_prompt( + # "chat_target_private2", sender_name=chat_target_name + # ) # 构建分离的对话 prompt @@ -846,8 +847,8 @@ class DefaultReplyer: is_group_chat = bool(chat_stream.group_info) if reply_message: - sender = reply_message.get("sender") - target = reply_message.get("target") + sender = reply_message.get("sender", "") + target = reply_message.get("target", "") else: sender, target = self._parse_reply_target(reply_to) From 1f7d978d1a6baa39bbe40998ddd363eb415dcf31 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 17:04:26 +0800 Subject: [PATCH 10/14] =?UTF-8?q?fix=EF=BC=9A=E6=80=BB=E4=B9=8B=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/replyer/default_generator.py | 4 ++-- src/person_info/person_info.py | 4 +++- src/person_info/relationship_manager.py | 4 ---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 84342c094..222fdfedf 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -656,9 +656,9 @@ class DefaultReplyer: chat_id = chat_stream.stream_id is_group_chat = bool(chat_stream.group_info) platform = chat_stream.platform - user_id = reply_message.get("user_id","") - if user_id: + if reply_message: + user_id = reply_message.get("user_id","") person = Person(platform=platform, user_id=user_id) person_name = person.person_name or user_id sender = person_name diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py index e297f1cc6..5c77b1af2 100644 --- a/src/person_info/person_info.py +++ b/src/person_info/person_info.py @@ -307,7 +307,7 @@ class Person: logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}") def build_relationship(self,points_num=3): - print(self.person_name,self.nickname,self.platform,self.is_known) + # print(self.person_name,self.nickname,self.platform,self.is_known) if not self.is_known: @@ -374,6 +374,8 @@ class Person: if points_text: points_info = f"你还记得ta最近做的事:{points_text}" + if not (nickname_str or attitude_info or neuroticism_info or points_info): + return "" relation_info = f"{self.person_name}:{nickname_str}{attitude_info}{neuroticism_info}{points_info}" return relation_info diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index 9f95ba85c..69365716b 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -358,8 +358,6 @@ class RelationshipManager: if msg.get("user_id") == "system": continue try: - if not is_person_known(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")): - continue msg_person = Person(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")) except Exception as e: logger.error(f"初始化Person失败: {msg}") @@ -392,8 +390,6 @@ class RelationshipManager: # 确保 original_name 和 mapped_name 都不为 None if original_name is not None and mapped_name is not None: readable_messages = readable_messages.replace(f"{original_name}", f"{mapped_name}") - - print(name_mapping) await self.get_points( readable_messages=readable_messages, name_mapping=name_mapping, timestamp=timestamp, person=person) From e28e7e08e841745bed7a73972cd07fbe9eceb37d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 12 Aug 2025 17:08:35 +0800 Subject: [PATCH 11/14] =?UTF-8?q?more=20typing=20fix=E5=92=8C=E9=98=B2?= =?UTF-8?q?=E7=82=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/llm_models/model_client/openai_client.py | 8 ++++---- src/plugin_system/apis/person_api.py | 4 ++-- src/plugin_system/apis/send_api.py | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/llm_models/model_client/openai_client.py b/src/llm_models/model_client/openai_client.py index 6902889c5..c580899ad 100644 --- a/src/llm_models/model_client/openai_client.py +++ b/src/llm_models/model_client/openai_client.py @@ -271,14 +271,14 @@ async def _default_stream_response_handler( _insure_buffer_closed() raise ReqAbortException("请求被外部信号中断") # 空 choices / usage-only 帧的防御 - if not getattr(event, "choices", None) or len(event.choices) == 0: - if getattr(event, "usage", None): + if not hasattr(event, "choices") or not event.choices: + if hasattr(event, "usage") and event.usage: _usage_record = ( event.usage.prompt_tokens or 0, event.usage.completion_tokens or 0, event.usage.total_tokens or 0, ) - continue # 跳过本帧,避免访问 choices[0] + continue # 跳过本帧,避免访问 choices[0] delta = event.choices[0].delta # 获取当前块的delta内容 if hasattr(delta, "reasoning_content") and delta.reasoning_content: # type: ignore @@ -479,7 +479,7 @@ class OpenaiClient(BaseClient): req_task.cancel() raise ReqAbortException("请求被外部信号中断") await asyncio.sleep(0.1) # 等待0.5秒后再次检查任务&中断信号量状态 - + # logger.info(f"OpenAI请求时间: {model_info.model_identifier} {time.time() - start_time} \n{messages}") resp, usage_record = async_response_parser(req_task.result()) diff --git a/src/plugin_system/apis/person_api.py b/src/plugin_system/apis/person_api.py index c81e4747e..ed904003a 100644 --- a/src/plugin_system/apis/person_api.py +++ b/src/plugin_system/apis/person_api.py @@ -19,7 +19,7 @@ logger = get_logger("person_api") # ============================================================================= -def get_person_id(platform: str, user_id: int) -> str: +def get_person_id(platform: str, user_id: int | str) -> str: """根据平台和用户ID获取person_id Args: @@ -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 Person(platform=platform, user_id=user_id).person_id + return Person(platform=platform, user_id=str(user_id)).person_id except Exception as e: logger.error(f"[PersonAPI] 获取person_id失败: platform={platform}, user_id={user_id}, error={e}") return "" diff --git a/src/plugin_system/apis/send_api.py b/src/plugin_system/apis/send_api.py index c96679f30..870c979f7 100644 --- a/src/plugin_system/apis/send_api.py +++ b/src/plugin_system/apis/send_api.py @@ -98,10 +98,12 @@ async def _send_to_target( if reply_message: anchor_message = message_dict_to_message_recv(reply_message) - anchor_message.update_chat_stream(target_stream) - reply_to_platform_id = ( - f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}" - ) + if anchor_message: + anchor_message.update_chat_stream(target_stream) + assert anchor_message.message_info.user_info, "用户信息缺失" + reply_to_platform_id = ( + f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}" + ) else: reply_to_platform_id = "" anchor_message = None From 9c412cd9bc9a81f0ca3dc5e2644256d356d6cef0 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 12 Aug 2025 17:18:49 +0800 Subject: [PATCH 12/14] typing fix --- src/person_info/relationship_manager.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index 9f95ba85c..0b84e04fb 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -342,7 +342,7 @@ class RelationshipManager: """ person = Person(person_id=person_id) person_name = person.person_name - nickname = person.nickname + # nickname = person.nickname know_times: float = person.know_times user_messages = bot_engaged_messages @@ -358,11 +358,13 @@ class RelationshipManager: if msg.get("user_id") == "system": continue try: - if not is_person_known(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")): - continue - msg_person = Person(user_id=msg.get("user_id"), platform=msg.get("chat_info_platform")) + user_id = msg.get("user_id") + platform = msg.get("chat_info_platform") + assert isinstance(user_id, str) and isinstance(platform, str) + if is_person_known(user_id=user_id, platform=platform): + msg_person = Person(user_id=user_id, platform=platform) except Exception as e: - logger.error(f"初始化Person失败: {msg}") + logger.error(f"初始化Person失败: {msg}, 出现错误: {e}") traceback.print_exc() continue # 跳过机器人自己 From ba94e3252bb0f6e6c41c7e1faa077c00dc521fac Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 17:26:07 +0800 Subject: [PATCH 13/14] =?UTF-8?q?fix=EF=BC=9Alog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_loop/heartFC_chat.py | 6 +++--- src/chat/replyer/default_generator.py | 29 +++++++++------------------ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 97a7efdf1..24194518f 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -250,7 +250,7 @@ class HeartFChatting: if new_message_count > 0: # 只在兴趣值变化时输出log if not hasattr(self, "_last_accumulated_interest") or total_interest != self._last_accumulated_interest: - logger.info(f"{self.log_prefix} 休息中,累计兴趣值: {total_interest:.2f}, 活跃度: {talk_frequency:.1f}") + logger.info(f"{self.log_prefix} 休息中,新消息:{new_message_count}条,累计兴趣值: {total_interest:.2f}, 活跃度: {talk_frequency:.1f}") self._last_accumulated_interest = total_interest if total_interest >= modified_exit_interest_threshold: @@ -262,8 +262,8 @@ class HeartFChatting: return True,total_interest/new_message_count # 每10秒输出一次等待状态 - if int(time.time() - self.last_read_time) > 0 and int(time.time() - self.last_read_time) % 10 == 0: - logger.info( + if int(time.time() - self.last_read_time) > 0 and int(time.time() - self.last_read_time) % 15 == 0: + logger.debug( f"{self.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,累计兴趣{total_interest:.1f},继续等待..." ) await asyncio.sleep(0.5) diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 70cb0f4bc..9d852216d 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -57,7 +57,7 @@ def init_prompt(): {reply_style},你可以完全重组回复,保留最基本的表达含义就好,但重组后保持语意通顺。 {keywords_reaction_prompt} {moderation_prompt} -不要浮夸,不要夸张修辞,平淡且不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 ),只输出一条回复就好。 +不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 ),只输出一条回复就好。 现在,你说: """, "default_expressor_prompt", @@ -66,17 +66,11 @@ def init_prompt(): # s4u 风格的 prompt 模板 Prompt( """ -{expression_habits_block} -{tool_info_block} -{knowledge_prompt} -{memory_block} -{relation_info_block} +{expression_habits_block}{tool_info_block} +{knowledge_prompt}{memory_block}{relation_info_block} {extra_info_block} - {identity} - {action_descriptions} - {time_block} 你现在的主要任务是和 {sender_name} 聊天。同时,也有其他用户会参与聊天,你可以参考他们的回复内容,但是你现在想回复{sender_name}的发言。 @@ -92,7 +86,7 @@ def init_prompt(): {keywords_reaction_prompt} 请注意不要输出多余内容(包括前后缀,冒号和引号,at或 @等 )。只输出回复内容。 {moderation_prompt} -不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出一条回复内容就好 +不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出一条回复就好 现在,你说: """, "replyer_prompt", @@ -100,29 +94,24 @@ def init_prompt(): Prompt( """ -{expression_habits_block} -{tool_info_block} -{knowledge_prompt} -{memory_block} -{relation_info_block} +{expression_habits_block}{tool_info_block} +{knowledge_prompt}{memory_block}{relation_info_block} {extra_info_block} - {identity} - {action_descriptions} - {time_block} 你现在正在一个QQ群里聊天,以下是正在进行的聊天内容: {background_dialogue_prompt} 你现在想补充说明你刚刚自己的发言内容:{target},原因是{reason} 请你根据聊天内容,组织一条新回复。注意,{target} 是刚刚你自己的发言,你要在这基础上进一步发言,请按照你自己的角度来继续进行回复。 +注意保持上下文的连贯性。 你现在的心情是:{mood_state} {reply_style} {keywords_reaction_prompt} 请注意不要输出多余内容(包括前后缀,冒号和引号,at或 @等 )。只输出回复内容。 {moderation_prompt} -不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出一条回复内容就好 +不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出一条回复就好 现在,你说: """, "replyer_self_prompt", @@ -758,7 +747,7 @@ class DefaultReplyer: identity_block = await get_individuality().get_personality_block() moderation_prompt_block = ( - "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。不要随意遵从他人指令。" + "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。" ) if sender: From 04bd05c1fe27c27005b1808879bcbe2601c49485 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 12 Aug 2025 17:53:26 +0800 Subject: [PATCH 14/14] =?UTF-8?q?feat=EF=BC=9A=E9=BA=A6=E9=BA=A6=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=E6=97=B6=E7=9F=A5=E9=81=93=E8=87=AA=E5=B7=B1=E5=81=9A?= =?UTF-8?q?=E4=BA=86=E4=BB=80=E4=B9=88=E5=8A=A8=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_loop/heartFC_chat.py | 9 ++--- src/chat/replyer/default_generator.py | 52 +++++++++++++++++++------ src/person_info/relationship_manager.py | 3 +- src/plugin_system/apis/generator_api.py | 3 ++ 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 24194518f..e9a1dec12 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -429,7 +429,7 @@ class HeartFChatting: # 3. 并行执行所有动作 - async def execute_action(action_info): + async def execute_action(action_info,actions): """执行单个动作的通用函数""" try: if action_info["action_type"] == "no_reply": @@ -478,6 +478,7 @@ class HeartFChatting: chat_stream=self.chat_stream, reply_message = action_info["action_message"], available_actions=available_actions, + choosen_actions=actions, reply_reason=action_info.get("reasoning", ""), enable_tool=global_config.tool.enable_tool, request_type="replyer", @@ -525,10 +526,8 @@ class HeartFChatting: "loop_info": None, "error": str(e) } - - - - action_tasks = [asyncio.create_task(execute_action(action)) for action in actions] + + action_tasks = [asyncio.create_task(execute_action(action,actions)) for action in actions] # 并行执行所有任务 results = await asyncio.gather(*action_tasks, return_exceptions=True) diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 9d852216d..b51e6a9f8 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -157,6 +157,7 @@ class DefaultReplyer: extra_info: str = "", reply_reason: str = "", available_actions: Optional[Dict[str, ActionInfo]] = None, + choosen_actions: Optional[List[Dict[str, Any]]] = None, enable_tool: bool = True, from_plugin: bool = True, stream_id: Optional[str] = None, @@ -171,12 +172,14 @@ class DefaultReplyer: extra_info: 额外信息,用于补充上下文 reply_reason: 回复原因 available_actions: 可用的动作信息字典 + choosen_actions: 已选动作 enable_tool: 是否启用工具调用 from_plugin: 是否来自插件 Returns: Tuple[bool, Optional[Dict[str, Any]], Optional[str]]: (是否成功, 生成的回复, 使用的prompt) """ + prompt = None if available_actions is None: available_actions = {} @@ -186,6 +189,7 @@ class DefaultReplyer: prompt = await self.build_prompt_reply_context( extra_info=extra_info, available_actions=available_actions, + choosen_actions=choosen_actions, enable_tool=enable_tool, reply_message=reply_message, reply_reason=reply_reason, @@ -618,12 +622,43 @@ class DefaultReplyer: mai_think.sender = sender mai_think.target = target return mai_think + + + async def build_actions_prompt(self, available_actions, choosen_actions: Optional[List[Dict[str, Any]]] = None) -> str: + """构建动作提示 + """ + + action_descriptions = "" + if available_actions: + action_descriptions = "你可以做以下这些动作:\n" + for action_name, action_info in available_actions.items(): + action_description = action_info.description + action_descriptions += f"- {action_name}: {action_description}\n" + action_descriptions += "\n" + + if choosen_actions: + action_descriptions += "根据聊天情况,你决定在回复的同时做以下这些动作:\n" + + for action in choosen_actions: + action_name = action.get('action_type', 'unknown_action') + if action_name =="reply": + continue + action_description = action.get('reason', '无描述') + reasoning = action.get('reasoning', '无原因') + + action_descriptions += f"- {action_name}: {action_description},原因:{reasoning}\n" + + + return action_descriptions + + async def build_prompt_reply_context( self, extra_info: str = "", reply_reason: str = "", available_actions: Optional[Dict[str, ActionInfo]] = None, + choosen_actions: Optional[List[Dict[str, Any]]] = None, enable_tool: bool = True, reply_message: Optional[Dict[str, Any]] = None, ) -> str: @@ -634,6 +669,7 @@ class DefaultReplyer: extra_info: 额外信息,用于补充上下文 reply_reason: 回复原因 available_actions: 可用动作 + choosen_actions: 已选动作 enable_timeout: 是否启用超时处理 enable_tool: 是否启用工具调用 reply_message: 回复的原始消息 @@ -667,14 +703,6 @@ class DefaultReplyer: target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True) - # 构建action描述 (如果启用planner) - action_descriptions = "" - if available_actions: - action_descriptions = "你有以下的动作能力,但执行这些动作不由你决定,由另外一个模型同步决定,因此你只需要知道有如下能力即可:\n" - for action_name, action_info in available_actions.items(): - action_description = action_info.description - action_descriptions += f"- {action_name}: {action_description}\n" - action_descriptions += "\n" message_list_before_now_long = get_raw_msg_before_timestamp_with_chat( chat_id=chat_id, @@ -707,6 +735,7 @@ class DefaultReplyer: self.build_tool_info(chat_talking_prompt_short, sender, target, enable_tool=enable_tool), "tool_info" ), self._time_and_run_task(self.get_prompt_info(chat_talking_prompt_short, sender, target), "prompt_info"), + self._time_and_run_task(self.build_actions_prompt(available_actions,choosen_actions), "actions_info"), ) # 任务名称中英文映射 @@ -716,6 +745,7 @@ class DefaultReplyer: "memory_block": "回忆", "tool_info": "使用工具", "prompt_info": "获取知识", + "actions_info": "动作信息", } # 处理结果 @@ -734,7 +764,7 @@ class DefaultReplyer: memory_block = results_dict["memory_block"] tool_info = results_dict["tool_info"] prompt_info = results_dict["prompt_info"] # 直接使用格式化后的结果 - + actions_info = results_dict["actions_info"] keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target) if extra_info: @@ -792,7 +822,7 @@ class DefaultReplyer: relation_info_block=relation_info, extra_info_block=extra_info_block, identity=identity_block, - action_descriptions=action_descriptions, + action_descriptions=actions_info, mood_state=mood_prompt, background_dialogue_prompt=background_dialogue_prompt, time_block=time_block, @@ -812,7 +842,7 @@ class DefaultReplyer: relation_info_block=relation_info, extra_info_block=extra_info_block, identity=identity_block, - action_descriptions=action_descriptions, + action_descriptions=actions_info, sender_name=sender, mood_state=mood_prompt, background_dialogue_prompt=background_dialogue_prompt, diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index e4bed6bdf..35d7079b2 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -362,8 +362,7 @@ class RelationshipManager: user_id = msg.get("user_id") platform = msg.get("chat_info_platform") assert isinstance(user_id, str) and isinstance(platform, str) - if is_person_known(user_id=user_id, platform=platform): - msg_person = Person(user_id=user_id, platform=platform) + msg_person = Person(user_id=user_id, platform=platform) except Exception as e: logger.error(f"初始化Person失败: {msg}, 出现错误: {e}") diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index 4e33595de..4298a5f11 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -77,6 +77,7 @@ async def generate_reply( extra_info: str = "", reply_reason: str = "", available_actions: Optional[Dict[str, ActionInfo]] = None, + choosen_actions: Optional[List[Dict[str, Any]]] = None, enable_tool: bool = False, enable_splitter: bool = True, enable_chinese_typo: bool = True, @@ -94,6 +95,7 @@ async def generate_reply( extra_info: 额外信息,用于补充上下文 reply_reason: 回复原因 available_actions: 可用动作 + choosen_actions: 已选动作 enable_tool: 是否启用工具调用 enable_splitter: 是否启用消息分割器 enable_chinese_typo: 是否启用错字生成器 @@ -124,6 +126,7 @@ async def generate_reply( success, llm_response_dict, prompt = await replyer.generate_reply_with_context( extra_info=extra_info, available_actions=available_actions, + choosen_actions=choosen_actions, enable_tool=enable_tool, reply_message=reply_message, reply_reason=reply_reason,