feat(platform): apply_llm_config自动生成模型配套技能(2026-09-08用户定夺:每个模型加入需配套skill)
- _write_model_skill: skills/models/{model}/SKILL.md,数据源=落库param_schema
(required必备/非required可选带默认值)+能力字典模态+媒体三数组契约+doc出处
- 幂等:重apply覆盖更新;生成失败只记skill_notes不阻断(门禁降级LLM能力语义→代码兜底)
- 目录约定:skills/models/不在SkillLoader扫描范围,不污染agent技能目录层
This commit is contained in:
parent
7a041cbb18
commit
02650d4c2d
@ -607,6 +607,133 @@ def _capability_guard(capability, description):
|
||||
return None, None
|
||||
|
||||
|
||||
# ────────────────────── 模型配套技能自动生成(2026-09-08 用户定夺) ──────────────────────
|
||||
|
||||
def _model_skill_path(model_name):
|
||||
"""模型配套技能路径:skills/models/{model}/SKILL.md(全局模板目录)。
|
||||
|
||||
skills/models/ 不在 SkillLoader 扫描范围(global/pipelines/orgs/users),
|
||||
不进 agent 技能目录层——它是 invoke_model 完备性裁决的数据契约,
|
||||
由 platform_model_tools.load_model_skill 直读。
|
||||
"""
|
||||
import os
|
||||
from pipeline_core.skill_pack import get_skills_base
|
||||
safe = "".join(ch for ch in (model_name or "") if ch.isalnum() or ch in "-_.")
|
||||
if not safe:
|
||||
raise ValueError("模型名不能用于目录: %r" % model_name)
|
||||
return os.path.join(get_skills_base(), "models", safe, "SKILL.md")
|
||||
|
||||
|
||||
_CAP_MODAL_TEXT = {
|
||||
"t2t": ("文本", "文本"), "i2t": ("图像", "文本"), "m2t": ("多媒体", "文本"),
|
||||
"t2i": ("文本", "图像"), "i2i": ("图像", "图像"), "t2v": ("文本", "视频"),
|
||||
"i2v": ("图像", "视频"), "r2v": ("参考媒体(图像/视频/音频组合)", "视频"),
|
||||
"tts": ("文本", "语音"), "asr": ("语音", "文本"),
|
||||
"embedding": ("文本", "向量"), "rerank": ("候选集", "排序"),
|
||||
}
|
||||
|
||||
|
||||
async def _write_model_skill(sor, vmid, m, spec, profile_ids):
|
||||
"""生成/更新模型配套技能(幂等:重 apply 覆盖旧版)。
|
||||
|
||||
内容全部来自本次落库的确定数据(不编造):
|
||||
- 能力模态(输入→输出,能力字典语义)
|
||||
- 必备输入(param_schema 里 required=true 的字段 + 能力模态决定的媒体输入)
|
||||
- 可选参数(required=false,带默认值)
|
||||
- 调用契约(三数组媒体格式 + 产物形态)
|
||||
- 出处(doc_url / vendor / sync_mode)
|
||||
invoke_model 的完备性裁决 LLM 按本技能对照用户已给内容判断缺什么。
|
||||
"""
|
||||
import os
|
||||
cap = (m.get("capability") or "t2t").strip().lower()
|
||||
inp, outp = _CAP_MODAL_TEXT.get(cap, ("文本", "产物"))
|
||||
desc = (m.get("description") or "").strip()
|
||||
sync_mode = "async" if str(m.get("sync_mode") or "").strip() == "async" else "sync"
|
||||
doc_url = (spec.get("doc_url") or "").strip()
|
||||
|
||||
# param_schema(本次生成/复用的模板)→ 必备/可选参数表
|
||||
required_rows, optional_rows = [], []
|
||||
pid = profile_ids.get(cap, "")
|
||||
if pid:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT param_schema FROM llm_api_profile WHERE id=${i}$ LIMIT 1", {"i": pid})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if recs:
|
||||
try:
|
||||
schema = json.loads(getattr(recs[0], "param_schema", "") or "[]")
|
||||
except Exception:
|
||||
schema = []
|
||||
for f in schema if isinstance(schema, list) else []:
|
||||
if not isinstance(f, dict):
|
||||
continue
|
||||
nm = f.get("name") or ""
|
||||
label = f.get("label") or nm
|
||||
dv = f.get("defaultvalue")
|
||||
if f.get("required"):
|
||||
required_rows.append("- %s(%s)" % (nm, label))
|
||||
else:
|
||||
optional_rows.append("- %s:%s%s" % (
|
||||
nm, label, (",默认 %s" % dv) if dv not in (None, "") else ""))
|
||||
|
||||
# 能力模态决定的媒体输入(媒体三数组契约)
|
||||
media_required = []
|
||||
if cap in ("i2v", "i2i"):
|
||||
media_required.append("- image_files:输入图片(公网 URL 或 base64,字符串或数组)——必备")
|
||||
elif cap == "asr":
|
||||
media_required.append("- audio_files:输入音频(公网 URL 或 base64)——必备")
|
||||
elif cap == "r2v":
|
||||
media_required.append("- image_files / audio_files / video_files:至少一类参考素材——必备")
|
||||
elif cap in ("t2v",):
|
||||
media_required.append("- image_files:(可选)提供参考图则接近 i2v 形态")
|
||||
|
||||
lines = [
|
||||
"---",
|
||||
"name: model-%s" % vmid,
|
||||
"description: 模型 %s(%s)的输入契约——必备输入/可选参数/媒体格式。invoke_model 完备性裁决依据。" % (vmid, cap),
|
||||
"capability: %s" % cap,
|
||||
"sync_mode: %s" % sync_mode,
|
||||
"---" ,
|
||||
"",
|
||||
"# 模型 %s 输入契约" % vmid,
|
||||
"",
|
||||
"能力:%s(%s → %s)%s" % (cap, inp, outp, ",异步任务(提交→轮询)" if sync_mode == "async" else ",同步返回"),
|
||||
]
|
||||
if desc:
|
||||
lines += ["", "描述:" + desc[:300]]
|
||||
lines += ["", "## 必备输入(缺任一即不可调用,须先向用户索取)"]
|
||||
if media_required:
|
||||
lines += media_required
|
||||
if required_rows:
|
||||
lines += required_rows
|
||||
if not media_required and not required_rows:
|
||||
lines.append("- task:任务描述/提示词(%s内容)" % inp)
|
||||
lines += ["", "## 可选参数(有默认值或缺省可接受,缺失**不算**不完备)"]
|
||||
lines += (optional_rows or ["- (无)"])
|
||||
lines += [
|
||||
"",
|
||||
"## 调用契约",
|
||||
"- 提示词经 task 传入(组包为 messages 末条文本)。",
|
||||
"- 媒体输入一律三数组:image_files / audio_files / video_files(值为公网 URL 或 base64 的字符串或数组)。",
|
||||
"- 产物:%s(返回本地持久 URL)。" % outp,
|
||||
]
|
||||
if doc_url:
|
||||
lines.append("- 文档出处:" + doc_url)
|
||||
lines += [
|
||||
"",
|
||||
"## 完备性裁决规则(judge_completeness 按此执行)",
|
||||
"- 对照「必备输入」逐项检查用户已提供的内容(含任务描述里隐含的 URL/素材)。",
|
||||
"- 必备项缺失 → 判不完备,追问话术说清缺什么、要什么格式。",
|
||||
"- 可选参数缺失不拦。判断不了放行(宁可放行不误拦)。",
|
||||
"",
|
||||
"(本技能由模型上线 apply_llm_config 自动生成,重 apply 时覆盖更新;数据源=落库的 param_schema+能力字典,人工可修订。)",
|
||||
]
|
||||
path = _model_skill_path(vmid)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lines))
|
||||
logger.info("模型配套技能已写入: %s", path)
|
||||
|
||||
|
||||
async def _h_apply_llm_config(sor, params, ctx):
|
||||
"""按提取的规格写库:供应商(复用/新建)+ 适配模板 + 模型。
|
||||
|
||||
@ -978,6 +1105,23 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
"已自动补绑为 %s" % (getattr(acc, "name", ""), bound,
|
||||
protocol, newb))
|
||||
audit_note = await _audit_media_convention(sor, list(profile_ids.values()))
|
||||
# 5. 模型配套技能自动生成(2026-09-08 用户定夺:每个模型的加入需要有
|
||||
# 配套 skill——记录该模型需要什么样的输入;invoke_model 完备性判断由
|
||||
# LLM 按此技能对照用户已给内容裁决缺什么,不再靠硬编码能力表)。
|
||||
# 数据全部来自本次落库结果(param_schema 的 required + 能力模态 +
|
||||
# 媒体契约),生成失败只记 note 不阻断(技能缺失时门禁降级为
|
||||
# 「LLM 按能力语义判断 → 代码表兜底」,功能不失效)。
|
||||
skill_notes = []
|
||||
for m in models:
|
||||
vmid = (m.get("vendor_model_id") or "").strip()
|
||||
if not vmid:
|
||||
continue
|
||||
try:
|
||||
await _write_model_skill(sor, vmid, m, spec, profile_ids)
|
||||
skill_notes.append("%s: 配套技能已生成/更新" % vmid)
|
||||
except Exception as e:
|
||||
skill_notes.append("%s: 配套技能生成失败(%s)——完备性判断将按能力语义降级"
|
||||
% (vmid, str(e)[:80]))
|
||||
return json.dumps({
|
||||
"vendor": vendor_action, "profiles": profile_ids,
|
||||
"models_created": created, "models_updated": updated, "models_skipped": skipped,
|
||||
@ -985,6 +1129,7 @@ async def _h_apply_llm_config(sor, params, ctx):
|
||||
"template_errors": tpl_errors,
|
||||
"template_notes": tpl_notes,
|
||||
"account_notes": account_notes,
|
||||
"skill_notes": skill_notes,
|
||||
"runtime_note": rt_note,
|
||||
"media_audit": audit_note,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user