From 9c3d8a21f99dd53b18df06ef8be225c7ab0957ac Mon Sep 17 00:00:00 2001 From: liuwei Date: Mon, 27 Apr 2026 13:07:35 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B2=BE=E7=AE=80=E6=96=97=E9=B1=BC=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E7=94=BB=E5=83=8F=E9=93=BE=E8=B7=AF=E7=9A=84=E8=BF=87?= =?UTF-8?q?=E7=A8=8B=E9=80=BB=E8=BE=91\n\n-=20=E5=88=A0=E9=99=A4=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E7=94=BB=E5=83=8F=E8=A1=A5=E5=85=A8=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=A4=8D=E6=9D=82=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=8C=89Redis=E7=BC=93=E5=AD=98=E7=9B=B4?= =?UTF-8?q?=E8=BF=9E=E4=B8=BB=E6=B5=81=E7=A8=8B\n-=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E7=BD=AE=E4=BF=A1=E5=BA=A6=E3=80=81=E8=AF=81=E6=8D=AE=E6=91=98?= =?UTF-8?q?=E8=A6=81=E3=80=81=E5=A4=8D=E6=A0=B8=E6=A0=87=E8=AE=B0=E7=AD=89?= =?UTF-8?q?=E8=BF=87=E7=A8=8B=E5=9E=8B=E5=AD=97=E6=AE=B5\n-=20=E7=B2=BE?= =?UTF-8?q?=E7=AE=80room=5Fcontext=E6=8F=90=E7=A4=BA=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=97=A5=E6=8A=A5=E7=9C=9F=E6=AD=A3=E4=BC=9A?= =?UTF-8?q?=E7=94=A8=E5=88=B0=E7=9A=84=E8=83=8C=E6=99=AF=E4=BF=A1=E6=81=AF?= =?UTF-8?q?\n-=20=E5=90=8C=E6=AD=A5=E6=94=B6=E5=8F=A3Dify=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E7=94=BB=E5=83=8F=E5=88=86=E6=94=AF=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=8D=E4=B8=8E=E6=96=87=E6=A1=A3=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dify_douyu_daily_report_workflow.md | 2 +- plugins/douyu/main.py | 110 +++-------------------- plugins/douyu/斗鱼日报AI.yml | 8 +- 3 files changed, 17 insertions(+), 103 deletions(-) diff --git a/docs/dify_douyu_daily_report_workflow.md b/docs/dify_douyu_daily_report_workflow.md index 4ce9d11..681523b 100644 --- a/docs/dify_douyu_daily_report_workflow.md +++ b/docs/dify_douyu_daily_report_workflow.md @@ -103,7 +103,7 @@ - 只输出结构化 JSON - 优先整理主播领域、职业生涯、相关人物、剧情关键词和梗解释 - 如果 Workflow 已接搜索/知识库,优先检索公开资料后再整理 -- 如果证据不足,宁可留空并把 `confidence` 设低 +- 如果证据不足,宁可留空,也不要为了凑字段硬编 ## 8. 回退 LLM 的设计建议 不要把三条主分支都挂到同一个通用回退模型。 diff --git a/plugins/douyu/main.py b/plugins/douyu/main.py index 1fa800c..d2fa86d 100644 --- a/plugins/douyu/main.py +++ b/plugins/douyu/main.py @@ -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: diff --git a/plugins/douyu/斗鱼日报AI.yml b/plugins/douyu/斗鱼日报AI.yml index c2af785..a46c796 100644 --- a/plugins/douyu/斗鱼日报AI.yml +++ b/plugins/douyu/斗鱼日报AI.yml @@ -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