feat(platform): 内部助手全链工具——apply_model_pricing(定价新模式:一模型一方案/无model维度/无filters平铺/doc_quote强制/出处URL入描述/幂等拉链)+test_model_call(真实治理链调用)+check_model_accounting(三态检查+独立复算金额);提取规格升级pricing结构化+doc_url;工作流提示词九步闭环
This commit is contained in:
parent
c74bf38506
commit
4543e035dd
@ -19,6 +19,9 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
|
||||
import yaml
|
||||
|
||||
from appPublic.uniqueID import getID
|
||||
|
||||
@ -208,6 +211,8 @@ async def _h_fetch_model_doc(sor, params, ctx):
|
||||
text = raw
|
||||
if len(text) > _MAX_DOC_CHARS:
|
||||
text = text[:_MAX_DOC_CHARS] + "\n[文档过长已截断,共 %d 字符]" % len(text)
|
||||
# 出处行:提取规格照抄进 doc_url,落库写进模型/定价描述(2026-09-05 用户规则)
|
||||
text = text + "\n\n[出处URL] " + url
|
||||
return text
|
||||
|
||||
|
||||
@ -229,6 +234,16 @@ _EXTRACT_PROMPT = """你是大模型 API 配置专家。通读下面这份模型
|
||||
"models": [{"vendor_model_id": "供应商侧模型ID", "capability": "t2t|t2i|i2t|t2v|i2v|embedding|rerank|tts|asr",
|
||||
"sync_mode": "sync|async(提交后需轮询查询结果的填 async)",
|
||||
"description": "一句话说明"}],
|
||||
"pricing": {
|
||||
"currency": "CNY|USD(文档标注的币种)",
|
||||
"items": [{"vendor_model_id": "对应模型ID",
|
||||
"factor": "duration|flat|prompt_tokens|completion_tokens(计价因子:视频按秒=duration,按次=flat,token计价=对应token因子)",
|
||||
"unit_price": 0.45,
|
||||
"unit": "秒|次|百万(文档标注的计价单位名)",
|
||||
"dimensions": {"resolution": "480P(影响价格档位的维度,按文档原文值;禁止放 model)"},
|
||||
"doc_quote": "文档原文定价句(逐字照抄,标明出处用)"}]
|
||||
},
|
||||
"doc_url": "从文档文本末尾的 [出处URL] 行逐字照抄(配置出处,必填)",
|
||||
"doc_notes": "文档中影响配置的关键注意点"
|
||||
}
|
||||
|
||||
@ -238,10 +253,12 @@ _EXTRACT_PROMPT = """你是大模型 API 配置专家。通读下面这份模型
|
||||
- 同步模型 async_steps 填空数组 []。
|
||||
|
||||
定价规则:
|
||||
- 价格按文档原文记录,并保留文档标注的单位(在 description 中写明,如「元/百万tokens」)
|
||||
- 禁止自行换算、推导或统一单位——单位是否吻合由上线测试阶段的记账核对验证
|
||||
- 文档没写价格的模型:价格字段填 null(禁止编造价格),在 description 注明
|
||||
- cost_* 未知时与 price_* 相同(无供应商折扣时成本=售价)
|
||||
- pricing.items 每条必须带 doc_quote(文档原文定价句逐字照抄)——定价只允许来自文档原文
|
||||
- 价格/单位按文档原文记录,禁止自行换算、推导或统一单位
|
||||
- 同一模型多档价格(如不同分辨率)拆成多条 item,各带自己的 dimensions 与 doc_quote
|
||||
- dimensions 禁止放 model——一个定价方案只服务一个模型;定价完全相同的多个模型
|
||||
共享同一个定价方案(同一 ppid),不是往定价里加 model 过滤
|
||||
- 文档没写价格:items 填空数组 [](禁止编造价格)
|
||||
|
||||
文档内容:
|
||||
"""
|
||||
@ -399,6 +416,7 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
profile_ids[cap] = pid
|
||||
|
||||
# 3. 模型:按 (vendor, vendor_model_id) 幂等
|
||||
doc_url = (spec.get("doc_url") or "").strip()
|
||||
created, updated, skipped = [], [], []
|
||||
for m in models:
|
||||
vmid = (m.get("vendor_model_id") or "").strip()
|
||||
@ -408,6 +426,9 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
cap = m.get("capability") or "t2t"
|
||||
sync_mode = "async" if str(m.get("sync_mode") or "").strip() == "async" else "sync"
|
||||
desc = (m.get("description", "") or "").strip()
|
||||
# 出处标注(2026-09-05 用户规则):模型注册表描述字段必须保存文档 URL
|
||||
if doc_url and ("[出处:" + doc_url + "]") not in desc:
|
||||
desc = (desc + " " if desc else "") + "[出处:%s]" % doc_url
|
||||
# 异步模型:后续步骤模板链(提交后顺序执行 query→[download])
|
||||
query_ids = []
|
||||
if sync_mode == "async":
|
||||
@ -588,7 +609,305 @@ async def _ensure_step_profile(sor, protocol, cap, vmid, step, spec):
|
||||
return pid
|
||||
|
||||
|
||||
# ────────────────────── 工具 5:模块/应用信息查询 ──────────────────────
|
||||
# ────────────────────── 工具 5:定价自动导入 ──────────────────────
|
||||
|
||||
_FACTOR_LABELS = {
|
||||
'duration': '时长', 'flat': '按次', 'prompt_tokens': '输入tokens',
|
||||
'completion_tokens': '输出tokens',
|
||||
}
|
||||
|
||||
|
||||
def _build_pricing_yaml(currency_items, dimensions_all, factor):
|
||||
"""生成定价 YAML(2026-09-05 用户定夺的新模式):
|
||||
|
||||
- 不放 model 过滤:一个定价方案只服务一个模型;定价相同的多个模型共享
|
||||
同一 ppid(不是往定价里加 model 维度)
|
||||
- 不用 filters 子结构:维度直接平铺在定价项(引擎对非保留键做 AND 匹配)
|
||||
- fields 需定义每个维度(role: filter),否则引擎报「在fields中没有定义」
|
||||
"""
|
||||
fields = {
|
||||
'price_factors': {'type': 'string', 'role': 'factor', 'label': '计价因子'},
|
||||
'unit_prices': {'type': 'float', 'role': 'factor', 'label': '单位定价'},
|
||||
'unit': {'type': 'string', 'role': 'factor', 'label': '计价单位'},
|
||||
}
|
||||
if factor not in fields:
|
||||
fields[factor] = {'type': 'float', 'role': 'factor',
|
||||
'label': _FACTOR_LABELS.get(factor, factor)}
|
||||
for dim in sorted(dimensions_all):
|
||||
fields[dim] = {'type': 'string', 'role': 'filter', 'label': dim}
|
||||
pricings = []
|
||||
for it in currency_items:
|
||||
item = {'price_factors': factor, 'unit_prices': it['unit_price'],
|
||||
'unit': it['unit']}
|
||||
for dk, dv in sorted((it.get('dimensions') or {}).items()):
|
||||
item[dk] = dv
|
||||
pricings.append(item)
|
||||
units = sorted(set(it['unit'] for it in currency_items))
|
||||
unit_values = {u: 1 for u in units}
|
||||
if '百万' in unit_values:
|
||||
unit_values['百万'] = 1000000
|
||||
if '千' in unit_values:
|
||||
unit_values['千'] = 1000
|
||||
doc = {'unit_values': unit_values, 'fields': fields, 'pricings': pricings}
|
||||
return yaml.dump(doc, allow_unicode=True, sort_keys=False)
|
||||
|
||||
|
||||
async def _h_apply_model_pricing(sor, params, ctx):
|
||||
"""定价自动导入:按提取规格建定价方案并挂模型 ppid(幂等)。
|
||||
|
||||
幂等键:模型已有 ppid 且方案描述含同一出处 URL → 更新时序(拉链);
|
||||
否则新建方案。价格只来自文档原文(提取层强制 doc_quote)。
|
||||
"""
|
||||
err = await _require_owner(sor, ctx)
|
||||
if err:
|
||||
return err
|
||||
try:
|
||||
spec = json.loads(params.get("spec") or "{}")
|
||||
except Exception:
|
||||
return "spec 不是合法 JSON"
|
||||
pricing = spec.get("pricing") or {}
|
||||
items = pricing.get("items") or []
|
||||
if not items:
|
||||
return "spec.pricing.items 为空——文档没提取到价格(禁止编造),如需定价请先核对文档"
|
||||
doc_url = (spec.get("doc_url") or "").strip()
|
||||
currency = (pricing.get("currency") or "CNY").strip() or "CNY"
|
||||
# 校验:每条必须有 doc_quote(出处);dimensions 禁止 model
|
||||
for it in items:
|
||||
if not (it.get("doc_quote") or "").strip():
|
||||
return "定价条目缺 doc_quote(文档原文定价句)——定价只允许来自文档原文,拒绝落库"
|
||||
if "model" in (it.get("dimensions") or {}):
|
||||
return "定价 dimensions 含 model——一个定价方案只服务一个模型,禁止 model 维度;定价相同的模型共享同一 ppid"
|
||||
# 按 vendor_model_id 分组(一个模型一个定价方案)
|
||||
by_model = {}
|
||||
for it in items:
|
||||
vmid = (it.get("vendor_model_id") or "").strip()
|
||||
if not vmid:
|
||||
return "定价条目缺 vendor_model_id"
|
||||
by_model.setdefault(vmid, []).append(it)
|
||||
|
||||
results = []
|
||||
for vmid, mitems in by_model.items():
|
||||
# 找模型
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name, ppid FROM llm_model WHERE vendor_model_id=${m}$ "
|
||||
"AND status='active' LIMIT 1", {"m": vmid})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if not recs:
|
||||
results.append({"model": vmid, "ok": False,
|
||||
"error": "模型未注册(先 apply_llm_config)"})
|
||||
continue
|
||||
model_id = getattr(recs[0], "id", "")
|
||||
old_ppid = getattr(recs[0], "ppid", "") or ""
|
||||
factors = sorted(set((it.get("factor") or "flat") for it in mitems))
|
||||
if len(factors) != 1:
|
||||
results.append({"model": vmid, "ok": False,
|
||||
"error": "同一模型混用多个计价因子 %s——请拆分为多个定价方案" % factors})
|
||||
continue
|
||||
factor = factors[0]
|
||||
dims_all = set()
|
||||
for it in mitems:
|
||||
dims_all.update((it.get("dimensions") or {}).keys())
|
||||
yaml_str = _build_pricing_yaml(mitems, dims_all, factor)
|
||||
# 引擎试算护栏:渲染一遍确认合法 YAML + fields 完整(不落库先验证)
|
||||
try:
|
||||
parsed = yaml.safe_load(yaml_str)
|
||||
assert parsed.get('pricings') and parsed.get('fields')
|
||||
except Exception as e:
|
||||
results.append({"model": vmid, "ok": False,
|
||||
"error": "定价 YAML 生成校验失败:%s" % str(e)[:150]})
|
||||
continue
|
||||
desc_quotes = ";".join((it.get("doc_quote") or "")[:80] for it in mitems)[:500]
|
||||
pp_desc = "定价出处:%s | 文档原文:%s" % (doc_url or "(未提供)", desc_quotes)
|
||||
now = time.strftime('%Y-%m-%d')
|
||||
if old_ppid:
|
||||
# 幂等更新:新时序行(拉链——旧行 expired_date 截到今天)
|
||||
ppid = old_ppid
|
||||
await sor.sqlExe(
|
||||
"UPDATE pricing_program_timing SET expired_date=${d}$ "
|
||||
"WHERE ppid=${p}$ AND expired_date='9999-12-31'",
|
||||
{"d": now, "p": ppid})
|
||||
await sor.sqlExe(
|
||||
"UPDATE pricing_program SET description=${d}$ WHERE id=${i}$",
|
||||
{"d": pp_desc[:1000], "i": ppid})
|
||||
action = "更新(新时序生效 %s)" % now
|
||||
else:
|
||||
ppid = getID()
|
||||
await sor.C("pricing_program", {
|
||||
"id": ppid, "name": "%s 定价" % vmid, "ownerid": "0",
|
||||
"providerid": "", "pricing_belong": "",
|
||||
"description": pp_desc[:1000], "currency": currency})
|
||||
action = "新建"
|
||||
await sor.C("pricing_program_timing", {
|
||||
"id": getID(), "ppid": ppid, "name": "%s %s计价" % (vmid, factor),
|
||||
"pricing_data": yaml_str,
|
||||
"enabled_date": now, "expired_date": "9999-12-31"})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
# 挂模型
|
||||
await sor.sqlExe("UPDATE llm_model SET ppid=${p}$ WHERE id=${i}$",
|
||||
{"p": ppid, "i": model_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
results.append({"model": vmid, "ok": True, "ppid": ppid,
|
||||
"action": action, "items": len(mitems),
|
||||
"factor": factor,
|
||||
"dimensions": sorted(dims_all)})
|
||||
ok_n = sum(1 for r in results if r.get("ok"))
|
||||
return json.dumps({
|
||||
"summary": "定价导入:%d/%d 个模型成功" % (ok_n, len(results)),
|
||||
"results": results,
|
||||
"convention": "一个定价方案只服务一个模型(无 model 维度/无 filters,维度平铺);"
|
||||
"定价完全相同的模型共享同一 ppid",
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ────────────────────── 工具 6:模型测试(真实调用) ──────────────────────
|
||||
|
||||
async def _h_test_model_call(sor, params, ctx):
|
||||
"""真实调用测试:走完整治理链(门禁→上游→结算),返回调用结果+流水ID。
|
||||
|
||||
参数:model_name 必填;prompt 文本提示(t2t);业务参数放 params JSON
|
||||
(如 image_file/resolution/duration);timeout 等待秒数(异步模型默认 600)。
|
||||
前置:供应商账号已配 api_key(无 key 会报可行动错误,不静默)。
|
||||
"""
|
||||
err = await _require_owner(sor, ctx)
|
||||
if err:
|
||||
return err
|
||||
model_name = (params.get("model_name") or "").strip()
|
||||
if not model_name:
|
||||
return "缺 model_name"
|
||||
prompt = (params.get("prompt") or "回复两个字:正常").strip()
|
||||
try:
|
||||
extra = json.loads(params.get("params") or "{}")
|
||||
except Exception:
|
||||
return "params 不是合法 JSON"
|
||||
timeout_s = int(_f(params.get("timeout"), 0)) or 600
|
||||
try:
|
||||
from pipeline_llm.inference import chat_inference
|
||||
except Exception as e:
|
||||
return "推理模块未加载:%s" % str(e)[:100]
|
||||
payload = {"model": model_name,
|
||||
"messages": [{"role": "user", "content": prompt}]}
|
||||
payload.update(extra)
|
||||
if timeout_s:
|
||||
payload["_timeout"] = timeout_s
|
||||
task_ref = "agent-test:%s:%d" % (model_name[:20], int(time.time()))
|
||||
t0 = time.time()
|
||||
try:
|
||||
data = await chat_inference(ctx.get("org_id", "") or "0",
|
||||
ctx.get("user_id", ""), payload,
|
||||
model_name=model_name, task_ref=task_ref)
|
||||
except Exception as e:
|
||||
return json.dumps({
|
||||
"ok": False, "model": model_name, "task_ref": task_ref,
|
||||
"elapsed_sec": round(time.time() - t0, 1),
|
||||
"error": str(e)[:400],
|
||||
"hint": "常见原因:账号无api_key/余额不足/端点超时/模板配置缺失——按错误消息处置",
|
||||
}, ensure_ascii=False)
|
||||
elapsed = round(time.time() - t0, 1)
|
||||
content = ""
|
||||
try:
|
||||
content = data["choices"][0]["message"]["content"]
|
||||
except Exception:
|
||||
content = str(data)[:200]
|
||||
out = {
|
||||
"ok": True, "model": model_name, "task_ref": task_ref,
|
||||
"elapsed_sec": elapsed,
|
||||
"content_head": str(content)[:200],
|
||||
"usage": data.get("usage") or {},
|
||||
}
|
||||
if data.get("media"):
|
||||
out["media"] = data["media"] # 生成物本地持久 URL(已落地)
|
||||
if data.get("task_id"):
|
||||
out["task_id"] = data["task_id"]
|
||||
return json.dumps(out, ensure_ascii=False)
|
||||
|
||||
|
||||
# ────────────────────── 工具 7:记账检查 ──────────────────────
|
||||
|
||||
async def _h_check_model_accounting(sor, params, ctx):
|
||||
"""记账正确性检查(三态:created/accounted/failed)。
|
||||
|
||||
按 task_ref(测试调用返回的)或 model_name 查最近流水:
|
||||
1. 流水存在性 + accounting_status
|
||||
2. usages 计价因子完整性
|
||||
3. accounted → charge 金额与定价引擎重算对比(独立复算,不信记账侧)
|
||||
4. failed → 给出 note 原因与处置建议
|
||||
5. created → 出账循环 60 秒一轮,提示等待或查 worker 进程
|
||||
"""
|
||||
err = await _require_owner(sor, ctx)
|
||||
if err:
|
||||
return err
|
||||
task_ref = (params.get("task_ref") or "").strip()
|
||||
model_name = (params.get("model_name") or "").strip()
|
||||
if not task_ref and not model_name:
|
||||
return "需要 task_ref(测试调用返回值)或 model_name 之一"
|
||||
if task_ref:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, model_id, status, accounting_status, charge, cost, usages, "
|
||||
"ppid, note, created_at FROM llm_usage WHERE task_ref=${t}$ "
|
||||
"ORDER BY created_at DESC LIMIT 3", {"t": task_ref})
|
||||
else:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT u.id, u.model_id, u.status, u.accounting_status, u.charge, u.cost, "
|
||||
"u.usages, u.ppid, u.note, u.created_at FROM llm_usage u "
|
||||
"JOIN llm_model m ON m.id=u.model_id "
|
||||
"WHERE m.name=${n}$ OR m.vendor_model_id=${n}$ "
|
||||
"ORDER BY u.created_at DESC LIMIT 3", {"n": model_name})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if not recs:
|
||||
return "未找到流水(task_ref=%s model=%s)——调用可能没发生或没结算" % (
|
||||
task_ref, model_name)
|
||||
out_rows = []
|
||||
for r in recs:
|
||||
row = dict(r)
|
||||
st = row.get("accounting_status", "")
|
||||
item = {
|
||||
"usage_id": row.get("id", ""),
|
||||
"call_status": row.get("status", ""),
|
||||
"accounting_status": st,
|
||||
"charge": float(row.get("charge") or 0),
|
||||
"usages": row.get("usages", "") or "",
|
||||
"ppid": row.get("ppid", "") or "",
|
||||
"created_at": str(row.get("created_at", "")),
|
||||
}
|
||||
if st == "failed":
|
||||
item["verdict"] = "记账失败"
|
||||
item["reason"] = (row.get("note") or "")[:200]
|
||||
item["hint"] = ("常见处置:模型未挂ppid→apply_model_pricing;"
|
||||
"模型未映射产品→产品管理导入产线模型;"
|
||||
"定价无匹配档位→核对usages维度值与定价YAML是否一致")
|
||||
elif st == "created":
|
||||
item["verdict"] = "待记账(出账循环60秒一轮,稍后复查;持续不变查记账worker进程)"
|
||||
elif st == "accounted":
|
||||
# 独立复算:用 usages 因子过定价引擎,对比 charge
|
||||
item["verdict"] = "已记账"
|
||||
try:
|
||||
usage_data = json.loads(row.get("usages") or "{}")
|
||||
except Exception:
|
||||
usage_data = {}
|
||||
ppid = row.get("ppid") or ""
|
||||
if ppid and usage_data:
|
||||
try:
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
fn = getattr(env, "buffered_charging", None)
|
||||
if fn:
|
||||
prices = await fn(ppid, usage_data)
|
||||
expect = round(sum(float(getattr(p, "amount", 0) or 0)
|
||||
for p in (prices or [])), 6)
|
||||
item["recomputed_amount"] = expect
|
||||
item["amount_match"] = abs(expect - float(row.get("charge") or 0)) < 0.01
|
||||
if not item["amount_match"]:
|
||||
item["verdict"] = "已记账但金额不符(复算 %.4f ≠ charge %.4f)" % (
|
||||
expect, float(row.get("charge") or 0))
|
||||
except Exception as e:
|
||||
item["recompute_error"] = str(e)[:150]
|
||||
else:
|
||||
item["verdict"] = "已记账(缺ppid或usages,无法独立复算)"
|
||||
out_rows.append(item)
|
||||
return json.dumps({"rows": out_rows}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ────────────────────── 工具 8:模块/应用信息查询 ──────────────────────
|
||||
|
||||
async def _h_platform_modules(sor, params, ctx):
|
||||
"""列出平台已装载的业务模块(内部 agent 了解平台构成用)。"""
|
||||
@ -639,11 +958,39 @@ PLATFORM_TOOLS = [
|
||||
),
|
||||
ToolDefinition(
|
||||
name="apply_llm_config",
|
||||
description="按提取的规格写入模型治理配置:供应商/端点/适配模板/模型(含定价)。幂等——已有模型只更新定价。仅owner组织角色可用。",
|
||||
description="按提取的规格写入模型治理配置:供应商/端点/适配模板/模型。幂等——已有模型只更新。定价不在此工具(用 apply_model_pricing)。仅owner组织角色可用。",
|
||||
parameters={"spec": "extract_llm_api_spec 返回的 JSON 规格"},
|
||||
category="platform",
|
||||
requires_confirmation=True,
|
||||
),
|
||||
ToolDefinition(
|
||||
name="apply_model_pricing",
|
||||
description="定价自动导入:按提取规格的 pricing 建定价方案(pricing_program+时序YAML)并挂模型 ppid。幂等(已有 ppid 拉链更新时序)。一个定价方案只服务一个模型;定价相同的模型共享同一 ppid。价格必须来自文档原文(每条带 doc_quote 否则拒绝)。仅owner组织角色可用。",
|
||||
parameters={"spec": "extract_llm_api_spec 返回的 JSON 规格(含 pricing 与 doc_url)"},
|
||||
category="platform",
|
||||
requires_confirmation=True,
|
||||
),
|
||||
ToolDefinition(
|
||||
name="test_model_call",
|
||||
description="模型真实调用测试:走完整治理链(门禁→上游→结算)。返回 ok/content/usage/media(生成物本地URL)/task_ref。前置:供应商账号已配 api_key。异步模型(视频等)等待至任务完成(默认600秒)。仅owner组织角色可用。",
|
||||
parameters={
|
||||
"model_name": "模型名(llm_model.name 或 vendor_model_id)",
|
||||
"prompt": "文本提示词(t2t 默认「回复两个字:正常」;生成类填生成描述)",
|
||||
"params": "业务参数 JSON 字符串(生成类模型用,如 {\"image_file\": \"https://...\", \"resolution\": \"480P\", \"duration\": 5})",
|
||||
"timeout": "等待秒数(默认600,上限900)",
|
||||
},
|
||||
category="platform",
|
||||
requires_confirmation=True,
|
||||
),
|
||||
ToolDefinition(
|
||||
name="check_model_accounting",
|
||||
description="记账正确性检查(三态 created/accounted/failed):查最近流水的记账状态/usages因子/charge金额,accounted 时用定价引擎独立复算金额对比。failed 给原因与处置建议。仅owner组织角色可用。",
|
||||
parameters={
|
||||
"task_ref": "test_model_call 返回的 task_ref(优先)",
|
||||
"model_name": "或按模型名查最近流水",
|
||||
},
|
||||
category="platform",
|
||||
),
|
||||
ToolDefinition(
|
||||
name="platform_modules",
|
||||
description="列出平台已装载的业务模块清单。用户问「平台有哪些模块」时调用。仅owner组织角色可用。",
|
||||
@ -655,20 +1002,32 @@ PLATFORM_TOOLS = [
|
||||
PLATFORM_PROMPT = """
|
||||
你是产线平台的内部运维 agent,服务对象是 owner 组织的角色。
|
||||
|
||||
## 模型自动配置工作流(用户给你文档 URL 要求配置模型时)
|
||||
1. **先 `load_skill` 加载 `model-onboarding`**(配置→测试→上线三步标准流程)和
|
||||
`auto-api-pricing-config`(模板库),按技能严格执行
|
||||
2. `fetch_model_doc` 抓取官方文档页面
|
||||
3. `extract_llm_api_spec` 通读文档提取配置规格——**定价只允许来自文档原文**,文档没写的价格一律 null,禁止编造
|
||||
4. 把提取结果摘要给用户确认(尤其定价和端点),用户确认后
|
||||
5. `apply_llm_config` 写入(幂等:已有模型只更新定价)
|
||||
6. 进入测试/上线阶段时,严格按 model-onboarding 技能的判据执行(无 apikey 先补录,测试必查调用成功+记账四项)
|
||||
## 模型自动配置全链工作流(用户给文档 URL 要求配置模型时,按序独立完成)
|
||||
1. **先 `load_skill` 加载 `model-auto-config`**(含媒体转换铁律/能力分类先行/定价新模式),严格执行
|
||||
2. `fetch_model_doc` 抓取官方文档(返回文本末尾带 [出处URL] 行)
|
||||
3. `extract_llm_api_spec` 通读文档提取规格:端点/协议/能力/sync_mode/async_steps,
|
||||
以及 **pricing(每条价格带 doc_quote 文档原文)和 doc_url(照抄出处行)**
|
||||
4. 把提取摘要给用户确认(端点/能力/定价原文/出处),确认后:
|
||||
5. `apply_llm_config` 落库供应商/适配模板/模型(模型描述自动带 [出处:URL])
|
||||
6. `apply_model_pricing` 建定价方案并挂 ppid(定价描述带出处URL+文档原文;
|
||||
一个方案只服务一个模型,无 model 维度无 filters,维度平铺;幂等拉链更新)
|
||||
7. `test_model_call` 真实调用测试(前置:账号已配 api_key,没配则如实告知用户去补,
|
||||
不要伪造结果)——生成类模型传 params(image_file/resolution/duration 等)
|
||||
8. `check_model_accounting` 检查记账(传上一步的 task_ref):
|
||||
- accounted 且 amount_match=true → 全链通过,汇报金额
|
||||
- created → 等 60~120 秒再查一次(出账循环 60 秒一轮)
|
||||
- failed → 按 reason/hint 处置(未映射产品→告知用户在产品管理导入产线模型)
|
||||
9. 汇报:配置/定价/测试/记账四段结果 + 出处 URL + 遗留事项(如模板需人工核对项)
|
||||
|
||||
## 硬规则
|
||||
- **定价按文档原文记录**,单位照文档所写如实标注(写入 description),禁止自行换算、推导或统一单位;单位是否吻合由测试阶段的记账核对验证
|
||||
- cost_*(供应商成本)未知时与 price_* 相同
|
||||
- 非 openai_compat 协议的模型:模板会存档但运行时暂不可调用,必须如实告知
|
||||
- **定价只允许来自文档原文**(doc_quote 强制),禁止编造/换算/推导
|
||||
- **出处必留**:模型注册表描述字段与定价方案描述字段都必须保存文档 URL
|
||||
- 能力分类不存在 → 先加分类(字典/种子/提示词/端点注释四处同步)再配模型,禁止塞近似分类
|
||||
- 生成类模型媒体铁律:上行 b64media2url(request, xxx_file)、下行 downloadfile2url(request, url);
|
||||
apply 返回的 media_audit 警告必须如实转告
|
||||
- 非标协议骨架模板需人工核对的项(__note__)必须如实转告用户,不假装完成
|
||||
- 文档抓取失败/内容不足时如实说明,不要凭记忆编造 API 格式
|
||||
- 测试没有 api_key 就停在配置阶段如实汇报,禁止跳过测试谎称完成
|
||||
- 所有工具仅 owner 组织角色可用,权限报错时如实转告用户
|
||||
|
||||
## 平台知识
|
||||
@ -681,6 +1040,9 @@ PLATFORM_HANDLERS = {
|
||||
"fetch_model_doc": _h_fetch_model_doc,
|
||||
"extract_llm_api_spec": _h_extract_llm_api_spec,
|
||||
"apply_llm_config": _h_apply_llm_config,
|
||||
"apply_model_pricing": _h_apply_model_pricing,
|
||||
"test_model_call": _h_test_model_call,
|
||||
"check_model_accounting": _h_check_model_accounting,
|
||||
"platform_modules": _h_platform_modules,
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user