feat(maizone): 新增无日程时随机生成主题并发送说说
当没有预设日程活动时,定时发送服务现在能够利用小型LLM动态生成一个随机主题,并自动发布说说。 为避免过于频繁地发布,该功能被限制为每小时最多执行一次。此项更新旨在提高账号在日程空闲期间的活跃度,使自动发布的动态看起来更加自然和多样化。
This commit is contained in:
committed by
Windpicker-owo
parent
5872198427
commit
e37a27cab7
@@ -375,3 +375,63 @@ class ContentService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"生成基于活动的说说内容异常: {e}")
|
logger.error(f"生成基于活动的说说内容异常: {e}")
|
||||||
return ""
|
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 ""
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from sqlalchemy import select
|
|||||||
from src.common.database.compatibility import get_db_session
|
from src.common.database.compatibility import get_db_session
|
||||||
from src.common.database.core.models import MaiZoneScheduleStatus
|
from src.common.database.core.models import MaiZoneScheduleStatus
|
||||||
from src.common.logger import get_logger
|
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 src.schedule.schedule_manager import schedule_manager
|
||||||
|
|
||||||
from .qzone_service import QZoneService
|
from .qzone_service import QZoneService
|
||||||
@@ -61,10 +63,40 @@ class SchedulerService:
|
|||||||
pass # 任务取消是正常操作
|
pass # 任务取消是正常操作
|
||||||
logger.info("基于日程表的说说定时发送任务已停止。")
|
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):
|
async def _schedule_loop(self):
|
||||||
"""
|
"""
|
||||||
定时任务的核心循环。
|
定时任务的核心循环。
|
||||||
每隔一段时间检查当前是否有日程活动,并判断是否需要触发发送流程。
|
每隔一段时间检查当前是否有日程活动,并判断是否需要触发发送流程。
|
||||||
|
也支持在没有日程时,根据配置进行不定时发送。
|
||||||
"""
|
"""
|
||||||
while self.is_running:
|
while self.is_running:
|
||||||
try:
|
try:
|
||||||
@@ -73,52 +105,62 @@ class SchedulerService:
|
|||||||
await asyncio.sleep(60) # 如果被禁用,则每分钟检查一次状态
|
await asyncio.sleep(60) # 如果被禁用,则每分钟检查一次状态
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 2. 获取当前时间的日程活动
|
|
||||||
current_activity = schedule_manager.get_current_activity()
|
|
||||||
logger.info(f"当前检测到的日程活动: {current_activity}")
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
is_forbidden_time = False
|
|
||||||
if forbidden_start < forbidden_end:
|
|
||||||
# 例如,2点到6点
|
|
||||||
is_forbidden_time = forbidden_start <= now.hour < forbidden_end
|
|
||||||
else:
|
|
||||||
# 例如,23点到第二天7点
|
|
||||||
is_forbidden_time = now.hour >= forbidden_start or now.hour < forbidden_end
|
|
||||||
|
|
||||||
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()
|
now = datetime.datetime.now()
|
||||||
hour_str = now.strftime("%Y-%m-%d %H")
|
hour_str = now.strftime("%Y-%m-%d %H")
|
||||||
await self._mark_as_processed(
|
|
||||||
hour_str, current_activity, result.get("success", False), result.get("message", "")
|
# 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))
|
||||||
)
|
)
|
||||||
|
|
||||||
# 7. 更新上一个处理的活动
|
if is_forbidden_time:
|
||||||
self.last_processed_activity = current_activity
|
logger.info(f"当前时间 {now.hour}点 处于禁止发送时段 ({forbidden_start}-{forbidden_end}),本次跳过。")
|
||||||
else:
|
else:
|
||||||
logger.info(f"活动 '{current_activity}' 与上次相同,本次跳过。")
|
# 3. 获取当前时间的日程活动
|
||||||
|
current_activity_dict = schedule_manager.get_current_activity()
|
||||||
|
logger.info(f"当前检测到的日程活动: {current_activity_dict}")
|
||||||
|
|
||||||
# 8. 计算并等待一个随机的时间间隔
|
if current_activity_dict:
|
||||||
min_minutes = self.get_config("schedule.random_interval_min_minutes", 5)
|
# --- 有日程活动时的逻辑 ---
|
||||||
max_minutes = self.get_config("schedule.random_interval_max_minutes", 15)
|
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:
|
||||||
|
# --- 没有日程活动时的逻辑 ---
|
||||||
|
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} 已执行过无日程发送任务,本次跳过。")
|
||||||
|
|
||||||
|
# 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)
|
wait_seconds = random.randint(min_minutes * 60, max_minutes * 60)
|
||||||
logger.info(f"下一次检查将在 {wait_seconds / 60:.2f} 分钟后进行。")
|
logger.info(f"下一次检查将在 {wait_seconds / 60:.2f} 分钟后进行。")
|
||||||
await asyncio.sleep(wait_seconds)
|
await asyncio.sleep(wait_seconds)
|
||||||
@@ -133,10 +175,6 @@ class SchedulerService:
|
|||||||
async def _is_processed(self, hour_str: str, activity: str) -> bool:
|
async def _is_processed(self, hour_str: str, activity: str) -> bool:
|
||||||
"""
|
"""
|
||||||
检查指定的任务(某个小时的某个活动)是否已经被成功处理过。
|
检查指定的任务(某个小时的某个活动)是否已经被成功处理过。
|
||||||
|
|
||||||
:param hour_str: 时间字符串,格式为 "YYYY-MM-DD HH"。
|
|
||||||
:param activity: 活动名称。
|
|
||||||
:return: 如果已处理过,返回 True,否则返回 False。
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
async with get_db_session() as session:
|
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):
|
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:
|
try:
|
||||||
async with get_db_session() as session:
|
async with get_db_session() as session:
|
||||||
|
|||||||
Reference in New Issue
Block a user