From 9dd4fbd8909a45d547a3ef9a141408423fb1b1fc Mon Sep 17 00:00:00 2001 From: rui40000 Date: Fri, 24 Jul 2026 14:30:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20ZenMux=20=E8=8A=82=E7=82=B9=E8=87=AA?= =?UTF-8?q?=E9=80=82=E5=BA=94=E5=8F=82=E6=95=B0=E9=87=8D=E8=AF=95=EF=BC=8C?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E5=BC=83=E7=94=A8=20temperature=20=E7=AD=89?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户实测 anthropic/claude-sonnet-5 报 400:`temperature` is deprecated。 根因:节点无条件把 temperature/top_p/seed/max_tokens 塞进请求体,而 ZenMux 聚合的部分模型弃用/不支持这些采样参数,规则各家不一且随模型迭代变化。 - 新增 _diagnose_param:从 400 错误消息识别是哪个采样参数导致失败 - 请求改为重试循环:命中即剔除该参数(或把 max_tokens 改名为 max_completion_tokens,适配 gpt-5 reasoning 系)后重试 - 只动可选采样参数,绝不触碰 model/messages;非参数类 400 不重试、不死循环 - 正常请求零额外开销;剔除动作打印到控制台,最终仍失败时提示已剔除项 Co-Authored-By: Claude Fable 5 --- README.md | 1 + zenmux/zenmux_node.py | 68 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e724693..f57b0b9 100644 --- a/README.md +++ b/README.md @@ -733,6 +733,7 @@ ComfyUI-SDMatte 传 `aux_input="trimap"`,把模型推到了没训练过的输 模型类型:openai/gpt-5.4-nano [入$0.2/M 出$1.25/M] 价格换算,美元:0.000955,人民币:0.006876 ``` +- **自动参数兼容**: 部分模型弃用或不支持某些采样参数(如 `claude-sonnet-5` 弃用 `temperature`、gpt-5 reasoning 系要求 `max_completion_tokens`)。节点会在收到相关 400 错误时**自动剔除或改名该参数并重试**,无需手动调整;剔除动作会打印到 ComfyUI 控制台。正常请求不受影响、无额外开销。 **输入参数**: - `api_key` (STRING): ZenMux 平台的 API Key(在 zenmux.ai 控制台获取) diff --git a/zenmux/zenmux_node.py b/zenmux/zenmux_node.py index e8aa1a8..4c9acc6 100644 --- a/zenmux/zenmux_node.py +++ b/zenmux/zenmux_node.py @@ -80,6 +80,46 @@ def _build_proxy_url(raw_proxy: str) -> str: return ('http://' + s) if s else '' +# 遇到「参数不被支持 / 已弃用」的 400 时,可安全剔除的采样参数 +# (绝不触碰 model / messages 等核心字段) +_ADJUSTABLE_PARAMS = ("temperature", "top_p", "seed", "max_tokens", + "max_completion_tokens", "frequency_penalty", + "presence_penalty", "top_k") +# 判定「这条 400 是参数问题」的关键词(命中才尝试剔除重试) +_PARAM_ERR_HINTS = ("deprecat", "unsupport", "not support", "not allowed", + "invalid", "unexpected", "unknown", "must be", "cannot", + "not permitted", "removed") + + +def _diagnose_param(resp, payload): + """ + 从 400 响应里判断是哪个采样参数导致失败,返回处理指令: + ("drop", 参数名) —— 从 payload 剔除该参数后重试 + ("rename_mct", ...) —— 把 max_tokens 改名为 max_completion_tokens 后重试 + None —— 非参数类错误,不重试 + ZenMux 各模型参数规则不一(claude-sonnet-5 弃用 temperature、gpt-5 reasoning + 系要求 max_completion_tokens 等),靠错误消息动态识别,免维护静态清单。 + """ + try: + msg = (resp.json().get("error", {}) or {}).get("message", "") or resp.text or "" + except Exception: + msg = getattr(resp, "text", "") or "" + low = msg.lower() + # 特例:OpenAI reasoning 系要求用 max_completion_tokens 替代 max_tokens + if "max_completion_tokens" in low and "max_tokens" in payload: + return ("rename_mct", "max_tokens") + # 反引号/引号里的参数名优先(错误消息通常形如 `temperature` is deprecated) + for name in re.findall(r"""[`'"]([a-zA-Z_]+)[`'"]""", msg): + if name in payload and name in _ADJUSTABLE_PARAMS: + return ("drop", name) + # 回退:错误像参数问题时,扫描 payload 里哪个参数名出现在消息中 + if any(h in low for h in _PARAM_ERR_HINTS): + for name in _ADJUSTABLE_PARAMS: + if name in payload and name in low: + return ("drop", name) + return None + + class ZenMuxNode: """ZenMux API 连接节点。""" @@ -326,9 +366,30 @@ class ZenMuxNode: proxies = {"http": p, "https": p} resp = None + dropped = [] # 记录被剔除/改名的参数,供最终报错时提示 try: - resp = requests.post(chat_url, headers=headers, json=payload, - proxies=proxies, timeout=180) + # 自适应参数重试:ZenMux 聚合的部分模型弃用/不支持某些采样参数 + # (claude-sonnet-5 弃用 temperature、gpt-5 reasoning 系要 max_completion_tokens + # 等),命中即剔除/改名后重试,正常请求不受影响、零额外开销。 + for _ in range(len(_ADJUSTABLE_PARAMS) + 2): + resp = requests.post(chat_url, headers=headers, json=payload, + proxies=proxies, timeout=180) + if resp.status_code == 400: + fix = _diagnose_param(resp, payload) + if fix: + action, param = fix + if action == "rename_mct": + payload["max_completion_tokens"] = payload.pop("max_tokens") + dropped.append("max_tokens→max_completion_tokens") + print("[Rui-Node] ZenMux: 该模型要求 max_completion_tokens," + "已改名重试") + else: + payload.pop(param, None) + dropped.append(param) + print(f"[Rui-Node] ZenMux: 该模型不支持参数 '{param}'," + "已剔除后重试") + continue + break resp.raise_for_status() data = resp.json() if data.get("choices"): @@ -352,7 +413,8 @@ class ZenMuxNode: except requests.exceptions.HTTPError: code = resp.status_code if resp is not None else "?" body = resp.text[:600] if resp is not None else "" - return (f"HTTP 错误 {code}: {body}", model_id, zero_stats) + hint = f"(已尝试剔除参数 {', '.join(dropped)} 仍失败)" if (code == 400 and dropped) else "" + return (f"HTTP 错误 {code}: {body}{hint}", model_id, zero_stats) except Exception as e: return (f"请求异常: {type(e).__name__}: {e}", model_id, zero_stats)