🔍 核心机关与修改指南
📋 SKILL_SPECS 清单 双技能同构
同时管理 energy_station(社交能量补给站)和 universal_skill(启元百变互动官)两套王牌技能。
🔄 自动改写机型变量 防手抖设计
精准校验源码里恰好有一处 ROBOT_MODEL = "q1",并在构建 T1 包时自动替换成 "t1",绝不污染原文件。
📝 自动配对上架小抄 publish_*.json
自动生成包含双字段(categoryIds/CodeList、robotModelIds/CodeList)的 JSON 文件,彻底杜绝发布时报 1001 缺字段错误!
💻 完整源码与配置内容(共 97 行 · 支持行号定位与一键复制)
| 1 | """从 Example/ 下的技能源码构建 Q1/T1 双机型 .rpk 包及可审阅的发布元数据。""" |
| 2 | |
| 3 | import importlib.util |
| 4 | import json |
| 5 | import shutil |
| 6 | import tempfile |
| 7 | from pathlib import Path |
| 8 | |
| 9 | ROOT = Path(__file__).resolve().parents[1] |
| 10 | WORKSPACE = ROOT.parent |
| 11 | DEV_KIT = ROOT / "primehub_dev_kit" |
| 12 | EXAMPLE_ROOT = WORKSPACE / "Example" |
| 13 | OUTPUT = ROOT / "dist" |
| 14 | |
| 15 | SKILL_SPECS = ( |
| 16 | { |
| 17 | "folder": "energy_station", |
| 18 | "pkg_prefix": "cans518_energy_station", |
| 19 | "publish_prefix": "publish", |
| 20 | "title": "社交能量补给站", |
| 21 | "short_desc": "三格好心情,给每个敢动手的人充个电", |
| 22 | "long_desc_tpl": ( |
| 23 | "离线小剧场:开场、勇气/灵感/快乐三格充电、谢幕。" |
| 24 | "每格短台词配动作,无需联网或语音输入。" |
| 25 | "仅适配%s,使用预设动作,运行时请留出周围1米空间。" |
| 26 | "基于官方示例改编;台词和编排可在源码中修改。" |
| 27 | ), |
| 28 | "icon": "skill-game.svg", |
| 29 | }, |
| 30 | { |
| 31 | "folder": "universal_skill", |
| 32 | "pkg_prefix": "cans518_universal_skill", |
| 33 | "publish_prefix": "publish_universal", |
| 34 | "title": "启元百变互动官", |
| 35 | "short_desc": "迎宾问候+灵感广播+好运盲盒抽签的通用互动剧场", |
| 36 | "long_desc_tpl": ( |
| 37 | "Q1/T1通用互动技能模板:包含元气迎宾、每日灵感金句(支持联网API与断网自动兜底)、" |
| 38 | "随机好运抽签及谢幕。仅适配%s,内置形态检测与TTS防抖轮询,运行时请留出周围1米空间。" |
| 39 | ), |
| 40 | "icon": "skill-verify.svg", |
| 41 | }, |
| 42 | ) |
| 43 | |
| 44 | |
| 45 | def build_skill(pack_module, spec_cfg: dict) -> None: |
| 46 | source_dir = EXAMPLE_ROOT / spec_cfg["folder"] |
| 47 | for model, model_id, model_code in (("q1", 100, "robot_q"), ("t1", 101, "robot_t")): |
| 48 | with tempfile.TemporaryDirectory(prefix="qiyuan_build_") as staging: |
| 49 | skill_dir = Path(staging) / (spec_cfg["folder"] + "_" + model) |
| 50 | shutil.copytree(source_dir, skill_dir, ignore=shutil.ignore_patterns("__pycache__", "*.pyc")) |
| 51 | script = skill_dir / "src/main.py" |
| 52 | source = script.read_text(encoding="utf-8") |
| 53 | assignment = 'ROBOT_MODEL = "q1"' |
| 54 | if source.count(assignment) != 1: |
| 55 | raise ValueError("期望恰好一个 ROBOT_MODEL 声明: " + str(script)) |
| 56 | script.write_text(source.replace(assignment, 'ROBOT_MODEL = "%s"' % model), encoding="utf-8") |
| 57 | config = skill_dir / "cfg/pkg.yaml" |
| 58 | package_name = "%s_%s_20261024" % (spec_cfg["pkg_prefix"], model) |
| 59 | lines = [] |
| 60 | for line in config.read_text(encoding="utf-8").splitlines(): |
| 61 | if line.startswith("package_name:"): |
| 62 | line = "package_name: " + package_name |
| 63 | elif line.startswith("robot_model:"): |
| 64 | line = "robot_model: " + model |
| 65 | lines.append(line) |
| 66 | config.write_text("\n".join(lines) + "\n", encoding="utf-8") |
| 67 | pack_module.pack(skill_dir, OUTPUT) |
| 68 | metadata = { |
| 69 | "skillName": spec_cfg["title"] + " " + model.upper(), |
| 70 | "version": "0.1.0", |
| 71 | "skillDesc": spec_cfg["short_desc"], |
| 72 | "long_description": spec_cfg["long_desc_tpl"] % model.upper(), |
| 73 | "categoryIds": [104], |
| 74 | "categoryCodeList": ["action"], |
| 75 | "tagsIds": [127], |
| 76 | "tagsCodeList": ["companion"], |
| 77 | "robotModelIds": [model_id], |
| 78 | "robotModelCodeList": [model_code], |
| 79 | "iconUrl": spec_cfg["icon"], |
| 80 | } |
| 81 | publish_file = "%s_%s.json" % (spec_cfg["publish_prefix"], model) |
| 82 | (OUTPUT / publish_file).write_text( |
| 83 | json.dumps(metadata, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" |
| 84 | ) |
| 85 | |
| 86 | |
| 87 | def build() -> None: |
| 88 | spec = importlib.util.spec_from_file_location("primehub_pack", DEV_KIT / "pack.py") |
| 89 | pack_module = importlib.util.module_from_spec(spec) |
| 90 | spec.loader.exec_module(pack_module) |
| 91 | OUTPUT.mkdir(exist_ok=True) |
| 92 | for spec_cfg in SKILL_SPECS: |
| 93 | build_skill(pack_module, spec_cfg) |
| 94 | |
| 95 | |
| 96 | if __name__ == "__main__": |
| 97 | build() |