diff --git a/src/chat/heart_flow/heartFC_chat.py b/src/chat/heart_flow/heartFC_chat.py index f3b8e976..ad7de7c5 100644 --- a/src/chat/heart_flow/heartFC_chat.py +++ b/src/chat/heart_flow/heartFC_chat.py @@ -104,6 +104,8 @@ class HeartFChatting: self.is_mute = False self.last_active_time = time.time() # 记录上一次非noreply时间 + + self.questioned = False async def start(self): @@ -180,16 +182,27 @@ class HeartFChatting: filter_command=True, ) - force_reply = False - if time.time() - self.last_active_time > 300: #长久没有回复,可以试试主动发言 - print(f"{self.log_prefix} 长久没有回复,可以试试主动发言") - if random.random() < 0.02: # 30%概率主动发言 + question_probability = 0 + if time.time() - self.last_active_time > 1200: + question_probability = 0.04 + elif time.time() - self.last_active_time > 600: + question_probability = 0.02 + elif time.time() - self.last_active_time > 300: + question_probability = 0.005 + else: + question_probability = 0.001 + + if question_probability > 0 and not self.questioned and len(global_conflict_tracker.get_questions_by_chat_id(self.stream_id)) > 0: #长久没有回复,可以试试主动发言,提问概率随着时间增加 + if random.random() < question_probability: # 30%概率主动发言 print(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题") + self.last_active_time = time.time() cycle_timers, thinking_id = self.start_cycle() question_maker = QuestionMaker(self.stream_id) question, conflict_context = await question_maker.make_question() - await global_conflict_tracker.track_conflict(question, conflict_context, True, self.stream_id) - await self._lift_question_reply(question,cycle_timers,thinking_id) + if question and conflict_context: + await global_conflict_tracker.track_conflict(question, conflict_context, True, self.stream_id) + await self._lift_question_reply(question,cycle_timers,thinking_id) + self.end_cycle(cycle_timers, thinking_id) if len(recent_messages_list) >= 1: @@ -703,6 +716,8 @@ class HeartFChatting: elif action_planner_info.action_type == "reply": # 直接当场执行reply逻辑 + self.questioned = False + # 刷新主动发言状态 reason = action_planner_info.reasoning or "选择回复" await database_api.store_action_info( diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index 4dc8171a..0efdfb7b 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -324,6 +324,7 @@ class MemoryChest(BaseModel): title = TextField() # 标题 content = TextField() # 内容 + chat_id = TextField(null=True) # 聊天ID locked = BooleanField(default=False) # 是否锁定 class Meta: @@ -339,6 +340,7 @@ class MemoryConflict(BaseModel): create_time = FloatField() # 创建时间 update_time = FloatField() # 更新时间 context = TextField(null=True) # 上下文 + chat_id = TextField(null=True) # 聊天ID class Meta: table_name = "memory_conflicts" diff --git a/src/memory_system/Memory_chest.py b/src/memory_system/Memory_chest.py index 7062f73a..78c967d5 100644 --- a/src/memory_system/Memory_chest.py +++ b/src/memory_system/Memory_chest.py @@ -13,10 +13,12 @@ from src.plugin_system.apis.message_api import build_readable_messages from src.plugin_system.apis.message_api import get_raw_msg_by_timestamp_with_chat from json_repair import repair_json from src.memory_system.questions import global_conflict_tracker + from .memory_utils import ( find_best_matching_memory, check_title_exists_fuzzy, get_all_titles, + get_memory_titles_by_chat_id_weighted, ) @@ -306,7 +308,6 @@ class MemoryChest: title, (reasoning_content, model_name, tool_calls) = await self.LLMRequest.generate_response_async(prompt) # 根据 title 获取 titles 里的对应项 - titles = get_all_titles() selected_title = None # 使用模糊查找匹配标题 @@ -379,7 +380,8 @@ class MemoryChest: # 保存到数据库 MemoryChestModel.create( title=title.strip(), - content=content + content=content, + chat_id=chat_id ) logger.info(f"已保存记忆仓库内容,标题: {title.strip()}, chat_id: {chat_id}") @@ -393,24 +395,26 @@ class MemoryChest: except Exception as e: logger.error(f"保存记忆仓库内容时出错: {e}") - async def choose_merge_target(self, memory_title: str) -> list[str]: + async def choose_merge_target(self, memory_title: str, chat_id: str = None) -> list[str]: """ 选择与给定记忆标题相关的记忆目标 Args: memory_title: 要匹配的记忆标题 + chat_id: 聊天ID,用于加权抽样 Returns: list[str]: 选中的记忆内容列表 """ try: - all_titles = get_all_titles(exclude_locked=True) + # 如果提供了chat_id,使用加权抽样 + all_titles = get_memory_titles_by_chat_id_weighted(chat_id) + # 剔除掉输入的 memory_title 本身 + all_titles = [title for title in all_titles if title and title.strip() != (memory_title or "").strip()] + content = "" display_index = 1 for title in all_titles: - # 剔除掉输入的 memory_title 本身 - if title and title.strip() == (memory_title or "").strip(): - continue content += f"{display_index}. {title}\n" display_index += 1 @@ -615,7 +619,7 @@ class MemoryChest: logger.error(f"解析合并目标JSON时出错: {e}") return [] - async def merge_memory(self,memory_list: list[str]) -> tuple[str, str]: + async def merge_memory(self,memory_list: list[str], chat_id: str = None) -> tuple[str, str]: """ 合并记忆 """ @@ -670,7 +674,7 @@ class MemoryChest: if part2_content and part2_content.strip() != "none": logger.info(f"合并记忆part2记录冲突内容: {len(part2_content)} 字符") # 记录冲突到数据库 - await global_conflict_tracker.record_memory_merge_conflict(part2_content) + await global_conflict_tracker.record_memory_merge_conflict(part2_content,chat_id) # 处理part1:生成标题并保存 if part1_content and part1_content.strip() != "none": @@ -679,7 +683,8 @@ class MemoryChest: # 保存part1到数据库 MemoryChestModel.create( title=merged_title, - content=part1_content + content=part1_content, + chat_id=chat_id ) logger.info(f"合并记忆part1已保存: {merged_title}") diff --git a/src/memory_system/memory_management_task.py b/src/memory_system/memory_management_task.py index 09e90a71..b801e9cf 100644 --- a/src/memory_system/memory_management_task.py +++ b/src/memory_system/memory_management_task.py @@ -97,14 +97,14 @@ class MemoryManagementTask(AsyncTask): if current_count < 10: return - # 随机选择一个记忆标题 - selected_title = self._get_random_memory_title() + # 随机选择一个记忆标题和chat_id + selected_title, selected_chat_id = self._get_random_memory_title() if not selected_title: logger.warning("无法获取随机记忆标题,跳过执行") return # 执行choose_merge_target获取相关记忆(标题与内容) - related_titles, related_contents = await global_memory_chest.choose_merge_target(selected_title) + related_titles, related_contents = await global_memory_chest.choose_merge_target(selected_title, selected_chat_id) if not related_titles or not related_contents: logger.info("无合适合并内容,跳过本次合并") return @@ -127,21 +127,21 @@ class MemoryManagementTask(AsyncTask): except Exception as e: logger.error(f"[记忆管理] 执行记忆管理任务时发生错误: {e}", exc_info=True) - def _get_random_memory_title(self) -> str: - """随机获取一个记忆标题""" + def _get_random_memory_title(self) -> tuple[str, str]: + """随机获取一个记忆标题和对应的chat_id""" try: - # 获取所有记忆标题 - all_titles = get_all_titles() - if not all_titles: - return "" + # 获取所有记忆记录 + all_memories = MemoryChestModel.select() + if not all_memories: + return "", "" - # 随机选择一个标题 - selected_title = random.choice(all_titles) - return selected_title + # 随机选择一个记忆 + selected_memory = random.choice(list(all_memories)) + return selected_memory.title, selected_memory.chat_id or "" except Exception as e: logger.error(f"[记忆管理] 获取随机记忆标题时发生错误: {e}") - return "" + return "", "" def _delete_original_memories(self, related_titles: List[str]) -> int: """按标题删除原始记忆""" diff --git a/src/memory_system/memory_utils.py b/src/memory_system/memory_utils.py index c9297691..bdf8c2d3 100644 --- a/src/memory_system/memory_utils.py +++ b/src/memory_system/memory_utils.py @@ -218,3 +218,89 @@ def check_title_exists_fuzzy(target_title: str, similarity_threshold: float = 0. except Exception as e: logger.error(f"检查标题是否存在时出错: {e}") return False + + +def get_memories_by_chat_id_weighted(target_chat_id: str, same_chat_weight: float = 0.95, other_chat_weight: float = 0.05) -> List[Tuple[str, str, str]]: + """ + 根据chat_id进行加权抽样获取记忆列表 + + Args: + target_chat_id: 目标聊天ID + same_chat_weight: 同chat_id记忆的权重,默认0.95(95%概率) + other_chat_weight: 其他chat_id记忆的权重,默认0.05(5%概率) + + Returns: + List[Tuple[str, str, str]]: 选中的记忆列表,每个元素为(title, content, chat_id) + """ + try: + # 获取所有记忆 + all_memories = MemoryChestModel.select() + + # 按chat_id分组 + same_chat_memories = [] + other_chat_memories = [] + + for memory in all_memories: + if memory.title and not memory.locked: # 排除锁定的记忆 + if memory.chat_id == target_chat_id: + same_chat_memories.append((memory.title, memory.content, memory.chat_id)) + else: + other_chat_memories.append((memory.title, memory.content, memory.chat_id)) + + # 如果没有同chat_id的记忆,返回空列表 + if not same_chat_memories: + logger.warning(f"未找到chat_id为 '{target_chat_id}' 的记忆") + return [] + + # 计算抽样数量 + total_same = len(same_chat_memories) + total_other = len(other_chat_memories) + + # 根据权重计算抽样数量 + if total_other > 0: + # 计算其他chat_id记忆的抽样数量(至少1个,最多不超过总数的10%) + other_sample_count = max(1, min(total_other, int(total_same * other_chat_weight / same_chat_weight))) + else: + other_sample_count = 0 + + # 随机抽样 + selected_memories = [] + + # 选择同chat_id的记忆(全部选择,因为权重很高) + selected_memories.extend(same_chat_memories) + + # 随机选择其他chat_id的记忆 + if other_sample_count > 0 and total_other > 0: + import random + other_selected = random.sample(other_chat_memories, min(other_sample_count, total_other)) + selected_memories.extend(other_selected) + + logger.info(f"加权抽样结果: 同chat_id记忆 {len(same_chat_memories)} 条,其他chat_id记忆 {min(other_sample_count, total_other)} 条") + + return selected_memories + + except Exception as e: + logger.error(f"按chat_id加权抽样记忆时出错: {e}") + return [] + + +def get_memory_titles_by_chat_id_weighted(target_chat_id: str, same_chat_weight: float = 0.95, other_chat_weight: float = 0.05) -> List[str]: + """ + 根据chat_id进行加权抽样获取记忆标题列表(用于合并选择) + + Args: + target_chat_id: 目标聊天ID + same_chat_weight: 同chat_id记忆的权重,默认0.95(95%概率) + other_chat_weight: 其他chat_id记忆的权重,默认0.05(5%概率) + + Returns: + List[str]: 选中的记忆标题列表 + """ + try: + memories = get_memories_by_chat_id_weighted(target_chat_id, same_chat_weight, other_chat_weight) + titles = [memory[0] for memory in memories] # 提取标题 + return titles + + except Exception as e: + logger.error(f"按chat_id加权抽样记忆标题时出错: {e}") + return [] \ No newline at end of file diff --git a/src/memory_system/question_maker.py b/src/memory_system/question_maker.py index 3fe18d85..6dc82874 100644 --- a/src/memory_system/question_maker.py +++ b/src/memory_system/question_maker.py @@ -26,7 +26,7 @@ class QuestionMaker: async def get_all_conflicts(self): - conflicts = list(MemoryConflict.select()) + conflicts = list(MemoryConflict.select().where(MemoryConflict.chat_id == self.chat_id)) return conflicts async def get_un_answered_conflict(self): @@ -35,10 +35,14 @@ class QuestionMaker: async def get_random_unanswered_conflict(self): conflicts = await self.get_un_answered_conflict() + if not conflicts: + return None return random.choice(conflicts) async def make_question(self): conflict = await self.get_random_unanswered_conflict() + if not conflict: + return None, None question = conflict.conflict_content conflict_context = conflict.context chat_context = self.get_context() diff --git a/src/memory_system/questions.py b/src/memory_system/questions.py index 72367809..f87e8011 100644 --- a/src/memory_system/questions.py +++ b/src/memory_system/questions.py @@ -165,6 +165,7 @@ class ConflictTracker: create_time=time.time(), update_time=time.time(), answer="", + chat_id=chat_id, ) logger.info(f"记录冲突内容: {len(conflict_content)} 字符") @@ -278,6 +279,7 @@ class ConflictTracker: create_time=time.time(), update_time=time.time(), answer="", + chat_id=tracker.chat_id, ) logger.info(f"记录冲突内容(未解答): {len(original_question)} 字符") logger.info(f"问题跟踪结束:{original_question}") @@ -304,7 +306,15 @@ class ConflictTracker: except Exception as e: logger.error(f"移除追踪器时出错: {e}") - async def add_or_update_conflict(self,conflict_content: str,create_time: float,update_time: float,answer: str = "",context: str = "") -> bool: + async def add_or_update_conflict( + self, + conflict_content: str, + create_time: float, + update_time: float, + answer: str = "", + context: str = "", + chat_id: str = None + ) -> bool: """ 根据conflict_content匹配数据库内容,如果找到相同的就更新update_time和answer, 如果没有相同的,就新建一条保存全部内容 @@ -312,7 +322,8 @@ class ConflictTracker: try: # 尝试根据conflict_content查找现有记录 existing_conflict = MemoryConflict.get_or_none( - MemoryConflict.conflict_content == conflict_content + MemoryConflict.conflict_content == conflict_content, + MemoryConflict.chat_id == chat_id ) if existing_conflict: @@ -329,6 +340,7 @@ class ConflictTracker: update_time=update_time, answer=answer, context=context, + chat_id=chat_id, ) return True except Exception as e: @@ -336,7 +348,7 @@ class ConflictTracker: logger.error(f"添加或更新冲突记录时出错: {e}") return False - async def record_memory_merge_conflict(self, part2_content: str) -> bool: + async def record_memory_merge_conflict(self, part2_content: str, chat_id: str = None) -> bool: """ 记录记忆整合过程中的冲突内容(part2) @@ -386,6 +398,7 @@ class ConflictTracker: conflict_content=question["question"], context=reasoning_content, start_following=False, + chat_id=chat_id, ) return True diff --git a/src/plugins/built_in/MaiCurious/plugin.py b/src/plugins/built_in/MaiCurious/plugin.py index fa984a03..c5df4826 100644 --- a/src/plugins/built_in/MaiCurious/plugin.py +++ b/src/plugins/built_in/MaiCurious/plugin.py @@ -52,7 +52,7 @@ class CuriousAction(BaseAction): async def execute(self) -> Tuple[bool, str]: """执行频率调节动作""" try: - if len(global_conflict_tracker.question_tracker_list) > 3: + if len(global_conflict_tracker.question_tracker_list) > 1: return False, "当前有太多问题,请先解答完再提问,不要再使用make_question动作" question = self.action_data.get("question", "")