diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 3b571cd4..8d16c04f 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -842,21 +842,19 @@ class Conversation: action_successful: bool = False # 标记动作是否成功执行 final_status: str = "recall" # 动作最终状态,默认为 recall (表示未成功或需重试) final_reason: str = "动作未成功执行" # 动作最终原因 - need_replan_from_checker: bool = False # 标记是否由 ReplyChecker 要求重新规划 + # need_replan_from_checker: bool = False # 这个变量在循环内部被赋值 try: # --- 根据不同的 action 类型执行相应的逻辑 --- # 1. 处理需要生成、检查、发送的动作 if action in ["direct_reply", "send_new_message"]: - max_reply_attempts: int = 3 # 最多尝试次数 (可配置) + max_reply_attempts: int = global_config.get("pfc_max_reply_attempts", 3) # 最多尝试次数 (可配置) reply_attempt_count: int = 0 is_suitable: bool = False # 标记回复是否合适 generated_content: str = "" # 存储生成的回复 check_reason: str = "未进行检查" # 存储检查结果原因 - - # --- [核心修复] 引入重试循环 --- - is_send_decision_from_rg = False # 标记是否由 reply_generator 决定发送 + need_replan_from_checker: bool = False # 在循环前初始化 while reply_attempt_count < max_reply_attempts and not is_suitable and not need_replan_from_checker: reply_attempt_count += 1 @@ -878,12 +876,6 @@ class Conversation: if action == "send_new_message": is_send_decision_from_rg = True # 标记 send_new_message 的决策来自RG try: - # 使用 pfc_utils.py 中的 get_items_from_json 来解析 - # 注意:get_items_from_json 目前主要用于提取固定字段的字典。 - # reply_generator 返回的是一个顶级JSON对象。 - # 我们需要稍微调整用法或增强 get_items_from_json。 - # 简单起见,这里我们先直接用 json.loads,后续可以优化。 - parsed_json = None try: parsed_json = json.loads(raw_llm_output) @@ -923,17 +915,24 @@ class Conversation: generated_content_for_check_or_send = text_to_process - if not generated_content_for_check_or_send or generated_content_for_check_or_send.startswith("抱歉") or (action == "send_new_message" and generated_content_for_check_or_send == "no"): - logger.warning(f"{log_prefix} 生成内容无效或为错误提示 (或send:no),将进行下一次尝试 (如果适用)。") - check_reason = "生成内容无效或选择不发送" + if not generated_content_for_check_or_send or \ + generated_content_for_check_or_send.startswith("抱歉") or \ + generated_content_for_check_or_send.strip() == "" or \ + (action == "send_new_message" and generated_content_for_check_or_send == "no" and should_send_reply): # should_send_reply is true here means RG said yes, but txt is "no" or empty + + warning_msg = f"{log_prefix} 生成内容无效或为错误提示" + if action == "send_new_message" and generated_content_for_check_or_send == "no": + warning_msg += " (ReplyGenerator决定发送但文本为'no')" + + logger.warning(warning_msg + ",将进行下一次尝试 (如果适用)。") + check_reason = "生成内容无效或选择不发送" # 统一原因 conversation_info.last_reply_rejection_reason = check_reason conversation_info.last_rejected_reply_content = generated_content_for_check_or_send - if action == "direct_reply": # direct_reply 失败时才继续尝试 - await asyncio.sleep(0.5) - continue - else: # send_new_message 如果是 no,不应该继续尝试,上面已经break了 - pass # 理论上不会到这里如果上面break了 - + + # if reply_attempt_count < max_reply_attempts: # 只有在还有尝试次数时才继续 + await asyncio.sleep(0.5) # 暂停一下 + continue # 直接进入下一次循环尝试 + self.state = ConversationState.CHECKING if not self.reply_checker: raise RuntimeError("ReplyChecker 未初始化") @@ -973,10 +972,30 @@ class Conversation: if not is_suitable: conversation_info.last_reply_rejection_reason = check_reason conversation_info.last_rejected_reply_content = generated_content_for_check_or_send - if not need_replan_from_checker and reply_attempt_count < max_reply_attempts: + + # 如果是我们特定的复读原因,并且 checker 说不需要重新规划 (这是我们的新逻辑) + if check_reason == "机器人尝试发送重复消息" and not need_replan_from_checker: + logger.warning(f"{log_prefix} 回复因自身重复被拒绝: {check_reason}。将使用相同 Prompt 类型重试。") + # 不需要额外操作,循环会自动继续,因为 is_suitable=False 且 need_replan_from_checker=False + if reply_attempt_count < max_reply_attempts: # 还有尝试次数 + await asyncio.sleep(0.5) # 暂停一下 + continue # 进入下一次重试 + else: # 达到最大次数 + logger.warning(f"{log_prefix} 即使是复读,也已达到最大尝试次数。") + break # 结束循环,按失败处理 + elif not need_replan_from_checker and reply_attempt_count < max_reply_attempts: # 其他不合适原因,但无需重规划,且可重试 logger.warning(f"{log_prefix} 回复不合适,原因: {check_reason}。将进行下一次尝试。") - await asyncio.sleep(0.5) - # 如果需要重规划或达到最大次数,循环会在下次判断时自动结束 + await asyncio.sleep(0.5) # 暂停一下 + continue # 进入下一次重试 + else: # 需要重规划,或达到最大次数 + logger.warning(f"{log_prefix} 回复不合适且(需要重规划或已达最大次数)。原因: {check_reason}") + break # 结束循环,将在循环外部处理 + else: # is_suitable is True + # 找到了合适的回复 + conversation_info.last_reply_rejection_reason = None # 清除之前的拒绝原因 + conversation_info.last_rejected_reply_content = None + break # 成功,跳出循环 + # --- 循环结束后处理 --- if action == "send_new_message" and not should_send_reply and is_send_decision_from_rg: @@ -1013,17 +1032,11 @@ class Conversation: logger.info(f"[私聊][{self.private_name}] 反思后的新规划动作: {new_action}, 原因: {new_reason}") # **暂定方案:** - # _handle_action 在这种情况下返回一个特殊标记。 - # 为了不立即修改返回类型,我们暂时在这里记录日志,并在 _plan_and_action_loop 中添加逻辑。 - # _handle_action 会将 action_successful 设置为 True,final_status 为 "done_no_reply"。 - # _plan_and_action_loop 之后会检查这个状态。 - - # (这里的 final_status, final_reason, action_successful 已在上面设置) - + elif is_suitable: # 适用于 direct_reply 或 send_new_message (RG决定发送且检查通过) logger.info(f"[私聊][{self.private_name}] 动作 '{action}': 找到合适的回复,准备发送。") - conversation_info.last_reply_rejection_reason = None - conversation_info.last_rejected_reply_content = None + # conversation_info.last_reply_rejection_reason = None # 已在循环内清除 + # conversation_info.last_rejected_reply_content = None self.generated_reply = generated_content_for_check_or_send timestamp_before_sending = time.time() logger.debug( @@ -1035,6 +1048,8 @@ class Conversation: if send_success: action_successful = True + final_status = "done" # 明确设置 final_status + final_reason = "成功发送" # 明确设置 final_reason logger.info(f"[私聊][{self.private_name}] 动作 '{action}': 成功发送回复.") if self.idle_conversation_starter: await self.idle_conversation_starter.update_last_message_time(send_end_time) @@ -1103,6 +1118,7 @@ class Conversation: logger.error(f"[私聊][{self.private_name}] 动作 '{action}': 发送回复失败。") final_status = "recall" final_reason = "发送回复时失败" + action_successful = False # 确保 action_successful 为 False conversation_info.last_successful_reply_action = None conversation_info.my_message_count = 0 # 发送失败,重置计数 @@ -1121,6 +1137,7 @@ class Conversation: ) final_status = "recall" final_reason = f"尝试{max_reply_attempts}次后失败: {check_reason}" + action_successful = False conversation_info.last_successful_reply_action = None # my_message_count 保持不变 @@ -1335,58 +1352,68 @@ class Conversation: # 2. 更新动作历史记录的最终状态和原因 # 优化:如果动作成功但状态仍是默认的 recall,则更新为 done - if final_status == "recall" and action_successful: - final_status = "done" - # 根据动作类型设置更具体的成功原因 - if action == "wait": - # 检查是否是因为超时结束的(需要 waiter 返回值,或者检查 goal_list) - # 这里简化处理,直接使用通用成功原因 - timeout_occurred = ( - any("分钟," in g.get("goal", "") for g in conversation_info.goal_list if isinstance(g, dict)) - if conversation_info.goal_list - else False - ) - final_reason = "等待完成" + (" (超时)" if timeout_occurred else " (收到新消息或中断)") - elif action == "listening": - final_reason = "进入倾听状态" - elif action in ["rethink_goal", "end_conversation", "block_and_ignore"]: - final_reason = f"成功执行 {action}" - elif action in ["direct_reply", "send_new_message", "say_goodbye"]: - # 如果是因为发送成功,设置原因 - final_reason = "成功发送" - else: - # 其他未知但标记成功的动作 - final_reason = "动作成功完成" + if action_successful: + # 如果动作标记为成功,但 final_status 仍然是初始的 "recall" 或者 "start" + # (因为可能在try块中成功执行了但没有显式更新 final_status 为 "done") + # 或者是 "done_no_reply" 这种特殊的成功状态 + if final_status in ["recall", "start"] and action != "send_new_message": # send_new_message + no_reply 是特殊成功 + final_status = "done" + if not final_reason or final_reason == "动作未成功执行": + # 为不同类型的成功动作提供更具体的默认成功原因 + if action == "wait": + timeout_occurred = ( + any("分钟," in g.get("goal", "") for g in conversation_info.goal_list if isinstance(g, dict)) + if conversation_info.goal_list + else False + ) + final_reason = "等待完成" + (" (超时)" if timeout_occurred else " (收到新消息或中断)") + elif action == "listening": + final_reason = "进入倾听状态" + elif action in ["rethink_goal", "end_conversation", "block_and_ignore", "say_goodbye"]: + final_reason = f"成功执行 {action}" + elif action in ["direct_reply", "send_new_message"]: # 正常发送成功的case + final_reason = "成功发送" + else: + final_reason = f"动作 {action} 成功完成" + # 如果已经是 "done" 或 "done_no_reply",则保留它们和它们对应的 final_reason + + else: # action_successful is False + # 如果动作标记为失败,且 final_status 还是 "recall" (初始值) 或 "start" + if final_status in ["recall", "start"]: + # 尝试从 conversation_info 中获取更具体的失败原因(例如 checker 的原因) + # 这个 specific_rejection_reason 是在 try 块中被设置的 + specific_rejection_reason = getattr(conversation_info, 'last_reply_rejection_reason', None) + rejected_content = getattr(conversation_info, 'last_rejected_reply_content', None) - elif final_status == "recall" and not action_successful: - # 如果最终是 recall 且未成功,且不是因为检查不通过(比如生成失败),确保原因合理 - # 保留之前的逻辑,检查是否已有更具体的失败原因 - if not final_reason or final_reason == "动作未成功执行": - # 检查是否有 checker 的原因 - checker_reason = conversation_info.last_reply_rejection_reason - if checker_reason: - final_reason = f"回复检查不通过: {checker_reason}" - else: - final_reason = "动作执行失败或被取消" # 通用失败原因 + if specific_rejection_reason: + final_reason = f"执行失败: {specific_rejection_reason}" + if rejected_content and specific_rejection_reason == "机器人尝试发送重复消息": # 对复读提供更清晰的日志 + final_reason += f" (内容: '{rejected_content[:30]}...')" + elif not final_reason or final_reason == "动作未成功执行": # 如果没有更具体的原因 + final_reason = f"动作 {action} 执行失败或被意外中止" + # 如果 final_status 已经是 "error" 或 "cancelled",则保留它们和它们对应的 final_reason - # 更新历史记录字典 + # 更新 done_action 中的记录 if conversation_info.done_action and action_index < len(conversation_info.done_action): - # 使用 update 方法更新字典,更安全 + # 确保 current_action_record 是最新的(尽管我们是更新它) + # 实际上我们是更新 list 中的元素 conversation_info.done_action[action_index].update( { - "status": final_status, # 最终状态 - "time_completed": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 完成时间 - "final_reason": final_reason, # 最终原因 - "duration_ms": int((time.time() - action_start_time) * 1000), # 记录耗时(毫秒) + "status": final_status, + "time_completed": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "final_reason": final_reason, + "duration_ms": int((time.time() - action_start_time) * 1000), } ) - logger.debug( - f"[私聊][{self.private_name}] 动作 '{action}' 最终状态: {final_status}, 原因: {final_reason}" - ) else: - # 如果索引无效或列表为空,记录错误 logger.error(f"[私聊][{self.private_name}] 无法更新动作历史记录,索引 {action_index} 无效或列表为空。") + + # 最终日志输出 + log_final_reason = final_reason if final_reason else "无明确原因" + if final_status == "done" and action_successful and action in ["direct_reply", "send_new_message"] and hasattr(self, 'generated_reply'): + log_final_reason += f" (发送内容: '{self.generated_reply[:30]}...')" + logger.info(f"[私聊][{self.private_name}] 动作 '{action}' 处理完成。最终状态: {final_status}, 原因: {log_final_reason}") async def _send_reply(self) -> bool: """发送 `self.generated_reply` 中的内容到聊天流""" # 检查是否有内容可发送 diff --git a/src/plugins/PFC/reply_checker.py b/src/plugins/PFC/reply_checker.py index a07dc54f..a238a110 100644 --- a/src/plugins/PFC/reply_checker.py +++ b/src/plugins/PFC/reply_checker.py @@ -1,193 +1,79 @@ + import json from typing import Tuple, List, Dict, Any from src.common.logger import get_module_logger -from ..models.utils_model import LLMRequest -from ...config.config import global_config +# LLMRequest 和 global_config 不再需要直接在此文件中使用(除非 ReplyChecker 以后有其他功能) +# from ..models.utils_model import LLMRequest # <--- 移除 +# from ...config.config import global_config # <--- 移除,但下面会用到 bot_id +from ...config.config import global_config # 为了获取 BOT_QQ from .chat_observer import ChatObserver -from maim_message import UserInfo +from maim_message import UserInfo # 保持,可能用于未来扩展,但当前逻辑不直接使用 logger = get_module_logger("reply_checker") class ReplyChecker: - """回复检查器""" + """回复检查器 - 新版:仅检查机器人自身发言的精确重复""" def __init__(self, stream_id: str, private_name: str): - self.llm = LLMRequest( - model=global_config.llm_PFC_reply_checker, temperature=0.50, max_tokens=1000, request_type="reply_check" - ) + # self.llm = LLMRequest(...) # <--- 移除 LLM 初始化 self.name = global_config.BOT_NICKNAME self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id, private_name) - self.max_retries = 3 # 最大重试次数 + # self.max_retries = 3 # 这个 max_retries 属性在当前设计下不再由 checker 控制,而是由 conversation.py 控制 + self.bot_qq_str = str(global_config.BOT_QQ) # 获取机器人QQ号用于识别自身消息 async def check( - self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_text: str,current_time_str: str, retry_count: int = 0 + self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_text: str, current_time_str: str, retry_count: int = 0 ) -> Tuple[bool, str, bool]: - """检查生成的回复是否合适 + """检查生成的回复是否与机器人之前的发言完全一致(长度大于4) Args: - reply: 生成的回复 - goal: 对话目标 - chat_history: 对话历史记录 - chat_history_text: 对话历史记录文本 - retry_count: 当前重试次数 + reply: 待检查的机器人回复内容 + goal: 当前对话目标 (新逻辑中未使用) + chat_history: 对话历史记录 (包含用户和机器人的消息字典列表) + chat_history_text: 对话历史记录的文本格式 (新逻辑中未使用) + current_time_str: 当前时间的字符串格式 (新逻辑中未使用) + retry_count: 当前重试次数 (新逻辑中未使用) Returns: Tuple[bool, str, bool]: (是否合适, 原因, 是否需要重新规划) + 对于重复消息: (False, "机器人尝试发送重复消息", False) + 对于非重复消息: (True, "消息内容未与机器人历史发言重复。", False) """ - # 不再从 observer 获取,直接使用传入的 chat_history - # messages = self.chat_observer.get_cached_messages(limit=20) + if not self.bot_qq_str: + logger.error(f"[私聊][{self.private_name}] ReplyChecker: BOT_QQ 未配置,无法检查机器人自身消息。") + return True, "BOT_QQ未配置,跳过重复检查。", False # 无法检查则默认通过 + + if len(reply) <= 4: + return True, "消息长度小于等于4字符,跳过重复检查。", False + try: - # 筛选出最近由 Bot 自己发送的消息 - bot_messages = [] - for msg in reversed(chat_history): - user_info = UserInfo.from_dict(msg.get("user_info", {})) - if str(user_info.user_id) == str(global_config.BOT_QQ): # 确保比较的是字符串 - bot_messages.append(msg.get("processed_plain_text", "")) - if len(bot_messages) >= 2: # 只和最近的两条比较 - break - # 进行比较 - if bot_messages: - # 可以用简单比较,或者更复杂的相似度库 (如 difflib) - # 简单比较:是否完全相同 - if reply == bot_messages[0]: # 和最近一条完全一样 - logger.warning( - f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息完全相同: '{reply}'" - ) - return ( - False, - "被逻辑检查拒绝:回复内容与你上一条发言完全相同,可以选择深入话题或寻找其它话题或等待", - True, - ) # 不合适,需要返回至决策层 - # 2. 相似度检查 (如果精确匹配未通过) - import difflib # 导入 difflib 库 + for msg_dict in chat_history: + if not isinstance(msg_dict, dict): + continue - # 计算编辑距离相似度,ratio() 返回 0 到 1 之间的浮点数 - similarity_ratio = difflib.SequenceMatcher(None, reply, bot_messages[0]).ratio() - logger.debug(f"[私聊][{self.private_name}]ReplyChecker - 相似度: {similarity_ratio:.2f}") + user_info_data = msg_dict.get("user_info") + if not isinstance(user_info_data, dict): + continue - # 设置一个相似度阈值 - similarity_threshold = 0.9 - if similarity_ratio > similarity_threshold: - logger.warning( - f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息高度相似 (相似度 {similarity_ratio:.2f}): '{reply}'" - ) - return ( - False, - f"被逻辑检查拒绝:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),可以选择深入话题或寻找其它话题或等待。", - True, - ) + sender_id = str(user_info_data.get("user_id")) + + # 只检查机器人自己发送过的历史消息 + if sender_id == self.bot_qq_str: + historical_message_text = msg_dict.get("processed_plain_text", "") + if reply == historical_message_text: + logger.warning( + f"[私聊][{self.private_name}] ReplyChecker 检测到机器人自身重复消息: '{reply}'" + ) + return (False, "机器人尝试发送重复消息", False) # is_suitable=False, reason, need_replan=False + + # 如果循环结束都没有找到重复 + return (True, "消息内容未与机器人历史发言重复。", False) except Exception as e: import traceback - - logger.error(f"[私聊][{self.private_name}]检查回复时出错: 类型={type(e)}, 值={e}") - logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") # 打印详细的回溯信息 - - prompt_template = """ -当前时间:{current_time_str} -你是一个聊天逻辑检查器,请检查以下回复或消息是否合适: - -对话记录: -{chat_history_text} - -待检查的消息: -{reply} - -请结合聊天记录检查以下几点: -1. 这条消息是否与最新的对话记录保持连贯性(当话题切换时须保证平滑切换) -2. 是否存在重复发言,或重复表达同质内容(尤其是只是换一种方式表达了相同的含义) -3. 这条消息是否包含违规内容(例如血腥暴力,政治敏感等) -4. 这条消息是否以发送者的角度发言(不要让发送者自己回复自己的消息) -5. 这条消息是否通俗易懂 -6. 这条消息是否有些多余,例如在对方没有回复的情况下,依然连续多次“消息轰炸”(尤其是已经连续发送3条信息的情况,这很可能不合理,需要着重判断) -7. 这条消息是否使用了完全没必要的修辞 -8. 这条消息是否逻辑通顺 -9. 这条消息是否太过冗长了(通常私聊的每条消息长度在20字以内,除非特殊情况) -10. 在连续多次发送消息的情况下,这条消息是否衔接自然,会不会显得奇怪(例如连续两条消息中部分内容重叠) - -请以JSON格式输出,包含以下字段: -1. suitable: 是否合适 (true/false) -2. reason: 原因说明 -3. need_replan: 是否需要重新决策 (true/false),当你认为此时已经不适合发消息,需要规划其它行动时,设为true - -输出格式示例: -{{ - "suitable": true, - "reason": "回复符合要求,虽然有可能略微偏离目标,但是整体内容流畅得体", - "need_replan": false -}} - -注意:请严格按照JSON格式输出,不要包含任何其他内容。""" - - prompt = prompt_template.format( - current_time_str=current_time_str, # 使用传入的参数 - goal=goal, - chat_history_text=chat_history_text, - reply=reply - ) - - # 调用 LLM - content, _ = await self.llm.generate_response_async(prompt) - - try: - content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"[私聊][{self.private_name}]检查回复的原始返回: {content}") - - # 清理内容,尝试提取JSON部分 - content = content.strip() - try: - # 尝试直接解析 - result = json.loads(content) - except json.JSONDecodeError: - # 如果直接解析失败,尝试查找和提取JSON部分 - import re - - json_pattern = r"\{[^{}]*\}" - json_match = re.search(json_pattern, content) - if json_match: - try: - result = json.loads(json_match.group()) - except json.JSONDecodeError: - # 如果JSON解析失败,尝试从文本中提取结果 - is_suitable = "不合适" not in content.lower() and "违规" not in content.lower() - reason = content[:100] if content else "无法解析响应" - need_replan = "重新规划" in content.lower() or "目标不适合" in content.lower() - return is_suitable, reason, need_replan - else: - # 如果找不到JSON,从文本中判断 - is_suitable = "不合适" not in content.lower() and "违规" not in content.lower() - reason = content[:100] if content else "无法解析响应" - need_replan = "重新规划" in content.lower() or "目标不适合" in content.lower() - return is_suitable, reason, need_replan - - # 验证JSON字段 - suitable = result.get("suitable", None) - reason = result.get("reason", "未提供原因") - need_replan = result.get("need_replan", False) - - # 如果suitable字段是字符串,转换为布尔值 - if isinstance(suitable, str): - suitable = suitable.lower() == "true" - - # 如果suitable字段不存在或不是布尔值,从reason中判断 - if suitable is None: - suitable = "不合适" not in reason.lower() and "违规" not in reason.lower() - - # 如果不合适且未达到最大重试次数,返回需要重试 - if not suitable and retry_count < self.max_retries: - return False, reason, False - - # 如果不合适且已达到最大重试次数,返回需要重新规划 - if not suitable and retry_count >= self.max_retries: - return False, f"多次重试后仍不合适: {reason}", True - - return suitable, reason, need_replan - - except Exception as e: - logger.error(f"[私聊][{self.private_name}]检查回复时出错: {e}") - # 如果出错且已达到最大重试次数,建议重新规划 - if retry_count >= self.max_retries: - return False, "多次检查失败,建议重新规划", True - return False, f"检查过程出错,建议重试: {str(e)}", False + logger.error(f"[私聊][{self.private_name}] ReplyChecker 检查重复时出错: 类型={type(e)}, 值={e}") + logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") + # 发生未知错误时,为安全起见,默认通过,并记录原因 + return (True, f"检查重复时发生内部错误: {str(e)}", False) \ No newline at end of file diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index fba8fbda..f278a9bd 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -215,19 +215,31 @@ class ReplyGenerator: last_content = getattr(conversation_info, "last_rejected_reply_content", None) if last_reason and last_content: - last_rejection_info_str = ( - f"\n------\n" - f"【重要提示:你上一次尝试回复时失败了,以下是详细信息】\n" - f"上次试图发送的消息内容: “{last_content}”\n" - f"失败原因: “{last_reason}”\n" - f"请根据【消息内容】和【失败原因】调整你的新回复,避免重复之前的错误。\n" - f"------\n" - ) - logger.info( - f"[私聊][{self.private_name}]检测到上次回复失败信息,将加入 Prompt:\n" - f" 内容: {last_content}\n" - f" 原因: {last_reason}" - ) + if last_reason == "机器人尝试发送重复消息": # 这是我们从 ReplyChecker 设置的特定原因 + last_rejection_info_str = ( + f"\n------\n" + f"【重要提示:你上一次尝试发送的消息 “{last_content}” 与你更早之前发送过的某条消息完全相同。这属于复读行为,请避免。】\n" + f"请根据此提示调整你的新回复,确保内容新颖,不要重复你已经说过的话。\n" + f"------\n" + ) + logger.info( + f"[私聊][{self.private_name}] (ReplyGenerator) 检测到自身复读,将加入特定警告到 Prompt:\n" + f" 内容: {last_content}" + ) + else: # 其他类型的拒绝原因,保持原有格式 + last_rejection_info_str = ( + f"\n------\n" + f"【重要提示:你上一次尝试回复时失败了,以下是详细信息】\n" + f"上次试图发送的消息内容: “{last_content}”\n" + f"失败原因: “{last_reason}”\n" + f"请根据【消息内容】和【失败原因】调整你的新回复,避免重复之前的错误。\n" + f"------\n" + ) + logger.info( + f"[私聊][{self.private_name}] (ReplyGenerator) 检测到上次回复失败信息,将加入 Prompt:\n" + f" 内容: {last_content}\n" + f" 原因: {last_reason}" + ) # 新增:构建刷屏警告信息 for PROMPT_SEND_NEW_MESSAGE spam_warning_message = "" @@ -324,12 +336,4 @@ class ReplyGenerator: else: return "抱歉,我现在有点混乱,让我重新思考一下..." - # check_reply 方法保持不变 - async def check_reply( - self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_str: str, current_time_str: str, retry_count: int = 0 - ) -> Tuple[bool, str, bool]: - """检查回复是否合适 - (此方法逻辑保持不变, 但注意 current_time_str 参数的传递) - """ - # 确保 current_time_str 被正确传递给 reply_checker.check - return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, current_time_str, retry_count) \ No newline at end of file + # check_reply 方法在 ReplyGenerator 中不再需要 \ No newline at end of file