From 0bb409ab49a26ca276b5ef3aa3d0979b0b66c3e6 Mon Sep 17 00:00:00 2001 From: liuwei Date: Mon, 27 Apr 2026 12:28:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=97=E9=B1=BC=E6=97=A5?= =?UTF-8?q?=E6=8A=A5Dify=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=B9=B6=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E7=B2=89=E4=B8=9D=E6=97=A5=E6=8A=A5=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 基于最新导出的斗鱼日报AI工作流,补充 fans_daily_report 任务类型说明。\n2. 同步更新主LLM和回退LLM的提示词规则,使粉丝向日报与运营版日报在同一工作流内稳定路由。\n3. 重写斗鱼 Dify 接入文档,改为和当前实际工作流结构一致的说明,明确当前推荐使用 task_type 提示词路由而不是图分支。 --- docs/dify_douyu_daily_report_workflow.md | 195 ++++++++++-------- plugins/douyu/斗鱼日报AI.yml | 246 ++++++++++++++++++++--- 2 files changed, 327 insertions(+), 114 deletions(-) diff --git a/docs/dify_douyu_daily_report_workflow.md b/docs/dify_douyu_daily_report_workflow.md index 90e1856..940ebe5 100644 --- a/docs/dify_douyu_daily_report_workflow.md +++ b/docs/dify_douyu_daily_report_workflow.md @@ -1,109 +1,130 @@ -# Dify 工作流设计:斗鱼日报迁移 +# Dify 工作流设计:斗鱼日报最新接入说明 ## 1. 目标 -- 让 `plugins/douyu` 的日报生成从 OpenAI-compatible 切到 Dify Workflow。 -- 同一个工作流同时处理两类任务:`daily_report`(整段日报)和 `danmu_summary`(图片上半部分弹幕总结)。 -- 输出字段统一为 `text`,与项目内 `workflow_output_key` 对齐。 +- 让 `plugins/douyu` 统一通过 Dify Workflow 处理三类斗鱼日报任务。 +- 保持“一个 scene + 一个 workflow” 的接法,减少插件侧和 Dify 侧的维护成本。 +- 让运营版日报、弹幕总结、粉丝向乐子日报共用一套工作流入口,但通过 `task_type` 做风格路由。 -## 2. 项目侧输入约定 -斗鱼插件会向 Dify Workflow 发送以下输入(`inputs`): -- `task_type`: 任务类型,值为 `daily_report` 或 `danmu_summary` -- `query`: 用户提示词(等价于 `user_prompt`) -- `system_prompt`: 系统提示词 -- `user_prompt`: 用户提示词 -- `report_payload`: 结构化 payload(对象) -- `report_payload_json`: payload 的 JSON 字符串 -- `room_id`: 房间号 -- `anchor_day`: 报告日期,例如 `2026-04-20` -- `nickname`: 主播昵称 -- `max_length`: 最大输出长度(默认 1800) +## 2. 当前仓库里的真实接法 +当前项目不是“多个 workflow 按业务拆开”,而是: -## 3. Dify 工作流节点编排 -按以下顺序创建节点。 +1. 插件侧固定走场景 `douyu.daily_report` +2. 场景路由到 `llm.backends.dify_workflow_douyu_daily_report` +3. Dify 端使用一个 Workflow 应用统一接收不同 `task_type` +对应文件: +- 场景路由配置见 [config.yaml](d:/learn/abot/config.yaml:130) +- 斗鱼插件配置见 [plugins/douyu/config.toml](d:/learn/abot/plugins/douyu/config.toml:34) +- 最新工作流导出见 [plugins/douyu/斗鱼日报AI.yml](d:/learn/abot/plugins/douyu/斗鱼日报AI.yml:1) + +## 3. 项目侧输入约定 +斗鱼插件会向 Dify Workflow 发送以下输入: + +- `task_type` + 当前支持三个值: + - `daily_report`:运营版完整日报正文 + - `danmu_summary`:运营版图片上半部分弹幕总结 + - `fans_daily_report`:粉丝向欢乐恶搞日报 +- `query` + 兼容 Dify workflow 的通用输入字段,值通常等于 `user_prompt` +- `system_prompt` + 插件侧拼好的系统提示词 +- `user_prompt` + 插件侧拼好的用户提示词 +- `report_payload_json` + 结构化日报材料 JSON 字符串 +- `room_id` + 房间号 +- `anchor_day` + 报告日期,例如 `2026-04-27` +- `nickname` + 主播昵称 +- `max_length` + 最大输出长度,项目侧当前默认传字符串 + +说明: +- 斗鱼插件目前默认 `include_structured_inputs = false`,见 [plugins/douyu/config.toml](d:/learn/abot/plugins/douyu/config.toml:37) +- 所以最稳的做法是至少在工作流中保留 `report_payload_json` 这个字符串输入 + +## 4. 最新工作流结构 +当前最新导出的 Workflow 结构更接近“提示词路由 + 失败回退”,不是旧版文档里的 Code 节点拼装方案。 + +### 4.1 节点结构 1. `Start` -- 输入变量按“项目侧输入约定”逐个创建。 +2. 主 `LLM` +3. `End` +4. 主 `LLM` 的 `fail-branch` 指向备用 `LLM` +5. 备用 `LLM` 再输出到第二个 `End` -2. `Code` 节点(命名建议:`build_prompt`) -- 作用:根据 `task_type` 统一拼装最终 prompt,避免在 LLM 节点里写复杂模板。 -- 输入:`task_type`、`system_prompt`、`user_prompt`、`report_payload_json`、`max_length` -- 输出: - - `final_prompt` - - `safe_max_length` -- Python 示例: +### 4.2 设计意图 +- 主模型先尝试生成结果 +- 如果主模型节点失败,自动切到备用模型 +- 两个 LLM 节点使用相同的输入协议和相近的提示词规则 +- 业务类型不依赖图上的条件分支,而是依赖 `task_type + system_prompt + user_prompt` -```python -import json +## 5. 是否必须在 Dify 里做业务分支 +结论:当前这套最新版本里,不是必须。 -def main(task_type: str, system_prompt: str, user_prompt: str, report_payload_json: str, max_length: int = 1800): - task = (task_type or "").strip().lower() - length = int(max_length or 1800) - payload_text = report_payload_json or "{}" +原因: +- 插件侧已经针对三类任务分别构造了不同的 `system_prompt` 和 `user_prompt` +- 工作流里也已经接收 `task_type` +- 只要 LLM 节点提示词里明确说明三类任务的输出要求,一个 workflow 就能稳定工作 - # 统一模板:保留项目侧 prompt,同时附加长度约束。 - if task == "danmu_summary": - suffix = "\\n\\n请严格输出弹幕总结,不要输出运营策略,不要使用代码块。" - else: - suffix = "\\n\\n请严格输出完整日报正文,不要输出代码块。" +更准确地说: +- 现在推荐的是“提示词路由” +- 不是“图上 condition 节点硬分叉” - final_prompt = ( - f"{system_prompt or ''}\\n\\n" - f"{user_prompt or ''}\\n\\n" - f"【输出长度约束】最多 {length} 字。\\n" - f"【结构化材料】{payload_text}" - f"{suffix}" - ).strip() +## 6. 什么时候建议再升级成图分支 +如果后续出现下面任一情况,再考虑在 Dify 图里加 `If/Else` 或 `Code + Condition`: - return { - "final_prompt": final_prompt, - "safe_max_length": length, - } -``` +- `fans_daily_report` 想使用和运营日报完全不同的模型 +- 三类任务的温度、最大输出长度、后处理逻辑差异很大 +- 你希望在 Dify 控制台里直观看到每类任务的单独节点与耗时 +- 你希望未来继续加第四类、第五类斗鱼子任务 -3. `LLM` 节点(命名建议:`report_llm`) -- 模型建议:你当前 Dify 可用的大模型里,优先选长上下文、中文稳定的模型。 -- 输入:`build_prompt.final_prompt` -- 参数建议: - - Temperature:`0.2 - 0.4` - - Max tokens:`1200 - 2000`(按模型上限调整) +在目前这版里,先不加图分支反而更稳,改动面更小。 -4. `Code` 节点(命名建议:`normalize_output`) -- 作用:做统一清洗与截断,确保最终给项目的是纯文本。 -- 输入:`report_llm.text`、`build_prompt.safe_max_length` -- 输出:`text` -- Python 示例: +## 7. 最新工作流里必须补齐的点 +如果你使用的是本仓库里的最新版工作流导出,那么至少要确保: -```python -def main(text: str, safe_max_length: int = 1800): - value = (text or "").strip() - max_len = int(safe_max_length or 1800) +1. `Start.task_type` 的说明已经包含 `fans_daily_report` +2. 主 `LLM` 的系统提示词里已经出现三类任务的路由规则 +3. 备用 `LLM` 的系统提示词也同步包含三类任务的路由规则 +4. 输出字段仍然固定为 `text` - # 清理连续空行,避免图片模板排版过疏。 - while "\\n\\n\\n" in value: - value = value.replace("\\n\\n\\n", "\\n\\n") +本仓库已经把这些更新写进 [plugins/douyu/斗鱼日报AI.yml](d:/learn/abot/plugins/douyu/斗鱼日报AI.yml:128)。 - if len(value) > max_len: - value = value[: max_len - 20].rstrip() + "\\n...(已截断)" +## 8. 推荐的 LLM 路由规则 +主 LLM 和备用 LLM 的系统提示词都建议具备这三条兜底规则: - return {"text": value} -``` +- 当 `task_type=danmu_summary` + 输出“弹幕总结”风格,偏现场氛围、观众情绪与高频梗,不写运营策略。 -5. `End` -- 输出变量固定为 `text`。 +- 当 `task_type=daily_report` + 输出“完整日报正文”风格,包含主线、运营观察、热点时段、高频梗等。 -## 4. 关键配置要求 -- Dify 应用类型选择 `Workflow`。 -- API 路径使用 `/v1/workflows/run`。 -- 项目侧 `workflow_output_key` 必须为 `text`。 -- 响应模式建议 `blocking`,避免日报场景流式拼接不完整。 +- 当 `task_type=fans_daily_report` + 输出“粉丝向乐子日报”风格,整体要开心、欢乐、带一点整活气质,更像群友复盘直播间名场面;不要写运营分析,不要出现策略、转化、建议等运营词。 -## 5. 仓库内已完成的切换项 -- `plugins/douyu/main.py`:新增 Dify 专用输入封装与 provider 分流调用。 -- `plugins/douyu/config.toml`:`report_api.backend` 已切换为 `dify_workflow_douyu_daily_report`。 -- `config.yaml`:新增 `llm.backends.dify_workflow_douyu_daily_report` 模板配置。 +## 9. 项目侧对应代码位置 +如果你后续还要联调,这几个方法最关键: -## 6. 上线前检查 -1. 在 `config.yaml` 填入 Dify 工作流真实 `api_key`。 -2. 执行群命令:`#强制斗鱼弹幕日报 2026-04-19`。 -3. 观察日志是否出现 `斗鱼每日报告 LLM 生成失败`。 -4. 若失败,先在 Dify 控制台手动 Run,确认 `End.text` 有值。 +- Dify 输入封装见 [plugins/douyu/main.py](d:/learn/abot/plugins/douyu/main.py:2144) +- 运营版日报任务调用见 [plugins/douyu/main.py](d:/learn/abot/plugins/douyu/main.py:2386) +- 弹幕总结任务调用见 [plugins/douyu/main.py](d:/learn/abot/plugins/douyu/main.py:2231) +- 粉丝日报任务调用见 [plugins/douyu/main.py](d:/learn/abot/plugins/douyu/main.py:2254) + +## 10. 上线前检查 +1. 在 Dify 中重新导入或手动同步最新工作流配置。 +2. 确认 Workflow 的 `End` 输出变量名是 `text`。 +3. 先执行 `#强制斗鱼弹幕日报 2026-04-27`,验证运营版链路。 +4. 再执行 `#强制斗鱼粉丝日报 2026-04-27`,验证粉丝版链路。 +5. 如果某类结果语气不对,优先检查 Dify 中对应 LLM 节点的系统提示词是否已经包含 `fans_daily_report` 路由规则。 + +## 11. 一句建议 +当前这版最优先的不是“在图上拆很多分支”,而是保证: +- 插件传入的 `task_type` 正确 +- Workflow 导出里的两套 LLM 提示词都同步支持 `fans_daily_report` +- `workflow_output_key=text` 不变 + +这样成本最低,和你现在这份“最新 Dify 配置”也最一致。 diff --git a/plugins/douyu/斗鱼日报AI.yml b/plugins/douyu/斗鱼日报AI.yml index 81e2e82..847348b 100644 --- a/plugins/douyu/斗鱼日报AI.yml +++ b/plugins/douyu/斗鱼日报AI.yml @@ -1,6 +1,6 @@ app: - description: 斗鱼直播日报与弹幕总结工作流 - icon: 🎮 + description: 斗鱼直播日报、弹幕总结与粉丝乐子日报工作流 + icon: 🤖 icon_background: '#FFEAD5' mode: workflow name: 斗鱼日报AI @@ -87,6 +87,29 @@ workflow: targetHandle: target type: custom zIndex: 0 + - data: + isInLoop: false + sourceType: llm + targetType: llm + id: 200000002-fail-branch-17766504655450-target + source: '200000002' + sourceHandle: fail-branch + target: '17766504655450' + targetHandle: target + type: custom + zIndex: 0 + - data: + isInIteration: false + isInLoop: false + sourceType: llm + targetType: end + id: 17766504655450-source-1776650579720-target + source: '17766504655450' + sourceHandle: source + target: '1776650579720' + targetHandle: target + type: custom + zIndex: 0 nodes: - data: selected: false @@ -102,8 +125,12 @@ workflow: required: true type: paragraph variable: query + # task_type 是项目侧与 Dify 工作流之间的核心路由字段: + # 1. daily_report:运营版完整日报正文; + # 2. danmu_summary:运营版图片上半部分的弹幕总结; + # 3. fans_daily_report:单独给粉丝看的欢乐恶搞日报。 - default: daily_report - hint: daily_report 或 danmu_summary + hint: daily_report / danmu_summary / fans_daily_report label: task_type max_length: 255 options: [] @@ -174,72 +201,106 @@ workflow: required: false type: paragraph variable: max_length - height: 420 + height: 317 id: '200000001' position: - x: 80 - y: 210 + x: 0 + y: 0 positionAbsolute: - x: 80 - y: 210 + x: 0 + y: 0 selected: false sourcePosition: right targetPosition: left type: custom - width: 260 + width: 242 - data: context: enabled: false variable_selector: [] + error_strategy: fail-branch model: completion_params: temperature: 0.3 mode: chat - name: gpt-5.4 + name: grok-4 provider: langgenius/openai_api_compatible/openai_api_compatible prompt_template: - id: 00000000-0000-0000-0000-000000000001 role: system - text: | - 你是「斗鱼直播日报助手」。 + text: '你是「斗鱼直播日报助手」。 + 你会收到 task_type、提示词和结构化弹幕材料。 + 输出总原则: + 1. 只根据给定材料生成内容,不编造。 + 2. 使用中文自然表达,不要输出代码块。 + 3. 输出纯文本,不要添加额外前后缀说明。 + 4. 严格遵守长度约束:不超过 max_length。 + 任务路由规则: + - 当 task_type=danmu_summary:输出“弹幕总结”风格,偏现场氛围、观众情绪与高频梗,不写运营策略。 + - 当 task_type=daily_report:输出“完整日报正文”风格,包含主线、运营观察、热点时段、高频梗等。 + - 当 task_type=fans_daily_report:输出“粉丝向乐子日报”风格,整体要开心、欢乐、带一点整活气质, + 更像群友复盘直播间名场面;不要写运营分析,不要出现策略、转化、建议等运营词。 + + 若 system_prompt 非空,优先遵循其格式和结构要求。 + + ' - id: 00000000-0000-0000-0000-000000000002 role: user - text: | - 【task_type】 + text: '【task_type】 + {{#200000001.task_type#}} + 【长度限制 max_length】 + {{#200000001.max_length#}} + 【system_prompt】 + {{#200000001.system_prompt#}} + 【user_prompt】 + {{#200000001.user_prompt#}} + 【query(兼容字段)】 + {{#200000001.query#}} + 【meta】 - room_id={{#200000001.room_id#}}, anchor_day={{#200000001.anchor_day#}}, nickname={{#200000001.nickname#}} + + room_id={{#200000001.room_id#}}, anchor_day={{#200000001.anchor_day#}}, + nickname={{#200000001.nickname#}} + 【report_payload_json】 + {{#200000001.report_payload_json#}} + 请按上述信息直接产出最终结果。 + + 如果 task_type=fans_daily_report,请优先保留“现场弹幕感”和“群友整活感”, + 让输出明显区别于运营版日报。 + + ' retry_config: max_retries: 3 retry_enabled: true @@ -249,14 +310,14 @@ workflow: type: llm vision: enabled: false - height: 118 + height: 154 id: '200000002' position: - x: 380 - y: 282 + x: 342 + y: 82 positionAbsolute: - x: 380 - y: 282 + x: 342 + y: 82 selected: true sourcePosition: right targetPosition: left @@ -275,17 +336,148 @@ workflow: height: 88 id: '200000003' position: - x: 682 - y: 282 + x: 704 + y: 89 positionAbsolute: - x: 682 - y: 282 + x: 704 + y: 89 + selected: false + sourcePosition: right + targetPosition: left + type: custom + width: 242 + - data: + context: + enabled: false + variable_selector: [] + model: + completion_params: + temperature: 0.3 + mode: chat + name: gpt-5.4 + provider: langgenius/openai_api_compatible/openai_api_compatible + prompt_template: + - id: 00000000-0000-0000-0000-000000000001 + role: system + text: '你是「斗鱼直播日报助手」。 + + 你会收到 task_type、提示词和结构化弹幕材料。 + + + 输出总原则: + + 1. 只根据给定材料生成内容,不编造。 + + 2. 使用中文自然表达,不要输出代码块。 + + 3. 输出纯文本,不要添加额外前后缀说明。 + + 4. 严格遵守长度约束:不超过 max_length。 + + + 任务路由规则: + + - 当 task_type=danmu_summary:输出“弹幕总结”风格,偏现场氛围、观众情绪与高频梗,不写运营策略。 + + - 当 task_type=daily_report:输出“完整日报正文”风格,包含主线、运营观察、热点时段、高频梗等。 + + - 当 task_type=fans_daily_report:输出“粉丝向乐子日报”风格,整体要开心、欢乐、带一点整活气质, + 更像群友复盘直播间名场面;不要写运营分析,不要出现策略、转化、建议等运营词。 + + + 若 system_prompt 非空,优先遵循其格式和结构要求。 + + ' + - id: 00000000-0000-0000-0000-000000000002 + role: user + text: '【task_type】 + + {{#200000001.task_type#}} + + + 【长度限制 max_length】 + + {{#200000001.max_length#}} + + + 【system_prompt】 + + {{#200000001.system_prompt#}} + + + 【user_prompt】 + + {{#200000001.user_prompt#}} + + + 【query(兼容字段)】 + + {{#200000001.query#}} + + + 【meta】 + + room_id={{#200000001.room_id#}}, anchor_day={{#200000001.anchor_day#}}, + nickname={{#200000001.nickname#}} + + + 【report_payload_json】 + + {{#200000001.report_payload_json#}} + + + 请按上述信息直接产出最终结果。 + + 如果 task_type=fans_daily_report,请优先保留“现场弹幕感”和“群友整活感”, + 让输出明显区别于运营版日报。 + + ' + retry_config: + max_retries: 3 + retry_enabled: true + retry_interval: 1000 + selected: false + title: LLM (1) + type: llm + vision: + enabled: false + height: 118 + id: '17766504655450' + position: + x: 704 + y: 257 + positionAbsolute: + x: 704 + y: 257 + selected: false + sourcePosition: right + targetPosition: left + type: custom + width: 242 + - data: + outputs: + - value_selector: + - '17766504655450' + - text + value_type: string + variable: text + selected: false + title: 输出 2 + type: end + height: 88 + id: '1776650579720' + position: + x: 1046 + y: 272 + positionAbsolute: + x: 1046 + y: 272 sourcePosition: right targetPosition: left type: custom width: 242 viewport: - x: 60 - y: 40 - zoom: 1 + x: 231.0000000000001 + y: 237 + zoom: 0.7 rag_pipeline_variables: []