diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 08cafdbf..174e9c69 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -30,6 +30,7 @@ from .utils_user import get_user_nickname, get_user_cardname, get_groupname from .willing_manager import willing_manager # 导入意愿管理器 from .message_base import UserInfo, GroupInfo, Seg from ..utils.logger_config import setup_logger, LogModule +from .daily_share_scheduler import DailyShareScheduler # 配置日志 logger = setup_logger(LogModule.CHAT) @@ -45,6 +46,7 @@ class ChatBot: self.mood_manager.start_mood_update() # 启动情绪更新 self.emoji_chance = 0.2 # 发送表情包的基础概率 + self.daily_share_scheduler = DailyShareScheduler() # self.message_streams = MessageStreamContainer() async def _ensure_started(self): diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 88cb31ed..60d6c1f1 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -105,6 +105,11 @@ class BotConfig: default_factory=lambda: ["表情包", "图片", "回复", "聊天记录"] ) # 添加新的配置项默认值 + daily_share_willing: float = 0.3 # 基础分享意愿 + daily_share_interval: int = 1800 # 日常分享检查间隔(秒) + daily_share_time_start: int = 8 # 日常分享开始时间(小时) + daily_share_time_end: int = 22 # 日常分享结束时间(小时) + @staticmethod def get_config_dir() -> str: """获取配置文件目录""" @@ -342,6 +347,17 @@ class BotConfig: config.enable_debug_output = others_config.get("enable_debug_output", config.enable_debug_output) config.enable_friend_chat = others_config.get("enable_friend_chat", config.enable_friend_chat) + def shares(parent: dict): + shares_config = parent["shares"] + config.daily_share_willing = shares_config.get("daily_share_willing", config.daily_share_willing) + config.daily_share_interval = shares_config.get("daily_share_interval", config.daily_share_interval) + config.daily_share_time_start = shares_config.get("daily_share_time_start", config.daily_share_time_start) + config.daily_share_time_end = shares_config.get("daily_share_time_end", config.daily_share_time_end) + + def shares_allow_groups(parent: dict): + shares_allow_groups_config = parent["shares_allow_groups"] + config.shares_allow_groups = set(shares_allow_groups_config.get("shares_allow_groups", [])) + # 版本表达式:>=1.0.0,<2.0.0 # 允许字段:func: method, support: str, notice: str, necessary: bool # 如果使用 notice 字段,在该组配置加载时,会展示该字段对用户的警示 @@ -361,6 +377,8 @@ class BotConfig: "chinese_typo": {"func": chinese_typo, "support": ">=0.0.3", "necessary": False}, "groups": {"func": groups, "support": ">=0.0.0"}, "others": {"func": others, "support": ">=0.0.0"}, + "shares": {"func": shares, "support": ">=0.0.0"}, + "shares_allow_groups": {"func": shares_allow_groups, "support": ">=0.0.0"}, } # 原地修改,将 字符串版本表达式 转换成 版本对象 diff --git a/src/plugins/chat/daily_share_scheduler.py b/src/plugins/chat/daily_share_scheduler.py new file mode 100644 index 00000000..7296cd96 --- /dev/null +++ b/src/plugins/chat/daily_share_scheduler.py @@ -0,0 +1,143 @@ +import asyncio +import random +import datetime +from typing import Optional, Dict +from loguru import logger + +from nonebot import get_driver +from .config import global_config +from .willing_manager import willing_manager +from .llm_generator import DailyMessageGenerate +from .message import Message, MessageSending, MessageSet +from .message_sender import message_manager +from .message_base import UserInfo, GroupInfo, Seg +from .chat_stream import chat_manager +from .message_cq import ( + MessageRecvCQ, +) + +class DailyShareScheduler: + def __init__(self): + self.daily_generator = DailyMessageGenerate() + self._tasks: Dict[str, asyncio.Task] = {} # 存储每个群的任务 + self._started = False + + async def _group_share_task(self, group_id: str): + """单个群组的分享任务""" + # 为每个群组设置随机的初始等待时间,避免同时执行 + initial_wait = random.uniform(0, 30) + daily_share_interval = global_config.daily_share_interval + initial_wait + await asyncio.sleep(daily_share_interval) + + while True: + try: + current_time = datetime.datetime.now() + + # 检查是否在允许的时间范围内 + if global_config.daily_share_time_start <= current_time.hour < global_config.daily_share_time_end: + try: + # 创建用户信息 + bot_user_info = UserInfo( + user_id=global_config.BOT_QQ, + user_nickname=global_config.BOT_NICKNAME, + platform="qq" + ) + + # 创建群组信息 + group_info = GroupInfo( + group_id=group_id, + group_name=None, + platform="qq" + ) + + # 创建聊天流 + chat_stream = await chat_manager.get_or_create_stream( + platform="qq", + user_info=bot_user_info, + group_info=group_info + ) + + # 检查分享意愿 + share_willing = await willing_manager.check_daily_share_wiling(chat_stream) + + # 生成随机数并与意愿比较 + if random.random() < share_willing: + try: + # 创建消息对象 + message = Message( + message_id=f"daily_{int(datetime.datetime.now().timestamp())}", + time=int(datetime.datetime.now().timestamp()), + chat_stream=chat_stream, + user_info=bot_user_info, + message_segment=None, + reply=None, + detailed_plain_text="", + processed_plain_text="" + ) + + # 生成日常分享内容 + content = await self.daily_generator.gen_response(message) + + if content: + # 创建消息段 + message_segment = Seg(type="text", data=content) + + message_set = MessageSet( + chat_stream=chat_stream, + message_id=message.message_info.message_id + ) + + # 创建发送消息对象 + bot_message = MessageSending( + message_id=message.message_info.message_id, + chat_stream=chat_stream, + bot_user_info=bot_user_info, + sender_info=bot_user_info, + message_segment=message_segment, + reply=None, + is_head=True, + is_emoji=False + ) + await bot_message.process() + message_set.add_message(bot_message) + message_manager.add_message(message_set) + logger.info(f"群[{group_id}]分享意愿[{share_willing:.2f}],已生成日常分享并加入发送队列") + + # 重置该群的分享意愿 + await willing_manager.reset_daily_share_wiling(chat_stream) + except Exception as e: + logger.error(f"生成或发送消息时出错: {str(e)}") + else: + logger.debug(f"群[{group_id}]分享意愿[{share_willing:.2f}]不足,跳过分享") + except Exception as e: + logger.error(f"处理群[{group_id}]时出错: {str(e)}") + + # 等待2小时,加上随机偏移,避免同时执行 + wait_time = 30 + random.uniform(-5, 5) # 测试时使用30秒,加上随机偏移 + await asyncio.sleep(wait_time) + + except Exception as e: + logger.error(f"群[{group_id}]的日常分享任务出错: {str(e)}") + await asyncio.sleep(60) # 出错后等待1分钟再试 + + async def start(self): + """启动定时任务""" + if not self._started: + self._started = True + # 为每个群创建独立的任务 + for group_id in global_config.shares_allow_groups: + logger.info(f"启动群[{group_id}]的日常分享任务") + # 使用 create_task 创建独立的任务 + task = asyncio.create_task(self._group_share_task(group_id)) + self._tasks[group_id] = task + logger.info("日常分享定时任务已启动") + +# 创建全局实例 +daily_share_scheduler = DailyShareScheduler() + +# 在驱动器启动时启动定时任务 +driver = get_driver() + +@driver.on_startup +async def _(): + await daily_share_scheduler.start() \ No newline at end of file diff --git a/src/plugins/chat/llm_generator.py b/src/plugins/chat/llm_generator.py index 2e0c0eb1..5282914d 100644 --- a/src/plugins/chat/llm_generator.py +++ b/src/plugins/chat/llm_generator.py @@ -243,3 +243,20 @@ class InitiativeMessageGenerate: content, reasoning = self.model_r1.generate_response_async(prompt) logger.debug(f"[DEBUG] {content} {reasoning}") return content + + +class DailyMessageGenerate: + def __init__(self): + self.model_r1 = LLM_request(model=global_config.llm_reasoning, temperature=0.7) + self.model_v3 = LLM_request(model=global_config.llm_normal, temperature=0.7) + self.model_r1_distill = LLM_request( + model=global_config.llm_reasoning_minor, temperature=0.7 + ) + + async def gen_response(self, message: Message): + prompt, prompt_template = ( + prompt_builder._build_daily_prompt(message.message_info.group_info.group_id) + ) + content, reasoning =await self.model_r1.generate_response_async(prompt) + # logger.debug(f"[DEBUG] {content} {reasoning}") + return content \ No newline at end of file diff --git a/src/plugins/chat/prompt_builder.py b/src/plugins/chat/prompt_builder.py index a41ed51e..e357826d 100644 --- a/src/plugins/chat/prompt_builder.py +++ b/src/plugins/chat/prompt_builder.py @@ -319,5 +319,32 @@ class PromptBuilder: # 返回所有找到的内容,用换行分隔 return '\n'.join(str(result['content']) for result in results) + def _build_daily_prompt(self, group_id, probability_1=0.8, probability_2=0.1): + current_date = time.strftime("%Y-%m-%d", time.localtime()) + current_time = time.strftime("%H:%M:%S", time.localtime()) + bot_schedule_now_time, bot_schedule_now_activity = bot_schedule.get_current_task() + prompt_date = f'''今天是{current_date},现在是{current_time},你今天的日程是:\n{bot_schedule.today_schedule}\n你现在正在{bot_schedule_now_activity}\n''' + + # print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的消息记录:{chat_talking_prompt}") + + # 激活prompt构建 + + personality = global_config.PROMPT_PERSONALITY + prompt_personality = '' + personality_choice = random.random() + if personality_choice < probability_1: # 第一种人格 + prompt_personality = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[0]}''' + elif personality_choice < probability_1 + probability_2: # 第二种人格 + prompt_personality = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[1]}''' + else: # 第三种人格 + prompt_personality = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[2]}''' + + prompt_for_select = f"现在,根据你今天的日程,生成一条日常分享,讲述你的上一个日程是什么,在日程中发生了什么有意思的事情。注意,只需要输出日常分享,不要输出其他任何内容,不要在,也不要提到之后的日程是什么,字数不要超过50字。" + + prompt_initiative_select = f"{prompt_date}\n{prompt_personality}\n{prompt_for_select}" + prompt_regular = f"{prompt_date}\n{prompt_personality}" + + return prompt_initiative_select, prompt_regular + prompt_builder = PromptBuilder() diff --git a/src/plugins/chat/willing_manager.py b/src/plugins/chat/willing_manager.py index 6df27f3a..47ea33fd 100644 --- a/src/plugins/chat/willing_manager.py +++ b/src/plugins/chat/willing_manager.py @@ -4,7 +4,7 @@ import time from typing import Dict from loguru import logger - +from ...common.database import db from .config import global_config from .chat_stream import ChatStream @@ -20,10 +20,12 @@ class WillingManager: self.chat_last_reply_time: Dict[str, float] = {} # 存储每个聊天流上次回复的时间 self.chat_last_sender_id: Dict[str, str] = {} # 存储每个聊天流上次回复的用户ID self.chat_conversation_context: Dict[str, bool] = {} # 标记是否处于对话上下文中 + + self.daily_share_wiling: Dict[str, float] = {} # 存储每个聊天流的分享意愿 self._decay_task = None self._mode_switch_task = None self._started = False - + async def _decay_reply_willing(self): """定期衰减回复意愿""" while True: @@ -255,5 +257,72 @@ class WillingManager: self._mode_switch_task = asyncio.create_task(self._mode_switch_check()) self._started = True + async def check_daily_share_wiling(self, chat_stream: ChatStream) -> float: + """检查群聊消息活跃度,决定是否提高分享日常意愿 + 目前仅支持群聊主动发起聊天,私聊不支持 + Args: + chat_stream: 聊天流对象, 分享意愿不依赖聊天内容,只与群聊活跃度相关 + + Returns: + float: 分享意愿值 + """ + if not chat_stream or not chat_stream.group_info: + return 0.0 + + try: + # 获取24小时前的时间戳 + one_day_ago = int(time.time()) - 86400 # 24小时 = 86400秒 + + # 从数据库查询24小时内的总消息数 + daily_messages = list(db.messages.find({ + "chat_id": chat_stream.stream_id, + "time": {"$gte": one_day_ago} + })) + + # 如果24小时内消息数不超过100,不激活分享意愿 + # 仅统计bot运行期间的消息 + # 暂时不启用,配置文档指定群聊分享功能是否开启 + # if len(daily_messages) <= 100: + # logger.debug(f"群{chat_stream.group_info.group_id}在24小时内消息数{len(daily_messages)}条,小于阈值,鉴定为不活跃群,不激活分享意愿") + # self.daily_share_wiling[chat_stream.stream_id] = 0 + # return 0.0 + + # 获取60分钟前的时间戳 + thirty_minutes_ago = int(time.time()) - 36 + + # 从数据库查询最近60分钟的消息 + recent_messages = list(db.messages.find({ + "chat_id": chat_stream.stream_id, + "time": {"$gte": thirty_minutes_ago} + })) + + # 如果没有最近消息,大幅提高分享意愿 + if not recent_messages: + share_willing = self.daily_share_wiling.get(chat_stream.stream_id, global_config.daily_share_willing) + new_willing = min(0.6, max(0.3, share_willing + 0.2)) + self.daily_share_wiling[chat_stream.stream_id] = new_willing + logger.info(f"群{chat_stream.group_info.group_id}最近60分钟无消息,提高分享意愿至{new_willing}") + return new_willing + + # 如果有消息,但消息数量很少(比如少于3条),适度提高意愿 + if len(recent_messages) < 3: + share_willing = self.daily_share_wiling.get(chat_stream.stream_id, global_config.daily_share_willing) + new_willing = min(0.4, max(0.2, share_willing + 0.1)) + self.daily_share_wiling[chat_stream.stream_id] = new_willing + logger.info(f"群{chat_stream.group_info.group_id}最近60分钟消息较少,适度提高分享意愿至{new_willing}") + return new_willing + + # 消息活跃度正常,保持当前意愿 + logger.debug( + f"群{chat_stream.group_info.group_id}消息活跃度正常,保持分享意愿{self.daily_share_wiling.get(chat_stream.stream_id, global_config.daily_share_willing)}") + return self.daily_share_wiling.get(chat_stream.stream_id, global_config.daily_share_willing) + + except Exception as e: + logger.error(f"检查群聊活跃度时出错: {str(e)}") + return global_config.daily_share_willing # 出错时返回基础分享意愿 + + async def reset_daily_share_wiling(self, chat_stream: ChatStream) -> float: + """重置分享意愿""" + self.daily_share_wiling[chat_stream.stream_id] = 0 # 创建全局实例 willing_manager = WillingManager() \ No newline at end of file