优化斗鱼粉丝日报定时任务与弹幕素材
1. 将斗鱼粉丝日报接入插件定时任务体系,支持后台独立启停与调度执行\n2. 为粉丝日报新增按群去重发送标记,避免任务补偿或多群订阅时重复推送\n3. 扩充日报传给LLM的弹幕材料,补充顺时序现场样本与场次故事线,提升语境完整度\n4. 提升斗鱼日报缓存版本,确保新链路生成结果不复用旧缓存
This commit is contained in:
@@ -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 = []
|
||||
|
||||
Reference in New Issue
Block a user