From 7a041cbb188723ca246ce3e15f4bfe9904942fad Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 8 Sep 2026 11:58:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(platform):=20=E8=83=BD=E5=8A=9B=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E5=88=A4=E6=96=AD=E6=94=B9LLM=E8=A3=81=E5=86=B3(2026-?= =?UTF-8?q?09-08=E7=94=A8=E6=88=B7=E5=AE=9A=E5=A4=BA:=E8=AF=8D=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E4=B8=8D=E5=BD=93=E8=A3=81=E5=86=B3=E8=80=85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qwen-image-plus实测根因:词匹配守卫把描述"图像生成"的输出词当输入证据, 反复把t2i纠正成i2i,人工override也被改回,死循环。 - 新增_llm_capability_judge:utility模型按能力字典语义裁决(输入→输出模态), 失败不裁决不纠正(宁可保守不让词匹配越权) - extract/apply两处:词匹配只当廉价触发器报可疑,裁决交LLM - 人工override权威:__human_cap_overrides__登记的模型apply守卫一律跳过 - LLM裁决改能力后重新_spec_save锚定(否则apply取回旧值裁决白做) --- pipeline_platform/platform_ability.py | 123 ++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 15 deletions(-) diff --git a/pipeline_platform/platform_ability.py b/pipeline_platform/platform_ability.py index 92b0513..4f29e79 100644 --- a/pipeline_platform/platform_ability.py +++ b/pipeline_platform/platform_ability.py @@ -162,6 +162,12 @@ def _spec_overlay(base, overrides): if vmid and newcap and newcap != m.get('capability'): fixed.append('%s: %s→%s' % (vmid, m.get('capability'), newcap)) m['capability'] = newcap + # 人工纠正登记(2026-09-08):apply 阶段的能力守卫必须跳过 + # 这些模型——人工指令是权威,守卫再纠正=把用户的纠正改回去 + # (qwen-image-plus 实测死循环的机制根源)。 + hc = dict(out.get('__human_cap_overrides__') or {}) + hc[vmid] = newcap + out['__human_cap_overrides__'] = hc out['models'] = models if fixed: out['doc_notes'] = ((out.get('doc_notes') or '') + @@ -449,17 +455,35 @@ async def _h_extract_llm_api_spec(sor, params, ctx): "需改动只传覆盖项(overrides 白名单:vendor_name/base_url/" "protocol/doc_url/doc_notes/model_capability)。" "禁止把本规格复制进 spec 参数——复制即编造。") - # 能力一致性告警(2026-09-06 wan3.0 教训):提取完当场校验描述模态声明 vs - # capability,矛盾信息直接回给助手(用户纠正时传 overrides.model_capability 重 apply) + # 能力一致性告警(2026-09-06 wan3.0 教训;2026-09-08 升级:词匹配只当触发器, + # 裁决交 LLM——qwen-image-plus 实测词匹配把输出词当输入证据、t2i 被反复改成 i2i) cap_alerts = [] + _cap_changed = False for _m in (spec.get("models") or []): _fix, _warn = _capability_guard(_m.get("capability", ""), _m.get("description", "")) - if _warn: - cap_alerts.append("%s: %s" % (_m.get("vendor_model_id", "?"), _warn)) + if _warn or _fix: + # 词匹配报可疑 → LLM 语义裁决(裁决失败则只告警不纠正) + _jfix, _jnote = await _llm_capability_judge( + _m.get("capability", ""), _m.get("description", ""), + _m.get("vendor_model_id", "")) + if _jfix: + _m["capability"] = _jfix + _cap_changed = True + cap_alerts.append("%s: %s(已按 LLM 裁决更新提取规格)" + % (_m.get("vendor_model_id", "?"), _jnote)) + elif _jnote: + cap_alerts.append("%s: %s" % (_m.get("vendor_model_id", "?"), _jnote)) + elif _warn: + cap_alerts.append("%s: %s(词匹配可疑,LLM 裁决维持原判)" + % (_m.get("vendor_model_id", "?"), _warn)) + # 裁决改了能力 → 必须重新锚定:_spec_save 在裁决前已存旧值,不重存则 + # apply(use_last_extract=true) 取回的还是旧能力,LLM 裁决白做(实测根因)。 + if _cap_changed: + _spec_save(ctx, spec) if cap_alerts: - out["__capability_alert__"] = ("能力分类与文档描述矛盾(按示例误判的典型信号)," - "apply 时会自动纠正,也可用 overrides.model_capability " - "人工指定:" + ";".join(cap_alerts)) + out["__capability_alert__"] = ("能力分类存疑项(LLM 语义裁决结果):" + + ";".join(cap_alerts) + + "。如需人工指定用 overrides.model_capability。") return json.dumps(out, ensure_ascii=False) @@ -481,6 +505,58 @@ _DESC_MEDIA_WORDS = { _DESC_REF_WORDS = ('参考生', '全能参考', '参考图', '参考视频', '参考音频', '四模态', '多模态参考') +async def _llm_capability_judge(capability, description, vendor_model_id=''): + """LLM 语义裁决能力分类(2026-09-08 用户定夺:语义判断必须 LLM 参与, + 词匹配只能当廉价触发器,不能当裁决者)。 + + 背景:缺陷2(qwen-image-plus)实测——关键词守卫把描述里「图像生成」的 + 「图像」(输出词)当输入证据,反复把 t2i 纠正成 i2i,人工 override 也被 + 改回去,死循环。词匹配分不清输入/输出语义,LLM 能。 + + 分工:_capability_guard(词匹配)只报「可疑」;本函数用 utility 廉价模型 + 按能力字典语义裁决。LLM 失败时返回 (None, 原因)——不裁决就不纠正, + 保持提取 LLM 的原判定(宁可保守也不让词匹配越权)。 + """ + if not (description or '').strip(): + return None, '' + prompt = ( + '你是模型能力分类裁决器。模型 %s 的文档描述:\n%s\n\n' + '当前判定的能力分类:%s\n\n' + '能力分类字典(按输入→输出模态判定,描述里的输出媒体词不算输入证据):\n' + 't2t=文本→文本;i2t=图像→文本;m2t=多媒体→文本;t2i=文本→图像;' + 'i2i=图像→图像;t2v=文本→视频;i2v=图像→视频;r2v=参考媒体(图/视频/音频组合)→视频;' + 'tts=文本→语音;asr=语音→文本;embedding=文本→向量;rerank=重排序\n\n' + '请判断当前分类是否与描述的输入/输出模态一致。只返回 JSON:' + '{"consistent": true/false, "capability": "正确分类(不一致时给出)", ' + '"reason": "一句话依据"}' % (vendor_model_id or '(未命名)', description[:600], + capability or '(空)')) + try: + from pipeline_service.llm_bridge import llm_call_msgs + raw = await llm_call_msgs( + [{"role": "user", "content": prompt}], + temperature=0, org_id="0", purpose="utility", timeout=60) + except Exception as e: + return None, 'LLM 裁决调用失败(保持原判定不纠正): %s' % repr(e)[:120] + txt = (raw or '').strip() + m = re.search(r'\{[\s\S]*\}', txt) + if not m: + return None, 'LLM 裁决输出非 JSON(保持原判定不纠正): %s' % txt[:120] + try: + obj = json.loads(m.group(0)) + except Exception: + return None, 'LLM 裁决 JSON 解析失败(保持原判定不纠正)' + if not isinstance(obj, dict): + return None, '' + if obj.get('consistent') is True: + return None, '' + newcap = str(obj.get('capability') or '').strip().lower() + reason = str(obj.get('reason') or '')[:150] + if newcap and newcap != (capability or ''): + return newcap, 'LLM 语义裁决 %s→%s:%s' % (capability, newcap, reason) + return None, ('LLM 裁决认为能力分类存疑但未给出替代(%s),请人工核对' + % reason) if reason else None + + def _capability_guard(capability, description): """描述模态声明 vs capability 一致性守卫(2026-09-06 wan3.0-video-prime 实测: 描述自带"支持四模态全能参考"、capability 却是 t2v——提取 LLM 照调用示例判能力, @@ -590,18 +666,35 @@ async def _h_apply_llm_config(sor, params, ctx): if not models: return "spec.models 为空——文档里没有可配置的模型?" - # 能力一致性守卫前置(2026-09-06 wan3.0 二次教训):必须在 caps 计算/模板 - # 生成之前跑——上一版放在模型循环里,纠正 t2v→r2v 时模板已按 t2v 生成, - # profile_ids 无 r2v 键,模型更新的 IF 空值保护把旧 t2v 模板原样保留 - # (实测:template_notes 有纠正记录、库里 cap 仍是 t2v)。 + # 能力一致性守卫前置(2026-09-06 wan3.0 二次教训;2026-09-08 用户定夺升级): + # 必须在 caps 计算/模板生成之前跑——纠正能力后模板按新能力生成,否则 + # profile_ids 无该能力键、模型更新的 IF 空值保护把旧模板原样保留。 + # ⚠️ 语义判断必须 LLM 参与:词匹配只当廉价触发器(报可疑),裁决交 + # _llm_capability_judge;**人工 override 的模型一律跳过守卫**——人工指令是 + # 权威,守卫把用户纠正改回去正是 qwen-image-plus 死循环的机制根源。 cap_notes = [] + human_caps = spec.get('__human_cap_overrides__') or {} for m in models: + vmid = (m.get('vendor_model_id') or m.get('name') or '').strip() + if vmid and vmid in human_caps: + cap_notes.append("%s: 能力按人工指定 %s(守卫已跳过,不再自动纠正)" + % (vmid, human_caps[vmid])) + continue _fix, _warn = _capability_guard( m.get("capability") or "", m.get("description") or "") - if _fix: - m["capability"] = _fix - if _warn: - cap_notes.append("%s: %s" % (m.get("vendor_model_id") or "?", _warn)) + if not (_fix or _warn): + continue + _jfix, _jnote = await _llm_capability_judge( + m.get("capability") or "", m.get("description") or "", vmid) + if _jfix: + m["capability"] = _jfix + cap_notes.append("%s: %s" % (vmid or "?", _jnote)) + elif _jnote: + cap_notes.append("%s: %s" % (vmid or "?", _jnote)) + else: + cap_notes.append("%s: 词匹配报可疑但 LLM 裁决维持 %s(%s)" + % (vmid or "?", m.get("capability") or "?", + (_warn or _fix or '')[:80])) # 能力分类校验(2026-09-05 用户定夺的规则):模型能力必须是字典已登记的 # 分类;不存在则拒绝落库——先加能力分类(appcodes_kv llm_capability,