better:重构no_reply,不再使用小模型,而是采用激活度决定是否结束等待

This commit is contained in:
SengokuCola
2025-07-11 01:23:49 +08:00
parent 825cfb44f5
commit 81c1e66867
3 changed files with 420 additions and 418 deletions

4
.gitignore vendored
View File

@@ -316,4 +316,6 @@ run_pet.bat
!/plugins/hello_world_plugin
!/plugins/take_picture_plugin
config.toml
config.toml
interested_rates.txt

View File

@@ -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

View File

@@ -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):