fix(platform): media键条件渲染下沉到真实示例路径(_tpl_from_example)——四种键位+干跑覆盖

上一提交只修了合成兜底路径;用权威文档示例重新apply会走真实示例路径,
该路径零媒体同样发出"media": []空数组。本次统一下沉:

- 新增 _media_any_expr(mps):「任一媒体有值」Jinja布尔(字符串/数组归一),
  合成/真实两路径共用
- _tpl_from_example: media数组token记录(键名,mps),替换阶段按json.dumps
  逗号位置分三模式(首/中键带尾逗号,末键吃前逗号,独键直包)整键条件化
- 单测22项全绿:四种键位×(零媒体省略/三模态/字符串形态/干跑)+合成路径
  +schema(媒体非必填/prompt必填/multiple/ratio六值枚举)
This commit is contained in:
yumoqing 2026-09-06 20:56:49 +08:00
parent 89acb08ea9
commit 03d1e13ebd

View File

@ -1032,6 +1032,20 @@ def _is_media_array(lst, capability):
return True
def _media_any_expr(mps):
"""「任一媒体参数有值」Jinja 布尔表达式(字符串/数组两形态归一)。
2026-09-06 用户纠正后引入全能参考模型wan3.0 r2v文档明示
prompt media 必填其一文生视频不传入任何媒体文件零媒体是
合法 t2v 形态且文档示例**不带 media **无条件渲染会发出 "media": []
空数组API 未承诺接受不能赌 media 键必须条件渲染有媒体才
输出键零媒体整个省略真实示例路径与合成兜底路径共用本表达式
"""
return ' or '.join(
'(params.%s if params.%s is not string and params.%s else '
'([params.%s] if params.%s is string else []))' % ((mp,) * 5) for mp in mps)
def _media_array_expr(elements):
"""媒体数组示例 → Jinja 动态组装表达式(三数组契约 image_files/
audio_files/video_files值为字符串或数组均可
@ -1094,6 +1108,7 @@ def _tpl_from_example(example, capability):
返回 (模板串, media_params, biz_params, used_example)
"""
tokens = {}
cond_tokens = {}
media_params, biz_params = [], []
used_example = isinstance(example, dict) and bool(example)
tree = example if used_example else _example_to_nested(None)
@ -1111,7 +1126,14 @@ def _tpl_from_example(example, capability):
for mp in mps:
if mp not in media_params:
media_params.append(mp)
return _put(expr)
tk = _put(expr)
# media 键条件渲染2026-09-06 用户纠正):零媒体=合法 t2v
# 形态且文档示例不带 media 键,无条件渲染发出 "media": []
# 空数组API 未承诺接受,不能赌)。记录 (token→键名/媒体参数)
# 供替换阶段整键包 Jinja 条件。仅 dict 下的数组键可条件化。
if parent_key:
cond_tokens[tk] = (parent_key, mps)
return tk
return [walk(v, parent_key, sib_type) for v in node]
if not isinstance(node, dict):
return node
@ -1150,6 +1172,25 @@ def _tpl_from_example(example, capability):
body = walk(tree)
s = json.dumps(body, ensure_ascii=False)
for k, expr in tokens.items():
if k in cond_tokens:
# media 键条件渲染:零媒体时整键省略(对齐文档 t2v 形态)。
# json.dumps 分隔符固定 ", ",按键在对象中的位置分三模式吃逗号:
# 首/中键(后随 ", ")→ 条件段自带尾逗号
# 末键(前缀 ", " → 条件段吃掉前逗号
# 独键 → 直接包
keyname, mps = cond_tokens[k]
anyexpr = _media_any_expr(mps)
seg_body = '"%s": %s' % (keyname, expr)
pat_first = '"%s": "%s", ' % (keyname, k)
pat_last = ', "%s": "%s"' % (keyname, k)
pat_only = '"%s": "%s"' % (keyname, k)
if pat_first in s:
s = s.replace(pat_first, ' {%% if %s %%}%s, {%% endif %%}' % (anyexpr, seg_body))
elif pat_last in s:
s = s.replace(pat_last, '{%% if %s %%}, %s{%% endif %%}' % (anyexpr, seg_body))
elif pat_only in s:
s = s.replace(pat_only, ' {%% if %s %%}%s{%% endif %%}' % (anyexpr, seg_body))
continue
s = s.replace('"%s"' % k, expr)
return s, media_params, biz_params, used_example
@ -1277,11 +1318,8 @@ def _gen_templates(protocol: str, capability: str, spec: dict) -> dict:
# 文件」——零媒体是合法 t2v 形态,且文档 t2v 示例**不带 media
# 键**;无条件渲染会发出 "media": [] 空数组API 未承诺接受,
# 不能赌)。有媒体才渲染 media 键,零媒体整个省略。
anyexpr = ' or '.join(
'(params.%s if params.%s is not string and params.%s else '
'([params.%s] if params.%s is string else []))'
% ((mp,) * 5) for mp in mps)
# 空格不可省media_seg 以 {% 开头,紧跟 input 的 { 会拼成
anyexpr = _media_any_expr(mps)
# 前导空格不可省:段以 {% 开头,紧跟 input 的 { 会拼成
# "{{%" —— Jinja 词法把 {{ 当变量表达式开始 → unexpected '%'
# 2026-09-06 单测实抓。JSON 空白无副作用。
media_seg = (' {%% if %s %%}"media": %s, {%% endif %%}'