修复斗鱼日报Dify 400请求并增强错误诊断

变更项:\n1. 在斗鱼插件新增 daily_report_include_structured_inputs 配置(默认 false),Dify 入参默认改为精简字段(task_type/query/system_prompt/user_prompt/room_id 等),避免复杂对象输入触发 400。\n2. 调整 _build_dify_daily_report_inputs:仅在 include_structured_inputs=true 时才附带 report_payload_json,大幅降低 Workflow 入参类型不匹配风险。\n3. 在初始化流程中读取 report_api.include_structured_inputs,支持按环境开关结构化入参。\n4. 在 UnifiedLLMClient 的 Dify 调用中新增 HTTPError 细粒度处理,last_error 与日志会追加 response_body 片段,便于快速定位 Dify 侧具体报错原因。\n5. 更新 douyu 配置模板注释,明确复杂输入导致 400 的风险与开关用途,方便后续维护。
This commit is contained in:
liuwei
2026-04-20 13:36:56 +08:00
parent 6386cd5940
commit 382f3967bd
3 changed files with 39 additions and 5 deletions

View File

@@ -29,3 +29,6 @@ audience_stats_sample_interval_seconds = 0
[Douyu.report_api] [Douyu.report_api]
# 切换到 Dify 斗鱼日报专用工作流;对应配置位于根目录 config.yaml 的 llm.backends。 # 切换到 Dify 斗鱼日报专用工作流;对应配置位于根目录 config.yaml 的 llm.backends。
backend = "dify_workflow_douyu_daily_report" backend = "dify_workflow_douyu_daily_report"
# 是否把完整结构化 payloadJSON 大对象)作为输入传给 Dify。
# 某些 Workflow 对复杂输入类型校验严格,会导致 400默认关闭以保证可用性。
include_structured_inputs = false

View File

@@ -523,6 +523,10 @@ class DouyuPlugin(MessagePluginInterface):
self._daily_report_max_sessions = 4 self._daily_report_max_sessions = 4
self._daily_report_max_length = 1800 self._daily_report_max_length = 1800
self._daily_report_send_image = True self._daily_report_send_image = True
# Dify 入参策略:
# 默认发送精简字段,避免某些 Workflow 对复杂对象输入校验严格导致 400。
# 如需在工作流中使用完整结构化 payload可在 report_api 显式开启。
self._daily_report_include_structured_inputs = False
self._audience_stats_sample_interval_seconds = 60 self._audience_stats_sample_interval_seconds = 60
self._status_check_retry_count = 3 self._status_check_retry_count = 3
self._status_check_retry_delay_seconds = 1 self._status_check_retry_delay_seconds = 1
@@ -613,6 +617,12 @@ class DouyuPlugin(MessagePluginInterface):
) )
report_api_cfg = cfg.get("report_api", {}) or {} report_api_cfg = cfg.get("report_api", {}) or {}
self._daily_report_include_structured_inputs = bool(
report_api_cfg.get(
"include_structured_inputs",
self._daily_report_include_structured_inputs,
)
)
if report_api_cfg: if report_api_cfg:
self._daily_report_llm_client = UnifiedLLMClient(report_api_cfg) self._daily_report_llm_client = UnifiedLLMClient(report_api_cfg)
return True return True
@@ -1857,8 +1867,11 @@ class DouyuPlugin(MessagePluginInterface):
room_id = str(meta.get("room_id") or "").strip() room_id = str(meta.get("room_id") or "").strip()
anchor_day = str(meta.get("anchor_day") or "").strip() anchor_day = str(meta.get("anchor_day") or "").strip()
nickname = str(meta.get("nickname") or meta.get("room_name") or "").strip() nickname = str(meta.get("nickname") or meta.get("room_name") or "").strip()
payload_json = json.dumps(payload, ensure_ascii=False) # 说明:
return { # 1. 部分 Dify Workflow 对输入变量类型校验较严格,复杂对象(dict/list)容易触发 400
# 2. 默认只提交精简字符串字段,优先保证链路可用;
# 3. 如需在工作流内使用完整载荷,可通过 include_structured_inputs 开关启用。
inputs = {
# 任务路由字段:在 Dify 条件分支里用于区分日报正文/弹幕摘要。 # 任务路由字段:在 Dify 条件分支里用于区分日报正文/弹幕摘要。
"task_type": task_type, "task_type": task_type,
# 兼容 Workflow 中直接读取 query 的场景。 # 兼容 Workflow 中直接读取 query 的场景。
@@ -1866,9 +1879,6 @@ class DouyuPlugin(MessagePluginInterface):
# 保留原有两段提示词,便于工作流内部二次拼装或调试。 # 保留原有两段提示词,便于工作流内部二次拼装或调试。
"system_prompt": system_prompt, "system_prompt": system_prompt,
"user_prompt": user_prompt, "user_prompt": user_prompt,
# 结构化报告载荷:既提供对象,也提供 JSON 文本,适配不同节点处理能力。
"report_payload": payload,
"report_payload_json": payload_json,
# 关键元信息:用于日志、标题拼接、数据看板或异常追踪。 # 关键元信息:用于日志、标题拼接、数据看板或异常追踪。
"room_id": room_id, "room_id": room_id,
"anchor_day": anchor_day, "anchor_day": anchor_day,
@@ -1876,6 +1886,9 @@ class DouyuPlugin(MessagePluginInterface):
# 控制输出长度:避免 Dify 侧生成超长内容后再被本地硬截断。 # 控制输出长度:避免 Dify 侧生成超长内容后再被本地硬截断。
"max_length": int(self._daily_report_max_length or 1800), "max_length": int(self._daily_report_max_length or 1800),
} }
if self._daily_report_include_structured_inputs:
inputs["report_payload_json"] = json.dumps(payload, ensure_ascii=False)
return inputs
def _call_daily_report_llm( def _call_daily_report_llm(
self, self,

View File

@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
import requests import requests
from requests import HTTPError
from loguru import logger from loguru import logger
from utils.ai.llm_registry import LLMRegistry from utils.ai.llm_registry import LLMRegistry
@@ -282,6 +283,23 @@ class UnifiedLLMClient:
if parsed and parsed.get("text"): if parsed and parsed.get("text"):
return parsed return parsed
self.last_error = f"empty_model_output:{self.mode}" self.last_error = f"empty_model_output:{self.mode}"
except HTTPError as exc:
# 诊断增强:
# 1. Dify 返回 400 时,异常信息默认只包含状态码和 URL不含具体原因
# 2. 这里把响应体片段追加到 last_error便于快速定位“入参字段/类型”问题。
response_text = ""
response_obj = getattr(exc, "response", None)
if response_obj is not None:
try:
response_text = str(response_obj.text or "").strip()
except Exception:
response_text = ""
response_text = response_text[:500] if response_text else ""
self.last_error = (
f"request_failed:attempt_{attempt}:{exc}"
+ (f" | response_body={response_text}" if response_text else "")
)
self.LOG.warning(f"[UnifiedLLMClient] Dify 请求失败: tag={tag}, attempt={attempt}, error={self.last_error}")
except Exception as exc: except Exception as exc:
self.last_error = f"request_failed:attempt_{attempt}:{exc}" self.last_error = f"request_failed:attempt_{attempt}:{exc}"
self.LOG.warning(f"[UnifiedLLMClient] Dify 请求失败: tag={tag}, attempt={attempt}, error={exc}") self.LOG.warning(f"[UnifiedLLMClient] Dify 请求失败: tag={tag}, attempt={attempt}, error={exc}")