feat(inference): 媒体文件转换双函数接入模板命名空间(对齐uapi铁律)——data模板b64media2url上传转公网URL/response模板downloadfile2url生成物落地;同类接口参数统一xxx_file契约;异步结果经response模板渲染出media字段
This commit is contained in:
parent
7576ffd6f8
commit
8da377fc81
@ -64,6 +64,45 @@ def _fnum(v):
|
|||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def _media_ns_extra():
|
||||||
|
"""模板命名空间扩展:媒体文件转换函数(对齐 uapi 约定)+ request 占位。
|
||||||
|
|
||||||
|
生成类模型的两条铁律(2026-09-05 用户定夺):
|
||||||
|
上行(data 模板):上传/本地媒体用 {{b64media2url(request, xxx_file)}}
|
||||||
|
转成本地公网 URL 再传上游(base64 落盘→/idfile 公网地址);
|
||||||
|
下行(response 模板):上游生成物 URL 用
|
||||||
|
{{downloadfile2url(request, output.video_url)}} 落地为本地持久
|
||||||
|
URL 再给调用者(上游 URL 有效期短,DashScope 视频仅 24 小时)。
|
||||||
|
request=None:内部执行器无 HTTP 请求上下文,函数走 BASEURL 环境变量
|
||||||
|
兜底拼公网地址(部署机须配置)。导入失败退回透传函数(不阻断调用)。
|
||||||
|
"""
|
||||||
|
ns = {'request': None, 'json': json}
|
||||||
|
try:
|
||||||
|
from ahserver.filestorage import downloadfile2url, b64media2url
|
||||||
|
ns['downloadfile2url'] = downloadfile2url
|
||||||
|
ns['b64media2url'] = b64media2url
|
||||||
|
except Exception:
|
||||||
|
async def _d2u(request, url, **kw):
|
||||||
|
return url
|
||||||
|
async def _b2u(request, media):
|
||||||
|
return media
|
||||||
|
ns['downloadfile2url'] = _d2u
|
||||||
|
ns['b64media2url'] = _b2u
|
||||||
|
return ns
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_media_aliases(upstream):
|
||||||
|
"""同类接口对外契约统一(2026-09-05 用户规则):上传媒体一律 xxx_file 命名。
|
||||||
|
|
||||||
|
旧别名 xxx_url 归一到 xxx_file——同能力模板写法一致(如 i2v 统一
|
||||||
|
image_file 进 → video 出),模板不用为同一类能力写多套参数名。
|
||||||
|
"""
|
||||||
|
for media in ('image', 'video', 'audio'):
|
||||||
|
fk, uk = media + '_file', media + '_url'
|
||||||
|
if not upstream.get(fk) and upstream.get(uk):
|
||||||
|
upstream[fk] = upstream.get(uk)
|
||||||
|
|
||||||
|
|
||||||
def _texts_len(messages):
|
def _texts_len(messages):
|
||||||
"""messages 内容总长(估预授权用量用)。"""
|
"""messages 内容总长(估预授权用量用)。"""
|
||||||
try:
|
try:
|
||||||
@ -283,6 +322,7 @@ async def _build_upstream_body(ctx, payload):
|
|||||||
'api_key': ctx.get('api_key') or '',
|
'api_key': ctx.get('api_key') or '',
|
||||||
'org_id': ctx.get('org_id') or '',
|
'org_id': ctx.get('org_id') or '',
|
||||||
}
|
}
|
||||||
|
ns.update(_media_ns_extra())
|
||||||
s = await _render_tmpl(tmpl, ns)
|
s = await _render_tmpl(tmpl, ns)
|
||||||
try:
|
try:
|
||||||
body = json.loads(s)
|
body = json.loads(s)
|
||||||
@ -406,15 +446,17 @@ async def _build_async_body(ctx, payload):
|
|||||||
upstream['model'] = ctx.get('model_id') or upstream.get('model') or ''
|
upstream['model'] = ctx.get('model_id') or upstream.get('model') or ''
|
||||||
for k, v in (ctx.get('default_params') or {}).items():
|
for k, v in (ctx.get('default_params') or {}).items():
|
||||||
upstream.setdefault(k, v)
|
upstream.setdefault(k, v)
|
||||||
|
_normalize_media_aliases(upstream) # 上传媒体统一 xxx_file 契约
|
||||||
messages = upstream.pop('messages', [])
|
messages = upstream.pop('messages', [])
|
||||||
model = upstream.pop('model', '')
|
model = upstream.pop('model', '')
|
||||||
ns = {
|
ns = {
|
||||||
'model': model, 'messages': messages,
|
'model': model, 'messages': messages,
|
||||||
'prompt': _last_prompt_text(messages),
|
'prompt': _last_prompt_text(messages),
|
||||||
'params': upstream, # 业务参数(resolution/duration/image_url 等)
|
'params': upstream, # 业务参数(resolution/duration/image_file 等)
|
||||||
'api_key': ctx.get('api_key') or '',
|
'api_key': ctx.get('api_key') or '',
|
||||||
'org_id': ctx.get('org_id') or '',
|
'org_id': ctx.get('org_id') or '',
|
||||||
}
|
}
|
||||||
|
ns.update(_media_ns_extra())
|
||||||
s = await _render_tmpl(tmpl, ns)
|
s = await _render_tmpl(tmpl, ns)
|
||||||
try:
|
try:
|
||||||
body = json.loads(s)
|
body = json.loads(s)
|
||||||
@ -545,9 +587,30 @@ async def _async_inference(ctx, payload, req_timeout):
|
|||||||
|
|
||||||
# ── 3. 组装结果 + 用量因子记账 ──
|
# ── 3. 组装结果 + 用量因子记账 ──
|
||||||
out = last.get('output') or {}
|
out = last.get('output') or {}
|
||||||
content = (out.get('video_url') or out.get('image_url') or out.get('result_url')
|
|
||||||
or out.get('text') or '')
|
|
||||||
usage = last.get('usage') or {}
|
usage = last.get('usage') or {}
|
||||||
|
# response 模板渲染(对齐 uapi):生成物 URL 必须经 downloadfile2url
|
||||||
|
# 落地为本地持久 URL 再给调用者(上游 URL 有效期短,视频仅 24 小时)。
|
||||||
|
# 同类能力出参字段统一(i2v/t2v → video,t2i → image)。
|
||||||
|
rendered = {}
|
||||||
|
resp_tmpl = (profile.get('response_template') or '').strip()
|
||||||
|
if resp_tmpl and '__note__' not in resp_tmpl:
|
||||||
|
ns = dict(out)
|
||||||
|
ns['output'] = out
|
||||||
|
ns['usage'] = usage
|
||||||
|
ns['task_id'] = task_id
|
||||||
|
ns.update(_media_ns_extra())
|
||||||
|
try:
|
||||||
|
s = await _render_tmpl(resp_tmpl, ns)
|
||||||
|
parsed = json.loads(s)
|
||||||
|
if isinstance(parsed, dict):
|
||||||
|
rendered = parsed
|
||||||
|
except Exception as e:
|
||||||
|
print("[inference] response模板渲染失败(回退原始URL,生成物未落地): %s"
|
||||||
|
% str(e)[:150])
|
||||||
|
content = (rendered.get('video') or rendered.get('image')
|
||||||
|
or rendered.get('glb') or rendered.get('3dmodel')
|
||||||
|
or out.get('video_url') or out.get('image_url')
|
||||||
|
or out.get('result_url') or out.get('text') or '')
|
||||||
usages = dict(usage) if isinstance(usage, dict) else {}
|
usages = dict(usage) if isinstance(usage, dict) else {}
|
||||||
usages['model'] = ctx.get('model_id') or ''
|
usages['model'] = ctx.get('model_id') or ''
|
||||||
# 计价过滤维度用请求值(与文档计价档位原文一致,如 resolution=720P);
|
# 计价过滤维度用请求值(与文档计价档位原文一致,如 resolution=720P);
|
||||||
@ -556,13 +619,16 @@ async def _async_inference(ctx, payload, req_timeout):
|
|||||||
if payload.get(k) not in (None, ''):
|
if payload.get(k) not in (None, ''):
|
||||||
usages.setdefault(k, payload.get(k))
|
usages.setdefault(k, payload.get(k))
|
||||||
await govern_settle(ctx, True, 0, 0, '' if usage else 'no-usage', usages)
|
await govern_settle(ctx, True, 0, 0, '' if usage else 'no-usage', usages)
|
||||||
return {
|
result = {
|
||||||
"choices": [{"message": {"content": content, "role": "assistant"},
|
"choices": [{"message": {"content": content, "role": "assistant"},
|
||||||
"finish_reason": "stop"}],
|
"finish_reason": "stop"}],
|
||||||
"usage": usage,
|
"usage": usage,
|
||||||
"output": out,
|
"output": out,
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
}
|
}
|
||||||
|
if rendered:
|
||||||
|
result["media"] = rendered # 统一出参:本地持久 URL(video/image/...)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# ────────────────────────── 对外主入口 ──────────────────────────
|
# ────────────────────────── 对外主入口 ──────────────────────────
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user