diff --git a/.gitignore b/.gitignore index 326b85948..15a2d5739 100644 --- a/.gitignore +++ b/.gitignore @@ -316,4 +316,6 @@ run_pet.bat !/plugins/hello_world_plugin !/plugins/take_picture_plugin -config.toml \ No newline at end of file +config.toml + +interested_rates.txt \ No newline at end of file diff --git a/interested_rates.txt b/interested_rates.txt index 7d28a45b2..2afb31fe2 100644 --- a/interested_rates.txt +++ b/interested_rates.txt @@ -618,3 +618,333 @@ 6.234776695167793 6.327799950981746 6.234224872543608 +0.12555594323694738 +4.691362169321614 +0.760960373429408 +0.03190953220831115 +0.21221850656149352 +0.21221850656149352 +0.21740663700282584 +0.2209989240456402 +0.20691340738722538 +6.01401596040071 +6.01401596040071 +6.012067868465945 +1.0339877364806525 +1.033607944262916 +0.02126634371149695 +6.013123465676255 +6.013123465676255 +0.014013152602464482 +0.03203964454588806 +0.014013152602464482 +0.03203964454588806 +0.03203964454588806 +1.7309854097635113 +0.03203964454588806 +1.121214185121496 +1.121214185121496 +6.013123465676255 +0.019318251776732617 +6.0176000459743335 +6.013123465676255 +6.012067868465945 +1.3442614225195928 +0.018026305204928966 +6.013123465676255 +6.01880222709907 +6.012067868465945 +0.21221850656149352 +6.021149770881184 +1.3442614225195928 +0.03203964454588806 +6.013123465676255 +0.020373848987042205 +0.21475589539598097 +0.016360696384577725 +0.02203945780739345 +6.001110655549735 +0.21129104527597597 +5.989741835616894 +0.21129104527597597 +0.022721392769155448 +5.987020629586234 +0.019318251776732617 +0.019318251776732617 +5.989741835616894 +0.022721392769155448 +0.21066124009593168 +1.4665149210247743 +0.03203964454588806 +0.5645996978699889 +0.20681943402193914 +0.014013152602464482 +0.5638265837740923 +0.02203945780739345 +0.019318251776732617 +1.0300657630123224 +5.982811300748257 +0.5638265837740923 +5.982811300748257 +1.3509125289080943 +0.022721392769155448 +5.977132539325442 +5.994180120681098 +0.03677746112588288 +5.990837605953186 +1.7039225527703115 +1.7053776018279698 +0.02388322700338219 +0.014013152602464482 +0.02126634371149695 +5.993867084697062 +0.016360696384577725 +5.994666941359478 +0.2131553677924724 +7.678798914186269 +6.1788740664574044 +0.02567894816131034 +5.994572796921638 +5.985158844530371 +5.988398883036939 +5.994180120681098 +5.994759579421516 +5.994940524002717 +1.13603122500849 +1.021266343711497 +0.020373848987042205 +5.985158844530371 +5.982038186652361 +0.014013152602464482 +0.016360696384577725 +0.016360696384577725 +0.02203945780739345 +5.984655069944246 +1.7046956668662079 +1.7046956668662079 +1.7030300580458566 +1.1226176950628484 +6.0881758047020735 +6.0881758047020735 +0.037161756536937846 +5.994077644459755 +0.037161756536937846 +0.3189385906841562 +0.02203945780739345 +5.980090094717597 +0.019318251776732617 +7.664694395711176 +0.014013152602464482 +5.986051339254826 +0.02126634371149695 +0.014013152602464482 +0.016360696384577725 +1.6990169054433921 +1.701974460835547 +0.21066124009593168 +0.3189385906841562 +7.666759456378876 +5.982038186652361 +1.0399358374132401 +5.994940524002717 +0.03927442624240585 +0.016360696384577725 +0.0233314043791971 +0.0233314043791971 +6.088857739663836 +6.088857739663836 +6.088857739663836 +0.014013152602464482 +1.5083907510110965 +0.02126634371149695 +0.014013152602464482 +2.819591566494889 +1.7046956668662079 +0.03203964454588806 +0.02388322700338219 +0.03203964454588806 +6.047071409613139 +0.014013152602464482 +0.016360696384577725 +6.053135130371711 +6.040993196342974 +0.21159916334682072 +1.0405293774464313 +0.03203964454588806 +6.224053874545717 +5.94049068378016 +5.944903288229973 +5.943107567072045 +1.6898299547044922 +0.21859874798662643 +5.948916440832438 +5.946568897050325 +0.020373848987042205 +0.12176885627431103 +0.12176885627431103 +0.036889887092514305 +0.022721392769155448 +5.941263797876056 +5.952632617808897 +8.287843209657629 +5.954808346153387 +5.941263797876056 +8.291482700011546 +0.016360696384577725 +5.95231958182486 +5.953985190645212 +0.014013152602464482 +0.3745255599226728 +0.21159916334682072 +0.03203964454588806 +5.9419457328378185 +5.94255574444786 +0.21159916334682072 +0.20910503565028 +5.935585036453241 +0.014013152602464482 +7.610509343830814 +0.014013152602464482 +1.7911055813632024 +0.21159916334682072 +0.025279496313961432 +0.014013152602464482 +0.31291086336433277 +0.014013152602464482 +0.035856515457896934 +5.941263797876056 +5.941263797876056 +5.941263797876056 +5.941263797876056 +0.014013152602464482 +1.0227213927691554 +0.014013152602464482 +0.21159916334682072 +0.3150010423159331 +5.94049068378016 +5.94361134165817 +0.2148392018533887 +1.0363045653081948 +1.2173498481213567 +1.216787293788153 +0.20910503565028 +0.21745608514527395 +5.944074769353784 +5.944074769353784 +0.014013152602464482 +5.9395981890557055 +0.02388322700338219 +0.31444921969174805 +5.943107567072045 +0.02969210076377482 +0.034919483934155664 +0.0233314043791971 +0.02126634371149695 +0.02126634371149695 +5.944074769353784 +1.446424442364109 +0.027047581355656602 +5.945627952207503 +5.9419457328378185 +1.1168632089473918 +1.0203738489870422 +5.935585036453241 +0.016360696384577725 +0.014013152602464482 +0.014013152602464482 +5.951988648592081 +5.945627952207503 +5.94255574444786 +10.502971083394339 +5.953393021130516 +0.21159916334682072 +5.94049068378016 +7.417010356982582 +0.018026305204928966 +0.0233314043791971 +0.3096075482130941 +0.035653345301003635 +0.13594529810112496 +0.02789637960584667 +0.12655512297267202 +0.03467987365713867 +0.20629406417255258 +0.31444921969174805 +0.014013152602464482 +5.94255574444786 +0.21159916334682072 +0.20910503565028 +0.020373848987042205 +0.016360696384577725 +0.02388322700338219 +0.02126634371149695 +0.20910503565028 +0.014013152602464482 +0.014013152602464482 +0.21394670712893396 +0.02203945780739345 +0.02388322700338219 +0.12578200887677551 +0.21159916334682072 +0.024387001589506692 +0.014013152602464482 +0.018026305204928966 +0.019318251776732617 +0.02388322700338219 +0.03150067377017187 +0.018026305204928966 +0.21159916334682072 +0.026734545371619928 +0.020373848987042205 +0.21159916334682072 +0.20629406417255258 +0.019318251776732617 +0.03434414162146728 +0.025279496313961432 +0.21159916334682072 +0.12383391694201118 +0.016360696384577725 +0.13019461332658888 +0.02388322700338219 +0.018026305204928966 +0.02126634371149695 +0.022721392769155448 +0.20629406417255258 +0.13019461332658888 +0.11982076433954669 +0.02388322700338219 +0.0233314043791971 +1.4942766388975819 +0.13019461332658888 +2.369300804550725 +0.0233314043791971 +5.938542591845396 +0.02126634371149695 +5.94049068378016 +5.938542591845396 +0.02388322700338219 +0.02388322700338219 +0.03264965615592971 +0.1322789410848081 +1.018026305204929 +0.13019461332658888 +0.03818183366431797 +0.21159916334682072 +0.03642645939690014 +0.0233314043791971 +0.0233314043791971 +0.02203945780739345 +5.9527333117444465 +0.21159916334682072 +0.21159916334682072 +5.897685246828215 +1.6780365136197586 +5.897685246828215 +5.9010883878206375 +0.13532111938738237 +1.2293321950146374 +0.2075746817768152 +0.018026305204928966 +0.21006880947335593 +0.016360696384577725 +0.041803481922888616 diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index a573a39fd..68c040cfb 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -10,26 +10,27 @@ from src.plugin_system import BaseAction, ActionActivationType, ChatMode from src.common.logger import get_logger # 导入API模块 - 标准Python包方式 -from src.plugin_system.apis import message_api, llm_api +from src.plugin_system.apis import message_api from src.config.config import global_config -from json_repair import repair_json +from src.chat.message_receive.message import MessageRecv +from src.chat.message_receive.chat_stream import get_chat_manager +from src.chat.memory_system.Hippocampus import hippocampus_manager +import math +from src.chat.utils.utils import is_mentioned_bot_in_message + logger = get_logger("core_actions") class NoReplyAction(BaseAction): - """不回复动作,使用智能判断机制决定何时结束等待 + """不回复动作,根据新消息的兴趣值或数量决定何时结束等待. - 新的等待逻辑: - - 每0.2秒检查是否有新消息(提高响应性) - - 如果累计消息数量达到阈值(默认20条),直接结束等待 - - 有新消息时进行LLM判断,但最快1秒一次(防止过于频繁) - - 如果判断需要回复,则结束等待;否则继续等待 - - 达到最大超时时间后强制结束 + 新的等待逻辑: + 1. 新消息累计兴趣值超过阈值 (默认10) 则结束等待 + 2. 累计新消息数量达到随机阈值 (默认5-10条) 则结束等待 """ focus_activation_type = ActionActivationType.ALWAYS - # focus_activation_type = ActionActivationType.RANDOM normal_activation_type = ActionActivationType.NEVER mode_enable = ChatMode.FOCUS parallel_action = False @@ -41,21 +42,11 @@ class NoReplyAction(BaseAction): # 连续no_reply计数器 _consecutive_count = 0 - # LLM判断的最小间隔时间 - _min_judge_interval = 1.0 # 最快1秒一次LLM判断 - - # 自动结束的消息数量阈值 - _auto_exit_message_count = 20 # 累计20条消息自动结束 - - # 最大等待超时时间 - _max_timeout = 600 # 1200秒 - - # 跳过LLM判断的配置 - _skip_judge_when_tired = True - _skip_probability = 0.5 - - # 新增:回复频率退出专注模式的配置 - _frequency_check_window = 600 # 频率检查窗口时间(秒) + # 新增:兴趣值退出阈值 + _interest_exit_threshold = 3.0 + # 新增:消息数量退出阈值 + _min_exit_message_count = 5 + _max_exit_message_count = 10 # 动作参数定义 action_parameters = {"reason": "不回复的原因"} @@ -67,7 +58,7 @@ class NoReplyAction(BaseAction): associated_types = [] async def execute(self) -> Tuple[bool, str]: - """执行不回复动作,有新消息时进行判断,但最快1秒一次""" + """执行不回复动作""" import asyncio try: @@ -77,30 +68,14 @@ class NoReplyAction(BaseAction): reason = self.action_data.get("reason", "") start_time = time.time() - last_judge_time = start_time # 上次进行LLM判断的时间 - min_judge_interval = self._min_judge_interval # 最小判断间隔,从配置获取 - check_interval = 0.2 # 检查新消息的间隔,设为0.2秒提高响应性 + check_interval = 1.0 # 每秒检查一次 - # 累积判断历史 - judge_history = [] # 存储每次判断的结果和理由 - - # 获取no_reply开始时的上下文消息(10条),用于后续记录 - context_messages = message_api.get_messages_by_time_in_chat( - chat_id=self.chat_id, - start_time=start_time - 600, # 获取开始前10分钟内的消息 - end_time=start_time, - limit=10, - limit_mode="latest", + # 随机生成本次等待需要的新消息数量阈值 + exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count) + logger.info( + f"{self.log_prefix} 本次no_reply需要 {exit_message_count_threshold} 条新消息或累计兴趣值超过 {self._interest_exit_threshold} 才能打断" ) - # 构建上下文字符串 - context_str = "" - if context_messages: - context_str = message_api.build_readable_messages( - messages=context_messages, timestamp_mode="normal_no_YMD", truncate=False, show_actions=True - ) - context_str = f"当时选择no_reply前的聊天上下文:\n{context_str}\n" - logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始摸鱼,原因: {reason}") # 进入等待状态 @@ -108,196 +83,52 @@ class NoReplyAction(BaseAction): current_time = time.time() elapsed_time = current_time - start_time - if global_config.chat.chat_mode == "auto" and self.is_group: - # 检查是否超时 - if elapsed_time >= self._max_timeout or self._check_no_activity_and_exit_focus(current_time): - logger.info( - f"{self.log_prefix} 等待时间过久({self._max_timeout}秒)或过去10分钟完全没有发言,退出专注模式" - ) - # 标记退出专注模式 - self.action_data["_system_command"] = "stop_focus_chat" - exit_reason = f"{global_config.bot.nickname}(你)等待了{self._max_timeout}秒,或完全没有说话,感觉群里没有新内容,决定退出专注模式,稍作休息" - await self.store_action_info( - action_build_into_prompt=True, - action_prompt_display=exit_reason, - action_done=True, - ) - return True, exit_reason - - # 检查是否有新消息 - new_message_count = message_api.count_new_messages( + # 1. 检查新消息 + recent_messages_dict = message_api.get_messages_by_time_in_chat( chat_id=self.chat_id, start_time=start_time, end_time=current_time ) + new_message_count = len(recent_messages_dict) - # 如果累计消息数量达到阈值,直接结束等待 - if new_message_count >= self._auto_exit_message_count: - logger.info(f"{self.log_prefix} 累计消息数量达到{new_message_count}条,直接结束等待") + # 2. 检查消息数量是否达到阈值 + if new_message_count >= exit_message_count_threshold: + logger.info(f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{exit_message_count_threshold}),结束等待") exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复" await self.store_action_info( - action_build_into_prompt=True, + action_build_into_prompt=False, action_prompt_display=exit_reason, action_done=True, ) - return True, f"累计消息数量达到{new_message_count}条,直接结束等待 (等待时间: {elapsed_time:.1f}秒)" + return True, f"累计消息数量达到{new_message_count}条,结束等待 (等待时间: {elapsed_time:.1f}秒)" - # 判定条件:累计3条消息或等待超过5秒且有新消息 - time_since_last_judge = current_time - last_judge_time - should_judge, trigger_reason = self._should_trigger_judge(new_message_count, time_since_last_judge) - - if should_judge and time_since_last_judge >= min_judge_interval: - logger.info(f"{self.log_prefix} 触发判定({trigger_reason}),进行智能判断...") - - # 获取最近的消息内容用于判断 - recent_messages = message_api.get_messages_by_time_in_chat( - chat_id=self.chat_id, - start_time=start_time, - end_time=current_time, - ) - - if recent_messages: - # 使用message_api构建可读的消息字符串 - messages_text = message_api.build_readable_messages( - messages=recent_messages, - timestamp_mode="normal_no_YMD", - truncate=False, - show_actions=False, + # 3. 检查累计兴趣值 + if new_message_count > 0: + accumulated_interest = await self._calculate_accumulated_interest(recent_messages_dict) + logger.info(f"{self.log_prefix} 当前累计兴趣值: {accumulated_interest:.2f}") + if accumulated_interest >= self._interest_exit_threshold: + logger.info( + f"{self.log_prefix} 累计兴趣值达到{accumulated_interest:.2f}(>{self._interest_exit_threshold}),结束等待" ) - - # 获取身份信息 - bot_name = global_config.bot.nickname - bot_nickname = "" - if global_config.bot.alias_names: - bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}" - bot_core_personality = global_config.personality.personality_core - identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}" - - # 构建判断历史字符串(最多显示3条) - history_block = "" - if judge_history: - history_block = "之前的判断历史:\n" - # 只取最近的3条历史记录 - recent_history = judge_history[-3:] if len(judge_history) > 3 else judge_history - for i, (timestamp, judge_result, reason) in enumerate(recent_history, 1): - elapsed_seconds = int(timestamp - start_time) - history_block += f"{i}. 等待{elapsed_seconds}秒时判断:{judge_result},理由:{reason}\n" - history_block += "\n" - - # 检查过去10分钟的发言频率 - frequency_block, should_skip_llm_judge = self._get_fatigue_status(current_time) - - # 如果决定跳过LLM判断,直接更新时间并继续等待 - if should_skip_llm_judge: - logger.info(f"{self.log_prefix} 疲劳,继续等待。") - last_judge_time = time.time() # 更新判断时间,避免立即重新判断 - start_time = current_time # 更新消息检查的起始时间,以避免重复判断 - continue # 跳过本次LLM判断,继续循环等待 - - # 构建判断上下文 - chat_context = "QQ群" if self.is_group else "私聊" - judge_prompt = f""" -{identity_block} - -你现在正在{chat_context}参与聊天,以下是聊天内容: -{context_str} -在以上的聊天中,你选择了暂时不回复,现在,你看到了新的聊天消息如下: -{messages_text} - -{history_block} -请注意:{frequency_block} -请你判断,是否要结束不回复的状态,重新加入聊天讨论。 - -判断标准: -1. 如果有人直接@你、提到你的名字或明确向你询问,应该回复 -2. 如果话题发生重要变化,需要你参与讨论,应该回复 -3. 如果只是普通闲聊、重复内容或与你无关的讨论,不需要回复 -4. 如果消息内容过于简单(如单纯的表情、"哈哈"等),不需要回复 -5. 参考之前的判断历史,如果情况有明显变化或持续等待时间过长,考虑调整判断 - -请用JSON格式回复你的判断,严格按照以下格式: -{{ - "should_reply": true/false, - "reason": "详细说明你的判断理由" -}} -""" - - try: - # 获取可用的模型配置 - available_models = llm_api.get_available_models() - - # 使用 utils_small 模型 - small_model = getattr(available_models, "utils_small", None) - - logger.debug(judge_prompt) - - if small_model: - # 使用小模型进行判断 - success, response, reasoning, model_name = await llm_api.generate_with_model( - prompt=judge_prompt, - model_config=small_model, - request_type="plugin.no_reply_judge", - temperature=0.7, # 进一步降低温度,提高JSON输出的一致性和准确性 - ) - - # 更新上次判断时间 - last_judge_time = time.time() - - if success and response: - response = response.strip() - logger.debug(f"{self.log_prefix} 模型({model_name})原始JSON响应: {response}") - - # 解析LLM的JSON响应,提取判断结果和理由 - judge_result, reason = self._parse_llm_judge_response(response) - - if judge_result: - logger.info(f"{self.log_prefix} 决定继续参与讨论,结束等待,原因: {reason}") - else: - logger.info(f"{self.log_prefix} 决定不参与讨论,继续等待,原因: {reason}") - - # 将判断结果保存到历史中 - judge_history.append((current_time, judge_result, reason)) - - if judge_result == "需要回复": - # logger.info(f"{self.log_prefix} 模型判断需要回复,结束等待") - - full_prompt = f"{global_config.bot.nickname}(你)的想法是:{reason}" - await self.store_action_info( - action_build_into_prompt=True, - action_prompt_display=full_prompt, - action_done=True, - ) - return True, f"检测到需要回复的消息,结束等待 (等待时间: {elapsed_time:.1f}秒)" - else: - logger.info(f"{self.log_prefix} 模型判断不需要回复,理由: {reason},继续等待") - # 更新开始时间,避免重复判断同样的消息 - start_time = current_time - else: - logger.warning(f"{self.log_prefix} 模型判断失败,继续等待") - else: - logger.warning(f"{self.log_prefix} 未找到可用的模型配置,继续等待") - last_judge_time = time.time() # 即使失败也更新时间,避免频繁重试 - - except Exception as e: - logger.error(f"{self.log_prefix} 模型判断异常: {e},继续等待") - last_judge_time = time.time() # 异常时也更新时间,避免频繁重试 + exit_reason = f"{global_config.bot.nickname}(你)感觉到了大家浓厚的兴趣(兴趣值{accumulated_interest:.1f}),决定重新加入讨论" + await self.store_action_info( + action_build_into_prompt=False, + action_prompt_display=exit_reason, + action_done=True, + ) + return True, f"累计兴趣值达到{accumulated_interest:.2f},结束等待 (等待时间: {elapsed_time:.1f}秒)" # 每10秒输出一次等待状态 - if elapsed_time < 60: - if int(elapsed_time) % 10 == 0 and int(elapsed_time) > 0: - logger.debug(f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,等待新消息...") - await asyncio.sleep(1) - else: - if int(elapsed_time) % 180 == 0 and int(elapsed_time) > 0: - logger.info(f"{self.log_prefix} 已等待{elapsed_time / 60:.0f}分钟,等待新消息...") - await asyncio.sleep(1) + if int(elapsed_time) > 0 and int(elapsed_time) % 10 == 0: + logger.debug(f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,累计{new_message_count}条消息,继续等待...") + # 使用 asyncio.sleep(1) 来避免在同一秒内重复打印日志 + await asyncio.sleep(1) # 短暂等待后继续检查 await asyncio.sleep(check_interval) except Exception as e: logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}") - # 即使执行失败也要记录 exit_reason = f"执行异常: {str(e)}" - full_prompt = f"{context_str}{exit_reason},你思考是否要进行回复" + full_prompt = f"no_reply执行异常: {exit_reason},你思考是否要进行回复" await self.store_action_info( action_build_into_prompt=True, action_prompt_display=full_prompt, @@ -305,214 +136,53 @@ class NoReplyAction(BaseAction): ) return False, f"不回复动作执行失败: {e}" - def _should_trigger_judge(self, new_message_count: int, time_since_last_judge: float) -> Tuple[bool, str]: - """判断是否应该触发智能判断,并返回触发原因。 + async def _calculate_accumulated_interest(self, messages_dicts: list[dict]) -> float: + """将所有新消息文本合并,然后一次性计算兴趣值""" + if not messages_dicts: + return 0.0 - Args: - new_message_count: 新消息的数量。 - time_since_last_judge: 距离上次判断的时间。 - Returns: - 一个元组 (should_judge, reason)。 - - should_judge: 一个布尔值,指示是否应该触发判断。 - - reason: 触发判断的原因字符串。 - """ - # 判定条件:累计3条消息或等待超过15秒且有新消息 - should_judge_flag = new_message_count >= 3 or (new_message_count > 0 and time_since_last_judge >= 15.0) - if not should_judge_flag: - return False, "" + combined_text_parts = [] + is_any_mentioned = False - # 判断触发原因 - if new_message_count >= 3: - return True, f"累计{new_message_count}条消息" - elif new_message_count > 0 and time_since_last_judge >= 15.0: - return True, f"等待{time_since_last_judge:.1f}秒且有新消息" - - return False, "" - - def _get_fatigue_status(self, current_time: float) -> Tuple[str, bool]: - """ - 根据最近的发言频率生成疲劳提示,并决定是否跳过判断。 - - Args: - current_time: 当前时间戳。 - - Returns: - 一个元组 (frequency_block, should_skip_judge)。 - - frequency_block: 疲劳度相关的提示字符串。 - - should_skip_judge: 是否应该跳过LLM判断的布尔值。 - """ - try: - # 获取过去10分钟的所有消息 - past_10min_time = current_time - 600 # 10分钟前 - all_messages_10min = message_api.get_messages_by_time_in_chat( - chat_id=self.chat_id, - start_time=past_10min_time, - end_time=current_time, - ) - - # 手动过滤bot自己的消息 - bot_message_count = 0 - if all_messages_10min: - user_id = global_config.bot.qq_account - for message in all_messages_10min: - sender_id = message.get("user_id", "") - if sender_id == user_id: - bot_message_count += 1 - - talk_frequency_threshold = global_config.chat.get_current_talk_frequency(self.chat_id) * 10 - - if bot_message_count > talk_frequency_threshold: - over_count = bot_message_count - talk_frequency_threshold - skip_probability = 0 - frequency_block = "" - - if over_count <= 3: - frequency_block = "你感觉稍微有些累,回复的有点多了。\n" - elif over_count <= 5: - frequency_block = "你今天说话比较多,感觉有点疲惫,想要稍微休息一下。\n" - elif over_count <= 8: - frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n" - skip_probability = self._skip_probability - else: - frequency_block = "你感觉非常累,想要安静一会儿。\n" - skip_probability = 1 - - should_skip_judge = self._skip_judge_when_tired and random.random() < skip_probability - - if should_skip_judge: - logger.info( - f"{self.log_prefix} 发言过多(超过{over_count}条),随机决定跳过此次LLM判断(概率{skip_probability * 100:.0f}%)" - ) - - logger.info( - f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,超过阈值{talk_frequency_threshold},添加疲惫提示" - ) - return frequency_block, should_skip_judge - else: - # 回复次数少时的正向提示 - under_count = talk_frequency_threshold - bot_message_count - frequency_block = "" - if under_count >= talk_frequency_threshold * 0.8: - frequency_block = "你感觉精力充沛,状态很好,积极参与聊天。\n" - elif under_count >= talk_frequency_threshold * 0.5: - frequency_block = "你感觉状态不错。\n" - - logger.info( - f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,未超过阈值{talk_frequency_threshold},添加正向提示" - ) - return frequency_block, False - - except Exception as e: - logger.warning(f"{self.log_prefix} 检查发言频率时出错: {e}") - return "", False - - def _check_no_activity_and_exit_focus(self, current_time: float) -> bool: - """检查过去10分钟是否完全没有发言,决定是否退出专注模式 - - Args: - current_time: 当前时间戳 - - Returns: - bool: 是否应该退出专注模式 - """ - try: - # 只在auto模式下进行检查 - if global_config.chat.chat_mode != "auto": - return False - - # 获取过去10分钟的所有消息 - past_10min_time = current_time - 600 # 10分钟前 - all_messages = message_api.get_messages_by_time_in_chat( - chat_id=self.chat_id, - start_time=past_10min_time, - end_time=current_time, - ) - - if not all_messages: - # 如果完全没有消息,也不需要退出专注模式 - return False - - # 统计bot自己的回复数量 - bot_message_count = 0 - user_id = global_config.bot.qq_account - - for message in all_messages: - sender_id = message.get("user_id", "") - if sender_id == user_id: - bot_message_count += 1 - - # 如果过去10分钟bot一条消息也没有发送,退出专注模式 - if bot_message_count == 0: - logger.info(f"{self.log_prefix} 过去10分钟bot完全没有发言,准备退出专注模式") - return True - else: - logger.debug(f"{self.log_prefix} 过去10分钟bot发言{bot_message_count}条,继续保持专注模式") - return False - - except Exception as e: - logger.error(f"{self.log_prefix} 检查无活动状态时出错: {e}") - return False - - def _parse_llm_judge_response(self, response: str) -> tuple[str, str]: - """解析LLM判断响应,使用JSON格式提取判断结果和理由 - - Args: - response: LLM的原始JSON响应 - - Returns: - tuple: (判断结果, 理由) - """ - try: - # 使用repair_json修复可能有问题的JSON格式 - fixed_json_string = repair_json(response) - logger.debug(f"{self.log_prefix} repair_json修复后的响应: {fixed_json_string}") - - # 如果repair_json返回的是字符串,需要解析为Python对象 - if isinstance(fixed_json_string, str): - result_json = json.loads(fixed_json_string) - else: - # 如果repair_json直接返回了字典对象,直接使用 - result_json = fixed_json_string - - # 从JSON中提取判断结果和理由 - should_reply = result_json.get("should_reply", False) - reason = result_json.get("reason", "无法获取判断理由") - - # 转换布尔值为中文字符串 - judge_result = "需要回复" if should_reply else "不需要回复" - - logger.debug(f"{self.log_prefix} JSON解析成功 - 判断: {judge_result}, 理由: {reason}") - return judge_result, reason - - except (json.JSONDecodeError, KeyError, TypeError) as e: - logger.warning(f"{self.log_prefix} JSON解析失败,尝试文本解析: {e}") - - # 如果JSON解析失败,回退到简单的关键词匹配 + for msg_dict in messages_dicts: try: - response_lower = response.lower() + text = msg_dict.get("processed_plain_text", "") + if text: + combined_text_parts.append(text) + except Exception as e: + logger.error(f"{self.log_prefix} 处理单条消息以计算兴趣值时出错: {e}") - if "true" in response_lower or "需要回复" in response: - judge_result = "需要回复" - reason = "从响应文本中检测到需要回复的指示" - elif "false" in response_lower or "不需要回复" in response: - judge_result = "不需要回复" - reason = "从响应文本中检测到不需要回复的指示" - else: - judge_result = "不需要回复" # 默认值 - reason = f"无法解析响应格式,使用默认判断。原始响应: {response[:100]}..." + full_text = " ".join(combined_text_parts).strip() + if not full_text: + return 0.0 - logger.debug(f"{self.log_prefix} 文本解析结果 - 判断: {judge_result}, 理由: {reason}") - return judge_result, reason + # --- 使用合并后的文本计算兴趣值 --- + + if global_config.bot.nickname in full_text: + is_any_mentioned = True - except Exception as fallback_e: - logger.error(f"{self.log_prefix} 文本解析也失败: {fallback_e}") - return "不需要回复", f"解析异常: {str(e)}, 回退解析也失败: {str(fallback_e)}" + interested_rate = 0.0 + if global_config.memory.enable_memory: + try: + interested_rate = await hippocampus_manager.get_activate_from_text( + full_text, + fast_retrieval=True, + ) + except Exception as e: + logger.error(f"{self.log_prefix} 记忆激活计算失败: {e}") - except Exception as e: - logger.error(f"{self.log_prefix} 解析LLM响应时出错: {e}") - return "不需要回复", f"解析异常: {str(e)}" + text_len = len(full_text) + # 根据文本长度调整兴趣度 + base_interest = 0.01 + (0.05 - 0.01) * (math.log10(text_len + 1) / math.log10(1000 + 1)) + base_interest = min(max(base_interest, 0.01), 0.05) + interested_rate += base_interest + + if is_any_mentioned: + interested_rate += 1 + + return interested_rate @classmethod def reset_consecutive_count(cls):