mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c76749ee5 | |||
| 5d1b7aac3a | |||
| 2389681ad6 | |||
| 946b7029c6 | |||
| b9ecd5c43b | |||
| 978f332fea | |||
| 4502424200 | |||
| 03839766bc | |||
| 5007b9b574 | |||
| 1f8b9ace7e | |||
| 8286a74fb6 | |||
| 9eb2acbb65 | |||
| 1d35326c86 | |||
| eb6c2b8e2a | |||
| ebd6226a9f | |||
| e25d3b0b8e | |||
| 3c64461cca | |||
| f30fff9065 | |||
| a7245c0f62 |
@@ -0,0 +1,27 @@
|
||||
# Poke the FC publish-skills flow after skills/ changes land.
|
||||
# The FC side reconciles this repo's skills/ directory against OSS
|
||||
# (bailian-wiki/skills/) using the repo HEAD snapshot as the only
|
||||
# source of truth — the request itself carries no content. Both the
|
||||
# repo and branch params are validated against FC-side whitelists
|
||||
# (PUBLISH_REPOS / PUBLISH_BRANCHES).
|
||||
#
|
||||
# feat/cli-skill-sync is temporary for end-to-end testing; remove it
|
||||
# (here and from the FC PUBLISH_BRANCHES whitelist) once the sync
|
||||
# link is verified on main.
|
||||
name: Publish skills to OSS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- feat/cli-skill-sync
|
||||
paths:
|
||||
- "skills/**"
|
||||
|
||||
jobs:
|
||||
poke:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger FC publish-skills
|
||||
run: |
|
||||
curl -sf -X POST "${{ vars.FC_TRIGGER_URL }}/publish-skills?repo=modelstudioai/cli&branch=${{ github.ref_name }}"
|
||||
@@ -13,8 +13,9 @@
|
||||
|
||||
---
|
||||
|
||||
_Chat with Qwen, generate images & videos, understand images, call agents,_
|
||||
_manage memory, search the web — all from your terminal._
|
||||
_Chat with Qwen, generate and edit images and videos, understand images, synthesize_
|
||||
_and recognize speech, call apps, manage memory, retrieve knowledge, search the web —_
|
||||
_every AI capability, one command away._
|
||||
|
||||
_Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
@@ -22,28 +23,16 @@ _Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
## Features
|
||||
|
||||
Equip your AI Agent out-of-the-box with these capabilities, composable across complex tasks:
|
||||
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
|
||||
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
|
||||
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
|
||||
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
|
||||
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
|
||||
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step
|
||||
|
||||
- **Text chat** — Qwen3.8-max: major gains in agentic coding, frontend coding, and vibe coding
|
||||
- **Multimodal (Omni)** — Full omni-modal support across text + image + audio + video
|
||||
- **Image generation & editing** — Qwen-Image 3.0: pro text rendering, photorealism, strong semantic adherence, multi-image composition
|
||||
- **Video generation & editing** — happyhorse-1.1 series: text-/image-/reference-to-video and natural-language video editing (up to 9-image reference)
|
||||
- **Speech synthesis & recognition** — CosyVoice streaming TTS, voice cloning from 5–20s samples; FunAudio-ASR covers 30 languages including 7 Chinese dialects and 20+ Mandarin accents
|
||||
- **Image & video understanding** — Qwen-VL: long-form video analysis, chart/document parsing, visual reasoning, multilingual OCR
|
||||
- **Coding agent setup** — Configure Claude Code, Qwen Code, OpenCode, OpenClaw, Hermes Agent, or Codex to use DashScope with `bl config agent`
|
||||
> **Note:** App orchestration, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
> **Note:** The features below are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
- **Knowledge base & memory** — Multimodal RAG retrieval and cross-session memory for personalized, coherent dialogue
|
||||
- **App calls** — Invoke agents and workflows already published on Aliyun Model Studio
|
||||
- **MCP integration** — Orchestrate Bailian MCP servers: list services, inspect tools, and invoke any tool directly from the terminal
|
||||
- **Web search** — Real-time internet retrieval for up-to-date, accurate answers
|
||||
- **Model recommendation** — Describe your scenario and get best-fit model suggestions; supports scoped search, model comparison, and alternative discovery
|
||||
- **Fine-tuning & deployment** — Upload datasets, create text/audio/image fine-tune jobs (`finetune text|audio|image create`; text covers SFT/LoRA/DPO/CPT), probe job status non-blockingly (`finetune watch`), query per-model training capability (`finetune capability`), and deploy trained models as endpoints (`deploy text|audio|image create`)
|
||||
- **Console capabilities** — Browse the model marketplace (`model list`) and Bailian apps (`app list`), review a unified usage view (`usage summary`), check free-tier quota (`usage free`), view model usage statistics (`usage stats`), manage workspaces (`workspace list`), and manage rate limits (`quota list/request/check/history`)
|
||||
- **Local file auto-upload** — Every URL parameter accepts a local path; uploaded to free temp storage with 48-hour validity
|
||||
|
||||
## Showcase: One-Sentence Cinematic Video
|
||||
## Showcase 1: A Cinematic Short Film from One Sentence
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -56,129 +45,77 @@ Equip your AI Agent out-of-the-box with these capabilities, composable across co
|
||||
A complete **2-minute, 16:9 cinematic short film** — produced end-to-end from a single natural-language sentence, with **zero manual editing**. This showcase demonstrates how an AI Agent can compose a multi-step creative pipeline by orchestrating three primitives:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — the agentic coding model that interprets the user's intent and drives the workflow
|
||||
- **[Aliyun Model Studio CLI](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** — handles scene decomposition, storyboarding, shot continuity, and final stitching
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Generate a roughly 2-minute video in Japanese cinematic style — a sweet, innocent first-love story about a high-school girl. The plot should be heart-fluttering enough to make viewers want to fall in love. Aspect ratio: 16:9."_
|
||||
>
|
||||
> _(Original: "帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2分钟左右的视频,尺寸是16:9")_
|
||||
|
||||
### How it works
|
||||
## Showcase 2: A Short-Film Director Managed Agent from One Sentence
|
||||
|
||||
1. **Qwen Code** parses the request, plans the narrative beats, and decides which tools to call.
|
||||
2. The **spark-video Skill** breaks the story into shots, writes per-shot prompts, and enforces visual continuity (characters, lighting, palette, lens language).
|
||||
3. **`bl video generate`** dispatches each shot to **HappyHorse 1.1** in parallel.
|
||||
4. The skill stitches all clips back together into a single 16:9 / ~2-min deliverable.
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="Click to play the demo video" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
No timeline scrubbing. No frame-by-frame editing. Just one sentence → one video.
|
||||
<p align="center"><i>👆 Click the cover to play the full demo</i></p>
|
||||
|
||||
One sentence builds a reusable cloud-side short-film director for storyboarding, storyboard image generation, and video creation:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — understands the requirement and generates the agent configuration
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — validates the configuration, previews the changes, and completes the deployment
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** — runs the director role along with its skills and tools in the cloud
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Build me a Managed Agent app that can produce short films — a director expert that generates videos and can also design the matching storyboards."_
|
||||
|
||||
## Installation
|
||||
|
||||
**Agent install (recommended)**
|
||||
|
||||
Send the following to your Agent — it will detect your environment, then install and verify the CLI for you:
|
||||
|
||||
```text
|
||||
Please read https://bailian.aliyun.com/cli/install.md and install the Aliyun Model Studio CLI for me
|
||||
```
|
||||
|
||||
**Manual install (npm)**
|
||||
|
||||
```bash
|
||||
# Recommended — no Node required
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows (PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node users / developers (Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> Binary install does not require Node.js. `npm install -g` remains fully supported.
|
||||
> Requires Node.js >= 18.17.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Authenticate, recommended
|
||||
bl auth login --console
|
||||
Once installed, just describe your task to your AI Agent — no need to assemble commands by hand.
|
||||
|
||||
# Or authenticate with an API key
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Or use Token Plan (Base URL built in; the key is tested during login)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# Configure a coding agent to use DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# Chat with Qwen
|
||||
bl text chat --message "What is DashScope?"
|
||||
|
||||
# Multimodal chat (text + image + audio + video)
|
||||
bl omni --message "Describe this image" --image ./photo.jpg
|
||||
|
||||
# Generate an image
|
||||
bl image generate --prompt "A cat in a spacesuit" --out-dir ./images/
|
||||
|
||||
# Generate a video from local image
|
||||
bl video generate --image ./cat.png --prompt "Make the cat move" --download cat.mp4
|
||||
|
||||
# Model recommendation — find the best model for your use case
|
||||
bl advisor recommend --message "I need a visual-understanding chatbot"
|
||||
|
||||
# Compare specific models
|
||||
bl advisor recommend --message "qwen-max vs deepseek-v3 for code generation"
|
||||
|
||||
# Browser login (required for console capability commands)
|
||||
bl auth login --console
|
||||
|
||||
# Fine-tune & deploy — a one-shot train-to-serve workflow
|
||||
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
|
||||
bl finetune watch --job-id ft-xxx --output json # Non-blocking probe (running/succeeded return 0; failed/canceled report an error)
|
||||
bl finetune capability --model qwen3-8b # Which training types a model supports
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
|
||||
|
||||
# Browse models / apps / free-tier quota / usage statistics / workspaces
|
||||
bl model list # Browse model families and pricing
|
||||
bl app list
|
||||
bl usage summary # Unified view: free-tier quota + recent usage overview
|
||||
bl usage free # Free-tier quota across models (add --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # Model usage statistics (add --model for per-model)
|
||||
bl workspace list # List all workspaces
|
||||
|
||||
# Rate limit management (list / check / request / history)
|
||||
bl quota list # View RPM/TPM limits (add --model to filter)
|
||||
bl quota check # Current usage vs rate limits (add --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # Request a temporary TPM increase
|
||||
bl quota history # View quota-change history
|
||||
|
||||
# Token Plan team management (requires AK/SK, see auth below)
|
||||
bl token-plan list-seats # View subscription seat details
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| Scenario | What to say to your Agent |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- |
|
||||
| Managed Agent | "Create a Managed Agent that can generate short-film storyboards and videos." |
|
||||
| Image & video generation | "Generate an image of a cat in a spacesuit on Mars, then turn it into a video." |
|
||||
| Usage & quota | "Show my recent model usage, free-tier quota, and rate limits." |
|
||||
| Model selection | "Recommend a model for image understanding and customer support." |
|
||||
| About Bailian CLI | "Tell me what Bailian CLI can do for me, and suggest how to use it for my needs." |
|
||||
|
||||
> More examples and scenarios: [Aliyun Model Studio CLI Site](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## Authentication
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
Required for most commands. Get your key from the [DashScope Console](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key).
|
||||
|
||||
```bash
|
||||
# Option 1: Environment variable
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# Option 2: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Option 3: Per-command flag
|
||||
bl text chat --api-key sk-xxxxx --message "Hello"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
Get or copy the API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
The CLI has the default Token Plan Base URL built in. Login tests the key first, then saves and activates the `token-plan` config only when validation succeeds.
|
||||
Get or copy your Token Plan API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -186,26 +123,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### Console Login (OAuth)
|
||||
|
||||
Required for console capability commands (`model list`, `app list`, `usage summary/free/stats`, `workspace list`, `quota list/request/check/history`). Opens the Bailian console in your browser to sign in.
|
||||
Required for console capability commands (model list, app list, MCP list, workspace, usage queries, rate-limit increases, direct console calls). Opens the Bailian console in your browser to sign in.
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### Alibaba Cloud OpenAPI AK/SK (Token Plan only)
|
||||
### Alibaba Cloud OpenAPI AK/SK
|
||||
|
||||
Required for the `token-plan` command group. Get your AccessKey from [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
Token Plan seat and member management requires an Alibaba Cloud AccessKey. Get yours from the [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
|
||||
> Recommended: create a RAM sub-account with minimum privileges instead of using the root account's AK/SK.
|
||||
|
||||
```bash
|
||||
# Option 1: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# Option 2: Environment variables
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -214,18 +145,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# View current config
|
||||
bl config show
|
||||
|
||||
# Set defaults
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# List all config profiles
|
||||
bl config list
|
||||
|
||||
# Self-update to latest or a specific version
|
||||
bl update
|
||||
bl update --to 0.1.14
|
||||
# Switch config profile
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
Config file location: `~/.bailian/config.json`
|
||||
|
||||
## Update
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
Upgrades the CLI to the latest version and refreshes the installed Agent Skills. Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
Scan the QR code to join the Aliyun Model Studio CLI DingTalk user group for usage help, troubleshooting, bug reports, and tips from other users.
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="Aliyun Model Studio CLI DingTalk user group" width="240" />
|
||||
|
||||
## Links
|
||||
|
||||
| Resource | URL |
|
||||
@@ -237,11 +181,3 @@ Config file location: `~/.bailian/config.json`
|
||||
| Get API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| Get Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| Get AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## Changelog
|
||||
|
||||
Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
+73
-138
@@ -22,28 +22,16 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
## 功能特性
|
||||
|
||||
让您的 AI Agent 开箱即具备以下能力,并可在复杂任务中自动组合调用:
|
||||
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
|
||||
- **素材理解** — 图像、文档、音频、长视频的解析与问答
|
||||
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
|
||||
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
|
||||
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
|
||||
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent
|
||||
|
||||
- **文本对话** — Qwen3.8-max:Agentic coding、前端编程、Vibe coding 等能力显著增强
|
||||
- **全模态对话** — 文本 + 图像 + 音频 + 视频全模态支持
|
||||
- **图像生成与编辑** — Qwen-Image 3.0:专业文字渲染、真实质感、强语义遵循、多图合成
|
||||
- **视频生成与编辑** — happyhorse-1.1 系列,支持文生 / 图生 / 参考生(最多 9 张图参考)/ 自然语言视频编辑
|
||||
- **语音合成与识别** — CosyVoice 实时流式合成,5-20s 样本即可克隆;FunAudio-ASR 覆盖 30 种语种,含汉语七大方言与 20+ 口音官话
|
||||
- **图像与视频理解** — Qwen-VL:长视频解析、复杂图表与文档识别、视觉推理、多语种 OCR
|
||||
- **Coding Agent 配置** — 使用 `bl config agent` 将 Claude Code、Qwen Code、OpenCode、OpenClaw、Hermes Agent 或 Codex 配置为使用 DashScope
|
||||
> **注意:** 应用编排、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。
|
||||
|
||||
> **注意:** 以下功能目前仅对中国站(aliyun.com)账号开放,国际站 / 全球站账号暂不支持。
|
||||
|
||||
- **知识库与记忆库** — 多模态 RAG 检索 + 跨会话记忆,提供个性化连贯对话体验
|
||||
- **应用调用** — 调用已发布在阿里云百炼平台上的智能体与工作流应用
|
||||
- **MCP 集成** — 统一调度百炼 MCP 服务:列出服务、查看工具、直接在终端调用任意工具
|
||||
- **联网搜索** — 实时互联网信息检索,提升回答准确性及时效性
|
||||
- **模型推荐** — 描述你的场景,智能推荐最适合的模型;支持限定范围搜索、模型对比和替代发现
|
||||
- **微调与部署** — 上传数据集、创建文本/音频/图像调优任务(`finetune text|audio|image create`;文本涵盖 SFT/LoRA/DPO/CPT)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy text|audio|image create`)
|
||||
- **控制台能力** — 浏览模型市场(`model list`)和百炼应用(`app list`),查看统一用量视图(`usage summary`),查询模型免费额度(`usage free`),查看模型用量统计(`usage stats`),管理业务空间(`workspace list`),管理限流与提额(`quota list/request/check/history`)
|
||||
- **本地文件自动上传** — 所有 URL 参数同时支持本地路径,免费临时存储 48 小时
|
||||
|
||||
## 示例:一句话生成一部电影短片
|
||||
## 示例 1:一句话生成一部电影短片
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -53,130 +41,80 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
<p align="center"><i>👆 点击封面播放完整 2 分钟演示</i></p>
|
||||
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** —— 负责场景拆分、分镜设计、镜头连贯性和最终拼接
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _"帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9"_
|
||||
> _“帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9。”_
|
||||
|
||||
### 工作流程
|
||||
## 示例 2:一句话构建短片导演 Managed Agent
|
||||
|
||||
1. **Qwen Code** 解析需求、规划叙事节奏,决定要调用哪些工具。
|
||||
2. **spark-video Skill** 把故事拆成镜头、为每个镜头写提示词,并保证视觉连贯性(角色、光线、色调、镜头语言)。
|
||||
3. **`bl video generate`** 把每个镜头并行下发给 **HappyHorse 1.1**。
|
||||
4. Skill 把所有片段拼成最终的 16:9 / 约 2 分钟成片。
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="点击播放演示视频" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
没有时间线拖拽,没有逐帧剪辑。一句话 → 一部短片。
|
||||
<p align="center"><i>👆 点击封面播放完整演示</i></p>
|
||||
|
||||
一句话构建一个可复用的云端短片导演,用于分镜设计、分镜图生成和视频创作:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— 理解需求并生成 Agent 配置
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 校验配置、预览变更并完成部署
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** —— 在云端运行导演角色及其 Skill 和工具
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _“帮我构建一个 managedagent 应用,能够实现短片拍摄,导演专家生成视频,然后也能进行设计对应的分镜图。”_
|
||||
|
||||
## 安装
|
||||
|
||||
**Agent 安装(推荐)**
|
||||
|
||||
把下面这句话发给你的 Agent,它会自行判断环境并完成安装与校验:
|
||||
|
||||
```text
|
||||
请阅读:https://bailian.aliyun.com/cli/install.md 并按照说明为我安装阿里云百炼 CLI
|
||||
```
|
||||
|
||||
**手动安装(npm)**
|
||||
|
||||
```bash
|
||||
# 推荐 — 无需本机 Node.js
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows(PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node 用户 / 开发者(需要 Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> 二进制安装不依赖 Node.js。`npm install -g` 长期保留。
|
||||
> 需要预先安装 Node.js >= 18.17。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 认证(推荐浏览器登录)
|
||||
bl auth login --console
|
||||
安装完成后,直接在 AI Agent 中描述你的任务,无需手动拼接命令。
|
||||
|
||||
# 或使用 API key 认证
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 或使用 Token Plan(已内置 Base URL,登录时自动测试 Key)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# 配置 Coding Agent 使用 DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# 和通义千问对话
|
||||
bl text chat --message "你好,介绍一下阿里云百炼平台"
|
||||
|
||||
# 多模态对话(文本 + 图片 + 音频 + 视频)
|
||||
bl omni --message "描述这张图片" --image ./photo.jpg
|
||||
|
||||
# 生成图片
|
||||
bl image generate --prompt "一只穿太空服的猫在火星上" --out-dir ./images/
|
||||
|
||||
# 图生视频(本地文件自动上传)
|
||||
bl video generate --image ./cat.png --prompt "让画面中的猫动起来" --download cat.mp4
|
||||
|
||||
# 模型推荐 — 根据场景推荐最适合的模型
|
||||
bl advisor recommend --message "我要做一个能理解图片的客服机器人"
|
||||
|
||||
# 对比特定模型
|
||||
bl advisor recommend --message "qwen-max 和 deepseek-v3 哪个更适合做代码生成"
|
||||
|
||||
# 浏览器登录(控制台能力相关命令需要)
|
||||
bl auth login --console
|
||||
|
||||
# 微调与部署 — 从训练到服务的一站式流程
|
||||
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
|
||||
bl finetune watch --job-id ft-xxx --output json # 非阻塞探测(运行中/成功返回 0;失败/取消报错)
|
||||
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
|
||||
|
||||
# 浏览模型 / 应用 / 免费额度 / 用量统计 / 业务空间
|
||||
bl model list # 浏览模型系列与价格信息
|
||||
bl app list
|
||||
bl usage summary # 统一视图:免费额度 + 近期用量概览
|
||||
bl usage free # 各模型免费额度(可加 --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # 模型用量统计(加 --model 查单模型)
|
||||
bl workspace list # 列出所有业务空间
|
||||
|
||||
# 限流管理与提额(list / check / request / history)
|
||||
bl quota list # 查看 RPM/TPM 限额(加 --model 过滤)
|
||||
bl quota check # 当前用量 vs 限流阈值(加 --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # 申请临时 TPM 提额
|
||||
bl quota history # 查看提额历史记录
|
||||
|
||||
# Token Plan 团队版管理(需 AK/SK,见下方认证说明)
|
||||
bl token-plan list-seats # 查看订阅席位明细
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| 场景 | 可以这样对 Agent 说 |
|
||||
| ---------------- | ----------------------------------------------------------------------- |
|
||||
| Managed Agent | “帮我创建一个能够生成短片分镜和视频的 Managed Agent。” |
|
||||
| 图片和视频生成 | “生成一张穿着太空服的猫站在火星上的图片,再把它制作成一段视频。” |
|
||||
| 用量与额度 | “查看最近的模型用量、免费额度和限流情况。” |
|
||||
| 模型选型 | “推荐一个适合图片理解和智能客服的模型。” |
|
||||
| 了解 Bailian CLI | “介绍一下 Bailian CLI 能帮我完成哪些任务,并根据我的需求推荐使用方式。” |
|
||||
|
||||
> 更多案例与使用场景:[阿里云百炼 CLI 官方主页](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## 认证方式
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
大部分命令均需要 API Key。前往 [DashScope 控制台](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key) 获取。
|
||||
|
||||
```bash
|
||||
# 方式一:环境变量
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# 方式二:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 方式三:命令行参数
|
||||
bl text chat --api-key sk-xxxxx --message "你好"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制 API Key。
|
||||
CLI 已内置 Token Plan 的默认 Base URL;登录命令会先测试 Key,通过后才保存并激活 `token-plan` 配置。
|
||||
Token Plan 的 API Key 前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制。
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -184,26 +122,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### 控制台登录(OAuth)
|
||||
|
||||
控制台能力命令(`model list`、`app list`、`usage summary/free/stats`、`workspace list`、`quota list/request/check/history`)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### 阿里云 OpenAPI AK/SK(仅 Token Plan)
|
||||
### 阿里云 OpenAPI AK/SK
|
||||
|
||||
`token-plan` 命令组需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
Token Plan 的席位与成员管理需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
|
||||
> 建议:创建 RAM 子账号并授予最小权限,避免使用主账号 AK/SK。
|
||||
|
||||
```bash
|
||||
# 方式一:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# 方式二:环境变量
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## 配置
|
||||
@@ -212,20 +144,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# 查看当前配置
|
||||
bl config show
|
||||
|
||||
# 设置默认值
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# 查看全部配置档
|
||||
bl config list
|
||||
|
||||
# 自更新到最新版本
|
||||
bl update
|
||||
|
||||
# 安装指定版本
|
||||
bl update --to 0.1.14
|
||||
# 切换配置档
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
配置文件位置:`~/.bailian/config.json`
|
||||
|
||||
## 更新
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
升级 CLI 至最新版本,并同步更新已安装的 Agent Skills。每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
欢迎扫码加入阿里云百炼 CLI 钉钉用户交流群,获取使用答疑、问题排查、Bug 反馈和使用经验交流支持。
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="阿里云百炼 CLI 钉钉用户交流群" width="240" />
|
||||
|
||||
## 相关链接
|
||||
|
||||
| 资源 | 地址 |
|
||||
@@ -237,11 +180,3 @@ bl update --to 0.1.14
|
||||
| 获取 API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| 获取 Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| 获取 AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## 更新日志
|
||||
|
||||
每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
@@ -25,7 +25,7 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
当前 command 鉴权域(`AuthRequirement`):
|
||||
|
||||
- `apiKey` — DashScope / OpenAI-compatible 模型域,用 API key 与 model base URL
|
||||
- `console` — Bailian Console Gateway,用 console access token + region/site/switchAgent/workspace
|
||||
- `console` — Bailian Console Gateway,用 console access token + region/site/switchAgent;`workspace_id` 是独立的 Settings 作用域,不属于 credential
|
||||
- `openapi` — 阿里云 OpenAPI 签名域,用 AccessKey ID/Secret 调用 Token Plan 等 OpenAPI
|
||||
- `none` — 本地命令、登录/配置类命令、无需 credential 的命令
|
||||
|
||||
@@ -35,7 +35,7 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
|
||||
- `bl auth login --api-key ...` 只更新 `api_key` / `base_url`
|
||||
- `bl auth login --console` 只更新 `access_token` 以及回调携带的 console 作用域字段
|
||||
- `bl auth login --open-api ...` 只更新 `access_key_id` / `access_key_secret`
|
||||
- `bl auth login --open-api ...` 更新 `access_key_id` / `access_key_secret`,同时会调用 OpenAPI 生成 CLI `access_token` 并一并写入;即一次 `--open-api` 登录同时产生 `openapi` 与 `console` 域凭证
|
||||
- `bl auth logout --console` 只清 `access_token`
|
||||
- `bl auth logout --open-api` 只清 `access_key_id` / `access_key_secret` / `security_token`
|
||||
- `bl auth logout` 清 `api_key` + `base_url` + `access_token` + `access_key_*`
|
||||
@@ -78,6 +78,9 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
- 如新增鉴权域,扩展 `AuthRequirement`
|
||||
- 更新 `credentialFlagDefs()` 暴露该域可见的 flag
|
||||
- 必要时新增 `*_AUTH_FLAGS`
|
||||
- `workspace_id` 是作用域字段而非 credential,不要把它放进 `ConsoleCredential`;读取方式按命令 `auth` 域区分:
|
||||
- `auth: "console"` 命令通过 `CONSOLE_AUTH_FLAGS` 自动获得 `--workspace-id`,由 `buildSettings()` 解析到 `settings.workspaceId`,命令统一从 `settings.workspaceId` 读取
|
||||
- `auth: "apiKey"`/`"openapi"`/`"none"` 命令如需 `--workspace-id`,必须自声明 flag;因它不会进入 credential/global flags,命令从 `ctx.flags.workspaceId` 读取(可回退到 `settings.workspaceId`)
|
||||
- [ ] `packages/core/src/auth/types.ts`:
|
||||
- 新增 credential 类型 / source / scope 字段
|
||||
- [ ] `packages/core/src/auth/resolver.ts`:
|
||||
@@ -131,6 +134,8 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
|
||||
|
||||
## 完成后自查
|
||||
|
||||
本仓库同时存在 `bl`(packages/cli) 与 `kscli`(packages/kscli) 两个入口,二者共享 core/runtime 鉴权链路,但暴露的命令不同。如果改动会影响两个入口共用的命令或错误提示,再分别验证它们各自实际暴露的路径;不要假设 `kscli` 也有 `bl auth *` 命令。
|
||||
|
||||
```sh
|
||||
# 各种凭证组合
|
||||
unset DASHSCOPE_API_KEY ALIBABA_CLOUD_ACCESS_KEY_ID ALIBABA_CLOUD_ACCESS_KEY_SECRET
|
||||
@@ -150,9 +155,11 @@ Console 登录/网关相关改动:
|
||||
|
||||
```sh
|
||||
pnpm -F bailian-cli exec tsx src/main.ts auth login --console
|
||||
pnpm -F bailian-cli exec tsx src/main.ts usage stats --dry-run --output json
|
||||
pnpm -F bailian-cli exec tsx src/main.ts usage stats --dry-run --output json --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
注意:`usage stats --dry-run` 仍会先校验 workspace,必须传入 `--workspace-id`(或 `BAILIAN_WORKSPACE_ID` / config `workspace_id`)。
|
||||
|
||||
## 常见漏点
|
||||
|
||||
- ✗ 加了新 token 来源但忘了改 resolver 优先级,实际不生效
|
||||
|
||||
+73
-137
@@ -13,8 +13,9 @@
|
||||
|
||||
---
|
||||
|
||||
_Chat with Qwen, generate images & videos, understand images, call agents,_
|
||||
_manage memory, search the web — all from your terminal._
|
||||
_Chat with Qwen, generate and edit images and videos, understand images, synthesize_
|
||||
_and recognize speech, call apps, manage memory, retrieve knowledge, search the web —_
|
||||
_every AI capability, one command away._
|
||||
|
||||
_Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
@@ -22,28 +23,16 @@ _Built for AI Agents. Every command works as a structured tool call._
|
||||
|
||||
## Features
|
||||
|
||||
Equip your AI Agent out-of-the-box with these capabilities, composable across complex tasks:
|
||||
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
|
||||
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
|
||||
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools
|
||||
- **Training & deployment** — Validate and upload datasets, fine-tune models, deploy dedicated models as endpoints
|
||||
- **Account operations** — Login, UI-based configuration, model marketplace, usage and quota, rate-limit increases, team seat management
|
||||
- **Plan onboarding** — Connect subscription plans such as Token Plan to the CLI and common coding agents in one step
|
||||
|
||||
- **Text chat** — Qwen3.8-max: major gains in agentic coding, frontend coding, and vibe coding
|
||||
- **Multimodal (Omni)** — Full omni-modal support across text + image + audio + video
|
||||
- **Image generation & editing** — Qwen-Image 3.0: pro text rendering, photorealism, strong semantic adherence, multi-image composition
|
||||
- **Video generation & editing** — happyhorse-1.1 series: text-/image-/reference-to-video and natural-language video editing (up to 9-image reference)
|
||||
- **Speech synthesis & recognition** — CosyVoice streaming TTS, voice cloning from 5–20s samples; FunAudio-ASR covers 30 languages including 7 Chinese dialects and 20+ Mandarin accents
|
||||
- **Image & video understanding** — Qwen-VL: long-form video analysis, chart/document parsing, visual reasoning, multilingual OCR
|
||||
- **Coding agent setup** — Configure Claude Code, Qwen Code, OpenCode, OpenClaw, Hermes Agent, or Codex to use DashScope with `bl config agent`
|
||||
> **Note:** App orchestration, training & deployment, account operations, and plan onboarding are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
> **Note:** The features below are currently available only to China site (aliyun.com) account holders and are not yet supported for international / global site accounts.
|
||||
|
||||
- **Knowledge base & memory** — Multimodal RAG retrieval and cross-session memory for personalized, coherent dialogue
|
||||
- **App calls** — Invoke agents and workflows already published on Aliyun Model Studio
|
||||
- **MCP integration** — Orchestrate Bailian MCP servers: list services, inspect tools, and invoke any tool directly from the terminal
|
||||
- **Web search** — Real-time internet retrieval for up-to-date, accurate answers
|
||||
- **Model recommendation** — Describe your scenario and get best-fit model suggestions; supports scoped search, model comparison, and alternative discovery
|
||||
- **Fine-tuning & deployment** — Upload datasets, create text/audio/image fine-tune jobs (`finetune text|audio|image create`; text covers SFT/LoRA/DPO/CPT), probe job status non-blockingly (`finetune watch`), query per-model training capability (`finetune capability`), and deploy trained models as endpoints (`deploy text|audio|image create`)
|
||||
- **Console capabilities** — Browse the model marketplace (`model list`) and Bailian apps (`app list`), review a unified usage view (`usage summary`), check free-tier quota (`usage free`), view model usage statistics (`usage stats`), manage workspaces (`workspace list`), and manage rate limits (`quota list/request/check/history`)
|
||||
- **Local file auto-upload** — Every URL parameter accepts a local path; uploaded to free temp storage with 48-hour validity
|
||||
|
||||
## Showcase: One-Sentence Cinematic Video
|
||||
## Showcase 1: A Cinematic Short Film from One Sentence
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -56,129 +45,77 @@ Equip your AI Agent out-of-the-box with these capabilities, composable across co
|
||||
A complete **2-minute, 16:9 cinematic short film** — produced end-to-end from a single natural-language sentence, with **zero manual editing**. This showcase demonstrates how an AI Agent can compose a multi-step creative pipeline by orchestrating three primitives:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — the agentic coding model that interprets the user's intent and drives the workflow
|
||||
- **[Aliyun Model Studio CLI](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — invokes **HappyHorse 1.1**, Aliyun Model Studio's text-/image-/reference-to-video generation model
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** — handles scene decomposition, storyboarding, shot continuity, and final stitching
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Generate a roughly 2-minute video in Japanese cinematic style — a sweet, innocent first-love story about a high-school girl. The plot should be heart-fluttering enough to make viewers want to fall in love. Aspect ratio: 16:9."_
|
||||
>
|
||||
> _(Original: "帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2分钟左右的视频,尺寸是16:9")_
|
||||
|
||||
### How it works
|
||||
## Showcase 2: A Short-Film Director Managed Agent from One Sentence
|
||||
|
||||
1. **Qwen Code** parses the request, plans the narrative beats, and decides which tools to call.
|
||||
2. The **spark-video Skill** breaks the story into shots, writes per-shot prompts, and enforces visual continuity (characters, lighting, palette, lens language).
|
||||
3. **`bl video generate`** dispatches each shot to **HappyHorse 1.1** in parallel.
|
||||
4. The skill stitches all clips back together into a single 16:9 / ~2-min deliverable.
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="Click to play the demo video" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
No timeline scrubbing. No frame-by-frame editing. Just one sentence → one video.
|
||||
<p align="center"><i>👆 Click the cover to play the full demo</i></p>
|
||||
|
||||
One sentence builds a reusable cloud-side short-film director for storyboarding, storyboard image generation, and video creation:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** — understands the requirement and generates the agent configuration
|
||||
- **[Aliyun Model Studio CLI](https://github.com/modelstudioai/cli/)** — validates the configuration, previews the changes, and completes the deployment
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** — runs the director role along with its skills and tools in the cloud
|
||||
|
||||
### The single prompt
|
||||
|
||||
> _"Build me a Managed Agent app that can produce short films — a director expert that generates videos and can also design the matching storyboards."_
|
||||
|
||||
## Installation
|
||||
|
||||
**Agent install (recommended)**
|
||||
|
||||
Send the following to your Agent — it will detect your environment, then install and verify the CLI for you:
|
||||
|
||||
```text
|
||||
Please read https://bailian.aliyun.com/cli/install.md and install the Aliyun Model Studio CLI for me
|
||||
```
|
||||
|
||||
**Manual install (npm)**
|
||||
|
||||
```bash
|
||||
# Recommended — no Node required
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows (PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node users / developers (Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> Binary install does not require Node.js. `npm install -g` remains fully supported.
|
||||
> Requires Node.js >= 18.17.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Authenticate, recommended
|
||||
bl auth login --console
|
||||
Once installed, just describe your task to your AI Agent — no need to assemble commands by hand.
|
||||
|
||||
# Or authenticate with an API key
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Or use Token Plan (Base URL built in; the key is tested during login)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# Configure a coding agent to use DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# Chat with Qwen
|
||||
bl text chat --message "What is DashScope?"
|
||||
|
||||
# Multimodal chat (text + image + audio + video)
|
||||
bl omni --message "Describe this image" --image ./photo.jpg
|
||||
|
||||
# Generate an image
|
||||
bl image generate --prompt "A cat in a spacesuit" --out-dir ./images/
|
||||
|
||||
# Generate a video from local image
|
||||
bl video generate --image ./cat.png --prompt "Make the cat move" --download cat.mp4
|
||||
|
||||
# Model recommendation — find the best model for your use case
|
||||
bl advisor recommend --message "I need a visual-understanding chatbot"
|
||||
|
||||
# Compare specific models
|
||||
bl advisor recommend --message "qwen-max vs deepseek-v3 for code generation"
|
||||
|
||||
# Browser login (required for console capability commands)
|
||||
bl auth login --console
|
||||
|
||||
# Fine-tune & deploy — a one-shot train-to-serve workflow
|
||||
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
|
||||
bl finetune watch --job-id ft-xxx --output json # Non-blocking probe (running/succeeded return 0; failed/canceled report an error)
|
||||
bl finetune capability --model qwen3-8b # Which training types a model supports
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
|
||||
|
||||
# Browse models / apps / free-tier quota / usage statistics / workspaces
|
||||
bl model list # Browse model families and pricing
|
||||
bl app list
|
||||
bl usage summary # Unified view: free-tier quota + recent usage overview
|
||||
bl usage free # Free-tier quota across models (add --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # Model usage statistics (add --model for per-model)
|
||||
bl workspace list # List all workspaces
|
||||
|
||||
# Rate limit management (list / check / request / history)
|
||||
bl quota list # View RPM/TPM limits (add --model to filter)
|
||||
bl quota check # Current usage vs rate limits (add --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # Request a temporary TPM increase
|
||||
bl quota history # View quota-change history
|
||||
|
||||
# Token Plan team management (requires AK/SK, see auth below)
|
||||
bl token-plan list-seats # View subscription seat details
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| Scenario | What to say to your Agent |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- |
|
||||
| Managed Agent | "Create a Managed Agent that can generate short-film storyboards and videos." |
|
||||
| Image & video generation | "Generate an image of a cat in a spacesuit on Mars, then turn it into a video." |
|
||||
| Usage & quota | "Show my recent model usage, free-tier quota, and rate limits." |
|
||||
| Model selection | "Recommend a model for image understanding and customer support." |
|
||||
| About Bailian CLI | "Tell me what Bailian CLI can do for me, and suggest how to use it for my needs." |
|
||||
|
||||
> More examples and scenarios: [Aliyun Model Studio CLI Site](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## Authentication
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
Required for most commands. Get your key from the [DashScope Console](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key).
|
||||
|
||||
```bash
|
||||
# Option 1: Environment variable
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# Option 2: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# Option 3: Per-command flag
|
||||
bl text chat --api-key sk-xxxxx --message "Hello"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
Get or copy the API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
The CLI has the default Token Plan Base URL built in. Login tests the key first, then saves and activates the `token-plan` config only when validation succeeds.
|
||||
Get or copy your Token Plan API key from the [Token Plan subscription overview](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview).
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -186,26 +123,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### Console Login (OAuth)
|
||||
|
||||
Required for console capability commands (`model list`, `app list`, `usage summary/free/stats`, `workspace list`, `quota list/request/check/history`). Opens the Bailian console in your browser to sign in.
|
||||
Required for console capability commands (model list, app list, MCP list, workspace, usage queries, rate-limit increases, direct console calls). Opens the Bailian console in your browser to sign in.
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### Alibaba Cloud OpenAPI AK/SK (Token Plan only)
|
||||
### Alibaba Cloud OpenAPI AK/SK
|
||||
|
||||
Required for the `token-plan` command group. Get your AccessKey from [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
Token Plan seat and member management requires an Alibaba Cloud AccessKey. Get yours from the [RAM Console](https://ram.console.aliyun.com/manage/ak).
|
||||
|
||||
> Recommended: create a RAM sub-account with minimum privileges instead of using the root account's AK/SK.
|
||||
|
||||
```bash
|
||||
# Option 1: Login command (persisted to ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# Option 2: Environment variables
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -214,18 +145,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# View current config
|
||||
bl config show
|
||||
|
||||
# Set defaults
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# List all config profiles
|
||||
bl config list
|
||||
|
||||
# Self-update to latest or a specific version
|
||||
bl update
|
||||
bl update --to 0.1.14
|
||||
# Switch config profile
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
Config file location: `~/.bailian/config.json`
|
||||
|
||||
## Update
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
Upgrades the CLI to the latest version and refreshes the installed Agent Skills. Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
Scan the QR code to join the Aliyun Model Studio CLI DingTalk user group for usage help, troubleshooting, bug reports, and tips from other users.
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="Aliyun Model Studio CLI DingTalk user group" width="240" />
|
||||
|
||||
## Links
|
||||
|
||||
| Resource | URL |
|
||||
@@ -237,11 +181,3 @@ Config file location: `~/.bailian/config.json`
|
||||
| Get API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| Get Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| Get AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## Changelog
|
||||
|
||||
Release notes for every version live in [CHANGELOG.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and PRs are welcome. See [CONTRIBUTING.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.md) for developer setup, repo layout, and the workflow for adding or changing commands.
|
||||
|
||||
+73
-138
@@ -22,28 +22,16 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
## 功能特性
|
||||
|
||||
让您的 AI Agent 开箱即具备以下能力,并可在复杂任务中自动组合调用:
|
||||
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
|
||||
- **素材理解** — 图像、文档、音频、长视频的解析与问答
|
||||
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具
|
||||
- **模型训推** — 数据集校验上传、模型精调、专属模型部署上线
|
||||
- **账号运维** — 授权登录、界面化配置、模型市场、用量与额度、限流提额、团队席位管理
|
||||
- **套餐接入** — 支持 Token Plan 等订阅计划一键接到 CLI 和常见 Coding Agent
|
||||
|
||||
- **文本对话** — Qwen3.8-max:Agentic coding、前端编程、Vibe coding 等能力显著增强
|
||||
- **全模态对话** — 文本 + 图像 + 音频 + 视频全模态支持
|
||||
- **图像生成与编辑** — Qwen-Image 3.0:专业文字渲染、真实质感、强语义遵循、多图合成
|
||||
- **视频生成与编辑** — happyhorse-1.1 系列,支持文生 / 图生 / 参考生(最多 9 张图参考)/ 自然语言视频编辑
|
||||
- **语音合成与识别** — CosyVoice 实时流式合成,5-20s 样本即可克隆;FunAudio-ASR 覆盖 30 种语种,含汉语七大方言与 20+ 口音官话
|
||||
- **图像与视频理解** — Qwen-VL:长视频解析、复杂图表与文档识别、视觉推理、多语种 OCR
|
||||
- **Coding Agent 配置** — 使用 `bl config agent` 将 Claude Code、Qwen Code、OpenCode、OpenClaw、Hermes Agent 或 Codex 配置为使用 DashScope
|
||||
> **注意:** 应用编排、模型训推、账号运维和套餐接入目前仅支持中国站(aliyun.com)账号,暂不支持国际站 / 全球站账号。
|
||||
|
||||
> **注意:** 以下功能目前仅对中国站(aliyun.com)账号开放,国际站 / 全球站账号暂不支持。
|
||||
|
||||
- **知识库与记忆库** — 多模态 RAG 检索 + 跨会话记忆,提供个性化连贯对话体验
|
||||
- **应用调用** — 调用已发布在阿里云百炼平台上的智能体与工作流应用
|
||||
- **MCP 集成** — 统一调度百炼 MCP 服务:列出服务、查看工具、直接在终端调用任意工具
|
||||
- **联网搜索** — 实时互联网信息检索,提升回答准确性及时效性
|
||||
- **模型推荐** — 描述你的场景,智能推荐最适合的模型;支持限定范围搜索、模型对比和替代发现
|
||||
- **微调与部署** — 上传数据集、创建文本/音频/图像调优任务(`finetune text|audio|image create`;文本涵盖 SFT/LoRA/DPO/CPT)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy text|audio|image create`)
|
||||
- **控制台能力** — 浏览模型市场(`model list`)和百炼应用(`app list`),查看统一用量视图(`usage summary`),查询模型免费额度(`usage free`),查看模型用量统计(`usage stats`),管理业务空间(`workspace list`),管理限流与提额(`quota list/request/check/history`)
|
||||
- **本地文件自动上传** — 所有 URL 参数同时支持本地路径,免费临时存储 48 小时
|
||||
|
||||
## 示例:一句话生成一部电影短片
|
||||
## 示例 1:一句话生成一部电影短片
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/dS2F4huqbw5Nfe5L3wwb3grz2q2DNYD3retq8dU-iHo.mp4">
|
||||
@@ -53,130 +41,80 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
|
||||
|
||||
<p align="center"><i>👆 点击封面播放完整 2 分钟演示</i></p>
|
||||
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
一部完整的 **2 分钟、16:9 电影感短片** —— 由一句自然语言端到端生成,**全程零手动剪辑**。这个示例展示了 AI Agent 如何把三个基础能力编排成一条多步创作流水线:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— Agentic coding 模型,解析用户意图、驱动整个工作流
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 调用 **HappyHorse 1.1**,百炼的文生/图生/参考生视频模型
|
||||
- **[spark-video Skill](https://github.com/JohnKeating1997/spark-video)** —— 负责场景拆分、分镜设计、镜头连贯性和最终拼接
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _"帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9"_
|
||||
> _“帮我生成一段日系影视风格,高中女生的青涩初恋故事,剧情高甜,让人看了想谈恋爱,2 分钟左右的视频,尺寸是 16:9。”_
|
||||
|
||||
### 工作流程
|
||||
## 示例 2:一句话构建短片导演 Managed Agent
|
||||
|
||||
1. **Qwen Code** 解析需求、规划叙事节奏,决定要调用哪些工具。
|
||||
2. **spark-video Skill** 把故事拆成镜头、为每个镜头写提示词,并保证视觉连贯性(角色、光线、色调、镜头语言)。
|
||||
3. **`bl video generate`** 把每个镜头并行下发给 **HappyHorse 1.1**。
|
||||
4. Skill 把所有片段拼成最终的 16:9 / 约 2 分钟成片。
|
||||
<p align="center">
|
||||
<a href="https://cloud.video.taobao.com/vod/2v0GYLbJSQb2saj4iopTJDW3iRIHsintYlK-wTKbhqE.mp4">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/6000000001674/O1CN01xhzixhxltbH3LxWu_!!6000000001674-0-tbvideo.jpg" alt="点击播放演示视频" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
没有时间线拖拽,没有逐帧剪辑。一句话 → 一部短片。
|
||||
<p align="center"><i>👆 点击封面播放完整演示</i></p>
|
||||
|
||||
一句话构建一个可复用的云端短片导演,用于分镜设计、分镜图生成和视频创作:
|
||||
|
||||
- **[Qwen Code](https://github.com/QwenLM/qwen-code)** —— 理解需求并生成 Agent 配置
|
||||
- **[阿里云百炼 CLI](https://github.com/modelstudioai/cli/)** —— 校验配置、预览变更并完成部署
|
||||
- **[Managed Agent](https://bailian.console.aliyun.com/cn-beijing/?tab=managed-agents#/managed-agents/quick-start)** —— 在云端运行导演角色及其 Skill 和工具
|
||||
|
||||
### 唯一的提示词
|
||||
|
||||
> _“帮我构建一个 managedagent 应用,能够实现短片拍摄,导演专家生成视频,然后也能进行设计对应的分镜图。”_
|
||||
|
||||
## 安装
|
||||
|
||||
**Agent 安装(推荐)**
|
||||
|
||||
把下面这句话发给你的 Agent,它会自行判断环境并完成安装与校验:
|
||||
|
||||
```text
|
||||
请阅读:https://bailian.aliyun.com/cli/install.md 并按照说明为我安装阿里云百炼 CLI
|
||||
```
|
||||
|
||||
**手动安装(npm)**
|
||||
|
||||
```bash
|
||||
# 推荐 — 无需本机 Node.js
|
||||
curl -fsSL https://bailian.aliyun.com/cli/install.sh | bash
|
||||
|
||||
# Windows(PowerShell)
|
||||
irm https://bailian.aliyun.com/cli/install.ps1 | iex
|
||||
|
||||
# Node 用户 / 开发者(需要 Node.js >= 18.17)
|
||||
npm install -g bailian-cli
|
||||
|
||||
# Agent skills
|
||||
npx skills add modelstudioai/cli --all -g
|
||||
```
|
||||
|
||||
> 二进制安装不依赖 Node.js。`npm install -g` 长期保留。
|
||||
> 需要预先安装 Node.js >= 18.17。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 认证(推荐浏览器登录)
|
||||
bl auth login --console
|
||||
安装完成后,直接在 AI Agent 中描述你的任务,无需手动拼接命令。
|
||||
|
||||
# 或使用 API key 认证
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 或使用 Token Plan(已内置 Base URL,登录时自动测试 Key)
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
# 配置 Coding Agent 使用 DashScope
|
||||
bl config agent --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3-coder-plus
|
||||
|
||||
# 和通义千问对话
|
||||
bl text chat --message "你好,介绍一下阿里云百炼平台"
|
||||
|
||||
# 多模态对话(文本 + 图片 + 音频 + 视频)
|
||||
bl omni --message "描述这张图片" --image ./photo.jpg
|
||||
|
||||
# 生成图片
|
||||
bl image generate --prompt "一只穿太空服的猫在火星上" --out-dir ./images/
|
||||
|
||||
# 图生视频(本地文件自动上传)
|
||||
bl video generate --image ./cat.png --prompt "让画面中的猫动起来" --download cat.mp4
|
||||
|
||||
# 模型推荐 — 根据场景推荐最适合的模型
|
||||
bl advisor recommend --message "我要做一个能理解图片的客服机器人"
|
||||
|
||||
# 对比特定模型
|
||||
bl advisor recommend --message "qwen-max 和 deepseek-v3 哪个更适合做代码生成"
|
||||
|
||||
# 浏览器登录(控制台能力相关命令需要)
|
||||
bl auth login --console
|
||||
|
||||
# 微调与部署 — 从训练到服务的一站式流程
|
||||
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
|
||||
bl finetune watch --job-id ft-xxx --output json # 非阻塞探测(运行中/成功返回 0;失败/取消报错)
|
||||
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
|
||||
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
|
||||
|
||||
# 浏览模型 / 应用 / 免费额度 / 用量统计 / 业务空间
|
||||
bl model list # 浏览模型系列与价格信息
|
||||
bl app list
|
||||
bl usage summary # 统一视图:免费额度 + 近期用量概览
|
||||
bl usage free # 各模型免费额度(可加 --model/--expiring/--sort)
|
||||
bl usage stats --workspace-id <id> # 模型用量统计(加 --model 查单模型)
|
||||
bl workspace list # 列出所有业务空间
|
||||
|
||||
# 限流管理与提额(list / check / request / history)
|
||||
bl quota list # 查看 RPM/TPM 限额(加 --model 过滤)
|
||||
bl quota check # 当前用量 vs 限流阈值(加 --model/--period)
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # 申请临时 TPM 提额
|
||||
bl quota history # 查看提额历史记录
|
||||
|
||||
# Token Plan 团队版管理(需 AK/SK,见下方认证说明)
|
||||
bl token-plan list-seats # 查看订阅席位明细
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
| 场景 | 可以这样对 Agent 说 |
|
||||
| ---------------- | ----------------------------------------------------------------------- |
|
||||
| Managed Agent | “帮我创建一个能够生成短片分镜和视频的 Managed Agent。” |
|
||||
| 图片和视频生成 | “生成一张穿着太空服的猫站在火星上的图片,再把它制作成一段视频。” |
|
||||
| 用量与额度 | “查看最近的模型用量、免费额度和限流情况。” |
|
||||
| 模型选型 | “推荐一个适合图片理解和智能客服的模型。” |
|
||||
| 了解 Bailian CLI | “介绍一下 Bailian CLI 能帮我完成哪些任务,并根据我的需求推荐使用方式。” |
|
||||
|
||||
> 更多案例与使用场景:[阿里云百炼 CLI 官方主页](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
|
||||
## 认证方式
|
||||
|
||||
### DashScope API Key
|
||||
### API Key
|
||||
|
||||
大部分命令均需要 API Key。前往 [DashScope 控制台](https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key) 获取。
|
||||
|
||||
```bash
|
||||
# 方式一:环境变量
|
||||
export DASHSCOPE_API_KEY=sk-xxxxx
|
||||
|
||||
# 方式二:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --api-key sk-xxxxx
|
||||
|
||||
# 方式三:命令行参数
|
||||
bl text chat --api-key sk-xxxxx --message "你好"
|
||||
```
|
||||
|
||||
### Token Plan API Key
|
||||
|
||||
前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制 API Key。
|
||||
CLI 已内置 Token Plan 的默认 Base URL;登录命令会先测试 Key,通过后才保存并激活 `token-plan` 配置。
|
||||
Token Plan 的 API Key 前往 [Token Plan 订阅详情](https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview) 获取或复制。
|
||||
|
||||
```bash
|
||||
bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
@@ -184,26 +122,20 @@ bl auth login --config token-plan --api-key sk-sp-xxxxx
|
||||
|
||||
### 控制台登录(OAuth)
|
||||
|
||||
控制台能力命令(`model list`、`app list`、`usage summary/free/stats`、`workspace list`、`quota list/request/check/history`)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
控制台能力命令(模型列表、应用列表、MCP 列表、工作空间、用量查询、限流提额、控制台直调)需要使用此登录方式。打开浏览器跳转百炼控制台完成登录。
|
||||
|
||||
```bash
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### 阿里云 OpenAPI AK/SK(仅 Token Plan)
|
||||
### 阿里云 OpenAPI AK/SK
|
||||
|
||||
`token-plan` 命令组需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
Token Plan 的席位与成员管理需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
|
||||
> 建议:创建 RAM 子账号并授予最小权限,避免使用主账号 AK/SK。
|
||||
|
||||
```bash
|
||||
# 方式一:登录命令(持久化到 ~/.bailian/config.json)
|
||||
bl auth login --open-api --access-key-id LTAI5t... --access-key-secret ...
|
||||
|
||||
# 方式二:环境变量
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5t...
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...
|
||||
export BAILIAN_WORKSPACE_ID=ws-...
|
||||
```
|
||||
|
||||
## 配置
|
||||
@@ -212,20 +144,31 @@ export BAILIAN_WORKSPACE_ID=ws-...
|
||||
# 查看当前配置
|
||||
bl config show
|
||||
|
||||
# 设置默认值
|
||||
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
|
||||
bl config set --key default_text_model --value qwen-turbo
|
||||
bl config set --key timeout --value 600
|
||||
# 查看全部配置档
|
||||
bl config list
|
||||
|
||||
# 自更新到最新版本
|
||||
bl update
|
||||
|
||||
# 安装指定版本
|
||||
bl update --to 0.1.14
|
||||
# 切换配置档
|
||||
bl config use --name token-plan
|
||||
```
|
||||
|
||||
配置文件位置:`~/.bailian/config.json`
|
||||
|
||||
## 更新
|
||||
|
||||
```bash
|
||||
bl update
|
||||
```
|
||||
|
||||
升级 CLI 至最新版本,并同步更新已安装的 Agent Skills。每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
欢迎扫码加入阿里云百炼 CLI 钉钉用户交流群,获取使用答疑、问题排查、Bug 反馈和使用经验交流支持。
|
||||
|
||||
<img src="https://img.alicdn.com/imgextra/i3/O1CN015uuhYGb6j0L12xJZ_!!6000000006304-2-tps-516-485.png" alt="阿里云百炼 CLI 钉钉用户交流群" width="240" />
|
||||
|
||||
## 相关链接
|
||||
|
||||
| 资源 | 地址 |
|
||||
@@ -237,11 +180,3 @@ bl update --to 0.1.14
|
||||
| 获取 API Key | https://bailian.console.aliyun.com/cn-beijing/?source_channel=key_github&tab=app#/api-key |
|
||||
| 获取 Token Plan API Key | https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/overview |
|
||||
| 获取 AccessKey | https://ram.console.aliyun.com/manage/ak |
|
||||
|
||||
## 更新日志
|
||||
|
||||
每个版本的变更详情记录在 [CHANGELOG.zh.md](https://github.com/modelstudioai/cli/blob/main/CHANGELOG.zh.md)。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提 Issue、Feature Request 和 PR。开发环境搭建、仓库结构、新增/修改命令的工作流请见 [CONTRIBUTING.zh.md](https://github.com/modelstudioai/cli/blob/main/CONTRIBUTING.zh.md)。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli",
|
||||
"version": "1.14.1",
|
||||
"version": "1.14.2",
|
||||
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
|
||||
"keywords": [
|
||||
"agent",
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
finetuneTextCreate,
|
||||
finetuneAudioCreate,
|
||||
finetuneImageCreate,
|
||||
finetuneVideoCreate,
|
||||
finetuneList,
|
||||
finetuneGet,
|
||||
finetuneCancel,
|
||||
@@ -71,6 +72,7 @@ import {
|
||||
finetuneExport,
|
||||
finetuneWatch,
|
||||
finetuneCapability,
|
||||
finetunePrice,
|
||||
deployTextCreate,
|
||||
deployAudioCreate,
|
||||
deployImageCreate,
|
||||
@@ -80,6 +82,8 @@ import {
|
||||
deployScale,
|
||||
deployUpdate,
|
||||
deployDelete,
|
||||
deployPause,
|
||||
deployResume,
|
||||
tokenPlanListSeats,
|
||||
tokenPlanCreateKey,
|
||||
tokenPlanAssignSeats,
|
||||
@@ -93,6 +97,7 @@ import {
|
||||
skillUpdate,
|
||||
skillRemove,
|
||||
skillList,
|
||||
skillInit,
|
||||
managedAgentInit,
|
||||
managedAgentValidate,
|
||||
managedAgentPlan,
|
||||
@@ -180,6 +185,7 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"finetune text create": finetuneTextCreate,
|
||||
"finetune audio create": finetuneAudioCreate,
|
||||
"finetune image create": finetuneImageCreate,
|
||||
"finetune video create": finetuneVideoCreate,
|
||||
"finetune list": finetuneList,
|
||||
"finetune get": finetuneGet,
|
||||
"finetune cancel": finetuneCancel,
|
||||
@@ -189,6 +195,7 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"finetune export": finetuneExport,
|
||||
"finetune watch": finetuneWatch,
|
||||
"finetune capability": finetuneCapability,
|
||||
"finetune price": finetunePrice,
|
||||
"deploy text create": deployTextCreate,
|
||||
"deploy audio create": deployAudioCreate,
|
||||
"deploy image create": deployImageCreate,
|
||||
@@ -198,6 +205,8 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"deploy scale": deployScale,
|
||||
"deploy update": deployUpdate,
|
||||
"deploy delete": deployDelete,
|
||||
"deploy pause": deployPause,
|
||||
"deploy resume": deployResume,
|
||||
"token-plan list-seats": tokenPlanListSeats,
|
||||
"token-plan create-key": tokenPlanCreateKey,
|
||||
"token-plan assign-seats": tokenPlanAssignSeats,
|
||||
@@ -211,6 +220,7 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"skill update": skillUpdate,
|
||||
"skill remove": skillRemove,
|
||||
"skill list": skillList,
|
||||
"skill init": skillInit,
|
||||
"managed-agent init": managedAgentInit,
|
||||
"managed-agent validate": managedAgentValidate,
|
||||
"managed-agent plan": managedAgentPlan,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-commands",
|
||||
"version": "1.14.1",
|
||||
"version": "1.14.2",
|
||||
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, deleteDataset, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, deleteDataset, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const DELETE_FLAGS = {
|
||||
fileId: {
|
||||
@@ -19,20 +19,18 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const fileId = flags.fileId;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "dataset.delete", file_id: fileId }, format);
|
||||
emitResult({ action: "dataset.delete", file_id: fileId }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await deleteDataset(ctx.client, fileId);
|
||||
|
||||
if (settings.quiet || format === "text") {
|
||||
emitBare(`Deleted ${fileId}.`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
if (settings.quiet) {
|
||||
emitBare(fileId);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, getDataset, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, getDataset, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const GET_FLAGS = {
|
||||
fileId: {
|
||||
@@ -19,10 +19,9 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const fileId = flags.fileId;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "dataset.get", file_id: fileId }, format);
|
||||
emitResult({ action: "dataset.get", file_id: fileId }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,19 +44,10 @@ export default defineCommand({
|
||||
description: file.description ?? "",
|
||||
};
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ ...item, request_id: response.request_id }, format);
|
||||
return;
|
||||
if (settings.quiet) {
|
||||
emitBare(item.file_id);
|
||||
} else {
|
||||
emitResult({ ...item, request_id: response.request_id }, "json");
|
||||
}
|
||||
|
||||
// text / quiet
|
||||
emitBare(`file_id: ${item.file_id}`);
|
||||
emitBare(`name: ${item.name}`);
|
||||
emitBare(`size: ${item.size}`);
|
||||
if (item.md5) emitBare(`md5: ${item.md5}`);
|
||||
if (item.purpose) emitBare(`purpose: ${item.purpose}`);
|
||||
if (item.created_at) emitBare(`created_at: ${item.created_at}`);
|
||||
if (item.description) emitBare(`description: ${item.description}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, listDatasets, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId, formatTable } from "bailian-cli-runtime";
|
||||
import { defineCommand, listDatasets, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const LIST_FLAGS = {
|
||||
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
|
||||
@@ -23,7 +23,6 @@ export default defineCommand({
|
||||
exampleArgs: ["", "--purpose fine-tune", "--purpose evaluation --page-size 20", "--output json"],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
@@ -33,7 +32,7 @@ export default defineCommand({
|
||||
page_size: flags.pageSize,
|
||||
purpose: flags.purpose,
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -46,7 +45,6 @@ export default defineCommand({
|
||||
const files = response.data?.files ?? [];
|
||||
const total = response.data?.total;
|
||||
|
||||
// Normalize to consistent structure for both text/json output.
|
||||
const items = files.map((item) => ({
|
||||
file_id: item.file_id ?? "",
|
||||
name: item.name ?? "",
|
||||
@@ -54,20 +52,10 @@ export default defineCommand({
|
||||
purpose: item.purpose ?? "",
|
||||
}));
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ items, total, request_id: response.request_id }, format);
|
||||
return;
|
||||
if (settings.quiet) {
|
||||
for (const item of items) emitBare(item.file_id);
|
||||
} else {
|
||||
emitResult({ items, total, request_id: response.request_id }, "json");
|
||||
}
|
||||
|
||||
// text / quiet
|
||||
if (items.length === 0) {
|
||||
emitBare("No dataset files found.");
|
||||
return;
|
||||
}
|
||||
const headers = ["FILE_ID", "NAME", "SIZE", "PURPOSE"];
|
||||
const rows = items.map((i) => [i.file_id, i.name, i.size, i.purpose]);
|
||||
for (const line of formatTable(headers, rows)) emitBare(line);
|
||||
if (total !== undefined) emitBare(`\nTotal: ${total}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
uploadDataset,
|
||||
validateDataset,
|
||||
parseDatasetSchemaFlag,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const UPLOAD_FLAGS = {
|
||||
file: {
|
||||
@@ -29,7 +28,7 @@ const UPLOAD_FLAGS = {
|
||||
type: "string",
|
||||
valueHint: "<s>",
|
||||
description:
|
||||
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), or "image" (image generation). Default auto-detects per record.',
|
||||
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.',
|
||||
},
|
||||
noValidate: {
|
||||
type: "switch",
|
||||
@@ -45,7 +44,7 @@ export default defineCommand({
|
||||
description: "Upload a dataset file (.jsonl or .zip) to Bailian",
|
||||
auth: "apiKey",
|
||||
usageArgs:
|
||||
"--file <path> [--purpose <name>] [--schema <chatml|dpo|cpt|tts|image>] [--no-validate] [--full-validate]",
|
||||
"--file <path> [--purpose <name>] [--schema <chatml|dpo|cpt|tts|image|video>] [--no-validate] [--full-validate]",
|
||||
flags: UPLOAD_FLAGS,
|
||||
exampleArgs: [
|
||||
"--file train.jsonl",
|
||||
@@ -73,16 +72,8 @@ export default defineCommand({
|
||||
const filePath = flags.file;
|
||||
const purpose = flags.purpose || "fine-tune";
|
||||
const schema = parseDatasetSchemaFlag(flags.schema);
|
||||
if (schema === "video") {
|
||||
throw new BailianError(
|
||||
`--schema video is not supported.`,
|
||||
ExitCode.USAGE,
|
||||
`Supported schemas: chatml, dpo, cpt, tts, image.`,
|
||||
);
|
||||
}
|
||||
const format = detectOutputFormat(settings.output);
|
||||
// Image schema allows larger ZIPs (1 GB vs 300 MB for text).
|
||||
const isMediaSchema = schema === "image";
|
||||
// Image and video schemas allow larger ZIPs (1 GB vs 300 MB for text).
|
||||
const isMediaSchema = schema === "image" || schema === "video";
|
||||
|
||||
if (!flags.noValidate) {
|
||||
const maxBytes = isMediaSchema ? MAX_MEDIA_ZIP_BYTES : MAX_DATASET_BYTES;
|
||||
@@ -129,7 +120,7 @@ export default defineCommand({
|
||||
validate: !flags.noValidate,
|
||||
schema: schema ?? "auto",
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -142,11 +133,8 @@ export default defineCommand({
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(file.file_id);
|
||||
} else if (format === "text") {
|
||||
emitBare(`Uploaded ${file.name} → file_id=${file.file_id}`);
|
||||
emitRequestId(request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult({ ...file, request_id }, format);
|
||||
emitResult({ ...file, request_id }, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
validateDataset,
|
||||
parseDatasetSchemaFlag,
|
||||
formatIssue,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type ValidationResult,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
function formatStats(result: ValidationResult): string[] {
|
||||
const out: string[] = [];
|
||||
if (result.stats.totalRecords !== undefined) out.push(`records: ${result.stats.totalRecords}`);
|
||||
if (result.stats.sampledRecords !== undefined)
|
||||
out.push(`sampled: ${result.stats.sampledRecords}`);
|
||||
if (result.stats.bytes !== undefined) out.push(`bytes: ${result.stats.bytes}`);
|
||||
if (result.stats.durationMs !== undefined) out.push(`took: ${result.stats.durationMs}ms`);
|
||||
return out;
|
||||
}
|
||||
|
||||
const VALIDATE_FLAGS = {
|
||||
file: {
|
||||
type: "string",
|
||||
@@ -36,7 +23,7 @@ const VALIDATE_FLAGS = {
|
||||
type: "string",
|
||||
valueHint: "<s>",
|
||||
description:
|
||||
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), or "image" (image generation). Default auto-detects per record.',
|
||||
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.',
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
@@ -44,13 +31,14 @@ export default defineCommand({
|
||||
description: "Locally validate a dataset file (.jsonl or .zip) without uploading",
|
||||
// 纯本地校验,不触网、不需 API key(与 `pipeline validate` 一致)。
|
||||
auth: "none",
|
||||
usageArgs: "--file <path> [--full-validate] [--schema <chatml|dpo|cpt|tts|image>]",
|
||||
usageArgs: "--file <path> [--full-validate] [--schema <chatml|dpo|cpt|tts|image|video>]",
|
||||
flags: VALIDATE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--file train.jsonl",
|
||||
"--file dpo.jsonl --schema dpo",
|
||||
"--file cpt.jsonl --schema cpt",
|
||||
"--file audio.zip --schema tts",
|
||||
"--file wan-i2v-training-dataset.zip --schema video",
|
||||
"--file eval.jsonl --full-validate",
|
||||
"--file train.jsonl --output json",
|
||||
],
|
||||
@@ -60,27 +48,20 @@ export default defineCommand({
|
||||
"Schemas: chatml = {messages:[...]} (SFT); dpo = {messages:[...], chosen,",
|
||||
'rejected}; cpt = {text:"..."} (continual pre-training, raw text);',
|
||||
'tts = {wav_fn:"train/xxx.wav", text:"..."} (audio fine-tuning);',
|
||||
'image = {img_path:"..."} (image generation). With no --schema, a record',
|
||||
"carrying wav_fn is validated as TTS, img_path as image, chosen/rejected",
|
||||
"as DPO, text (no messages) as CPT, otherwise ChatML. Pass --schema to",
|
||||
"require a specific shape on every record. ZIP archives (.zip) are",
|
||||
"validated structurally (data.jsonl present, media references resolve) in",
|
||||
"addition to per-record content checks. Use --full-validate to JSON.parse",
|
||||
"every line.",
|
||||
'image = {img_path:"..."} (image generation);',
|
||||
'video = {first_frame_path:"...", video_path:"..."} (video generation,',
|
||||
"i2v first-frame or kf2v first+last-frame with last_frame_path). With no",
|
||||
"--schema, a record carrying wav_fn is validated as TTS, img_path as image,",
|
||||
"first_frame_path/video_path as video, chosen/rejected as DPO, text (no",
|
||||
"messages) as CPT, otherwise ChatML. Pass --schema to require a specific",
|
||||
"shape on every record. ZIP archives (.zip) are validated structurally",
|
||||
"(data.jsonl present, media references resolve) in addition to per-record",
|
||||
"content checks. Use --full-validate to JSON.parse every line.",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const filePath = flags.file;
|
||||
const schema = parseDatasetSchemaFlag(flags.schema);
|
||||
if (schema === "video") {
|
||||
throw new BailianError(
|
||||
`--schema video is not supported.`,
|
||||
ExitCode.USAGE,
|
||||
`Supported schemas: chatml, dpo, cpt, tts, image.`,
|
||||
);
|
||||
}
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
{
|
||||
@@ -89,38 +70,17 @@ export default defineCommand({
|
||||
full: flags.fullValidate,
|
||||
schema: schema ?? "auto",
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await validateDataset(filePath, { fullValidate: flags.fullValidate, schema });
|
||||
|
||||
if (format === "json") {
|
||||
// For json output we always emit the structured result, exit code conveys validity.
|
||||
emitResult(result, format);
|
||||
} else if (settings.quiet) {
|
||||
if (settings.quiet) {
|
||||
emitBare(result.valid ? "ok" : "fail");
|
||||
} else {
|
||||
const status = result.valid ? "PASSED" : "FAILED";
|
||||
emitBare(`Dataset validation ${status} for ${result.filePath}`);
|
||||
const stats = formatStats(result);
|
||||
if (stats.length) emitBare(` ${stats.join(" · ")}`);
|
||||
|
||||
if (result.errors.length) {
|
||||
emitBare(`Errors (${result.errors.length}):`);
|
||||
for (const error of result.errors.slice(0, 20)) emitBare(formatIssue(error));
|
||||
if (result.errors.length > 20) {
|
||||
emitBare(` … and ${result.errors.length - 20} more.`);
|
||||
}
|
||||
}
|
||||
if (result.warnings.length) {
|
||||
emitBare(`Warnings (${result.warnings.length}):`);
|
||||
for (const warning of result.warnings.slice(0, 10)) emitBare(formatIssue(warning));
|
||||
if (result.warnings.length > 10) {
|
||||
emitBare(` … and ${result.warnings.length - 10} more.`);
|
||||
}
|
||||
}
|
||||
emitResult(result, "json");
|
||||
}
|
||||
|
||||
if (!result.valid) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
createDeployment,
|
||||
pickPlanStrategy,
|
||||
STRATEGIES,
|
||||
@@ -11,16 +10,16 @@ import {
|
||||
type CommandContext,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const CREATE_FLAGS = {
|
||||
model: {
|
||||
modelName: {
|
||||
type: "string",
|
||||
valueHint: "<name>",
|
||||
description: "Model name (catalog model or fine-tuned output) (required)",
|
||||
valueHint: "<model_name>",
|
||||
description: "Model to deploy — fine-tuned output name or catalog model (required)",
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
displayName: {
|
||||
type: "string",
|
||||
valueHint: "<display_name>",
|
||||
description: "Console display name for the deployment (required)",
|
||||
@@ -64,7 +63,7 @@ const CREATE_FLAGS = {
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const CREATE_USAGE =
|
||||
"--model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]";
|
||||
"--model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]";
|
||||
|
||||
const CREATE_NOTES = [
|
||||
"Plan defaults to `lora` (Token-billed) for text/image and `mu` (model-unit-",
|
||||
@@ -78,14 +77,11 @@ const CREATE_NOTES = [
|
||||
"Use `bl deploy models --source base` to inspect available templates.",
|
||||
"After creation, status starts at PENDING and transitions to RUNNING.",
|
||||
"Invoke the deployed model with: bl text chat --model <deployed_model>",
|
||||
"WARNING: --model is overloaded across commands and refers to DIFFERENT",
|
||||
"values. `bl deploy <modality> create --model` takes the exported model_name",
|
||||
"(e.g. `qwen3-8b-ft-...`), but the create response also returns a",
|
||||
"`deployed_model` field (the deployment instance id, e.g.",
|
||||
"`qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use",
|
||||
"the `deployed_model` from the create response — NOT the `model_name` you",
|
||||
"passed to `deploy <modality> create`. Do not reuse the value across the two",
|
||||
"commands.",
|
||||
"NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).",
|
||||
"The create response also returns a `deployed_model` field — the deployment",
|
||||
"instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference",
|
||||
"(`bl text chat --model <deployed_model>`) and lifecycle commands",
|
||||
"(`deploy get/scale/pause/resume/delete --deployed-model <id>`).",
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -119,10 +115,9 @@ async function runCreate(
|
||||
ctx: CommandContext<typeof CREATE_FLAGS>,
|
||||
): Promise<void> {
|
||||
const { identity, settings, flags } = ctx;
|
||||
const model = flags.model as string;
|
||||
const name = flags.name as string;
|
||||
const model = flags.modelName as string;
|
||||
const name = flags.displayName as string;
|
||||
const plan = (flags.plan as string | undefined) || defaultDeployPlan(modality);
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
// Plan-specific behaviour is owned by core `plans.ts`. The strategy resolves
|
||||
// the plan-specific body fragment (mu may auto-pick a template from the
|
||||
@@ -146,7 +141,7 @@ async function runCreate(
|
||||
};
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "deploy.create", body }, format);
|
||||
emitResult({ action: "deploy.create", body }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,17 +150,8 @@ async function runCreate(
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(deployment?.deployed_model ?? "");
|
||||
} else if (format === "text") {
|
||||
emitBare(`Created deployment.`);
|
||||
if (deployment?.deployed_model) emitBare(` deployed_model: ${deployment.deployed_model}`);
|
||||
if (deployment?.status) emitBare(` status: ${deployment.status}`);
|
||||
if (deployment?.plan) emitBare(` plan: ${deployment.plan}`);
|
||||
emitBare(
|
||||
`\nNext: track readiness with: ${identity.binName} deploy get --deployed-model ${deployment?.deployed_model ?? "<id>"}`,
|
||||
);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,10 +162,10 @@ export const deployTextCreate = defineCommand({
|
||||
usageArgs: CREATE_USAGE,
|
||||
flags: CREATE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--model my-qwen-sft --name my-sft-test",
|
||||
"--model qwen3.6-flash-2026-04-16 --name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000",
|
||||
"--model qwen3-8b --name my-qwen3-mu --plan mu",
|
||||
"--model qwen3-8b --name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2",
|
||||
"--model-name my-qwen-sft --display-name my-sft-test",
|
||||
"--model-name qwen3.6-flash-2026-04-16 --display-name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000",
|
||||
"--model-name qwen3-8b --display-name my-qwen3-mu --plan mu",
|
||||
"--model-name qwen3-8b --display-name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2",
|
||||
],
|
||||
notes: CREATE_NOTES,
|
||||
validate: (flags) => validateCreate("text", flags),
|
||||
@@ -193,9 +179,9 @@ export const deployAudioCreate = defineCommand({
|
||||
usageArgs: CREATE_USAGE,
|
||||
flags: CREATE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--model my-cosyvoice-ft --name my-tts",
|
||||
"--model my-cosyvoice-ft --name my-tts --deploy-spec dps-xxxx --capacity 1",
|
||||
"--model my-cosyvoice-ft --name my-tts --dry-run",
|
||||
"--model-name my-cosyvoice-ft --display-name my-tts",
|
||||
"--model-name my-cosyvoice-ft --display-name my-tts --deploy-spec dps-xxxx --capacity 1",
|
||||
"--model-name my-cosyvoice-ft --display-name my-tts --dry-run",
|
||||
],
|
||||
notes: CREATE_NOTES,
|
||||
validate: (flags) => validateCreate("audio", flags),
|
||||
@@ -209,9 +195,9 @@ export const deployImageCreate = defineCommand({
|
||||
usageArgs: CREATE_USAGE,
|
||||
flags: CREATE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--model my-wan-ft --name my-wan",
|
||||
"--model my-wan-ft --name my-wan-mu --plan mu",
|
||||
"--model my-wan-ft --name my-wan --dry-run",
|
||||
"--model-name my-wan-ft --display-name my-wan",
|
||||
"--model-name my-wan-ft --display-name my-wan-mu --plan mu",
|
||||
"--model-name my-wan-ft --display-name my-wan --dry-run",
|
||||
],
|
||||
notes: CREATE_NOTES,
|
||||
validate: (flags) => validateCreate("image", flags),
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
deleteDeployment,
|
||||
getDeployment,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const DELETE_FLAGS = {
|
||||
deployedModel: {
|
||||
@@ -38,10 +37,9 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const deployedModel = flags.deployedModel;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "deploy.delete", deployed_model: deployedModel }, format);
|
||||
emitResult({ action: "deploy.delete", deployed_model: deployedModel }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,7 +53,8 @@ export default defineCommand({
|
||||
if (status && status !== "STOPPED" && status !== "FAILED") {
|
||||
throw new BailianError(
|
||||
`Deployment ${deployedModel} is ${status}. Only STOPPED / FAILED deployments can be deleted. ` +
|
||||
`Stop it first via the platform console, or pass --skip-precheck to attempt deletion anyway.`,
|
||||
`Run \`bl deploy pause --deployed-model ${deployedModel}\` to pause it first, ` +
|
||||
`or pass --skip-precheck to attempt deletion anyway.`,
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
@@ -69,11 +68,8 @@ export default defineCommand({
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(deployedModel);
|
||||
} else if (format === "text") {
|
||||
emitBare(`Deleted ${deployedModel}.`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, getDeployment, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, getDeployment, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
const GET_FLAGS = {
|
||||
deployedModel: {
|
||||
@@ -22,10 +22,9 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const deployedModel = flags.deployedModel;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "deploy.get", deployed_model: deployedModel }, format);
|
||||
emitResult({ action: "deploy.get", deployed_model: deployedModel }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ export default defineCommand({
|
||||
const deployment = response.output ?? response.data;
|
||||
|
||||
if (!deployment) {
|
||||
emitBare(`No data returned for ${deployedModel}`);
|
||||
emitResult({ deployed_model: deployedModel, request_id: response.request_id }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,18 +56,6 @@ export default defineCommand({
|
||||
if (deployment.gmt_create) item.created_at = deployment.gmt_create;
|
||||
if (deployment.gmt_modified) item.updated_at = deployment.gmt_modified;
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ ...item, request_id: response.request_id }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
// text / quiet — fixed-width label column for alignment
|
||||
const label = (key: string) => `${key}:`.padEnd(18);
|
||||
for (const [key, value] of Object.entries(item)) {
|
||||
if (value === "" || value === undefined) continue;
|
||||
const display = typeof value === "string" ? value : JSON.stringify(value);
|
||||
emitBare(`${label(key)}${display}`);
|
||||
}
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
emitResult({ ...item, request_id: response.request_id }, "json");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
listDeployments,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId, formatTable } from "bailian-cli-runtime";
|
||||
import { defineCommand, listDeployments, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
const LIST_FLAGS = {
|
||||
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
|
||||
@@ -28,13 +23,12 @@ export default defineCommand({
|
||||
exampleArgs: ["", "--status RUNNING", "--page-size 20 --output json"],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
const status = flags.status || undefined;
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
{ action: "deploy.list", page: flags.page, page_size: flags.pageSize, status },
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -57,27 +51,6 @@ export default defineCommand({
|
||||
created_at: item.gmt_create ?? "",
|
||||
}));
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ items, total, request_id: response.request_id }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
// text / quiet
|
||||
if (items.length === 0) {
|
||||
emitBare("No deployments found.");
|
||||
return;
|
||||
}
|
||||
const headers = ["DEPLOYED_MODEL", "MODEL_NAME", "STATUS", "PLAN", "CAPACITY", "CREATED_AT"];
|
||||
const rows = items.map((item) => [
|
||||
item.deployed_model,
|
||||
item.model_name,
|
||||
item.status,
|
||||
item.plan,
|
||||
item.capacity,
|
||||
item.created_at,
|
||||
]);
|
||||
for (const line of formatTable(headers, rows)) emitBare(line);
|
||||
if (total !== undefined) emitBare(`\nTotal: ${total}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
emitResult({ items, total, request_id: response.request_id }, "json");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
listDeployableModels,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId, formatTable } from "bailian-cli-runtime";
|
||||
import { defineCommand, listDeployableModels, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
const MODELS_FLAGS = {
|
||||
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
|
||||
@@ -39,7 +34,6 @@ export default defineCommand({
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
// Default version to v1.0 — without it, the API returns the legacy catalog
|
||||
// (only old fine-tune outputs). Pass --catalog-version "" to opt out.
|
||||
const version = flags.catalogVersion === "" ? undefined : (flags.catalogVersion ?? "v1.0");
|
||||
@@ -54,7 +48,7 @@ export default defineCommand({
|
||||
version,
|
||||
model_source: modelSource,
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -72,102 +66,55 @@ export default defineCommand({
|
||||
// Two response shapes:
|
||||
// - custom (fine-tuned): top-level supported_plans: string[]
|
||||
// - base (catalog): plans: [{plan, templates?, cu_specs?}]
|
||||
// For json: surface the deployment-relevant fields preserved as a tree, so
|
||||
// Surface the deployment-relevant fields preserved as a tree, so
|
||||
// downstream tooling can drive `bl deploy <modality> create --deploy-spec <…>`
|
||||
// without a second round-trip. For text: keep the compact one-line summary.
|
||||
if (format === "json") {
|
||||
const items = models.map((model) => {
|
||||
const out: Record<string, unknown> = {
|
||||
model_name: model.model_name ?? "",
|
||||
};
|
||||
if (model.base_model) out.base_model = model.base_model;
|
||||
if (model.model_source) out.model_source = model.model_source;
|
||||
if (model.supported_plans && model.supported_plans.length > 0) {
|
||||
out.supported_plans = model.supported_plans;
|
||||
}
|
||||
if (model.plans && model.plans.length > 0) {
|
||||
out.plans = model.plans.map((plan) => {
|
||||
const planEntry: Record<string, unknown> = { plan: plan.plan ?? "" };
|
||||
if (plan.cu_specs && plan.cu_specs.length > 0) {
|
||||
planEntry.cu_specs = plan.cu_specs;
|
||||
}
|
||||
if (plan.templates && plan.templates.length > 0) {
|
||||
// Pull the top 6 fields most useful for `bl deploy <modality> create`.
|
||||
// Drop noisy/redundant: template_source, template_type,
|
||||
// template_version, deploy_spec (typically == template_id).
|
||||
planEntry.templates = plan.templates.map((template) => {
|
||||
const tpl: Record<string, unknown> = {};
|
||||
if (template.template_id) tpl.template_id = template.template_id;
|
||||
if (template.template_name) tpl.template_name = template.template_name;
|
||||
if (template.charge_type) tpl.charge_type = template.charge_type;
|
||||
// Flatten roles.unified for the common COUPLED case.
|
||||
const unified = template.roles?.unified;
|
||||
if (unified?.model_unit_spec) tpl.model_unit_spec = unified.model_unit_spec;
|
||||
if (unified?.capacity_unit_per_instance !== undefined)
|
||||
tpl.capacity_unit_per_instance = unified.capacity_unit_per_instance;
|
||||
// Preserve split-role configs (SEPERATED) as-is so callers
|
||||
// can still drive prefill/decode sizing.
|
||||
if (template.roles?.prefill || template.roles?.decode) {
|
||||
tpl.roles = {
|
||||
prefill: template.roles?.prefill,
|
||||
decode: template.roles?.decode,
|
||||
};
|
||||
}
|
||||
if (template.template_desc) tpl.template_desc = template.template_desc;
|
||||
return tpl;
|
||||
});
|
||||
}
|
||||
return planEntry;
|
||||
});
|
||||
}
|
||||
return out;
|
||||
});
|
||||
emitResult({ items, total, request_id: response.request_id }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
// text / quiet — keep the compact single-line summary table.
|
||||
const textItems = models.map((model) => {
|
||||
let plansSummary = "";
|
||||
if (model.supported_plans && model.supported_plans.length > 0) {
|
||||
plansSummary = model.supported_plans.join(",");
|
||||
} else if (model.plans && model.plans.length > 0) {
|
||||
plansSummary = model.plans
|
||||
.map((plan) => {
|
||||
const planName = plan.plan ?? "?";
|
||||
if (plan.templates && plan.templates.length > 0) {
|
||||
return `${planName}(${plan.templates.length}t)`;
|
||||
}
|
||||
if (plan.cu_specs && plan.cu_specs.length > 0) {
|
||||
return `${planName}(${plan.cu_specs.join("/")})`;
|
||||
}
|
||||
return planName;
|
||||
})
|
||||
.join(",");
|
||||
} else {
|
||||
plansSummary = "-";
|
||||
}
|
||||
return {
|
||||
// without a second round-trip.
|
||||
const items = models.map((model) => {
|
||||
const out: Record<string, unknown> = {
|
||||
model_name: model.model_name ?? "",
|
||||
base_model: model.base_model ?? "",
|
||||
source: model.model_source ?? "",
|
||||
plans: plansSummary,
|
||||
};
|
||||
if (model.base_model) out.base_model = model.base_model;
|
||||
if (model.model_source) out.model_source = model.model_source;
|
||||
if (model.supported_plans && model.supported_plans.length > 0) {
|
||||
out.supported_plans = model.supported_plans;
|
||||
}
|
||||
if (model.plans && model.plans.length > 0) {
|
||||
out.plans = model.plans.map((plan) => {
|
||||
const planEntry: Record<string, unknown> = { plan: plan.plan ?? "" };
|
||||
if (plan.cu_specs && plan.cu_specs.length > 0) {
|
||||
planEntry.cu_specs = plan.cu_specs;
|
||||
}
|
||||
if (plan.templates && plan.templates.length > 0) {
|
||||
// Pull the top 6 fields most useful for `bl deploy <modality> create`.
|
||||
// Drop noisy/redundant: template_source, template_type,
|
||||
// template_version, deploy_spec (typically == template_id).
|
||||
planEntry.templates = plan.templates.map((template) => {
|
||||
const tpl: Record<string, unknown> = {};
|
||||
if (template.template_id) tpl.template_id = template.template_id;
|
||||
if (template.template_name) tpl.template_name = template.template_name;
|
||||
if (template.charge_type) tpl.charge_type = template.charge_type;
|
||||
// Flatten roles.unified for the common COUPLED case.
|
||||
const unified = template.roles?.unified;
|
||||
if (unified?.model_unit_spec) tpl.model_unit_spec = unified.model_unit_spec;
|
||||
if (unified?.capacity_unit_per_instance !== undefined)
|
||||
tpl.capacity_unit_per_instance = unified.capacity_unit_per_instance;
|
||||
// Preserve split-role configs (SEPERATED) as-is so callers
|
||||
// can still drive prefill/decode sizing.
|
||||
if (template.roles?.prefill || template.roles?.decode) {
|
||||
tpl.roles = {
|
||||
prefill: template.roles?.prefill,
|
||||
decode: template.roles?.decode,
|
||||
};
|
||||
}
|
||||
if (template.template_desc) tpl.template_desc = template.template_desc;
|
||||
return tpl;
|
||||
});
|
||||
}
|
||||
return planEntry;
|
||||
});
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
if (textItems.length === 0) {
|
||||
emitBare("No deployable models found.");
|
||||
return;
|
||||
}
|
||||
const headers = ["MODEL_NAME", "BASE_MODEL", "SOURCE", "PLANS"];
|
||||
const rows = textItems.map((item) => [
|
||||
item.model_name,
|
||||
item.base_model,
|
||||
item.source,
|
||||
item.plans,
|
||||
]);
|
||||
for (const line of formatTable(headers, rows)) emitBare(line);
|
||||
if (total !== undefined) emitBare(`\nTotal: ${total}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
emitResult({ items, total, request_id: response.request_id }, "json");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
defineCommand,
|
||||
stopModelService,
|
||||
listIndependentDeployedModels,
|
||||
findDeploymentEntry,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const PAUSE_FLAGS = {
|
||||
deployedModel: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Deployed model identifier (required)",
|
||||
required: true,
|
||||
},
|
||||
skipPrecheck: {
|
||||
type: "switch",
|
||||
description: "Skip the local RUNNING/PENDING status precheck",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
/**
|
||||
* `bl deploy pause` — pause a running deployment.
|
||||
*
|
||||
* Takes the model service offline so it no longer serves inference requests.
|
||||
* For mu/ptu plans, billing stops while paused.
|
||||
* Precheck: status must be RUNNING or PENDING.
|
||||
*/
|
||||
export default defineCommand({
|
||||
description: "Pause a running model deployment (stops billing for mu/ptu)",
|
||||
auth: "console",
|
||||
usageArgs: "--deployed-model <id> [--skip-precheck]",
|
||||
flags: PAUSE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--deployed-model dep-...",
|
||||
"--deployed-model dep-... --skip-precheck",
|
||||
"--deployed-model dep-... --dry-run",
|
||||
],
|
||||
notes: [
|
||||
"While paused, billing ceases for mu/ptu plans. Use `deploy resume` to bring it back online or `deploy delete` to remove.",
|
||||
"Precheck verifies status is RUNNING/PENDING before issuing the pause; pass --skip-precheck to bypass.",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const deployedModel = flags.deployedModel;
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "deploy.pause", deployed_model: deployedModel }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
// Precheck: verify the deployment is in a pausable state.
|
||||
if (!flags.skipPrecheck) {
|
||||
try {
|
||||
const entries = await listIndependentDeployedModels(ctx.client);
|
||||
const entry = findDeploymentEntry(entries, deployedModel);
|
||||
if (entry) {
|
||||
const status = (entry.status ?? "").toUpperCase();
|
||||
if (status && status !== "RUNNING" && status !== "PENDING") {
|
||||
throw new BailianError(
|
||||
`Deployment ${deployedModel} is ${status}. Only RUNNING / PENDING deployments can be paused. ` +
|
||||
`Pass --skip-precheck to attempt the pause anyway.`,
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
}
|
||||
// If entry not found in list, proceed — the server will surface the real error.
|
||||
} catch (error) {
|
||||
if (error instanceof BailianError) throw error;
|
||||
// If the list call itself failed, proceed and let the API call surface the error.
|
||||
}
|
||||
}
|
||||
|
||||
const response = await stopModelService(ctx.client, deployedModel);
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(deployedModel);
|
||||
} else {
|
||||
emitResult({ deployed_model: deployedModel, action: "pause", ...response }, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import {
|
||||
defineCommand,
|
||||
startModelService,
|
||||
listIndependentDeployedModels,
|
||||
findDeploymentEntry,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const RESUME_FLAGS = {
|
||||
deployedModel: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: "Deployed model identifier (required)",
|
||||
required: true,
|
||||
},
|
||||
skipPrecheck: {
|
||||
type: "switch",
|
||||
description: "Skip the local STOPPED status precheck",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
/**
|
||||
* `bl deploy resume` — resume a paused deployment.
|
||||
*
|
||||
* Brings the model service back online so it can serve inference requests.
|
||||
* Precheck: status must be STOPPED.
|
||||
*/
|
||||
export default defineCommand({
|
||||
description: "Resume a paused model deployment (brings service back online)",
|
||||
auth: "console",
|
||||
usageArgs: "--deployed-model <id> [--skip-precheck]",
|
||||
flags: RESUME_FLAGS,
|
||||
exampleArgs: [
|
||||
"--deployed-model dep-...",
|
||||
"--deployed-model dep-... --skip-precheck",
|
||||
"--deployed-model dep-... --dry-run",
|
||||
],
|
||||
notes: [
|
||||
"Precheck verifies status is STOPPED before issuing the resume; pass --skip-precheck to bypass.",
|
||||
"For mu/ptu plans, billing resumes once the service is back online.",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const deployedModel = flags.deployedModel;
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "deploy.resume", deployed_model: deployedModel }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
// Precheck: verify the deployment is in a resumable state.
|
||||
if (!flags.skipPrecheck) {
|
||||
try {
|
||||
const entries = await listIndependentDeployedModels(ctx.client);
|
||||
const entry = findDeploymentEntry(entries, deployedModel);
|
||||
if (entry) {
|
||||
const status = (entry.status ?? "").toUpperCase();
|
||||
if (status && status !== "STOPPED") {
|
||||
throw new BailianError(
|
||||
`Deployment ${deployedModel} is ${status}. Only STOPPED deployments can be resumed. ` +
|
||||
`Pass --skip-precheck to attempt the resume anyway.`,
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
}
|
||||
// If entry not found in list, proceed — the server will surface the real error.
|
||||
} catch (error) {
|
||||
if (error instanceof BailianError) throw error;
|
||||
// If the list call itself failed, proceed and let the API call surface the error.
|
||||
}
|
||||
}
|
||||
|
||||
const response = await startModelService(ctx.client, deployedModel);
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(deployedModel);
|
||||
} else {
|
||||
emitResult({ deployed_model: deployedModel, action: "resume", ...response }, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
scaleDeployment,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, scaleDeployment, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const SCALE_FLAGS = {
|
||||
deployedModel: {
|
||||
@@ -52,7 +47,6 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const deployedModel = flags.deployedModel;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
const body: Record<string, unknown> = {};
|
||||
if (flags.capacity !== undefined) body.capacity = flags.capacity;
|
||||
@@ -60,21 +54,16 @@ export default defineCommand({
|
||||
if (flags.outputTpm !== undefined) body.output_tpm = flags.outputTpm;
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "deploy.scale", deployed_model: deployedModel, body }, format);
|
||||
emitResult({ action: "deploy.scale", deployed_model: deployedModel, body }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await scaleDeployment(ctx.client, deployedModel, body);
|
||||
const deployment = response.output ?? response.data;
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(deployedModel);
|
||||
} else if (format === "text") {
|
||||
const cap = deployment?.capacity !== undefined ? ` (capacity=${deployment.capacity})` : "";
|
||||
emitBare(`Scaled ${deployedModel}${cap}.`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
updateDeployment,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, updateDeployment, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const UPDATE_FLAGS = {
|
||||
deployedModel: {
|
||||
@@ -48,31 +43,22 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const deployedModel = flags.deployedModel;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
const body: Record<string, unknown> = {};
|
||||
if (flags.rpmLimit !== undefined) body.rpm_limit = flags.rpmLimit;
|
||||
if (flags.tpmLimit !== undefined) body.tpm_limit = flags.tpmLimit;
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "deploy.update", deployed_model: deployedModel, body }, format);
|
||||
emitResult({ action: "deploy.update", deployed_model: deployedModel, body }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await updateDeployment(ctx.client, deployedModel, body);
|
||||
const deployment = response.output ?? response.data;
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(deployedModel);
|
||||
} else if (format === "text") {
|
||||
const parts: string[] = [];
|
||||
if (deployment?.rpm_limit !== undefined) parts.push(`rpm_limit=${deployment.rpm_limit}`);
|
||||
if (deployment?.tpm_limit !== undefined) parts.push(`tpm_limit=${deployment.tpm_limit}`);
|
||||
const summary = parts.length ? ` (${parts.join(", ")})` : "";
|
||||
emitBare(`Updated ${deployedModel}${summary}.`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, cancelFineTune, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, cancelFineTune, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const CANCEL_FLAGS = {
|
||||
jobId: {
|
||||
@@ -23,24 +23,18 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const jobId = flags.jobId;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "finetune.cancel", job_id: jobId }, format);
|
||||
emitResult({ action: "finetune.cancel", job_id: jobId }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await cancelFineTune(ctx.client, jobId);
|
||||
const job = response.output ?? response.data;
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(jobId);
|
||||
} else if (format === "text") {
|
||||
const status = job?.status ? ` (status=${job.status})` : "";
|
||||
emitBare(`Cancelled ${jobId}${status}.`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
fetchModelList,
|
||||
fetchModelCapability,
|
||||
listSupportedTrainingTypes,
|
||||
@@ -43,19 +42,8 @@ async function fetchAllFoundationModels(settings: Settings): Promise<ModelCapabi
|
||||
return all as ModelCapability[];
|
||||
}
|
||||
|
||||
const VARIANT_LABEL: Record<string, string> = {
|
||||
full: "full-parameter",
|
||||
lora: "LoRA",
|
||||
};
|
||||
|
||||
function describeTrainingType(value: string): string {
|
||||
if (!isTrainingTypeCli(value)) return value;
|
||||
const { method, variant } = trainingTypeMethodVariant(value);
|
||||
return `${VARIANT_LABEL[variant] ?? variant} ${method.toUpperCase()}`;
|
||||
}
|
||||
|
||||
const CAPABILITY_FLAGS = {
|
||||
model: {
|
||||
baseModel: {
|
||||
type: "string",
|
||||
valueHint: "<m>",
|
||||
description: "List training types supported by this base model.",
|
||||
@@ -71,31 +59,31 @@ export default defineCommand({
|
||||
description:
|
||||
"Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it)",
|
||||
auth: "none",
|
||||
usageArgs: "--model <m> | --training-type <t>",
|
||||
usageArgs: "--base-model <m> | --training-type <t>",
|
||||
flags: CAPABILITY_FLAGS,
|
||||
exampleArgs: [
|
||||
"--model qwen3-8b",
|
||||
"--base-model qwen3-8b",
|
||||
"--training-type sft-lora",
|
||||
"--training-type cpt --output json",
|
||||
"--training-type sft --quiet",
|
||||
],
|
||||
notes: [
|
||||
"Exactly one of --model / --training-type is required.",
|
||||
"Exactly one of --base-model / --training-type is required.",
|
||||
"Training-type values use the `<method>` / `<method>-lora` convention:",
|
||||
"sft | sft-lora | dpo | dpo-lora | cpt. (cpt has no -lora variant server-side.)",
|
||||
"Queries listFoundationModels, a public API — no console login needed.",
|
||||
],
|
||||
validate: (f) => {
|
||||
if (f.model && f.trainingType)
|
||||
return "--model and --training-type are mutually exclusive; pass one.";
|
||||
if (!f.model && !f.trainingType) return "one of --model / --training-type is required.";
|
||||
if (f.baseModel && f.trainingType)
|
||||
return "--base-model and --training-type are mutually exclusive; pass one.";
|
||||
if (!f.baseModel && !f.trainingType)
|
||||
return "one of --base-model / --training-type is required.";
|
||||
return undefined;
|
||||
},
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const model = flags.model || undefined;
|
||||
const model = flags.baseModel || undefined;
|
||||
const trainingType = flags.trainingType || undefined;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
@@ -104,7 +92,7 @@ export default defineCommand({
|
||||
model,
|
||||
training_type: trainingType,
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -113,7 +101,7 @@ export default defineCommand({
|
||||
if (model) {
|
||||
const capability = await fetchModelCapability(settings, model);
|
||||
if (!capability) {
|
||||
emitBare(`No foundation model found matching "${model}".`);
|
||||
emitResult({ model, error: `No foundation model found matching "${model}".` }, "json");
|
||||
return;
|
||||
}
|
||||
const supported = listSupportedTrainingTypes(capability);
|
||||
@@ -121,23 +109,15 @@ export default defineCommand({
|
||||
for (const value of supported) emitBare(value);
|
||||
return;
|
||||
}
|
||||
if (format !== "text") {
|
||||
emitResult(
|
||||
{
|
||||
model: capability.model ?? model,
|
||||
supported,
|
||||
supports: capability.supports,
|
||||
trainingTypes: capability.trainingTypes,
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
emitBare(`${capability.model ?? model}`);
|
||||
emitBare(supported.length ? "Supported training types:" : "No supported training types.");
|
||||
for (const value of supported) {
|
||||
emitBare(` ${value.padEnd(10)} ${describeTrainingType(value)}`);
|
||||
}
|
||||
emitResult(
|
||||
{
|
||||
model: capability.model ?? model,
|
||||
supported,
|
||||
supports: capability.supports,
|
||||
trainingTypes: capability.trainingTypes,
|
||||
},
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,20 +142,15 @@ export default defineCommand({
|
||||
for (const entry of matched) emitBare(entry.model);
|
||||
return;
|
||||
}
|
||||
if (format !== "text") {
|
||||
emitResult(
|
||||
{
|
||||
training_type: trainingType,
|
||||
method,
|
||||
variant,
|
||||
count: matched.length,
|
||||
models: matched,
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
emitBare(`Models supporting ${trainingType} (${method} / ${variant}): ${matched.length}`);
|
||||
for (const entry of matched) emitBare(` ${entry.model}`);
|
||||
emitResult(
|
||||
{
|
||||
training_type: trainingType,
|
||||
method,
|
||||
variant,
|
||||
count: matched.length,
|
||||
models: matched,
|
||||
},
|
||||
"json",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
listCheckpoints,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId, formatTable } from "bailian-cli-runtime";
|
||||
import { defineCommand, listCheckpoints, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
const CHECKPOINTS_FLAGS = {
|
||||
jobId: {
|
||||
@@ -15,6 +10,8 @@ const CHECKPOINTS_FLAGS = {
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const EXPIRY_WARN_THRESHOLD_MS = 72 * 60 * 60 * 1000; // 72 hours
|
||||
|
||||
export default defineCommand({
|
||||
description: "List checkpoints produced by a fine-tune job",
|
||||
auth: "apiKey",
|
||||
@@ -22,16 +19,15 @@ export default defineCommand({
|
||||
flags: CHECKPOINTS_FLAGS,
|
||||
exampleArgs: ["--job-id ft-xxx", "--job-id ft-xxx --output json"],
|
||||
notes: [
|
||||
"Use the returned `checkpoint` value with `finetune export` to publish",
|
||||
"a deployable model.",
|
||||
"`model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model-name`.",
|
||||
"Checkpoints expire ~15 days after creation; `expire_time` shows the deadline. Export or deploy before expiry.",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const jobId = flags.jobId;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "finetune.checkpoints", job_id: jobId }, format);
|
||||
emitResult({ action: "finetune.checkpoints", job_id: jobId }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,22 +40,26 @@ export default defineCommand({
|
||||
checkpoint: item.checkpoint ?? item.checkpoint_id ?? "",
|
||||
step: item.step !== undefined ? String(item.step) : "",
|
||||
status: item.status ?? "",
|
||||
model_name: item.model_name ?? "",
|
||||
expire_time: item.expire_time ?? "",
|
||||
}));
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ items, total, request_id: response.request_id }, format);
|
||||
return;
|
||||
}
|
||||
emitResult({ items, total, request_id: response.request_id }, "json");
|
||||
|
||||
// text / quiet
|
||||
if (items.length === 0) {
|
||||
emitBare("No checkpoints found.");
|
||||
return;
|
||||
// Near-expiry warning: check if any non-expired checkpoint is within 72h of expiry.
|
||||
const now = Date.now();
|
||||
const expiringSoon = items.filter((item) => {
|
||||
if (!item.expire_time) return false;
|
||||
const deadline = new Date(item.expire_time).getTime();
|
||||
if (Number.isNaN(deadline)) return false;
|
||||
const remaining = deadline - now;
|
||||
return remaining > 0 && remaining < EXPIRY_WARN_THRESHOLD_MS;
|
||||
});
|
||||
if (expiringSoon.length > 0) {
|
||||
process.stderr.write(
|
||||
`\n[warning] ${expiringSoon.length} checkpoint(s) will expire within 72 hours. ` +
|
||||
"Export or deploy before expiry to avoid losing the model artifact.\n",
|
||||
);
|
||||
}
|
||||
const headers = ["CHECKPOINT", "STEP", "STATUS"];
|
||||
const rows = items.map((i) => [i.checkpoint, i.step, i.status]);
|
||||
for (const line of formatTable(headers, rows)) emitBare(line);
|
||||
emitBare(`\nTotal: ${total}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
createFineTune,
|
||||
getDataset,
|
||||
uploadDataset,
|
||||
@@ -27,7 +26,7 @@ import {
|
||||
} from "bailian-cli-core";
|
||||
import { existsSync, statSync } from "fs";
|
||||
import { basename } from "path";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
/**
|
||||
* A `--datasets` / `--validations` token is treated as a local file to upload
|
||||
@@ -208,7 +207,7 @@ async function uploadResolvedLocal(
|
||||
}
|
||||
|
||||
/** The modality a `finetune <modality> create` subcommand is bound to. */
|
||||
type CommandModality = "text" | "audio" | "image";
|
||||
type CommandModality = "text" | "audio" | "image" | "video";
|
||||
|
||||
/**
|
||||
* Flags shared by every `finetune <modality> create` subcommand: what to train
|
||||
@@ -216,10 +215,10 @@ type CommandModality = "text" | "audio" | "image";
|
||||
* output. Every modality's model consumes these.
|
||||
*/
|
||||
const COMMON_FLAGS = {
|
||||
model: {
|
||||
baseModel: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Base model to fine-tune",
|
||||
description: "Base model to fine-tune (e.g. qwen3-8b; not the output model name)",
|
||||
required: true,
|
||||
},
|
||||
datasets: {
|
||||
@@ -317,13 +316,41 @@ const IMAGE_FLAGS = {
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const TEXT_USAGE =
|
||||
"--model <model> --datasets <id|path,...> [--validations <id|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft|sft-lora|dpo|dpo-lora|cpt>]";
|
||||
"--base-model <model> --datasets <id|path,...> [--validations <id|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft|sft-lora|dpo|dpo-lora|cpt>]";
|
||||
|
||||
const AUDIO_USAGE =
|
||||
"--model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>]";
|
||||
"--base-model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>]";
|
||||
|
||||
const IMAGE_USAGE =
|
||||
"--model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i|i2i>] [--learning-rate <str>]";
|
||||
"--base-model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i|i2i>] [--learning-rate <str>]";
|
||||
|
||||
/**
|
||||
* Video (Wan i2v/kf2v) flags: exposes the three hyper-parameters that the
|
||||
* video API supports and users may want to override. Defaults are model-specific
|
||||
* (resolved by the sft-lora profile: wan2.7 → batch_size 1 / max_pixels 102400,
|
||||
* wan2.5 → 4 / 36864, wan2.2 → 4 / 262144).
|
||||
*/
|
||||
const VIDEO_FLAGS = {
|
||||
...COMMON_FLAGS,
|
||||
nEpochs: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Training epochs (default: 50)",
|
||||
},
|
||||
batchSize: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Batch size (default: model-specific, 1 for wan2.7, 4 for wan2.5/2.2)",
|
||||
},
|
||||
learningRate: {
|
||||
type: "string",
|
||||
valueHint: "<str>",
|
||||
description: 'Learning rate as a string to preserve precision (default: "2e-5")',
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const VIDEO_USAGE =
|
||||
"--base-model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>]";
|
||||
|
||||
const COMMON_NOTES = [
|
||||
"Creating a job uploads any local datasets and consumes training quota.",
|
||||
@@ -383,7 +410,7 @@ async function runCreate<F extends FlagsDef>(
|
||||
): Promise<void> {
|
||||
const { identity, settings } = ctx;
|
||||
const flags = ctx.flags as Record<string, unknown>;
|
||||
const model = flags.model as string;
|
||||
const model = flags.baseModel as string;
|
||||
const datasetsRaw = flags.datasets as string;
|
||||
|
||||
// CosyVoice audio fine-tuning accepts exactly one training file
|
||||
@@ -606,8 +633,6 @@ async function runCreate<F extends FlagsDef>(
|
||||
if (modelName) body.model_name = modelName;
|
||||
if (suffix) body.finetuned_output_suffix = suffix;
|
||||
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
const pending = [
|
||||
...training.localPaths.map((path) => ({ field: "datasets", path })),
|
||||
@@ -617,7 +642,7 @@ async function runCreate<F extends FlagsDef>(
|
||||
pending.length > 0
|
||||
? { action: "finetune.create", body, pending_uploads: pending }
|
||||
: { action: "finetune.create", body },
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -627,16 +652,8 @@ async function runCreate<F extends FlagsDef>(
|
||||
|
||||
if (settings.quiet) {
|
||||
if (job?.job_id) emitBare(job.job_id);
|
||||
} else if (format === "text") {
|
||||
if (job?.job_id) {
|
||||
emitBare(`Created fine-tune job: ${job.job_id}`);
|
||||
if (job.status) emitBare(`Status: ${job.status}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
}
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,14 +664,14 @@ export const finetuneTextCreate = defineCommand({
|
||||
usageArgs: TEXT_USAGE,
|
||||
flags: TEXT_FLAGS,
|
||||
exampleArgs: [
|
||||
"--model qwen3-8b --datasets file-xxx",
|
||||
"--model qwen3-8b --datasets ./train.jsonl",
|
||||
"--model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl",
|
||||
"--model qwen3-8b --datasets file-aaa,./extra.jsonl",
|
||||
"--model qwen3-8b --datasets ./train.jsonl --training-type sft",
|
||||
'--model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4',
|
||||
"--model qwen3-8b --datasets file-xxx --output json",
|
||||
"--model qwen3-8b --datasets file-xxx --dry-run",
|
||||
"--base-model qwen3-8b --datasets file-xxx",
|
||||
"--base-model qwen3-8b --datasets ./train.jsonl",
|
||||
"--base-model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl",
|
||||
"--base-model qwen3-8b --datasets file-aaa,./extra.jsonl",
|
||||
"--base-model qwen3-8b --datasets ./train.jsonl --training-type sft",
|
||||
'--base-model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4',
|
||||
"--base-model qwen3-8b --datasets file-xxx --output json",
|
||||
"--base-model qwen3-8b --datasets file-xxx --dry-run",
|
||||
],
|
||||
notes: TEXT_NOTES,
|
||||
run: (ctx) => runCreate("text", ctx),
|
||||
@@ -667,11 +684,11 @@ export const finetuneAudioCreate = defineCommand({
|
||||
usageArgs: AUDIO_USAGE,
|
||||
flags: AUDIO_FLAGS,
|
||||
exampleArgs: [
|
||||
"--model cosyvoice-v3-flash --datasets ./audio.zip",
|
||||
"--model cosyvoice-v3-flash --datasets file-xxx",
|
||||
"--model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts",
|
||||
"--model cosyvoice-v3-flash --datasets file-xxx --output json",
|
||||
"--model cosyvoice-v3-flash --datasets ./audio.zip --dry-run",
|
||||
"--base-model cosyvoice-v3-flash --datasets ./audio.zip",
|
||||
"--base-model cosyvoice-v3-flash --datasets file-xxx",
|
||||
"--base-model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts",
|
||||
"--base-model cosyvoice-v3-flash --datasets file-xxx --output json",
|
||||
"--base-model cosyvoice-v3-flash --datasets ./audio.zip --dry-run",
|
||||
],
|
||||
notes: AUDIO_NOTES,
|
||||
run: (ctx) => runCreate("audio", ctx),
|
||||
@@ -684,13 +701,38 @@ export const finetuneImageCreate = defineCommand({
|
||||
usageArgs: IMAGE_USAGE,
|
||||
flags: IMAGE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--model wan2.7-image-pro --datasets ./images.zip",
|
||||
"--model wan2.7-image-pro --datasets file-xxx",
|
||||
"--model wan2.7-image-pro --datasets file-xxx --generation-type i2i",
|
||||
"--model wan2.7-image-pro --datasets ./images.zip --model-name my-wan",
|
||||
"--model wan2.7-image-pro --datasets file-xxx --output json",
|
||||
"--model wan2.7-image-pro --datasets ./images.zip --dry-run",
|
||||
"--base-model wan2.7-image-pro --datasets ./images.zip",
|
||||
"--base-model wan2.7-image-pro --datasets file-xxx",
|
||||
"--base-model wan2.7-image-pro --datasets file-xxx --generation-type i2i",
|
||||
"--base-model wan2.7-image-pro --datasets ./images.zip --model-name my-wan",
|
||||
"--base-model wan2.7-image-pro --datasets file-xxx --output json",
|
||||
"--base-model wan2.7-image-pro --datasets ./images.zip --dry-run",
|
||||
],
|
||||
notes: IMAGE_NOTES,
|
||||
run: (ctx) => runCreate("image", ctx),
|
||||
});
|
||||
|
||||
const VIDEO_NOTES = [
|
||||
...COMMON_NOTES,
|
||||
"Video generation training (Wan i2v/kf2v) runs efficient_sft with model-",
|
||||
"specific defaults: wan2.7 (batch_size=1, max_pixels=102400), wan2.5/2.2",
|
||||
"(batch_size=4, max_pixels per model). Override with --batch-size/--n-epochs.",
|
||||
"Datasets are .zip archives with data.jsonl + frame images + videos.",
|
||||
"Recommended: ≥10 training samples, 20-100 for stable results.",
|
||||
];
|
||||
|
||||
/** `bl finetune video create` — fine-tune a video generation model. Datasets are `.zip`. */
|
||||
export const finetuneVideoCreate = defineCommand({
|
||||
description: "Create a video generation model fine-tune job (Wan i2v/kf2v, efficient_sft)",
|
||||
auth: "apiKey",
|
||||
usageArgs: VIDEO_USAGE,
|
||||
flags: VIDEO_FLAGS,
|
||||
exampleArgs: [
|
||||
"--base-model wan2.7-i2v --datasets file-xxx",
|
||||
"--base-model wan2.7-i2v --datasets ./i2v-data.zip",
|
||||
"--base-model wan2.2-kf2v-flash --datasets file-xxx --n-epochs 100",
|
||||
"--base-model wan2.7-i2v --datasets file-xxx --dry-run",
|
||||
],
|
||||
notes: VIDEO_NOTES,
|
||||
run: (ctx) => runCreate("video", ctx),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, deleteFineTune, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, deleteFineTune, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const DELETE_FLAGS = {
|
||||
jobId: {
|
||||
@@ -23,10 +23,9 @@ export default defineCommand({
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const jobId = flags.jobId;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "finetune.delete", job_id: jobId }, format);
|
||||
emitResult({ action: "finetune.delete", job_id: jobId }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,11 +33,8 @@ export default defineCommand({
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(jobId);
|
||||
} else if (format === "text") {
|
||||
emitBare(`Deleted ${jobId}.`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
exportCheckpoint,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, exportCheckpoint, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const EXPORT_FLAGS = {
|
||||
jobId: {
|
||||
@@ -39,11 +34,10 @@ export default defineCommand({
|
||||
"explicit export is the canonical path for non-best checkpoints.",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { identity, settings, flags } = ctx;
|
||||
const { settings, flags } = ctx;
|
||||
const jobId = flags.jobId;
|
||||
const checkpoint = flags.checkpoint;
|
||||
const modelName = flags.modelName;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
@@ -53,7 +47,7 @@ export default defineCommand({
|
||||
checkpoint,
|
||||
model_name: modelName,
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -64,14 +58,8 @@ export default defineCommand({
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(exported);
|
||||
} else if (format === "text") {
|
||||
emitBare(`Exported ${jobId} / ${checkpoint} → model_name=${exported}`);
|
||||
emitBare(
|
||||
`Next: ${identity.binName} deploy text create --model ${exported} --name <display-name>`,
|
||||
);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
emitResult(response, "json");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Best-effort actual training fee calculation using the model catalog's
|
||||
* "ft" (fine-tune) price entry. Pure API-key domain — no console auth needed.
|
||||
*
|
||||
* The model catalog (`listFoundationModels` via public gateway) returns a
|
||||
* `prices[]` array **only when `queryPrice: true` is passed** (the same flag
|
||||
* `fetchModelDetail` uses). Combined with the job's `output.usage` (actual
|
||||
* consumed tokens, present on SUCCEEDED / CANCELED), this gives the exact
|
||||
* training cost without any console-domain login.
|
||||
*/
|
||||
import {
|
||||
callConsoleGateway,
|
||||
effectiveConsoleGatewayConfig,
|
||||
unwrapResponse,
|
||||
MODEL_LIST_API,
|
||||
type Settings,
|
||||
type ModelPriceInfo,
|
||||
} from "bailian-cli-core";
|
||||
|
||||
export interface ActualFee {
|
||||
cost: number;
|
||||
unitPrice: number;
|
||||
priceUnit: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the model's training price from the public catalog gateway.
|
||||
* Uses the same anonymous gateway path as `fetchModelCapability` (no console
|
||||
* token required), but adds `queryPrice: true` to include the prices array.
|
||||
*/
|
||||
async function fetchTrainingPrice(
|
||||
settings: Settings,
|
||||
model: string,
|
||||
): Promise<ModelPriceInfo | null> {
|
||||
const eff = effectiveConsoleGatewayConfig(settings);
|
||||
const result = await callConsoleGateway(
|
||||
{ region: eff.consoleRegion, site: eff.consoleSite, switchAgent: eff.consoleSwitchAgent },
|
||||
settings.timeout,
|
||||
{
|
||||
api: MODEL_LIST_API,
|
||||
data: {
|
||||
input: {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
group: true,
|
||||
model,
|
||||
queryPrice: true,
|
||||
querySampleCode: false,
|
||||
queryGroupByModel: true,
|
||||
queryQuota: false,
|
||||
queryQpmInfo: false,
|
||||
queryApplyStatus: false,
|
||||
queryPermissions: false,
|
||||
queryActivationStatus: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const responseData = unwrapResponse(result as Record<string, unknown>);
|
||||
const list = (responseData.list as Record<string, unknown>[]) ?? [];
|
||||
// The response is grouped; find the exact model in items.
|
||||
for (const group of list) {
|
||||
const items = (group.items as Record<string, unknown>[]) ?? [];
|
||||
for (const item of items) {
|
||||
if (item.model === model) {
|
||||
const prices = (item.prices as ModelPriceInfo[]) ?? [];
|
||||
return prices.find((entry) => entry.type === "ft") ?? null;
|
||||
}
|
||||
}
|
||||
// Flat response fallback (no items nesting).
|
||||
if (group.model === model) {
|
||||
const prices = (group.prices as ModelPriceInfo[]) ?? [];
|
||||
return prices.find((entry) => entry.type === "ft") ?? null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the actual training fee from the model catalog's "ft" price entry.
|
||||
* Returns null when the price is unavailable (network error, model not in
|
||||
* catalog, or no "ft" entry). Never throws.
|
||||
*
|
||||
* Only uses the public model catalog (model metadata) — does NOT call
|
||||
* console-domain pricing APIs (modelCenter.getModelPrice). Models whose
|
||||
* catalog entry lacks a "ft" price (e.g. CosyVoice) will simply omit the
|
||||
* training_cost field until the platform adds it to the catalog.
|
||||
*/
|
||||
export async function computeActualFee(
|
||||
settings: Settings,
|
||||
model: string,
|
||||
usageTokens: number,
|
||||
): Promise<ActualFee | null> {
|
||||
try {
|
||||
const ftEntry = await fetchTrainingPrice(settings, model);
|
||||
const unitPrice = Number(ftEntry?.price);
|
||||
if (!Number.isFinite(unitPrice) || unitPrice <= 0) return null;
|
||||
const priceUnit = ftEntry?.priceUnit ?? "每百万tokens";
|
||||
// Catalog price is yuan per million tokens.
|
||||
const cost = (usageTokens / 1_000_000) * unitPrice;
|
||||
return { cost: Number(cost.toFixed(4)), unitPrice, priceUnit };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineCommand, detectOutputFormat, getFineTune, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { defineCommand, getFineTune, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { computeActualFee } from "./fee.ts";
|
||||
|
||||
const GET_FLAGS = {
|
||||
jobId: {
|
||||
@@ -17,12 +18,11 @@ export default defineCommand({
|
||||
flags: GET_FLAGS,
|
||||
exampleArgs: ["--job-id ft-xxx", "--job-id ft-xxx --output json"],
|
||||
async run(ctx) {
|
||||
const { identity, settings, flags } = ctx;
|
||||
const { settings, flags } = ctx;
|
||||
const jobId = flags.jobId;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "finetune.get", job_id: jobId }, format);
|
||||
emitResult({ action: "finetune.get", job_id: jobId }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30,18 +30,24 @@ export default defineCommand({
|
||||
const job = response.output ?? response.data;
|
||||
|
||||
if (!job) {
|
||||
emitBare(`No data returned for ${jobId}`);
|
||||
emitResult({ job_id: jobId, error: "No data returned" }, "json");
|
||||
return;
|
||||
}
|
||||
|
||||
const hp = job.hyper_parameters;
|
||||
const hyperParameters = job.hyper_parameters;
|
||||
const hyperParts: string[] = [];
|
||||
if (hp?.n_epochs !== undefined) hyperParts.push(`n_epochs=${hp.n_epochs}`);
|
||||
if (hp?.batch_size !== undefined) hyperParts.push(`batch_size=${hp.batch_size}`);
|
||||
if (hp?.learning_rate !== undefined) hyperParts.push(`learning_rate=${hp.learning_rate}`);
|
||||
if (hp?.max_length !== undefined) hyperParts.push(`max_length=${hp.max_length}`);
|
||||
if (hyperParameters?.n_epochs !== undefined)
|
||||
hyperParts.push(`n_epochs=${hyperParameters.n_epochs}`);
|
||||
if (hyperParameters?.batch_size !== undefined)
|
||||
hyperParts.push(`batch_size=${hyperParameters.batch_size}`);
|
||||
if (hyperParameters?.learning_rate !== undefined)
|
||||
hyperParts.push(`learning_rate=${hyperParameters.learning_rate}`);
|
||||
if (hyperParameters?.max_length !== undefined)
|
||||
hyperParts.push(`max_length=${hyperParameters.max_length}`);
|
||||
|
||||
const item = {
|
||||
const usageTokens = typeof job.usage === "number" ? job.usage : undefined;
|
||||
|
||||
const item: Record<string, unknown> = {
|
||||
job_id: job.job_id ?? jobId,
|
||||
base_model: job.model ?? "",
|
||||
status: job.status ?? "",
|
||||
@@ -53,29 +59,20 @@ export default defineCommand({
|
||||
model_name: job.model_name ?? "",
|
||||
created_at: job.create_time ?? job.gmt_create ?? "",
|
||||
updated_at: job.end_time ?? job.gmt_modified ?? "",
|
||||
usage_tokens: usageTokens ?? "",
|
||||
charge_type: typeof job.charge_type === "string" ? job.charge_type : "",
|
||||
};
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ ...item, request_id: response.request_id }, format);
|
||||
return;
|
||||
// Actual fee: only when the platform reports a concrete token count
|
||||
// (SUCCEEDED / CANCELED). Best-effort — silently omitted on lookup failure.
|
||||
if (usageTokens !== undefined && usageTokens > 0 && job.model) {
|
||||
const fee = await computeActualFee(settings, job.model, usageTokens);
|
||||
if (fee) {
|
||||
item.training_cost = fee.cost;
|
||||
item.cost_basis = `${fee.unitPrice} 元/${fee.priceUnit}`;
|
||||
}
|
||||
}
|
||||
|
||||
// text / quiet
|
||||
emitBare(`job_id: ${item.job_id}`);
|
||||
if (item.base_model) emitBare(`base_model: ${item.base_model}`);
|
||||
if (item.status) emitBare(`status: ${item.status}`);
|
||||
if (item.training_type) emitBare(`training_type: ${item.training_type}`);
|
||||
if (item.training_files.length) emitBare(`training_files: ${item.training_files.join(", ")}`);
|
||||
if (item.validation_files.length)
|
||||
emitBare(`validation_files: ${item.validation_files.join(", ")}`);
|
||||
if (item.hyper_params) emitBare(`hyper_params: ${item.hyper_params}`);
|
||||
if (item.output_model)
|
||||
emitBare(
|
||||
`output_model: ${item.output_model} (→ ${identity.binName} deploy text create --model)`,
|
||||
);
|
||||
if (item.model_name) emitBare(`model_name: ${item.model_name}`);
|
||||
if (item.created_at) emitBare(`created_at: ${item.created_at}`);
|
||||
if (item.updated_at) emitBare(`updated_at: ${item.updated_at}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
emitResult({ ...item, request_id: response.request_id }, "json");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, detectOutputFormat, listFineTunes, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId, formatTable } from "bailian-cli-runtime";
|
||||
import { defineCommand, listFineTunes, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
const LIST_FLAGS = {
|
||||
page: { type: "number", valueHint: "<n>", description: "Page number (default: 1)" },
|
||||
@@ -13,71 +13,48 @@ const LIST_FLAGS = {
|
||||
valueHint: "<s>",
|
||||
description: "Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED)",
|
||||
},
|
||||
baseModel: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Filter by base model ID (server-side)",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export default defineCommand({
|
||||
description: "List fine-tune jobs",
|
||||
auth: "apiKey",
|
||||
usageArgs: "[--page <n>] [--page-size <n>] [--status <s>]",
|
||||
usageArgs: "[--page <n>] [--page-size <n>] [--status <s>] [--base-model <model>]",
|
||||
flags: LIST_FLAGS,
|
||||
exampleArgs: ["", "--status RUNNING", "--page-size 20 --output json"],
|
||||
exampleArgs: ["", "--status RUNNING", "--base-model qwen3-8b", "--page-size 20"],
|
||||
async run(ctx) {
|
||||
const { identity, settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
const { settings, flags } = ctx;
|
||||
const pageNo = flags.page;
|
||||
const pageSize = flags.pageSize;
|
||||
const status = flags.status || undefined;
|
||||
const model = flags.baseModel || undefined;
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult({ action: "finetune.list", page: pageNo, page_size: pageSize, status }, format);
|
||||
emitResult(
|
||||
{ action: "finetune.list", page: pageNo, page_size: pageSize, status, model },
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await listFineTunes(ctx.client, { pageNo, pageSize, status });
|
||||
const response = await listFineTunes(ctx.client, { pageNo, pageSize, status, model });
|
||||
const payload = response.output ?? response.data;
|
||||
const jobs = payload?.jobs ?? [];
|
||||
const total = payload?.total;
|
||||
|
||||
const items = jobs.map((item) => ({
|
||||
job_id: item.job_id ?? "",
|
||||
base_model: item.model ?? "",
|
||||
status: item.status ?? "",
|
||||
training_type: item.training_type ?? "",
|
||||
output_model: item.finetuned_output ?? "",
|
||||
created_at: item.create_time ?? item.gmt_create ?? "",
|
||||
const items = jobs.map((job) => ({
|
||||
job_id: job.job_id ?? "",
|
||||
base_model: job.model ?? "",
|
||||
status: job.status ?? "",
|
||||
training_type: job.training_type ?? "",
|
||||
output_model: job.finetuned_output ?? "",
|
||||
created_at: job.create_time ?? job.gmt_create ?? "",
|
||||
}));
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ items, total, request_id: response.request_id }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
// text / quiet
|
||||
if (items.length === 0) {
|
||||
emitBare("No fine-tune jobs found.");
|
||||
return;
|
||||
}
|
||||
const headers = [
|
||||
"JOB_ID",
|
||||
"BASE_MODEL",
|
||||
"STATUS",
|
||||
"TRAINING_TYPE",
|
||||
"OUTPUT_MODEL",
|
||||
"CREATED_AT",
|
||||
];
|
||||
const rows = items.map((i) => [
|
||||
i.job_id,
|
||||
i.base_model,
|
||||
i.status,
|
||||
i.training_type,
|
||||
i.output_model,
|
||||
i.created_at,
|
||||
]);
|
||||
for (const line of formatTable(headers, rows)) emitBare(line);
|
||||
if (total !== undefined) emitBare(`\nTotal: ${total}`);
|
||||
emitBare(
|
||||
`Tip: OUTPUT_MODEL is the input for \`${identity.binName} deploy text create --model\``,
|
||||
);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
emitResult({ items, total, request_id: response.request_id }, "json");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
getFineTuneLogs,
|
||||
type Client,
|
||||
type FineTuneLogEntry,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
/**
|
||||
* Render a single log entry as a single line (mirrors the flatten logic used
|
||||
* for non-search text output: prefer common fields, fall back to JSON).
|
||||
* Render a single log entry as a single line (used for search matching:
|
||||
* prefer common fields, fall back to JSON).
|
||||
*/
|
||||
function renderEntry(entry: FineTuneLogEntry | string): string {
|
||||
if (typeof entry === "string") return entry;
|
||||
const record = entry as Record<string, unknown>;
|
||||
const ts = (record.timestamp ?? record.time ?? record.create_time ?? "") as string;
|
||||
const timestamp = (record.timestamp ?? record.time ?? record.create_time ?? "") as string;
|
||||
const level = (record.level ?? "") as string;
|
||||
const msg = (record.message ?? record.msg ?? record.log ?? "") as string;
|
||||
if (msg || ts || level) {
|
||||
return [ts, level, msg].filter(Boolean).join("\t");
|
||||
const message = (record.message ?? record.msg ?? record.log ?? "") as string;
|
||||
if (message || timestamp || level) {
|
||||
return [timestamp, level, message].filter(Boolean).join("\t");
|
||||
}
|
||||
return JSON.stringify(entry);
|
||||
}
|
||||
@@ -48,16 +47,16 @@ async function fetchAllLogs(
|
||||
let total = 0;
|
||||
// Hard cap to avoid an unbounded loop if the server misreports `total`.
|
||||
const maxPages = 200;
|
||||
for (let i = 0; i < maxPages; i++) {
|
||||
for (let page = 0; page < maxPages; page++) {
|
||||
const response = await getFineTuneLogs(client, jobId, { pageNo, pageSize });
|
||||
const payload = response.output ?? response.data;
|
||||
const page = payload?.logs ?? [];
|
||||
const logs = payload?.logs ?? [];
|
||||
total = payload?.total ?? total;
|
||||
if (page.length === 0) break;
|
||||
entries.push(...page);
|
||||
if (logs.length === 0) break;
|
||||
entries.push(...logs);
|
||||
// Stop once we've collected everything the server claims exists.
|
||||
if (total && entries.length >= total) break;
|
||||
if (page.length < pageSize) break;
|
||||
if (logs.length < pageSize) break;
|
||||
pageNo++;
|
||||
}
|
||||
return { entries, total };
|
||||
@@ -110,7 +109,6 @@ export default defineCommand({
|
||||
const pageSize = flags.pageSize;
|
||||
const search = flags.search || undefined;
|
||||
const tail = flags.tail;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
@@ -122,7 +120,7 @@ export default defineCommand({
|
||||
search,
|
||||
tail,
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -147,18 +145,6 @@ export default defineCommand({
|
||||
const result =
|
||||
tailApplied !== undefined ? scanned.slice(scanned.length - tailApplied) : scanned;
|
||||
|
||||
if (settings.quiet || format === "text") {
|
||||
if (result.length === 0) {
|
||||
emitBare(search ? `No logs matched "${search}".` : "No logs returned.");
|
||||
return;
|
||||
}
|
||||
for (const entry of result) emitBare(renderEntry(entry));
|
||||
const parts: string[] = [`${result.length} shown`];
|
||||
if (matched !== undefined) parts.push(`matched ${matched}`);
|
||||
parts.push(`of ${entries.length}` + (total ? ` (total ${total})` : ""));
|
||||
emitBare(`\n${parts.join(", ")}`);
|
||||
return;
|
||||
}
|
||||
emitResult(
|
||||
{
|
||||
...(matched !== undefined ? { matched } : {}),
|
||||
@@ -168,28 +154,13 @@ export default defineCommand({
|
||||
...(tailApplied !== undefined ? { tail: tailApplied } : {}),
|
||||
logs: result,
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: single page, verbatim response.
|
||||
const response = await getFineTuneLogs(ctx.client, jobId, { pageNo, pageSize });
|
||||
const payload = response.output ?? response.data;
|
||||
const logs = payload?.logs ?? [];
|
||||
|
||||
if (settings.quiet || format === "text") {
|
||||
if (logs.length === 0) {
|
||||
emitBare("No logs returned.");
|
||||
return;
|
||||
}
|
||||
for (const entry of logs) {
|
||||
emitBare(renderEntry(entry));
|
||||
}
|
||||
if (payload?.total !== undefined) emitBare(`\nTotal: ${payload.total}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
emitResult(response, format);
|
||||
}
|
||||
emitResult(response, "json");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
defineCommand,
|
||||
fetchTrainingModelPrice,
|
||||
estimateSftDpoTokens,
|
||||
estimateCptTokens,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
|
||||
const PRICE_FLAGS = {
|
||||
baseModel: {
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: "Base model to fine-tune (e.g. qwen3-8b; not the output model name)",
|
||||
required: true,
|
||||
},
|
||||
datasets: {
|
||||
type: "string",
|
||||
valueHint: "<ids>",
|
||||
description: "Training dataset file IDs, comma-separated (required)",
|
||||
required: true,
|
||||
},
|
||||
trainingType: {
|
||||
type: "string",
|
||||
valueHint: "<type>",
|
||||
description: "Training type: sft | dpo | cpt (default: sft)",
|
||||
},
|
||||
nEpochs: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: "Number of training epochs (default: 3)",
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const SUPPORTED_TRAINING_TYPES = ["sft", "dpo", "cpt"];
|
||||
|
||||
// Fixed hyper-parameters used for estimation. Only n_epochs materially affects
|
||||
// the estimate; the rest are held at representative defaults (not exposed as
|
||||
// flags to keep the command surface minimal).
|
||||
const ESTIMATE_BATCH_SIZE = 16;
|
||||
const ESTIMATE_MAX_LENGTH = 8192;
|
||||
const DEFAULT_N_EPOCHS = 3;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Estimate the training cost for a fine-tune job (token billing)",
|
||||
auth: "console",
|
||||
usageArgs: "--base-model <model> --datasets <ids> [--training-type <type>] [--n-epochs <n>]",
|
||||
flags: PRICE_FLAGS,
|
||||
exampleArgs: [
|
||||
"--base-model qwen3-8b --datasets file-ft-xxx",
|
||||
"--base-model qwen3-8b --datasets file-ft-xxx,file-ft-yyy --n-epochs 2",
|
||||
"--base-model qwen3-8b --datasets file-ft-xxx --training-type cpt",
|
||||
],
|
||||
notes: [
|
||||
"Estimate only — the server computes token usage from the datasets; final cost is subject to the bill.",
|
||||
"Covers token billing for sft / dpo / cpt. Training-unit (MTU) billing is not supported by this command.",
|
||||
"Hyper-parameters other than --n-epochs are fixed at representative defaults for estimation.",
|
||||
],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const model = flags.baseModel;
|
||||
const datasetIds = flags.datasets
|
||||
.split(",")
|
||||
.map((datasetId) => datasetId.trim())
|
||||
.filter(Boolean);
|
||||
const trainingType = (flags.trainingType ?? "sft").toLowerCase();
|
||||
const nEpochs = flags.nEpochs ?? DEFAULT_N_EPOCHS;
|
||||
|
||||
if (!SUPPORTED_TRAINING_TYPES.includes(trainingType)) {
|
||||
throw new BailianError(
|
||||
`Unsupported training type "${trainingType}". Supported: ${SUPPORTED_TRAINING_TYPES.join(", ")}.`,
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
if (datasetIds.length === 0) {
|
||||
throw new BailianError("--datasets must contain at least one file ID.", ExitCode.USAGE);
|
||||
}
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
{ action: "finetune.price", model, datasets: datasetIds, trainingType, nEpochs },
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unit price (yuan per 千Token).
|
||||
const priceInfo = await fetchTrainingModelPrice(ctx.client, model);
|
||||
const unitPrice = Number(priceInfo.price);
|
||||
if (!Number.isFinite(unitPrice)) {
|
||||
throw new BailianError(
|
||||
`No training price found for model "${model}".`,
|
||||
ExitCode.GENERAL,
|
||||
undefined,
|
||||
{ rawResponse: JSON.stringify(priceInfo) },
|
||||
);
|
||||
}
|
||||
|
||||
// Per-epoch token estimate (min/max range).
|
||||
const estimate =
|
||||
trainingType === "cpt"
|
||||
? await estimateCptTokens(ctx.client, model, datasetIds.join(","), nEpochs)
|
||||
: await estimateSftDpoTokens(ctx.client, datasetIds, {
|
||||
nEpochs,
|
||||
batchSize: ESTIMATE_BATCH_SIZE,
|
||||
maxLength: ESTIMATE_MAX_LENGTH,
|
||||
});
|
||||
|
||||
const minPerEpoch = estimate.estimatedDatasetConsumedTokensMinPerEpoch ?? 0;
|
||||
const maxPerEpoch = estimate.estimatedDatasetConsumedTokensMaxPerEpoch ?? 0;
|
||||
const mixedMinPerEpoch = estimate.estimatedMixedConsumedTokensMinPerEpoch ?? 0;
|
||||
const mixedMaxPerEpoch = estimate.estimatedMixedConsumedTokensMaxPerEpoch ?? 0;
|
||||
|
||||
const minTokens = (minPerEpoch + mixedMinPerEpoch) * nEpochs;
|
||||
const maxTokens = (maxPerEpoch + mixedMaxPerEpoch) * nEpochs;
|
||||
// price is yuan per 1000 tokens.
|
||||
const minFee = (minTokens / 1000) * unitPrice;
|
||||
const maxFee = (maxTokens / 1000) * unitPrice;
|
||||
|
||||
emitResult(
|
||||
{
|
||||
model,
|
||||
training_type: trainingType,
|
||||
n_epochs: nEpochs,
|
||||
unit_price: unitPrice,
|
||||
price_unit: priceInfo.priceUnit ?? "千Token",
|
||||
estimated_tokens: { min: minTokens, max: maxTokens },
|
||||
estimated_fee_yuan: {
|
||||
min: Number(minFee.toFixed(4)),
|
||||
max: Number(maxFee.toFixed(4)),
|
||||
},
|
||||
disclaimer: "Server-side estimate; final cost is subject to the bill.",
|
||||
},
|
||||
"json",
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
getFineTune,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, emitRequestId } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { computeActualFee } from "./fee.ts";
|
||||
|
||||
const DEFAULT_INTERVAL_SEC = 10;
|
||||
const MIN_INTERVAL_SEC = 1;
|
||||
@@ -103,7 +103,6 @@ export default defineCommand({
|
||||
const follow = flags.follow;
|
||||
const intervalSec = Math.max(MIN_INTERVAL_SEC, flags.interval ?? DEFAULT_INTERVAL_SEC);
|
||||
const pollTimeoutSec = flags.pollTimeout;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
@@ -114,7 +113,7 @@ export default defineCommand({
|
||||
interval: intervalSec,
|
||||
timeout: pollTimeoutSec,
|
||||
},
|
||||
format,
|
||||
"json",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -132,16 +131,24 @@ export default defineCommand({
|
||||
if (settings.quiet) {
|
||||
// Just the status word — ideal for `status=$(... finetune watch ... --quiet)`.
|
||||
emitBare(status || "UNKNOWN");
|
||||
} else if (format === "text") {
|
||||
emitBare(`${nowStamp()} ${jobId} ${status || "UNKNOWN"}`);
|
||||
if (status === "SUCCEEDED") emitBare(`✓ ${jobId} ${status}`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
} else {
|
||||
// json: a compact, purpose-built status probe.
|
||||
emitResult(
|
||||
{ job_id: jobId, status: status || "UNKNOWN", terminal, request_id: response.request_id },
|
||||
format,
|
||||
);
|
||||
const output: Record<string, unknown> = {
|
||||
job_id: jobId,
|
||||
status: status || "UNKNOWN",
|
||||
terminal,
|
||||
request_id: response.request_id,
|
||||
};
|
||||
// Enrich terminal output with actual fee when usage is reported.
|
||||
const usageTokens = typeof job?.usage === "number" ? job.usage : undefined;
|
||||
if (terminal && usageTokens && usageTokens > 0 && job?.model) {
|
||||
output.usage_tokens = usageTokens;
|
||||
const fee = await computeActualFee(settings, job.model as string, usageTokens);
|
||||
if (fee) {
|
||||
output.training_cost = fee.cost;
|
||||
output.cost_basis = `${fee.unitPrice} 元/${fee.priceUnit}`;
|
||||
}
|
||||
}
|
||||
emitResult(output, "json");
|
||||
}
|
||||
|
||||
if (terminal && status !== "SUCCEEDED") {
|
||||
@@ -168,18 +175,28 @@ export default defineCommand({
|
||||
const job = response.output ?? response.data;
|
||||
const status = String(job?.status ?? "").toUpperCase();
|
||||
|
||||
if (format === "text" && !settings.quiet && status !== lastStatus) {
|
||||
emitBare(`${nowStamp()} ${jobId} ${status || "UNKNOWN"}`);
|
||||
if (!settings.quiet && status !== lastStatus) {
|
||||
process.stderr.write(`${nowStamp()} ${jobId} ${status || "UNKNOWN"}\n`);
|
||||
lastStatus = status;
|
||||
}
|
||||
|
||||
if (TERMINAL_STATUSES.has(status)) {
|
||||
const elapsed = Date.now() - startedAt;
|
||||
if (format !== "text" || settings.quiet) {
|
||||
emitResult(response, format);
|
||||
} else if (status === "SUCCEEDED") {
|
||||
emitBare(`\n✓ ${jobId} ${status} (elapsed ${formatElapsed(elapsed)})`);
|
||||
emitRequestId(response.request_id, settings.quiet);
|
||||
if (settings.quiet) {
|
||||
emitBare(status || "UNKNOWN");
|
||||
} else {
|
||||
// Enrich the raw response with actual fee when usage is available.
|
||||
const usageTokens = typeof job?.usage === "number" ? job.usage : undefined;
|
||||
const enriched: Record<string, unknown> = { ...response };
|
||||
if (usageTokens && usageTokens > 0 && job?.model) {
|
||||
const fee = await computeActualFee(settings, job.model as string, usageTokens);
|
||||
if (fee) {
|
||||
enriched.training_cost = fee.cost;
|
||||
enriched.usage_tokens = usageTokens;
|
||||
enriched.cost_basis = `${fee.unitPrice} 元/${fee.priceUnit}`;
|
||||
}
|
||||
}
|
||||
emitResult(enriched, "json");
|
||||
}
|
||||
if (status !== "SUCCEEDED") {
|
||||
throw new BailianError(
|
||||
@@ -205,7 +222,7 @@ export default defineCommand({
|
||||
// Any other error (including the BailianError thrown above) propagates to
|
||||
// the central handler.
|
||||
if (controller.signal.aborted) {
|
||||
emitBare("\nInterrupted.");
|
||||
process.stderr.write("\nInterrupted.\n");
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
detectInstalledAgents,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
@@ -28,22 +27,31 @@ const INSTALL_CONCURRENCY = 3;
|
||||
export default defineCommand({
|
||||
description: "Install skills from the Bailian skill registry into local agents",
|
||||
auth: "none",
|
||||
usageArgs: "--name <all|name,...>",
|
||||
usageArgs: "--all | --name <name,...>",
|
||||
flags: {
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Install all skills from the registry",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<all|name,...>",
|
||||
description: "Skills to install: all or comma-separated skill names",
|
||||
required: true,
|
||||
valueHint: "<name,...>",
|
||||
description: "Comma-separated skill names to install",
|
||||
},
|
||||
},
|
||||
exampleArgs: ["--name all", "--name spark-video,bailian-model-recommend"],
|
||||
validate(flags) {
|
||||
if (flags.all && flags.name) return "Use either --all or --name, not both";
|
||||
if (!flags.all && !flags.name)
|
||||
return "Specify --all to install everything or --name <name,...> for specific skills";
|
||||
return undefined;
|
||||
},
|
||||
exampleArgs: ["--all", "--name spark-video,bailian-model-recommend"],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const requested = parseSkillNames(ctx.flags.name, false);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const index = await fetchSkillsIndex();
|
||||
const remoteNames = Object.keys(index.skills);
|
||||
const names = requested === "all" ? remoteNames : requested;
|
||||
const parsed = ctx.flags.all ? "all" : parseSkillNames(ctx.flags.name, false);
|
||||
const names = parsed === "all" ? remoteNames : parsed;
|
||||
|
||||
const lock = readSkillLock();
|
||||
const agents = detectInstalledAgents();
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectInstalledAgents,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
installSkillWithFanout,
|
||||
readSkillLock,
|
||||
runWithConcurrency,
|
||||
writeSkillLock,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
|
||||
|
||||
interface InitOutcome {
|
||||
name: string;
|
||||
status: "installed" | "failed";
|
||||
publishedAt?: string;
|
||||
agents?: string[];
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/** Prefix used to identify first-party Bailian skills in the registry. */
|
||||
const BAILIAN_PREFIX = "bailian-";
|
||||
|
||||
/** Max number of skills downloading/installing at the same time. */
|
||||
const INIT_CONCURRENCY = 3;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Install all bailian-* skills (one-shot bootstrap for new environments)",
|
||||
auth: "none",
|
||||
usageArgs: "",
|
||||
exampleArgs: [""],
|
||||
notes: [
|
||||
"Fetches the registry index and installs every skill whose name starts with bailian-",
|
||||
"Equivalent to: bl skill add --all (filtered to bailian-* skills)",
|
||||
],
|
||||
async run(ctx) {
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const index = await fetchSkillsIndex();
|
||||
|
||||
// Discover all bailian-* skills from the live registry index
|
||||
const names = Object.keys(index.skills).filter((name) => name.startsWith(BAILIAN_PREFIX));
|
||||
|
||||
const lock = readSkillLock();
|
||||
const agents = detectInstalledAgents();
|
||||
|
||||
const tasks = names.map((name) => async (): Promise<InitOutcome> => {
|
||||
const entry = index.skills[name];
|
||||
try {
|
||||
const record = await installSkillWithFanout(
|
||||
name,
|
||||
entry,
|
||||
agents,
|
||||
lock.skills[name]?.links ?? [],
|
||||
);
|
||||
lock.skills[name] = record.lockEntry;
|
||||
return {
|
||||
name,
|
||||
status: "installed",
|
||||
publishedAt: entry.publishedAt,
|
||||
agents: record.linkedAgents,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
name,
|
||||
status: "failed",
|
||||
reason: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
}
|
||||
});
|
||||
const results = await runWithConcurrency(tasks, INIT_CONCURRENCY);
|
||||
writeSkillLock(lock);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(
|
||||
{
|
||||
registry: getSkillRegistryBaseUrl(),
|
||||
agents: agents.map((agent) => agent.id),
|
||||
skills: results,
|
||||
},
|
||||
format,
|
||||
);
|
||||
} else if (results.length === 0) {
|
||||
emitBare("No bailian-* skills found in the registry.");
|
||||
} else {
|
||||
const rows = results.map((result) => [
|
||||
result.name,
|
||||
result.status,
|
||||
result.publishedAt ? result.publishedAt.slice(0, 10) : "-",
|
||||
result.status === "installed" ? result.agents?.join(", ") || "-" : (result.reason ?? "-"),
|
||||
]);
|
||||
for (const line of formatTable(["NAME", "STATUS", "PUBLISHED", "AGENTS / REASON"], rows)) {
|
||||
emitBare(line);
|
||||
}
|
||||
}
|
||||
|
||||
const failed = results.filter((result) => result.status === "failed");
|
||||
if (failed.length > 0) {
|
||||
throw new BailianError(
|
||||
`${failed.length}/${results.length} skill(s) failed to install`,
|
||||
ExitCode.GENERAL,
|
||||
"Check the reason for failed skills in the output; network failures can be retried with bl skill init",
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
computeSkillStatuses,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
@@ -24,7 +23,7 @@ export default defineCommand({
|
||||
"STATUS: installed | outdated | not-installed | missing (lock has it, dir deleted) | untracked (dir exists, not managed)",
|
||||
],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
// Three-way reconciliation: live remote index × skill-lock.json (installation facts) × disk
|
||||
const index = await fetchSkillsIndex();
|
||||
const lock = readSkillLock();
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
listSkillDirsOnDisk,
|
||||
parseSkillNames,
|
||||
readSkillLock,
|
||||
@@ -34,7 +33,7 @@ export default defineCommand({
|
||||
exampleArgs: ["--name spark-video", "--name all"],
|
||||
async run(ctx) {
|
||||
// Purely local operation: no remote access, works offline
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const requested = parseSkillNames(ctx.flags.name, false);
|
||||
const lock = readSkillLock();
|
||||
const names = requested === "all" ? Object.keys(lock.skills) : requested;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
detectInstalledAgents,
|
||||
fanOutSkillToAgents,
|
||||
fetchSkillsIndex,
|
||||
@@ -29,19 +28,27 @@ const UPDATE_CONCURRENCY = 3;
|
||||
export default defineCommand({
|
||||
description: "Update installed skills to the latest registry versions",
|
||||
auth: "none",
|
||||
usageArgs: "[--name <all|name,...>]",
|
||||
usageArgs: "[--all] [--name <name,...>]",
|
||||
flags: {
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Update all installed skills (default when neither --all nor --name is given)",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<all|name,...>",
|
||||
description:
|
||||
"Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills)",
|
||||
valueHint: "<name,...>",
|
||||
description: "Comma-separated skill names to update (must be already installed)",
|
||||
},
|
||||
},
|
||||
exampleArgs: ["", "--name spark-video"],
|
||||
validate(flags) {
|
||||
if (flags.all && flags.name) return "Use either --all or --name, not both";
|
||||
return undefined;
|
||||
},
|
||||
exampleArgs: ["", "--all", "--name spark-video"],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const requested = parseSkillNames(ctx.flags.name, true);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const updateAll = ctx.flags.all || !ctx.flags.name;
|
||||
const requested = updateAll ? "all" : parseSkillNames(ctx.flags.name, false);
|
||||
const index = await fetchSkillsIndex();
|
||||
const lock = readSkillLock();
|
||||
const disk = new Set(listSkillDirsOnDisk());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
defineCommand,
|
||||
videoGeneratePath,
|
||||
image2videoPath,
|
||||
taskPath,
|
||||
detectOutputFormat,
|
||||
type DashScopeVideoRequest,
|
||||
@@ -43,6 +44,11 @@ export default defineCommand({
|
||||
valueHint: "<url>",
|
||||
description: "Input image URL for image-to-video generation",
|
||||
},
|
||||
lastFrame: {
|
||||
type: "string",
|
||||
valueHint: "<url>",
|
||||
description: "Last frame image URL (with --image, enables kf2v first+last frame mode)",
|
||||
},
|
||||
negativePrompt: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
@@ -110,12 +116,20 @@ export default defineCommand({
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
const imageUrl = flags.image;
|
||||
const lastFrameUrl = flags.lastFrame as string | undefined;
|
||||
|
||||
// Auto-upload local image file for i2v
|
||||
let resolvedImageUrl: string | undefined;
|
||||
if (imageUrl) {
|
||||
resolvedImageUrl = await ctx.client.resolveImageInput(imageUrl, model);
|
||||
}
|
||||
let resolvedLastFrameUrl: string | undefined;
|
||||
if (lastFrameUrl) {
|
||||
resolvedLastFrameUrl = await ctx.client.resolveImageInput(lastFrameUrl, model);
|
||||
}
|
||||
|
||||
// kf2v mode: both --image and --last-frame provided.
|
||||
const isKf2v = Boolean(resolvedImageUrl && resolvedLastFrameUrl);
|
||||
|
||||
const watermark = resolveWatermark(flags.watermark);
|
||||
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
|
||||
@@ -125,10 +139,16 @@ export default defineCommand({
|
||||
input: {
|
||||
prompt: prompt,
|
||||
negative_prompt: flags.negativePrompt || undefined,
|
||||
// i2v models (happyhorse-1.1-i2v) require input.media with type 'first_frame'
|
||||
...(resolvedImageUrl
|
||||
? { media: [{ type: "first_frame" as const, url: resolvedImageUrl }] }
|
||||
: {}),
|
||||
// kf2v: first+last frame flat fields via image2video endpoint.
|
||||
// wan2.1~2.6 i2v: flat img_url via video-generation endpoint.
|
||||
// wan2.7+ / happyhorse i2v: media[] via video-generation endpoint.
|
||||
...(isKf2v
|
||||
? { first_frame_url: resolvedImageUrl, last_frame_url: resolvedLastFrameUrl }
|
||||
: resolvedImageUrl
|
||||
? /wan[x]?2\.[1-6]/i.test(model)
|
||||
? { img_url: resolvedImageUrl }
|
||||
: { media: [{ type: "first_frame" as const, url: resolvedImageUrl }] }
|
||||
: {}),
|
||||
},
|
||||
parameters: {
|
||||
resolution: flags.resolution || undefined,
|
||||
@@ -141,15 +161,28 @@ export default defineCommand({
|
||||
};
|
||||
|
||||
if (settings.dryRun) {
|
||||
const previewBody = resolvedImageUrl
|
||||
? {
|
||||
...body,
|
||||
input: {
|
||||
...body.input,
|
||||
media: [{ type: "first_frame" as const, url: redactDataUri(resolvedImageUrl) }],
|
||||
},
|
||||
}
|
||||
: body;
|
||||
let previewBody = body;
|
||||
if (isKf2v) {
|
||||
previewBody = {
|
||||
...body,
|
||||
input: {
|
||||
...body.input,
|
||||
first_frame_url: redactDataUri(resolvedImageUrl ?? ""),
|
||||
last_frame_url: redactDataUri(resolvedLastFrameUrl ?? ""),
|
||||
},
|
||||
};
|
||||
} else if (resolvedImageUrl) {
|
||||
const redactedUrl = redactDataUri(resolvedImageUrl);
|
||||
previewBody = {
|
||||
...body,
|
||||
input: {
|
||||
...body.input,
|
||||
...(/wan[x]?2\.[1-6]/i.test(model)
|
||||
? { img_url: redactedUrl }
|
||||
: { media: [{ type: "first_frame" as const, url: redactedUrl }] }),
|
||||
},
|
||||
};
|
||||
}
|
||||
emitResult({ request: previewBody }, format);
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +195,7 @@ export default defineCommand({
|
||||
settings,
|
||||
() =>
|
||||
ctx.client.requestJson<DashScopeAsyncResponse>({
|
||||
path: videoGeneratePath(),
|
||||
path: isKf2v ? image2videoPath() : videoGeneratePath(),
|
||||
method: "POST",
|
||||
body,
|
||||
async: true,
|
||||
|
||||
@@ -66,6 +66,7 @@ export {
|
||||
finetuneTextCreate,
|
||||
finetuneAudioCreate,
|
||||
finetuneImageCreate,
|
||||
finetuneVideoCreate,
|
||||
} from "./commands/finetune/create.ts";
|
||||
export { default as finetuneList } from "./commands/finetune/list.ts";
|
||||
export { default as finetuneGet } from "./commands/finetune/get.ts";
|
||||
@@ -76,6 +77,7 @@ export { default as finetuneCheckpoints } from "./commands/finetune/checkpoints.
|
||||
export { default as finetuneExport } from "./commands/finetune/export.ts";
|
||||
export { default as finetuneWatch } from "./commands/finetune/watch.ts";
|
||||
export { default as finetuneCapability } from "./commands/finetune/capability.ts";
|
||||
export { default as finetunePrice } from "./commands/finetune/price.ts";
|
||||
export {
|
||||
deployTextCreate,
|
||||
deployAudioCreate,
|
||||
@@ -87,6 +89,8 @@ export { default as deployModels } from "./commands/deploy/models.ts";
|
||||
export { default as deployScale } from "./commands/deploy/scale.ts";
|
||||
export { default as deployUpdate } from "./commands/deploy/update.ts";
|
||||
export { default as deployDelete } from "./commands/deploy/delete.ts";
|
||||
export { default as deployPause } from "./commands/deploy/pause.ts";
|
||||
export { default as deployResume } from "./commands/deploy/resume.ts";
|
||||
export { default as tokenPlanListSeats } from "./commands/token-plan/list-seats.ts";
|
||||
export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts";
|
||||
export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts";
|
||||
@@ -117,3 +121,4 @@ export { default as skillAdd } from "./commands/skill/add.ts";
|
||||
export { default as skillUpdate } from "./commands/skill/update.ts";
|
||||
export { default as skillRemove } from "./commands/skill/remove.ts";
|
||||
export { default as skillList } from "./commands/skill/list.ts";
|
||||
export { default as skillInit } from "./commands/skill/init.ts";
|
||||
|
||||
@@ -30,7 +30,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--model|--name/i);
|
||||
expect(stderr).toMatch(/--model-name|--display-name/i);
|
||||
});
|
||||
|
||||
test("deploy create --dry-run 构造 lora 部署请求体", async () => {
|
||||
@@ -38,9 +38,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
"deploy",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--model-name",
|
||||
"qwen-plus-2025-12-01",
|
||||
"--name",
|
||||
"--display-name",
|
||||
"my-qwen-plus",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
@@ -68,9 +68,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
"deploy",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--model-name",
|
||||
"qwen3-8b",
|
||||
"--name",
|
||||
"--display-name",
|
||||
"my-qwen3-mu",
|
||||
"--plan",
|
||||
"mu",
|
||||
@@ -102,9 +102,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
"deploy",
|
||||
"audio",
|
||||
"create",
|
||||
"--model",
|
||||
"--model-name",
|
||||
"my-cosyvoice-ft",
|
||||
"--name",
|
||||
"--display-name",
|
||||
"my-tts",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
|
||||
@@ -31,7 +31,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--model|--datasets/i);
|
||||
expect(stderr).toMatch(/--base-model|--datasets/i);
|
||||
});
|
||||
|
||||
test("finetune create --dry-run 构造 SFT 默认请求体", async () => {
|
||||
@@ -39,7 +39,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
"file-aaa,file-bbb",
|
||||
@@ -73,7 +73,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
"file-aaa",
|
||||
@@ -135,7 +135,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
"file-aaa",
|
||||
@@ -157,7 +157,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
"file-aaa",
|
||||
@@ -176,7 +176,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
`${localPath},file-bbb`,
|
||||
@@ -207,7 +207,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
" , ",
|
||||
@@ -228,7 +228,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
localPath,
|
||||
@@ -250,7 +250,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
localPath,
|
||||
@@ -272,7 +272,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
["cancel", ["--job-id", "ft-xxx"]],
|
||||
["delete", ["--job-id", "ft-xxx"]],
|
||||
["watch", ["--job-id", "ft-xxx"]],
|
||||
["capability", ["--model", "qwen3-8b"]],
|
||||
["capability", ["--base-model", "qwen3-8b"]],
|
||||
])("finetune %s --dry-run 发出结构化动作", async (sub, extra) => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
@@ -292,7 +292,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"text",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"qwen3-8b",
|
||||
"--datasets",
|
||||
" file-a , ,file-b ",
|
||||
@@ -314,7 +314,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"audio",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"cosyvoice-v3-flash",
|
||||
"--datasets",
|
||||
"file-audio",
|
||||
@@ -343,7 +343,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--model|--datasets/i);
|
||||
expect(stderr).toMatch(/--base-model|--datasets/i);
|
||||
expect(stderr).not.toMatch(/--training-type|--n-epochs|--batch-size|--max-length/);
|
||||
});
|
||||
|
||||
@@ -352,7 +352,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
"finetune",
|
||||
"image",
|
||||
"create",
|
||||
"--model",
|
||||
"--base-model",
|
||||
"wan2.7-image-pro",
|
||||
"--datasets",
|
||||
"file-image",
|
||||
@@ -365,6 +365,104 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
expect(data.action).toBe("finetune.create");
|
||||
expect(data.body.training_type).toBe("efficient_sft");
|
||||
});
|
||||
|
||||
test("finetune video create --help 暴露视频超参且不含文本超参", async () => {
|
||||
// Video exposes --n-epochs / --batch-size / --learning-rate; the text-only
|
||||
// --training-type / --max-length surface is not offered.
|
||||
const { stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"video",
|
||||
"create",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--base-model/);
|
||||
expect(stderr).toMatch(/--n-epochs/);
|
||||
expect(stderr).toMatch(/--batch-size/);
|
||||
expect(stderr).toMatch(/--learning-rate/);
|
||||
expect(stderr).not.toMatch(/--training-type|--max-length/);
|
||||
});
|
||||
|
||||
test("finetune video create --datasets 缺失时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"video",
|
||||
"create",
|
||||
"--base-model",
|
||||
"wan2.7-i2v",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--datasets|Missing required/i);
|
||||
});
|
||||
|
||||
test.each([
|
||||
// Model-family-specific defaults resolved by the sft-lora video profile.
|
||||
["wan2.7-i2v", 1, 102400],
|
||||
["wan2.5-i2v-preview", 4, 36864],
|
||||
["wan2.2-kf2v-flash", 4, 262144],
|
||||
])(
|
||||
"finetune video create --dry-run %s 解析 batch_size=%i / max_pixels=%i",
|
||||
async (baseModel, batchSize, maxPixels) => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"video",
|
||||
"create",
|
||||
"--base-model",
|
||||
baseModel,
|
||||
"--datasets",
|
||||
"file-video",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
action: string;
|
||||
body: {
|
||||
model: string;
|
||||
training_type: string;
|
||||
hyper_parameters: Record<string, unknown>;
|
||||
};
|
||||
}>(stdout);
|
||||
expect(data.action).toBe("finetune.create");
|
||||
expect(data.body.model).toBe(baseModel);
|
||||
expect(data.body.training_type).toBe("efficient_sft");
|
||||
expect(data.body.hyper_parameters.batch_size).toBe(batchSize);
|
||||
expect(data.body.hyper_parameters.max_pixels).toBe(maxPixels);
|
||||
expect(data.body.hyper_parameters.learning_rate).toBe("2e-5");
|
||||
expect(data.body.hyper_parameters.lora_rank).toBe(32);
|
||||
},
|
||||
);
|
||||
|
||||
test("finetune video create --dry-run 转发超参覆盖且不做 clamp", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"video",
|
||||
"create",
|
||||
"--base-model",
|
||||
"wan2.7-i2v",
|
||||
"--datasets",
|
||||
"file-video",
|
||||
"--n-epochs",
|
||||
"100",
|
||||
"--batch-size",
|
||||
"2",
|
||||
"--learning-rate",
|
||||
"1e-5",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
body: { hyper_parameters: Record<string, unknown> };
|
||||
}>(stdout);
|
||||
// Video overrides are forwarded verbatim (no [8, 1024] text clamp).
|
||||
expect(data.body.hyper_parameters.n_epochs).toBe(100);
|
||||
expect(data.body.hyper_parameters.batch_size).toBe(2);
|
||||
expect(data.body.hyper_parameters.learning_rate).toBe("1e-5");
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (DashScope)", () => {
|
||||
|
||||
@@ -17,12 +17,14 @@ describe("e2e: skill", () => {
|
||||
test("skill add --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "add", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--all/);
|
||||
expect(stderr).toMatch(/--name/);
|
||||
});
|
||||
|
||||
test("skill update --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "update", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--all/);
|
||||
expect(stderr).toMatch(/--name/);
|
||||
});
|
||||
|
||||
@@ -37,18 +39,37 @@ describe("e2e: skill", () => {
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/list|registry/i);
|
||||
});
|
||||
|
||||
test("skill init --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "init", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/bailian/i);
|
||||
});
|
||||
});
|
||||
|
||||
// Local-only cases: auth "none" + validation happens before any network access, no gating needed
|
||||
describe("e2e: skill (local, no credentials)", () => {
|
||||
test("skill add without --name errors as usage error (2)", async () => {
|
||||
test("skill add without --all or --name errors as usage error (2)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--name|Usage:/i);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--all|--name|Usage:/i);
|
||||
});
|
||||
|
||||
test("skill add with both --all and --name errors as usage error (2)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--all",
|
||||
"--name",
|
||||
"spark-video",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--all|--name|either/i);
|
||||
});
|
||||
|
||||
test("skill remove without --name errors as usage error (2)", async () => {
|
||||
@@ -61,19 +82,6 @@ describe("e2e: skill (local, no credentials)", () => {
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--name|Usage:/i);
|
||||
});
|
||||
|
||||
test("skill add rejects mixing all with specific names (2)", async () => {
|
||||
// parseSkillNames throws UsageError before fetchSkillsIndex — offline-safe
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--name",
|
||||
"all,spark-video",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/all/i);
|
||||
});
|
||||
|
||||
test("skill remove of a not-installed skill fails with reason (1)", async () => {
|
||||
const configDir = makeTempConfigDir();
|
||||
const { stdout, exitCode } = await runCommandE2e(
|
||||
|
||||
@@ -135,6 +135,7 @@ export const FINETUNE_ROUTES: E2eRouteExports = {
|
||||
"finetune text create": "finetuneTextCreate",
|
||||
"finetune audio create": "finetuneAudioCreate",
|
||||
"finetune image create": "finetuneImageCreate",
|
||||
"finetune video create": "finetuneVideoCreate",
|
||||
"finetune list": "finetuneList",
|
||||
"finetune get": "finetuneGet",
|
||||
"finetune cancel": "finetuneCancel",
|
||||
@@ -165,6 +166,7 @@ export const SKILL_ROUTES: E2eRouteExports = {
|
||||
"skill update": "skillUpdate",
|
||||
"skill remove": "skillRemove",
|
||||
"skill list": "skillList",
|
||||
"skill init": "skillInit",
|
||||
};
|
||||
|
||||
export const MANAGED_AGENT_ROUTES: E2eRouteExports = {
|
||||
|
||||
@@ -112,6 +112,62 @@ describe("e2e: video generate (i2v)", () => {
|
||||
}>(stdout);
|
||||
expect(data.request?.input?.media?.[0]?.url).toBe("data:image/png;base64,<omitted>");
|
||||
});
|
||||
|
||||
test.each([
|
||||
// wan2.1~2.6 (legacy) use flat img_url; wan2.7+ and happyhorse use media[].
|
||||
["wan2.5-i2v-preview", "img_url"],
|
||||
["wan2.6-i2v", "img_url"],
|
||||
["wan2.7-i2v", "media"],
|
||||
["happyhorse-1.1-i2v", "media"],
|
||||
])("video generate --dry-run %s 首帧走 %s 字段", async (model, field) => {
|
||||
const configDir = makeE2eOutputDir(`video-i2v-input-shape-${model}`);
|
||||
writeFileSync(
|
||||
join(configDir, "config.json"),
|
||||
JSON.stringify({
|
||||
"token-plan": {
|
||||
api_key: "sk-sp-e2e-placeholder",
|
||||
base_url: "https://token-plan.cn-beijing.maas.aliyuncs.com",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
VIDEO_ROUTES,
|
||||
[
|
||||
"video",
|
||||
"generate",
|
||||
"--config",
|
||||
"token-plan",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
model,
|
||||
"--image",
|
||||
"https://example.com/placeholder.png",
|
||||
"--prompt",
|
||||
"干跑校验",
|
||||
"--output",
|
||||
"json",
|
||||
],
|
||||
{
|
||||
BAILIAN_CONFIG_DIR: configDir,
|
||||
DASHSCOPE_API_KEY: "",
|
||||
DASHSCOPE_BASE_URL: "",
|
||||
},
|
||||
);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
request?: {
|
||||
input?: { img_url?: string; media?: Array<{ type?: string; url?: string }> };
|
||||
};
|
||||
}>(stdout);
|
||||
if (field === "img_url") {
|
||||
expect(data.request?.input?.img_url).toBe("https://example.com/placeholder.png");
|
||||
expect(data.request?.input?.media).toBeUndefined();
|
||||
} else {
|
||||
expect(data.request?.input?.media?.[0]?.type).toBe("first_frame");
|
||||
expect(data.request?.input?.img_url).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-core",
|
||||
"version": "1.14.1",
|
||||
"version": "1.14.2",
|
||||
"description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -32,6 +32,11 @@ export function videoGeneratePath(): string {
|
||||
return "/api/v1/services/aigc/video-generation/video-synthesis";
|
||||
}
|
||||
|
||||
/** POST /api/v1/services/aigc/image2video/video-synthesis — kf2v (first+last frame). */
|
||||
export function image2videoPath(): string {
|
||||
return "/api/v1/services/aigc/image2video/video-synthesis";
|
||||
}
|
||||
|
||||
// ---- Async Task Query ----
|
||||
export function taskPath(taskId: string): string {
|
||||
return `/api/v1/tasks/${encodeURIComponent(taskId)}`;
|
||||
|
||||
@@ -19,6 +19,7 @@ export {
|
||||
taskPath,
|
||||
userProfilePath,
|
||||
videoGeneratePath,
|
||||
image2videoPath,
|
||||
} from "./endpoints.ts";
|
||||
export {
|
||||
isLegacyImage2ImageModel,
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "./api.ts";
|
||||
export * from "./types.ts";
|
||||
export * from "./constants.ts";
|
||||
export * from "./plans.ts";
|
||||
export * from "./lifecycle.ts";
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Deployment lifecycle operations via the **console gateway**.
|
||||
*
|
||||
* Unlike the DashScope REST endpoints in `api.ts`, start/stop/list-independent
|
||||
* are console-domain APIs (`zeldaEasy.broadscope-platform.modelInstance.*`).
|
||||
* Commands using these must declare `auth: "console"`.
|
||||
*/
|
||||
import type { Client } from "../client/client.ts";
|
||||
import { unwrapResponse } from "../console/models.ts";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API names
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const DEPLOY_START_API = "zeldaEasy.broadscope-platform.modelInstance.startModelService";
|
||||
export const DEPLOY_STOP_API = "zeldaEasy.broadscope-platform.modelInstance.stopModelService";
|
||||
export const DEPLOY_LIST_INDEPENDENT_API =
|
||||
"zeldaEasy.broadscope-platform.modelInstance.listIndependentDeployedModel";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface ModelServiceEntry {
|
||||
modelServiceId?: string;
|
||||
deployedModel?: string;
|
||||
deployed_model?: string;
|
||||
status?: string;
|
||||
modelName?: string;
|
||||
model_name?: string;
|
||||
plan?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API wrappers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Start (bring online) a stopped deployment. */
|
||||
export async function startModelService(
|
||||
client: Client,
|
||||
modelServiceId: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
const raw = await client.console<Record<string, unknown>>(DEPLOY_START_API, {
|
||||
input: { modelServiceId },
|
||||
});
|
||||
return unwrapResponse(raw);
|
||||
}
|
||||
|
||||
/** Stop (take offline) a running deployment. Stops billing for mu/ptu plans. */
|
||||
export async function stopModelService(
|
||||
client: Client,
|
||||
modelServiceId: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
const raw = await client.console<Record<string, unknown>>(DEPLOY_STOP_API, {
|
||||
input: { modelServiceId },
|
||||
});
|
||||
return unwrapResponse(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* List independently deployed models (console domain).
|
||||
* Used for precheck status verification and ID mapping.
|
||||
* Paginates internally to return all entries.
|
||||
*/
|
||||
export async function listIndependentDeployedModels(client: Client): Promise<ModelServiceEntry[]> {
|
||||
const allEntries: ModelServiceEntry[] = [];
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
const raw = await client.console<Record<string, unknown>>(DEPLOY_LIST_INDEPENDENT_API, {
|
||||
input: { pageNo: page, pageSize: 50 },
|
||||
});
|
||||
const resp = unwrapResponse(raw);
|
||||
const records = (resp.records ?? []) as ModelServiceEntry[];
|
||||
allEntries.push(...records);
|
||||
const pageCount = (resp.pageCount as number) ?? 1;
|
||||
if (page >= pageCount || records.length === 0) break;
|
||||
page++;
|
||||
}
|
||||
|
||||
return allEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a deployment entry by its identifier in the console-domain list.
|
||||
* Matches against `modelServiceId`, `deployedModel`, or `deployed_model`.
|
||||
*/
|
||||
export function findDeploymentEntry(
|
||||
entries: ModelServiceEntry[],
|
||||
deployedModel: string,
|
||||
): ModelServiceEntry | undefined {
|
||||
return entries.find(
|
||||
(entry) =>
|
||||
entry.modelServiceId === deployedModel ||
|
||||
entry.deployedModel === deployedModel ||
|
||||
entry.deployed_model === deployedModel,
|
||||
);
|
||||
}
|
||||
@@ -43,6 +43,8 @@ export interface ListFineTunesParams {
|
||||
pageNo?: number;
|
||||
pageSize?: number;
|
||||
status?: string;
|
||||
/** Filter by base model ID (server-side). */
|
||||
model?: string;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
@@ -55,6 +57,7 @@ export async function listFineTunes(
|
||||
if (params.pageNo !== undefined) qs.set("page_no", String(params.pageNo));
|
||||
if (params.pageSize !== undefined) qs.set("page_size", String(params.pageSize));
|
||||
if (params.status) qs.set("status", params.status);
|
||||
if (params.model) qs.set("model", params.model);
|
||||
const base = finetuneJobsPath();
|
||||
const path = qs.toString() ? `${base}?${qs.toString()}` : base;
|
||||
return client.requestJson<ListFineTunesResponse>({
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from "./api.ts";
|
||||
export * from "./capability.ts";
|
||||
export * from "./preflight.ts";
|
||||
export * from "./profiles/index.ts";
|
||||
export * from "./price.ts";
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Training price estimation via the **console gateway**.
|
||||
*
|
||||
* These are console-domain APIs (`zeldaEasy.broadscope-platform.*`); commands
|
||||
* using them must declare `auth: "console"`. Two different argument wrappers
|
||||
* exist: `getModelPrice` takes a top-level `query`, while the token-estimation
|
||||
* APIs take a top-level `input`.
|
||||
*/
|
||||
import type { Client } from "../client/client.ts";
|
||||
import { unwrapResponse } from "../console/models.ts";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API names
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const TRAINING_MODEL_PRICE_API = "zeldaEasy.broadscope-platform.modelCenter.getModelPrice";
|
||||
export const CALC_DATASETS_TOKENS_API =
|
||||
"zeldaEasy.broadscope-platform.modelInstance.calculateDatasetsTotalTokens";
|
||||
export const ESTIMATE_FINETUNE_TOKENS_API =
|
||||
"zeldaEasy.broadscope-platform.modelInstance.estimateFinetuneTokens";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface TrainingModelPrice {
|
||||
price?: string;
|
||||
priceUnit?: string;
|
||||
modelId?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface TokenEstimate {
|
||||
estimatedDatasetConsumedTokensMinPerEpoch?: number;
|
||||
estimatedDatasetConsumedTokensMaxPerEpoch?: number;
|
||||
estimatedMixedConsumedTokensMinPerEpoch?: number;
|
||||
estimatedMixedConsumedTokensMaxPerEpoch?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API wrappers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Training unit price for a model. `price` is denominated in `priceUnit`
|
||||
* (typically "千Token" — yuan per 1000 tokens).
|
||||
*/
|
||||
export async function fetchTrainingModelPrice(
|
||||
client: Client,
|
||||
modelId: string,
|
||||
): Promise<TrainingModelPrice> {
|
||||
const raw = await client.console<Record<string, unknown>>(TRAINING_MODEL_PRICE_API, {
|
||||
query: { type: 0, modelId },
|
||||
});
|
||||
return unwrapResponse(raw) as TrainingModelPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate training tokens for SFT / DPO jobs.
|
||||
* Returns a per-epoch min/max range; multiply by `n_epochs` for the total.
|
||||
*/
|
||||
export async function estimateSftDpoTokens(
|
||||
client: Client,
|
||||
datasetIds: string[],
|
||||
hyperParams: { nEpochs: number; batchSize: number; maxLength: number },
|
||||
): Promise<TokenEstimate> {
|
||||
const raw = await client.console<Record<string, unknown>>(CALC_DATASETS_TOKENS_API, {
|
||||
input: { trainDatasetIds: datasetIds, hyperParams },
|
||||
});
|
||||
return unwrapResponse(raw) as TokenEstimate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate training tokens for CPT jobs.
|
||||
*
|
||||
* The console API requires `hyperParams` as a **JSON string** with a full
|
||||
* `userDefinedObj` payload (captured from the console frontend), plus several
|
||||
* top-level fields (`algorithmType`, `bizType`, `priority`, …). Only
|
||||
* `n_epochs` / `max_length` materially affect the estimate; the remaining
|
||||
* hyper-parameters are fixed defaults.
|
||||
*/
|
||||
export async function estimateCptTokens(
|
||||
client: Client,
|
||||
model: string,
|
||||
datasetIdsCsv: string,
|
||||
nEpochs: number,
|
||||
): Promise<TokenEstimate> {
|
||||
const userDefinedObj = {
|
||||
batch_size: 16,
|
||||
eval_steps: 50,
|
||||
learning_rate: "7e-6",
|
||||
lr_scheduler_type: "linear",
|
||||
max_length: 8192,
|
||||
n_epochs: nEpochs,
|
||||
split: 0.9,
|
||||
save_total_limit: "3",
|
||||
resume_from_checkpoint: false,
|
||||
save_strategy: "epoch",
|
||||
};
|
||||
const hyperParams = JSON.stringify({
|
||||
useDefault: false,
|
||||
userDefinedObj,
|
||||
useQwenMixedStrategy: false,
|
||||
});
|
||||
const raw = await client.console<Record<string, unknown>>(ESTIMATE_FINETUNE_TOKENS_API, {
|
||||
input: {
|
||||
trainingType: "cpt",
|
||||
instanceName: `${model}_cli_estimate`,
|
||||
algorithmType: 100,
|
||||
bizType: 100,
|
||||
trainDatasetIds: datasetIdsCsv,
|
||||
hyperParams,
|
||||
bailianTrainModel: model,
|
||||
validationDatasetIds: "",
|
||||
jobName: `${model}_cli_estimate`,
|
||||
priority: "L0",
|
||||
},
|
||||
});
|
||||
return unwrapResponse(raw) as TokenEstimate;
|
||||
}
|
||||
@@ -75,20 +75,24 @@ const IMAGE_HYPER_PARAMS_I2I: Record<string, unknown> = {
|
||||
*
|
||||
* Shared across all video models; `batch_size` and `max_pixels` differ by model
|
||||
* family (resolved per model in `resolveHyperParameters`):
|
||||
* - wan2.5 (e.g. wan2.5-i2v-preview): batch_size 2, max_pixels 36864
|
||||
* - wan2.2 (i2v-flash / kf2v-flash): batch_size 4, max_pixels 262144
|
||||
* - wan2.7 (e.g. wan2.7-i2v): batch_size 1, max_pixels 102400
|
||||
* - wan2.5 (e.g. wan2.5-i2v-preview): batch_size 4, max_pixels 36864
|
||||
* - wan2.2 (i2v-flash / kf2v-flash): batch_size 4, max_pixels 262144
|
||||
*
|
||||
* `learning_rate` is a string to avoid JSON-number precision loss (consistent
|
||||
* with the image defaults). `split` (0.9) + `max_split_val_dataset_sample`
|
||||
* (5) drive the automatic train/validation split when no explicit
|
||||
* validation_file_ids are provided.
|
||||
*
|
||||
* Values aligned with the official user guide (2026-07):
|
||||
* n_epochs 50, eval_epochs 20 (≥ n_epochs/10).
|
||||
*/
|
||||
const VIDEO_HYPER_PARAMS_BASE: Record<string, unknown> = {
|
||||
n_epochs: 400,
|
||||
n_epochs: 50,
|
||||
learning_rate: "2e-5",
|
||||
split: 0.9,
|
||||
split: 0.5,
|
||||
max_split_val_dataset_sample: 5,
|
||||
eval_epochs: 50,
|
||||
eval_epochs: 20,
|
||||
save_total_limit: 10,
|
||||
lora_rank: 32,
|
||||
lora_alpha: 32,
|
||||
@@ -109,6 +113,11 @@ function isWan25(model: string | undefined): boolean {
|
||||
return typeof model === "string" && /wan2\.5/i.test(model);
|
||||
}
|
||||
|
||||
/** wan2.7 family uses batch_size 1 and max_pixels 102400. */
|
||||
function isWan27(model: string | undefined): boolean {
|
||||
return typeof model === "string" && /wan2\.7/i.test(model);
|
||||
}
|
||||
|
||||
export const sftLoraProfile: TrainingProfile = {
|
||||
clientTrainingType: "sft-lora",
|
||||
serverTrainingType: "efficient_sft",
|
||||
@@ -195,15 +204,18 @@ export const sftLoraProfile: TrainingProfile = {
|
||||
}
|
||||
if (isVideo(modality)) {
|
||||
// Video: shared defaults + model-family-specific batch_size / max_pixels.
|
||||
// wan2.5 uses batch_size 2 / max_pixels 36864; wan2.2 uses 4 / 262144.
|
||||
const wan25 = isWan25(flags.model as string | undefined);
|
||||
// wan2.7: batch_size 1, max_pixels 102400
|
||||
// wan2.5: batch_size 4, max_pixels 36864
|
||||
// wan2.2: batch_size 4, max_pixels 262144
|
||||
const model = (flags.model ?? flags.baseModel) as string | undefined;
|
||||
const hp: Record<string, unknown> = {
|
||||
...VIDEO_HYPER_PARAMS_BASE,
|
||||
batch_size: 4,
|
||||
max_pixels: wan25 ? 36864 : 262144,
|
||||
batch_size: isWan27(model) ? 1 : 4,
|
||||
max_pixels: isWan27(model) ? 102400 : isWan25(model) ? 36864 : 262144,
|
||||
};
|
||||
// Optional overrides (no clamping — video batch_size is intentionally small).
|
||||
if (flags.nEpochs !== undefined) hp.n_epochs = flags.nEpochs as number;
|
||||
if (flags.batchSize !== undefined) hp.batch_size = flags.batchSize as number;
|
||||
if (flags.learningRate !== undefined) hp.learning_rate = flags.learningRate as string;
|
||||
return hp;
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ export interface DashScopeVideoRequest {
|
||||
prompt: string;
|
||||
negative_prompt?: string;
|
||||
img_url?: string;
|
||||
first_frame_url?: string;
|
||||
last_frame_url?: string;
|
||||
media?: Array<{
|
||||
type: "image" | "video" | "first_frame" | "last_frame" | "driving_audio" | "first_clip";
|
||||
url: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "knowledge-studio-cli",
|
||||
"version": "1.14.1",
|
||||
"version": "1.14.2",
|
||||
"description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.",
|
||||
"keywords": [
|
||||
"alibaba-cloud",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-runtime",
|
||||
"version": "1.14.1",
|
||||
"version": "1.14.2",
|
||||
"description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-cli
|
||||
metadata:
|
||||
version: "1.14.1"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -52,6 +52,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) |
|
||||
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) |
|
||||
| `bl skill list` | List registry skills and diff against local installs | [skill.md](skill.md) |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) | [skill.md](skill.md) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions | [skill.md](skill.md) |
|
||||
@@ -86,7 +87,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) |
|
||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `skill` | `add`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
|
||||
@@ -7,12 +7,13 @@ Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Description |
|
||||
| ----------------- | ---------------------------------------------------------------- |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents |
|
||||
| `bl skill list` | List registry skills and diff against local installs |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions |
|
||||
| Command | Description |
|
||||
| ----------------- | ----------------------------------------------------------------------- |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents |
|
||||
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) |
|
||||
| `bl skill list` | List registry skills and diff against local installs |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions |
|
||||
|
||||
## Command details
|
||||
|
||||
@@ -22,24 +23,48 @@ Index: [index.md](index.md)
|
||||
| --------------- | ---------------------------------------------------------------- |
|
||||
| **Name** | `skill add` |
|
||||
| **Description** | Install skills from the Bailian skill registry into local agents |
|
||||
| **Usage** | `bl skill add --name <all\|name,...>` |
|
||||
| **Usage** | `bl skill add --all \| --name <name,...>` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------ | -------- | ----------------------------------------------------- |
|
||||
| `--name <all\|name,...>` | string | yes | Skills to install: all or comma-separated skill names |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------- | ------ | -------- | -------------------------------------- |
|
||||
| `--all` | switch | no | Install all skills from the registry |
|
||||
| `--name <name,...>` | string | no | Comma-separated skill names to install |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl skill add --name all
|
||||
bl skill add --all
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill add --name spark-video,bailian-model-recommend
|
||||
```
|
||||
|
||||
### `bl skill init`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------- |
|
||||
| **Name** | `skill init` |
|
||||
| **Description** | Install all bailian-\* skills (one-shot bootstrap for new environments) |
|
||||
| **Usage** | `bl skill init` |
|
||||
|
||||
#### Flags
|
||||
|
||||
_No command-specific flags._
|
||||
|
||||
#### Notes
|
||||
|
||||
- Fetches the registry index and installs every skill whose name starts with bailian-
|
||||
- Equivalent to: bl skill add --all (filtered to bailian-\* skills)
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl skill init
|
||||
```
|
||||
|
||||
### `bl skill list`
|
||||
|
||||
| Field | Value |
|
||||
@@ -96,13 +121,14 @@ bl skill remove --name all
|
||||
| --------------- | ------------------------------------------------------- |
|
||||
| **Name** | `skill update` |
|
||||
| **Description** | Update installed skills to the latest registry versions |
|
||||
| **Usage** | `bl skill update [--name <all\|name,...>]` |
|
||||
| **Usage** | `bl skill update [--all] [--name <name,...>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| `--name <all\|name,...>` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills) |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------- | ------ | -------- | ---------------------------------------------------------------------------- |
|
||||
| `--all` | switch | no | Update all installed skills (default when neither --all nor --name is given) |
|
||||
| `--name <name,...>` | string | no | Comma-separated skill names to update (must be already installed) |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -110,6 +136,10 @@ bl skill remove --name all
|
||||
bl skill update
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill update --all
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill update --name spark-video
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-finetune
|
||||
metadata:
|
||||
version: "1.14.1"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
@@ -23,14 +23,14 @@ description: >-
|
||||
```
|
||||
1. Validate data bl dataset validate --file train.jsonl [--schema chatml|dpo|cpt|tts|image]
|
||||
2. Upload data bl dataset upload --file train.jsonl # returns a file-id
|
||||
3. Create job bl finetune text|audio|image create --model <base> --datasets <file-id|path>
|
||||
3. Create job bl finetune text|audio|image create --base-model <base> --datasets <file-id|path>
|
||||
4. Watch progress bl finetune watch --job-id ft-xxx # or get / logs
|
||||
5. Pick artifact bl finetune checkpoints --job-id ft-xxx
|
||||
6. Export model bl finetune export --job-id ft-xxx --checkpoint ckpt-N --model-name my-model
|
||||
7. Deploy service bl deploy text|audio|image create --model my-model --name my-svc
|
||||
7. Deploy service bl deploy text|audio|image create --model-name my-model --display-name my-svc
|
||||
```
|
||||
|
||||
- Unsure which training methods a base model supports → `bl finetune capability --model <base>` or `--training-type sft|sft-lora|dpo|cpt`.
|
||||
- Unsure which training methods a base model supports → `bl finetune capability --base-model <base>` or `--training-type sft|sft-lora|dpo|cpt`.
|
||||
- Text `--training-type` values: `sft` / `sft-lora` / `dpo` / `dpo-lora` / `cpt`. Audio bases include `cosyvoice-v3-flash`; image bases include `wan2.7-image-pro`.
|
||||
- Deployment plans: audio defaults to `--plan mu`; text/image default to `lora`.
|
||||
- Preview write operations (create / delete / cancel / scale) with `--dry-run` first, and confirm with the user before deleting a job or dataset.
|
||||
@@ -55,10 +55,10 @@ Flags, usage, and examples: see [`reference/`](reference/index.md) or `bl <comma
|
||||
```bash
|
||||
bl dataset validate --file train.jsonl
|
||||
bl dataset upload --file train.jsonl
|
||||
bl finetune text create --model qwen3-8b --training-type sft-lora --datasets file-xxx
|
||||
bl finetune text create --base-model qwen3-8b --training-type sft-lora --datasets file-xxx
|
||||
bl finetune watch --job-id ft-xxx
|
||||
bl finetune export --job-id ft-xxx --checkpoint ckpt-3 --model-name my-qwen-sft
|
||||
bl deploy text create --model my-qwen-sft --name my-svc
|
||||
bl deploy text create --model-name my-qwen-sft --display-name my-svc
|
||||
```
|
||||
|
||||
## Common hand-offs
|
||||
|
||||
@@ -107,23 +107,23 @@ bl dataset list --output json
|
||||
|
||||
### `bl dataset upload`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `dataset upload` |
|
||||
| **Description** | Upload a dataset file (.jsonl or .zip) to Bailian |
|
||||
| **Usage** | `bl dataset upload --file <path> [--purpose <name>] [--schema <chatml\|dpo\|cpt\|tts\|image>] [--no-validate] [--full-validate]` |
|
||||
| Field | Value |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `dataset upload` |
|
||||
| **Description** | Upload a dataset file (.jsonl or .zip) to Bailian |
|
||||
| **Usage** | `bl dataset upload --file <path> [--purpose <name>] [--schema <chatml\|dpo\|cpt\|tts\|image\|video>] [--no-validate] [--full-validate]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--file <path>` | string | yes | Local dataset file (.jsonl or .zip; ≤300MB text, ≤1GB image) |
|
||||
| `--purpose <name>` | string | no | Dataset purpose tag (default: "fine-tune"; e.g. "evaluation") |
|
||||
| `--schema <s>` | string | no | Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), or "image" (image generation). Default auto-detects per record. |
|
||||
| `--no-validate` | switch | no | Skip the local JSONL pre-flight check (not recommended) |
|
||||
| `--full-validate` | switch | no | JSON.parse every line instead of sampling (slower) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--file <path>` | string | yes | Local dataset file (.jsonl or .zip; ≤300MB text, ≤1GB image) |
|
||||
| `--purpose <name>` | string | no | Dataset purpose tag (default: "fine-tune"; e.g. "evaluation") |
|
||||
| `--schema <s>` | string | no | Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record. |
|
||||
| `--no-validate` | switch | no | Skip the local JSONL pre-flight check (not recommended) |
|
||||
| `--full-validate` | switch | no | JSON.parse every line instead of sampling (slower) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -170,19 +170,19 @@ bl dataset upload --file train.jsonl --no-validate
|
||||
|
||||
### `bl dataset validate`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `dataset validate` |
|
||||
| **Description** | Locally validate a dataset file (.jsonl or .zip) without uploading |
|
||||
| **Usage** | `bl dataset validate --file <path> [--full-validate] [--schema <chatml\|dpo\|cpt\|tts\|image>]` |
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| **Name** | `dataset validate` |
|
||||
| **Description** | Locally validate a dataset file (.jsonl or .zip) without uploading |
|
||||
| **Usage** | `bl dataset validate --file <path> [--full-validate] [--schema <chatml\|dpo\|cpt\|tts\|image\|video>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--file <path>` | string | yes | Local dataset file (.jsonl or .zip) |
|
||||
| `--full-validate` | switch | no | JSON.parse every line instead of sampling (slower) |
|
||||
| `--schema <s>` | string | no | Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), or "image" (image generation). Default auto-detects per record. |
|
||||
| Flag | Type | Required | Description |
|
||||
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--file <path>` | string | yes | Local dataset file (.jsonl or .zip) |
|
||||
| `--full-validate` | switch | no | JSON.parse every line instead of sampling (slower) |
|
||||
| `--schema <s>` | string | no | Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record. |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -191,13 +191,15 @@ bl dataset upload --file train.jsonl --no-validate
|
||||
- Schemas: chatml = {messages:[...]} (SFT); dpo = {messages:[...], chosen,
|
||||
- rejected}; cpt = {text:"..."} (continual pre-training, raw text);
|
||||
- tts = {wav_fn:"train/xxx.wav", text:"..."} (audio fine-tuning);
|
||||
- image = {img_path:"..."} (image generation). With no --schema, a record
|
||||
- carrying wav_fn is validated as TTS, img_path as image, chosen/rejected
|
||||
- as DPO, text (no messages) as CPT, otherwise ChatML. Pass --schema to
|
||||
- require a specific shape on every record. ZIP archives (.zip) are
|
||||
- validated structurally (data.jsonl present, media references resolve) in
|
||||
- addition to per-record content checks. Use --full-validate to JSON.parse
|
||||
- every line.
|
||||
- image = {img_path:"..."} (image generation);
|
||||
- video = {first_frame_path:"...", video_path:"..."} (video generation,
|
||||
- i2v first-frame or kf2v first+last-frame with last_frame_path). With no
|
||||
- --schema, a record carrying wav_fn is validated as TTS, img_path as image,
|
||||
- first_frame_path/video_path as video, chosen/rejected as DPO, text (no
|
||||
- messages) as CPT, otherwise ChatML. Pass --schema to require a specific
|
||||
- shape on every record. ZIP archives (.zip) are validated structurally
|
||||
- (data.jsonl present, media references resolve) in addition to per-record
|
||||
- content checks. Use --full-validate to JSON.parse every line.
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -217,6 +219,10 @@ bl dataset validate --file cpt.jsonl --schema cpt
|
||||
bl dataset validate --file audio.zip --schema tts
|
||||
```
|
||||
|
||||
```bash
|
||||
bl dataset validate --file wan-i2v-training-dataset.zip --schema video
|
||||
```
|
||||
|
||||
```bash
|
||||
bl dataset validate --file eval.jsonl --full-validate
|
||||
```
|
||||
|
||||
@@ -7,43 +7,45 @@ Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------ | --------------------------------------------------------- |
|
||||
| `bl deploy audio create` | Create an audio (TTS) model deployment |
|
||||
| `bl deploy delete` | Delete a model deployment (must be STOPPED or FAILED) |
|
||||
| `bl deploy get` | Get details of a single model deployment |
|
||||
| `bl deploy image create` | Create an image generation model deployment |
|
||||
| `bl deploy list` | List model deployments |
|
||||
| `bl deploy models` | List models available for deployment |
|
||||
| `bl deploy scale` | Scale a deployment's capacity |
|
||||
| `bl deploy text create` | Create a text model deployment |
|
||||
| `bl deploy update` | Update a deployment's rate limits (rpm_limit / tpm_limit) |
|
||||
| Command | Description |
|
||||
| ------------------------ | ------------------------------------------------------------- |
|
||||
| `bl deploy audio create` | Create an audio (TTS) model deployment |
|
||||
| `bl deploy delete` | Delete a model deployment (must be STOPPED or FAILED) |
|
||||
| `bl deploy get` | Get details of a single model deployment |
|
||||
| `bl deploy image create` | Create an image generation model deployment |
|
||||
| `bl deploy list` | List model deployments |
|
||||
| `bl deploy models` | List models available for deployment |
|
||||
| `bl deploy pause` | Pause a running model deployment (stops billing for mu/ptu) |
|
||||
| `bl deploy resume` | Resume a paused model deployment (brings service back online) |
|
||||
| `bl deploy scale` | Scale a deployment's capacity |
|
||||
| `bl deploy text create` | Create a text model deployment |
|
||||
| `bl deploy update` | Update a deployment's rate limits (rpm_limit / tpm_limit) |
|
||||
|
||||
## Command details
|
||||
|
||||
### `bl deploy audio create`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `deploy audio create` |
|
||||
| **Description** | Create an audio (TTS) model deployment |
|
||||
| **Usage** | `bl deploy audio create --model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Name** | `deploy audio create` |
|
||||
| **Description** | Create an audio (TTS) model deployment |
|
||||
| **Usage** | `bl deploy audio create --model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
|
||||
| `--model <name>` | string | yes | Model name (catalog model or fine-tuned output) (required) |
|
||||
| `--name <display_name>` | string | yes | Console display name for the deployment (required) |
|
||||
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
|
||||
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
|
||||
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
|
||||
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
|
||||
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
|
||||
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
|
||||
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
|
||||
| `--model-name <model_name>` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) |
|
||||
| `--display-name <display_name>` | string | yes | Console display name for the deployment (required) |
|
||||
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
|
||||
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
|
||||
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
|
||||
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
|
||||
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
|
||||
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
|
||||
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -58,27 +60,24 @@ Index: [index.md](index.md)
|
||||
- Use `bl deploy models --source base` to inspect available templates.
|
||||
- After creation, status starts at PENDING and transitions to RUNNING.
|
||||
- Invoke the deployed model with: bl text chat --model <deployed_model>
|
||||
- WARNING: --model is overloaded across commands and refers to DIFFERENT
|
||||
- values. `bl deploy <modality> create --model` takes the exported model_name
|
||||
- (e.g. `qwen3-8b-ft-...`), but the create response also returns a
|
||||
- `deployed_model` field (the deployment instance id, e.g.
|
||||
- `qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use
|
||||
- the `deployed_model` from the create response — NOT the `model_name` you
|
||||
- passed to `deploy <modality> create`. Do not reuse the value across the two
|
||||
- commands.
|
||||
- NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).
|
||||
- The create response also returns a `deployed_model` field — the deployment
|
||||
- instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference
|
||||
- (`bl text chat --model <deployed_model>`) and lifecycle commands
|
||||
- (`deploy get/scale/pause/resume/delete --deployed-model <id>`).
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl deploy audio create --model my-cosyvoice-ft --name my-tts
|
||||
bl deploy audio create --model-name my-cosyvoice-ft --display-name my-tts
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy audio create --model my-cosyvoice-ft --name my-tts --deploy-spec dps-xxxx --capacity 1
|
||||
bl deploy audio create --model-name my-cosyvoice-ft --display-name my-tts --deploy-spec dps-xxxx --capacity 1
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy audio create --model my-cosyvoice-ft --name my-tts --dry-run
|
||||
bl deploy audio create --model-name my-cosyvoice-ft --display-name my-tts --dry-run
|
||||
```
|
||||
|
||||
### `bl deploy delete`
|
||||
@@ -136,27 +135,27 @@ bl deploy get --deployed-model qwen-plus-2025-12-01-b6d61c71 --output json
|
||||
|
||||
### `bl deploy image create`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `deploy image create` |
|
||||
| **Description** | Create an image generation model deployment |
|
||||
| **Usage** | `bl deploy image create --model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Name** | `deploy image create` |
|
||||
| **Description** | Create an image generation model deployment |
|
||||
| **Usage** | `bl deploy image create --model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
|
||||
| `--model <name>` | string | yes | Model name (catalog model or fine-tuned output) (required) |
|
||||
| `--name <display_name>` | string | yes | Console display name for the deployment (required) |
|
||||
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
|
||||
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
|
||||
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
|
||||
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
|
||||
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
|
||||
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
|
||||
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
|
||||
| `--model-name <model_name>` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) |
|
||||
| `--display-name <display_name>` | string | yes | Console display name for the deployment (required) |
|
||||
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
|
||||
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
|
||||
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
|
||||
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
|
||||
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
|
||||
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
|
||||
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -171,27 +170,24 @@ bl deploy get --deployed-model qwen-plus-2025-12-01-b6d61c71 --output json
|
||||
- Use `bl deploy models --source base` to inspect available templates.
|
||||
- After creation, status starts at PENDING and transitions to RUNNING.
|
||||
- Invoke the deployed model with: bl text chat --model <deployed_model>
|
||||
- WARNING: --model is overloaded across commands and refers to DIFFERENT
|
||||
- values. `bl deploy <modality> create --model` takes the exported model_name
|
||||
- (e.g. `qwen3-8b-ft-...`), but the create response also returns a
|
||||
- `deployed_model` field (the deployment instance id, e.g.
|
||||
- `qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use
|
||||
- the `deployed_model` from the create response — NOT the `model_name` you
|
||||
- passed to `deploy <modality> create`. Do not reuse the value across the two
|
||||
- commands.
|
||||
- NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).
|
||||
- The create response also returns a `deployed_model` field — the deployment
|
||||
- instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference
|
||||
- (`bl text chat --model <deployed_model>`) and lifecycle commands
|
||||
- (`deploy get/scale/pause/resume/delete --deployed-model <id>`).
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl deploy image create --model my-wan-ft --name my-wan
|
||||
bl deploy image create --model-name my-wan-ft --display-name my-wan
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy image create --model my-wan-ft --name my-wan-mu --plan mu
|
||||
bl deploy image create --model-name my-wan-ft --display-name my-wan-mu --plan mu
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy image create --model my-wan-ft --name my-wan --dry-run
|
||||
bl deploy image create --model-name my-wan-ft --display-name my-wan --dry-run
|
||||
```
|
||||
|
||||
### `bl deploy list`
|
||||
@@ -263,6 +259,82 @@ bl deploy models --source custom --page-size 50
|
||||
bl deploy models --catalog-version v1.0 --output json
|
||||
```
|
||||
|
||||
### `bl deploy pause`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------- |
|
||||
| **Name** | `deploy pause` |
|
||||
| **Description** | Pause a running model deployment (stops billing for mu/ptu) |
|
||||
| **Usage** | `bl deploy pause --deployed-model <id> [--skip-precheck]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||
| `--deployed-model <id>` | string | yes | Deployed model identifier (required) |
|
||||
| `--skip-precheck` | switch | no | Skip the local RUNNING/PENDING status precheck |
|
||||
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||
|
||||
#### Notes
|
||||
|
||||
- While paused, billing ceases for mu/ptu plans. Use `deploy resume` to bring it back online or `deploy delete` to remove.
|
||||
- Precheck verifies status is RUNNING/PENDING before issuing the pause; pass --skip-precheck to bypass.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl deploy pause --deployed-model dep-...
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy pause --deployed-model dep-... --skip-precheck
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy pause --deployed-model dep-... --dry-run
|
||||
```
|
||||
|
||||
### `bl deploy resume`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------- |
|
||||
| **Name** | `deploy resume` |
|
||||
| **Description** | Resume a paused model deployment (brings service back online) |
|
||||
| **Usage** | `bl deploy resume --deployed-model <id> [--skip-precheck]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||
| `--deployed-model <id>` | string | yes | Deployed model identifier (required) |
|
||||
| `--skip-precheck` | switch | no | Skip the local STOPPED status precheck |
|
||||
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Precheck verifies status is STOPPED before issuing the resume; pass --skip-precheck to bypass.
|
||||
- For mu/ptu plans, billing resumes once the service is back online.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl deploy resume --deployed-model dep-...
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy resume --deployed-model dep-... --skip-precheck
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy resume --deployed-model dep-... --dry-run
|
||||
```
|
||||
|
||||
### `bl deploy scale`
|
||||
|
||||
| Field | Value |
|
||||
@@ -294,27 +366,27 @@ bl deploy scale --deployed-model dep-... --capacity 2
|
||||
|
||||
### `bl deploy text create`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `deploy text create` |
|
||||
| **Description** | Create a text model deployment |
|
||||
| **Usage** | `bl deploy text create --model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `deploy text create` |
|
||||
| **Description** | Create a text model deployment |
|
||||
| **Usage** | `bl deploy text create --model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
|
||||
| `--model <name>` | string | yes | Model name (catalog model or fine-tuned output) (required) |
|
||||
| `--name <display_name>` | string | yes | Console display name for the deployment (required) |
|
||||
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
|
||||
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
|
||||
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
|
||||
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
|
||||
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
|
||||
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
|
||||
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
|
||||
| `--model-name <model_name>` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) |
|
||||
| `--display-name <display_name>` | string | yes | Console display name for the deployment (required) |
|
||||
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
|
||||
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
|
||||
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
|
||||
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
|
||||
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
|
||||
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
|
||||
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -329,31 +401,28 @@ bl deploy scale --deployed-model dep-... --capacity 2
|
||||
- Use `bl deploy models --source base` to inspect available templates.
|
||||
- After creation, status starts at PENDING and transitions to RUNNING.
|
||||
- Invoke the deployed model with: bl text chat --model <deployed_model>
|
||||
- WARNING: --model is overloaded across commands and refers to DIFFERENT
|
||||
- values. `bl deploy <modality> create --model` takes the exported model_name
|
||||
- (e.g. `qwen3-8b-ft-...`), but the create response also returns a
|
||||
- `deployed_model` field (the deployment instance id, e.g.
|
||||
- `qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use
|
||||
- the `deployed_model` from the create response — NOT the `model_name` you
|
||||
- passed to `deploy <modality> create`. Do not reuse the value across the two
|
||||
- commands.
|
||||
- NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).
|
||||
- The create response also returns a `deployed_model` field — the deployment
|
||||
- instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference
|
||||
- (`bl text chat --model <deployed_model>`) and lifecycle commands
|
||||
- (`deploy get/scale/pause/resume/delete --deployed-model <id>`).
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl deploy text create --model my-qwen-sft --name my-sft-test
|
||||
bl deploy text create --model-name my-qwen-sft --display-name my-sft-test
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy text create --model qwen3.6-flash-2026-04-16 --name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000
|
||||
bl deploy text create --model-name qwen3.6-flash-2026-04-16 --display-name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy text create --model qwen3-8b --name my-qwen3-mu --plan mu
|
||||
bl deploy text create --model-name qwen3-8b --display-name my-qwen3-mu --plan mu
|
||||
```
|
||||
|
||||
```bash
|
||||
bl deploy text create --model qwen3-8b --name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2
|
||||
bl deploy text create --model-name qwen3-8b --display-name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2
|
||||
```
|
||||
|
||||
### `bl deploy update`
|
||||
|
||||
@@ -19,24 +19,26 @@ Index: [index.md](index.md)
|
||||
| `bl finetune image create` | Create an image generation model fine-tune job (sft-lora) |
|
||||
| `bl finetune list` | List fine-tune jobs |
|
||||
| `bl finetune logs` | Fetch training logs for a fine-tune job |
|
||||
| `bl finetune price` | Estimate the training cost for a fine-tune job (token billing) |
|
||||
| `bl finetune text create` | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) |
|
||||
| `bl finetune video create` | Create a video generation model fine-tune job (Wan i2v/kf2v, efficient_sft) |
|
||||
| `bl finetune watch` | Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal. |
|
||||
|
||||
## Command details
|
||||
|
||||
### `bl finetune audio create`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune audio create` |
|
||||
| **Description** | Create an audio TTS model fine-tune job (sft-lora) |
|
||||
| **Usage** | `bl finetune audio create --model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>]` |
|
||||
| Field | Value |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune audio create` |
|
||||
| **Description** | Create an audio TTS model fine-tune job (sft-lora) |
|
||||
| **Usage** | `bl finetune audio create --base-model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--model <model>` | string | yes | Base model to fine-tune |
|
||||
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
|
||||
| `--datasets <ids\|paths>` | string | yes | Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used. |
|
||||
| `--validations <ids\|paths>` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). |
|
||||
| `--model-name <name>` | string | no | Output model name (after training) |
|
||||
@@ -58,23 +60,23 @@ Index: [index.md](index.md)
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl finetune audio create --model cosyvoice-v3-flash --datasets ./audio.zip
|
||||
bl finetune audio create --base-model cosyvoice-v3-flash --datasets ./audio.zip
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune audio create --model cosyvoice-v3-flash --datasets file-xxx
|
||||
bl finetune audio create --base-model cosyvoice-v3-flash --datasets file-xxx
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune audio create --model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts
|
||||
bl finetune audio create --base-model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune audio create --model cosyvoice-v3-flash --datasets file-xxx --output json
|
||||
bl finetune audio create --base-model cosyvoice-v3-flash --datasets file-xxx --output json
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune audio create --model cosyvoice-v3-flash --datasets ./audio.zip --dry-run
|
||||
bl finetune audio create --base-model cosyvoice-v3-flash --datasets ./audio.zip --dry-run
|
||||
```
|
||||
|
||||
### `bl finetune cancel`
|
||||
@@ -114,18 +116,18 @@ bl finetune cancel --job-id ft-xxx --dry-run
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune capability` |
|
||||
| **Description** | Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it) |
|
||||
| **Usage** | `bl finetune capability --model <m> \| --training-type <t>` |
|
||||
| **Usage** | `bl finetune capability --base-model <m> \| --training-type <t>` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------------------------- |
|
||||
| `--model <m>` | string | no | List training types supported by this base model. |
|
||||
| `--base-model <m>` | string | no | List training types supported by this base model. |
|
||||
| `--training-type <t>` | string | no | List models supporting this training type: sft \| sft-lora \| dpo \| dpo-lora \| cpt. |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Exactly one of --model / --training-type is required.
|
||||
- Exactly one of --base-model / --training-type is required.
|
||||
- Training-type values use the `<method>` / `<method>-lora` convention:
|
||||
- sft | sft-lora | dpo | dpo-lora | cpt. (cpt has no -lora variant server-side.)
|
||||
- Queries listFoundationModels, a public API — no console login needed.
|
||||
@@ -133,7 +135,7 @@ bl finetune cancel --job-id ft-xxx --dry-run
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl finetune capability --model qwen3-8b
|
||||
bl finetune capability --base-model qwen3-8b
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -166,8 +168,8 @@ bl finetune capability --training-type sft --quiet
|
||||
|
||||
#### Notes
|
||||
|
||||
- Use the returned `checkpoint` value with `finetune export` to publish
|
||||
- a deployable model.
|
||||
- `model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model-name`.
|
||||
- Checkpoints expire ~15 days after creation; `expire_time` shows the deadline. Export or deploy before expiry.
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -268,17 +270,17 @@ bl finetune get --job-id ft-xxx --output json
|
||||
|
||||
### `bl finetune image create`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Name** | `finetune image create` |
|
||||
| **Description** | Create an image generation model fine-tune job (sft-lora) |
|
||||
| **Usage** | `bl finetune image create --model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i\|i2i>] [--learning-rate <str>]` |
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune image create` |
|
||||
| **Description** | Create an image generation model fine-tune job (sft-lora) |
|
||||
| **Usage** | `bl finetune image create --base-model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i\|i2i>] [--learning-rate <str>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | yes | Base model to fine-tune |
|
||||
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
|
||||
| `--datasets <ids\|paths>` | string | yes | Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used. |
|
||||
| `--validations <ids\|paths>` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). |
|
||||
| `--model-name <name>` | string | no | Output model name (after training) |
|
||||
@@ -304,46 +306,47 @@ bl finetune get --job-id ft-xxx --output json
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl finetune image create --model wan2.7-image-pro --datasets ./images.zip
|
||||
bl finetune image create --base-model wan2.7-image-pro --datasets ./images.zip
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune image create --model wan2.7-image-pro --datasets file-xxx
|
||||
bl finetune image create --base-model wan2.7-image-pro --datasets file-xxx
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune image create --model wan2.7-image-pro --datasets file-xxx --generation-type i2i
|
||||
bl finetune image create --base-model wan2.7-image-pro --datasets file-xxx --generation-type i2i
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune image create --model wan2.7-image-pro --datasets ./images.zip --model-name my-wan
|
||||
bl finetune image create --base-model wan2.7-image-pro --datasets ./images.zip --model-name my-wan
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune image create --model wan2.7-image-pro --datasets file-xxx --output json
|
||||
bl finetune image create --base-model wan2.7-image-pro --datasets file-xxx --output json
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune image create --model wan2.7-image-pro --datasets ./images.zip --dry-run
|
||||
bl finetune image create --base-model wan2.7-image-pro --datasets ./images.zip --dry-run
|
||||
```
|
||||
|
||||
### `bl finetune list`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ---------------------------------------------------------------- |
|
||||
| **Name** | `finetune list` |
|
||||
| **Description** | List fine-tune jobs |
|
||||
| **Usage** | `bl finetune list [--page <n>] [--page-size <n>] [--status <s>]` |
|
||||
| Field | Value |
|
||||
| --------------- | --------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune list` |
|
||||
| **Description** | List fine-tune jobs |
|
||||
| **Usage** | `bl finetune list [--page <n>] [--page-size <n>] [--status <s>] [--base-model <model>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | -------------------------------------------------------------------- |
|
||||
| `--page <n>` | number | no | Page number (default: 1) |
|
||||
| `--page-size <n>` | number | no | Results per page (default: 10, max 100) |
|
||||
| `--status <s>` | string | no | Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | -------------------------------------------------------------------- |
|
||||
| `--page <n>` | number | no | Page number (default: 1) |
|
||||
| `--page-size <n>` | number | no | Results per page (default: 10, max 100) |
|
||||
| `--status <s>` | string | no | Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED) |
|
||||
| `--base-model <model>` | string | no | Filter by base model ID (server-side) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -356,7 +359,11 @@ bl finetune list --status RUNNING
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune list --page-size 20 --output json
|
||||
bl finetune list --base-model qwen3-8b
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune list --page-size 20
|
||||
```
|
||||
|
||||
### `bl finetune logs`
|
||||
@@ -405,19 +412,60 @@ bl finetune logs --job-id ft-xxx --tail 20
|
||||
bl finetune logs --job-id ft-xxx --search checkpoint --tail 5
|
||||
```
|
||||
|
||||
### `bl finetune price`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune price` |
|
||||
| **Description** | Estimate the training cost for a fine-tune job (token billing) |
|
||||
| **Usage** | `bl finetune price --base-model <model> --datasets <ids> [--training-type <type>] [--n-epochs <n>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------ |
|
||||
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
|
||||
| `--datasets <ids>` | string | yes | Training dataset file IDs, comma-separated (required) |
|
||||
| `--training-type <type>` | string | no | Training type: sft \| dpo \| cpt (default: sft) |
|
||||
| `--n-epochs <n>` | number | no | Number of training epochs (default: 3) |
|
||||
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Estimate only — the server computes token usage from the datasets; final cost is subject to the bill.
|
||||
- Covers token billing for sft / dpo / cpt. Training-unit (MTU) billing is not supported by this command.
|
||||
- Hyper-parameters other than --n-epochs are fixed at representative defaults for estimation.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl finetune price --base-model qwen3-8b --datasets file-ft-xxx
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune price --base-model qwen3-8b --datasets file-ft-xxx,file-ft-yyy --n-epochs 2
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune price --base-model qwen3-8b --datasets file-ft-xxx --training-type cpt
|
||||
```
|
||||
|
||||
### `bl finetune text create`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune text create` |
|
||||
| **Description** | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) |
|
||||
| **Usage** | `bl finetune text create --model <model> --datasets <id\|path,...> [--validations <id\|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft\|sft-lora\|dpo\|dpo-lora\|cpt>]` |
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Name** | `finetune text create` |
|
||||
| **Description** | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) |
|
||||
| **Usage** | `bl finetune text create --base-model <model> --datasets <id\|path,...> [--validations <id\|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft\|sft-lora\|dpo\|dpo-lora\|cpt>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | yes | Base model to fine-tune |
|
||||
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
|
||||
| `--datasets <ids\|paths>` | string | yes | Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used. |
|
||||
| `--validations <ids\|paths>` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). |
|
||||
| `--model-name <name>` | string | no | Output model name (after training) |
|
||||
@@ -453,35 +501,89 @@ bl finetune logs --job-id ft-xxx --search checkpoint --tail 5
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets file-xxx
|
||||
bl finetune text create --base-model qwen3-8b --datasets file-xxx
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl
|
||||
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl
|
||||
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets file-aaa,./extra.jsonl
|
||||
bl finetune text create --base-model qwen3-8b --datasets file-aaa,./extra.jsonl
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft
|
||||
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --training-type sft
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4
|
||||
bl finetune text create --base-model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets file-xxx --output json
|
||||
bl finetune text create --base-model qwen3-8b --datasets file-xxx --output json
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune text create --model qwen3-8b --datasets file-xxx --dry-run
|
||||
bl finetune text create --base-model qwen3-8b --datasets file-xxx --dry-run
|
||||
```
|
||||
|
||||
### `bl finetune video create`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `finetune video create` |
|
||||
| **Description** | Create a video generation model fine-tune job (Wan i2v/kf2v, efficient_sft) |
|
||||
| **Usage** | `bl finetune video create --base-model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
|
||||
| `--datasets <ids\|paths>` | string | yes | Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used. |
|
||||
| `--validations <ids\|paths>` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). |
|
||||
| `--model-name <name>` | string | no | Output model name (after training) |
|
||||
| `--suffix <text>` | string | no | Output suffix appended by the platform (finetuned_output_suffix) |
|
||||
| `--n-epochs <n>` | number | no | Training epochs (default: 50) |
|
||||
| `--batch-size <n>` | number | no | Batch size (default: model-specific, 1 for wan2.7, 4 for wan2.5/2.2) |
|
||||
| `--learning-rate <str>` | string | no | Learning rate as a string to preserve precision (default: "2e-5") |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Creating a job uploads any local datasets and consumes training quota.
|
||||
- Use --dry-run to preview the request body without submitting.
|
||||
- --datasets / --validations accept either file-ids (from `dataset upload`)
|
||||
- or local paths. Local paths are validated and uploaded first, then their
|
||||
- file-ids are submitted — a one-step upload-and-train.
|
||||
- Video generation training (Wan i2v/kf2v) runs efficient_sft with model-
|
||||
- specific defaults: wan2.7 (batch_size=1, max_pixels=102400), wan2.5/2.2
|
||||
- (batch_size=4, max_pixels per model). Override with --batch-size/--n-epochs.
|
||||
- Datasets are .zip archives with data.jsonl + frame images + videos.
|
||||
- Recommended: ≥10 training samples, 20-100 for stable results.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl finetune video create --base-model wan2.7-i2v --datasets file-xxx
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune video create --base-model wan2.7-i2v --datasets ./i2v-data.zip
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune video create --base-model wan2.2-kf2v-flash --datasets file-xxx --n-epochs 100
|
||||
```
|
||||
|
||||
```bash
|
||||
bl finetune video create --base-model wan2.7-i2v --datasets file-xxx --dry-run
|
||||
```
|
||||
|
||||
### `bl finetune watch`
|
||||
|
||||
@@ -22,6 +22,8 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl deploy image create` | Create an image generation model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy list` | List model deployments | [deploy.md](deploy.md) |
|
||||
| `bl deploy models` | List models available for deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy pause` | Pause a running model deployment (stops billing for mu/ptu) | [deploy.md](deploy.md) |
|
||||
| `bl deploy resume` | Resume a paused model deployment (brings service back online) | [deploy.md](deploy.md) |
|
||||
| `bl deploy scale` | Scale a deployment's capacity | [deploy.md](deploy.md) |
|
||||
| `bl deploy text create` | Create a text model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy update` | Update a deployment's rate limits (rpm_limit / tpm_limit) | [deploy.md](deploy.md) |
|
||||
@@ -35,16 +37,18 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl finetune image create` | Create an image generation model fine-tune job (sft-lora) | [finetune.md](finetune.md) |
|
||||
| `bl finetune list` | List fine-tune jobs | [finetune.md](finetune.md) |
|
||||
| `bl finetune logs` | Fetch training logs for a fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune price` | Estimate the training cost for a fine-tune job (token billing) | [finetune.md](finetune.md) |
|
||||
| `bl finetune text create` | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) | [finetune.md](finetune.md) |
|
||||
| `bl finetune video create` | Create a video generation model fine-tune job (Wan i2v/kf2v, efficient_sft) | [finetune.md](finetune.md) |
|
||||
| `bl finetune watch` | Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal. | [finetune.md](finetune.md) |
|
||||
|
||||
## By group
|
||||
|
||||
| Group | Commands | Reference |
|
||||
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) |
|
||||
| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `scale`, `text create`, `update` | [deploy.md](deploy.md) |
|
||||
| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `text create`, `watch` | [finetune.md](finetune.md) |
|
||||
| Group | Commands | Reference |
|
||||
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) |
|
||||
| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `pause`, `resume`, `scale`, `text create`, `update` | [deploy.md](deploy.md) |
|
||||
| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `price`, `text create`, `video create`, `watch` | [finetune.md](finetune.md) |
|
||||
|
||||
## Global flags
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-gen
|
||||
metadata:
|
||||
version: "1.14.1"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -108,6 +108,7 @@ bl video edit --video https://example.com/input.mp4 --prompt "Put clothes on the
|
||||
| `--model <model>` | string | no | Model ID (default: happyhorse-1.1-t2v, or happyhorse-1.1-i2v with --image) |
|
||||
| `--prompt <text>` | string | yes | Video description |
|
||||
| `--image <url>` | string | no | Input image URL for image-to-video generation |
|
||||
| `--last-frame <url>` | string | no | Last frame image URL (with --image, enables kf2v first+last frame mode) |
|
||||
| `--negative-prompt <text>` | string | no | Negative prompt to exclude unwanted content |
|
||||
| `--resolution <res>` | string | no | Resolution: 720P or 1080P (default: 1080P) |
|
||||
| `--ratio <ratio>` | string | no | Aspect ratio (e.g. 16:9, 9:16, 1:1) |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-managed-agent
|
||||
metadata:
|
||||
version: "1.14.1"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-protocol
|
||||
metadata:
|
||||
version: "1.14.1"
|
||||
version: "1.14.2"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
Reference in New Issue
Block a user