feat(platform): 媒体转换铁律落地——apply落库强制审计生成类模板(上行b64media2url/下行downloadfile2url,每个llm配置都检查);非标骨架模板自带转换约定占位

This commit is contained in:
yumoqing 2026-09-05 15:26:07 +08:00
parent 812f3fc6de
commit c74bf38506

View File

@ -448,13 +448,63 @@ async def _h_apply_llm_config(sor, params, ctx):
if protocol not in RUNTIME_PROTOCOLS:
rt_note = ("⚠️ 协议「%s」的适配模板已存档,但当前运行时调用链仅支持 %s——"
"该供应商模型暂不可被产线直接调用" % (protocol, "/".join(RUNTIME_PROTOCOLS)))
audit_note = await _audit_media_convention(sor, list(profile_ids.values()))
return json.dumps({
"vendor": vendor_action, "profiles": profile_ids,
"models_created": created, "models_updated": updated, "models_skipped": skipped,
"runtime_note": rt_note,
"media_audit": audit_note,
}, ensure_ascii=False)
# 生成类能力:出参是文件(图/视频/音频/3Dresponse 必须 downloadfile2url 落地
_MEDIA_CAPS = ('t2i', 'i2v', 't2v', 't2a', 'tts', 'i2i', 'v2v', '3d')
async def _audit_media_convention(sor, profile_ids):
"""强制检查2026-09-05 用户规则:每个 llm 配置都要检查媒体转换约定):
生成类能力的适配模板
request_template 含上传媒体参数时必须用 {{b64media2url(request, xxx_file)}}
转本地公网 URL 再传上游
response_template 必须用 {{downloadfile2url(request, <产物url>)}}
把生成物落地为本地持久 URL上游 URL 有效期短视频仅 24 小时
返回检查结论无问题为空串 apply 结果返回给内部 agent 转告
"""
if not profile_ids:
return ""
issues = []
for pid in profile_ids:
recs = await sor.sqlExe(
"SELECT name, capability, request_template, response_template "
"FROM llm_api_profile WHERE id=${i}$", {"i": pid})
await sor.sqlExe("COMMIT", {})
if not recs:
continue
r = recs[0]
cap = getattr(r, 'capability', '') or ''
name = getattr(r, 'name', '') or pid
if cap not in _MEDIA_CAPS:
continue # 非生成类t2t/embedding/rerank无生成物
rt = getattr(r, 'request_template', '') or ''
st = getattr(r, 'response_template', '') or ''
if '__note__' in st and 'downloadfile2url' not in st:
issues.append("%s」response 模板是骨架——生成物必须用 "
"downloadfile2url(request, <产物url>) 落地后再返回" % name)
elif st and 'downloadfile2url' not in st:
issues.append("%s」response 模板缺 downloadfile2url——生成类能力"
"%s)的产物 URL 必须落地为本地持久 URL" % (name, cap))
# 上传参数线索:模板里出现 image/video/audio 文件参数但没有 b64media2url
has_upload_ref = any(k in rt for k in ('image_file', 'video_file', 'audio_file',
'first_frame', 'image_url', 'media'))
if has_upload_ref and 'b64media2url' not in rt:
issues.append("%s」request 模板含媒体上传参数但缺 b64media2url——"
"上传文件必须转本地公网 URL 再传上游" % name)
if issues:
return "⚠️ 媒体转换约定检查未通过:" + "".join(issues)
return ""
def _default_templates(protocol: str, capability: str, spec: dict) -> dict:
"""生成适配模板默认值path/headers 独立列 + data/response 模板)。
@ -475,15 +525,30 @@ def _default_templates(protocol: str, capability: str, spec: dict) -> dict:
"completion_tokens": "usage.completion_tokens"},
}, ensure_ascii=False)
return {"path": chat_path, "headers": headers, "req": req, "resp": resp}
# 非标准协议:骨架模板(字段路径来自文档提取),标注需人工核对
# 非标准协议:骨架模板(字段路径来自文档提取),标注需人工核对。
# 生成类能力_MEDIA_CAPS骨架直接带媒体转换约定占位——按用户铁律
# 上行 b64media2url上传转公网URL、下行 downloadfile2url生成物落地
req = json.dumps({
"data": {"__from_doc__": spec.get("request_fields") or []},
"__note__": "非 openai_compat 协议骨架模板,需按文档人工核对",
}, ensure_ascii=False)
resp = json.dumps({
"content": spec.get("response_format", "") or "待按文档填写",
"__note__": "需人工核对",
}, ensure_ascii=False)
resp_body = {"content": spec.get("response_format", "") or "待按文档填写",
"__note__": "需人工核对"}
if capability in _MEDIA_CAPS:
# 生成类:骨架给出落地写法提示,人工只需把 <产物url字段> 换成真实路径
resp_body = {
"status": "SUCCEEDED",
"video": "{{ downloadfile2url(request, output.video_url) }}",
"__note__": "生成类能力:产物 URL 必须用 downloadfile2url 落地为本地"
"持久 URL上游仅 24 小时有效);上传媒体在 request 模板用 "
"b64media2url(request, xxx_file);产物字段名按文档核对",
}
req = json.dumps({
"data": {"__from_doc__": spec.get("request_fields") or [],
"__upload__": "{{ b64media2url(request, params.xxx_file) }}"},
"__note__": "上传媒体一律 xxx_file 命名 + b64media2url 转公网 URL",
}, ensure_ascii=False)
resp = json.dumps(resp_body, ensure_ascii=False)
return {"path": chat_path, "headers": headers, "req": req, "resp": resp}