fix(platform): 能力误判根治三层——提取提示词能力判定铁律(按正文模态声明禁照示例);apply能力一致性守卫(描述模态vs capability矛盾代码级纠正t2v→r2v);overrides.model_capability白名单(用户口头纠正能落进规格,禁重提取死循环);check_model_accounting区分failed调用行/充值行非待记账

This commit is contained in:
yumoqing 2026-09-06 14:56:34 +08:00
parent a24a2320de
commit b2ecf54f43

View File

@ -136,12 +136,35 @@ def _spec_overlay(base, overrides):
"""覆盖项合并:仅允许少量标量覆盖(vendor_name 等),不做大 JSON 搬运。 """覆盖项合并:仅允许少量标量覆盖(vendor_name 等),不做大 JSON 搬运。
overrides 只认白名单键,防止 LLM 借覆盖项塞回编造结构。 overrides 只认白名单键,防止 LLM 借覆盖项塞回编造结构。
model_capability(2026-09-06 wan3.0 教训):{vendor_model_id: capability}
按模型纠正能力——用户提醒「这是参考生视频」后助手必须有工具路径把纠正
落进规格,否则只能重跑 extract,同样提示词同样误判(提醒无效的死循环)。
纠正值仍要过能力字典校验(下游 unknown 拒绝落库),不会被塞私货。
""" """
allowed = {'vendor_name', 'base_url', 'protocol', 'doc_url', 'doc_notes'} allowed = {'vendor_name', 'base_url', 'protocol', 'doc_url', 'doc_notes'}
out = dict(base or {}) out = dict(base or {})
for k in allowed: for k in allowed:
if k in (overrides or {}) and str(overrides.get(k) or '').strip(): if k in (overrides or {}) and str(overrides.get(k) or '').strip():
out[k] = str(overrides[k]).strip() out[k] = str(overrides[k]).strip()
mc = (overrides or {}).get('model_capability')
if isinstance(mc, str):
try:
mc = json.loads(mc)
except Exception:
mc = None
if isinstance(mc, dict) and mc:
fixed = []
models = list(out.get('models') or [])
for m in models:
vmid = m.get('vendor_model_id') or m.get('name') or ''
newcap = str(mc.get(vmid) or '').strip()
if vmid and newcap and newcap != m.get('capability'):
fixed.append('%s: %s→%s' % (vmid, m.get('capability'), newcap))
m['capability'] = newcap
out['models'] = models
if fixed:
out['doc_notes'] = ((out.get('doc_notes') or '') +
' [能力已按人工纠正: %s]' % ';'.join(fixed)).strip()
return out return out
@ -335,7 +358,7 @@ _EXTRACT_PROMPT = """你是大模型 API 配置专家。通读下面这份模型
"response_format": "响应格式说明(content 字段路径 + usage 字段路径)", "response_format": "响应格式说明(content 字段路径 + usage 字段路径)",
"response_example": {"说明": "文档中的响应示例 JSON(或结果字段说明),逐字照抄;无则 null"}, "response_example": {"说明": "文档中的响应示例 JSON(或结果字段说明),逐字照抄;无则 null"},
"async_steps": [{"purpose": "query|download", "path": "该步骤接口路径", "method": "GET|POST", "request_fields": ["请求字段名列表"], "response_format": "该步骤响应格式说明"}], "async_steps": [{"purpose": "query|download", "path": "该步骤接口路径", "method": "GET|POST", "request_fields": ["请求字段名列表"], "response_format": "该步骤响应格式说明"}],
"models": [{"vendor_model_id": "供应商侧模型ID", "capability": "t2t|t2i|i2t|t2v|i2v|r2v|i2i|embedding|rerank|tts|asr", "models": [{"vendor_model_id": "供应商侧模型ID", "capability": "t2t|t2i|i2t|t2v|i2v|r2v|i2i|embedding|rerank|tts|asr|mm-embedding|mm-rerank",
"sync_mode": "sync|async(提交后需轮询查询结果的填 async)", "sync_mode": "sync|async(提交后需轮询查询结果的填 async)",
"description": "一句话说明"}], "description": "一句话说明"}],
"pricing": { "pricing": {
@ -358,6 +381,16 @@ _EXTRACT_PROMPT = """你是大模型 API 配置专家。通读下面这份模型
path 照文档逐字抄;同供应商多个模型会复用同一份查询模板,不要因模型而异。 path 照文档逐字抄;同供应商多个模型会复用同一份查询模板,不要因模型而异。
- 同步模型 async_steps 填空数组 []。 - 同步模型 async_steps 填空数组 []。
能力判定铁律(2026-09-06 wan3.0-video-prime 误判教训——概述明写"四模态全能参考"、
输入=音频/图像/文本/视频,却因调用示例只有 prompt 被判成 t2v):
- capability 必须以文档**概述正文 + 输入/输出模态声明**为准,禁止以调用示例为准——
示例常只演示最简形态(纯文本 prompt),照示例判会把多媒体参考模型误判成纯文本模型。
- 判定表(输入模态 → 输出模态 ⇒ 能力):
多种媒体输入或正文含「参考/reference/全能参考/多图/多模态输入」⇒ r2v(即使示例只有文本);
图像+文本 → 视频 ⇒ i2v;仅文本 → 视频 ⇒ t2v;仅文本 → 图像 ⇒ t2i;图像 → 图像 ⇒ i2i;
视频 → 视频 ⇒ v2v 类按字典就近;文本 → 音频 ⇒ tts;音频 → 文本 ⇒ asr。
- description 里必须保留正文的模态声明原文关键词(如"支持四模态全能参考"),供下游一致性校验。
定价规则: 定价规则:
- pricing.items 每条必须带 doc_quote(文档原文定价句逐字照抄)——定价只允许来自文档原文 - pricing.items 每条必须带 doc_quote(文档原文定价句逐字照抄)——定价只允许来自文档原文
- 价格/单位按文档原文记录,禁止自行换算、推导或统一单位 - 价格/单位按文档原文记录,禁止自行换算、推导或统一单位
@ -411,9 +444,20 @@ async def _h_extract_llm_api_spec(sor, params, ctx):
if anchored: if anchored:
out["__anchored__"] = ("规格已锚定到本会话缓存。下一步调用 apply_llm_config 与 " out["__anchored__"] = ("规格已锚定到本会话缓存。下一步调用 apply_llm_config 与 "
"apply_model_pricing 时传 {\"use_last_extract\": true}," "apply_model_pricing 时传 {\"use_last_extract\": true},"
"需改动只传覆盖项(overrides,仅 vendor_name/base_url/" "需改动只传覆盖项(overrides 白名单:vendor_name/base_url/"
"protocol/doc_url/doc_notes 白名单键)。" "protocol/doc_url/doc_notes/model_capability)。"
"禁止把本规格复制进 spec 参数——复制即编造。") "禁止把本规格复制进 spec 参数——复制即编造。")
# 能力一致性告警(2026-09-06 wan3.0 教训):提取完当场校验描述模态声明 vs
# capability,矛盾信息直接回给助手(用户纠正时传 overrides.model_capability 重 apply)
cap_alerts = []
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 cap_alerts:
out["__capability_alert__"] = ("能力分类与文档描述矛盾(按示例误判的典型信号),"
"apply 时会自动纠正,也可用 overrides.model_capability "
"人工指定:" + ";".join(cap_alerts))
return json.dumps(out, ensure_ascii=False) return json.dumps(out, ensure_ascii=False)
@ -426,6 +470,52 @@ def _f(v, default=0.0):
return default return default
# 描述里的媒体输入声明词(能力一致性守卫用)
_DESC_MEDIA_WORDS = {
'image': ('图像', '图片', '多图'),
'video': ('视频',),
'audio': ('音频', '声音'),
}
_DESC_REF_WORDS = ('参考生', '全能参考', '参考图', '参考视频', '参考音频', '四模态', '多模态参考')
def _capability_guard(capability, description):
"""描述模态声明 vs capability 一致性守卫(2026-09-06 wan3.0-video-prime 实测:
描述自带"支持四模态全能参考"、capability 却是 t2v——提取 LLM 照调用示例判能力,
示例只演示最简形态就误判;提示词加铁律后仍需代码级兜底,LLM 不保证遵守)。
保守策略:只在「纯文本能力(t2v/t2i/t2t)+ 描述含媒体输入/参考声明」这种
铁证矛盾时纠正;其他不一致只警告不改(避免误伤描述措辞不规范的正确配置)。
返回 (corrected_cap 或 None, warning 或 None)。
"""
desc = description or ''
if not desc or capability not in ('t2v', 't2i', 't2t'):
return None, None
has_ref = any(w in desc for w in _DESC_REF_WORDS)
kinds = [k for k, words in _DESC_MEDIA_WORDS.items() if any(w in desc for w in words)]
# 「输出 视频」的"视频"不算输入声明——t2v 描述几乎都带"视频"二字。
# 输入声明判据:参考词,或图像/音频词(这两个不会出现在纯文本生成的描述里),
# 或"视频"以外多模态组合。
input_kinds = [k for k in kinds if k != 'video']
if has_ref or len(input_kinds) >= 1 or (has_ref is False and len(kinds) >= 2 and 'video' in kinds and input_kinds):
out_word = '视频' if capability == 't2v' else ('图像' if capability == 't2i' else '')
if capability == 't2t':
return None, ("描述含媒体输入声明(%s)但能力是 t2t——请人工核对能力分类"
% '/'.join(kinds))
expected = 'r2v' if (has_ref or len(kinds) >= 2) else (
'i2v' if 'image' in input_kinds and out_word == '视频' else
('i2i' if 'image' in input_kinds and out_word == '图像' else None))
if expected and expected != capability:
return expected, ("能力自动纠正 %s→%s:描述声明媒体输入(%s%s),"
"纯文本能力与之矛盾(提取照示例误判,守卫按正文模态纠正)"
% (capability, expected, '/'.join(kinds),
',含参考声明' if has_ref else ''))
return None, ("描述含媒体输入声明(%s)但能力是 %s——无法确定目标能力,"
"请人工核对(可用 overrides.model_capability 纠正)"
% ('/'.join(kinds), capability))
return None, None
async def _h_apply_llm_config(sor, params, ctx): async def _h_apply_llm_config(sor, params, ctx):
"""按提取的规格写库:供应商(复用/新建)+ 适配模板 + 模型。 """按提取的规格写库:供应商(复用/新建)+ 适配模板 + 模型。
@ -649,6 +739,15 @@ async def _h_apply_llm_config(sor, params, ctx):
cap = m.get("capability") or "t2t" cap = m.get("capability") or "t2t"
sync_mode = "async" if str(m.get("sync_mode") or "").strip() == "async" else "sync" sync_mode = "async" if str(m.get("sync_mode") or "").strip() == "async" else "sync"
desc = (m.get("description", "") or "").strip() desc = (m.get("description", "") or "").strip()
# 能力一致性守卫(2026-09-06 wan3.0 实测):描述模态声明 vs capability
# 铁证矛盾时代码级纠正——提取 LLM 照示例误判,提示词铁律不保证遵守
cap_fix, cap_warn = _capability_guard(cap, desc)
if cap_fix:
tpl_notes.append("%s: %s" % (vmid, cap_warn))
cap = cap_fix
m["capability"] = cap_fix
elif cap_warn:
tpl_notes.append("%s: %s" % (vmid, cap_warn))
# 出处标注(2026-09-05 用户规则):模型注册表描述字段必须保存文档 URL # 出处标注(2026-09-05 用户规则):模型注册表描述字段必须保存文档 URL
if doc_url and ("[出处:" + doc_url + "]") not in desc: if doc_url and ("[出处:" + doc_url + "]") not in desc:
desc = (desc + " " if desc else "") + "[出处:%s]" % doc_url desc = (desc + " " if desc else "") + "[出处:%s]" % doc_url
@ -1731,7 +1830,17 @@ async def _h_check_model_accounting(sor, params, ctx):
"模型未映射产品→产品管理导入产线模型;" "模型未映射产品→产品管理导入产线模型;"
"定价无匹配档位→核对usages维度值与定价YAML是否一致") "定价无匹配档位→核对usages维度值与定价YAML是否一致")
elif st == "created": elif st == "created":
item["verdict"] = "待记账(出账循环60秒一轮,稍后复查;持续不变查记账worker进程)" # m0011 设计:accounting_status 只对 status='ok' 行有意义——
# failed 调用行/recharge 充值行挂 created 是被出账循环天然排除(不计费),
# 不是「待记账」(2026-09-06 实测:助手把 21 条 failed 行误报成待记账)
call_st = row.get("status", "")
if call_st == "recharge":
item["verdict"] = "充值对账流水(非计费用量,不参与出账)"
elif call_st != "ok":
item["verdict"] = "调用失败行(不计费,出账循环天然排除)"
item["reason"] = (row.get("note") or "")[:200]
else:
item["verdict"] = "待记账(出账循环60秒一轮,稍后复查;持续不变查记账worker进程)"
elif st == "accounted": elif st == "accounted":
# 独立复算:用 usages 因子过定价引擎,对比 charge # 独立复算:用 usages 因子过定价引擎,对比 charge
item["verdict"] = "已记账" item["verdict"] = "已记账"
@ -1813,10 +1922,10 @@ PLATFORM_TOOLS = [
), ),
ToolDefinition( ToolDefinition(
name="apply_llm_config", name="apply_llm_config",
description="按提取的规格写入模型治理配置:供应商/端点/适配模板/模型。幂等。推荐 use_last_extract=true 取本会话锚定规格(禁止把规格复制进 spec——复制即编造);需改动加 overrides(仅 vendor_name/base_url/protocol/doc_url/doc_notes)。仅owner组织角色可用。", description="按提取的规格写入模型治理配置:供应商/端点/适配模板/模型。幂等。推荐 use_last_extract=true 取本会话锚定规格(禁止把规格复制进 spec——复制即编造);需改动加 overrides(vendor_name/base_url/protocol/doc_url/doc_notes/model_capability——用户纠正能力分类时传 model_capability 而非重提取)。仅owner组织角色可用。",
parameters={ parameters={
"use_last_extract": "true=用本会话 extract_llm_api_spec 锚定的规格(推荐,会话级隔离)", "use_last_extract": "true=用本会话 extract_llm_api_spec 锚定的规格(推荐,会话级隔离)",
"overrides": "覆盖项 JSON(白名单:vendor_name/base_url/protocol/doc_url/doc_notes,如用户要求改供应商名)", "overrides": "覆盖项 JSON(白名单:vendor_name/base_url/protocol/doc_url/doc_notes/model_capability。model_capability={模型ID:能力} 如 {\\\"wan3.0-video-prime\\\": \\\"r2v\\\"}——用户纠正能力分类时传它+use_last_extract 重新 apply,不要重提取;模板会按新能力重建)",
"spec": "(仅无锚定时的兜底)extract 返回的 JSON 规格原文", "spec": "(仅无锚定时的兜底)extract 返回的 JSON 规格原文",
}, },
category="platform", category="platform",
@ -1904,6 +2013,11 @@ PLATFORM_PROMPT = """
- **定价只允许来自文档原文**(doc_quote 强制),禁止编造/换算/推导 - **定价只允许来自文档原文**(doc_quote 强制),禁止编造/换算/推导
- **出处必留**:模型注册表描述字段与定价方案描述字段都必须保存文档 URL - **出处必留**:模型注册表描述字段与定价方案描述字段都必须保存文档 URL
- 能力分类不存在 → 先加分类(字典/种子/提示词/端点注释四处同步)再配模型,禁止塞近似分类 - 能力分类不存在 → 先加分类(字典/种子/提示词/端点注释四处同步)再配模型,禁止塞近似分类
- 能力分类铁律:按文档正文的输入/输出模态声明判定,禁止照调用示例判(示例常只演示
最简形态——wan3.0 实测「四模态全能参考」被示例判成 t2v)。extract 返回
__capability_alert__ 说明描述与能力矛盾,apply 会自动纠正;用户口头纠正能力时,
传 overrides={"model_capability": {"模型ID": "能力"}} + use_last_extract 重新 apply,
禁止重提取(同样的提示词只会同样误判)
- 生成类模型媒体铁律:上行统一三数组契约 image_files/audio_files/video_files - 生成类模型媒体铁律:上行统一三数组契约 image_files/audio_files/video_files
(字符串或数组均可)经 b64media2url 转公网 URL;下行产物用 (字符串或数组均可)经 b64media2url 转公网 URL;下行产物用
downloadfile2url(request, url) 落地;apply 返回的 media_audit 警告必须如实转告 downloadfile2url(request, url) 落地;apply 返回的 media_audit 警告必须如实转告