feat(maizone): 新增无日程时随机生成主题并发送说说

当没有预设日程活动时,定时发送服务现在能够利用小型LLM动态生成一个随机主题,并自动发布说说。

为避免过于频繁地发布,该功能被限制为每小时最多执行一次。此项更新旨在提高账号在日程空闲期间的活跃度,使自动发布的动态看起来更加自然和多样化。
This commit is contained in:
minecraft1024a
2025-11-15 16:25:55 +08:00
committed by Windpicker-owo
parent 5872198427
commit e37a27cab7
2 changed files with 144 additions and 51 deletions

View File

@@ -375,3 +375,63 @@ class ContentService:
except Exception as e:
logger.error(f"生成基于活动的说说内容异常: {e}")
return ""
async def generate_random_topic(self) -> str:
"""
使用一个小型、高效的模型来动态生成一个随机的说说主题。
"""
try:
# 硬编码使用 'utils_small' 模型
model_name = "utils_small"
models = llm_api.get_available_models()
model_config = models.get(model_name)
if not model_config:
logger.error(f"无法找到用于生成主题的模型: {model_name}")
return ""
prompt = """
请你扮演一个想法的“生成器”。
你的任务是随机给出一个适合在QQ空间上发表说说的“主题”或“灵感”。
这个主题应该非常简短,通常是一个词、一个短语或一个开放性的问题,用于激发创作。
规则:
1. **绝对简洁**输出长度严格控制在15个字以内。
2. **多样性**:主题可以涉及日常生活、情感、自然、科技、哲学思考等任何方面。
3. **激发性**:主题应该是开放的,能够引发出一条内容丰富的说说。
4. **随机性**:每次给出的主题都应该不同。
5. **仅输出主题**:你的回答应该只有主题本身,不包含任何解释、引号或多余的文字。
好的例子:
- 一部最近看过的老电影
- 夏天傍晚的晚霞
- 关于拖延症的思考
- 一个奇怪的梦
- 雨天听什么音乐?
错误的例子:
- “我建议的主题是:一部最近看过的老电影” (错误:包含了多余的文字)
- “夏天傍晚的晚霞,那种橙色与紫色交织的感觉,总是能让人心生宁静。” (错误:太长了,变成了说说本身而不是主题)
现在,请给出一个随机主题。
"""
success, topic, _, _ = await llm_api.generate_with_model(
prompt=prompt,
model_config=model_config,
request_type="story.generate.topic",
temperature=0.8, # 提高创造性以获得更多样的主题
max_tokens=50,
)
if success and topic:
logger.info(f"成功生成随机主题: '{topic}'")
return topic.strip()
else:
logger.error("生成随机主题失败")
return ""
except Exception as e:
logger.error(f"生成随机主题时发生异常: {e}")
return ""

View File

@@ -14,6 +14,8 @@ from sqlalchemy import select
from src.common.database.compatibility import get_db_session
from src.common.database.core.models import MaiZoneScheduleStatus
from src.common.logger import get_logger
from src.config.config import model_config as global_model_config
from src.plugin_system.apis import llm_api
from src.schedule.schedule_manager import schedule_manager
from .qzone_service import QZoneService
@@ -61,10 +63,40 @@ 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):
"""
定时任务的核心循环。
每隔一段时间检查当前是否有日程活动,并判断是否需要触发发送流程。
也支持在没有日程时,根据配置进行不定时发送。
"""
while self.is_running:
try:
@@ -73,52 +105,62 @@ class SchedulerService:
await asyncio.sleep(60) # 如果被禁用,则每分钟检查一次状态
continue
# 2. 获取当前时间的日程活动
current_activity = schedule_manager.get_current_activity()
logger.info(f"当前检测到的日程活动: {current_activity}")
now = datetime.datetime.now()
hour_str = now.strftime("%Y-%m-%d %H")
if current_activity:
# 3. 检查当前时间是否在禁止发送的时间段内
now = datetime.datetime.now()
forbidden_start = self.get_config("schedule.forbidden_hours_start", 2)
forbidden_end = self.get_config("schedule.forbidden_hours_end", 6)
# 2. 检查是否在禁止发送的时间段内
forbidden_start = self.get_config("schedule.forbidden_hours_start", 2)
forbidden_end = self.get_config("schedule.forbidden_hours_end", 6)
is_forbidden_time = (
(forbidden_start < forbidden_end and forbidden_start <= now.hour < forbidden_end)
or (forbidden_start > forbidden_end and (now.hour >= forbidden_start or now.hour < forbidden_end))
)
is_forbidden_time = False
if forbidden_start < forbidden_end:
# 例如2点到6点
is_forbidden_time = forbidden_start <= now.hour < forbidden_end
if is_forbidden_time:
logger.info(f"当前时间 {now.hour}点 处于禁止发送时段 ({forbidden_start}-{forbidden_end}),本次跳过。")
else:
# 3. 获取当前时间的日程活动
current_activity_dict = schedule_manager.get_current_activity()
logger.info(f"当前检测到的日程活动: {current_activity_dict}")
if current_activity_dict:
# --- 有日程活动时的逻辑 ---
current_activity_name = current_activity_dict.get("activity", str(current_activity_dict))
if current_activity_dict != self.last_processed_activity:
logger.info(f"检测到新的日程活动: '{current_activity_name}',准备发送说说。")
result = await self.qzone_service.send_feed_from_activity(current_activity_name)
await self._mark_as_processed(
hour_str, current_activity_name, result.get("success", False), result.get("message", "")
)
self.last_processed_activity = current_activity_dict
else:
logger.info(f"活动 '{current_activity_name}' 与上次相同,本次跳过。")
else:
# 例如23点到第二天7点
is_forbidden_time = now.hour >= forbidden_start or now.hour < forbidden_end
# --- 没有日程活动时的逻辑 ---
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(
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} 已执行过无日程发送任务,本次跳过。")
if is_forbidden_time:
logger.info(
f"当前时间 {now.hour}点 处于禁止发送时段 ({forbidden_start}-{forbidden_end}),本次跳过。"
)
self.last_processed_activity = current_activity
# 4. 检查活动是否是新的活动
elif current_activity != self.last_processed_activity:
logger.info(f"检测到新的日程活动: '{current_activity}',准备发送说说。")
# 5. 调用QZoneService执行完整的发送流程
result = await self.qzone_service.send_feed_from_activity(current_activity)
# 6. 将处理结果记录到数据库
now = datetime.datetime.now()
hour_str = now.strftime("%Y-%m-%d %H")
await self._mark_as_processed(
hour_str, current_activity, result.get("success", False), result.get("message", "")
)
# 7. 更新上一个处理的活动
self.last_processed_activity = current_activity
else:
logger.info(f"活动 '{current_activity}' 与上次相同,本次跳过。")
# 8. 计算并等待一个随机的时间间隔
min_minutes = self.get_config("schedule.random_interval_min_minutes", 5)
max_minutes = self.get_config("schedule.random_interval_max_minutes", 15)
# 4. 计算并等待一个随机的时间间隔
min_minutes = self.get_config("schedule.random_interval_min_minutes", 15)
max_minutes = self.get_config("schedule.random_interval_max_minutes", 45)
wait_seconds = random.randint(min_minutes * 60, max_minutes * 60)
logger.info(f"下一次检查将在 {wait_seconds / 60:.2f} 分钟后进行。")
await asyncio.sleep(wait_seconds)
@@ -133,10 +175,6 @@ class SchedulerService:
async def _is_processed(self, hour_str: str, activity: str) -> bool:
"""
检查指定的任务(某个小时的某个活动)是否已经被成功处理过。
:param hour_str: 时间字符串,格式为 "YYYY-MM-DD HH"
:param activity: 活动名称。
:return: 如果已处理过,返回 True否则返回 False。
"""
try:
async with get_db_session() as session:
@@ -154,11 +192,6 @@ class SchedulerService:
async def _mark_as_processed(self, hour_str: str, activity: str, success: bool, content: str):
"""
将任务的处理状态和结果写入数据库。
:param hour_str: 时间字符串。
:param activity: 活动名称。
:param success: 发送是否成功。
:param content: 最终发送的说说内容或错误信息。
"""
try:
async with get_db_session() as session: