#!/usr/bin/env python3 """一次性:给 capabilities.yaml 增加「图像」类能力 + 视频类新能力(含 docs 关联) 用法:python3 scripts/tools/extend_capabilities.py --dry # 只看,不写 python3 scripts/tools/extend_capabilities.py # 写入 """ from __future__ import annotations import argparse import shutil import sys from datetime import datetime from pathlib import Path import yaml ROOT = Path(__file__).resolve().parents[2] CFG = ROOT / "config" / "capabilities.yaml" # ── 新增分类 ────────────────────────────────────────────── NEW_CATEGORIES = { "image": {"name": "图像", "icon": "🖼️", "order": 2}, # 插在视觉之前 "visual": {"name": "视觉", "icon": "🎨", "order": 3}, "video": {"name": "视频", "icon": "🎞️", "order": 4}, "audio": {"name": "音频", "icon": "🔊", "order": 5}, "analysis": {"name": "分析", "icon": "🧠", "order": 6}, "external": {"name": "外部", "icon": "🧩", "order": 7}, } # ── 图像类能力(关键帧环节的主力)──────────────────────── Q = "qwen" # 千问AI平台(阿里 DashScope) IMG_CAPS = [ { "id": "text_to_image", "name": "文生图", "category": "image", "description": "文本 → 图片(角色定妆照 / 概念图 / 场景图 / 关键帧草图)", "input": {"prompt": "string"}, "output": {"image_path": "string"}, "params_schema": { "size": {"type": "enum", "options": ["1K", "2K", "4K"], "default": "2K", "label": "分辨率档"}, "n": {"type": "number", "min": 1, "max": 12, "default": 1, "label": "输出张数"}, "negative_prompt": {"type": "string", "default": "", "label": "负向提示词(排除不要的)"}, }, "providers": [ {"id": Q, "name": "千问AI平台·万相2.7", "api_key_ref": "aliyun.api_key", "model": "wan2.7-image-pro", "default": True}, {"id": Q, "name": "千问AI平台·Qwen-Image", "api_key_ref": "aliyun.api_key", "model": "qwen-image-3.0-pro"}, {"id": Q, "name": "千问AI平台·Z-Image(快/便宜)", "api_key_ref": "aliyun.api_key", "model": "z-image-turbo"}, {"id": "kling", "name": "可灵", "api_key_ref": "kling.api_key", "model": "kling-v3"}, {"id": "jimeng", "name": "即梦", "api_key_ref": "volcano.ark_api_key", "model": "seedream"}, ], "defaults": {"provider": Q, "model": "wan2.7-image-pro", "size": "2K", "n": 1}, "execution": "remote", "status": "ready", "impl": "character_sheet.py / 待接 qwen-image", "docs": [{"source": Q, "slug": "api-reference-image-generation-wan27-image-gen-edit-synchronous", "title": "Wan 2.7 — 同步调用"}], }, { "id": "ref_to_image", "name": "参考图生图(多图融合)★", "category": "image", "description": "1~9 张参考图(角色多角度 + 场景多机位 + 道具)+ 文本指令 → 融合成一张画面" " —— 关键帧创作的核心能力:角色与场景不再二选一", "input": {"prompt": "string", "ref_images": "list[file]"}, "output": {"image_path": "string"}, "params_schema": { "size": {"type": "enum", "options": ["1K", "2K", "4K"], "default": "2K", "label": "输出档"}, "n": {"type": "number", "min": 1, "max": 6, "default": 1, "label": "输出张数"}, "mode": {"type": "enum", "options": ["single", "sequential"], "default": "single", "label": "单张 / 顺序模式(一次多张且角色一致)"}, }, "providers": [ {"id": Q, "name": "千问AI平台·万相2.7 Pro(最多 9 图 + 顺序 12 张)", "api_key_ref": "aliyun.api_key", "model": "wan2.7-image-pro", "default": True}, {"id": Q, "name": "千问AI平台·Qwen-Image-2.1 Pro(最多 10 图 + 透明通道)", "api_key_ref": "aliyun.api_key", "model": "qwen-image-2.1-pro"}, {"id": "kling", "name": "可灵(主体1-4 + 场景 + 风格分离)", "api_key_ref": "kling.api_key", "model": "kling-v2-1"}, ], "defaults": {"provider": Q, "model": "wan2.7-image-pro", "mode": "single", "n": 1}, "execution": "remote", "status": "partial", "impl": "待接(capabilities/kling_video.submit_multi_image2image 已有可灵版)", "note": "关键帧创作的主力;可灵版接口已实现但从未接入流程", "docs": [{"source": Q, "slug": "developer-guides-image-generation-wan-image-editing", "title": "图像编辑 - 万相2.7/2.6/2.5(多图输入多图输出)"}, {"source": "kling", "slug": "multiImage2Image", "title": "可灵·多图参考生图"}], }, { "id": "image_edit", "name": "图像编辑(指令)", "category": "image", "description": "文本指令修改图片(换主体 / 换环境 / 改细节),保留其余部分", "input": {"prompt": "string", "ref_images": "list[file]"}, "output": {"image_path": "string"}, "params_schema": {"size": {"type": "enum", "options": ["1K", "2K"], "default": "2K", "label": "分辨率档"}}, "providers": [ {"id": Q, "name": "千问AI平台·Qwen-Image-Edit", "api_key_ref": "aliyun.api_key", "model": "qwen-image-3.0-pro", "default": True}, {"id": Q, "name": "千问AI平台·Wan 2.5 图像编辑", "api_key_ref": "aliyun.api_key", "model": "wan2.5-i2i-preview"}, ], "defaults": {"provider": Q, "model": "qwen-image-3.0-pro"}, "execution": "remote", "status": "ready", "impl": "待接(同步接口 ImageGeneration.call 支持 text+image 混排)", "docs": [{"source": Q, "slug": "developer-guides-image-generation-image-editing", "title": "图像编辑"}], }, { "id": "image_inpaint", "name": "局部重绘", "category": "image", "description": "给定原图 + 涂抹掩码 + 描述,只重绘指定区域", "input": {"prompt": "string", "image": "file", "mask": "file"}, "output": {"image_path": "string"}, "params_schema": {}, "providers": [{"id": Q, "name": "千问AI平台·wanx-x-painting", "api_key_ref": "aliyun.api_key", "model": "wanx-x-painting", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-image-inpainting", "title": "图像局部重绘"}], }, { "id": "image_outpaint", "name": "画面扩展(扩图)", "category": "image", "description": "按宽高比 / 比例 / 方向扩展画面,或旋转扩图", "input": {"image": "file"}, "output": {"image_path": "string"}, "params_schema": {"expand_ratio": {"type": "number", "default": 1.5, "label": "扩展比例"}}, "providers": [{"id": Q, "name": "千问AI平台·image-out-painting", "api_key_ref": "aliyun.api_key", "model": "image-out-painting", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-image-out-painting", "title": "图像画面扩展"}], }, { "id": "image_erase", "name": "擦除补全", "category": "image", "description": "保留背景的同时擦除指定区域(去杂物 / 去路人)", "input": {"image": "file", "mask": "file", "keep_mask": "file?"}, "output": {"image_path": "string"}, "params_schema": {}, "providers": [{"id": Q, "name": "千问AI平台·图像擦除补全", "api_key_ref": "aliyun.api_key", "model": "image-erase-completion", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-image-erase-completion", "title": "图像擦除补全"}], }, { "id": "image_style_transfer", "name": "图像风格迁移", "category": "image", "description": "把写实图转成指定美术风格(漫画 / 3D / 国风 / 纸艺 …)", "input": {"image": "file", "style": "string"}, "output": {"image_path": "string"}, "params_schema": {}, "providers": [ {"id": Q, "name": "千问AI平台·万相2.1 通用编辑(风格迁移)", "api_key_ref": "aliyun.api_key", "model": "wanx2.1-imageedit", "default": True}, {"id": Q, "name": "千问AI平台·Wan(背景生成/风格)", "api_key_ref": "aliyun.api_key", "model": "wan2.7-image-pro"}, ], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-wan21-image-editing", "title": "万相2.1 通用图像编辑(含风格迁移)"}], }, { "id": "sketch_to_image", "name": "涂鸦作画", "category": "image", "description": "手绘草图 + 文字描述 → 成品画面", "input": {"image": "file", "prompt": "string"}, "output": {"image_path": "string"}, "params_schema": {}, "providers": [{"id": Q, "name": "千问AI平台·涂鸦作画", "api_key_ref": "aliyun.api_key", "model": "wanx-sketch-to-image", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-sketch-to-image", "title": "涂鸦作画"}], }, { "id": "background_generate", "name": "背景生成", "category": "image", "description": "为商品/人物图生成高质量背景(文本引导 / 图像引导 / 边缘引导)", "input": {"image": "file", "prompt": "string"}, "output": {"image_path": "string"}, "params_schema": {}, "providers": [{"id": Q, "name": "千问AI平台·图像背景生成", "api_key_ref": "aliyun.api_key", "model": "wanx-background-generation", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-background-generation", "title": "图像背景生成"}], }, { "id": "virtual_model", "name": "虚拟模特 / 换装", "category": "image", "description": "保持真人站姿不变,替换背景与模特(服装试穿 / 模特图批量生成)", "input": {"image": "file", "template": "file"}, "output": {"image_path": "string"}, "params_schema": {}, "providers": [{"id": Q, "name": "千问AI平台·虚拟模特", "api_key_ref": "aliyun.api_key", "model": "wanx-virtualmodel", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-virtual-model", "title": "虚拟模特生成"}], }, { "id": "person_segment", "name": "人物分割(抠图)", "category": "image", "description": "识别图中人物并输出像素级掩码(抠人 / 合成前置)", "input": {"image": "file"}, "output": {"mask_path": "string"}, "params_schema": {}, "providers": [{"id": Q, "name": "千问AI平台·人物实例分割", "api_key_ref": "aliyun.api_key", "model": "person-instance-segmentation", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "developer-guides-image-generation-person-instance-segmentation", "title": "人物实例分割"}], }, ] # ── 视频类新增 ──────────────────────────────────────────── VIDEO_CAPS = [ { "id": "ref_to_video", "name": "参考驱动生视频(R2V)★", "category": "video", "description": "参考图/视频(最多 5 个混合)+ 文本 → 视频;可同时给角色图 + 场景图 + 首帧," "还可绑音色(reference_voice)", "input": {"prompt": "string", "media": "list[{type,url}]"}, "output": {"video_path": "string"}, "params_schema": { "resolution": {"type": "enum", "options": ["720P", "1080P"], "default": "720P", "label": "清晰度"}, "duration": {"type": "number", "min": 2, "max": 10, "default": 5, "label": "时长(秒)"}, "ratio": {"type": "enum", "options": ["16:9", "9:16", "1:1"], "default": "9:16", "label": "宽高比"}, }, "providers": [ {"id": Q, "name": "千问AI平台·Wan2.7-R2V(5 参考 + 音色)", "api_key_ref": "aliyun.api_key", "model": "wan2.7-r2v", "default": True}, {"id": Q, "name": "千问AI平台·Wan2.6-R2V-Flash(¥0.3/秒)", "api_key_ref": "aliyun.api_key", "model": "wan2.6-r2v-flash"}, ], "defaults": {"provider": Q, "model": "wan2.6-r2v-flash", "resolution": "720P", "duration": 5}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "api-reference-video-generation-wan27-image-to-video-create-task", "title": "Wan 2.7 参考生视频"}], }, { "id": "keyframe_video", "name": "首尾帧生视频(KF2V)★", "category": "video", "description": "首帧图 + 尾帧图 + 过渡描述 → 平滑过渡视频(固定 5 秒)—— 关键帧 → 视频的主力", "input": {"prompt": "string?", "first_frame_url": "file", "last_frame_url": "file"}, "output": {"video_path": "string"}, "params_schema": { "resolution": {"type": "enum", "options": ["480P", "720P", "1080P"], "default": "480P", "label": "清晰度"}, "duration": {"type": "number", "default": 5, "label": "时长(秒) · 固定 5"}, "template": {"type": "string", "default": "", "label": "特效模板(填了则只需首帧)"}, }, "providers": [ {"id": Q, "name": "千问AI平台·Wan2.2-KF2V-Flash(¥0.1/秒·480P)", "api_key_ref": "aliyun.api_key", "model": "wan2.2-kf2v-flash", "default": True}, {"id": Q, "name": "千问AI平台·Wan2.1-KF2V-Plus", "api_key_ref": "aliyun.api_key", "model": "wanx2.1-kf2v-plus"}, ], "defaults": {"provider": Q, "model": "wan2.2-kf2v-flash", "resolution": "480P"}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "api-reference-video-generation-wan-image-to-video-first-last-frames-create-task", "title": "Wan — 首尾帧图生视频"}], }, { "id": "video_style_transform", "name": "视频风格重绘", "category": "video", "description": "把视频转成 8 种艺术风格(日式漫画 / 美式漫画 / 清新漫画 / 3D卡通 / 国风 / 纸艺 / 简易插画 …)", "input": {"video_url": "file"}, "output": {"video_path": "string"}, "params_schema": { "style": {"type": "enum", "options": ["0 日式漫画", "1 美式漫画", "2 清新漫画", "3 3D卡通", "4 国风卡通", "5 纸艺风格", "6 简易插画", "7"], "default": "3 3D卡通", "label": "目标风格"}, "animate_emotion": {"type": "bool", "default": False, "label": "情绪表情迁移"}, "use_SR": {"type": "bool", "default": False, "label": "超分辨率增强"}, "min_len": {"type": "enum", "options": [720, 540], "default": 540, "label": "短边分辨率"}, }, "providers": [{"id": Q, "name": "千问AI平台·视频风格重绘", "api_key_ref": "aliyun.api_key", "model": "video-style-transform", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "api-reference-video-generation-video-style-transform-create-task", "title": "视频风格重绘"}], }, { "id": "video_edit", "name": "统一视频编辑", "category": "video", "description": "一个接口靠 function 切换五大能力:多图参考 / 视频重绘 / 局部编辑 / 视频续写 / 画面扩展", "input": {"function": "enum", "prompt": "string", "ref_images_url": "list[file]", "video_url": "file?", "mask_*": "any"}, "output": {"video_path": "string"}, "params_schema": { "function": {"type": "enum", "options": ["多图参考", "视频重绘", "局部编辑", "视频续写", "画面扩展"], "default": "局部编辑", "label": "编辑类型"}, "strength": {"type": "number", "default": 0.8, "label": "强度"}, }, "providers": [{"id": Q, "name": "千问AI平台·Wan 统一视频编辑", "api_key_ref": "aliyun.api_key", "model": "wan-video-editing", "default": True}], "defaults": {"provider": Q}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "openapi-wan-video-editing", "title": "Wan 统一视频编辑"}], }, { "id": "character_swap", "name": "视频换人(保留场景光线)", "category": "video", "description": "把我的角色图换到别人的视频里,保留原视频的场景、光线与色调", "input": {"image_url": "file", "video_url": "file"}, "output": {"video_path": "string"}, "params_schema": { "mode": {"type": "enum", "options": ["wan-std", "wan-pro"], "default": "wan-std", "label": "模式(std 快/便宜 · pro 专业)"}, "check_image": {"type": "bool", "default": True, "label": "图片合规检测"}, }, "providers": [{"id": Q, "name": "千问AI平台·视频换脸", "api_key_ref": "aliyun.api_key", "model": "wan-character-swap", "default": True}], "defaults": {"provider": Q, "mode": "wan-std"}, "execution": "remote", "status": "ready", "impl": "待接", "docs": [{"source": Q, "slug": "openapi-wan-character-swap", "title": "Wan 视频换脸"}], }, ] def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--dry", action="store_true", help="只打印,不写文件") args = ap.parse_args() raw = CFG.read_text(encoding="utf-8") d = yaml.safe_load(raw) cats = d.get("categories") or {} caps = d.get("capabilities") or [] existing = {c["id"] for c in caps} # 1) 分类:新顺序(图像在最前) d["categories"] = NEW_CATEGORIES # 2) 已有能力:把图像相关的归到 image MOVE = {"text_to_image", "person_swap"} for c in caps: if c["id"] in MOVE: c["category"] = "image" # 3) 追加新能力(已存在的先移除旧的同名,保持新版) add = [c for c in (IMG_CAPS + VIDEO_CAPS) if c["id"] not in existing or c["id"] == "text_to_image"] caps = [c for c in caps if c["id"] not in {x["id"] for x in add}] caps += add d["capabilities"] = caps d["updated"] = datetime.now().strftime("%Y-%m-%d") out = yaml.dump(d, allow_unicode=True, sort_keys=False, width=200) n_new = len([x for x in add if x["id"] not in existing]) print(f"分类: {len(NEW_CATEGORIES)} · 能力总数: {len(caps)}(新增 {n_new})") for c in add: print(f" + [{c['category']}] {c['id']:<24} {c['name']}") if args.dry: print("\n(--dry:未写入)") return 0 bak = CFG.with_suffix(f".yaml.bak_{datetime.now().strftime('%Y%m%d_%H%M%S')}") shutil.copy2(CFG, bak) CFG.write_text(out, encoding="utf-8") print(f"\n✅ 已写入 {CFG.name}(备份 {bak.name})") return 0 if __name__ == "__main__": raise SystemExit(main())