Skip to content

Commit 8fd072b

Browse files
committed
feat: merge self-built-framework
1 parent 49095c3 commit 8fd072b

42 files changed

Lines changed: 2120 additions & 232 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

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,52 @@ 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/video schemas raise 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", "video"])(
242+
"dataset upload --dry-run 接受媒体 schema %s",
243+
async (schema) => {
244+
const file = join(__dirname, ".dataset-valid.jsonl");
245+
const { stdout, stderr, exitCode } = await runCli([
246+
"dataset",
247+
"upload",
248+
"--file",
249+
file,
250+
"--schema",
251+
schema,
252+
"--no-validate",
253+
"--dry-run",
254+
"--output",
255+
"json",
256+
]);
257+
expect(exitCode, stderr).toBe(0);
258+
const data = parseStdoutJson<{ action: string; schema: string }>(stdout);
259+
expect(data.action).toBe("dataset.upload");
260+
expect(data.schema).toBe(schema);
261+
},
262+
);
217263
});
218264

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

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,98 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
5656
expect(data.body.capacity).toBe(1);
5757
});
5858

59+
test("deploy create --dry-run 组装视频 LoRA 的 aigc_config", async () => {
60+
const { stdout, stderr, exitCode } = await runCli([
61+
"deploy",
62+
"create",
63+
"--model",
64+
"wan2.5-i2v-preview-ft-xxx",
65+
"--name",
66+
"my-video-lora",
67+
"--aigc-prompt",
68+
"a cat surfing",
69+
"--aigc-lora-prompt-default",
70+
"trigger-word",
71+
"--aigc-use-input-prompt",
72+
"true",
73+
"--dry-run",
74+
"--output",
75+
"json",
76+
]);
77+
expect(exitCode, stderr).toBe(0);
78+
const data = parseStdoutJson<{
79+
action: string;
80+
body: {
81+
plan: string;
82+
aigc_config?: {
83+
use_input_prompt?: boolean;
84+
prompt?: string;
85+
lora_prompt_default?: string;
86+
};
87+
};
88+
}>(stdout);
89+
expect(data.action).toBe("deploy.create");
90+
expect(data.body.plan).toBe("lora");
91+
expect(data.body.aigc_config).toEqual({
92+
use_input_prompt: true,
93+
prompt: "a cat surfing",
94+
lora_prompt_default: "trigger-word",
95+
});
96+
});
97+
98+
test("deploy create --aigc-* 仅对 plan=lora 有效(plan=ptu 时报错)", async () => {
99+
const { stdout, stderr, exitCode } = await runCli([
100+
"deploy",
101+
"create",
102+
"--model",
103+
"wan2.5-i2v-preview-ft-xxx",
104+
"--name",
105+
"my-video-lora",
106+
"--plan",
107+
"ptu",
108+
"--input-tpm",
109+
"10000",
110+
"--output-tpm",
111+
"1000",
112+
"--aigc-prompt",
113+
"a cat surfing",
114+
"--dry-run",
115+
"--output",
116+
"json",
117+
]);
118+
expect(exitCode, stdout + stderr).not.toBe(0);
119+
expect(`${stdout}\n${stderr}`).toMatch(/--aigc-\* flags are only valid for plan=lora/);
120+
});
121+
122+
test("deploy create --plan mu --deploy-spec --dry-run 透传 deploy_spec", async () => {
123+
const { stdout, stderr, exitCode } = await runCli([
124+
"deploy",
125+
"create",
126+
"--model",
127+
"qwen3-8b",
128+
"--name",
129+
"my-qwen3-mu",
130+
"--plan",
131+
"mu",
132+
"--deploy-spec",
133+
"MU1",
134+
"--capacity",
135+
"2",
136+
"--dry-run",
137+
"--output",
138+
"json",
139+
]);
140+
expect(exitCode, stderr).toBe(0);
141+
const data = parseStdoutJson<{
142+
action: string;
143+
body: { plan: string; deploy_spec?: string; capacity?: number };
144+
}>(stdout);
145+
expect(data.action).toBe("deploy.create");
146+
expect(data.body.plan).toBe("mu");
147+
expect(data.body.deploy_spec).toBe("MU1");
148+
expect(data.body.capacity).toBe(2);
149+
});
150+
59151
test("deploy scale --dry-run 转发 capacity", async () => {
60152
const { stdout, stderr, exitCode } = await runCli([
61153
"deploy",

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,35 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
114114
});
115115
});
116116

117+
test.each([
118+
["sft", "sft"],
119+
["sft-lora", "efficient_sft"],
120+
["dpo", "dpo_full"],
121+
["dpo-lora", "dpo_lora"],
122+
["cpt", "cpt"],
123+
])(
124+
"finetune create --training-type %s 经 profile 映射为 server 类型 %s",
125+
async (cliType, serverType) => {
126+
const { stdout, stderr, exitCode } = await runCli([
127+
"finetune",
128+
"create",
129+
"--model",
130+
"qwen3-8b",
131+
"--datasets",
132+
"file-aaa",
133+
"--training-type",
134+
cliType,
135+
"--dry-run",
136+
"--output",
137+
"json",
138+
]);
139+
expect(exitCode, stderr).toBe(0);
140+
const data = parseStdoutJson<{ action: string; body: { training_type: string } }>(stdout);
141+
expect(data.action).toBe("finetune.create");
142+
expect(data.body.training_type).toBe(serverType);
143+
},
144+
);
145+
117146
test("finetune create --training-type 拒绝不支持的训练类型值", async () => {
118147
const { stdout, stderr, exitCode } = await runCli([
119148
"finetune",

packages/commands/src/commands/dataset/upload.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
parseDatasetSchemaFlag,
77
formatIssue,
88
MAX_DATASET_BYTES,
9+
MAX_MEDIA_ZIP_BYTES,
910
BailianError,
1011
ExitCode,
1112
type DatasetFile,
@@ -17,7 +18,7 @@ const UPLOAD_FLAGS = {
1718
file: {
1819
type: "string",
1920
valueHint: "<path>",
20-
description: "Local .jsonl dataset file (≤300MB)",
21+
description: "Local dataset file (.jsonl or .zip; ≤300MB text, ≤1GB image/video)",
2122
required: true,
2223
},
2324
purpose: {
@@ -29,7 +30,7 @@ const UPLOAD_FLAGS = {
2930
type: "string",
3031
valueHint: "<s>",
3132
description:
32-
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), or "cpt" (raw text). Default auto-detects per record.',
33+
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.',
3334
},
3435
noValidate: {
3536
type: "switch",
@@ -42,30 +43,31 @@ const UPLOAD_FLAGS = {
4243
} satisfies FlagsDef;
4344

4445
export default defineCommand({
45-
description: "Upload a dataset file (.jsonl) to Bailian",
46+
description: "Upload a dataset file (.jsonl or .zip) to Bailian",
4647
auth: "apiKey",
4748
usageArgs:
48-
"--file <path> [--purpose <name>] [--schema <chatml|dpo|cpt>] [--no-validate] [--full-validate]",
49+
"--file <path> [--purpose <name>] [--schema <chatml|dpo|cpt|tts|image|video>] [--no-validate] [--full-validate]",
4950
flags: UPLOAD_FLAGS,
5051
exampleArgs: [
5152
"--file train.jsonl",
5253
"--file dpo.jsonl --schema dpo",
5354
"--file cpt.jsonl --schema cpt",
55+
"--file audio.zip --schema tts",
5456
"--file eval.jsonl --purpose evaluation",
5557
"--file train.jsonl --full-validate",
5658
"--file train.jsonl --no-validate",
5759
],
5860
notes: [
59-
"Only .jsonl is supported in this release. Three record schemas are",
60-
"recognized: chatml = {messages:[...]} (SFT); dpo = {messages:[...],",
61-
"chosen, rejected} where chosen/rejected are single assistant messages;",
62-
'cpt = {text:"..."} (continual pre-training, raw text). With no --schema,',
63-
"a record carrying chosen/rejected is validated as DPO, one with text (and",
64-
"no messages) as CPT, otherwise as ChatML. Pass --schema dpo / cpt to",
65-
"require that shape on every record, or --schema chatml to ignore the",
66-
"preference / text fields. Other purposes may carry a different schema in",
67-
"the future and would be served by a purpose-specific validator.",
68-
"The dataset upload cap is 300MB per file.",
61+
"Supports .jsonl (text) and .zip (audio/image/video archives with a",
62+
"data.jsonl manifest). Six record schemas are recognized: chatml =",
63+
"{messages:[...]} (SFT); dpo = {messages:[...], chosen, rejected};",
64+
'cpt = {text:"..."} (continual pre-training, raw text); tts =',
65+
'{wav_fn:"train/xxx.wav", text:"..."} (audio fine-tuning); image =',
66+
'{img_path:"..."} (image generation); video = {first_frame_path:"...",',
67+
'video_path:"..."} (video generation). With no --schema, a record',
68+
"carrying wav_fn is validated as TTS, img_path as image, video_path /",
69+
"first_frame_path as video, chosen/rejected as DPO, text (no messages)",
70+
"as CPT, otherwise ChatML. Upload cap: 300MB text, 1GB image/video.",
6971
"Upload uses the OpenAI-compatible /compatible-mode/v1/files endpoint so",
7072
"the purpose tag is persisted (the DashScope-native /api/v1/files drops it).",
7173
],
@@ -75,9 +77,16 @@ export default defineCommand({
7577
const purpose = flags.purpose || "fine-tune";
7678
const schema = parseDatasetSchemaFlag(flags.schema);
7779
const format = detectOutputFormat(settings.output);
80+
// Image / video schemas allow larger ZIPs (1 GB vs 300 MB for text).
81+
const isMediaSchema = schema === "image" || schema === "video";
7882

7983
if (!flags.noValidate) {
80-
const result = await validateDataset(filePath, { fullValidate: flags.fullValidate, schema });
84+
const maxBytes = isMediaSchema ? MAX_MEDIA_ZIP_BYTES : MAX_DATASET_BYTES;
85+
const result = await validateDataset(filePath, {
86+
fullValidate: flags.fullValidate,
87+
schema,
88+
maxBytes,
89+
});
8190
if (!result.valid) {
8291
const lines = [
8392
`Dataset validation failed for ${filePath}`,
@@ -112,7 +121,7 @@ export default defineCommand({
112121
action: "dataset.upload",
113122
file: filePath,
114123
purpose,
115-
max_bytes: MAX_DATASET_BYTES,
124+
max_bytes: isMediaSchema ? MAX_MEDIA_ZIP_BYTES : MAX_DATASET_BYTES,
116125
validate: !flags.noValidate,
117126
schema: schema ?? "auto",
118127
},

packages/commands/src/commands/dataset/validate.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const VALIDATE_FLAGS = {
2525
file: {
2626
type: "string",
2727
valueHint: "<path>",
28-
description: "Local .jsonl dataset file",
28+
description: "Local dataset file (.jsonl or .zip)",
2929
required: true,
3030
},
3131
fullValidate: {
@@ -36,33 +36,38 @@ const VALIDATE_FLAGS = {
3636
type: "string",
3737
valueHint: "<s>",
3838
description:
39-
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), or "cpt" (raw text). Default auto-detects per record.',
39+
'Record schema: "chatml" (SFT), "dpo" (chosen/rejected), "cpt" (raw text), "tts" (audio), "image" (image generation), or "video" (video generation). Default auto-detects per record.',
4040
},
4141
} satisfies FlagsDef;
4242

4343
export default defineCommand({
44-
description: "Locally validate a dataset file (.jsonl) without uploading",
44+
description: "Locally validate a dataset file (.jsonl or .zip) without uploading",
4545
// 纯本地校验,不触网、不需 API key(与 `pipeline validate` 一致)。
4646
auth: "none",
47-
usageArgs: "--file <path> [--full-validate] [--schema <chatml|dpo|cpt>]",
47+
usageArgs: "--file <path> [--full-validate] [--schema <chatml|dpo|cpt|tts|image|video>]",
4848
flags: VALIDATE_FLAGS,
4949
exampleArgs: [
5050
"--file train.jsonl",
5151
"--file dpo.jsonl --schema dpo",
5252
"--file cpt.jsonl --schema cpt",
53+
"--file audio.zip --schema tts",
5354
"--file eval.jsonl --full-validate",
5455
"--file train.jsonl --output json",
5556
],
5657
notes: [
5758
"Default scan: every line gets a structural check, then ~160 lines (front 50,",
5859
"evenly spaced 100, last 10) are JSON.parsed against the active schema.",
5960
"Schemas: chatml = {messages:[...]} (SFT); dpo = {messages:[...], chosen,",
60-
"rejected} where chosen/rejected are single assistant messages; cpt =",
61-
'{text:"..."} (continual pre-training, raw text). With no --schema, a',
62-
"record carrying chosen/rejected is validated as DPO, one with text (and no",
63-
"messages) as CPT, otherwise as ChatML. Pass --schema dpo / cpt to require",
64-
"that shape on every record (strict), or --schema chatml to ignore the",
65-
"preference / text fields. Use --full-validate to JSON.parse every line.",
61+
'rejected}; cpt = {text:"..."} (continual pre-training, raw text);',
62+
'tts = {wav_fn:"train/xxx.wav", text:"..."} (audio fine-tuning);',
63+
'image = {img_path:"..."} (image generation); video =',
64+
'{first_frame_path:"...", video_path:"..."} (video generation). With no',
65+
"--schema, a record carrying wav_fn is validated as TTS, img_path as",
66+
"image, video_path / first_frame_path as video, chosen/rejected as DPO,",
67+
"text (no messages) as CPT, otherwise ChatML. Pass --schema to require a",
68+
"specific shape on every record. ZIP archives (.zip) are validated",
69+
"structurally (data.jsonl present, media references resolve) in addition",
70+
"to per-record content checks. Use --full-validate to JSON.parse every line.",
6671
],
6772
async run(ctx) {
6873
const { settings, flags } = ctx;

0 commit comments

Comments
 (0)