From 82a87f492648bf896402b4fdf89e15dbcfdcd1a9 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 11 Nov 2025 23:57:33 +0800 Subject: [PATCH] =?UTF-8?q?better=EF=BC=9A=E4=BC=98=E5=8C=96planner?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E5=8F=8A=E6=97=B6=E6=B6=88=E8=80=97=E6=9B=B4?= =?UTF-8?q?=E5=B0=91=EF=BC=8C=E8=BF=9E=E7=BB=ADno=5Freply=E6=97=B6?= =?UTF-8?q?=E9=99=8D=E4=BD=8E=E6=95=8F=E6=84=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/heart_flow/heartFC_chat.py | 135 ++++++---------------------- src/chat/planner_actions/planner.py | 110 +++++++++++++++++------ 2 files changed, 113 insertions(+), 132 deletions(-) diff --git a/src/chat/heart_flow/heartFC_chat.py b/src/chat/heart_flow/heartFC_chat.py index 82c0289d..c60b8647 100644 --- a/src/chat/heart_flow/heartFC_chat.py +++ b/src/chat/heart_flow/heartFC_chat.py @@ -111,6 +111,9 @@ class HeartFChatting: self.question_probability_multiplier = 1 self.questioned = False + # 跟踪连续 no_reply 次数,用于动态调整阈值 + self.consecutive_no_reply_count = 0 + # 聊天内容概括器 self.chat_history_summarizer = ChatHistorySummarizer(chat_id=self.stream_id) @@ -192,43 +195,21 @@ class HeartFChatting: filter_command=True, ) - question_probability = 0 - if time.time() - self.last_active_time > 7200: - question_probability = 0.0003 - elif time.time() - self.last_active_time > 3600: - question_probability = 0.0001 + + + + # 根据连续 no_reply 次数动态调整阈值 + # 3次 no_reply 时,阈值调高到 1.5(50%概率为1,50%概率为2) + # 5次 no_reply 时,提高到 2(大于等于两条消息的阈值) + if self.consecutive_no_reply_count >= 5: + threshold = 2 + elif self.consecutive_no_reply_count >= 3: + # 1.5 的含义:50%概率为1,50%概率为2 + threshold = 2 if random.random() < 0.5 else 1 else: - question_probability = 0.00003 - - question_probability = question_probability * global_config.chat.get_auto_chat_value(self.stream_id) * self.question_probability_multiplier + threshold = 1 - #暂时禁用 - - # print(f"{self.log_prefix} questioned: {self.questioned},len: {len(global_conflict_tracker.get_questions_by_chat_id(self.stream_id))}") - # if question_probability > 0 and not self.questioned and len(global_conflict_tracker.get_questions_by_chat_id(self.stream_id)) == 0: #长久没有回复,可以试试主动发言,提问概率随着时间增加 - # # logger.info(f"{self.log_prefix} 长久没有回复,可以试试主动发言,概率: {question_probability}") - # if random.random() < question_probability: # 30%概率主动发言 - # try: - # self.questioned = True - # self.last_active_time = time.time() - # # print(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题") - # logger.info(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题") - # cycle_timers, thinking_id = self.start_cycle() - # question_maker = QuestionMaker(self.stream_id) - # question, context,conflict_context = await question_maker.make_question() - # if question: - # logger.info(f"{self.log_prefix} 问题: {question}") - # await global_conflict_tracker.track_conflict(question, conflict_context, True, self.stream_id) - # await self._lift_question_reply(question,context,thinking_id) - # else: - # logger.info(f"{self.log_prefix} 无问题") - # # self.end_cycle(cycle_timers, thinking_id) - # except Exception as e: - # logger.error(f"{self.log_prefix} 主动提问失败: {e}") - # print(traceback.format_exc()) - - - if len(recent_messages_list) >= 1: + if len(recent_messages_list) >= threshold: # for message in recent_messages_list: # print(message.processed_plain_text) # !处理no_reply_until_call逻辑 @@ -332,14 +313,15 @@ class HeartFChatting: available_actions: Dict[str, ActionInfo], cycle_timers: Dict[str, float], ) -> List[ActionPlannerInfo]: - """执行planner,但不包含reply动作(用于并行执行场景)""" + """执行planner,但不包含reply动作(用于并行执行场景,提及时使用简化版提示词)""" try: with Timer("规划器", cycle_timers): action_to_use_info = await self.action_planner.plan( loop_start_time=self.last_read_time, available_actions=available_actions, + is_mentioned=True, # 标记为提及时,使用简化版提示词 ) - # 过滤掉reply动作 + # 过滤掉reply动作(虽然提及时不应该有reply,但为了安全还是过滤一下) return [action for action in action_to_use_info if action.action_type != "reply"] except Exception as e: logger.error(f"{self.log_prefix} Planner执行失败: {e}") @@ -356,6 +338,8 @@ class HeartFChatting: """当被提及时,独立生成回复的任务""" try: self.questioned = False + # 重置连续 no_reply 计数 + self.consecutive_no_reply_count = 0 reason = "有人提到了你,进行回复" await database_api.store_action_info( @@ -675,76 +659,6 @@ class HeartFChatting: traceback.print_exc() return False, "" - async def _lift_question_reply(self, question: str, question_context: str, thinking_id: str): - reason = f"在聊天中:\n{question_context}\n你对问题\"{question}\"感到好奇,想要和群友讨论" - new_msg = get_raw_msg_before_timestamp_with_chat( - chat_id=self.stream_id, - timestamp=time.time(), - limit=1, - ) - - reply_action_info = ActionPlannerInfo( - action_type="reply", - reasoning= "", - action_data={}, - action_message=new_msg[0], - available_actions=None, - loop_start_time=time.time(), - action_reasoning=reason) - self.action_planner.add_plan_log(reasoning=f"你对问题\"{question}\"感到好奇,想要和群友讨论", actions=[reply_action_info]) - - success, llm_response = await generator_api.rewrite_reply( - chat_stream=self.chat_stream, - reply_data={ - "raw_reply": f"我对这个问题感到好奇:{question}", - "reason": reason, - }, - ) - - if not success or not llm_response or not llm_response.reply_set: - logger.info("主动提问发言失败") - self.action_planner.add_plan_excute_log(result="主动回复生成失败") - return {"action_type": "reply", "success": False, "result": "主动回复生成失败", "loop_info": None} - - if success: - for reply_seg in llm_response.reply_set.reply_data: - send_data = reply_seg.content - await send_api.text_to_stream( - text=send_data, - stream_id=self.stream_id, - ) - - await database_api.store_action_info( - chat_stream=self.chat_stream, - action_build_into_prompt=False, - action_prompt_display=reason, - action_done=True, - thinking_id=thinking_id, - action_data={"reply_text": llm_response.reply_set.reply_data[0].content}, - action_name="reply", - ) - - # 构建循环信息 - loop_info: Dict[str, Any] = { - "loop_plan_info": { - "action_result": [reply_action_info], - }, - "loop_action_info": { - "action_taken": True, - "reply_text": llm_response.reply_set.reply_data[0].content, - "command": "", - "taken_time": time.time(), - }, - } - self.last_active_time = time.time() - self.action_planner.add_plan_excute_log(result=f"你提问:{question}") - - return { - "action_type": "reply", - "success": True, - "result": f"你提问:{question}", - "loop_info": loop_info, - } async def _send_response( @@ -808,6 +722,9 @@ class HeartFChatting: reason = action_planner_info.reasoning or "选择不回复" # logger.info(f"{self.log_prefix} 选择不回复,原因: {reason}") + # 增加连续 no_reply 计数 + self.consecutive_no_reply_count += 1 + await database_api.store_action_info( chat_stream=self.chat_stream, action_build_into_prompt=False, @@ -827,6 +744,8 @@ class HeartFChatting: logger.info(f"{self.log_prefix} 保持沉默,直到有人直接叫的名字") reason = action_planner_info.reasoning or "选择不回复" + # 增加连续 no_reply 计数 + self.consecutive_no_reply_count += 1 self.no_reply_until_call = True await database_api.store_action_info( chat_stream=self.chat_stream, @@ -844,6 +763,8 @@ class HeartFChatting: # 直接当场执行reply逻辑 self.questioned = False # 刷新主动发言状态 + # 重置连续 no_reply 计数 + self.consecutive_no_reply_count = 0 reason = action_planner_info.reasoning or "选择回复" await database_api.store_action_info( diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 24b88057..5dfd2578 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -83,6 +83,42 @@ no_reply "planner_prompt", ) + Prompt( + """{time_block} +{name_block} +{chat_context_description},以下是具体的聊天内容 +**聊天内容** +{chat_content_block} + +**可选的action** +no_reply +动作描述: +没有合适的可以使用的动作,不使用action +{{"action":"no_reply"}} + +{action_options_text} + +**你之前的action执行和思考记录** +{actions_before_now_block} + +请选择**可选的**且符合使用条件的action,并说明触发action的消息id(消息id格式:m+数字) +先输出你的简短的选择思考理由,再输出你选择的action,理由不要分点,精简。 +**动作选择要求** +请你根据聊天内容,用户的最新消息和以下标准选择合适的动作: +1.思考**所有**的可用的action中的**每个动作**是否符合当下条件,如果动作使用条件符合聊天内容就使用 +2.如果相同的内容已经被执行,请不要重复执行 +{moderation_prompt} + +请选择所有符合使用要求的action,动作用json格式输出,用```json包裹,如果输出多个json,每个json都要单独一行放在同一个```json代码块内,你可以重复使用同一个动作或不同动作: +**示例** +// 理由文本(简短) +```json +{{"action":"动作名", "target_message_id":"m123", "reason":"原因"}} +{{"action":"动作名", "target_message_id":"m456", "reason":"原因"}} +```""", + "planner_prompt_mentioned", + ) + Prompt( """ {action_name} @@ -205,6 +241,7 @@ class ActionPlanner: self, available_actions: Dict[str, ActionInfo], loop_start_time: float = 0.0, + is_mentioned: bool = False, ) -> List[ActionPlannerInfo]: # sourcery skip: use-named-expression """ @@ -244,6 +281,11 @@ class ActionPlanner: logger.debug(f"{self.log_prefix}过滤后有{len(filtered_actions)}个可用动作") + # 如果是提及时且没有可用动作,直接返回空列表,不调用LLM以节省token + if is_mentioned and not filtered_actions: + logger.info(f"{self.log_prefix}提及时没有可用动作,跳过plan调用") + return [] + # 构建包含所有动作的提示词 prompt, message_id_list = await self.build_planner_prompt( is_group_chat=is_group_chat, @@ -252,6 +294,7 @@ class ActionPlanner: chat_content_block=chat_content_block, message_id_list=message_id_list, interest=global_config.personality.interest, + is_mentioned=is_mentioned, ) # 调用LLM获取决策 @@ -355,6 +398,7 @@ class ActionPlanner: message_id_list: List[Tuple[str, "DatabaseMessages"]], chat_content_block: str = "", interest: str = "", + is_mentioned: bool = False, ) -> tuple[str, List[Tuple[str, "DatabaseMessages"]]]: """构建 Planner LLM 的提示词 (获取模板并填充数据)""" try: @@ -367,17 +411,6 @@ class ActionPlanner: # 构建动作选项块 action_options_block = await self._build_action_options_block(current_available_actions) - # 检查是否有连续3次以上no_reply,如果有则添加no_reply_until_call选项 - no_reply_until_call_block = "" - if self._has_consecutive_no_reply(min_count=3): - no_reply_until_call_block = """no_reply_until_call -动作描述: -保持沉默,直到有人直接叫你的名字 -当前话题不感兴趣时使用,或有人不喜欢你的发言时使用 -当你频繁选择no_reply时使用,表示话题暂时与你无关 -{{"action":"no_reply_until_call"}} -""" - # 其他信息 moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。" time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" @@ -387,20 +420,47 @@ class ActionPlanner: ) name_block = f"你的名字是{bot_name}{bot_nickname},请注意哪些是你自己的发言。" - # 获取主规划器模板并填充 - planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt") - prompt = planner_prompt_template.format( - time_block=time_block, - chat_context_description=chat_context_description, - chat_content_block=chat_content_block, - actions_before_now_block=actions_before_now_block, - action_options_text=action_options_block, - no_reply_until_call_block=no_reply_until_call_block, - moderation_prompt=moderation_prompt_block, - name_block=name_block, - interest=interest, - plan_style=global_config.personality.plan_style, - ) + # 根据是否是提及时选择不同的模板 + if is_mentioned: + # 提及时使用简化版提示词,不需要reply、no_reply、no_reply_until_call + planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt_mentioned") + prompt = planner_prompt_template.format( + time_block=time_block, + chat_context_description=chat_context_description, + chat_content_block=chat_content_block, + actions_before_now_block=actions_before_now_block, + action_options_text=action_options_block, + moderation_prompt=moderation_prompt_block, + name_block=name_block, + interest=interest, + plan_style=global_config.personality.plan_style, + ) + else: + # 正常流程使用完整版提示词 + # 检查是否有连续3次以上no_reply,如果有则添加no_reply_until_call选项 + no_reply_until_call_block = "" + if self._has_consecutive_no_reply(min_count=3): + no_reply_until_call_block = """no_reply_until_call +动作描述: +保持沉默,直到有人直接叫你的名字 +当前话题不感兴趣时使用,或有人不喜欢你的发言时使用 +当你频繁选择no_reply时使用,表示话题暂时与你无关 +{{"action":"no_reply_until_call"}} +""" + + planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt") + prompt = planner_prompt_template.format( + time_block=time_block, + chat_context_description=chat_context_description, + chat_content_block=chat_content_block, + actions_before_now_block=actions_before_now_block, + action_options_text=action_options_block, + no_reply_until_call_block=no_reply_until_call_block, + moderation_prompt=moderation_prompt_block, + name_block=name_block, + interest=interest, + plan_style=global_config.personality.plan_style, + ) return prompt, message_id_list except Exception as e: