fix(platform): 端到端实测揪出3个bug——①模型更新分支不刷新profile_id(旧模板被弃用重建后模型仍指deprecated→运行时崩)②legacy查询模板复用未做骨架自愈③查询path归一化(单花括号{task_id}对齐运行时str.replace契约+剥base_url重复前缀+缺占位符补/tasks/{task_id})
This commit is contained in:
parent
e9d0bfc62d
commit
69884fd2ec
Binary file not shown.
@ -621,11 +621,16 @@ async def _h_apply_llm_config(sor, params, ctx):
|
|||||||
"%s: 模型已存在且挂供应商 id=%s,与本次规格供应商(%s)不一致——"
|
"%s: 模型已存在且挂供应商 id=%s,与本次规格供应商(%s)不一致——"
|
||||||
"未改供应商归属,仅更新描述/同步模式;如需迁移请明确指示"
|
"未改供应商归属,仅更新描述/同步模式;如需迁移请明确指示"
|
||||||
% (vmid, cur_vendor, vendor_name))
|
% (vmid, cur_vendor, vendor_name))
|
||||||
|
# profile_id 必须一并刷新(2026-09-05 实测 bug):模型原挂模板可能已被
|
||||||
|
# 弃用重建(旧骨架自愈),不刷新则运行时仍指 deprecated 模板→必崩
|
||||||
|
new_pid = profile_ids.get(cap, "")
|
||||||
await sor.sqlExe(
|
await sor.sqlExe(
|
||||||
"UPDATE llm_model SET description=${d}$, "
|
"UPDATE llm_model SET description=${d}$, "
|
||||||
"sync_mode=${sm}$, query_profile_ids=${q}$, updated_at=NOW() "
|
"sync_mode=${sm}$, query_profile_ids=${q}$, "
|
||||||
|
"profile_id=IF(${p}$='', profile_id, ${p}$), updated_at=NOW() "
|
||||||
"WHERE id=${i}$",
|
"WHERE id=${i}$",
|
||||||
{"d": desc, "sm": sync_mode, "q": query_ids_json, "i": mid})
|
{"d": desc, "sm": sync_mode, "q": query_ids_json,
|
||||||
|
"p": new_pid, "i": mid})
|
||||||
await sor.sqlExe("COMMIT", {})
|
await sor.sqlExe("COMMIT", {})
|
||||||
updated.append(vmid)
|
updated.append(vmid)
|
||||||
continue
|
continue
|
||||||
@ -989,42 +994,56 @@ async def _ensure_step_profile(sor, protocol, cap, vmid, step, spec):
|
|||||||
"""异步后续步骤的适配模板(query/download)。
|
"""异步后续步骤的适配模板(query/download)。
|
||||||
|
|
||||||
2026-09-05 改造(用户:同类任务查询接口供应商级相同,可复用):
|
2026-09-05 改造(用户:同类任务查询接口供应商级相同,可复用):
|
||||||
幂等键=名称「{protocol}-query-{path}」(去掉模型维度)——同供应商同协议
|
幂等键=名称「{protocol}-{purpose}-{path}」(去掉模型维度)——同供应商同协议
|
||||||
下多个模型共享同一份查询模板;旧的按 {vmid}-{purpose} 命名继续兼容复用。
|
下多个模型共享同一份查询模板;旧的 {vmid}-{purpose} 命名兼容复用,
|
||||||
模板按文档真实生成:运行时查询只用 path/headers/method,task_id 经
|
但若旧模板仍是骨架(__from_doc__)同样弃用重建。
|
||||||
path 占位(如 /tasks/{{task_id}});不再产 __from_doc__ 骨架。
|
path 归一化(对齐运行时契约,实测踩过三个坑):
|
||||||
|
- 任务号占位符用**单花括号** {task_id}(运行时 str.replace 消费,
|
||||||
|
Jinja 双花括号不会被替换);文档给 {{task_id}} 也归一为单花括号
|
||||||
|
- 剥掉 base_url 已有的路径前缀(提取给 /api/v1/tasks/{task_id},
|
||||||
|
运行时 url=base_url+path,base_url 已含 /api/v1 → 不归一会双前缀 404)
|
||||||
|
- 缺占位符时按 DashScope 惯例补 /tasks/{task_id}
|
||||||
返回 profile id。
|
返回 profile id。
|
||||||
"""
|
"""
|
||||||
purpose = (step.get("purpose") or "query").strip() or "query"
|
purpose = (step.get("purpose") or "query").strip() or "query"
|
||||||
path = (step.get("path") or "").strip()
|
path = (step.get("path") or "").strip()
|
||||||
method = (step.get("method") or ("GET" if purpose == "query" else "POST")).strip().upper()
|
method = (step.get("method") or ("GET" if purpose == "query" else "POST")).strip().upper()
|
||||||
|
# path 归一化
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
bu_path = urlparse((spec.get("base_url") or "").strip()).path.rstrip("/")
|
||||||
|
if bu_path and path.startswith(bu_path + "/"):
|
||||||
|
path = path[len(bu_path):]
|
||||||
|
path = path.replace("{{task_id}}", "{task_id}")
|
||||||
|
if "{task_id}" not in path and purpose == "query":
|
||||||
|
path = (path.rstrip("/") + "/{task_id}") if path else "/tasks/{task_id}"
|
||||||
shared_name = "%s-%s-%s" % (protocol, purpose, path or "default")
|
shared_name = "%s-%s-%s" % (protocol, purpose, path or "default")
|
||||||
legacy_name = "%s-%s" % (vmid, purpose)
|
legacy_name = "%s-%s" % (vmid, purpose)
|
||||||
recs = await sor.sqlExe(
|
for cand in (shared_name, legacy_name):
|
||||||
"SELECT id FROM llm_api_profile WHERE name=${n}$ AND status='active' LIMIT 1",
|
recs = await sor.sqlExe(
|
||||||
{"n": shared_name})
|
"SELECT id, request_template, response_template FROM llm_api_profile "
|
||||||
await sor.sqlExe("COMMIT", {})
|
"WHERE name=${n}$ AND status='active' LIMIT 1", {"n": cand})
|
||||||
if recs:
|
await sor.sqlExe("COMMIT", {})
|
||||||
return getattr(recs[0], "id", "")
|
if not recs:
|
||||||
recs = await sor.sqlExe(
|
continue
|
||||||
"SELECT id FROM llm_api_profile WHERE name=${n}$ AND status='active' LIMIT 1",
|
old_tpl = (getattr(recs[0], "request_template", "") or "") + \
|
||||||
{"n": legacy_name})
|
(getattr(recs[0], "response_template", "") or "")
|
||||||
await sor.sqlExe("COMMIT", {})
|
if not any(mk in old_tpl for mk in _SKELETON_MARKERS):
|
||||||
if recs:
|
return getattr(recs[0], "id", "")
|
||||||
return getattr(recs[0], "id", "")
|
pid_old = getattr(recs[0], "id", "")
|
||||||
|
await sor.sqlExe("UPDATE llm_api_profile SET status='deprecated' "
|
||||||
|
"WHERE id=${i}$", {"i": pid_old})
|
||||||
|
await sor.sqlExe("COMMIT", {})
|
||||||
|
# 旧骨架弃用——继续往下重建(不 return)
|
||||||
headers = _headers_from_spec(spec)
|
headers = _headers_from_spec(spec)
|
||||||
if method == "GET":
|
if method == "GET":
|
||||||
req = json.dumps({"method": method}, ensure_ascii=False)
|
req = json.dumps({"method": method}, ensure_ascii=False)
|
||||||
if "{{task_id}}" not in path:
|
|
||||||
# path 没带任务号占位时按 DashScope 惯例拼 /tasks/{id};notes 如实标注
|
|
||||||
path = (path.rstrip("/") + "/{{task_id}}") if path else "/tasks/{{task_id}}"
|
|
||||||
else:
|
else:
|
||||||
req = json.dumps({"method": method,
|
req = json.dumps({"method": method,
|
||||||
"data": {"task_id": "{{task_id}}"}}, ensure_ascii=False)
|
"data": {"task_id": "{task_id}"}}, ensure_ascii=False)
|
||||||
# 查询步骤的响应渲染在运行时由提交模板的 response_template 接管
|
# 查询步骤的响应解析在运行时由提交模板的 response_template 接管
|
||||||
# (inference 轮询直接调 _render_response(submit_profile,...)),这里存档即可
|
# (inference._async_inference 轮询后直接用提交模板渲染),这里存档即可
|
||||||
resp = json.dumps({
|
resp = json.dumps({
|
||||||
"__note__": "查询步骤模板:运行时响应解析走模型提交模板的 response_template",
|
"note": "查询步骤模板:运行时响应解析走模型提交模板的 response_template",
|
||||||
"response_format": step.get("response_format", "") or "",
|
"response_format": step.get("response_format", "") or "",
|
||||||
}, ensure_ascii=False)
|
}, ensure_ascii=False)
|
||||||
pid = getID()
|
pid = getID()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user