mirror of https://github.com/Mai-with-u/MaiBot.git
Merge branch 'MaiM-with-u:dev' into dev
commit
cd00071450
|
|
@ -67,7 +67,7 @@ class HelloWorldPlugin(BasePlugin):
|
||||||
|
|
||||||
- 首先,我们在plugin.py中定义了一个HelloWorldPulgin插件类,继承自 `BasePlugin` ,提供基本功能。
|
- 首先,我们在plugin.py中定义了一个HelloWorldPulgin插件类,继承自 `BasePlugin` ,提供基本功能。
|
||||||
- 通过给类加上,`@register_plugin` 装饰器,我们告诉系统"这是一个插件"
|
- 通过给类加上,`@register_plugin` 装饰器,我们告诉系统"这是一个插件"
|
||||||
- `plugin_name` 等是插件的基本信息,必须填写
|
- `plugin_name` 等是插件的基本信息,必须填写,**此部分必须与目录名称相同,否则插件无法使用**
|
||||||
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何action(动作)或者command(指令),是空的
|
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何action(动作)或者command(指令),是空的
|
||||||
|
|
||||||
### 3. 测试基础插件
|
### 3. 测试基础插件
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ class ActionModifier:
|
||||||
logger.debug(f"{self.log_prefix}开始完整动作修改流程")
|
logger.debug(f"{self.log_prefix}开始完整动作修改流程")
|
||||||
|
|
||||||
# === 第一阶段:传统观察处理 ===
|
# === 第一阶段:传统观察处理 ===
|
||||||
|
chat_content = None
|
||||||
|
|
||||||
if observations:
|
if observations:
|
||||||
hfc_obs = None
|
hfc_obs = None
|
||||||
chat_obs = None
|
chat_obs = None
|
||||||
|
|
@ -78,7 +80,7 @@ class ActionModifier:
|
||||||
if hfc_obs:
|
if hfc_obs:
|
||||||
obs = hfc_obs
|
obs = hfc_obs
|
||||||
# 获取适用于FOCUS模式的动作
|
# 获取适用于FOCUS模式的动作
|
||||||
all_actions = self.action_manager.get_using_actions_for_mode("focus")
|
all_actions = self.all_actions
|
||||||
action_changes = await self.analyze_loop_actions(obs)
|
action_changes = await self.analyze_loop_actions(obs)
|
||||||
if action_changes["add"] or action_changes["remove"]:
|
if action_changes["add"] or action_changes["remove"]:
|
||||||
# 合并动作变更
|
# 合并动作变更
|
||||||
|
|
@ -94,9 +96,8 @@ class ActionModifier:
|
||||||
|
|
||||||
# 处理ChattingObservation - 传统的类型匹配检查
|
# 处理ChattingObservation - 传统的类型匹配检查
|
||||||
if chat_obs:
|
if chat_obs:
|
||||||
obs = chat_obs
|
|
||||||
# 检查动作的关联类型
|
# 检查动作的关联类型
|
||||||
chat_context = get_chat_manager().get_stream(obs.chat_id).context
|
chat_context = get_chat_manager().get_stream(chat_obs.chat_id).context
|
||||||
type_mismatched_actions = []
|
type_mismatched_actions = []
|
||||||
|
|
||||||
for action_name in all_actions.keys():
|
for action_name in all_actions.keys():
|
||||||
|
|
@ -128,26 +129,13 @@ class ActionModifier:
|
||||||
f"{self.log_prefix}传统动作修改完成,当前使用动作: {list(self.action_manager.get_using_actions().keys())}"
|
f"{self.log_prefix}传统动作修改完成,当前使用动作: {list(self.action_manager.get_using_actions().keys())}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# === chat_mode检查:强制移除非auto模式下的exit_focus_chat ===
|
# 注释:已移除exit_focus_chat动作,现在由no_reply动作处理频率检测退出专注模式
|
||||||
if global_config.chat.chat_mode != "auto":
|
|
||||||
if "exit_focus_chat" in self.action_manager.get_using_actions():
|
|
||||||
self.action_manager.remove_action_from_using("exit_focus_chat")
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix}移除动作: exit_focus_chat,原因: chat_mode不为auto(当前模式: {global_config.chat.chat_mode})"
|
|
||||||
)
|
|
||||||
|
|
||||||
# === 第二阶段:激活类型判定 ===
|
# === 第二阶段:激活类型判定 ===
|
||||||
# 如果提供了聊天上下文,则进行激活类型判定
|
# 如果提供了聊天上下文,则进行激活类型判定
|
||||||
if chat_content is not None:
|
if chat_content is not None:
|
||||||
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
|
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
|
||||||
|
|
||||||
# 保存exit_focus_chat动作(如果存在)
|
|
||||||
exit_focus_action = None
|
|
||||||
if "exit_focus_chat" in self.action_manager.get_using_actions():
|
|
||||||
exit_focus_action = self.action_manager.get_using_actions()["exit_focus_chat"]
|
|
||||||
self.action_manager.remove_action_from_using("exit_focus_chat")
|
|
||||||
logger.debug(f"{self.log_prefix}临时移除exit_focus_chat动作以进行激活类型判定")
|
|
||||||
|
|
||||||
# 获取当前使用的动作集(经过第一阶段处理,且适用于FOCUS模式)
|
# 获取当前使用的动作集(经过第一阶段处理,且适用于FOCUS模式)
|
||||||
current_using_actions = self.action_manager.get_using_actions()
|
current_using_actions = self.action_manager.get_using_actions()
|
||||||
all_registered_actions = self.action_manager.get_registered_actions()
|
all_registered_actions = self.action_manager.get_registered_actions()
|
||||||
|
|
@ -197,16 +185,7 @@ class ActionModifier:
|
||||||
reason = removal_reasons.get(action_name, "未知原因")
|
reason = removal_reasons.get(action_name, "未知原因")
|
||||||
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: {reason}")
|
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: {reason}")
|
||||||
|
|
||||||
# 恢复exit_focus_chat动作(如果之前存在)
|
# 注释:已完全移除exit_focus_chat动作
|
||||||
if exit_focus_action:
|
|
||||||
# 只有在auto模式下才恢复exit_focus_chat动作
|
|
||||||
if global_config.chat.chat_mode == "auto":
|
|
||||||
self.action_manager.add_action_to_using("exit_focus_chat")
|
|
||||||
logger.debug(f"{self.log_prefix}恢复exit_focus_chat动作")
|
|
||||||
else:
|
|
||||||
logger.debug(
|
|
||||||
f"{self.log_prefix}跳过恢复exit_focus_chat动作,原因: chat_mode不为auto(当前模式: {global_config.chat.chat_mode})"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"{self.log_prefix}激活类型判定完成,最终可用动作: {list(final_activated_actions.keys())}")
|
logger.info(f"{self.log_prefix}激活类型判定完成,最终可用动作: {list(final_activated_actions.keys())}")
|
||||||
|
|
||||||
|
|
@ -576,30 +555,13 @@ class ActionModifier:
|
||||||
if not recent_cycles:
|
if not recent_cycles:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 统计no_reply的数量
|
|
||||||
no_reply_count = 0
|
|
||||||
reply_sequence = [] # 记录最近的动作序列
|
reply_sequence = [] # 记录最近的动作序列
|
||||||
|
|
||||||
for cycle in recent_cycles:
|
for cycle in recent_cycles:
|
||||||
action_result = cycle.loop_plan_info.get("action_result", {})
|
action_result = cycle.loop_plan_info.get("action_result", {})
|
||||||
action_type = action_result.get("action_type", "unknown")
|
action_type = action_result.get("action_type", "unknown")
|
||||||
if action_type == "no_reply":
|
|
||||||
no_reply_count += 1
|
|
||||||
reply_sequence.append(action_type == "reply")
|
reply_sequence.append(action_type == "reply")
|
||||||
|
|
||||||
# 检查no_reply比例
|
|
||||||
if len(recent_cycles) >= (4 * global_config.chat.exit_focus_threshold) and (
|
|
||||||
no_reply_count / len(recent_cycles)
|
|
||||||
) >= (0.7 * global_config.chat.exit_focus_threshold):
|
|
||||||
if global_config.chat.chat_mode == "auto":
|
|
||||||
result["add"].append("exit_focus_chat")
|
|
||||||
result["remove"].append("no_reply")
|
|
||||||
result["remove"].append("reply")
|
|
||||||
no_reply_ratio = no_reply_count / len(recent_cycles)
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix}检测到高no_reply比例: {no_reply_ratio:.2f},达到退出聊天阈值,将添加exit_focus_chat并移除no_reply/reply动作"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 计算连续回复的相关阈值
|
# 计算连续回复的相关阈值
|
||||||
|
|
||||||
max_reply_num = int(global_config.focus_chat.consecutive_replies * 3.2)
|
max_reply_num = int(global_config.focus_chat.consecutive_replies * 3.2)
|
||||||
|
|
@ -613,7 +575,7 @@ class ActionModifier:
|
||||||
last_max_reply_num = reply_sequence[:]
|
last_max_reply_num = reply_sequence[:]
|
||||||
|
|
||||||
# 详细打印阈值和序列信息,便于调试
|
# 详细打印阈值和序列信息,便于调试
|
||||||
logger.debug(
|
logger.info(
|
||||||
f"连续回复阈值: max={max_reply_num}, sec={sec_thres_reply_num}, one={one_thres_reply_num},"
|
f"连续回复阈值: max={max_reply_num}, sec={sec_thres_reply_num}, one={one_thres_reply_num},"
|
||||||
f"最近reply序列: {last_max_reply_num}"
|
f"最近reply序列: {last_max_reply_num}"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from src.common.message.api import get_global_api
|
||||||
# from ...common.database import db # 数据库依赖似乎不需要了,注释掉
|
# from ...common.database import db # 数据库依赖似乎不需要了,注释掉
|
||||||
from .message import MessageSending, MessageThinking, MessageSet
|
from .message import MessageSending, MessageThinking, MessageSet
|
||||||
|
|
||||||
from .storage import MessageStorage
|
from src.chat.message_receive.storage import MessageStorage
|
||||||
from ...config.config import global_config
|
from ...config.config import global_config
|
||||||
from ..utils.utils import truncate_message, calculate_typing_time, count_messages_between
|
from ..utils.utils import truncate_message, calculate_typing_time, count_messages_between
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,12 @@ class MessageStorage:
|
||||||
# 莫越权 救世啊
|
# 莫越权 救世啊
|
||||||
pattern = r"<MainRule>.*?</MainRule>|<schedule>.*?</schedule>|<UserMessage>.*?</UserMessage>"
|
pattern = r"<MainRule>.*?</MainRule>|<schedule>.*?</schedule>|<UserMessage>.*?</UserMessage>"
|
||||||
|
|
||||||
|
# print(message)
|
||||||
|
|
||||||
processed_plain_text = message.processed_plain_text
|
processed_plain_text = message.processed_plain_text
|
||||||
|
|
||||||
|
# print(processed_plain_text)
|
||||||
|
|
||||||
if processed_plain_text:
|
if processed_plain_text:
|
||||||
filtered_processed_plain_text = re.sub(pattern, "", processed_plain_text, flags=re.DOTALL)
|
filtered_processed_plain_text = re.sub(pattern, "", processed_plain_text, flags=re.DOTALL)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -563,21 +563,21 @@ class NormalChat:
|
||||||
self.interest_dict.pop(msg_id, None)
|
self.interest_dict.pop(msg_id, None)
|
||||||
|
|
||||||
# 创建并行任务列表
|
# 创建并行任务列表
|
||||||
tasks = []
|
coroutines = []
|
||||||
for msg_id, (message, interest_value, is_mentioned) in items_to_process:
|
for msg_id, (message, interest_value, is_mentioned) in items_to_process:
|
||||||
task = process_single_message(msg_id, message, interest_value, is_mentioned)
|
coroutine = process_single_message(msg_id, message, interest_value, is_mentioned)
|
||||||
tasks.append(task)
|
coroutines.append(coroutine)
|
||||||
|
|
||||||
# 并行执行所有任务,限制并发数量避免资源过度消耗
|
# 并行执行所有任务,限制并发数量避免资源过度消耗
|
||||||
if tasks:
|
if coroutines:
|
||||||
# 使用信号量控制并发数,最多同时处理5个消息
|
# 使用信号量控制并发数,最多同时处理5个消息
|
||||||
semaphore = asyncio.Semaphore(5)
|
semaphore = asyncio.Semaphore(5)
|
||||||
|
|
||||||
async def limited_process(task, sem):
|
async def limited_process(coroutine, sem):
|
||||||
async with sem:
|
async with sem:
|
||||||
await task
|
await coroutine
|
||||||
|
|
||||||
limited_tasks = [limited_process(task, semaphore) for task in tasks]
|
limited_tasks = [limited_process(coroutine, semaphore) for coroutine in coroutines]
|
||||||
await asyncio.gather(*limited_tasks, return_exceptions=True)
|
await asyncio.gather(*limited_tasks, return_exceptions=True)
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
|
|
|
||||||
|
|
@ -423,8 +423,7 @@ class RelationshipManager:
|
||||||
|
|
||||||
请根据你对ta过去的了解,和ta最近的行为,修改,整合,原有的了解,总结出对用户 {person_name}(昵称:{nickname})新的了解。
|
请根据你对ta过去的了解,和ta最近的行为,修改,整合,原有的了解,总结出对用户 {person_name}(昵称:{nickname})新的了解。
|
||||||
|
|
||||||
了解可以包含性格,关系,感受,态度,你推测的ta的性别,年龄,外貌,身份,习惯,爱好,重要事件,重要经历等等内容。也可以包含其他点。
|
了解请包含性格,对你的态度,你推测的ta的年龄,身份,习惯,爱好,重要事件和其他重要属性这几方面内容。
|
||||||
关注友好和不友好的因素,不要忽略。
|
|
||||||
请严格按照以下给出的信息,不要新增额外内容。
|
请严格按照以下给出的信息,不要新增额外内容。
|
||||||
|
|
||||||
你之前对他的了解是:
|
你之前对他的了解是:
|
||||||
|
|
@ -467,23 +466,23 @@ class RelationshipManager:
|
||||||
|
|
||||||
relation_value_prompt = f"""
|
relation_value_prompt = f"""
|
||||||
你的名字是{global_config.bot.nickname}。
|
你的名字是{global_config.bot.nickname}。
|
||||||
你对{person_name}的了解如下:
|
你最近对{person_name}的了解如下:
|
||||||
{compressed_summary}
|
{points_text}
|
||||||
|
|
||||||
请根据以上信息,评估你和{person_name}的关系,给出两个维度的值:熟悉度和好感度。
|
请根据以上信息,评估你和{person_name}的关系,给出两个维度的值:熟悉度和好感度。
|
||||||
1. **熟悉度 (familiarity_value)**: 0-100的整数,表示你对ta的熟悉程度。
|
1. 了解度 (familiarity_value): 0-100的整数,表示这些信息让你对ta的了解增进程度。
|
||||||
- 0: 完全陌生
|
- 0: 没有任何进一步了解
|
||||||
- 25: 有点眼熟
|
- 25: 有点进一步了解
|
||||||
- 50: 比较熟悉
|
- 50: 有进一步了解
|
||||||
- 75: 很熟悉
|
- 75: 有更多了解
|
||||||
- 100: 非常熟悉,了如指掌
|
- 100: 有了更多重要的了解
|
||||||
|
|
||||||
2. **好感度 (liking_value)**: 0-100的整数,表示你对ta的喜好程度。
|
2. **好感度 (liking_value)**: 0-100的整数,表示这些信息让你对ta的喜。
|
||||||
- 0: 非常厌恶
|
- 0: 非常厌恶
|
||||||
- 25: 有点反感
|
- 25: 有点反感
|
||||||
- 50: 中立/无感
|
- 50: 中立/无感
|
||||||
- 75: 有点喜欢
|
- 75: 有点喜欢
|
||||||
- 100: 非常喜欢/挚友
|
- 100: 非常喜欢/开心对这个人
|
||||||
|
|
||||||
请严格按照json格式输出,不要有其他多余内容:
|
请严格按照json格式输出,不要有其他多余内容:
|
||||||
{{
|
{{
|
||||||
|
|
@ -501,19 +500,20 @@ class RelationshipManager:
|
||||||
new_familiarity_value = int(relation_value_json.get("familiarity_value", 0))
|
new_familiarity_value = int(relation_value_json.get("familiarity_value", 0))
|
||||||
new_liking_value = int(relation_value_json.get("liking_value", 50))
|
new_liking_value = int(relation_value_json.get("liking_value", 50))
|
||||||
|
|
||||||
# 获取数据库中的旧值,如果不存在则使用默认值
|
if new_familiarity_value > 25:
|
||||||
old_familiarity_value = await person_info_manager.get_value(person_id, "familiarity_value") or 0
|
old_familiarity_value = await person_info_manager.get_value(person_id, "familiarity_value") or 0
|
||||||
old_liking_value = await person_info_manager.get_value(person_id, "liking_value") or 50
|
old_familiarity_value += new_familiarity_value - 25 / 75
|
||||||
|
|
||||||
# 计算平均值
|
if new_liking_value > 50:
|
||||||
final_familiarity_value = (old_familiarity_value + new_familiarity_value) // 2
|
liking_value = await person_info_manager.get_value(person_id, "liking_value") or 50
|
||||||
final_liking_value = (old_liking_value + new_liking_value) // 2
|
liking_value += new_liking_value - 50 / 50
|
||||||
|
if new_liking_value < 50:
|
||||||
|
liking_value = await person_info_manager.get_value(person_id, "liking_value") or 50
|
||||||
|
liking_value -= (50 - new_liking_value / 50) * 1.5
|
||||||
|
|
||||||
await person_info_manager.update_one_field(person_id, "familiarity_value", final_familiarity_value)
|
await person_info_manager.update_one_field(person_id, "familiarity_value", liking_value)
|
||||||
await person_info_manager.update_one_field(person_id, "liking_value", final_liking_value)
|
await person_info_manager.update_one_field(person_id, "liking_value", liking_value)
|
||||||
logger.info(
|
logger.info(f"更新了与 {person_name} 的关系值: 熟悉度={liking_value}, 好感度={liking_value}")
|
||||||
f"更新了与 {person_name} 的关系值: 熟悉度={final_familiarity_value}, 好感度={final_liking_value}"
|
|
||||||
)
|
|
||||||
except (json.JSONDecodeError, ValueError, TypeError) as e:
|
except (json.JSONDecodeError, ValueError, TypeError) as e:
|
||||||
logger.error(f"解析relation_value JSON失败或值无效: {e}, 响应: {relation_value_response}")
|
logger.error(f"解析relation_value JSON失败或值无效: {e}, 响应: {relation_value_response}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,16 +39,6 @@
|
||||||
"type": "action",
|
"type": "action",
|
||||||
"name": "emoji",
|
"name": "emoji",
|
||||||
"description": "发送表情包辅助表达情绪"
|
"description": "发送表情包辅助表达情绪"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "action",
|
|
||||||
"name": "change_to_focus_chat",
|
|
||||||
"description": "切换到专注聊天,从普通模式切换到专注模式"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "action",
|
|
||||||
"name": "exit_focus_chat",
|
|
||||||
"description": "退出专注聊天,从专注模式切换到普通模式"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,569 @@
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
# 导入新插件系统
|
||||||
|
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.config.config import global_config
|
||||||
|
from json_repair import repair_json
|
||||||
|
|
||||||
|
logger = get_logger("core_actions")
|
||||||
|
|
||||||
|
|
||||||
|
class NoReplyAction(BaseAction):
|
||||||
|
"""不回复动作,使用智能判断机制决定何时结束等待
|
||||||
|
|
||||||
|
新的等待逻辑:
|
||||||
|
- 每0.2秒检查是否有新消息(提高响应性)
|
||||||
|
- 如果累计消息数量达到阈值(默认20条),直接结束等待
|
||||||
|
- 有新消息时进行LLM判断,但最快1秒一次(防止过于频繁)
|
||||||
|
- 如果判断需要回复,则结束等待;否则继续等待
|
||||||
|
- 达到最大超时时间后强制结束
|
||||||
|
"""
|
||||||
|
|
||||||
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
# focus_activation_type = ActionActivationType.RANDOM
|
||||||
|
normal_activation_type = ActionActivationType.NEVER
|
||||||
|
mode_enable = ChatMode.FOCUS
|
||||||
|
parallel_action = False
|
||||||
|
|
||||||
|
# 动作基本信息
|
||||||
|
action_name = "no_reply"
|
||||||
|
action_description = "暂时不回复消息"
|
||||||
|
|
||||||
|
# 连续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 # 频率检查窗口时间(秒)
|
||||||
|
|
||||||
|
# 动作参数定义
|
||||||
|
action_parameters = {"reason": "不回复的原因"}
|
||||||
|
|
||||||
|
# 动作使用场景
|
||||||
|
action_require = ["你发送了消息,目前无人回复"]
|
||||||
|
|
||||||
|
# 关联类型
|
||||||
|
associated_types = []
|
||||||
|
|
||||||
|
async def execute(self) -> Tuple[bool, str]:
|
||||||
|
"""执行不回复动作,有新消息时进行判断,但最快1秒一次"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 增加连续计数
|
||||||
|
NoReplyAction._consecutive_count += 1
|
||||||
|
count = NoReplyAction._consecutive_count
|
||||||
|
|
||||||
|
reason = self.action_data.get("reason", "")
|
||||||
|
start_time = time.time()
|
||||||
|
last_judge_time = 0 # 上次进行LLM判断的时间
|
||||||
|
min_judge_interval = self._min_judge_interval # 最小判断间隔,从配置获取
|
||||||
|
check_interval = 0.2 # 检查新消息的间隔,设为0.2秒提高响应性
|
||||||
|
|
||||||
|
# 累积判断历史
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建上下文字符串
|
||||||
|
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}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
current_time = time.time()
|
||||||
|
elapsed_time = current_time - start_time
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if elapsed_time >= self._max_timeout:
|
||||||
|
logger.info(f"{self.log_prefix} 达到最大等待时间{self._max_timeout}秒,退出专注模式")
|
||||||
|
# 标记退出专注模式
|
||||||
|
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
|
||||||
|
|
||||||
|
# **新增**:检查回复频率,决定是否退出专注模式
|
||||||
|
should_exit_focus = await self._check_frequency_and_exit_focus(current_time)
|
||||||
|
if should_exit_focus:
|
||||||
|
logger.info(f"{self.log_prefix} 检测到回复频率过高,退出专注模式")
|
||||||
|
# 标记退出专注模式
|
||||||
|
self.action_data["_system_command"] = "stop_focus_chat"
|
||||||
|
exit_reason = f"{global_config.bot.nickname}(你)发现自己回复太频繁了,决定退出专注模式,稍作休息"
|
||||||
|
await self.store_action_info(
|
||||||
|
action_build_into_prompt=True,
|
||||||
|
action_prompt_display=exit_reason,
|
||||||
|
action_done=True,
|
||||||
|
)
|
||||||
|
return True, exit_reason
|
||||||
|
|
||||||
|
# **新增**:检查过去10分钟是否完全没有发言,如果是则退出专注模式
|
||||||
|
should_exit_no_activity = await self._check_no_activity_and_exit_focus(current_time)
|
||||||
|
if should_exit_no_activity:
|
||||||
|
logger.info(f"{self.log_prefix} 检测到过去10分钟完全没有发言,退出专注模式")
|
||||||
|
# 标记退出专注模式
|
||||||
|
self.action_data["_system_command"] = "stop_focus_chat"
|
||||||
|
exit_reason = f"{global_config.bot.nickname}(你)发现自己过去10分钟完全没有说话,感觉可能不太活跃,决定退出专注模式"
|
||||||
|
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(
|
||||||
|
chat_id=self.chat_id, start_time=start_time, end_time=current_time
|
||||||
|
)
|
||||||
|
|
||||||
|
# 如果累计消息数量达到阈值,直接结束等待
|
||||||
|
if new_message_count >= self._auto_exit_message_count:
|
||||||
|
logger.info(f"{self.log_prefix} 累计消息数量达到{new_message_count}条,直接结束等待")
|
||||||
|
exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复"
|
||||||
|
await self.store_action_info(
|
||||||
|
action_build_into_prompt=True,
|
||||||
|
action_prompt_display=exit_reason,
|
||||||
|
action_done=True,
|
||||||
|
)
|
||||||
|
return True, f"累计消息数量达到{new_message_count}条,直接结束等待 (等待时间: {elapsed_time:.1f}秒)"
|
||||||
|
|
||||||
|
# 判定条件:累计3条消息或等待超过5秒且有新消息
|
||||||
|
time_since_last_judge = current_time - last_judge_time
|
||||||
|
should_judge = (
|
||||||
|
new_message_count >= 3 # 累计3条消息
|
||||||
|
or (new_message_count > 0 and time_since_last_judge >= 5.0) # 等待超过5秒且有新消息
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_judge and time_since_last_judge >= min_judge_interval:
|
||||||
|
# 判断触发原因
|
||||||
|
trigger_reason = ""
|
||||||
|
if new_message_count >= 3:
|
||||||
|
trigger_reason = f"累计{new_message_count}条消息"
|
||||||
|
elif time_since_last_judge >= 5.0:
|
||||||
|
trigger_reason = f"等待{time_since_last_judge:.1f}秒且有{new_message_count}条新消息"
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取身份信息
|
||||||
|
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 = False # 是否跳过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:
|
||||||
|
# 检查消息发送者是否是bot
|
||||||
|
sender_id = message.get("user_id", "")
|
||||||
|
|
||||||
|
if sender_id == user_id:
|
||||||
|
bot_message_count += 1
|
||||||
|
|
||||||
|
talk_frequency_threshold = global_config.chat.talk_frequency * 10
|
||||||
|
|
||||||
|
if bot_message_count > talk_frequency_threshold:
|
||||||
|
over_count = bot_message_count - talk_frequency_threshold
|
||||||
|
|
||||||
|
# 根据超过的数量设置不同的提示词和跳过概率
|
||||||
|
skip_probability = 0
|
||||||
|
if over_count <= 3:
|
||||||
|
frequency_block = "你感觉稍微有些累,回复的有点多了。\n"
|
||||||
|
elif over_count <= 5:
|
||||||
|
frequency_block = "你今天说话比较多,感觉有点疲惫,想要稍微休息一下。\n"
|
||||||
|
else:
|
||||||
|
frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n"
|
||||||
|
skip_probability = self._skip_probability
|
||||||
|
|
||||||
|
# 根据配置和概率决定是否跳过LLM判断
|
||||||
|
if self._skip_judge_when_tired and random.random() < skip_probability:
|
||||||
|
should_skip_llm_judge = True
|
||||||
|
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},添加疲惫提示"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 回复次数少时的正向提示
|
||||||
|
under_count = talk_frequency_threshold - bot_message_count
|
||||||
|
|
||||||
|
if under_count >= talk_frequency_threshold * 0.8: # 回复很少(少于20%)
|
||||||
|
frequency_block = "你感觉精力充沛,状态很好。\n"
|
||||||
|
elif under_count >= talk_frequency_threshold * 0.5: # 回复较少(少于50%)
|
||||||
|
frequency_block = "你感觉状态不错。\n"
|
||||||
|
else: # 刚好达到阈值
|
||||||
|
frequency_block = ""
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,未超过阈值{talk_frequency_threshold},添加正向提示"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"{self.log_prefix} 检查发言频率时出错: {e}")
|
||||||
|
frequency_block = ""
|
||||||
|
|
||||||
|
# 如果决定跳过LLM判断,直接更新时间并继续等待
|
||||||
|
if should_skip_llm_judge:
|
||||||
|
last_judge_time = time.time() # 更新判断时间,避免立即重新判断
|
||||||
|
continue # 跳过本次LLM判断,继续循环等待
|
||||||
|
|
||||||
|
# 构建判断上下文
|
||||||
|
judge_prompt = f"""
|
||||||
|
{identity_block}
|
||||||
|
|
||||||
|
你现在正在QQ群参与聊天,以下是聊天内容:
|
||||||
|
{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)
|
||||||
|
|
||||||
|
print(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.info(f"{self.log_prefix} 模型({model_name})原始JSON响应: {response}")
|
||||||
|
|
||||||
|
# 解析LLM的JSON响应,提取判断结果和理由
|
||||||
|
judge_result, reason = self._parse_llm_judge_response(response)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix} JSON解析结果 - 判断: {judge_result}, 理由: {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() # 异常时也更新时间,避免频繁重试
|
||||||
|
|
||||||
|
# 每10秒输出一次等待状态
|
||||||
|
if elapsed_time < 60:
|
||||||
|
if int(elapsed_time) % 10 == 0 and int(elapsed_time) > 0:
|
||||||
|
logger.info(f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,等待新消息...")
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
else:
|
||||||
|
if int(elapsed_time) % 60 == 0 and int(elapsed_time) > 0:
|
||||||
|
logger.info(f"{self.log_prefix} 已等待{elapsed_time / 60:.0f}分钟,等待新消息...")
|
||||||
|
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},你思考是否要进行回复"
|
||||||
|
await self.store_action_info(
|
||||||
|
action_build_into_prompt=True,
|
||||||
|
action_prompt_display=full_prompt,
|
||||||
|
action_done=True,
|
||||||
|
)
|
||||||
|
return False, f"不回复动作执行失败: {e}"
|
||||||
|
|
||||||
|
async def _check_frequency_and_exit_focus(self, current_time: float) -> bool:
|
||||||
|
"""检查回复频率,决定是否退出专注模式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_time: 当前时间戳
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否应该退出专注模式
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 只在auto模式下进行频率检查
|
||||||
|
if global_config.chat.chat_mode != "auto":
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取检查窗口内的所有消息
|
||||||
|
window_start_time = current_time - self._frequency_check_window
|
||||||
|
all_messages = message_api.get_messages_by_time_in_chat(
|
||||||
|
chat_id=self.chat_id,
|
||||||
|
start_time=window_start_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
|
||||||
|
|
||||||
|
# 计算当前回复频率(每分钟回复数)
|
||||||
|
window_minutes = self._frequency_check_window / 60
|
||||||
|
current_frequency = bot_message_count / window_minutes
|
||||||
|
|
||||||
|
# 计算阈值频率:使用 exit_focus_threshold * 1.5
|
||||||
|
threshold_multiplier = global_config.chat.exit_focus_threshold * 1.5
|
||||||
|
threshold_frequency = global_config.chat.talk_frequency * threshold_multiplier
|
||||||
|
|
||||||
|
# 判断是否超过阈值
|
||||||
|
if current_frequency > threshold_frequency:
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix} 回复频率检查:当前频率 {current_frequency:.2f}/分钟,超过阈值 {threshold_frequency:.2f}/分钟 (exit_threshold={global_config.chat.exit_focus_threshold} * 1.5),准备退出专注模式"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
f"{self.log_prefix} 回复频率检查:当前频率 {current_frequency:.2f}/分钟,未超过阈值 {threshold_frequency:.2f}/分钟 (exit_threshold={global_config.chat.exit_focus_threshold} * 1.5)"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{self.log_prefix} 检查回复频率时出错: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async 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解析失败,回退到简单的关键词匹配
|
||||||
|
try:
|
||||||
|
response_lower = response.lower()
|
||||||
|
|
||||||
|
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]}..."
|
||||||
|
|
||||||
|
logger.debug(f"{self.log_prefix} 文本解析结果 - 判断: {judge_result}, 理由: {reason}")
|
||||||
|
return judge_result, reason
|
||||||
|
|
||||||
|
except Exception as fallback_e:
|
||||||
|
logger.error(f"{self.log_prefix} 文本解析也失败: {fallback_e}")
|
||||||
|
return "不需要回复", f"解析异常: {str(e)}, 回退解析也失败: {str(fallback_e)}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{self.log_prefix} 解析LLM响应时出错: {e}")
|
||||||
|
return "不需要回复", f"解析异常: {str(e)}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reset_consecutive_count(cls):
|
||||||
|
"""重置连续计数器"""
|
||||||
|
cls._consecutive_count = 0
|
||||||
|
logger.debug("NoReplyAction连续计数器已重置")
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
import json
|
|
||||||
from typing import List, Tuple, Type
|
from typing import List, Tuple, Type
|
||||||
|
|
||||||
# 导入新插件系统
|
# 导入新插件系统
|
||||||
|
|
@ -18,10 +17,8 @@ from src.plugin_system.base.config_types import ConfigField
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
# 导入API模块 - 标准Python包方式
|
# 导入API模块 - 标准Python包方式
|
||||||
from src.plugin_system.apis import emoji_api, generator_api, message_api, llm_api
|
from src.plugin_system.apis import emoji_api, generator_api, message_api
|
||||||
from src.config.config import global_config
|
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
||||||
from datetime import datetime
|
|
||||||
from json_repair import repair_json
|
|
||||||
|
|
||||||
logger = get_logger("core_actions")
|
logger = get_logger("core_actions")
|
||||||
|
|
||||||
|
|
@ -112,424 +109,6 @@ class ReplyAction(BaseAction):
|
||||||
return False, f"回复失败: {str(e)}"
|
return False, f"回复失败: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
class NoReplyAction(BaseAction):
|
|
||||||
"""不回复动作,使用智能判断机制决定何时结束等待
|
|
||||||
|
|
||||||
新的等待逻辑:
|
|
||||||
- 每0.2秒检查是否有新消息(提高响应性)
|
|
||||||
- 如果累计消息数量达到阈值(默认20条),直接结束等待
|
|
||||||
- 有新消息时进行LLM判断,但最快1秒一次(防止过于频繁)
|
|
||||||
- 如果判断需要回复,则结束等待;否则继续等待
|
|
||||||
- 达到最大超时时间后强制结束
|
|
||||||
"""
|
|
||||||
|
|
||||||
focus_activation_type = ActionActivationType.ALWAYS
|
|
||||||
# focus_activation_type = ActionActivationType.RANDOM
|
|
||||||
normal_activation_type = ActionActivationType.NEVER
|
|
||||||
mode_enable = ChatMode.FOCUS
|
|
||||||
parallel_action = False
|
|
||||||
|
|
||||||
# 动作基本信息
|
|
||||||
action_name = "no_reply"
|
|
||||||
action_description = "暂时不回复消息"
|
|
||||||
|
|
||||||
# 连续no_reply计数器
|
|
||||||
_consecutive_count = 0
|
|
||||||
|
|
||||||
# LLM判断的最小间隔时间
|
|
||||||
_min_judge_interval = 1.0 # 最快1秒一次LLM判断
|
|
||||||
|
|
||||||
# 自动结束的消息数量阈值
|
|
||||||
_auto_exit_message_count = 20 # 累计20条消息自动结束
|
|
||||||
|
|
||||||
# 最大等待超时时间
|
|
||||||
_max_timeout = 1200 # 1200秒
|
|
||||||
|
|
||||||
# 跳过LLM判断的配置
|
|
||||||
_skip_judge_when_tired = True
|
|
||||||
_skip_probability_light = 0.2 # 轻度疲惫跳过概率
|
|
||||||
_skip_probability_medium = 0.4 # 中度疲惫跳过概率
|
|
||||||
_skip_probability_heavy = 0.6 # 重度疲惫跳过概率
|
|
||||||
|
|
||||||
# 动作参数定义
|
|
||||||
action_parameters = {"reason": "不回复的原因"}
|
|
||||||
|
|
||||||
# 动作使用场景
|
|
||||||
action_require = ["你发送了消息,目前无人回复"]
|
|
||||||
|
|
||||||
# 关联类型
|
|
||||||
associated_types = []
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
|
||||||
"""执行不回复动作,有新消息时进行判断,但最快1秒一次"""
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 增加连续计数
|
|
||||||
NoReplyAction._consecutive_count += 1
|
|
||||||
count = NoReplyAction._consecutive_count
|
|
||||||
|
|
||||||
reason = self.action_data.get("reason", "")
|
|
||||||
start_time = time.time()
|
|
||||||
last_judge_time = 0 # 上次进行LLM判断的时间
|
|
||||||
min_judge_interval = self._min_judge_interval # 最小判断间隔,从配置获取
|
|
||||||
check_interval = 0.2 # 检查新消息的间隔,设为0.2秒提高响应性
|
|
||||||
|
|
||||||
# 累积判断历史
|
|
||||||
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",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 构建上下文字符串
|
|
||||||
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}")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
current_time = time.time()
|
|
||||||
elapsed_time = current_time - start_time
|
|
||||||
|
|
||||||
# 检查是否超时
|
|
||||||
if elapsed_time >= self._max_timeout:
|
|
||||||
logger.info(f"{self.log_prefix} 达到最大等待时间{self._max_timeout}秒,结束等待")
|
|
||||||
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(
|
|
||||||
chat_id=self.chat_id, start_time=start_time, end_time=current_time
|
|
||||||
)
|
|
||||||
|
|
||||||
# 如果累计消息数量达到阈值,直接结束等待
|
|
||||||
if new_message_count >= self._auto_exit_message_count:
|
|
||||||
logger.info(f"{self.log_prefix} 累计消息数量达到{new_message_count}条,直接结束等待")
|
|
||||||
exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复"
|
|
||||||
await self.store_action_info(
|
|
||||||
action_build_into_prompt=True,
|
|
||||||
action_prompt_display=exit_reason,
|
|
||||||
action_done=True,
|
|
||||||
)
|
|
||||||
return True, f"累计消息数量达到{new_message_count}条,直接结束等待 (等待时间: {elapsed_time:.1f}秒)"
|
|
||||||
|
|
||||||
# 判定条件:累计3条消息或等待超过5秒且有新消息
|
|
||||||
time_since_last_judge = current_time - last_judge_time
|
|
||||||
should_judge = (
|
|
||||||
new_message_count >= 3 # 累计3条消息
|
|
||||||
or (new_message_count > 0 and time_since_last_judge >= 5.0) # 等待超过5秒且有新消息
|
|
||||||
)
|
|
||||||
|
|
||||||
if should_judge and time_since_last_judge >= min_judge_interval:
|
|
||||||
# 判断触发原因
|
|
||||||
trigger_reason = ""
|
|
||||||
if new_message_count >= 3:
|
|
||||||
trigger_reason = f"累计{new_message_count}条消息"
|
|
||||||
elif time_since_last_judge >= 5.0:
|
|
||||||
trigger_reason = f"等待{time_since_last_judge:.1f}秒且有{new_message_count}条新消息"
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 参考simple_planner构建更完整的判断信息
|
|
||||||
# 获取时间信息
|
|
||||||
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
||||||
|
|
||||||
# 获取身份信息
|
|
||||||
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 = False # 是否跳过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:
|
|
||||||
# 检查消息发送者是否是bot
|
|
||||||
sender_id = message.get("user_id", "")
|
|
||||||
|
|
||||||
if sender_id == user_id:
|
|
||||||
bot_message_count += 1
|
|
||||||
|
|
||||||
talk_frequency_threshold = global_config.chat.talk_frequency * 10
|
|
||||||
|
|
||||||
if bot_message_count > talk_frequency_threshold:
|
|
||||||
over_count = bot_message_count - talk_frequency_threshold
|
|
||||||
|
|
||||||
# 根据超过的数量设置不同的提示词和跳过概率
|
|
||||||
if over_count <= 3:
|
|
||||||
frequency_block = "你感觉稍微有些累,回复的有点多了。\n"
|
|
||||||
elif over_count <= 5:
|
|
||||||
frequency_block = "你今天说话比较多,感觉有点疲惫,想要稍微休息一下。\n"
|
|
||||||
else:
|
|
||||||
frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n"
|
|
||||||
skip_probability = self._skip_probability_heavy
|
|
||||||
|
|
||||||
# 根据配置和概率决定是否跳过LLM判断
|
|
||||||
if self._skip_judge_when_tired and random.random() < skip_probability:
|
|
||||||
should_skip_llm_judge = True
|
|
||||||
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},添加疲惫提示"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# 回复次数少时的正向提示
|
|
||||||
under_count = talk_frequency_threshold - bot_message_count
|
|
||||||
|
|
||||||
if under_count >= talk_frequency_threshold * 0.8: # 回复很少(少于20%)
|
|
||||||
frequency_block = "你感觉精力充沛,状态很好。\n"
|
|
||||||
elif under_count >= talk_frequency_threshold * 0.5: # 回复较少(少于50%)
|
|
||||||
frequency_block = "你感觉状态不错。\n"
|
|
||||||
else: # 刚好达到阈值
|
|
||||||
frequency_block = ""
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix} 过去10分钟发言{bot_message_count}条,未超过阈值{talk_frequency_threshold},添加正向提示"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"{self.log_prefix} 检查发言频率时出错: {e}")
|
|
||||||
frequency_block = ""
|
|
||||||
|
|
||||||
# 如果决定跳过LLM判断,直接更新时间并继续等待
|
|
||||||
if should_skip_llm_judge:
|
|
||||||
last_judge_time = time.time() # 更新判断时间,避免立即重新判断
|
|
||||||
start_time = current_time # 更新开始时间,避免重复计算同样的消息
|
|
||||||
continue # 跳过本次LLM判断,继续循环等待
|
|
||||||
|
|
||||||
# 构建判断上下文
|
|
||||||
judge_prompt = f"""
|
|
||||||
{time_block}
|
|
||||||
{identity_block}
|
|
||||||
|
|
||||||
你现在正在QQ群参与聊天,以下是聊天内容:
|
|
||||||
{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)
|
|
||||||
|
|
||||||
print(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.info(f"{self.log_prefix} 模型({model_name})原始JSON响应: {response}")
|
|
||||||
|
|
||||||
# 解析LLM的JSON响应,提取判断结果和理由
|
|
||||||
judge_result, reason = self._parse_llm_judge_response(response)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix} JSON解析结果 - 判断: {judge_result}, 理由: {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() # 异常时也更新时间,避免频繁重试
|
|
||||||
|
|
||||||
# 每10秒输出一次等待状态
|
|
||||||
if int(elapsed_time) % 10 == 0 and int(elapsed_time) > 0:
|
|
||||||
logger.info(f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,等待新消息...")
|
|
||||||
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},你思考是否要进行回复"
|
|
||||||
await self.store_action_info(
|
|
||||||
action_build_into_prompt=True,
|
|
||||||
action_prompt_display=full_prompt,
|
|
||||||
action_done=True,
|
|
||||||
)
|
|
||||||
return False, f"不回复动作执行失败: {e}"
|
|
||||||
|
|
||||||
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解析失败,回退到简单的关键词匹配
|
|
||||||
try:
|
|
||||||
response_lower = response.lower()
|
|
||||||
|
|
||||||
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]}..."
|
|
||||||
|
|
||||||
logger.debug(f"{self.log_prefix} 文本解析结果 - 判断: {judge_result}, 理由: {reason}")
|
|
||||||
return judge_result, reason
|
|
||||||
|
|
||||||
except Exception as fallback_e:
|
|
||||||
logger.error(f"{self.log_prefix} 文本解析也失败: {fallback_e}")
|
|
||||||
return "不需要回复", f"解析异常: {str(e)}, 回退解析也失败: {str(fallback_e)}"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{self.log_prefix} 解析LLM响应时出错: {e}")
|
|
||||||
return "不需要回复", f"解析异常: {str(e)}"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def reset_consecutive_count(cls):
|
|
||||||
"""重置连续计数器"""
|
|
||||||
cls._consecutive_count = 0
|
|
||||||
logger.debug("NoReplyAction连续计数器已重置")
|
|
||||||
|
|
||||||
|
|
||||||
class EmojiAction(BaseAction):
|
class EmojiAction(BaseAction):
|
||||||
"""表情动作 - 发送表情包"""
|
"""表情动作 - 发送表情包"""
|
||||||
|
|
||||||
|
|
@ -596,67 +175,6 @@ class EmojiAction(BaseAction):
|
||||||
return False, f"表情发送失败: {str(e)}"
|
return False, f"表情发送失败: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
class ExitFocusChatAction(BaseAction):
|
|
||||||
"""退出专注聊天动作 - 从专注模式切换到普通模式"""
|
|
||||||
|
|
||||||
# 激活设置
|
|
||||||
focus_activation_type = ActionActivationType.NEVER
|
|
||||||
normal_activation_type = ActionActivationType.NEVER
|
|
||||||
mode_enable = ChatMode.FOCUS
|
|
||||||
parallel_action = False
|
|
||||||
|
|
||||||
# 动作基本信息
|
|
||||||
action_name = "exit_focus_chat"
|
|
||||||
action_description = "退出专注聊天,从专注模式切换到普通模式"
|
|
||||||
|
|
||||||
# LLM判断提示词
|
|
||||||
llm_judge_prompt = """
|
|
||||||
判定是否需要退出专注聊天的条件:
|
|
||||||
1. 很长时间没有回复,应该退出专注聊天
|
|
||||||
2. 当前内容不需要持续专注关注
|
|
||||||
3. 聊天内容已经完成,话题结束
|
|
||||||
|
|
||||||
请回答"是"或"否"。
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 动作参数定义
|
|
||||||
action_parameters = {}
|
|
||||||
|
|
||||||
# 动作使用场景
|
|
||||||
action_require = [
|
|
||||||
"很长时间没有回复,你决定退出专注聊天",
|
|
||||||
"当前内容不需要持续专注关注,你决定退出专注聊天",
|
|
||||||
"聊天内容已经完成,你决定退出专注聊天",
|
|
||||||
]
|
|
||||||
|
|
||||||
# 关联类型
|
|
||||||
associated_types = []
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
|
||||||
"""执行退出专注聊天动作"""
|
|
||||||
logger.info(f"{self.log_prefix} 决定退出专注聊天: {self.reasoning}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 标记状态切换请求
|
|
||||||
self._mark_state_change()
|
|
||||||
|
|
||||||
# 重置NoReplyAction的连续计数器
|
|
||||||
NoReplyAction.reset_consecutive_count()
|
|
||||||
|
|
||||||
status_message = "决定退出专注聊天模式"
|
|
||||||
return True, status_message
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{self.log_prefix} 退出专注聊天动作执行失败: {e}")
|
|
||||||
return False, f"退出专注聊天失败: {str(e)}"
|
|
||||||
|
|
||||||
def _mark_state_change(self):
|
|
||||||
"""标记状态切换请求"""
|
|
||||||
# 通过action_data传递状态切换命令
|
|
||||||
self.action_data["_system_command"] = "stop_focus_chat"
|
|
||||||
logger.info(f"{self.log_prefix} 已标记状态切换命令: stop_focus_chat")
|
|
||||||
|
|
||||||
|
|
||||||
@register_plugin
|
@register_plugin
|
||||||
class CoreActionsPlugin(BasePlugin):
|
class CoreActionsPlugin(BasePlugin):
|
||||||
"""核心动作插件
|
"""核心动作插件
|
||||||
|
|
@ -686,7 +204,7 @@ class CoreActionsPlugin(BasePlugin):
|
||||||
config_schema = {
|
config_schema = {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
||||||
"config_version": ConfigField(type=str, default="0.0.9", description="配置文件版本"),
|
"config_version": ConfigField(type=str, default="0.1.0", description="配置文件版本"),
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"enable_reply": ConfigField(type=bool, default=True, description="是否启用'回复'动作"),
|
"enable_reply": ConfigField(type=bool, default=True, description="是否启用'回复'动作"),
|
||||||
|
|
@ -742,7 +260,7 @@ class CoreActionsPlugin(BasePlugin):
|
||||||
auto_exit_message_count = self.get_config("no_reply.auto_exit_message_count", 20)
|
auto_exit_message_count = self.get_config("no_reply.auto_exit_message_count", 20)
|
||||||
NoReplyAction._auto_exit_message_count = auto_exit_message_count
|
NoReplyAction._auto_exit_message_count = auto_exit_message_count
|
||||||
|
|
||||||
max_timeout = self.get_config("no_reply.max_timeout", 1200)
|
max_timeout = self.get_config("no_reply.max_timeout", 600)
|
||||||
NoReplyAction._max_timeout = max_timeout
|
NoReplyAction._max_timeout = max_timeout
|
||||||
|
|
||||||
skip_judge_when_tired = self.get_config("no_reply.skip_judge_when_tired", True)
|
skip_judge_when_tired = self.get_config("no_reply.skip_judge_when_tired", True)
|
||||||
|
|
@ -757,6 +275,10 @@ class CoreActionsPlugin(BasePlugin):
|
||||||
skip_probability_heavy = self.get_config("no_reply.skip_probability_heavy", 0.6)
|
skip_probability_heavy = self.get_config("no_reply.skip_probability_heavy", 0.6)
|
||||||
NoReplyAction._skip_probability_heavy = skip_probability_heavy
|
NoReplyAction._skip_probability_heavy = skip_probability_heavy
|
||||||
|
|
||||||
|
# 新增:频率检测相关配置
|
||||||
|
frequency_check_window = self.get_config("no_reply.frequency_check_window", 600)
|
||||||
|
NoReplyAction._frequency_check_window = frequency_check_window
|
||||||
|
|
||||||
# --- 根据配置注册组件 ---
|
# --- 根据配置注册组件 ---
|
||||||
components = []
|
components = []
|
||||||
if self.get_config("components.enable_reply", True):
|
if self.get_config("components.enable_reply", True):
|
||||||
|
|
@ -765,8 +287,6 @@ class CoreActionsPlugin(BasePlugin):
|
||||||
components.append((NoReplyAction.get_action_info(), NoReplyAction))
|
components.append((NoReplyAction.get_action_info(), NoReplyAction))
|
||||||
if self.get_config("components.enable_emoji", True):
|
if self.get_config("components.enable_emoji", True):
|
||||||
components.append((EmojiAction.get_action_info(), EmojiAction))
|
components.append((EmojiAction.get_action_info(), EmojiAction))
|
||||||
if self.get_config("components.enable_exit_focus", True):
|
|
||||||
components.append((ExitFocusChatAction.get_action_info(), ExitFocusChatAction))
|
|
||||||
|
|
||||||
# components.append((DeepReplyAction.get_action_info(), DeepReplyAction))
|
# components.append((DeepReplyAction.get_action_info(), DeepReplyAction))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue