From 94de38b22f77722807c068648d86e8a7da22aa68 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 2 Sep 2026 17:35:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20model-onboarding=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E2=80=94=E2=80=94=E6=A8=A1=E5=9E=8B=E4=B8=8A=E7=BA=BF=E6=B5=81?= =?UTF-8?q?=E7=A8=8B(=E9=85=8D=E7=BD=AE=E2=86=92=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E2=86=92=E4=B8=8A=E7=BA=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 治理链全貌+表角色+策略即开关机制 - 无apikey场景: 配置可全部完成,测试须先补key - 测试判据四项: 调用成功/流水status=ok/charge与四价吻合/账号余额扣cost - 同协议模型快速复用通道(模板按协议×能力去重) --- .../common/model-onboarding/SKILL.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 skills_library/pipelines/platform_general/common/model-onboarding/SKILL.md diff --git a/skills_library/pipelines/platform_general/common/model-onboarding/SKILL.md b/skills_library/pipelines/platform_general/common/model-onboarding/SKILL.md new file mode 100644 index 0000000..90fd7c9 --- /dev/null +++ b/skills_library/pipelines/platform_general/common/model-onboarding/SKILL.md @@ -0,0 +1,170 @@ +--- +name: model-onboarding +description: 模型上线流程:配置→测试→上线三步。新供应商无apikey场景、同协议模型快速复用配置、测试必查调用成功+记账。内部agent上线模型必读。 +capability: model_onboarding +tools: [fetch_model_doc, extract_llm_api_spec, apply_llm_config, platform_llm_status] +--- + +# 模型上线流程(配置 → 测试 → 上线) + +新模型接入治理链的标准流程。**核心约束:新供应商没有 apikey 时,配置步骤全部可完成 +(不需要 key),测试步骤必须先补 apikey 才能执行。** + +## 治理链全貌(运行时实际机制) + +``` +调用方 → llm_bridge.llm_call_msgs + → govern_resolve 门禁链: + ① 用户限流 → ② 机构限流 → ⑤ 模型链(主→备容错) + → ⑥ 候选选择(端点偏好过滤 → 限流冷却避让 → 余额加权) + → ③④ 预授权(按估算冻结机构池+个人额度) + → 实际调用 (api_base + api_key 进程内, OpenAI 兼容 /chat/completions) + → govern_settle 结算:多退少补 + 账号余额扣 cost + 写 llm_usage 流水 +``` + +**治理开关 = 策略即开关**:机构有 active 的 `llm_org_policy` 才走治理链; +无策略 → `__LEGACY__` 走旧 `llm` 表。上线新模型给某机构用,必须配该机构的策略。 + +## 涉及的表(pipeline 库) + +| 表 | 角色 | 关键约束 | +|---|---|---| +| llm_vendor | 供应商 + 端点目录 | endpoints JSON: [{base_url, region, timeout}],全球端点配一次 | +| llm_api_profile | 适配模板 | **按协议×能力去重**,同 (protocol, capability) 复用,勿重复建 | +| llm_model | 模型目录 | vendor_model_id + profile_id + ppid + 四价(元/千token) | +| llm_account | 供应商账号 | api_key(AES) + endpoint_ids(选用端点下标) + balance(成本侧钱包) | +| llm_org_policy | 机构策略 | 主模型 + 备链 + 端点偏好;**有它才启用治理** | +| llm_org_quota / llm_user_quota | 机构池/个人额度 | 余额+限流,预授权从这里冻结 | +| llm_usage | 双维度流水 | charge(售价侧,扣机构池) + cost(成本侧,扣账号余额),测试必查 | + +> uapi/upapp/upappkey 是 sage 旧机制,产线平台模型治理一律走上面的表。 + +## 步骤一:配置(无需 apikey,全部可完成) + +1. **抓文档**:`fetch_model_doc` 抓 API 文档 + 定价页 +2. **提取规格**:`extract_llm_api_spec` → 端点/协议/模型清单/定价 +3. **落库**:`apply_llm_config`(幂等): + - 供应商:按名复用,端点去重合并 + - 模板:按 (协议×能力) 复用——**已有同协议同能力的 active 模板直接引用,不新建** + - 模型:按 (vendor, vendor_model_id) 幂等,四价只来自文档原文 + - 定价只来自文档;未列价 → 0 + 描述标注「需人工补录」 +4. **定价项目**(可选,机构计费需要):建 `pricing_program`(pricing_belong=provider) + 并把 id 回填到 `llm_model.ppid` +5. 把配置摘要给用户确认 + +**此时状态**:模型已在目录,但因无账号,候选选择会报 +「供应商下无启用账号」——预期行为,不是错误。 + +## 步骤二:测试(先补 apikey,必查调用成功 + 记账) + +**前置(向用户要)**: +1. 供应商 **apikey**(由用户在模型治理页录入 `llm_account`,AES 加密存储; + **改 key 须重启服务**——key 有进程内缓存) +2. 账号**充值**(`recharge_account`,成本侧钱包;余额为 0 也能被选中但建议充值, + 余额参与加权选择) +3. 账号**选用端点**(endpoint_ids,不选端点则候选为空) + +**测试脚本模板**(测试机 /d/pipeline/pipeline-app 下执行): + +```python +import asyncio, sys, json +sys.path.insert(0, ".") +from sqlor.dbpools import DBPools +from appPublic.jsonConfig import getConfig +db = DBPools(); cfg = getConfig(".", {"workdir": "."}) +if cfg and cfg.databases: db.databases = cfg.databases + +MODEL = "<新模型注册名>"; ORG = "<测试机构id>" + +async def m(): + # 记录测试前余额(先取模型→供应商,避免子查询多行) + async with db.sqlorContext("pipeline") as sor: + rs = await sor.sqlExe("SELECT vendor_id FROM llm_model WHERE name=${n}$ LIMIT 1", {"n": MODEL}) + await sor.sqlExe("COMMIT", {}) + if not rs: + print("MODEL_MISSING: llm_model 无此注册名"); return + vid = getattr(rs[0], "vendor_id", "") + rs = await sor.sqlExe("SELECT id, balance FROM llm_account WHERE status='active' " + "AND vendor_id=${v}$ LIMIT 5", {"v": vid}) + await sor.sqlExe("COMMIT", {}) + before = {getattr(r,'id',''): float(getattr(r,'balance',0)) for r in (rs or [])} + # ① 真实调用(走完整治理链:限流→模型链→候选→预授权→调用→结算) + from pipeline_service.llm_bridge import llm_call_msgs + out = await llm_call_msgs( + [{"role": "user", "content": "ping"}], + model=MODEL, org_id=ORG, user_id="onboarding-test") + print("CALL_OK:", str(out)[:100]) + # ② 记账检查(必须做) + async with db.sqlorContext("pipeline") as sor: + rs = await sor.sqlExe( + "SELECT id, req_tokens, resp_tokens, charge, cost, status, note FROM llm_usage " + "WHERE model_id=(SELECT id FROM llm_model WHERE name=${n}$ LIMIT 1) " + "ORDER BY created_at DESC LIMIT 1", {"n": MODEL}) + await sor.sqlExe("COMMIT", {}) + if not rs: + print("USAGE_MISSING: 无流水——治理链未生效或结算失败,必须排查") + else: + u = rs[0] + print("USAGE|status=%s req=%s resp=%s charge=%s cost=%s note=%s" % ( + getattr(u,'status',''), getattr(u,'req_tokens',''), + getattr(u,'resp_tokens',''), getattr(u,'charge',''), + getattr(u,'cost',''), str(getattr(u,'note',''))[:60])) + rs = await sor.sqlExe("SELECT id, balance FROM llm_account WHERE status='active' " + "AND vendor_id=${v}$ LIMIT 5", {"v": vid}) + await sor.sqlExe("COMMIT", {}) + for r in (rs or []): + aid = getattr(r,'id','') + print("BALANCE|%s: %s -> %s" % (aid, before.get(aid), float(getattr(r,'balance',0)))) + +asyncio.run(m()) +``` + +**测试通过判据(四项缺一不可)**: +- [ ] `CALL_OK`:模型真实返回内容(不是报错/空) +- [ ] `llm_usage` 有新流水且 `status=ok`(status=failed 即未通过) +- [ ] `charge = req_tokens/1000×price_input + resp_tokens/1000×price_output`(售价侧金额与四价吻合,核对 llm_model 定价) +- [ ] 账号 `balance` 减少 ≈ `cost`(成本侧扣减生效) + +任一项不通过:**禁止进入上线步骤**,先查根因(见故障对照表)。 +测试完成后,清理或标注测试产生的流水与额度消耗。 + +## 步骤三:上线(机构策略 = 开关) + +1. 为目标机构配置 `llm_org_policy`:主模型 = 新模型,备链 = 既有模型(容错), + 端点偏好(any/prefer/must × domestic/international) +2. 确认机构额度池 `llm_org_quota` 有余额(否则预授权失败) +3. **重启服务**(策略与 key 均有进程内缓存:`_govern_cache`、key 缓存) +4. 复测一次:该机构真实调用新模型 + 记账四项复查 +5. 回报:模型已上线,主备链与端点偏好说明 + +## 同协议模型快速配置(复用通道) + +同供应商追加模型、或不同供应商但协议相同(如又一个 OpenAI 兼容端点): + +1. **模板零新建**:(protocol, capability) 已有 active `llm_api_profile` → 新模型 + `profile_id` 直接引用,`apply_llm_config` 内部已自动按 (协议×能力) 去重复用 +2. **端点零重建**:同供应商直接追加模型;新供应商但同协议 → 新建供应商记录, + 端点目录填自己的,模板仍复用 +3. **批量注册**:多个模型一次 `apply_llm_config`(spec.models 数组), + 每个模型只需 `vendor_model_id + capability + 四价` +4. **同价模型**:文档里同价的模型逐一照录价格(禁止省略成"同上"推导); + 文档未列价的模型 → 0 + 标注,单独列出让用户补 + +## 故障对照表(govern_resolve 真实报错 → 根因) + +| 报错关键词 | 根因 | 处置 | +|---|---|---| +| 机构未配置模型容错策略 | 该机构无 active llm_org_policy | 上线步骤未做(预期)或忘配策略 | +| 未绑定供应商 | llm_model.vendor_id 空 | 配置步骤漏了 | +| 端点目录为空 | llm_vendor.endpoints 空 | 补端点 | +| 供应商下无启用账号 | 无 active llm_account | apikey 未录入(配置阶段预期) | +| 所有账号都未选用端点 | account.endpoint_ids 空 | 账号要选端点 | +| 端点偏好为「仅…」但没有 | must 偏好无匹配区域端点 | 配该区域端点或改偏好,**不跨端点降级** | +| 主/备模型全部不可用 | 候选全败(余额/冷却/限流) | 看最后原因;429 = 触发冷却,稍后重试 | + +## 硬规则 + +- 定价只来自文档原文(参见 auto-api-pricing-config 技能);缓存价/折扣价未列不配 +- apikey 只在 `llm_account`(AES),**不写进技能/日志/回报文本** +- 改 apikey、改策略后必须重启(进程内缓存) +- 测试不过不上线;测试必查调用成功 + 记账四项,缺流水即失败