fix(platform): 能力语义判断改LLM裁决(2026-09-08用户定夺:词匹配不当裁决者)

qwen-image-plus实测根因:词匹配守卫把描述"图像生成"的输出词当输入证据,
反复把t2i纠正成i2i,人工override也被改回,死循环。
- 新增_llm_capability_judge:utility模型按能力字典语义裁决(输入→输出模态),
  失败不裁决不纠正(宁可保守不让词匹配越权)
- extract/apply两处:词匹配只当廉价触发器报可疑,裁决交LLM
- 人工override权威:__human_cap_overrides__登记的模型apply守卫一律跳过
- LLM裁决改能力后重新_spec_save锚定(否则apply取回旧值裁决白做)
This commit is contained in:
yumoqing 2026-09-08 11:58:55 +08:00
parent 48735b98ba
commit 7a041cbb18

View File

@ -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-08apply 阶段的能力守卫必须跳过
# 这些模型——人工指令是权威,守卫再纠正=把用户的纠正改回去
# 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 参与,
词匹配只能当廉价触发器不能当裁决者
背景缺陷2qwen-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