feat(govern): 能力降级规则i2i→t2i/r2v→t2v(2026-09-08用户定夺,与r2v→t2v同例)——selection加CAP_DEGRADE事实源+degrade_expand,models_catalog候选池含降级源模型,网关备链/owner容错链同能力铁律放行合法降级源,auto_select提示词补降级规则;单向降级反向永远非法

This commit is contained in:
yumoqing 2026-09-08 17:20:39 +08:00
parent fba76a0cdc
commit d086c4f7d2
2 changed files with 44 additions and 5 deletions

View File

@ -201,10 +201,18 @@ async def _model_chain(sor, org_id: str, model_name: str, purpose: str = ''):
# sqlor IN 列表必须展开占位符(传 list 会崩)
ph = ','.join('${b%d}$' % i for i in range(len(backups)))
bparams = {'b%d' % i: b for i, b in enumerate(backups)}
# 能力降级2026-09-08备链允许同能力或合法降级源能力
# t2i→i2i、t2v→r2v跨能力如 t2t仍永远非法
from .selection import CAP_DEGRADE_SOURCE
_alt = CAP_DEGRADE_SOURCE.get(want_cap, '')
if _alt:
ph += ',${altcap}$'
bparams['altcap'] = _alt
bparams['cap'] = want_cap
brecs = await sor.sqlExe(
"SELECT id FROM llm_model WHERE status='active' AND id IN (%s) "
"AND COALESCE(NULLIF(capability,''),'t2t')=${cap}$" % ph, bparams)
_sql = ("SELECT id FROM llm_model WHERE status='active' AND id IN (%s) "
"AND COALESCE(NULLIF(capability,''),'t2t') IN (${cap}$"
+ (",${altcap}$" if _alt else "") + ")") % ph
brecs = await sor.sqlExe(_sql, bparams)
await sor.sqlExe("COMMIT", {})
for br in (brecs or []):
bid = getattr(br, 'id', '')
@ -542,10 +550,14 @@ async def govern_resolve(org_id: str, user_id: str = '', model_name: str = '',
models0 = [m for m in models0 if _owner_allowed(m.get('org_id'), org_id)]
# 同能力铁律2026-09-07显式指定模型时 owner 容错链也只许
# 同能力替代——要图给文的静默回退在任何一层都不合法。
# 能力降级2026-09-08合法降级源能力t2i→i2i、t2v→r2v除外。
if model_name and models0:
from .selection import CAP_DEGRADE_SOURCE
_want = (models0[0].get('capability') or 't2t').strip().lower()
_alt = CAP_DEGRADE_SOURCE.get(_want, '')
models0 = [m for m in models0
if (m.get('capability') or 't2t').strip().lower() == _want]
if (m.get('capability') or 't2t').strip().lower()
in (_want, _alt)]
for m in models0:
ok, cand = await _pick_candidate(sor, m, pref0)
if ok and isinstance(cand, tuple):

View File

@ -23,6 +23,26 @@ logger = logging.getLogger("pipeline_llm.selection")
# 会话 agent 模型下拉只列这些embedding/rerank/图视频生成等形态各有专属入口。
CHAT_CAPS = ('t2t', 'i2t', 'm2t')
# 能力降级表2026-09-08 用户定夺,与 r2v→t2v 同例):请求能力无可用模型时,
# 允许降级用「超集能力」模型执行任务。方向 = 降级目标模型的输入契约覆盖
# 请求能力:
# i2i图像→图像接受文本提示+可选图)可执行 t2i文本→图像任务
# r2v参考媒体→视频接受文本+参考媒体)可执行 t2v文本→视频任务
# 单向降级t2i 模型不能接 i2i缺源图、t2v 不能接 r2v——反向永远非法。
CAP_DEGRADE = {'t2i': 'i2i', 't2v': 'r2v'}
# 反向索引:降级源能力 → 请求能力门禁契约换算用i2i 模型无图输入时按 t2i 契约校验)
CAP_DEGRADE_SOURCE = {v: k for k, v in CAP_DEGRADE.items()}
def degrade_expand(caps):
"""能力白名单 + 其合法降级源能力(候选池扩展用)。无映射时原样返回。"""
out = [str(c).strip().lower() for c in (caps or ()) if str(c or '').strip()]
for c in list(out):
d = CAP_DEGRADE.get(c)
if d and d not in out:
out.append(d)
return tuple(out)
def _norm_caps(capabilities):
"""capabilities 参数归一:'chat' 哨兵 → CHAT_CAPS逗号串 → tuple
@ -178,6 +198,10 @@ async def models_catalog(org_id, capabilities=(), limit=50):
"""
db, dbname = _get_sor()
caps = _norm_caps(capabilities)
# 能力降级2026-09-08候选池含请求能力的合法降级源模型
# t2i→i2i、t2v→r2v无可用 t2i 模型时 i2i 模型可承接文生图任务
if caps:
caps = degrade_expand(caps)
async with db.sqlorContext(dbname) as sor:
sql = ("SELECT m.id, m.name, m.vendor_model_id, m.capability, m.description, "
"m.org_id, v.name AS vendor_name "
@ -249,7 +273,10 @@ async def auto_select_model(org_id, user_input, user_id='', capabilities='chat')
'(格式:模型名 [能力类型] 供应商:描述):\n%s\n\n'
'能力类型含义t2t=文本对话 i2t=图像理解 m2t=多媒体理解 t2i=文生图 '
'i2v=图生视频 t2v=文生视频 r2v=参考生视频 tts=语音合成 asr=语音识别 '
'embedding=向量化 rerank=重排序\n\n'
'embedding=向量化 rerank=重排序\n'
'能力降级规则:无 t2i 模型时 i2i图生图模型可承接文生图任务'
'无 t2v 模型时 r2v参考生视频模型可承接文生视频任务'
'反向t2i 接图生图、t2v 接参考生视频)非法。\n\n'
'用户任务:\n%s\n\n'
'请根据任务的内容领域、语言、复杂度,从目录中选出最适合完成该任务的模型'
'(只能选目录中的模型名,按适合度从高到低排序,最多 3 个)。'