Skip to content

Commit 03405d5

Browse files
authored
Merge pull request #91 from modelstudioai/feat/model-deploy-update
feat: add tts/i2i/t2i model deploy
2 parents 03f0e7c + 1870500 commit 03405d5

57 files changed

Lines changed: 3178 additions & 739 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ CLI 只为「自己能权威解释的错误」发出语义化信号,服务端的
104104

105105
如果命令调用 Console Gateway,`defineCommand` 必须设置 `auth: "console"`。runtime 会基于 `CONSOLE_AUTH_FLAGS` 自动在 help 中展示 `--console-region``--console-site``--console-switch-agent``--workspace-id`,并由 `authStage` 解析/注入 console credential。命令不要重复声明这些凭证域 flag,也不要手动从 env/config 解析 token。
106106

107+
### 5. 禁止单字母变量命名
108+
109+
所有变量、参数、回调形参必须使用有语义的命名,不允许单字母(如 `i``m``p``t``e``s`)。具体表现:
110+
111+
- 回调参数: `.map((m) => ...)``.map((model) => ...)`, `.find((t) => ...)``.find((template) => ...)`
112+
- catch 变量: `catch (e)``catch (error)`
113+
- for-of 循环: `for (const i of items)``for (const item of items)`
114+
- 临时变量: `const s = ...``const strategy = ...`
115+
116+
例外: 仅当作用域极小(≤3 行)且语义从上下文完全明确时,可使用 `k`/`v`(Object.entries 的 key/value)。
117+
107118
## 完成改动后的快速验证
108119

109120
```sh

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Equip your AI Agent out-of-the-box with these capabilities, composable across co
3838
- **MCP integration** — Orchestrate Bailian MCP servers: list services, inspect tools, and invoke any tool directly from the terminal
3939
- **Web search** — Real-time internet retrieval for up-to-date, accurate answers
4040
- **Model recommendation** — Describe your scenario and get best-fit model suggestions; supports scoped search, model comparison, and alternative discovery
41-
- **Fine-tuning & deployment** — Upload datasets, create SFT/LoRA/DPO/CPT jobs (`finetune create`), probe job status non-blockingly (`finetune watch`), query per-model training capability (`finetune capability`), and deploy trained models as endpoints (`deploy create`)
41+
- **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`)
4242
- **Console capabilities** — Browse Bailian apps (`app list`), 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`)
4343
- **Local file auto-upload** — Every URL parameter accepts a local path; uploaded to free temp storage with 48-hour validity
4444

@@ -114,10 +114,10 @@ bl auth login --console
114114

115115
# Fine-tune & deploy — a one-shot train-to-serve workflow
116116
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
117-
bl finetune create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
117+
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
118118
bl finetune watch --job-id ft-xxx --output json # Non-blocking status probe (exit 0/1/3 = done/failed/running)
119119
bl finetune capability --model qwen3-8b # Which training types a model supports
120-
bl deploy create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
120+
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
121121

122122
# Browse apps / free-tier quota / usage statistics / workspaces
123123
bl app list

README.zh.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
3838
- **MCP 集成** — 统一调度百炼 MCP 服务:列出服务、查看工具、直接在终端调用任意工具
3939
- **联网搜索** — 实时互联网信息检索,提升回答准确性及时效性
4040
- **模型推荐** — 描述你的场景,智能推荐最适合的模型;支持限定范围搜索、模型对比和替代发现
41-
- **微调与部署** — 上传数据集、创建 SFT/LoRA/DPO/CPT 调优任务`finetune create`)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy create`
41+
- **微调与部署** — 上传数据集、创建文本/音频/图像调优任务`finetune text|audio|image create`;文本涵盖 SFT/LoRA/DPO/CPT)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy text|audio|image create`
4242
- **控制台能力** — 浏览百炼应用(`app list`),查询模型免费额度(`usage free`),查看模型用量统计(`usage stats`),管理业务空间(`workspace list`),管理限流与提额(`quota list/request/check/history`
4343
- **本地文件自动上传** — 所有 URL 参数同时支持本地路径,免费临时存储 48 小时
4444

@@ -112,10 +112,10 @@ bl auth login --console
112112

113113
# 微调与部署 — 从训练到服务的一站式流程
114114
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
115-
bl finetune create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
115+
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
116116
bl finetune watch --job-id ft-xxx --output json # 非阻塞状态探测(退出码 0/1/3 = 成功/失败/进行中)
117117
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
118-
bl deploy create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
118+
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
119119

120120
# 浏览应用 / 免费额度 / 用量统计 / 业务空间
121121
bl app list

packages/cli/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Equip your AI Agent out-of-the-box with these capabilities, composable across co
3838
- **MCP integration** — Orchestrate Bailian MCP servers: list services, inspect tools, and invoke any tool directly from the terminal
3939
- **Web search** — Real-time internet retrieval for up-to-date, accurate answers
4040
- **Model recommendation** — Describe your scenario and get best-fit model suggestions; supports scoped search, model comparison, and alternative discovery
41-
- **Fine-tuning & deployment** — Upload datasets, create SFT/LoRA/DPO/CPT jobs (`finetune create`), probe job status non-blockingly (`finetune watch`), query per-model training capability (`finetune capability`), and deploy trained models as endpoints (`deploy create`)
41+
- **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`)
4242
- **Console capabilities** — Browse Bailian apps (`app list`), 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`)
4343
- **Local file auto-upload** — Every URL parameter accepts a local path; uploaded to free temp storage with 48-hour validity
4444

@@ -114,10 +114,10 @@ bl auth login --console
114114

115115
# Fine-tune & deploy — a one-shot train-to-serve workflow
116116
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
117-
bl finetune create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
117+
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
118118
bl finetune watch --job-id ft-xxx --output json # Non-blocking status probe (exit 0/1/3 = done/failed/running)
119119
bl finetune capability --model qwen3-8b # Which training types a model supports
120-
bl deploy create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
120+
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
121121

122122
# Browse apps / free-tier quota / usage statistics / workspaces
123123
bl app list

packages/cli/README.zh.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
3838
- **MCP 集成** — 统一调度百炼 MCP 服务:列出服务、查看工具、直接在终端调用任意工具
3939
- **联网搜索** — 实时互联网信息检索,提升回答准确性及时效性
4040
- **模型推荐** — 描述你的场景,智能推荐最适合的模型;支持限定范围搜索、模型对比和替代发现
41-
- **微调与部署** — 上传数据集、创建 SFT/LoRA/DPO/CPT 调优任务`finetune create`)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy create`
41+
- **微调与部署** — 上传数据集、创建文本/音频/图像调优任务`finetune text|audio|image create`;文本涵盖 SFT/LoRA/DPO/CPT)、非阻塞探测任务状态(`finetune watch`)、按模型查训练能力(`finetune capability`),并把训练好的模型部署为推理服务(`deploy text|audio|image create`
4242
- **控制台能力** — 浏览百炼应用(`app list`),查询模型免费额度(`usage free`),查看模型用量统计(`usage stats`),管理业务空间(`workspace list`),管理限流与提额(`quota list/request/check/history`
4343
- **本地文件自动上传** — 所有 URL 参数同时支持本地路径,免费临时存储 48 小时
4444

@@ -112,10 +112,10 @@ bl auth login --console
112112

113113
# 微调与部署 — 从训练到服务的一站式流程
114114
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
115-
bl finetune create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
115+
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
116116
bl finetune watch --job-id ft-xxx --output json # 非阻塞状态探测(退出码 0/1/3 = 成功/失败/进行中)
117117
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
118-
bl deploy create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
118+
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
119119

120120
# 浏览应用 / 免费额度 / 用量统计 / 业务空间
121121
bl app list

packages/cli/src/commands.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ import {
5252
datasetGet,
5353
datasetDelete,
5454
datasetValidate,
55-
finetuneCreate,
55+
finetuneTextCreate,
56+
finetuneAudioCreate,
57+
finetuneImageCreate,
5658
finetuneList,
5759
finetuneGet,
5860
finetuneCancel,
@@ -62,7 +64,9 @@ import {
6264
finetuneExport,
6365
finetuneWatch,
6466
finetuneCapability,
65-
deployCreate,
67+
deployTextCreate,
68+
deployAudioCreate,
69+
deployImageCreate,
6670
deployList,
6771
deployGet,
6872
deployModels,
@@ -133,7 +137,9 @@ export const commands: Record<string, AnyCommand> = {
133137
"dataset get": datasetGet,
134138
"dataset delete": datasetDelete,
135139
"dataset validate": datasetValidate,
136-
"finetune create": finetuneCreate,
140+
"finetune text create": finetuneTextCreate,
141+
"finetune audio create": finetuneAudioCreate,
142+
"finetune image create": finetuneImageCreate,
137143
"finetune list": finetuneList,
138144
"finetune get": finetuneGet,
139145
"finetune cancel": finetuneCancel,
@@ -143,7 +149,9 @@ export const commands: Record<string, AnyCommand> = {
143149
"finetune export": finetuneExport,
144150
"finetune watch": finetuneWatch,
145151
"finetune capability": finetuneCapability,
146-
"deploy create": deployCreate,
152+
"deploy text create": deployTextCreate,
153+
"deploy audio create": deployAudioCreate,
154+
"deploy image create": deployImageCreate,
147155
"deploy list": deployList,
148156
"deploy get": deployGet,
149157
"deploy models": deployModels,

packages/cli/tests/e2e/dataset.e2e.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,83 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
214214
expect(data.action).toBe("dataset.upload");
215215
expect(data.schema).toBe("dpo");
216216
});
217+
218+
test("dataset upload --schema image --no-validate --dry-run 采用 1GB 媒体上限", async () => {
219+
// image schema raises the upload cap to 1 GiB (vs 300 MB for text).
220+
// --no-validate keeps this offline (the jsonl fixture is not a real zip).
221+
const file = join(__dirname, ".dataset-valid.jsonl");
222+
const { stdout, stderr, exitCode } = await runCli([
223+
"dataset",
224+
"upload",
225+
"--file",
226+
file,
227+
"--schema",
228+
"image",
229+
"--no-validate",
230+
"--dry-run",
231+
"--output",
232+
"json",
233+
]);
234+
expect(exitCode, stderr).toBe(0);
235+
const data = parseStdoutJson<{ action: string; schema: string; max_bytes: number }>(stdout);
236+
expect(data.action).toBe("dataset.upload");
237+
expect(data.schema).toBe("image");
238+
expect(data.max_bytes).toBe(1024 * 1024 * 1024);
239+
});
240+
241+
test.each(["tts", "image"])("dataset upload --dry-run 接受媒体 schema %s", async (schema) => {
242+
const file = join(__dirname, ".dataset-valid.jsonl");
243+
const { stdout, stderr, exitCode } = await runCli([
244+
"dataset",
245+
"upload",
246+
"--file",
247+
file,
248+
"--schema",
249+
schema,
250+
"--no-validate",
251+
"--dry-run",
252+
"--output",
253+
"json",
254+
]);
255+
expect(exitCode, stderr).toBe(0);
256+
const data = parseStdoutJson<{ action: string; schema: string }>(stdout);
257+
expect(data.action).toBe("dataset.upload");
258+
expect(data.schema).toBe(schema);
259+
});
260+
261+
test("dataset validate --schema video 拒绝(视频生成入口已隐藏)", async () => {
262+
const file = join(__dirname, ".dataset-valid.jsonl");
263+
const { stdout, stderr, exitCode } = await runCli([
264+
"dataset",
265+
"validate",
266+
"--file",
267+
file,
268+
"--schema",
269+
"video",
270+
"--output",
271+
"json",
272+
]);
273+
expect(exitCode, stdout + stderr).not.toBe(0);
274+
expect(`${stdout}\n${stderr}`).toMatch(/--schema video is not supported/);
275+
});
276+
277+
test("dataset upload --schema video 拒绝(视频生成入口已隐藏)", async () => {
278+
const file = join(__dirname, ".dataset-valid.jsonl");
279+
const { stdout, stderr, exitCode } = await runCli([
280+
"dataset",
281+
"upload",
282+
"--file",
283+
file,
284+
"--schema",
285+
"video",
286+
"--no-validate",
287+
"--dry-run",
288+
"--output",
289+
"json",
290+
]);
291+
expect(exitCode, stdout + stderr).not.toBe(0);
292+
expect(`${stdout}\n${stderr}`).toMatch(/--schema video is not supported/);
293+
});
217294
});
218295

219296
describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (DashScope)", () => {

packages/cli/tests/e2e/deploy.e2e.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
2222
});
2323

2424
test("deploy create --help 正常退出并展示必填项", async () => {
25-
const { stderr, exitCode } = await runCli(["deploy", "create", "--help"]);
25+
const { stderr, exitCode } = await runCli(["deploy", "text", "create", "--help"]);
2626
expect(exitCode, stderr).toBe(0);
2727
expect(stderr).toMatch(/--model|--name/i);
2828
});
2929

3030
test("deploy create --dry-run 构造 lora 部署请求体", async () => {
3131
const { stdout, stderr, exitCode } = await runCli([
3232
"deploy",
33+
"text",
3334
"create",
3435
"--model",
3536
"qwen-plus-2025-12-01",
@@ -56,6 +57,65 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
5657
expect(data.body.capacity).toBe(1);
5758
});
5859

60+
test("deploy create --plan mu --deploy-spec --dry-run 透传 deploy_spec", async () => {
61+
const { stdout, stderr, exitCode } = await runCli([
62+
"deploy",
63+
"text",
64+
"create",
65+
"--model",
66+
"qwen3-8b",
67+
"--name",
68+
"my-qwen3-mu",
69+
"--plan",
70+
"mu",
71+
"--deploy-spec",
72+
"MU1",
73+
"--capacity",
74+
"2",
75+
"--dry-run",
76+
"--output",
77+
"json",
78+
]);
79+
expect(exitCode, stderr).toBe(0);
80+
const data = parseStdoutJson<{
81+
action: string;
82+
body: { plan: string; deploy_spec?: string; capacity?: number };
83+
}>(stdout);
84+
expect(data.action).toBe("deploy.create");
85+
expect(data.body.plan).toBe("mu");
86+
expect(data.body.deploy_spec).toBe("MU1");
87+
expect(data.body.capacity).toBe(2);
88+
});
89+
90+
test("deploy audio create --dry-run 默认 plan=mu(CosyVoice 部署契约)", async () => {
91+
// Audio (CosyVoice TTS) outputs deploy model-unit-billed: the modality fixes
92+
// the default plan to `mu` (text/image stay `lora`). In dry-run the mu
93+
// strategy skips the catalog lookup, so deploy_spec is omitted and capacity
94+
// falls back to 1 with billing_method POST_PAY.
95+
const { stdout, stderr, exitCode } = await runCli([
96+
"deploy",
97+
"audio",
98+
"create",
99+
"--model",
100+
"my-cosyvoice-ft",
101+
"--name",
102+
"my-tts",
103+
"--dry-run",
104+
"--output",
105+
"json",
106+
]);
107+
expect(exitCode, stderr).toBe(0);
108+
const data = parseStdoutJson<{
109+
action: string;
110+
body: { plan: string; name: string; billing_method?: string; capacity?: number };
111+
}>(stdout);
112+
expect(data.action).toBe("deploy.create");
113+
expect(data.body.plan).toBe("mu");
114+
expect(data.body.name).toBe("my-tts");
115+
expect(data.body.billing_method).toBe("POST_PAY");
116+
expect(data.body.capacity).toBe(1);
117+
});
118+
59119
test("deploy scale --dry-run 转发 capacity", async () => {
60120
const { stdout, stderr, exitCode } = await runCli([
61121
"deploy",

0 commit comments

Comments
 (0)