fix(platform): 端点协议感知治理(404根因)——端点落库带protocol标签;裸域退化拦截(同host已有带路径端点则跳过);账号端点绑定自检:活跃账号未绑本协议端点自动补绑并报account_notes
This commit is contained in:
parent
ac52a61e0b
commit
d6228c59c6
@ -499,7 +499,13 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
|
||||
from appPublic.uniqueID import getID
|
||||
|
||||
# 1. 供应商:按名称复用
|
||||
# 1. 供应商:按名称复用。端点带协议标签(2026-09-05 404 实测教训):
|
||||
# 同一供应商可同时有 openai_compat(compatible-mode/v1) 与原生异步(api/v1)
|
||||
# 两类端点,账号绑定/运行时选择必须按协议匹配,否则原生模型被拼到
|
||||
# compatible-mode 前缀下 → 404。另拦截「裸域退化」:提取 LLM 偶发把
|
||||
# base_url 提成 https://host(丢了 /api/v1),与已有带路径端点同 host
|
||||
# 时该裸域无意义,跳过不入目录。
|
||||
from urllib.parse import urlparse as _urlparse
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, endpoints FROM llm_vendor WHERE name=${n}$", {"n": vendor_name})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
@ -512,29 +518,55 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
eps_old = []
|
||||
merged = list(eps_old)
|
||||
added_eps = []
|
||||
skipped_eps = []
|
||||
for ep in endpoints:
|
||||
bu = (ep.get("base_url") or "").rstrip("/")
|
||||
if bu and bu not in [(e.get("base_url") or "").rstrip("/") for e in merged]:
|
||||
merged.append({"base_url": bu, "region": ep.get("region") or "domestic",
|
||||
"timeout": int(_f(ep.get("timeout"), 60))})
|
||||
added_eps.append(bu)
|
||||
# 2026-09-05:供应商表不再有 protocol——请求形态由模型挂的适配模板决定
|
||||
if not bu:
|
||||
continue
|
||||
# 同 base_url 已存在:只补协议标签(旧端点无 protocol 时回填)
|
||||
hit = None
|
||||
for e in merged:
|
||||
if (e.get("base_url") or "").rstrip("/") == bu:
|
||||
hit = e
|
||||
break
|
||||
if hit is not None:
|
||||
if protocol and not hit.get("protocol"):
|
||||
hit["protocol"] = protocol
|
||||
continue
|
||||
# 裸域退化拦截:同 host 已有带路径端点 → 裸域跳过
|
||||
pu = _urlparse(bu)
|
||||
if not pu.path.strip("/"):
|
||||
same_host = any(_urlparse((e.get("base_url") or "")).netloc == pu.netloc
|
||||
for e in merged)
|
||||
if same_host:
|
||||
skipped_eps.append("%s(裸域退化:同 host 已有带路径端点)" % bu)
|
||||
continue
|
||||
merged.append({"base_url": bu, "region": ep.get("region") or "domestic",
|
||||
"timeout": int(_f(ep.get("timeout"), 60)),
|
||||
"protocol": protocol})
|
||||
added_eps.append(bu)
|
||||
await sor.sqlExe(
|
||||
"UPDATE llm_vendor SET endpoints=${e}$, updated_at=NOW() WHERE id=${i}$",
|
||||
{"e": json.dumps(merged, ensure_ascii=False), "i": vendor_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
vendor_action = "复用供应商 %s(新增端点 %d 个)" % (vendor_id, len(added_eps))
|
||||
vendor_action = "复用供应商 %s(新增端点 %d 个%s)" % (
|
||||
vendor_id, len(added_eps),
|
||||
(",跳过 %s" % ";".join(skipped_eps)) if skipped_eps else "")
|
||||
vendor_endpoints = merged
|
||||
else:
|
||||
vendor_id = getID()
|
||||
eps_norm = [{"base_url": (ep.get("base_url") or "").rstrip("/"),
|
||||
"region": ep.get("region") or "domestic",
|
||||
"timeout": int(_f(ep.get("timeout"), 60))} for ep in endpoints]
|
||||
"timeout": int(_f(ep.get("timeout"), 60)),
|
||||
"protocol": protocol} for ep in endpoints
|
||||
if (ep.get("base_url") or "").strip()]
|
||||
await sor.C("llm_vendor", {
|
||||
"id": vendor_id, "name": vendor_name,
|
||||
"endpoints": json.dumps(eps_norm, ensure_ascii=False),
|
||||
"description": spec.get("doc_notes", "") or "",
|
||||
"status": "active", "org_id": ctx.get("org_id", "") or "0"})
|
||||
vendor_action = "新建供应商 %s(%s)" % (vendor_id, vendor_name)
|
||||
vendor_endpoints = eps_norm
|
||||
|
||||
# 2. 适配模板:按 (协议×能力) 复用;旧骨架模板(硬编码 xxx_file/__from_doc__,
|
||||
# 2026-09-05 实测 t2v 因它渲染崩)视为不可用,按文档示例自愈重建
|
||||
@ -649,6 +681,32 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
if protocol not in RUNTIME_PROTOCOLS:
|
||||
rt_note = ("⚠️ 协议「%s」的适配模板已存档,但当前运行时调用链仅支持 %s——"
|
||||
"该供应商模型暂不可被产线直接调用" % (protocol, "/".join(RUNTIME_PROTOCOLS)))
|
||||
# 4. 账号端点绑定自检(2026-09-05 404 教训):账号只绑 compatible-mode
|
||||
# 端点时,原生协议模型运行时会被选到错误端点 → 404。自动补绑本协议
|
||||
# 端点到该供应商全部活跃账号(不删已有绑定),如实报告。
|
||||
account_notes = []
|
||||
proto_idx = [i for i, e in enumerate(vendor_endpoints or [])
|
||||
if (e.get("protocol") or "") == protocol]
|
||||
if proto_idx:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name, endpoint_ids FROM llm_account "
|
||||
"WHERE vendor_id=${v}$ AND status='active'", {"v": vendor_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
for acc in recs or []:
|
||||
try:
|
||||
bound = [int(b) for b in json.loads(getattr(acc, "endpoint_ids", "") or "[]")]
|
||||
except Exception:
|
||||
bound = []
|
||||
if any(i in proto_idx for i in bound):
|
||||
continue # 已绑本协议端点,可用
|
||||
newb = sorted(set(bound) | set(proto_idx))
|
||||
await sor.sqlExe(
|
||||
"UPDATE llm_account SET endpoint_ids=${e}$, updated_at=NOW() WHERE id=${i}$",
|
||||
{"e": json.dumps(newb), "i": getattr(acc, "id", "")})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
account_notes.append("账号「%s」原绑定端点 %s 不含协议 %s 的端点(运行时会 404),"
|
||||
"已自动补绑为 %s" % (getattr(acc, "name", ""), bound,
|
||||
protocol, newb))
|
||||
audit_note = await _audit_media_convention(sor, list(profile_ids.values()))
|
||||
return json.dumps({
|
||||
"vendor": vendor_action, "profiles": profile_ids,
|
||||
@ -656,6 +714,7 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
"vendor_conflicts": conflicts,
|
||||
"template_errors": tpl_errors,
|
||||
"template_notes": tpl_notes,
|
||||
"account_notes": account_notes,
|
||||
"runtime_note": rt_note,
|
||||
"media_audit": audit_note,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user