From ee126a343d81792f1e198e27146eeaf9c1d1557a Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Fri, 20 Jun 2025 18:09:00 +0800 Subject: [PATCH] =?UTF-8?q?better=EF=BC=9A=E5=A4=A7=E5=A4=A7=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=87=AA=E6=88=91=E8=AE=A4=E7=9F=A5=E5=92=8C=E5=85=B3?= =?UTF-8?q?=E7=B3=BB=E5=A4=84=E7=90=86=E5=99=A8=E7=9A=84token=E6=B6=88?= =?UTF-8?q?=E8=80=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../info_processors/relationship_processor.py | 393 ++++++++---------- .../info_processors/self_processor.py | 226 ++++++++-- .../focus_chat/planners/planner_simple.py | 37 +- .../focus_chat/replyer/default_generator.py | 16 +- src/person_info/relationship_manager.py | 23 +- 5 files changed, 396 insertions(+), 299 deletions(-) diff --git a/src/chat/focus_chat/info_processors/relationship_processor.py b/src/chat/focus_chat/info_processors/relationship_processor.py index 6e856708d..c42c99790 100644 --- a/src/chat/focus_chat/info_processors/relationship_processor.py +++ b/src/chat/focus_chat/info_processors/relationship_processor.py @@ -27,11 +27,6 @@ import os import pickle -# 配置常量:是否启用小模型即时信息提取 -# 开启时:使用小模型并行即时提取,速度更快,但精度可能略低 -# 关闭时:使用原来的异步模式,精度更高但速度较慢 -ENABLE_INSTANT_INFO_EXTRACTION = True - # 消息段清理配置 SEGMENT_CLEANUP_CONFIG = { "enable_cleanup": True, # 是否启用清理 @@ -49,32 +44,26 @@ def init_prompt(): {chat_observe_info} -<调取记录> {info_cache_block} - +请不要重复调取相同的信息 {name_block} 请你阅读聊天记录,查看是否需要调取某个人的信息,这个人可以是出现在聊天记录中的,也可以是记录中提到的人。 你不同程度上认识群聊里的人,以及他们谈论到的人,你可以根据聊天记录,回忆起有关他们的信息,帮助你参与聊天 -1.你需要提供用户名,以及你想要提取的信息名称类型来进行调取 +1.你需要提供用户名和你想要提取的信息名称类型来进行调取 2.请注意,提取的信息类型一定要和用户有关,不要提取无关的信息 -3.阅读调取记录,如果已经回忆过某个人的信息,请不要重复调取,除非你忘记了 请以json格式输出,例如: {{ "用户A": "ta的昵称", "用户B": "ta对你的态度", - "用户C": "你和ta最近做的事", "用户D": "你对ta的印象", + "person_name": "其他信息", + "person_name": "其他信息", }} - -请严格按照以下输出格式,不要输出多余内容,person_name可以有多个: -{{ - "person_name": "信息名称", - "person_name": "信息名称", -}} +请严格按照json输出格式,不要输出多余内容,可以同时查询多个人的信息: """ Prompt(relationship_prompt, "relationship_prompt") @@ -83,20 +72,14 @@ def init_prompt(): {name_block} 以下是你对{person_name}的了解,请你从中提取用户的有关"{info_type}"的信息,如果用户没有相关信息,请输出none: -<对{person_name}的总体了解> -{person_impression} - - -<你记得{person_name}最近的事> -{points_text} - - +{person_impression_block} +{points_text_block} 请严格按照以下json输出格式,不要输出多余内容: {{ {info_json_str} }} """ - Prompt(fetch_info_prompt, "fetch_info_prompt") + Prompt(fetch_info_prompt, "fetch_person_info_prompt") class RelationshipProcessor(BaseProcessor): @@ -131,11 +114,10 @@ class RelationshipProcessor(BaseProcessor): ) # 小模型用于即时信息提取 - if ENABLE_INSTANT_INFO_EXTRACTION: - self.instant_llm_model = LLMRequest( - model=global_config.model.utils_small, - request_type="focus.relationship.instant", - ) + self.instant_llm_model = LLMRequest( + model=global_config.model.utils_small, + request_type="focus.relationship.instant", + ) name = get_chat_manager().get_stream_name(self.subheartflow_id) self.log_prefix = f"[{name}] " @@ -480,6 +462,7 @@ class RelationshipProcessor(BaseProcessor): for observation in observations: if isinstance(observation, ChattingObservation): chat_observe_info = observation.get_observe_info() + chat_observe_info = chat_observe_info[-300:] # 从聊天观察中提取用户信息并更新消息段 # 获取最新的非bot消息来更新消息段 @@ -536,31 +519,10 @@ class RelationshipProcessor(BaseProcessor): del self.person_engaged_cache[person_id] self._save_cache() - # 2. 减少info_fetched_cache中所有信息的TTL 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: - # 在删除前查找匹配的info_fetching_cache记录 - matched_record = None - min_time_diff = float("inf") - for record in self.info_fetching_cache: - if ( - record["person_id"] == person_id - and record["info_type"] == info_type - and not record["forget"] - ): - time_diff = abs( - record["start_time"] - self.info_fetched_cache[person_id][info_type]["start_time"] - ) - if time_diff < min_time_diff: - min_time_diff = time_diff - matched_record = record - - if matched_record: - matched_record["forget"] = True - logger.info(f"{self.log_prefix} 用户 {person_id} 的 {info_type} 信息已过期,标记为遗忘。") - del self.info_fetched_cache[person_id][info_type] if not self.info_fetched_cache[person_id]: del self.info_fetched_cache[person_id] @@ -571,11 +533,17 @@ class RelationshipProcessor(BaseProcessor): info_cache_block = "" if self.info_fetching_cache: + # 对于每个(person_id, info_type)组合,只保留最新的记录 + latest_records = {} for info_fetching in self.info_fetching_cache: - if info_fetching["forget"]: - info_cache_block += f"在{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(info_fetching['start_time']))},你回忆了[{info_fetching['person_name']}]的[{info_fetching['info_type']}],但是现在你忘记了\n" - else: - info_cache_block += f"在{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(info_fetching['start_time']))},你回忆了[{info_fetching['person_name']}]的[{info_fetching['info_type']}],还记着呢\n" + 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" prompt = (await global_prompt_manager.get_prompt_async("relationship_prompt")).format( name_block=name_block, @@ -585,7 +553,7 @@ class RelationshipProcessor(BaseProcessor): ) try: - # logger.debug(f"{self.log_prefix} 人物信息prompt: \n{prompt}\n") + logger.info(f"{self.log_prefix} 人物信息prompt: \n{prompt}\n") content, _ = await self.llm_model.generate_response_async(prompt=prompt) if content: # print(f"content: {content}") @@ -616,20 +584,12 @@ class RelationshipProcessor(BaseProcessor): logger.info(f"{self.log_prefix} 调取用户 {person_name} 的 {info_type} 信息。") - # 这里不需要检查person_engaged_cache,因为消息段的管理由_update_message_segments处理 - # 信息提取和消息段管理是独立的流程 - if ENABLE_INSTANT_INFO_EXTRACTION: - # 收集即时提取任务 - instant_tasks.append((person_id, info_type, time.time())) - else: - # 使用原来的异步模式 - async_tasks.append( - asyncio.create_task(self.fetch_person_info(person_id, [info_type], start_time=time.time())) - ) + # 收集即时提取任务 + instant_tasks.append((person_id, info_type, time.time())) # 执行即时提取任务 - if ENABLE_INSTANT_INFO_EXTRACTION and instant_tasks: + if instant_tasks: await self._execute_instant_extraction_batch(instant_tasks) # 启动异步任务(如果不是即时模式) @@ -660,31 +620,6 @@ class RelationshipProcessor(BaseProcessor): if person_infos_str: persons_infos_str += f"你对 {person_name} 的了解:{person_infos_str}\n" - # 处理正在调取但还没有结果的项目(只在非即时提取模式下显示) - if not ENABLE_INSTANT_INFO_EXTRACTION: - pending_info_dict = {} - for record in self.info_fetching_cache: - if not record["forget"]: - current_time = time.time() - # 只处理不超过2分钟的调取请求,避免过期请求一直显示 - if current_time - record["start_time"] <= 120: # 10分钟内的请求 - person_id = record["person_id"] - person_name = record["person_name"] - info_type = record["info_type"] - - # 检查是否已经在info_fetched_cache中有结果 - if person_id in self.info_fetched_cache and info_type in self.info_fetched_cache[person_id]: - continue - - # 按人物组织正在调取的信息 - if person_name not in pending_info_dict: - pending_info_dict[person_name] = [] - pending_info_dict[person_name].append(info_type) - - # 添加正在调取的信息到返回字符串 - for person_name, info_types in pending_info_dict.items(): - info_types_str = "、".join(info_types) - persons_infos_str += f"你正在识图回忆有关 {person_name} 的 {info_types_str} 信息,稍等一下再回答...\n" return persons_infos_str @@ -793,165 +728,163 @@ class RelationshipProcessor(BaseProcessor): 使用小模型提取单个信息类型 """ person_info_manager = get_person_info_manager() - nickname_str = ",".join(global_config.bot.alias_names) - name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。" - - person_name = await person_info_manager.get_value(person_id, "person_name") - - person_impression = await person_info_manager.get_value(person_id, "impression") - if not person_impression: - impression_block = "你对ta没有什么深刻的印象" - else: - impression_block = f"{person_impression}" - - points = await person_info_manager.get_value(person_id, "points") - if points: - points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points]) - else: - points_text = "你不记得ta最近发生了什么" - - prompt = (await global_prompt_manager.get_prompt_async("fetch_info_prompt")).format( - name_block=name_block, - info_type=info_type, - person_impression=impression_block, - person_name=person_name, - info_json_str=f'"{info_type}": "信息内容"', - points_text=points_text, - ) + + # 首先检查 info_list 缓存 + info_list = await person_info_manager.get_value(person_id, "info_list") or [] + cached_info = None + + print(f"info_list: {info_list}") + + # 查找对应的 info_type + for info_item in info_list: + if info_item.get("info_type") == info_type: + cached_info = info_item.get("info_content") + logger.info(f"{self.log_prefix} [缓存命中] 从 info_list 中找到 {info_type} 信息: {cached_info}") + break + + + # 如果缓存中有信息,直接使用 + if cached_info : + person_name = await person_info_manager.get_value(person_id, "person_name") + if person_id not in self.info_fetched_cache: + self.info_fetched_cache[person_id] = {} + + if cached_info == "none": + unknow = True + else: + unknow = False + + self.info_fetched_cache[person_id][info_type] = { + "info": cached_info, + "ttl": 8, + "start_time": start_time, + "person_name": person_name, + "unknow": unknow, + } + logger.info(f"{self.log_prefix} [缓存使用] 直接使用缓存的 {person_name} 的 {info_type}: {cached_info}") + return + + logger.info(f"{self.log_prefix} [缓存命中] 缓存中没有信息") + + try: + nickname_str = ",".join(global_config.bot.alias_names) + name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。" + person_name = await person_info_manager.get_value(person_id, "person_name") + person_impression = await person_info_manager.get_value(person_id, "impression") + if person_impression: + person_impression_block = f"<对{person_name}的总体了解>\n{person_impression}\n" + else: + person_impression_block = "" + + points = await person_info_manager.get_value(person_id, "points") + 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: + self.info_fetched_cache[person_id][info_type] = { + "info": "none", + "ttl": 8, + "start_time": start_time, + "person_name": person_name, + "unknow": True, + } + + prompt = (await global_prompt_manager.get_prompt_async("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, + ) + except Exception: + logger.error(traceback.format_exc()) + + print(prompt) try: # 使用小模型进行即时提取 content, _ = await self.instant_llm_model.generate_response_async(prompt=prompt) - logger.info(f"{self.log_prefix} [即时提取] {person_name} 的 {info_type} 结果: {content}") + logger.info(f"{self.log_prefix} [LLM提取] {person_name} 的 {info_type} 结果: {content}") if content: content_json = json.loads(repair_json(content)) if info_type in content_json: info_content = content_json[info_type] - if info_content != "none" and 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": info_content, - "ttl": 8, # 小模型提取的信息TTL稍短 - "start_time": start_time, - "person_name": person_name, - "unknow": False, - } - logger.info( - f"{self.log_prefix} [即时提取] 成功获取 {person_name} 的 {info_type}: {info_content}" - ) + 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": "unknow" if is_unknown else info_content, + "ttl": 8, + "start_time": start_time, + "person_name": person_name, + "unknow": is_unknown, + } + + # 保存到持久化缓存 (info_list) + await self._save_info_to_cache(person_id, info_type, info_content if not is_unknown else "none") + + if not is_unknown: + logger.info(f"{self.log_prefix} [LLM提取] 成功获取并缓存 {person_name} 的 {info_type}: {info_content}") else: - if person_id not in self.info_fetched_cache: - self.info_fetched_cache[person_id] = {} - self.info_fetched_cache[person_id][info_type] = { - "info": "unknow", - "ttl": 8, - "start_time": start_time, - "person_name": person_name, - "unknow": True, - } - logger.info(f"{self.log_prefix} [即时提取] {person_name} 的 {info_type} 信息不明确") + logger.info(f"{self.log_prefix} [LLM提取] {person_name} 的 {info_type} 信息不明确") else: - logger.warning( - f"{self.log_prefix} [即时提取] 小模型返回空结果,获取 {person_name} 的 {info_type} 信息失败。" - ) + logger.warning(f"{self.log_prefix} [LLM提取] 小模型返回空结果,获取 {person_name} 的 {info_type} 信息失败。") except Exception as e: - logger.error(f"{self.log_prefix} [即时提取] 执行小模型请求获取用户信息时出错: {e}") + logger.error(f"{self.log_prefix} [LLM提取] 执行小模型请求获取用户信息时出错: {e}") logger.error(traceback.format_exc()) - async def fetch_person_info(self, person_id: str, info_types: list[str], start_time: float): + async def _save_info_to_cache(self, person_id: str, info_type: str, info_content: str): """ - 获取某个人的信息 + 将提取到的信息保存到 person_info 的 info_list 字段中 + + Args: + person_id: 用户ID + info_type: 信息类型 + info_content: 信息内容 """ - # 检查缓存中是否已存在且未过期的信息 - info_types_to_fetch = [] - - for info_type in info_types: - if person_id in self.info_fetched_cache and info_type in self.info_fetched_cache[person_id]: - logger.info(f"{self.log_prefix} 用户 {person_id} 的 {info_type} 信息已存在且未过期,跳过调取。") - continue - info_types_to_fetch.append(info_type) - - if not info_types_to_fetch: - return - - 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 = await person_info_manager.get_value(person_id, "person_name") - - info_type_str = "" - info_json_str = "" - for info_type in info_types_to_fetch: - info_type_str += f"{info_type}," - info_json_str += f'"{info_type}": "信息内容",' - info_type_str = info_type_str[:-1] - info_json_str = info_json_str[:-1] - - person_impression = await person_info_manager.get_value(person_id, "impression") - if not person_impression: - impression_block = "你对ta没有什么深刻的印象" - else: - impression_block = f"{person_impression}" - - points = await person_info_manager.get_value(person_id, "points") - - if points: - points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points]) - else: - points_text = "你不记得ta最近发生了什么" - - prompt = (await global_prompt_manager.get_prompt_async("fetch_info_prompt")).format( - name_block=name_block, - info_type=info_type_str, - person_impression=impression_block, - person_name=person_name, - info_json_str=info_json_str, - points_text=points_text, - ) - try: - content, _ = await self.llm_model.generate_response_async(prompt=prompt) - - # logger.info(f"{self.log_prefix} fetch_person_info prompt: \n{prompt}\n") - logger.info(f"{self.log_prefix} fetch_person_info 结果: {content}") - - if content: - try: - content_json = json.loads(repair_json(content)) - for info_type, info_content in content_json.items(): - if info_content != "none" and 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": info_content, - "ttl": 10, - "start_time": start_time, - "person_name": person_name, - "unknow": False, - } - else: - if person_id not in self.info_fetched_cache: - self.info_fetched_cache[person_id] = {} - - self.info_fetched_cache[person_id][info_type] = { - "info": "unknow", - "ttl": 10, - "start_time": start_time, - "person_name": person_name, - "unknow": True, - } - except Exception as e: - logger.error(f"{self.log_prefix} 解析LLM返回的信息时出错: {e}") - logger.error(traceback.format_exc()) + 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: - logger.warning(f"{self.log_prefix} LLM返回空结果,获取用户 {person_name} 的 {info_type_str} 信息失败。") + # 添加新记录 + 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} 执行LLM请求获取用户信息时出错: {e}") + logger.error(f"{self.log_prefix} [缓存保存] 保存信息到缓存失败: {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 f53253cdd..7d8f63476 100644 --- a/src/chat/focus_chat/info_processors/self_processor.py +++ b/src/chat/focus_chat/info_processors/self_processor.py @@ -4,12 +4,14 @@ from src.llm_models.utils_model import LLMRequest from src.config.config import global_config import time import traceback +import json +import os +import hashlib from src.common.logger import get_logger -from src.individuality.individuality import get_individuality from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.message_receive.chat_stream import get_chat_manager from .base_processor import BaseProcessor -from typing import List +from typing import List, Dict from src.chat.heart_flow.observation.hfcloop_observation import HFCloopObservation from src.chat.focus_chat.info.info_base import InfoBase from src.chat.focus_chat.info.self_info import SelfInfo @@ -18,31 +20,31 @@ logger = get_logger("processor") def init_prompt(): + indentify_prompt = """ +{time_now},以下是正在进行的聊天内容: <聊天记录> {chat_observe_info} -<人格> {name_block} -你是一个AI,但是你伪装成了一个人类,不要让别人发现这一点,你的性格是: -{prompt_personality} -{indentify_block} - - -请区分聊天记录的内容和你稳定的人格,聊天记录是现在发生的事情,人格是你稳定的独特的特质。 - -{name_block} -现在请你提取你人格的关键信息,提取成一串文本: -1. 根据聊天记录,输出与聊天记录相关的自我描述,包括人格,形象等等,对人格形象进行精简 -2. 思考有没有内容与你的描述相关 -3. 如果没有明显相关内容,请输出十几个字的简短自我描述 - -现在请输出你的自我描述,格式是:“你是.....,你.................(描述)” -请注意不要输出多余内容(包括前后缀,括号(),表情包,at或 @等 ): - +请你根据以上聊天记录,思考聊天记录中是否有人提到你自己相关的信息,或者有人询问你的相关信息,例如你的性格,身高,喜好,外貌,身份,兴趣,爱好,习惯,等等。 +然后请你根据你的聊天需要,输出关键词属性在数据库中进行查询,数据库包含了关于你的所有信息,你需要直接输出你要查询的关键词,如果要输出多个,请用逗号隔开 +如果没有需要查询的内容,请输出none +现在请输出关键词,注意只输出关键词就好,不要输出其他内容: """ Prompt(indentify_prompt, "indentify_prompt") + + + fetch_info_prompt = """ + +{name_block},你的性格是: +{prompt_personality} +{indentify_block} + +请从中提取有关你的有关"{keyword}"信息,请输出原始内容,如果{bot_name}没有涉及"{keyword}"相关信息,请输出none: +""" + Prompt(fetch_info_prompt, "fetch_info_prompt") class SelfProcessor(BaseProcessor): @@ -52,14 +54,101 @@ class SelfProcessor(BaseProcessor): super().__init__() self.subheartflow_id = subheartflow_id + + self.info_fetched_cache: Dict[str, Dict[str, any]] = {} + + self.fetch_info_file_path = "data/personality/fetch_info.json" + self.meta_info_file_path = "data/personality/meta_info.json" self.llm_model = LLMRequest( - model=global_config.model.relation, + model=global_config.model.utils_small, request_type="focus.processor.self_identify", ) + name = get_chat_manager().get_stream_name(self.subheartflow_id) self.log_prefix = f"[{name}] " + + # 在初始化时检查配置是否发生变化 + self._check_config_change_and_clear() + + def _get_config_hash(self) -> str: + """获取当前personality和identity配置的哈希值""" + personality_sides = list(global_config.personality.personality_sides) + identity_detail = list(global_config.identity.identity_detail) + + # 将配置转换为字符串并排序,确保一致性 + config_str = json.dumps({ + "personality_sides": sorted(personality_sides), + "identity_detail": sorted(identity_detail) + }, sort_keys=True) + + return hashlib.md5(config_str.encode('utf-8')).hexdigest() + + def _load_meta_info(self) -> Dict[str, str]: + """从JSON文件中加载元信息""" + if os.path.exists(self.meta_info_file_path): + try: + with open(self.meta_info_file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + logger.warning(f"{self.log_prefix} 读取meta_info文件失败: {e}") + return {} + return {} + + def _save_meta_info(self, meta_info: Dict[str, str]): + """将元信息保存到JSON文件""" + try: + # 确保目录存在 + os.makedirs(os.path.dirname(self.meta_info_file_path), exist_ok=True) + with open(self.meta_info_file_path, 'w', encoding='utf-8') as f: + json.dump(meta_info, f, ensure_ascii=False, indent=2) + except Exception as e: + logger.error(f"{self.log_prefix} 保存meta_info文件失败: {e}") + + def _check_config_change_and_clear(self): + """检查配置是否发生变化,如果变化则清空fetch_info.json""" + current_config_hash = self._get_config_hash() + meta_info = self._load_meta_info() + + stored_config_hash = meta_info.get("config_hash", "") + + if current_config_hash != stored_config_hash: + logger.info(f"{self.log_prefix} 检测到personality或identity配置发生变化,清空fetch_info数据") + + # 清空fetch_info文件 + if os.path.exists(self.fetch_info_file_path): + try: + os.remove(self.fetch_info_file_path) + logger.info(f"{self.log_prefix} 已清空fetch_info文件") + except Exception as e: + logger.error(f"{self.log_prefix} 清空fetch_info文件失败: {e}") + + # 更新元信息 + meta_info["config_hash"] = current_config_hash + self._save_meta_info(meta_info) + logger.info(f"{self.log_prefix} 已更新配置哈希值") + + def _load_fetch_info_from_file(self) -> Dict[str, str]: + """从JSON文件中加载已保存的fetch_info数据""" + if os.path.exists(self.fetch_info_file_path): + try: + with open(self.fetch_info_file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + logger.warning(f"{self.log_prefix} 读取fetch_info文件失败: {e}") + return {} + return {} + + def _save_fetch_info_to_file(self, fetch_info_data: Dict[str, str]): + """将fetch_info数据保存到JSON文件""" + try: + # 确保目录存在 + os.makedirs(os.path.dirname(self.fetch_info_file_path), exist_ok=True) + with open(self.fetch_info_file_path, 'w', encoding='utf-8') as f: + json.dump(fetch_info_data, f, ensure_ascii=False, indent=2) + except Exception as e: + logger.error(f"{self.log_prefix} 保存fetch_info文件失败: {e}") async def process_info(self, observations: List[Observation] = None, *infos) -> List[InfoBase]: """处理信息对象 @@ -121,38 +210,99 @@ class SelfProcessor(BaseProcessor): nickname_str += f"{nicknames}," name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。" - personality_block = get_individuality().get_personality_prompt(x_person=2, level=2) - - identity_block = get_individuality().get_identity_prompt(x_person=2, level=2) + personality_sides_str = "你" + identity_detail_str = "你" + for personality_side in global_config.personality.personality_sides: + personality_sides_str += f"{personality_side}," + + for identity_detail in global_config.identity.identity_detail: + identity_detail_str += f"{identity_detail}," + prompt = (await global_prompt_manager.get_prompt_async("indentify_prompt")).format( name_block=name_block, - prompt_personality=personality_block, - indentify_block=identity_block, time_now=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - chat_observe_info=chat_observe_info, + chat_observe_info=chat_observe_info[-200:], + bot_name = global_config.bot.nickname ) - # print(prompt) - - content = "" + keyword = "" + try: - content, _ = await self.llm_model.generate_response_async(prompt=prompt) - if not content: + keyword, _ = await self.llm_model.generate_response_async(prompt=prompt) + + print(f"prompt: {prompt}\nkeyword: {keyword}") + + if not keyword: logger.warning(f"{self.log_prefix} LLM返回空结果,自我识别失败。") except Exception as e: # 处理总体异常 logger.error(f"{self.log_prefix} 执行LLM请求或处理响应时出错: {e}") logger.error(traceback.format_exc()) - content = "自我识别过程中出现错误" + keyword = "我是谁,我从哪来,要到哪去" + - if content == "None": - content = "" - # 记录初步思考结果 - # logger.debug(f"{self.log_prefix} 自我识别prompt: \n{prompt}\n") - logger.info(f"{self.log_prefix} 自我认知: {content}") + # keyword_json = json.loads(repair_json(keyword)) + # 根据逗号分割为list + keyword = keyword.strip() + if not keyword or keyword == "none": + keyword_set = [] + else: + # 只保留非空关键词,去除多余空格 + keyword_set = [k.strip() for k in keyword.split(",") if k.strip()] + + for keyword in keyword_set: + if keyword not in self.info_fetched_cache: + # 首先尝试从文件中读取 + fetch_info_data = self._load_fetch_info_from_file() + + if keyword in fetch_info_data: + # 从文件中获取已保存的信息 + fetched_info = fetch_info_data[keyword] + logger.info(f"{self.log_prefix} 从文件中读取到关键词 '{keyword}' 的信息") + else: + # 文件中没有,使用LLM生成 + prompt = (await global_prompt_manager.get_prompt_async("fetch_info_prompt")).format( + name_block=name_block, + prompt_personality=personality_sides_str, + indentify_block=identity_detail_str, + keyword=keyword, + bot_name = global_config.bot.nickname + ) + + print(prompt) + + fetched_info, _ = await self.llm_model.generate_response_async(prompt=prompt) + if not fetched_info: + logger.warning(f"{self.log_prefix} LLM返回空结果,自我识别失败。") + fetched_info = "" + elif fetched_info == "none": + fetched_info = "" + else: + # 保存新生成的信息到文件 + fetch_info_data[keyword] = fetched_info + self._save_fetch_info_to_file(fetch_info_data) + logger.info(f"{self.log_prefix} 新生成的关键词 '{keyword}' 信息已保存到文件") + + if fetched_info: + self.info_fetched_cache[keyword] = { + "info": fetched_info, + "ttl": 5, + } + + for fetched_keyword, info in self.info_fetched_cache.items(): + if info["ttl"] > 0: + info["ttl"] -= 1 + else: + del self.info_fetched_cache[fetched_keyword] + + + fetched_info_str = "" + for keyword, info in self.info_fetched_cache.items(): + fetched_info_str += f"你的:{keyword}信息是: {info['info']}\n" + + return fetched_info_str - return content init_prompt() diff --git a/src/chat/focus_chat/planners/planner_simple.py b/src/chat/focus_chat/planners/planner_simple.py index ea30c3257..d34966c08 100644 --- a/src/chat/focus_chat/planners/planner_simple.py +++ b/src/chat/focus_chat/planners/planner_simple.py @@ -28,15 +28,12 @@ install(extra_lines=3) def init_prompt(): Prompt( """ -你的自我认知是: -{self_info_block} -请记住你的性格,身份和特点。 - {time_block} -你是群内的一员,你现在正在参与群内的闲聊,以下是群内的聊天内容: +{indentify_block}你现在正在参与以下的聊天,以下是具体的聊天内容: {chat_content_block} +{self_info_block} {relation_info_block} {cycle_info_block} @@ -241,7 +238,7 @@ class ActionPlanner(BasePlanner): if key not in ["action", "reasoning"]: action_data[key] = value - action_data["identity"] = self_info + action_data["self_info_block"] = self_info extra_info_block = "\n".join(extra_info) extra_info_block += f"\n{structured_info}" @@ -345,21 +342,10 @@ class ActionPlanner(BasePlanner): else: chat_content_block = "你还未开始聊天" - # mind_info_block = "" - # if current_mind: - # mind_info_block = f"对聊天的规划:{current_mind}" - # else: - # mind_info_block = "你刚参与聊天" - - personality_block = get_individuality().get_prompt(x_person=2, level=2) action_options_block = "" for using_actions_name, using_actions_info in current_available_actions.items(): - # print(using_actions_name) - # print(using_actions_info) - # print(using_actions_info["parameters"]) - # print(using_actions_info["require"]) - # print(using_actions_info["description"]) + using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt") @@ -397,23 +383,26 @@ class ActionPlanner(BasePlanner): # 获取当前时间 time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + + bot_name = global_config.bot.nickname + if global_config.bot.alias_names: + bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}" + else: + bot_nickname = "" + bot_core_personality = global_config.personality.personality_core + indentify_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:" planner_prompt_template = await global_prompt_manager.get_prompt_async("simple_planner_prompt") prompt = planner_prompt_template.format( relation_info_block=relation_info_block, self_info_block=self_info_block, - # memory_str=memory_str, time_block=time_block, - # bot_name=global_config.bot.nickname, - prompt_personality=personality_block, chat_context_description=chat_context_description, chat_content_block=chat_content_block, - # mind_info_block=mind_info_block, cycle_info_block=cycle_info, action_options_text=action_options_block, - # action_available_block=action_available_block, - # extra_info_block=extra_info_block, moderation_prompt=moderation_prompt_block, + indentify_block=indentify_block, ) return prompt diff --git a/src/chat/focus_chat/replyer/default_generator.py b/src/chat/focus_chat/replyer/default_generator.py index 34b4476c9..f229922f2 100644 --- a/src/chat/focus_chat/replyer/default_generator.py +++ b/src/chat/focus_chat/replyer/default_generator.py @@ -36,6 +36,7 @@ def init_prompt(): {extra_info_block} {relation_info_block} +{self_info_block} {time_block} {chat_target} @@ -280,7 +281,7 @@ class DefaultReplyer: is_group_chat = bool(chat_stream.group_info) - identity = reply_data.get("identity", "") + self_info_block = reply_data.get("self_info_block", "") extra_info_block = reply_data.get("extra_info_block", "") relation_info_block = reply_data.get("relation_info_block", "") reply_to = reply_data.get("reply_to", "none") @@ -368,6 +369,13 @@ class DefaultReplyer: time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" # logger.debug("开始构建 focus prompt") + bot_name = global_config.bot.nickname + if global_config.bot.alias_names: + bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}" + else: + bot_nickname = "" + bot_core_personality = global_config.personality.personality_core + indentify_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:" if sender: reply_target_block = f"现在{sender}说的:{target}。引起了你的注意,你想要在群里发言或者回复这条消息。" @@ -390,10 +398,11 @@ class DefaultReplyer: chat_info=chat_talking_prompt, extra_info_block=extra_info_block, relation_info_block=relation_info_block, + self_info_block=self_info_block, time_block=time_block, reply_target_block=reply_target_block, keywords_reaction_prompt=keywords_reaction_prompt, - identity=identity, + indentify_block=indentify_block, target_message=target, sender_name=sender, config_expression_style=global_config.expression.expression_style, @@ -408,10 +417,11 @@ class DefaultReplyer: chat_info=chat_talking_prompt, extra_info_block=extra_info_block, relation_info_block=relation_info_block, + self_info_block=self_info_block, time_block=time_block, reply_target_block=reply_target_block, keywords_reaction_prompt=keywords_reaction_prompt, - identity=identity, + indentify_block=indentify_block, target_message=target, sender_name=sender, config_expression_style=global_config.expression.expression_style, diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index 3a30f4ba6..55a41f48d 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -240,6 +240,10 @@ class RelationshipManager: {{ "point": "{person_name}居然搞错了我的名字,生气了", "weight": 8 + }}, + {{ + "point": "{person_name}喜欢吃辣,我和她关系不错", + "weight": 8 }} }} @@ -259,10 +263,10 @@ class RelationshipManager: points = points.replace(mapped_name, original_name) logger.info(f"prompt: {prompt}") - logger.info(f"points: {points}") + # logger.info(f"points: {points}") if not points: - logger.warning(f"未能从LLM获取 {person_name} 的新印象") + logger.info(f"对 {person_name} 没啥新印象") return # 解析JSON并转换为元组列表 @@ -272,13 +276,20 @@ 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}") + # 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): points_data = [points_data] # 添加可读时间到每个point points_list = [(item["point"], float(item["weight"]), current_time) for item in points_data] + + + logger_str=f"了解了有关{person_name}的新印象:\n" + for point in points_list: + logger_str+=f"{point[0]},重要性:{point[1]}\n\n" + logger.info("logger_str") + except json.JSONDecodeError: logger.error(f"解析points JSON失败: {points}") return @@ -456,11 +467,15 @@ class RelationshipManager: await person_info_manager.update_one_field(person_id, "short_impression", compressed_short_summary) forgotten_points = [] + info_list = [] # 这句代码的作用是:将更新后的 forgotten_points(遗忘的记忆点)列表,序列化为 JSON 字符串后,写回到数据库中的 forgotten_points 字段 await person_info_manager.update_one_field( person_id, "forgotten_points", json.dumps(forgotten_points, ensure_ascii=False, indent=None) ) + await person_info_manager.update_one_field( + person_id, "info_list", json.dumps(info_list, ensure_ascii=False, indent=None) + ) # 更新数据库 await person_info_manager.update_one_field( @@ -482,7 +497,7 @@ class RelationshipManager: # 直接处理所有消息,不进行过滤 return build_readable_messages( - messages=messages, replace_bot_name=True, timestamp_mode="normal_no_YMD", truncate=False + messages=messages, replace_bot_name=True, timestamp_mode="normal_no_YMD", truncate=True ) def calculate_time_weight(self, point_time: str, current_time: str) -> float: