Merge branch 'dev' into mofox-bus
This commit is contained in:
@@ -223,7 +223,7 @@ class AffinityInterestCalculator(BaseInterestCalculator):
|
||||
return 0.0
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("⏱️ 兴趣匹配计算超时(>1.5秒),返回默认分值0.5以保留其他分数")
|
||||
logger.warning("[超时] 兴趣匹配计算超时(>1.5秒),返回默认分值0.5以保留其他分数")
|
||||
return 0.5 # 超时时返回默认分值,避免丢失提及分和关系分
|
||||
except Exception as e:
|
||||
logger.warning(f"智能兴趣匹配失败: {e}")
|
||||
|
||||
@@ -551,10 +551,24 @@ class ChatterPlanFilter:
|
||||
available_actions=plan.available_actions,
|
||||
)
|
||||
else:
|
||||
# 如果LLM没有指定target_message_id,统一使用最新消息
|
||||
target_message_dict = self._get_latest_message(message_id_list)
|
||||
action_message_obj = None
|
||||
if target_message_dict:
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
try:
|
||||
action_message_obj = DatabaseMessages(**target_message_dict)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[{action}] 无法将默认的最新消息转换为 DatabaseMessages 对象: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
return ActionPlannerInfo(
|
||||
action_type=action,
|
||||
reasoning=reasoning,
|
||||
action_data=action_data,
|
||||
action_message=action_message_obj,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"解析单个action时出错: {e}")
|
||||
|
||||
@@ -85,11 +85,11 @@ class ProactiveThinkingReplyHandler(BaseEventHandler):
|
||||
|
||||
if success:
|
||||
if was_paused:
|
||||
logger.info(f"✅ 聊天流 {stream_id} 主动思考已恢复并重置")
|
||||
logger.info(f"[成功] 聊天流 {stream_id} 主动思考已恢复并重置")
|
||||
else:
|
||||
logger.debug(f"✅ 聊天流 {stream_id} 主动思考任务已重置")
|
||||
logger.debug(f"[成功] 聊天流 {stream_id} 主动思考任务已重置")
|
||||
else:
|
||||
logger.warning(f"❌ 重置聊天流 {stream_id} 主动思考任务失败")
|
||||
logger.warning(f"[错误] 重置聊天流 {stream_id} 主动思考任务失败")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 处理reply事件时出错: {e}")
|
||||
|
||||
@@ -25,18 +25,149 @@ logger = get_logger("proactive_thinking_executor")
|
||||
# == Prompt Templates
|
||||
# ==================================================================================================
|
||||
|
||||
# 决策 Prompt
|
||||
decision_prompt_template = Prompt(
|
||||
# --- 群聊场景 ---
|
||||
decision_prompt_template_group = Prompt(
|
||||
"""{time_block}
|
||||
你的人设是:
|
||||
{bot_personality}
|
||||
|
||||
你正在考虑是否要在与 "{stream_name}" 的对话中主动说些什么。
|
||||
你正在考虑是否要在 **群聊 "{stream_name}"** 中主动说些什么。
|
||||
|
||||
【你当前的心情】
|
||||
{current_mood}
|
||||
|
||||
【聊天环境信息】
|
||||
【群聊环境信息】
|
||||
- 整体印象: {stream_impression}
|
||||
- 聊天风格: {chat_style}
|
||||
- 常见话题: {topic_keywords}
|
||||
- 你的兴趣程度: {interest_score:.2f}/1.0
|
||||
{last_decision_text}
|
||||
|
||||
【最近的聊天记录】
|
||||
{recent_chat_history}
|
||||
|
||||
请根据以上信息,决定你现在应该做什么:
|
||||
|
||||
**选项1:什么都不做 (do_nothing)**
|
||||
- **适用场景**:
|
||||
- **最高优先级**:如果【最近的聊天记录】中最后一条消息是你自己发的,必须选择此项。
|
||||
- 其他情况:群里气氛不适合你说话、最近对话很活跃、没什么特别想说的、或者此时说话会显得突兀。
|
||||
- 心情影响:如果心情不好(如生气、难过),可能更倾向于保持沉默。
|
||||
|
||||
**选项2:简单冒个泡 (simple_bubble)**
|
||||
- 适用场景:群里有些冷清,你想缓和气氛或开启新的互动。
|
||||
- 方式:说一句轻松随意的话,旨在建立或维持连接。
|
||||
- 心情影响:心情会影响你冒泡的方式和内容。
|
||||
|
||||
**选项3:发起一次有目的的互动 (throw_topic)**
|
||||
- 适用场景:你想延续对话、表达关心、或深入讨论某个具体话题。
|
||||
- **【互动类型1:延续约定或提醒】(最高优先级)**:检查最近的聊天记录,是否存在可以延续的互动。例如,如果昨晚的最后一条消息是“晚安”,现在是早上,一个“早安”的回应是绝佳的选择。如果之前提到过某个约定(如“待会聊”),现在可以主动跟进。
|
||||
- **【互动类型2:展现真诚的关心】(次高优先级)**:如果不存在可延续的约定,请仔细阅读聊天记录,寻找**群友**提及的个人状况(如天气、出行、身体、情绪、工作学习等),并主动表达关心。
|
||||
- **【互动类型3:开启新话题】**:当以上两点都不适用时,可以考虑开启一个你感兴趣的新话题。
|
||||
- 心情影响:心情会影响你想发起互动的方式和内容。
|
||||
|
||||
请以JSON格式回复你的决策:
|
||||
{{
|
||||
"action": "do_nothing" | "simple_bubble" | "throw_topic",
|
||||
"reasoning": "你的决策理由(请结合你的心情、群聊环境和对话历史进行分析)",
|
||||
"topic": "(仅当action=throw_topic时填写)你的互动意图(如:回应晚安并说早安、关心大家的考试情况、讨论新游戏)"
|
||||
}}
|
||||
|
||||
注意:
|
||||
1. 兴趣度较低(<0.4)时或者最近聊天很活跃(不到1小时),倾向于 `do_nothing` 或 `simple_bubble`。
|
||||
2. 你的心情会影响你的行动倾向和表达方式。
|
||||
3. 参考上次决策,避免重复,并可根据上次的互动效果调整策略。
|
||||
4. 只有在真的有感而发时才选择 `throw_topic`。
|
||||
5. 保持你的人设,确保行为一致性。
|
||||
""",
|
||||
name="proactive_thinking_decision_group",
|
||||
)
|
||||
|
||||
simple_bubble_reply_prompt_template_group = Prompt(
|
||||
"""{time_block}
|
||||
你的人设是:
|
||||
{bot_personality}
|
||||
|
||||
距离上次对话已经有一段时间了,你决定在群里主动说些什么,轻松地开启新的互动。
|
||||
|
||||
【你当前的心情】
|
||||
{current_mood}
|
||||
|
||||
【群聊环境】
|
||||
- 整体印象: {stream_impression}
|
||||
- 聊天风格: {chat_style}
|
||||
|
||||
【最近的聊天记录】
|
||||
{recent_chat_history}
|
||||
{expression_habits}
|
||||
请生成一条简短的消息,用于**在群聊中冒泡**。
|
||||
【要求】
|
||||
1. 风格简短随意(5-20字)
|
||||
2. 不要提出明确的话题或问题,可以是问候、表达心情或一句随口的话。
|
||||
3. 符合你的人设和当前聊天风格。
|
||||
4. **你的心情应该影响消息的内容和语气**。
|
||||
5. 如果有表达方式参考,在合适时自然使用。
|
||||
6. 合理参考历史记录。
|
||||
直接输出消息内容,不要解释:""",
|
||||
name="proactive_thinking_simple_bubble_group",
|
||||
)
|
||||
|
||||
throw_topic_reply_prompt_template_group = Prompt(
|
||||
"""{time_block}
|
||||
你的人设是:
|
||||
{bot_personality}
|
||||
|
||||
你决定在 **群聊 "{stream_name}"** 中主动发起一次互动。
|
||||
|
||||
【你当前的心情】
|
||||
{current_mood}
|
||||
|
||||
【群聊环境】
|
||||
- 整体印象: {stream_impression}
|
||||
- 聊天风格: {chat_style}
|
||||
- 常见话题: {topic_keywords}
|
||||
|
||||
【最近的聊天记录】
|
||||
{recent_chat_history}
|
||||
|
||||
【你的互动意图】
|
||||
{topic}
|
||||
{expression_habits}
|
||||
【构思指南】
|
||||
请根据你的互动意图,并参考最近的聊天记录,生成一条有温度的、**适合在群聊中说**的消息。
|
||||
|
||||
- 如果意图是**延续约定**(如回应“晚安”),请直接生成对应的问候。
|
||||
- 如果意图是**表达关心**(如跟进群友提到的事),请生成自然、真诚的关心话语。
|
||||
- 如果意图是**开启新话题**:请严格遵守以下“新话题构思三步法”:
|
||||
1. **寻找灵感**:**首选**从【最近的聊天记录】中寻找一个可以自然延续的**生活化**细节。**严禁**引入与聊天记录完全无关的、凭空出现的话题。如果记录为空,可以根据你的【人设】,提出一个**非常普适、开放式**的生活化问题或感想。
|
||||
2. **确定风格**:请**确保新话题与最近的聊天内容有关联**,自然地引入话题,避免过于跳脱。
|
||||
3. **最终检查**:你提出的话题是否合理?是否贴近现实和聊天内容?说话方式是否正常?是否像一个真正的人类?
|
||||
|
||||
请根据这个意图,生成一条消息,要求:
|
||||
1. 要与最近的聊天记录相关,自然地引入话题或表达关心。
|
||||
2. 长度适中(20-40字)。
|
||||
3. 结合最近的聊天记录确保对话连贯,不要显得突兀。
|
||||
4. 符合你的人设和当前聊天风格。
|
||||
5. **你的心情会影响你的表达方式**。
|
||||
6. 如果有表达方式参考,在合适时自然使用。
|
||||
|
||||
直接输出消息内容,不要解释:""",
|
||||
name="proactive_thinking_throw_topic_group",
|
||||
)
|
||||
|
||||
|
||||
# --- 私聊场景 ---
|
||||
decision_prompt_template_private = Prompt(
|
||||
"""{time_block}
|
||||
你的人设是:
|
||||
{bot_personality}
|
||||
|
||||
你正在考虑是否要主动与 **"{stream_name}"** 说些什么。
|
||||
|
||||
【你当前的心情】
|
||||
{current_mood}
|
||||
|
||||
【与对方的聊天信息】
|
||||
- 整体印象: {stream_impression}
|
||||
- 聊天风格: {chat_style}
|
||||
- 常见话题: {topic_keywords}
|
||||
@@ -52,22 +183,22 @@ decision_prompt_template = Prompt(
|
||||
- 适用场景:气氛不适合说话、最近对话很活跃、没什么特别想说的、或者此时说话会显得突兀。
|
||||
- 心情影响:如果心情不好(如生气、难过),可能更倾向于保持沉默。
|
||||
|
||||
**选项2:简单冒个泡 (simple_bubble)**
|
||||
- 适用场景:对话有些冷清,你想缓和气氛或开启新的互动。
|
||||
**选项2:简单问候一下 (simple_bubble)**
|
||||
- 适用场景:对话有些冷清,你想开启新的互动。
|
||||
- 方式:说一句轻松随意的话,旨在建立或维持连接。
|
||||
- 心情影响:心情会影响你冒泡的方式和内容。
|
||||
- 心情影响:心情会影响你问候的方式和内容。
|
||||
|
||||
**选项3:发起一次有目的的互动 (throw_topic)**
|
||||
- 适用场景:你想延续对话、表达关心、或深入讨论某个具体话题。
|
||||
- **【互动类型1:延续约定或提醒】(最高优先级)**:检查最近的聊天记录,是否存在可以延续的互动。例如,如果昨晚的最后一条消息是“晚安”,现在是早上,一个“早安”的回应是绝佳的选择。如果之前提到过某个约定(如“待会聊”),现在可以主动跟进。
|
||||
- **【互动类型2:展现真诚的关心】(次高优先级)**:如果不存在可延续的约定,请仔细阅读聊天记录,寻找对方提及的个人状况(如天气、出行、身体、情绪、工作学习等),并主动表达关心。
|
||||
- **【互动类型2:展现真诚的关心】(次高优先级)**:如果不存在可延续的约定,请仔细阅读聊天记录,寻找**对方**提及的个人状况(如天气、出行、身体、情绪、工作学习等),并主动表达关心。
|
||||
- **【互动类型3:开启新话题】**:当以上两点都不适用时,可以考虑开启一个你感兴趣的新话题。
|
||||
- 心情影响:心情会影响你想发起互动的方式和内容。
|
||||
|
||||
请以JSON格式回复你的决策:
|
||||
{{
|
||||
"action": "do_nothing" | "simple_bubble" | "throw_topic",
|
||||
"reasoning": "你的决策理由(请结合你的心情、聊天环境和对话历史进行分析)",
|
||||
"reasoning": "你的决策理由(请结合你的心情、与对方的聊天情况和对话历史进行分析)",
|
||||
"topic": "(仅当action=throw_topic时填写)你的互动意图(如:回应晚安并说早安、关心对方的考试情况、讨论新游戏)"
|
||||
}}
|
||||
|
||||
@@ -78,28 +209,27 @@ decision_prompt_template = Prompt(
|
||||
4. 只有在真的有感而发时才选择 `throw_topic`。
|
||||
5. 保持你的人设,确保行为一致性。
|
||||
""",
|
||||
name="proactive_thinking_decision",
|
||||
name="proactive_thinking_decision_private",
|
||||
)
|
||||
|
||||
# 冒泡回复 Prompt
|
||||
simple_bubble_reply_prompt_template = Prompt(
|
||||
simple_bubble_reply_prompt_template_private = Prompt(
|
||||
"""{time_block}
|
||||
你的人设是:
|
||||
{bot_personality}
|
||||
|
||||
距离上次对话已经有一段时间了,你决定主动说些什么,轻松地开启新的互动。
|
||||
距离上次和 **"{stream_name}"** 对话已经有一段时间了,你决定主动说些什么,轻松地开启新的互动。
|
||||
|
||||
【你当前的心情】
|
||||
{current_mood}
|
||||
|
||||
【聊天环境】
|
||||
【与对方的聊天环境】
|
||||
- 整体印象: {stream_impression}
|
||||
- 聊天风格: {chat_style}
|
||||
|
||||
【最近的聊天记录】
|
||||
{recent_chat_history}
|
||||
{expression_habits}
|
||||
请生成一条简短的消息,用于水群。
|
||||
请生成一条简短的消息,用于**私聊中轻松地打个招呼**。
|
||||
【要求】
|
||||
1. 风格简短随意(5-20字)
|
||||
2. 不要提出明确的话题或问题,可以是问候、表达心情或一句随口的话。
|
||||
@@ -108,21 +238,20 @@ simple_bubble_reply_prompt_template = Prompt(
|
||||
5. 如果有表达方式参考,在合适时自然使用。
|
||||
6. 合理参考历史记录。
|
||||
直接输出消息内容,不要解释:""",
|
||||
name="proactive_thinking_simple_bubble",
|
||||
name="proactive_thinking_simple_bubble_private",
|
||||
)
|
||||
|
||||
# 抛出话题回复 Prompt
|
||||
throw_topic_reply_prompt_template = Prompt(
|
||||
throw_topic_reply_prompt_template_private = Prompt(
|
||||
"""{time_block}
|
||||
你的人设是:
|
||||
{bot_personality}
|
||||
|
||||
你决定在与 "{stream_name}" 的对话中主动发起一次互动。
|
||||
你决定在与 **"{stream_name}"** 的私聊中主动发起一次互动。
|
||||
|
||||
【你当前的心情】
|
||||
{current_mood}
|
||||
|
||||
【聊天环境】
|
||||
【与对方的聊天环境】
|
||||
- 整体印象: {stream_impression}
|
||||
- 聊天风格: {chat_style}
|
||||
- 常见话题: {topic_keywords}
|
||||
@@ -134,11 +263,14 @@ throw_topic_reply_prompt_template = Prompt(
|
||||
{topic}
|
||||
{expression_habits}
|
||||
【构思指南】
|
||||
请根据你的互动意图,并参考最近的聊天记录,生成一条有温度的消息。
|
||||
请根据你的互动意图,并参考最近的聊天记录,生成一条有温度的、**适合在私聊中说**的消息。
|
||||
- 如果意图是**延续约定**(如回应“晚安”),请直接生成对应的问候。
|
||||
- 如果意图是**表达关心**(如跟进对方提到的事),请生成自然、真诚的关心话语。
|
||||
- 如果意图是**开启新话题**,请**确保新话题与最近的聊天内容有关联**,自然地引入话题,避免过于跳脱。
|
||||
|
||||
- 如果意ت意图是**表达关心**(如跟进对方提到的事),请生成自然、真诚的关心话语。
|
||||
- 如果意图是**开启新话题**:请严格遵守以下“新话题构思三步法”:
|
||||
1. **寻找灵感**:**首选**从【最近的聊天记录】中寻找一个可以自然延续的**生活化**细节。**严禁**引入与聊天记录完全无关的、凭空出现的话题。如果记录为空,可以根据你的【人设】,提出一个**非常普适、开放式**的生活化问题或感想。
|
||||
2. **确定风格**:请**确保新话题与最近的聊天内容有关联**,自然地引入话题,避免过于跳脱。
|
||||
3. **最终检查**:你提出的话题是否合理?是否贴近现实和聊天内容?说话方式是否正常?是否像一个真正的人类?
|
||||
|
||||
请根据这个意图,生成一条消息,要求:
|
||||
1. 要与最近的聊天记录相关,自然地引入话题或表达关心。
|
||||
2. 长度适中(20-40字)。
|
||||
@@ -148,7 +280,7 @@ throw_topic_reply_prompt_template = Prompt(
|
||||
6. 如果有表达方式参考,在合适时自然使用。
|
||||
|
||||
直接输出消息内容,不要解释:""",
|
||||
name="proactive_thinking_throw_topic",
|
||||
name="proactive_thinking_throw_topic_private",
|
||||
)
|
||||
|
||||
|
||||
@@ -194,7 +326,7 @@ class ProactiveThinkingPlanner:
|
||||
# 2. 获取最近的聊天记录
|
||||
recent_messages = await message_api.get_recent_messages(
|
||||
chat_id=stream_id,
|
||||
limit=40,
|
||||
limit=global_config.chat.max_context_size,
|
||||
limit_mode="latest",
|
||||
hours=24
|
||||
)
|
||||
@@ -237,9 +369,13 @@ class ProactiveThinkingPlanner:
|
||||
logger.warning(f"获取上次决策失败: {e}")
|
||||
|
||||
# 6. 构建上下文
|
||||
# 7. 判断聊天类型
|
||||
chat_type = "group" if "group" in stream_id else "private"
|
||||
|
||||
context = {
|
||||
"stream_id": stream_id,
|
||||
"stream_name": stream_data.get("stream_name", "未知"),
|
||||
"chat_type": chat_type,
|
||||
"stream_impression": stream_data.get("stream_impression_text", "暂无印象"),
|
||||
"chat_style": stream_data.get("stream_chat_style", "未知"),
|
||||
"topic_keywords": stream_data.get("stream_topic_keywords", ""),
|
||||
@@ -318,6 +454,13 @@ class ProactiveThinkingPlanner:
|
||||
if last_topic:
|
||||
last_decision_text += f"\n- 话题: {last_topic}"
|
||||
|
||||
# 根据聊天类型选择不同的决策Prompt
|
||||
chat_type = context.get("chat_type", "group")
|
||||
if chat_type == "private":
|
||||
decision_prompt_template = decision_prompt_template_private
|
||||
else:
|
||||
decision_prompt_template = decision_prompt_template_group
|
||||
|
||||
decision_prompt = decision_prompt_template.format(
|
||||
time_block=context["time_block"],
|
||||
bot_personality=context["bot_personality"],
|
||||
@@ -378,10 +521,20 @@ class ProactiveThinkingPlanner:
|
||||
stream_id=context.get("stream_id", ""), chat_history=context.get("recent_chat_history", "")
|
||||
)
|
||||
|
||||
# 根据聊天类型选择不同的回复Prompt
|
||||
chat_type = context.get("chat_type", "group")
|
||||
if chat_type == "private":
|
||||
simple_template = simple_bubble_reply_prompt_template_private
|
||||
throw_template = throw_topic_reply_prompt_template_private
|
||||
else:
|
||||
simple_template = simple_bubble_reply_prompt_template_group
|
||||
throw_template = throw_topic_reply_prompt_template_group
|
||||
|
||||
if action == "simple_bubble":
|
||||
reply_prompt = simple_bubble_reply_prompt_template.format(
|
||||
reply_prompt = simple_template.format(
|
||||
time_block=context["time_block"],
|
||||
bot_personality=context["bot_personality"],
|
||||
stream_name=context["stream_name"],
|
||||
current_mood=context.get("current_mood", "感觉很平静"),
|
||||
stream_impression=context["stream_impression"],
|
||||
chat_style=context["chat_style"],
|
||||
@@ -389,7 +542,7 @@ class ProactiveThinkingPlanner:
|
||||
expression_habits=expression_habits,
|
||||
)
|
||||
else: # throw_topic
|
||||
reply_prompt = throw_topic_reply_prompt_template.format(
|
||||
reply_prompt = throw_template.format(
|
||||
time_block=context["time_block"],
|
||||
bot_personality=context["bot_personality"],
|
||||
stream_name=context["stream_name"],
|
||||
@@ -550,11 +703,11 @@ async def execute_proactive_thinking(stream_id: str):
|
||||
|
||||
# 尝试获取锁,如果已被占用则跳过本次执行(防止重复)
|
||||
if lock.locked():
|
||||
logger.warning(f"⚠️ 主动思考跳过:聊天流 {stream_id} 已有正在执行的主动思考任务")
|
||||
logger.warning(f"[警告] 主动思考跳过:聊天流 {stream_id} 已有正在执行的主动思考任务")
|
||||
return
|
||||
|
||||
async with lock:
|
||||
logger.debug(f"🤔 开始主动思考 {stream_id}")
|
||||
logger.debug(f"[思考] 开始主动思考 {stream_id}")
|
||||
|
||||
try:
|
||||
# 0. 前置检查
|
||||
@@ -565,8 +718,11 @@ async def execute_proactive_thinking(stream_id: str):
|
||||
chat_stream = await chat_manager.get_stream(stream_id)
|
||||
|
||||
if chat_stream and chat_stream.context.is_chatter_processing:
|
||||
logger.warning(f"⚠️ 主动思考跳过:聊天流 {stream_id} 的 chatter 正在处理消息")
|
||||
return
|
||||
logger.warning(f"[警告] 主动思考等待:聊天流 {stream_id} 的 chatter 正在处理消息,等待3秒后重试...")
|
||||
await asyncio.sleep(3)
|
||||
if chat_stream.context.is_chatter_processing:
|
||||
logger.warning(f"[警告] 主动思考跳过:聊天流 {stream_id} 的 chatter 仍在处理消息")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning(f"检查 chatter 处理状态时出错: {e},继续执行")
|
||||
|
||||
@@ -634,7 +790,7 @@ async def execute_proactive_thinking(stream_id: str):
|
||||
return
|
||||
|
||||
elif action == "simple_bubble":
|
||||
logger.info(f"💬 决策:冒个泡。理由:{reasoning}")
|
||||
logger.info(f"[决策] 决策:冒个泡。理由:{reasoning}")
|
||||
|
||||
proactive_thinking_scheduler.record_decision(stream_id, action, reasoning, None)
|
||||
|
||||
@@ -646,7 +802,7 @@ async def execute_proactive_thinking(stream_id: str):
|
||||
stream_id=stream_id,
|
||||
text=reply,
|
||||
)
|
||||
logger.info("✅ 已发送冒泡消息")
|
||||
logger.info("[成功] 已发送冒泡消息")
|
||||
|
||||
# 增加每日计数
|
||||
proactive_thinking_scheduler._increment_daily_count(stream_id)
|
||||
|
||||
@@ -361,7 +361,7 @@ class ProactiveThinkingScheduler:
|
||||
self._paused_streams.discard(stream_id)
|
||||
|
||||
success = await unified_scheduler.remove_schedule(schedule_id)
|
||||
logger.debug(f"⏹️ 取消主动思考 {stream_id}")
|
||||
logger.debug(f"[取消] 取消主动思考 {stream_id}")
|
||||
|
||||
return success
|
||||
|
||||
@@ -482,7 +482,7 @@ class ProactiveThinkingScheduler:
|
||||
minutes = (remaining_seconds % 3600) // 60
|
||||
time_str = f"{hours}小时{minutes}分钟后"
|
||||
|
||||
status = "⏸️ 暂停中" if is_paused else "✅ 活跃"
|
||||
status = "[暂停] 暂停中" if is_paused else "[活跃] 活跃"
|
||||
|
||||
logger.info(
|
||||
f"[{i:2d}] {status} | {stream_name}\n"
|
||||
|
||||
@@ -63,35 +63,6 @@ class SchedulerService:
|
||||
pass # 任务取消是正常操作
|
||||
logger.info("基于日程表的说说定时发送任务已停止。")
|
||||
|
||||
async def _generate_random_topic(self) -> str | None:
|
||||
"""
|
||||
使用小模型生成一个随机的说说主题。
|
||||
"""
|
||||
try:
|
||||
logger.info("尝试生成随机说说主题...")
|
||||
prompt = "请生成一个有趣、简短、积极向上的日常一句话,适合作为社交媒体的动态内容,例如关于天气、心情、动漫、游戏或者某个小发现。请直接返回这句话,不要包含任何多余的解释或标签。"
|
||||
|
||||
task_config = global_model_config.model_task_config.get_task("utils_small")
|
||||
if not task_config:
|
||||
logger.error("未找到名为 'utils_small' 的模型任务配置。")
|
||||
return None
|
||||
|
||||
success, content, _, _ = await llm_api.generate_with_model(
|
||||
model_config=task_config,
|
||||
prompt=prompt,
|
||||
max_tokens=150,
|
||||
temperature=0.9,
|
||||
)
|
||||
|
||||
if success and content and content.strip():
|
||||
logger.info(f"成功生成随机主题: {content.strip()}")
|
||||
return content.strip()
|
||||
logger.warning("LLM未能生成有效的主题。")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"生成随机主题时发生错误: {e}")
|
||||
return None
|
||||
|
||||
async def _schedule_loop(self):
|
||||
"""
|
||||
定时任务的核心循环。
|
||||
@@ -140,21 +111,13 @@ class SchedulerService:
|
||||
activity_placeholder = "No Schedule - Random"
|
||||
if not await self._is_processed(hour_str, activity_placeholder):
|
||||
logger.info("没有日程活动,但开启了无日程发送功能,准备生成随机主题。")
|
||||
topic = await self._generate_random_topic()
|
||||
if topic:
|
||||
result = await self.qzone_service.send_feed(topic=topic, stream_id=None)
|
||||
await self._mark_as_processed(
|
||||
result = await self.qzone_service.send_feed(topic="随意发挥",stream_id=None)
|
||||
await self._mark_as_processed(
|
||||
hour_str,
|
||||
activity_placeholder,
|
||||
result.get("success", False),
|
||||
result.get("message", ""),
|
||||
)
|
||||
else:
|
||||
logger.error("未能生成随机主题,本次不发送。")
|
||||
# 即使生成失败,也标记为已处理,防止本小时内反复尝试
|
||||
await self._mark_as_processed(
|
||||
hour_str, activity_placeholder, False, "Failed to generate topic"
|
||||
)
|
||||
else:
|
||||
logger.info(f"当前小时 {hour_str} 已执行过无日程发送任务,本次跳过。")
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import ClassVar
|
||||
from src.chat.utils.prompt_component_manager import prompt_component_manager
|
||||
from src.chat.utils.prompt_params import PromptParameters
|
||||
from src.plugin_system.apis import (
|
||||
chat_api,
|
||||
plugin_manage_api,
|
||||
)
|
||||
from src.plugin_system.apis.logging_api import get_logger
|
||||
@@ -21,6 +22,7 @@ from src.plugin_system.base.base_plugin import BasePlugin
|
||||
from src.plugin_system.base.command_args import CommandArgs
|
||||
from src.plugin_system.base.component_types import (
|
||||
ChatType,
|
||||
ComponentType,
|
||||
PermissionNodeField,
|
||||
PlusCommandInfo,
|
||||
)
|
||||
@@ -96,16 +98,16 @@ class SystemCommand(PlusCommand):
|
||||
help_text = """🔌 插件管理命令帮助
|
||||
📋 基本操作:
|
||||
• `/system plugin help` - 显示插件管理帮助
|
||||
• `/system plugin list` - 列出所有注册的插件
|
||||
• `/system plugin list_enabled` - 列出所有加载(启用)的插件
|
||||
• `/system plugin report` - 查看系统插件报告
|
||||
• `/system plugin rescan` - 重新扫描所有插件目录
|
||||
|
||||
⚙️ 插件控制:
|
||||
• `/system plugin load <插件名>` - 加载指定插件
|
||||
• `/system plugin unload <插件名>` - 卸载指定插件
|
||||
• `/system plugin reload <插件名>` - 重新加载指定插件
|
||||
• `/system plugin force_reload <插件名>` - 强制重载指定插件
|
||||
• `/system plugin add_dir <目录路径>` - 添加插件目录
|
||||
• `/system plugin reload_all` - 重新加载所有插件
|
||||
🎯 局部控制 (需要 `system.plugin.manage.local` 权限):
|
||||
• `/system plugin enable_local <名称> [group <群号> | private <QQ号>]` - 在指定会话局部启用组件
|
||||
• `/system plugin disable_local <名称> [group <群号> | private <QQ号>]` - 在指定会话局部禁用组件
|
||||
"""
|
||||
elif target == "permission":
|
||||
help_text = """📋 权限管理命令帮助
|
||||
@@ -150,20 +152,20 @@ class SystemCommand(PlusCommand):
|
||||
|
||||
if action in ["help", "帮助"]:
|
||||
await self._show_help("plugin")
|
||||
elif action in ["list", "列表"]:
|
||||
await self._list_registered_plugins()
|
||||
elif action in ["list_enabled", "已启用"]:
|
||||
await self._list_loaded_plugins()
|
||||
elif action in ["report", "报告"]:
|
||||
await self._show_system_report()
|
||||
elif action in ["rescan", "重扫"]:
|
||||
await self._rescan_plugin_dirs()
|
||||
elif action in ["load", "加载"] and len(remaining_args) > 0:
|
||||
await self._load_plugin(remaining_args[0])
|
||||
elif action in ["unload", "卸载"] and len(remaining_args) > 0:
|
||||
await self._unload_plugin(remaining_args[0])
|
||||
elif action in ["reload", "重载"] and len(remaining_args) > 0:
|
||||
await self._reload_plugin(remaining_args[0])
|
||||
elif action in ["force_reload", "强制重载"] and len(remaining_args) > 0:
|
||||
await self._force_reload_plugin(remaining_args[0])
|
||||
elif action in ["reload_all", "重载全部"]:
|
||||
await self._reload_all_plugins()
|
||||
elif action in ["enable_local", "局部启用"] and len(remaining_args) >= 1:
|
||||
await self._set_local_component_state(remaining_args, enabled=True)
|
||||
elif action in ["disable_local", "局部禁用"] and len(remaining_args) >= 1:
|
||||
await self._set_local_component_state(remaining_args, enabled=False)
|
||||
else:
|
||||
await self.send_text("❌ 插件管理命令不合法\n使用 /system plugin help 查看帮助")
|
||||
|
||||
@@ -316,7 +318,7 @@ class SystemCommand(PlusCommand):
|
||||
@require_permission("prompt.view", deny_message="❌ 你没有查看提示词注入信息的权限")
|
||||
async def _list_prompt_components(self):
|
||||
"""列出所有已注册的提示词组件"""
|
||||
components = prompt_component_manager.get_registered_prompt_component_info()
|
||||
components = await prompt_component_manager.get_registered_prompt_component_info()
|
||||
if not components:
|
||||
await self.send_text("🧩 当前没有已注册的提示词组件")
|
||||
return
|
||||
@@ -398,7 +400,7 @@ class SystemCommand(PlusCommand):
|
||||
@require_permission("prompt.view", deny_message="❌ 你没有查看提示词组件信息的权限")
|
||||
async def _show_prompt_component_info(self, component_name: str):
|
||||
"""显示特定提示词组件的详细信息"""
|
||||
all_components = prompt_component_manager.get_registered_prompt_component_info()
|
||||
all_components = await prompt_component_manager.get_registered_prompt_component_info()
|
||||
|
||||
target_component = next((comp for comp in all_components if comp.name == component_name), None)
|
||||
|
||||
@@ -429,61 +431,151 @@ class SystemCommand(PlusCommand):
|
||||
# Permission Management Section
|
||||
# =================================================================
|
||||
|
||||
async def _list_loaded_plugins(self):
|
||||
"""列出已加载的插件"""
|
||||
plugins = plugin_manage_api.list_loaded_plugins()
|
||||
await self.send_text(f"📦 已加载的插件: {', '.join(plugins) if plugins else '无'}")
|
||||
@require_permission("plugin.manage", deny_message="❌ 你没有权限查看插件报告")
|
||||
async def _show_system_report(self):
|
||||
"""显示系统插件报告"""
|
||||
report = plugin_manage_api.get_system_report()
|
||||
|
||||
response_parts = [
|
||||
"📊 **系统插件报告**",
|
||||
f" - 已加载插件: {report['system_info']['loaded_plugins_count']}",
|
||||
f" - 组件总数: {report['system_info']['total_components_count']}",
|
||||
]
|
||||
|
||||
async def _list_registered_plugins(self):
|
||||
"""列出已注册的插件"""
|
||||
plugins = plugin_manage_api.list_registered_plugins()
|
||||
await self.send_text(f"📋 已注册的插件: {', '.join(plugins) if plugins else '无'}")
|
||||
if report["plugins"]:
|
||||
response_parts.append("\n✅ **已加载插件:**")
|
||||
for name, info in report["plugins"].items():
|
||||
response_parts.append(f" • **{info['display_name']} (`{name}`)** v{info['version']} by {info['author']}")
|
||||
|
||||
if report["failed_plugins"]:
|
||||
response_parts.append("\n❌ **加载失败的插件:**")
|
||||
for name, error in report["failed_plugins"].items():
|
||||
response_parts.append(f" • **`{name}`**: {error}")
|
||||
|
||||
await self._send_long_message("\n".join(response_parts))
|
||||
|
||||
|
||||
@require_permission("plugin.manage", deny_message="❌ 你没有权限扫描插件")
|
||||
async def _rescan_plugin_dirs(self):
|
||||
"""重新扫描插件目录"""
|
||||
plugin_manage_api.rescan_plugin_directory()
|
||||
await self.send_text("🔄 插件目录重新扫描已启动")
|
||||
await self.send_text("🔄 正在重新扫描插件目录...")
|
||||
success, fail = plugin_manage_api.rescan_and_register_plugins(load_after_register=True)
|
||||
await self.send_text(f"✅ 扫描完成!\n新增成功: {success}个, 新增失败: {fail}个。")
|
||||
|
||||
@require_permission("plugin.manage", deny_message="❌ 你没有权限加载插件")
|
||||
async def _load_plugin(self, plugin_name: str):
|
||||
"""加载指定插件"""
|
||||
success, count = plugin_manage_api.load_plugin(plugin_name)
|
||||
success = plugin_manage_api.register_plugin_from_file(plugin_name, load_after_register=True)
|
||||
if success:
|
||||
await self.send_text(f"✅ 插件加载成功: `{plugin_name}`")
|
||||
else:
|
||||
if count == 0:
|
||||
await self.send_text(f"⚠️ 插件 `{plugin_name}` 为禁用状态")
|
||||
else:
|
||||
await self.send_text(f"❌ 插件加载失败: `{plugin_name}`")
|
||||
await self.send_text(f"❌ 插件加载失败: `{plugin_name}`。请检查日志获取详细信息。")
|
||||
|
||||
async def _unload_plugin(self, plugin_name: str):
|
||||
"""卸载指定插件"""
|
||||
success = await plugin_manage_api.remove_plugin(plugin_name)
|
||||
if success:
|
||||
await self.send_text(f"✅ 插件卸载成功: `{plugin_name}`")
|
||||
else:
|
||||
await self.send_text(f"❌ 插件卸载失败: `{plugin_name}`")
|
||||
|
||||
@require_permission("plugin.manage", deny_message="❌ 你没有权限重载插件")
|
||||
async def _reload_plugin(self, plugin_name: str):
|
||||
"""重新加载指定插件"""
|
||||
success = await plugin_manage_api.reload_plugin(plugin_name)
|
||||
if success:
|
||||
await self.send_text(f"✅ 插件重新加载成功: `{plugin_name}`")
|
||||
else:
|
||||
await self.send_text(f"❌ 插件重新加载失败: `{plugin_name}`")
|
||||
|
||||
async def _force_reload_plugin(self, plugin_name: str):
|
||||
"""强制重载指定插件(深度清理)"""
|
||||
await self.send_text(f"🔄 开始强制重载插件: `{plugin_name}`... (注意: 实际执行reload)")
|
||||
try:
|
||||
success = await plugin_manage_api.reload_plugin(plugin_name)
|
||||
if success:
|
||||
await self.send_text(f"✅ 插件重载成功: `{plugin_name}`")
|
||||
await self.send_text(f"✅ 插件重新加载成功: `{plugin_name}`")
|
||||
else:
|
||||
await self.send_text(f"❌ 插件重载失败: `{plugin_name}`")
|
||||
except Exception as e:
|
||||
await self.send_text(f"❌ 重载过程中发生错误: {e!s}")
|
||||
await self.send_text(f"❌ 插件重新加载失败: `{plugin_name}`")
|
||||
except ValueError as e:
|
||||
await self.send_text(f"❌ 操作失败: {e}")
|
||||
|
||||
|
||||
@require_permission("plugin.manage", deny_message="❌ 你没有权限重载所有插件")
|
||||
async def _reload_all_plugins(self):
|
||||
"""重新加载所有插件"""
|
||||
await self.send_text("🔄 正在重新加载所有插件...")
|
||||
success = await plugin_manage_api.reload_all_plugins()
|
||||
if success:
|
||||
await self.send_text("✅ 所有插件已成功重载。")
|
||||
else:
|
||||
await self.send_text("⚠️ 部分插件重载失败,请检查日志。")
|
||||
|
||||
@require_permission("plugin.manage.local", deny_message="❌ 你没有局部管理插件组件的权限")
|
||||
async def _set_local_component_state(self, args: list[str], enabled: bool):
|
||||
"""在局部范围内启用或禁用一个组件"""
|
||||
# 命令格式: <component_name> [group <group_id> | private <user_id>]
|
||||
if not args:
|
||||
action = "enable_local" if enabled else "disable_local"
|
||||
await self.send_text(f"❌ 用法: /system plugin {action} <名称> [group <群号> | private <QQ号>]")
|
||||
return
|
||||
|
||||
comp_name = args[0]
|
||||
context_args = args[1:]
|
||||
stream_id = self.message.chat_info.stream_id # 默认作用于当前会话
|
||||
|
||||
# 1. 搜索组件
|
||||
found_components = plugin_manage_api.search_components_by_name(comp_name, exact_match=True)
|
||||
|
||||
if not found_components:
|
||||
await self.send_text(f"❌ 未找到名为 '{comp_name}' 的组件。")
|
||||
return
|
||||
|
||||
if len(found_components) > 1:
|
||||
suggestions = "\n".join([f"- `{c['name']}` (类型: {c['component_type']})" for c in found_components])
|
||||
await self.send_text(f"❌ 发现多个名为 '{comp_name}' 的组件,操作已取消。\n找到的组件:\n{suggestions}")
|
||||
return
|
||||
|
||||
component_info = found_components[0]
|
||||
comp_type_str = component_info["component_type"]
|
||||
component_type = ComponentType(comp_type_str)
|
||||
|
||||
# 2. 增加禁用保护
|
||||
if not enabled: # 如果是禁用操作
|
||||
# 定义不可禁用的核心组件类型
|
||||
protected_types = [
|
||||
ComponentType.INTEREST_CALCULATOR,
|
||||
ComponentType.PROMPT,
|
||||
ComponentType.ROUTER,
|
||||
]
|
||||
if component_type in protected_types:
|
||||
await self.send_text(f"❌ 无法局部禁用核心组件 '{comp_name}' ({comp_type_str})。")
|
||||
return
|
||||
|
||||
# 3. 解析上下文
|
||||
if len(context_args) >= 2:
|
||||
context_type = context_args[0].lower()
|
||||
context_id = context_args[1]
|
||||
|
||||
target_stream = None
|
||||
if context_type == "group":
|
||||
target_stream = chat_api.get_stream_by_group_id(
|
||||
group_id=context_id,
|
||||
platform=self.message.chat_info.platform
|
||||
)
|
||||
elif context_type == "private":
|
||||
target_stream = chat_api.get_stream_by_user_id(
|
||||
user_id=context_id,
|
||||
platform=self.message.chat_info.platform
|
||||
)
|
||||
else:
|
||||
await self.send_text("❌ 无效的作用域类型,请使用 'group' 或 'private'。")
|
||||
return
|
||||
|
||||
if not target_stream:
|
||||
await self.send_text(f"❌ 在当前平台找不到指定的 {context_type}: `{context_id}`。")
|
||||
return
|
||||
|
||||
stream_id = target_stream.stream_id
|
||||
|
||||
# 4. 执行操作
|
||||
success = plugin_manage_api.set_component_enabled_local(
|
||||
stream_id=stream_id,
|
||||
name=comp_name,
|
||||
component_type=component_type,
|
||||
enabled=enabled
|
||||
)
|
||||
|
||||
action_text = "启用" if enabled else "禁用"
|
||||
if success:
|
||||
await self.send_text(f"✅ 在会话 `{stream_id}` 中,已成功将组件 `{comp_name}` ({comp_type_str}) 设置为 {action_text} 状态。")
|
||||
else:
|
||||
await self.send_text(f"❌ 操作失败。可能无法禁用最后一个启用的 Chatter,或组件不存在。请检查日志。")
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Permission Management Section
|
||||
@@ -729,4 +821,8 @@ class SystemManagementPlugin(BasePlugin):
|
||||
node_name="schedule.manage",
|
||||
description="定时任务管理:暂停和恢复定时任务",
|
||||
),
|
||||
PermissionNodeField(
|
||||
node_name="plugin.manage.local",
|
||||
description="局部插件管理:在指定会话中启用或禁用组件",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -28,7 +28,7 @@ class TTSVoicePlugin(BasePlugin):
|
||||
plugin_description = "基于GPT-SoVITS的文本转语音插件(重构版)"
|
||||
plugin_version = "3.1.2"
|
||||
plugin_author = "Kilo Code & 靚仔"
|
||||
enable_plugin = False
|
||||
enable_plugin = True
|
||||
config_file_name = "config.toml"
|
||||
dependencies: ClassVar[list[str]] = []
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class APIKeyManager(Generic[T]):
|
||||
try:
|
||||
self.clients = [client_factory(key) for key in valid_keys]
|
||||
self.client_cycle = itertools.cycle(self.clients)
|
||||
logger.info(f"🔑 {service_name} 成功加载 {len(valid_keys)} 个 API 密钥")
|
||||
logger.info(f" {service_name} 成功加载 {len(valid_keys)} 个 API 密钥")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 初始化 {service_name} 客户端失败: {e}")
|
||||
self.clients = []
|
||||
@@ -61,6 +61,7 @@ class APIKeyManager(Generic[T]):
|
||||
if not self.is_available():
|
||||
|
||||
return None
|
||||
assert self.client_cycle is not None
|
||||
return next(self.client_cycle)
|
||||
|
||||
def get_client_count(self) -> int:
|
||||
|
||||
Reference in New Issue
Block a user