精简斗鱼背景画像链路的过程逻辑\n\n- 删除背景画像补全中的复杂判断逻辑,改为按Redis缓存直连主流程\n- 移除置信度、证据摘要、复核标记等过程型字段\n- 精简room_context提示块,保留日报真正会用到的背景信息\n- 同步收口Dify背景画像分支提示词与文档说明
This commit is contained in:
@@ -513,9 +513,9 @@ class DouyuRedisManager:
|
||||
class DouyuPlugin(MessagePluginInterface):
|
||||
# 报告缓存版本号:
|
||||
# 1. 版本升级后会自动让历史缓存失效,避免继续复用旧文本/旧图片;
|
||||
# 2. 本次将版本提升到 7,除了粉丝日报分流以外,还加入了 Redis 自动背景画像,
|
||||
# 需要强制刷新旧缓存,确保新版 prompt 能吃到最新 room_context。
|
||||
_DAILY_REPORT_CACHE_VERSION = 7
|
||||
# 2. 本次将版本提升到 8,背景画像链路删掉了部分过程控制和提示噪音,
|
||||
# 需要刷新旧日报缓存,确保新版 prompt 使用新的精简上下文。
|
||||
_DAILY_REPORT_CACHE_VERSION = 8
|
||||
FEATURE_KEY = "DOUYU_MONITOR"
|
||||
FEATURE_DESCRIPTION = "🎮 斗鱼开播提醒 [订阅斗鱼 房间号, 取消订阅斗鱼 房间号]"
|
||||
|
||||
@@ -715,7 +715,6 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
"domain",
|
||||
"identity_summary",
|
||||
"career_background",
|
||||
"evidence_summary",
|
||||
]
|
||||
for field in text_fields:
|
||||
if str(profile.get(field) or "").strip():
|
||||
@@ -732,51 +731,17 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _profile_needs_auto_enrichment(
|
||||
self,
|
||||
manual_profile: Optional[Dict[str, Any]],
|
||||
cached_profile: Optional[Dict[str, Any]],
|
||||
*,
|
||||
force_refresh: bool = False,
|
||||
) -> bool:
|
||||
"""
|
||||
判断当前房间是否值得触发一次自动画像生成。
|
||||
策略尽量保守:
|
||||
1. 手工画像已经比较完整时,不额外消耗模型;
|
||||
2. Redis 已有可用缓存时,优先复用;
|
||||
3. 只有“手工画像明显缺失/信息过少”时,才触发自动补全。
|
||||
"""
|
||||
if force_refresh:
|
||||
return True
|
||||
if self._profile_has_meaningful_content(cached_profile):
|
||||
return False
|
||||
if not self._profile_has_meaningful_content(manual_profile):
|
||||
return True
|
||||
|
||||
manual_profile = manual_profile or {}
|
||||
filled_core_fields = 0
|
||||
for field in ("domain", "identity_summary", "career_background"):
|
||||
if str(manual_profile.get(field) or "").strip():
|
||||
filled_core_fields += 1
|
||||
list_item_count = 0
|
||||
for field in ("related_people", "storyline_keywords", "meme_explanations", "style_hints"):
|
||||
list_item_count += len(self._normalize_text_list(manual_profile.get(field)))
|
||||
|
||||
return filled_core_fields < 3 or list_item_count < 4
|
||||
|
||||
def _normalize_auto_room_background_profile(self, profile: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
清洗 LLM 返回的背景画像 JSON。
|
||||
目标不是追求字段越多越好,而是保证进入 Redis 的内容:
|
||||
这里刻意只保留日报真正会用到的核心字段,避免把过多“过程型元信息”
|
||||
带进 Redis 和 prompt,导致链路越来越重。
|
||||
目标是保证进入 Redis 的内容:
|
||||
1. 结构稳定;
|
||||
2. 文本长度可控;
|
||||
3. 明确带上置信度与人工复核提示,方便后续在 prompt 中降权使用。
|
||||
3. 只保留能帮助日报理解圈内梗的字段。
|
||||
"""
|
||||
profile = profile if isinstance(profile, dict) else {}
|
||||
confidence = str(profile.get("confidence") or "").strip().lower()
|
||||
if confidence not in {"low", "medium", "high"}:
|
||||
confidence = "low"
|
||||
|
||||
normalized = {
|
||||
"domain": str(profile.get("domain") or "").strip()[:32],
|
||||
"domain_keywords": self._normalize_text_list(profile.get("domain_keywords"))[:12],
|
||||
@@ -786,9 +751,6 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
"storyline_keywords": self._normalize_text_list(profile.get("storyline_keywords"))[:12],
|
||||
"meme_explanations": self._normalize_text_list(profile.get("meme_explanations"))[:8],
|
||||
"style_hints": self._normalize_text_list(profile.get("style_hints"))[:8],
|
||||
"confidence": confidence,
|
||||
"evidence_summary": str(profile.get("evidence_summary") or "").strip()[:180],
|
||||
"needs_human_review": bool(profile.get("needs_human_review", confidence != "high")),
|
||||
}
|
||||
if not self._profile_has_meaningful_content(normalized):
|
||||
return {}
|
||||
@@ -844,15 +806,6 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
has_manual = self._profile_has_meaningful_content(manual_profile)
|
||||
has_auto = self._profile_has_meaningful_content(auto_profile)
|
||||
|
||||
if has_manual and has_auto:
|
||||
profile_source = "manual+redis_auto"
|
||||
elif has_manual:
|
||||
profile_source = "manual_config"
|
||||
elif has_auto:
|
||||
profile_source = "redis_auto"
|
||||
else:
|
||||
profile_source = ""
|
||||
|
||||
return {
|
||||
"domain": str(manual_profile.get("domain") or auto_profile.get("domain") or "").strip(),
|
||||
"domain_keywords": self._merge_text_list_values(
|
||||
@@ -887,10 +840,6 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
auto_profile.get("style_hints"),
|
||||
limit=8,
|
||||
),
|
||||
"profile_source": profile_source,
|
||||
"profile_confidence": str(auto_profile.get("confidence") or "").strip().lower(),
|
||||
"profile_evidence_summary": str(auto_profile.get("evidence_summary") or "").strip(),
|
||||
"profile_needs_human_review": bool(auto_profile.get("needs_human_review", False)),
|
||||
}
|
||||
|
||||
def _build_room_semantic_context(
|
||||
@@ -971,10 +920,6 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
"storyline_keywords": self._normalize_text_list(profile.get("storyline_keywords")),
|
||||
"meme_explanations": self._normalize_text_list(profile.get("meme_explanations")),
|
||||
"style_hints": self._normalize_text_list(profile.get("style_hints")),
|
||||
"profile_source": str(profile.get("profile_source") or "").strip(),
|
||||
"profile_confidence": str(profile.get("profile_confidence") or "").strip(),
|
||||
"profile_evidence_summary": str(profile.get("profile_evidence_summary") or "").strip(),
|
||||
"profile_needs_human_review": bool(profile.get("profile_needs_human_review", False)),
|
||||
}
|
||||
|
||||
def _build_room_context_prompt_block(self, payload: Dict[str, Any]) -> str:
|
||||
@@ -1000,24 +945,10 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
)
|
||||
if runtime_context.get("tags"):
|
||||
parts.append(f"- 房间标签:{'、'.join(self._normalize_text_list(runtime_context.get('tags'))[:8])}。")
|
||||
profile_source = str(room_context.get("profile_source") or "").strip()
|
||||
if profile_source == "redis_auto":
|
||||
parts.append("- 背景资料来源:以下主播背景为系统自动整理后缓存到 Redis,仅作辅助理解;若和当天真实弹幕冲突,以当天弹幕为准。")
|
||||
elif profile_source == "manual+redis_auto":
|
||||
parts.append("- 背景资料来源:以下信息以手工配置为主,并由 Redis 自动画像补充缺失细节;自动部分只作辅助线索。")
|
||||
if room_context.get("identity_summary"):
|
||||
parts.append(f"- 主播身份提示:{room_context.get('identity_summary')}。")
|
||||
if room_context.get("career_background"):
|
||||
parts.append(f"- 职业生涯背景:{room_context.get('career_background')}。")
|
||||
if profile_source in {"redis_auto", "manual+redis_auto"}:
|
||||
confidence_map = {"high": "高", "medium": "中", "low": "低"}
|
||||
confidence_text = confidence_map.get(str(room_context.get("profile_confidence") or "").strip().lower(), "")
|
||||
if confidence_text:
|
||||
parts.append(f"- 自动背景置信度:{confidence_text}。若出现重名主播、跨圈梗或年份细节,请优先保守解读。")
|
||||
if room_context.get("profile_evidence_summary"):
|
||||
parts.append(f"- 自动背景备注:{room_context.get('profile_evidence_summary')}。")
|
||||
if bool(room_context.get("profile_needs_human_review")):
|
||||
parts.append("- 自动背景复核提示:该画像仍建议人工复核,避免把模糊人物关系当成确定事实。")
|
||||
related_people = self._normalize_text_list(room_context.get("related_people"))
|
||||
if related_people:
|
||||
parts.append(f"- 重点相关人物:{'、'.join(related_people[:12])}。弹幕提到这些人时,优先考虑圈内关联。")
|
||||
@@ -2828,10 +2759,7 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
" \"related_people\": [],\n"
|
||||
" \"storyline_keywords\": [],\n"
|
||||
" \"meme_explanations\": [],\n"
|
||||
" \"style_hints\": [],\n"
|
||||
" \"confidence\": \"low|medium|high\",\n"
|
||||
" \"evidence_summary\": \"\",\n"
|
||||
" \"needs_human_review\": true\n"
|
||||
" \"style_hints\": []\n"
|
||||
"}\n\n"
|
||||
"规则:\n"
|
||||
"1. identity_summary 要像“这是什么类型主播、观众通常围绕什么背景接梗”的一句话。\n"
|
||||
@@ -2839,7 +2767,7 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
"3. related_people 只保留和该主播强相关的人物;不确定不要硬猜。\n"
|
||||
"4. meme_explanations 和 style_hints 要服务日报理解,不要写百科长文。\n"
|
||||
"5. 如果主播不是 Dota2 主播,也要按其真实领域整理,不要强行往 Dota2 上靠。\n"
|
||||
"6. 如果资料存在歧义、重名或证据不足,confidence 设为 low,并把 needs_human_review 设为 true。\n\n"
|
||||
"6. 如果资料存在歧义、重名或证据不足,直接留空,不要为了凑字段而硬编。\n\n"
|
||||
f"输入材料:\n{json.dumps(seed, ensure_ascii=False, indent=2)}"
|
||||
)
|
||||
return system_prompt, user_prompt, seed
|
||||
@@ -2938,12 +2866,6 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
normalized = self._normalize_auto_room_background_profile(parsed)
|
||||
if not normalized:
|
||||
return {}
|
||||
|
||||
normalized["generated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
normalized["source_mode"] = "redis_auto"
|
||||
normalized["generator"] = (
|
||||
f"{self._daily_report_llm_client.provider}:{self._daily_report_llm_client.model or self._daily_report_llm_client.endpoint}"
|
||||
)
|
||||
return normalized
|
||||
|
||||
async def _ensure_room_background_profile(
|
||||
@@ -2959,8 +2881,8 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
"""
|
||||
在生成日报前,确保房间背景画像已经就绪。
|
||||
流程说明:
|
||||
1. 先看手工配置与 Redis 缓存是否已经够用;
|
||||
2. 仅在必要时才触发一次 LLM 自动画像;
|
||||
1. 先看 Redis 里是否已有缓存;
|
||||
2. 没缓存或强制刷新时,就直接触发一次 LLM 自动画像;
|
||||
3. 无论是否生成成功,最后都重新构建 room_context,确保 payload 使用最新缓存。
|
||||
"""
|
||||
if not payload:
|
||||
@@ -2973,7 +2895,6 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
if not room_id:
|
||||
return payload
|
||||
|
||||
manual_profile = self._match_room_context_profile(room_id)
|
||||
cached_profile = (
|
||||
self.redis_manager.get_room_background_profile(room_id) if self.redis_manager else {}
|
||||
) or {}
|
||||
@@ -2983,11 +2904,7 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
and self._daily_report_use_llm
|
||||
and self._daily_report_llm_client is not None
|
||||
and self.redis_manager is not None
|
||||
and self._profile_needs_auto_enrichment(
|
||||
manual_profile,
|
||||
cached_profile,
|
||||
force_refresh=force_refresh,
|
||||
)
|
||||
and (force_refresh or not self._profile_has_meaningful_content(cached_profile))
|
||||
)
|
||||
|
||||
if should_build:
|
||||
@@ -3003,8 +2920,7 @@ class DouyuPlugin(MessagePluginInterface):
|
||||
ttl_seconds=ttl_seconds,
|
||||
)
|
||||
logger.info(
|
||||
f"斗鱼房间背景画像已刷新并缓存到 Redis: room={room_id}, "
|
||||
f"ttl={ttl_seconds}s, confidence={generated_profile.get('confidence', '')}"
|
||||
f"斗鱼房间背景画像已刷新并缓存到 Redis: room={room_id}, ttl={ttl_seconds}s"
|
||||
)
|
||||
|
||||
# 这里无论是否触发了自动画像,都重新构建一次 room_context:
|
||||
|
||||
@@ -737,7 +737,7 @@ workflow:
|
||||
# 背景画像分支:
|
||||
# 1. 只服务 room_background_profile;
|
||||
# 2. 优先整理公开资料与输入材料,输出结构化 JSON;
|
||||
# 3. 不确定时宁可留空,也不要编职业经历或圈内关系。
|
||||
# 3. 去掉额外过程字段,只保留日报真正会用到的背景信息。
|
||||
error_strategy: fail-branch
|
||||
model:
|
||||
completion_params:
|
||||
@@ -764,9 +764,7 @@ workflow:
|
||||
|
||||
5. 如果主播不是 Dota2 主播,也要按其真实领域整理,不要强行往 Dota2 上靠。
|
||||
|
||||
6. confidence 只能是 low / medium / high。
|
||||
|
||||
7. 如果 system_prompt 非空,优先遵循其中的补充规则。
|
||||
6. 如果 system_prompt 非空,优先遵循其中的补充规则。
|
||||
|
||||
'
|
||||
- id: background_user_1
|
||||
@@ -992,7 +990,7 @@ workflow:
|
||||
当前是回退链路,请稳定输出背景画像 JSON。
|
||||
|
||||
只输出 JSON 对象,不要使用代码块,不要输出额外说明。
|
||||
如果证据不足或重名风险较高,字段留空,confidence 设为 low,needs_human_review 设为 true。
|
||||
如果证据不足或重名风险较高,字段直接留空,不要为了凑字段而补猜测。
|
||||
|
||||
'
|
||||
- id: background_user_2
|
||||
|
||||
Reference in New Issue
Block a user