From 851a559b33cf97779f3ae3ffeec16b92e18f15d0 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sun, 8 Jun 2025 17:44:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=B0=86=E5=85=B3=E7=B3=BB=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8F=8A=E6=97=B6=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info_processors/relationship_processor.py | 222 +++++++++++++----- .../info_processors/self_processor.py | 19 +- .../focus_chat/replyer/default_replyer.py | 6 +- .../observation/chatting_observation.py | 5 +- src/main.py | 4 - src/person_info/impression_update_task.py | 16 +- src/person_info/relationship_manager.py | 5 +- template/bot_config_template.toml | 5 +- 8 files changed, 191 insertions(+), 91 deletions(-) diff --git a/src/chat/focus_chat/info_processors/relationship_processor.py b/src/chat/focus_chat/info_processors/relationship_processor.py index e6eaf32b6..f5c629307 100644 --- a/src/chat/focus_chat/info_processors/relationship_processor.py +++ b/src/chat/focus_chat/info_processors/relationship_processor.py @@ -16,6 +16,8 @@ from src.chat.focus_chat.info.relation_info import RelationInfo from json_repair import repair_json from src.person_info.person_info import person_info_manager import json +import asyncio +from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat logger = get_logger("processor") @@ -37,6 +39,7 @@ def init_prompt(): 1. 根据聊天记录的需求,如果需要你和某个人的信息,请输出你和这个人之间精简的信息 2. 如果没有特别需要提及的信息,就不用输出这个人的信息 3. 如果有人问你对他的看法或者关系,请输出你和这个人之间的信息 +4. 你可以完全不输出任何信息,或者不输出某个人 请从这些信息中提取出你对某人的了解信息,信息提取成一串文本: @@ -58,6 +61,11 @@ class RelationshipProcessor(BaseProcessor): super().__init__() self.subheartflow_id = subheartflow_id + self.person_cache: Dict[str, Dict[str, any]] = {} # {person_id: {"info": str, "ttl": int, "start_time": float}} + self.pending_updates: Dict[str, Dict[str, any]] = ( + {} + ) # {person_id: {"start_time": float, "end_time": float, "grace_period_ttl": int, "chat_id": str}} + self.grace_period_rounds = 5 self.llm_model = LLMRequest( model=global_config.model.relation, @@ -91,54 +99,109 @@ class RelationshipProcessor(BaseProcessor): return [relation_info] async def relation_identify( - self, observations: Optional[List[Observation]] = None, + self, + observations: Optional[List[Observation]] = None, ): """ 在回复前进行思考,生成内心想法并收集工具调用结果 - - 参数: - observations: 观察信息 - - 返回: - 如果return_prompt为False: - tuple: (current_mind, past_mind) 当前想法和过去的想法列表 - 如果return_prompt为True: - tuple: (current_mind, past_mind, prompt) 当前想法、过去的想法列表和使用的prompt """ + # 0. 从观察信息中提取所需数据 + person_list = [] + chat_observe_info = "" + is_group_chat = False + if observations: + for observation in observations: + if isinstance(observation, ChattingObservation): + is_group_chat = observation.is_group_chat + chat_observe_info = observation.get_observe_info() + person_list = observation.person_list + break - if observations is None: - observations = [] - for observation in observations: - if isinstance(observation, ChattingObservation): - # 获取聊天元信息 - is_group_chat = observation.is_group_chat - chat_target_info = observation.chat_target_info - chat_target_name = "对方" # 私聊默认名称 - if not is_group_chat and chat_target_info: - # 优先使用person_name,其次user_nickname,最后回退到默认值 - chat_target_name = ( - chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or chat_target_name + # 1. 处理等待更新的条目(仅检查TTL,不检查是否被重提) + persons_to_update_now = [] # 等待期结束,需要立即更新的用户 + for person_id, data in list(self.pending_updates.items()): + data["grace_period_ttl"] -= 1 + if data["grace_period_ttl"] <= 0: + persons_to_update_now.append(person_id) + + # 触发等待期结束的更新任务 + for person_id in persons_to_update_now: + if person_id in self.pending_updates: + update_data = self.pending_updates.pop(person_id) + logger.info(f"{self.log_prefix} 用户 {person_id} 等待期结束,开始印象更新。") + asyncio.create_task( + self.update_impression_on_cache_expiry( + person_id, update_data["chat_id"], update_data["start_time"], update_data["end_time"] ) - # 获取聊天内容 - chat_observe_info = observation.get_observe_info() - person_list = observation.person_list + ) - nickname_str = "" - for nicknames in global_config.bot.alias_names: - nickname_str += f"{nicknames}," + # 2. 维护活动缓存,并将过期条目移至等待区或立即更新 + persons_moved_to_pending = [] + for person_id, cache_data in self.person_cache.items(): + cache_data["ttl"] -= 1 + if cache_data["ttl"] <= 0: + persons_moved_to_pending.append(person_id) + + for person_id in persons_moved_to_pending: + if person_id in self.person_cache: + cache_item = self.person_cache.pop(person_id) + start_time = cache_item.get("start_time") + end_time = time.time() + time_elapsed = end_time - start_time + + impression_messages = get_raw_msg_by_timestamp_with_chat(self.subheartflow_id, start_time, end_time) + message_count = len(impression_messages) + + if message_count > 50 or (time_elapsed > 600 and message_count > 20): + logger.info( + f"{self.log_prefix} 用户 {person_id} 缓存过期,满足立即更新条件 (消息数: {message_count}, 持续时间: {time_elapsed:.0f}s),立即更新。" + ) + asyncio.create_task( + self.update_impression_on_cache_expiry(person_id, self.subheartflow_id, start_time, end_time) + ) + else: + logger.info(f"{self.log_prefix} 用户 {person_id} 缓存过期,进入更新等待区。") + self.pending_updates[person_id] = { + "start_time": start_time, + "end_time": end_time, + "grace_period_ttl": self.grace_period_rounds, + "chat_id": self.subheartflow_id, + } + + # 3. 准备LLM输入和直接使用缓存 + if not person_list: + return "" + + cached_person_info_str = "" + persons_to_process = [] + person_name_list_for_llm = [] + + for person_id in person_list: + if person_id in self.person_cache: + logger.info(f"{self.log_prefix} 关系识别 (缓存): {person_id}") + person_name = await person_info_manager.get_value(person_id, "person_name") + info = self.person_cache[person_id]["info"] + cached_person_info_str += f"你对 {person_name} 的了解:{info}\n" + else: + # 所有不在活动缓存中的用户(包括等待区的)都将由LLM处理 + persons_to_process.append(person_id) + person_name_list_for_llm.append(await person_info_manager.get_value(person_id, "person_name")) + + # 4. 如果没有需要LLM处理的人员,直接返回缓存信息 + if not persons_to_process: + final_result = cached_person_info_str.strip() + if final_result: + logger.info(f"{self.log_prefix} 关系识别 (全部缓存): {final_result}") + return final_result + + # 5. 为需要处理的人员准备LLM prompt + nickname_str = ",".join(global_config.bot.alias_names) name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。" - - if is_group_chat: - relation_prompt_init = "你对群聊里的人的印象是:\n" - else: - relation_prompt_init = "你对对方的印象是:\n" - + relation_prompt_init = "你对群聊里的人的印象是:\n" if is_group_chat else "你对对方的印象是:\n" relation_prompt = "" - person_name_list = [] - for person in person_list: - relation_prompt += f"{await relationship_manager.build_relationship_info(person, is_id=True)}\n\n" - person_name_list.append(await person_info_manager.get_value(person, "person_name")) - + for person_id in persons_to_process: + relation_prompt += f"{await relationship_manager.build_relationship_info(person_id, is_id=True)}\n\n" + if relation_prompt: relation_prompt = relation_prompt_init + relation_prompt else: @@ -151,45 +214,76 @@ class RelationshipProcessor(BaseProcessor): chat_observe_info=chat_observe_info, ) - # print(prompt) - - content = "" + # 6. 调用LLM并处理结果 + newly_processed_info_str = "" try: logger.info(f"{self.log_prefix} 关系识别prompt: \n{prompt}\n") content, _ = await self.llm_model.generate_response_async(prompt=prompt) - if not content: + if content: + print(f"content: {content}") + content_json = json.loads(repair_json(content)) + + for person_name, person_info in content_json.items(): + if person_name in person_name_list_for_llm: + try: + idx = person_name_list_for_llm.index(person_name) + person_id = persons_to_process[idx] + + # 关键:检查此人是否在等待区,如果是,则为"唤醒" + start_time = time.time() # 新用户的默认start_time + if person_id in self.pending_updates: + logger.info(f"{self.log_prefix} 用户 {person_id} 在等待期被LLM重提,重新激活缓存。") + revived_item = self.pending_updates.pop(person_id) + start_time = revived_item["start_time"] + + self.person_cache[person_id] = { + "info": person_info, + "ttl": 5, + "start_time": start_time, + } + newly_processed_info_str += f"你对 {person_name} 的了解:{person_info}\n" + except (ValueError, IndexError): + continue + else: logger.warning(f"{self.log_prefix} LLM返回空结果,关系识别失败。") - - print(f"content: {content}") - - content = repair_json(content) - content = json.loads(content) - - person_info_str = "" - - for person_name, person_info in content.items(): - # print(f"person_name: {person_name}, person_info: {person_info}") - # print(f"person_list: {person_name_list}") - if person_name not in person_name_list: - continue - person_str = f"你对 {person_name} 的了解:{person_info}\n" - person_info_str += person_str - - + except Exception as e: - # 处理总体异常 logger.error(f"{self.log_prefix} 执行LLM请求或处理响应时出错: {e}") logger.error(traceback.format_exc()) - person_info_str = "关系识别过程中出现错误" + newly_processed_info_str = "关系识别过程中出现错误" + + # 7. 合并缓存和新处理的信息 + person_info_str = (cached_person_info_str + newly_processed_info_str).strip() if person_info_str == "None": person_info_str = "" - - # 记录初步思考结果 logger.info(f"{self.log_prefix} 关系识别: {person_info_str}") return person_info_str + async def update_impression_on_cache_expiry( + self, person_id: str, chat_id: str, start_time: float, end_time: float + ): + """ + 在缓存过期时,获取聊天记录并更新用户印象 + """ + logger.info(f"缓存过期,开始为 {person_id} 更新印象。时间范围:{start_time} -> {end_time}") + try: + + + impression_messages = get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time) + if impression_messages: + logger.info(f"为 {person_id} 获取到 {len(impression_messages)} 条消息用于印象更新。") + await relationship_manager.update_person_impression( + person_id=person_id, timestamp=end_time, bot_engaged_messages=impression_messages + ) + else: + logger.info(f"在指定时间范围内没有找到 {person_id} 的消息,不更新印象。") + + except Exception as e: + logger.error(f"为 {person_id} 更新印象时发生错误: {e}") + logger.error(traceback.format_exc()) + init_prompt() diff --git a/src/chat/focus_chat/info_processors/self_processor.py b/src/chat/focus_chat/info_processors/self_processor.py index 86bafc5a2..450afdbab 100644 --- a/src/chat/focus_chat/info_processors/self_processor.py +++ b/src/chat/focus_chat/info_processors/self_processor.py @@ -20,22 +20,27 @@ logger = get_logger("processor") def init_prompt(): indentify_prompt = """ +<聊天记录> +{chat_observe_info} + + +<人格> {name_block} -你是一个AI,但是你伪装成了一个人类,不要让别人发现这一点 -请参考以下人格,不要被当前聊天内容中的内容误导: +你是一个AI,但是你伪装成了一个人类,不要让别人发现这一点,你的性格是: {prompt_personality} {indentify_block} + -以下是正在进行的聊天内容: -现在是{time_now},你正在参与聊天 -{chat_observe_info} +请区分聊天记录的内容和你稳定的人格,聊天记录是现在发生的事情,人格是你稳定的独特的特质。 -现在请你输出对自己的描述:请严格遵守以下规则 +{name_block} +现在请你提取你人格的关键信息,提取成一串文本: 1. 根据聊天记录,输出与聊天记录相关的自我描述,包括人格,形象等等,对人格形象进行精简 2. 思考有没有内容与你的描述相关 3. 如果没有明显相关内容,请输出十几个字的简短自我描述 -现在请输出你的自我描述,请注意不要输出多余内容(包括前后缀,括号(),表情包,at或 @等 ): +现在请输出你的自我描述,格式是:“你是.....,你.................(描述)” +请注意不要输出多余内容(包括前后缀,括号(),表情包,at或 @等 ): """ Prompt(indentify_prompt, "indentify_prompt") diff --git a/src/chat/focus_chat/replyer/default_replyer.py b/src/chat/focus_chat/replyer/default_replyer.py index e205e1fd4..a5b4592ad 100644 --- a/src/chat/focus_chat/replyer/default_replyer.py +++ b/src/chat/focus_chat/replyer/default_replyer.py @@ -37,11 +37,13 @@ def init_prompt(): {extra_info_block} +{relation_info_block} + {time_block} 你现在正在群里聊天,以下是群里正在进行的聊天内容: {chat_info} -{relation_info_block} + 以上是聊天内容,你需要了解聊天记录中的内容 @@ -605,6 +607,8 @@ class DefaultReplyer: platform=self.chat_stream.platform, ) + # await anchor_message.process() + bot_message = MessageSending( message_id=message_id, # 使用片段的唯一ID chat_stream=self.chat_stream, diff --git a/src/chat/heart_flow/observation/chatting_observation.py b/src/chat/heart_flow/observation/chatting_observation.py index 83ec21997..4df814a33 100644 --- a/src/chat/heart_flow/observation/chatting_observation.py +++ b/src/chat/heart_flow/observation/chatting_observation.py @@ -132,13 +132,13 @@ class ChattingObservation(Observation): # logger.debug(f"找到的锚定消息:find_msg: {find_msg}") break else: - similarity = difflib.SequenceMatcher(None, text, message["processed_plain_text"]).ratio() + similarity = difflib.SequenceMatcher(None, text, message["raw_message"]).ratio() msg_list.append({"message": message, "similarity": similarity}) # logger.debug(f"对锚定消息检查:message: {message['processed_plain_text']},similarity: {similarity}") if not find_msg: if msg_list: msg_list.sort(key=lambda x: x["similarity"], reverse=True) - if msg_list[0]["similarity"] >= 0.5: # 只返回相似度大于等于0.5的消息 + if msg_list[0]["similarity"] >= 0.9: # 只返回相似度大于等于0.5的消息 find_msg = msg_list[0]["message"] else: logger.debug("没有找到锚定消息,相似度低") @@ -191,6 +191,7 @@ class ChattingObservation(Observation): "detailed_plain_text": find_msg.get("processed_plain_text"), "processed_plain_text": find_msg.get("processed_plain_text"), } + # print(f"message_dict: {message_dict}") find_rec_msg = MessageRecv(message_dict) # logger.debug(f"锚定消息处理后:find_rec_msg: {find_rec_msg}") return find_rec_msg diff --git a/src/main.py b/src/main.py index 14ff66533..78edd4132 100644 --- a/src/main.py +++ b/src/main.py @@ -20,7 +20,6 @@ from .common.server import global_server, Server from rich.traceback import install from .chat.focus_chat.expressors.exprssion_learner import expression_learner from .api.main import start_api_server -from .person_info.impression_update_task import impression_update_task install(extra_lines=3) @@ -60,9 +59,6 @@ class MainSystem: # 添加遥测心跳任务 await async_task_manager.add_task(TelemetryHeartBeatTask()) - # 添加印象更新任务 - await async_task_manager.add_task(impression_update_task) - # 启动API服务器 start_api_server() logger.success("API服务器启动成功") diff --git a/src/person_info/impression_update_task.py b/src/person_info/impression_update_task.py index c3c4705ea..d6e1e2017 100644 --- a/src/person_info/impression_update_task.py +++ b/src/person_info/impression_update_task.py @@ -11,12 +11,12 @@ from collections import defaultdict logger = get_logger("relation") - +# 暂时弃用,改为实时更新 class ImpressionUpdateTask(AsyncTask): def __init__(self): super().__init__( task_name="impression_update", - wait_before_start=5, + wait_before_start=60, run_interval=global_config.relationship.build_relationship_interval, ) @@ -24,10 +24,10 @@ class ImpressionUpdateTask(AsyncTask): try: # 获取最近的消息 current_time = int(time.time()) - start_time = current_time - 600 # 1小时前 + start_time = current_time - global_config.relationship.build_relationship_interval # 100分钟前 # 获取所有消息 - messages = get_raw_msg_by_timestamp(timestamp_start=start_time, timestamp_end=current_time, limit=300) + messages = get_raw_msg_by_timestamp(timestamp_start=start_time, timestamp_end=current_time) if not messages: logger.info("没有找到需要处理的消息") @@ -45,6 +45,10 @@ class ImpressionUpdateTask(AsyncTask): # 处理每个聊天组 for chat_id, msgs in chat_messages.items(): # 获取chat_stream + if len(msgs) < 30: + logger.info(f"聊天组 {chat_id} 消息数小于30,跳过处理") + continue + chat_stream = chat_manager.get_stream(chat_id) if not chat_stream: logger.warning(f"未找到聊天组 {chat_id} 的chat_stream,跳过处理") @@ -168,7 +172,3 @@ class ImpressionUpdateTask(AsyncTask): except Exception as e: logger.exception(f"更新印象任务失败: {str(e)}") - - -# 创建任务实例 -impression_update_task = ImpressionUpdateTask() diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index 54391d15b..6e6cf80a2 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -282,8 +282,8 @@ class RelationshipManager: for original_name, mapped_name in name_mapping.items(): points = points.replace(mapped_name, original_name) - logger.info(f"prompt: {prompt}") - logger.info(f"points: {points}") + # logger.info(f"prompt: {prompt}") + # logger.info(f"points: {points}") if not points: logger.warning(f"未能从LLM获取 {person_name} 的新印象") @@ -296,6 +296,7 @@ class RelationshipManager: if points_data == "none" or not points_data or points_data.get("point") == "none": points_list = [] else: + logger.info(f"points_data: {points_data}") if isinstance(points_data, dict) and "points" in points_data: points_data = points_data["points"] if not isinstance(points_data, list): diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 6eceb7c7c..e6a177ee6 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -41,12 +41,11 @@ identity_detail = [ [expression] # 表达方式 expression_style = "描述麦麦说话的表达风格,表达习惯,例如:(回复尽量简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。不要有额外的符号,尽量简单简短)" -enable_expression_learning = false # 是否启用表达学习,麦麦会学习人类说话风格 +enable_expression_learning = false # 是否启用表达学习,麦麦会学习不同群里人类说话风格(群之间不互通) learning_interval = 600 # 学习间隔 单位秒 [relationship] -give_name = true # 麦麦是否给其他人取名,关闭后无法使用禁言功能 -build_relationship_interval = 600 # 构建关系间隔 单位秒 +give_name = true # 麦麦是否给其他人取名 [chat] #麦麦的聊天通用设置 chat_mode = "normal" # 聊天模式 —— 普通模式:normal,专注模式:focus,在普通模式和专注模式之间自动切换