From 9fc74cb066f935bdaf1e9de9c3a1ef963933909e Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 16 Apr 2025 01:13:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2prompt=E5=86=85=E5=94=AF=E4=B8=80=E6=A0=87?= =?UTF-8?q?=E8=AF=86=E7=AC=A6=EF=BC=8C=E4=BC=98=E5=8C=96prompt=E6=95=88?= =?UTF-8?q?=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | Bin 584 -> 574 bytes src/heart_flow/observation.py | 3 +- src/heart_flow/sub_heartflow.py | 71 +++------ src/plugins/chat/message.py | 28 ++-- src/plugins/chat/message_buffer.py | 4 +- src/plugins/chat/utils.py | 138 +++++++++++++++++ .../think_flow_chat/think_flow_chat.py | 44 +++++- .../think_flow_chat/think_flow_generator.py | 72 ++------- .../think_flow_prompt_builder.py | 8 +- src/plugins/config/config.py | 6 +- src/plugins/person_info/person_info.py | 140 ++++++++++++++++++ .../person_info/relationship_manager.py | 57 +++++++ src/plugins/remote/remote.py | 1 + 13 files changed, 443 insertions(+), 129 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0fcb31f83c499ae63c79092dcb349ca3f4112a35..45fb7e6e52e77aea95193b9fc7b9d5e9530410e6 100644 GIT binary patch delta 11 ScmX@XvX5nh9MfbCCItW(4+A^^ delta 17 YcmdnTa)M=p9209ULo!3bWJxAf0516icK`qY diff --git a/src/heart_flow/observation.py b/src/heart_flow/observation.py index df78bd2db..cc225be8f 100644 --- a/src/heart_flow/observation.py +++ b/src/heart_flow/observation.py @@ -58,7 +58,8 @@ class ChattingObservation(Observation): for msg in mid_memory_by_id["messages"]: msg_str += f"{msg['detailed_plain_text']}" time_diff = int((datetime.now().timestamp() - mid_memory_by_id["created_at"]) / 60) - mid_memory_str += f"距离现在{time_diff}分钟前:\n{msg_str}\n" + # mid_memory_str += f"距离现在{time_diff}分钟前:\n{msg_str}\n" + mid_memory_str += f"{msg_str}\n" except Exception as e: logger.error(f"获取mid_memory_id失败: {e}") traceback.print_exc() diff --git a/src/heart_flow/sub_heartflow.py b/src/heart_flow/sub_heartflow.py index b9da0f7ee..c7ff4524f 100644 --- a/src/heart_flow/sub_heartflow.py +++ b/src/heart_flow/sub_heartflow.py @@ -3,8 +3,9 @@ import asyncio from src.plugins.moods.moods import MoodManager from src.plugins.models.utils_model import LLM_request from src.plugins.config.config import global_config -import re import time +from src.plugins.chat.message import UserInfo +from src.plugins.chat.utils import parse_text_timestamps # from src.plugins.schedule.schedule_generator import bot_schedule # from src.plugins.memory_system.Hippocampus import HippocampusManager @@ -37,11 +38,11 @@ def init_prompt(): prompt += "{prompt_personality}\n" prompt += "刚刚你的想法是{current_thinking_info}。可以适当转换话题\n" prompt += "-----------------------------------\n" - prompt += "现在你正在上网,和qq群里的网友们聊天,群里正在聊的话题是:{chat_observe_info}\n" + prompt += "现在是{time_now},你正在上网,和qq群里的网友们聊天,群里正在聊的话题是:\n{chat_observe_info}\n" prompt += "你现在{mood_info}\n" prompt += "你注意到{sender_name}刚刚说:{message_txt}\n" prompt += "现在你接下去继续思考,产生新的想法,不要分点输出,输出连贯的内心独白" - prompt += "思考时可以想想如何对群聊内容进行回复。回复的要求是:平淡一些,简短一些,说中文,尽量不要说你说过的话\n" + prompt += "思考时可以想想如何对群聊内容进行回复。回复的要求是:平淡一些,简短一些,说中文,尽量不要说你说过的话。如果你要回复,最好只回复一个人的一个话题\n" prompt += "请注意不要输出多余内容(包括前后缀,冒号和引号,括号, 表情,等),不要带有括号和动作描写" prompt += "记得结合上述的消息,生成内心想法,文字不要浮夸,注意你就是{bot_name},{bot_name}指的就是你。" Prompt(prompt, "sub_heartflow_prompt_before") @@ -49,7 +50,7 @@ def init_prompt(): # prompt += f"你现在正在做的事情是:{schedule_info}\n" prompt += "{extra_info}\n" prompt += "{prompt_personality}\n" - prompt += "现在你正在上网,和qq群里的网友们聊天,群里正在聊的话题是:{chat_observe_info}\n" + prompt += "现在是{time_now},你正在上网,和qq群里的网友们聊天,群里正在聊的话题是:\n{chat_observe_info}\n" prompt += "刚刚你的想法是{current_thinking_info}。" prompt += "你现在看到了网友们发的新消息:{message_new_info}\n" prompt += "你刚刚回复了群友们:{reply_info}" @@ -154,7 +155,7 @@ class SubHeartflow: await observation.observe() async def do_thinking_before_reply( - self, message_txt: str, sender_name: str, chat_stream: ChatStream, extra_info: str, obs_id: int = None + self, message_txt: str, sender_info: UserInfo, chat_stream: ChatStream, extra_info: str, obs_id: int = None ): current_thinking_info = self.current_mind mood_info = self.current_state.mood @@ -207,8 +208,10 @@ class SubHeartflow: # f"根据你和说话者{sender_name}的关系和态度进行回复,明确你的立场和情感。" # ) relation_prompt_all = (await global_prompt_manager.get_prompt_async("relationship_prompt")).format( - relation_prompt, sender_name + relation_prompt, sender_info.user_nickname ) + + sender_name_sign = f"<{chat_stream.platform}:{sender_info.user_id}:{sender_info.user_nickname}:{sender_info.user_cardname}>" # prompt = "" # # prompt += f"麦麦的总体想法是:{self.main_heartflow_info}\n\n" @@ -226,18 +229,24 @@ class SubHeartflow: # prompt += "请注意不要输出多余内容(包括前后缀,冒号和引号,括号, 表情,等),不要带有括号和动作描写" # prompt += f"记得结合上述的消息,生成内心想法,文字不要浮夸,注意你就是{self.bot_name},{self.bot_name}指的就是你。" + time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + prompt = (await global_prompt_manager.get_prompt_async("sub_heartflow_prompt_before")).format( extra_info_prompt, # prompt_schedule, relation_prompt_all, prompt_personality, current_thinking_info, + time_now, chat_observe_info, mood_info, - sender_name, + sender_name_sign, message_txt, self.bot_name, ) + + prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt) + prompt = parse_text_timestamps(prompt, mode="lite") try: response, reasoning_content = await self.llm_model.generate_response_async(prompt) @@ -285,16 +294,22 @@ class SubHeartflow: message_new_info = chat_talking_prompt reply_info = reply_content + + time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) prompt = (await global_prompt_manager.get_prompt_async("sub_heartflow_prompt_after")).format( extra_info_prompt, prompt_personality, + time_now, chat_observe_info, current_thinking_info, message_new_info, reply_info, mood_info, ) + + prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt) + prompt = parse_text_timestamps(prompt, mode="lite") try: response, reasoning_content = await self.llm_model.generate_response_async(prompt) @@ -308,48 +323,6 @@ class SubHeartflow: self.last_reply_time = time.time() - async def judge_willing(self): - # 开始构建prompt - prompt_personality = "你" - # person - individuality = Individuality.get_instance() - - personality_core = individuality.personality.personality_core - prompt_personality += personality_core - - personality_sides = individuality.personality.personality_sides - random.shuffle(personality_sides) - prompt_personality += f",{personality_sides[0]}" - - identity_detail = individuality.identity.identity_detail - random.shuffle(identity_detail) - prompt_personality += f",{identity_detail[0]}" - - # print("麦麦闹情绪了1") - current_thinking_info = self.current_mind - mood_info = self.current_state.mood - # print("麦麦闹情绪了2") - prompt = "" - prompt += f"{prompt_personality}\n" - prompt += "现在你正在上网,和qq群里的网友们聊天" - prompt += f"你现在的想法是{current_thinking_info}。" - prompt += f"你现在{mood_info}。" - prompt += "现在请你思考,你想不想发言或者回复,请你输出一个数字,1-10,1表示非常不想,10表示非常想。" - prompt += "请你用<>包裹你的回复意愿,输出<1>表示不想回复,输出<10>表示非常想回复。请你考虑,你完全可以不回复" - try: - response, reasoning_content = await self.llm_model.generate_response_async(prompt) - # 解析willing值 - willing_match = re.search(r"<(\d+)>", response) - except Exception as e: - logger.error(f"意愿判断获取失败: {e}") - willing_match = None - if willing_match: - self.current_state.willing = int(willing_match.group(1)) - else: - self.current_state.willing = 0 - - return self.current_state.willing - def update_current_mind(self, response): self.past_mind.append(self.current_mind) self.current_mind = response diff --git a/src/plugins/chat/message.py b/src/plugins/chat/message.py index 5dc688c03..9f55b5741 100644 --- a/src/plugins/chat/message.py +++ b/src/plugins/chat/message.py @@ -142,14 +142,18 @@ class MessageRecv(Message): def _generate_detailed_text(self) -> str: """生成详细文本,包含时间和用户信息""" - time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(self.message_info.time)) + # time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(self.message_info.time)) + time = self.message_info.time user_info = self.message_info.user_info + # name = ( + # f"{user_info.user_nickname}(ta的昵称:{user_info.user_cardname},ta的id:{user_info.user_id})" + # if user_info.user_cardname != None + # else f"{user_info.user_nickname}(ta的id:{user_info.user_id})" + # ) name = ( - f"{user_info.user_nickname}(ta的昵称:{user_info.user_cardname},ta的id:{user_info.user_id})" - if user_info.user_cardname != None - else f"{user_info.user_nickname}(ta的id:{user_info.user_id})" + f"<{self.message_info.platform}:{user_info.user_id}:{user_info.user_nickname}:{user_info.user_cardname}>" ) - return f"[{time_str}] {name}: {self.processed_plain_text}\n" + return f"[{time}] {name}: {self.processed_plain_text}\n" @dataclass @@ -239,14 +243,18 @@ class MessageProcessBase(Message): def _generate_detailed_text(self) -> str: """生成详细文本,包含时间和用户信息""" - time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(self.message_info.time)) + # time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(self.message_info.time)) + time = self.message_info.time user_info = self.message_info.user_info + # name = ( + # f"{user_info.user_nickname}(ta的昵称:{user_info.user_cardname},ta的id:{user_info.user_id})" + # if user_info.user_cardname != None + # else f"{user_info.user_nickname}(ta的id:{user_info.user_id})" + # ) name = ( - f"{user_info.user_nickname}(ta的昵称:{user_info.user_cardname},ta的id:{user_info.user_id})" - if user_info.user_cardname != None - else f"{user_info.user_nickname}(ta的id:{user_info.user_id})" + f"<{self.message_info.platform}:{user_info.user_id}:{user_info.user_nickname}:{user_info.user_cardname}>" ) - return f"[{time_str}] {name}: {self.processed_plain_text}\n" + return f"[{time}] {name}: {self.processed_plain_text}\n" @dataclass diff --git a/src/plugins/chat/message_buffer.py b/src/plugins/chat/message_buffer.py index f62e015b4..21e490433 100644 --- a/src/plugins/chat/message_buffer.py +++ b/src/plugins/chat/message_buffer.py @@ -153,11 +153,11 @@ class MessageBuffer: # 更新当前消息的processed_plain_text if combined_text and combined_text[0] != message.processed_plain_text and is_update: if type == "text": - message.processed_plain_text = "".join(combined_text) + message.processed_plain_text = ",".join(combined_text) logger.debug(f"整合了{len(combined_text) - 1}条F消息的内容到当前消息") elif type == "emoji": combined_text.pop() - message.processed_plain_text = "".join(combined_text) + message.processed_plain_text = ",".join(combined_text) message.is_emoji = False logger.debug(f"整合了{len(combined_text) - 1}条F消息的内容,覆盖当前emoji消息") diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index b4e2cb3c2..3b7d2fc3c 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -629,3 +629,141 @@ def count_messages_between(start_time: float, end_time: float, stream_id: str) - except Exception as e: logger.error(f"计算消息数量时出错: {str(e)}") return 0, 0 + + +def translate_timestamp_to_human_readable(timestamp: float, mode: str = "normal") -> str: + """将时间戳转换为人类可读的时间格式 + + Args: + timestamp: 时间戳 + mode: 转换模式,"normal"为标准格式,"relative"为相对时间格式 + + Returns: + str: 格式化后的时间字符串 + """ + if mode == "normal": + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + elif mode == "relative": + now = time.time() + diff = now - timestamp + + if diff < 20: + return "刚刚:" + elif diff < 60: + return f"{int(diff)}秒前:" + elif diff < 1800: + return f"{int(diff / 60)}分钟前:" + elif diff < 3600: + return f"{int(diff / 60)}分钟前:\n" + elif diff < 86400: + return f"{int(diff / 3600)}小时前:\n" + elif diff < 604800: + return f"{int(diff / 86400)}天前:\n" + else: + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + ":" + +def parse_text_timestamps(text: str, mode: str = "normal") -> str: + """解析文本中的时间戳并转换为可读时间格式 + + Args: + text: 包含时间戳的文本,时间戳应以[]包裹 + mode: 转换模式,传递给translate_timestamp_to_human_readable,"normal"或"relative" + + Returns: + str: 替换后的文本 + + 转换规则: + - normal模式: 将文本中所有时间戳转换为可读格式 + - lite模式: + - 第一个和最后一个时间戳必须转换 + - 以5秒为间隔划分时间段,每段最多转换一个时间戳 + - 不转换的时间戳替换为空字符串 + """ + # 匹配[数字]或[数字.数字]格式的时间戳 + pattern = r'\[(\d+(?:\.\d+)?)\]' + + # 找出所有匹配的时间戳 + matches = list(re.finditer(pattern, text)) + + if not matches: + return text + + # normal模式: 直接转换所有时间戳 + if mode == "normal": + result_text = text + for match in matches: + timestamp = float(match.group(1)) + readable_time = translate_timestamp_to_human_readable(timestamp, "normal") + # 由于替换会改变文本长度,需要使用正则替换而非直接替换 + pattern_instance = re.escape(match.group(0)) + result_text = re.sub(pattern_instance, readable_time, result_text, count=1) + return result_text + else: + # lite模式: 按5秒间隔划分并选择性转换 + result_text = text + + # 提取所有时间戳及其位置 + timestamps = [(float(m.group(1)), m) for m in matches] + timestamps.sort(key=lambda x: x[0]) # 按时间戳升序排序 + + if not timestamps: + return text + + # 获取第一个和最后一个时间戳 + first_timestamp, first_match = timestamps[0] + last_timestamp, last_match = timestamps[-1] + + # 将时间范围划分成5秒间隔的时间段 + time_segments = {} + + # 对所有时间戳按15秒间隔分组 + for ts, match in timestamps: + segment_key = int(ts // 15) # 将时间戳除以15取整,作为时间段的键 + if segment_key not in time_segments: + time_segments[segment_key] = [] + time_segments[segment_key].append((ts, match)) + + # 记录需要转换的时间戳 + to_convert = [] + + # 从每个时间段中选择一个时间戳进行转换 + for segment, segment_timestamps in time_segments.items(): + # 选择这个时间段中的第一个时间戳 + to_convert.append(segment_timestamps[0]) + + # 确保第一个和最后一个时间戳在转换列表中 + first_in_list = False + last_in_list = False + + for ts, match in to_convert: + if ts == first_timestamp: + first_in_list = True + if ts == last_timestamp: + last_in_list = True + + if not first_in_list: + to_convert.append((first_timestamp, first_match)) + if not last_in_list: + to_convert.append((last_timestamp, last_match)) + + # 创建需要转换的时间戳集合,用于快速查找 + to_convert_set = {match.group(0) for _, match in to_convert} + + # 首先替换所有不需要转换的时间戳为空字符串 + for ts, match in timestamps: + if match.group(0) not in to_convert_set: + pattern_instance = re.escape(match.group(0)) + result_text = re.sub(pattern_instance, "", result_text, count=1) + + # 按照时间戳原始顺序排序,避免替换时位置错误 + to_convert.sort(key=lambda x: x[1].start()) + + # 执行替换 + # 由于替换会改变文本长度,从后向前替换 + to_convert.reverse() + for ts, match in to_convert: + readable_time = translate_timestamp_to_human_readable(ts, "relative") + pattern_instance = re.escape(match.group(0)) + result_text = re.sub(pattern_instance, readable_time, result_text, count=1) + + return result_text diff --git a/src/plugins/chat_module/think_flow_chat/think_flow_chat.py b/src/plugins/chat_module/think_flow_chat/think_flow_chat.py index 2e3a74693..1e8e844eb 100644 --- a/src/plugins/chat_module/think_flow_chat/think_flow_chat.py +++ b/src/plugins/chat_module/think_flow_chat/think_flow_chat.py @@ -235,6 +235,7 @@ class ThinkFlowChat: do_reply = False if random() < reply_probability: try: + do_reply = True # 回复前处理 @@ -258,7 +259,7 @@ class ThinkFlowChat: await heartflow.get_subheartflow(chat.stream_id).do_observe() except Exception as e: logger.error(f"心流观察失败: {e}") - traceback.print_exc() + logger.error(traceback.format_exc()) info_catcher.catch_after_observe(timing_results["观察"]) @@ -329,13 +330,17 @@ class ThinkFlowChat: chat.stream_id ).do_thinking_before_reply( message_txt=message.processed_plain_text, - sender_name=message.message_info.user_info.user_nickname, + sender_info=message.message_info.user_info, chat_stream=chat, obs_id=get_mid_memory_id, extra_info=tool_result_info, ) except Exception as e: logger.error(f"心流思考前脑内状态失败: {e}") + logger.error(traceback.format_exc()) + # 确保变量被定义,即使在错误情况下 + current_mind = "" + past_mind = "" info_catcher.catch_afer_shf_step(timing_results["思考前脑内状态"], past_mind, current_mind) @@ -373,6 +378,7 @@ class ThinkFlowChat: except Exception as e: logger.error(f"心流处理表情包失败: {e}") + # 思考后脑内状态更新 try: with Timer("思考后脑内状态更新", timing_results): stream_id = message.chat_stream.stream_id @@ -387,9 +393,43 @@ class ThinkFlowChat: ) except Exception as e: logger.error(f"心流思考后脑内状态更新失败: {e}") + logger.error(traceback.format_exc()) # 回复后处理 await willing_manager.after_generate_reply_handle(message.message_info.message_id) + + # 处理认识关系 + try: + is_known = await relationship_manager.is_known_some_one( + message.message_info.platform, + message.message_info.user_info.user_id + ) + if not is_known: + logger.info(f"首次认识用户: {message.message_info.user_info.user_nickname}") + await relationship_manager.first_knowing_some_one( + message.message_info.platform, + message.message_info.user_info.user_id, + message.message_info.user_info.user_nickname, + message.message_info.user_info.user_cardname or message.message_info.user_info.user_nickname, + "" + ) + else: + logger.debug(f"已认识用户: {message.message_info.user_info.user_nickname}") + if not await relationship_manager.is_qved_name( + message.message_info.platform, + message.message_info.user_info.user_id + ): + logger.info(f"更新已认识但未取名的用户: {message.message_info.user_info.user_nickname}") + await relationship_manager.first_knowing_some_one( + message.message_info.platform, + message.message_info.user_info.user_id, + message.message_info.user_info.user_nickname, + message.message_info.user_info.user_cardname or message.message_info.user_info.user_nickname, + "" + ) + except Exception as e: + logger.error(f"处理认识关系失败: {e}") + logger.error(traceback.format_exc()) except Exception as e: logger.error(f"心流处理消息失败: {e}") diff --git a/src/plugins/chat_module/think_flow_chat/think_flow_generator.py b/src/plugins/chat_module/think_flow_chat/think_flow_generator.py index 325ecd5c6..6f6c8bf26 100644 --- a/src/plugins/chat_module/think_flow_chat/think_flow_generator.py +++ b/src/plugins/chat_module/think_flow_chat/think_flow_generator.py @@ -100,15 +100,17 @@ class ResponseGenerator: info_catcher = info_catcher_manager.get_info_catcher(thinking_id) - if message.chat_stream.user_info.user_cardname and message.chat_stream.user_info.user_nickname: - sender_name = ( - f"[({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}]" - f"{message.chat_stream.user_info.user_cardname}" - ) - elif message.chat_stream.user_info.user_nickname: - sender_name = f"({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}" - else: - sender_name = f"用户({message.chat_stream.user_info.user_id})" + # if message.chat_stream.user_info.user_cardname and message.chat_stream.user_info.user_nickname: + # sender_name = ( + # f"[({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}]" + # f"{message.chat_stream.user_info.user_cardname}" + # ) + # elif message.chat_stream.user_info.user_nickname: + # sender_name = f"({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}" + # else: + # sender_name = f"用户({message.chat_stream.user_info.user_id})" + + sender_name = f"<{message.chat_stream.user_info.platform}:{message.chat_stream.user_info.user_id}:{message.chat_stream.user_info.user_nickname}:{message.chat_stream.user_info.user_cardname}>" # 构建prompt with Timer() as t_build_prompt: @@ -119,14 +121,7 @@ class ResponseGenerator: sender_name=sender_name, stream_id=message.chat_stream.stream_id, ) - elif mode == "simple": - prompt = await prompt_builder._build_prompt_simple( - message.chat_stream, - message_txt=message.processed_plain_text, - sender_name=sender_name, - stream_id=message.chat_stream.stream_id, - ) - logger.info(f"构建{mode}prompt时间: {t_build_prompt.human_readable}") + logger.info(f"构建prompt时间: {t_build_prompt.human_readable}") try: content, reasoning_content, self.current_model_name = await model.generate_response(prompt) @@ -141,49 +136,6 @@ class ResponseGenerator: return content - async def _check_response_with_model( - self, message: MessageRecv, content: str, model: LLM_request, thinking_id: str - ) -> str: - _info_catcher = info_catcher_manager.get_info_catcher(thinking_id) - - sender_name = "" - if message.chat_stream.user_info.user_cardname and message.chat_stream.user_info.user_nickname: - sender_name = ( - f"[({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}]" - f"{message.chat_stream.user_info.user_cardname}" - ) - elif message.chat_stream.user_info.user_nickname: - sender_name = f"({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}" - else: - sender_name = f"用户({message.chat_stream.user_info.user_id})" - - # 构建prompt - with Timer() as t_build_prompt_check: - prompt = await prompt_builder._build_prompt_check_response( - message.chat_stream, - message_txt=message.processed_plain_text, - sender_name=sender_name, - stream_id=message.chat_stream.stream_id, - content=content, - ) - logger.info(f"构建check_prompt: {prompt}") - logger.info(f"构建check_prompt时间: {t_build_prompt_check.human_readable}") - - try: - checked_content, reasoning_content, self.current_model_name = await model.generate_response(prompt) - - # info_catcher.catch_after_llm_generated( - # prompt=prompt, - # response=content, - # reasoning_content=reasoning_content, - # model_name=self.current_model_name) - - except Exception: - logger.exception("检查回复时出错") - return None - - return checked_content - async def _get_emotion_tags(self, content: str, processed_plain_text: str): """提取情感标签,结合立场和情绪""" try: diff --git a/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py b/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py index ed7ca72f3..29863ba72 100644 --- a/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py +++ b/src/plugins/chat_module/think_flow_chat/think_flow_prompt_builder.py @@ -8,7 +8,8 @@ from src.common.logger import get_module_logger from ....individuality.individuality import Individuality from src.heart_flow.heartflow import heartflow from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager - +from src.plugins.person_info.relationship_manager import relationship_manager +from src.plugins.chat.utils import parse_text_timestamps logger = get_module_logger("prompt") @@ -160,7 +161,10 @@ class PromptBuilder: prompt_ger=prompt_ger, moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"), ) - + + prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt) + prompt = parse_text_timestamps(prompt, mode="lite") + return prompt async def _build_prompt_simple( diff --git a/src/plugins/config/config.py b/src/plugins/config/config.py index d0a209d35..8238078c2 100644 --- a/src/plugins/config/config.py +++ b/src/plugins/config/config.py @@ -26,9 +26,9 @@ config_config = LogConfig( logger = get_module_logger("config", config=config_config) # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 -is_test = False -mai_version_main = "0.6.2" -mai_version_fix = "" +is_test = True +mai_version_main = "0.6.3" +mai_version_fix = "snapshot-1" if mai_version_fix: if is_test: diff --git a/src/plugins/person_info/person_info.py b/src/plugins/person_info/person_info.py index 1eb1d28dd..068c37d07 100644 --- a/src/plugins/person_info/person_info.py +++ b/src/plugins/person_info/person_info.py @@ -6,6 +6,9 @@ from typing import Any, Callable, Dict import datetime import asyncio import numpy as np +from src.plugins.models.utils_model import LLM_request +from src.plugins.config.config import global_config +from src.individuality.individuality import Individuality import matplotlib @@ -13,6 +16,8 @@ matplotlib.use("Agg") import matplotlib.pyplot as plt from pathlib import Path import pandas as pd +import json +import re """ @@ -32,6 +37,8 @@ logger = get_module_logger("person_info") person_info_default = { "person_id": None, + "person_name": None, + "name_reason": None, "platform": None, "user_id": None, "nickname": None, @@ -48,16 +55,46 @@ person_info_default = { class PersonInfoManager: def __init__(self): + self.person_name_list = {} + self.qv_name_llm = LLM_request( + model=global_config.llm_normal, + max_tokens=256, + request_type="qv_name", + ) if "person_info" not in db.list_collection_names(): db.create_collection("person_info") db.person_info.create_index("person_id", unique=True) + + # 初始化时读取所有person_name + cursor = db.person_info.find( + {"person_name": {"$exists": True}}, + {"person_id": 1, "person_name": 1, "_id": 0} + ) + for doc in cursor: + if doc.get("person_name"): + self.person_name_list[doc["person_id"]] = doc["person_name"] + logger.debug(f"已加载 {len(self.person_name_list)} 个用户名称") def get_person_id(self, platform: str, user_id: int): """获取唯一id""" + #如果platform中存在-,就截取-后面的部分 + if "-" in platform: + platform = platform.split("-")[1] + components = [platform, str(user_id)] key = "_".join(components) return hashlib.md5(key.encode()).hexdigest() + def is_person_known(self, platform: str, user_id: int): + """判断是否认识某人""" + person_id = self.get_person_id(platform, user_id) + document = db.person_info.find_one({"person_id": person_id}) + if document: + return True + else: + return False + + async def create_person_info(self, person_id: str, data: dict = None): """创建一个项""" if not person_id: @@ -88,6 +125,109 @@ class PersonInfoManager: Data[field_name] = value logger.debug(f"更新时{person_id}不存在,已新建") await self.create_person_info(person_id, Data) + + async def has_one_field(self, person_id: str, field_name: str): + """判断是否存在某一个字段""" + document = db.person_info.find_one({"person_id": person_id}, {field_name: 1}) + if document: + return True + else: + return False + + def _extract_json_from_text(self, text: str) -> dict: + """从文本中提取JSON数据的高容错方法""" + try: + + # 尝试直接解析 + return json.loads(text) + except json.JSONDecodeError: + try: + # 尝试找到JSON格式的部分 + json_pattern = r'\{[^{}]*\}' + matches = re.findall(json_pattern, text) + if matches: + return json.loads(matches[0]) + + # 如果上面都失败了,尝试提取键值对 + nickname_pattern = r'"nickname"[:\s]+"([^"]+)"' + reason_pattern = r'"reason"[:\s]+"([^"]+)"' + + nickname_match = re.search(nickname_pattern, text) + reason_match = re.search(reason_pattern, text) + + if nickname_match: + return { + "nickname": nickname_match.group(1), + "reason": reason_match.group(1) if reason_match else "未提供理由" + } + except Exception as e: + logger.error(f"JSON提取失败: {str(e)}") + + # 如果所有方法都失败了,返回空结果 + return {"nickname": "", "reason": ""} + + async def qv_person_name(self, person_id: str, user_nickname: str, user_cardname: str, user_avatar: str): + """给某个用户取名""" + if not person_id: + logger.debug("取名失败:person_id不能为空") + return + + old_name = await self.get_value(person_id, "person_name") + old_reason = await self.get_value(person_id, "name_reason") + + max_retries = 5 # 最大重试次数 + current_try = 0 + existing_names = "" + while current_try < max_retries: + individuality = Individuality.get_instance() + prompt_personality = individuality.get_prompt(type="personality", x_person=2, level=1) + bot_name = individuality.personality.bot_nickname + + qv_name_prompt = f"你是{bot_name},你{prompt_personality}" + qv_name_prompt += f"现在你想给一个用户取一个昵称,用户是的qq昵称是{user_nickname}," + qv_name_prompt += f"用户的qq群昵称名是{user_cardname}," + if user_avatar: + qv_name_prompt += f"用户的qq头像是{user_avatar}," + if old_name: + qv_name_prompt += f"你之前叫他{old_name},是因为{old_reason}," + + qv_name_prompt += "\n请根据以上用户信息,想想你叫他什么比较好,请最好使用用户的qq昵称,可以稍作修改" + if existing_names: + qv_name_prompt += f"\n请注意,以下名称已被使用,不要使用以下昵称:{existing_names}。\n" + qv_name_prompt += "请用json给出你的想法,并给出理由,示例如下:" + qv_name_prompt += '''{ + "nickname": "昵称", + "reason": "理由" + }''' + logger.debug(f"取名提示词:{qv_name_prompt}") + response = await self.qv_name_llm.generate_response(qv_name_prompt) + logger.debug(f"取名回复:{response}") + result = self._extract_json_from_text(response[0]) + + if not result["nickname"]: + logger.error("生成的昵称为空,重试中...") + current_try += 1 + continue + + # 检查生成的昵称是否已存在 + if result["nickname"] not in self.person_name_list.values(): + # 更新数据库和内存中的列表 + await self.update_one_field(person_id, "person_name", result["nickname"]) + # await self.update_one_field(person_id, "nickname", user_nickname) + # await self.update_one_field(person_id, "avatar", user_avatar) + await self.update_one_field(person_id, "name_reason", result["reason"]) + + self.person_name_list[person_id] = result["nickname"] + logger.debug(f"用户 {person_id} 的名称已更新为 {result['nickname']},原因:{result['reason']}") + return result + else: + existing_names += f"{result['nickname']}、" + + logger.debug(f"生成的昵称 {result['nickname']} 已存在,重试中...") + current_try += 1 + + logger.error(f"在{max_retries}次尝试后仍未能生成唯一昵称") + return None async def del_one_document(self, person_id: str): """删除指定 person_id 的文档""" diff --git a/src/plugins/person_info/relationship_manager.py b/src/plugins/person_info/relationship_manager.py index 23ae7f6c8..673e0b07f 100644 --- a/src/plugins/person_info/relationship_manager.py +++ b/src/plugins/person_info/relationship_manager.py @@ -4,6 +4,8 @@ import math from bson.decimal128 import Decimal128 from .person_info import person_info_manager import time +import re +import traceback relationship_config = LogConfig( # 使用关系专用样式 @@ -74,6 +76,61 @@ class RelationshipManager: return mood_value * coefficient else: return mood_value / coefficient + + async def is_known_some_one(self, platform , user_id): + """判断是否认识某人""" + is_known = person_info_manager.is_person_known(platform, user_id) + return is_known + + async def is_qved_name(self, platform , user_id): + """判断是否认识某人""" + person_id = person_info_manager.get_person_id(platform, user_id) + is_qved = await person_info_manager.has_one_field(person_id, "person_name") + old_name = await person_info_manager.get_value(person_id, "person_name") + print(f"old_name: {old_name}") + print(f"is_qved: {is_qved}") + if is_qved and old_name != None: + return True + else: + return False + + async def first_knowing_some_one(self, platform , user_id, user_nickname, user_cardname, user_avatar): + """判断是否认识某人""" + person_id = person_info_manager.get_person_id(platform,user_id) + await person_info_manager.update_one_field(person_id, "nickname", user_nickname) + # await person_info_manager.update_one_field(person_id, "user_cardname", user_cardname) + # await person_info_manager.update_one_field(person_id, "user_avatar", user_avatar) + await person_info_manager.qv_person_name(person_id, user_nickname, user_cardname, user_avatar) + + async def convert_all_person_sign_to_person_name(self,input_text:str): + """将所有人的格式转换为person_name""" + try: + # 使用正则表达式匹配格式 + all_person = person_info_manager.person_name_list + + pattern = r'<([^:]+):(\d+):([^:]+):([^>]+)>' + matches = re.findall(pattern, input_text) + + # 遍历匹配结果,将替换为person_name + result_text = input_text + for platform, user_id, nickname, cardname in matches: + person_id = person_info_manager.get_person_id(platform, user_id) + # 默认使用昵称作为人名 + person_name = nickname.strip() if nickname.strip() else cardname.strip() + + if person_id in all_person: + if all_person[person_id] != None: + person_name = all_person[person_id] + + print(f"将<{platform}:{user_id}:{nickname}:{cardname}>替换为{person_name}") + + + result_text = result_text.replace(f"<{platform}:{user_id}:{nickname}:{cardname}>", person_name) + + return result_text + except Exception as e: + logger.error(traceback.format_exc()) + return input_text async def calculate_update_relationship_value(self, chat_stream: ChatStream, label: str, stance: str) -> tuple: """计算并变更关系值 diff --git a/src/plugins/remote/remote.py b/src/plugins/remote/remote.py index a2084435f..bc7437057 100644 --- a/src/plugins/remote/remote.py +++ b/src/plugins/remote/remote.py @@ -126,6 +126,7 @@ def main(): """主函数,启动心跳线程""" # 配置 SERVER_URL = "http://hyybuth.xyz:10058" + # SERVER_URL = "http://localhost:10058" HEARTBEAT_INTERVAL = 300 # 5分钟(秒) # 创建并启动心跳线程