fix(platform): 模板复用门禁加param_schema体检自愈——存量脏schema(未注册uitype/default/重复字段/结构常量)判死弃用重建,soft级(缺multiple)仅提示;防代码修好前端仍坏表单
This commit is contained in:
parent
d6cfaf6014
commit
9cc92dc84a
@ -582,7 +582,8 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
# 名称过滤必须加(2026-09-05 e2e 实测 bug):查询/下载步骤模板同库同协议
|
||||
# 同能力,不加过滤会被「最新优先」误选为提交模板 → 提交打到查询 path → 404
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, request_template, response_template FROM llm_api_profile "
|
||||
"SELECT id, request_template, response_template, param_schema "
|
||||
"FROM llm_api_profile "
|
||||
"WHERE protocol=${p}$ AND capability=${c}$ AND status='active' "
|
||||
"AND name NOT LIKE ${l1}$ AND name NOT LIKE ${l2}$ "
|
||||
"ORDER BY created_at DESC LIMIT 1",
|
||||
@ -593,14 +594,23 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
pid_old = getattr(recs[0], "id", "")
|
||||
old_tpl = (getattr(recs[0], "request_template", "") or "") + \
|
||||
(getattr(recs[0], "response_template", "") or "")
|
||||
if not any(mk in old_tpl for mk in _SKELETON_MARKERS):
|
||||
# schema 体检:存量脏 schema(未注册 uitype/default/重复字段/结构常量)判死重建,
|
||||
# 否则代码修了、用户前端还是坏表单(自愈门禁,见 _schema_violations)
|
||||
sv = _schema_violations(getattr(recs[0], "param_schema", ""))
|
||||
for s in sv['soft']:
|
||||
tpl_notes.append("模板 %s(%s)schema 提示:%s" % (pid_old, cap, s))
|
||||
if not sv['hard'] and not any(mk in old_tpl for mk in _SKELETON_MARKERS):
|
||||
profile_ids[cap] = pid_old
|
||||
continue
|
||||
await sor.sqlExe("UPDATE llm_api_profile SET status='deprecated' "
|
||||
"WHERE id=${i}$", {"i": pid_old})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
tpl_notes.append("旧骨架模板 %s(%s)含硬编码占位符,已弃用并按文档示例重建"
|
||||
% (pid_old, cap))
|
||||
if sv['hard']:
|
||||
tpl_notes.append("存量模板 %s(%s)param_schema 不合规,已弃用重建:%s"
|
||||
% (pid_old, cap, ";".join(sv['hard'])))
|
||||
else:
|
||||
tpl_notes.append("旧骨架模板 %s(%s)含硬编码占位符,已弃用并按文档示例重建"
|
||||
% (pid_old, cap))
|
||||
tpl = _gen_templates(protocol, cap, spec)
|
||||
dry_err = _dry_render_check(tpl["req"], cap, tpl["biz_params"], tpl["media_params"])
|
||||
if dry_err:
|
||||
@ -1114,6 +1124,60 @@ _SKELETON_MARKERS = ('__from_doc__', '__note__', 'xxx_file',
|
||||
'params.image_file)', 'params.video_file)', 'params.audio_file)')
|
||||
|
||||
|
||||
def _schema_violations(raw):
|
||||
"""存量 param_schema 合规体检(2026-09-06 用户指正后补的自愈门禁)。
|
||||
|
||||
为什么必须有它:apply 的模板复用门禁原先只看 request_template 里的骨架标记,
|
||||
完全不看 param_schema —— 于是「模板正常但 schema 是 textarea/number/default」的
|
||||
存量 profile 会被永久复用(实测 happyhorse r2v/t2v/i2v 三个 active profile 全中招,
|
||||
r2v 还带 3 个重复 type 字段)。代码修好了,用户在前端看到的仍是坏表单:
|
||||
Input.create 遇未注册 uitype 返回 null 只打一行 debug(input.js:1294),字段静默消失。
|
||||
所以复用前必须体检 schema,违规一律弃用重建,让存量数据跟着代码自愈。
|
||||
|
||||
分级(避免误杀仿权威样例的 profile):
|
||||
hard = 会导致字段消失/撞名覆盖/默认值不生效 → 判死重建
|
||||
(未注册 uitype、用 default、同名字段重复、结构常量入表单)
|
||||
soft = 功能受限但不崩 → 只提示,不判死。典型:媒体数组缺 multiple:true
|
||||
(input.js:426 决定值是数组还是 files[0];权威样例 minimax_h3_setup.sql:13
|
||||
的 image_files 就没带 multiple,模板靠 for 循环兼容 str,故不能判死)
|
||||
返回 {'hard': [...], 'soft': [...]}。
|
||||
"""
|
||||
out = {'hard': [], 'soft': []}
|
||||
raw = (raw or "").strip()
|
||||
if not raw:
|
||||
return out
|
||||
try:
|
||||
schema = json.loads(raw)
|
||||
except Exception as exc:
|
||||
out['hard'].append("param_schema 不是合法 JSON(%s)" % exc)
|
||||
return out
|
||||
if not isinstance(schema, list):
|
||||
out['hard'].append("param_schema 顶层应为数组")
|
||||
return out
|
||||
seen = set()
|
||||
for f in schema:
|
||||
if not isinstance(f, dict):
|
||||
out['hard'].append("schema 元素非对象: %r" % (f,))
|
||||
continue
|
||||
nm = f.get("name")
|
||||
ut = f.get("uitype")
|
||||
if ut not in _BRICKS_UITYPES:
|
||||
out['hard'].append("字段 %s 的 uitype %r 未注册" % (nm, ut))
|
||||
if "default" in f:
|
||||
out['hard'].append("字段 %s 用了 default(应为 defaultvalue)" % nm)
|
||||
# 同名字段重复:前端 dom_element.id 撞名,后者覆盖前者
|
||||
if nm in seen:
|
||||
out['hard'].append("字段 %s 重复出现" % nm)
|
||||
seen.add(nm)
|
||||
# 结构常量不该出现在表单(值已固化在模板里)
|
||||
if nm in _STRUCT_CONST_FIELDS:
|
||||
out['hard'].append("结构常量字段 %s 不该让用户填" % nm)
|
||||
# 媒体数组参数缺 multiple:UI 只能选 1 个(soft,权威样例也这样)
|
||||
if nm in _MEDIA_UITYPE and not f.get("multiple"):
|
||||
out['soft'].append("媒体数组字段 %s 缺 multiple:true(UI 只能选 1 个)" % nm)
|
||||
return out
|
||||
|
||||
|
||||
def _biz_field_uitype(name, example, enums):
|
||||
"""业务参数 → bricks uitype(2026-09-06 用户指正:只准用注册过的类型)。
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user