优化斗鱼粉丝日报定时任务与弹幕素材

1. 将斗鱼粉丝日报接入插件定时任务体系,支持后台独立启停与调度执行\n2. 为粉丝日报新增按群去重发送标记,避免任务补偿或多群订阅时重复推送\n3. 扩充日报传给LLM的弹幕材料,补充顺时序现场样本与场次故事线,提升语境完整度\n4. 提升斗鱼日报缓存版本,确保新链路生成结果不复用旧缓存
This commit is contained in:
liuwei
2026-04-29 13:19:19 +08:00
parent 3accd84bd1
commit 6d33081e03
2 changed files with 236 additions and 19 deletions
+89
View File
@@ -213,6 +213,18 @@ class DouyuDanmuSummaryHelper:
"peak_buckets": cls._simplify_peak_buckets(peak_buckets),
"representative_messages": cls._pick_representative_messages(organized_messages, bucket_stats),
"raw_window_samples": cls._build_raw_window_samples(peak_buckets, per_bucket_limit=8),
# 给日报类 LLM 再补一层“按时间推进的现场切片”。
# 这样模型除了看热点窗口,还能顺着时间线理解气氛如何起、如何变、最后怎么收,
# 对粉丝日报这类强调“节目效果”和“接梗链路”的文本尤其有帮助。
"chronological_samples": cls._build_chronological_samples(organized_messages, limit=20),
# 每个 session 单独给一个轻量摘要,避免多场直播合并后,
# 模型只看到全局热点而丢失“第一场在聊什么、第二场为什么突然转节奏”的信息。
"session_storyline": cls._build_session_storyline(
organized_messages,
bucket_stats,
top_terms_limit=8,
sample_limit=10,
),
}
@staticmethod
@@ -834,6 +846,83 @@ class DouyuDanmuSummaryHelper:
})
return windows
@classmethod
def _build_chronological_samples(
cls,
messages: List[Dict[str, Any]],
limit: int = 20,
) -> List[Dict[str, str]]:
"""
从整场弹幕里按时间均匀抽取样本。
设计目的:
1. 热点窗口只能解释“最炸的几分钟”,但日报还需要理解整体节奏;
2. 顺时序样本能帮助 LLM 看到开场铺垫、中段起哄、尾段收束;
3. 对粉丝日报来说,这比单纯词频更容易还原“今天到底经历了什么”。
"""
if not messages:
return []
indexes = {
int(round((len(messages) - 1) * idx / max(limit - 1, 1)))
for idx in range(min(limit, len(messages)))
}
selected: List[Dict[str, str]] = []
seen = set()
for idx in sorted(indexes):
item = messages[idx]
content = str(item.get("content") or "").strip()
if not content:
continue
normalized = cls._normalize_template_text(content)
if normalized and normalized in seen:
continue
if normalized:
seen.add(normalized)
selected.append({
"time": str(item.get("timestamp_text") or ""),
"nickname": str(item.get("nickname") or ""),
"content": content[:90],
})
if len(selected) >= limit:
break
return selected
@classmethod
def _build_session_storyline(
cls,
messages: List[Dict[str, Any]],
bucket_stats: List[Dict[str, Any]],
*,
top_terms_limit: int = 8,
sample_limit: int = 10,
) -> Dict[str, Any]:
"""
组装单场直播的轻量叙事骨架。
这里不追求大而全,而是给模型一个“这场直播从头到尾在发生什么”的概览,
让它在写日报时更容易把梗、情绪和时间顺序串起来。
"""
first_message = messages[0] if messages else {}
last_message = messages[-1] if messages else {}
hottest_bucket = max(
bucket_stats,
key=lambda item: int(item.get("message_count", 0) or 0),
default={},
)
return {
"start_time": str(first_message.get("timestamp_text") or ""),
"end_time": str(last_message.get("timestamp_text") or ""),
"top_terms": cls._extract_top_terms(messages, limit=top_terms_limit),
"burst_terms": cls._build_burst_terms(messages)[:6],
"chronological_samples": cls._build_chronological_samples(messages, limit=sample_limit),
"hottest_moment": {
"start_time": str(hottest_bucket.get("start_time") or ""),
"message_count": int(hottest_bucket.get("message_count", 0) or 0),
"user_count": int(hottest_bucket.get("user_count", 0) or 0),
"top_terms": hottest_bucket.get("top_terms", [])[:6],
"sample_messages": hottest_bucket.get("sample_messages", [])[:6],
},
}
@staticmethod
def _simplify_peak_buckets(buckets: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
simplified = []