Skip to content

Commit 2cf6106

Browse files
committed
feat: add model training params cli command
1 parent 2c29049 commit 2cf6106

12 files changed

Lines changed: 819 additions & 5 deletions

File tree

‎packages/cli/src/commands.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ import {
179179
finetuneWatch,
180180
finetuneCapability,
181181
finetunePrice,
182+
finetuneParams,
183+
finetuneMetrics,
182184
finetuneCompressTemplates,
183185
finetuneCompressCreate,
184186
finetuneCompressList,
@@ -519,6 +521,8 @@ export const commands: Record<string, AnyCommand> = {
519521
"finetune watch": finetuneWatch,
520522
"finetune capability": finetuneCapability,
521523
"finetune price": finetunePrice,
524+
"finetune params": finetuneParams,
525+
"finetune metrics": finetuneMetrics,
522526
"finetune compress templates": finetuneCompressTemplates,
523527
"finetune compress create": finetuneCompressCreate,
524528
"finetune compress list": finetuneCompressList,
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { defineCommand, listBasicMetrics, type FlagsDef } from "bailian-cli-core";
2+
3+
/**
4+
* The old Zelda metric API defaulted to 100 records per page; the OpenAPI
5+
* defaults to 1000. Send 100 explicitly unless the user overrides, so page
6+
* shape stays the same across the switch.
7+
*/
8+
const LEGACY_DEFAULT_PAGE_SIZE = 100;
9+
10+
const METRICS_FLAGS = {
11+
jobId: {
12+
type: "string",
13+
valueHint: "<id>",
14+
description: {
15+
"en-US": "Fine-tune job ID (required)",
16+
"zh-CN": "微调任务 ID(必填)",
17+
},
18+
required: true,
19+
},
20+
page: {
21+
type: "number",
22+
valueHint: "<n>",
23+
description: {
24+
"en-US": "Page number, starting at 1 (default: 1)",
25+
"zh-CN": "页码,从 1 开始(默认:1)",
26+
},
27+
},
28+
pageSize: {
29+
type: "number",
30+
valueHint: "<n>",
31+
description: {
32+
"en-US": `Records per page (default: ${LEGACY_DEFAULT_PAGE_SIZE})`,
33+
"zh-CN": `每页记录数(默认:${LEGACY_DEFAULT_PAGE_SIZE})`,
34+
},
35+
},
36+
workspaceId: {
37+
type: "string",
38+
valueHint: "<id>",
39+
description: {
40+
"en-US":
41+
"Workspace ID sent as X-DashScope-WorkSpace; use the job's owning workspace. Omit when your API key is already workspace-bound.",
42+
"zh-CN":
43+
"以 X-DashScope-WorkSpace 头发送的业务空间 ID,使用任务所属空间;API Key 已绑定空间时可省略。",
44+
},
45+
},
46+
} satisfies FlagsDef;
47+
48+
export default defineCommand({
49+
defaultOutput: "json",
50+
description: {
51+
"en-US": "Query the training metrics of a fine-tune job",
52+
"zh-CN": "查询微调任务的训练指标",
53+
},
54+
auth: "apiKey",
55+
usageArgs: "--job-id <id> [--page <n>] [--page-size <n>] [--workspace-id <id>]",
56+
flags: METRICS_FLAGS,
57+
exampleArgs: [
58+
"--job-id ft-xxx",
59+
"--job-id ft-xxx --page 2 --page-size 100",
60+
"--job-id ft-xxx --output json",
61+
"--job-id ft-xxx --dry-run",
62+
],
63+
notes: [
64+
{
65+
"en-US":
66+
"Queries GET /api/v1/fine-tunes/{job_id}/basic_metrics. Each metrics record is returned verbatim (step + dynamic metric fields); an empty list is a normal result.",
67+
"zh-CN":
68+
"调用 GET /api/v1/fine-tunes/{job_id}/basic_metrics。每条指标记录原样返回(step + 动态指标字段);空列表是正常结果。",
69+
},
70+
{
71+
"en-US":
72+
"Metrics are not filtered server-side; the full result is returned and callers pick the metric keys from each record. Use --page/--page-size for paging.",
73+
"zh-CN":
74+
"指标不在服务端过滤;返回全量记录,调用方从记录中自行选取指标 key 展示。翻页用 --page/--page-size。",
75+
},
76+
],
77+
async run(ctx) {
78+
const { settings, flags } = ctx;
79+
const jobId = flags.jobId;
80+
const pageNo = flags.page ?? 1;
81+
const pageSize = flags.pageSize ?? LEGACY_DEFAULT_PAGE_SIZE;
82+
83+
if (settings.dryRun) {
84+
const resultData = {
85+
action: "finetune.metrics",
86+
job_id: jobId,
87+
page_no: pageNo,
88+
page_size: pageSize,
89+
...(flags.workspaceId ? { workspace_id: flags.workspaceId } : {}),
90+
};
91+
ctx.output.result(
92+
resultData,
93+
ctx.settings.output === "text" ? { text: JSON.stringify(resultData, null, 2) } : undefined,
94+
);
95+
return;
96+
}
97+
98+
const response = await listBasicMetrics(ctx.client, jobId, {
99+
pageNo,
100+
pageSize,
101+
workspaceId: flags.workspaceId || undefined,
102+
});
103+
104+
ctx.output.result(
105+
response,
106+
ctx.settings.output === "text" ? { text: JSON.stringify(response, null, 2) } : undefined,
107+
);
108+
},
109+
});
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import {
2+
defineCommand,
3+
getFineTuneParams,
4+
isTrainingTypeCli,
5+
TRAINING_TYPES_CLI,
6+
DEFAULT_TRAINING_TYPE,
7+
TRAINING_TYPE_MAP,
8+
BailianError,
9+
ExitCode,
10+
type FlagsDef,
11+
} from "bailian-cli-core";
12+
13+
/**
14+
* Build the training-option context from the dedicated flags. The OpenAPI
15+
* expects it as one `hyper_parameters` query parameter holding a JSON string
16+
* (e.g. {"modality":"vision","reasoning_mode":"thinking"}); models without
17+
* dynamic train options simply omit it.
18+
*/
19+
function buildTrainOptions(
20+
modality: string | undefined,
21+
reasoningMode: string | undefined,
22+
): Record<string, string> {
23+
const options: Record<string, string> = {};
24+
if (modality) options.modality = modality;
25+
if (reasoningMode) options.reasoning_mode = reasoningMode;
26+
return options;
27+
}
28+
29+
const PARAMS_FLAGS = {
30+
baseModel: {
31+
type: "string",
32+
valueHint: "<model>",
33+
description: {
34+
"en-US": "Base model to query training parameters for (e.g. qwen3-8b)",
35+
"zh-CN": "要查询训练参数的基础模型(例如 qwen3-8b)",
36+
},
37+
required: true,
38+
},
39+
trainingType: {
40+
type: "string",
41+
valueHint: "<t>",
42+
description: {
43+
"en-US": `Training type: ${TRAINING_TYPES_CLI.join(" | ")} (default: ${DEFAULT_TRAINING_TYPE}). Mapped to the server's training_type (e.g. sft-lora -> efficient_sft).`,
44+
"zh-CN": `训练类型:${TRAINING_TYPES_CLI.join(" | ")}(默认:${DEFAULT_TRAINING_TYPE})。会映射为服务端 training_type(例如 sft-lora -> efficient_sft)。`,
45+
},
46+
},
47+
mtuSpecCode: {
48+
type: "string",
49+
valueHint: "<code>",
50+
description: {
51+
"en-US": "MTU spec code, required for resource-group queries (e.g. MTU2)",
52+
"zh-CN": "训练单元(MTU)规格编码,查询资源类参数时需要(例如 MTU2)",
53+
},
54+
},
55+
paramType: {
56+
type: "string",
57+
valueHint: "<type>",
58+
description: {
59+
"en-US": "Parameter group: hyper | resource. Omit to return all groups.",
60+
"zh-CN": "参数类别:hyper | resource。不传返回全部分类。",
61+
},
62+
choices: ["hyper", "resource"] as const,
63+
},
64+
modality: {
65+
type: "string",
66+
valueHint: "<text|vision>",
67+
description: {
68+
"en-US":
69+
"Training modality: text | vision. Only meaningful for models with dynamic train options; sent as modality inside hyper_parameters.",
70+
"zh-CN":
71+
"训练模态:text | vision。仅对配置了动态训练选项的模型有意义;作为 hyper_parameters 中的 modality 发送。",
72+
},
73+
choices: ["text", "vision"] as const,
74+
},
75+
reasoningMode: {
76+
type: "string",
77+
valueHint: "<instruct|thinking>",
78+
description: {
79+
"en-US":
80+
"Reasoning mode: instruct (non-thinking) | thinking. Only meaningful for models with dynamic train options; sent as reasoning_mode inside hyper_parameters.",
81+
"zh-CN":
82+
"思考模式:instruct(非思考)| thinking(思考)。仅对配置了动态训练选项的模型有意义;作为 hyper_parameters 中的 reasoning_mode 发送。",
83+
},
84+
choices: ["instruct", "thinking"] as const,
85+
},
86+
lang: {
87+
type: "string",
88+
valueHint: "<locale>",
89+
description: {
90+
"en-US": "Locale for server-side display text (e.g. en, zh). Omit for the server default.",
91+
"zh-CN": "服务端展示文案的语言(例如 en、zh)。不传由服务端决定。",
92+
},
93+
},
94+
workspaceId: {
95+
type: "string",
96+
valueHint: "<id>",
97+
description: {
98+
"en-US":
99+
"Workspace ID sent as X-DashScope-WorkSpace; omit when your API key is already workspace-bound.",
100+
"zh-CN": "以 X-DashScope-WorkSpace 头发送的业务空间 ID;API Key 已绑定空间时可省略。",
101+
},
102+
},
103+
} satisfies FlagsDef;
104+
105+
export default defineCommand({
106+
defaultOutput: "json",
107+
description: {
108+
"en-US": "Query the training hyper-parameter configuration for a base model",
109+
"zh-CN": "查询基础模型的训练超参配置",
110+
},
111+
auth: "apiKey",
112+
usageArgs:
113+
"--base-model <model> [--training-type <t>] [--param-type <hyper|resource>] [--mtu-spec-code <code>] [--modality <text|vision>] [--reasoning-mode <instruct|thinking>] [--lang <locale>] [--workspace-id <id>]",
114+
flags: PARAMS_FLAGS,
115+
exampleArgs: [
116+
"--base-model qwen3-8b",
117+
"--base-model qwen3-8b --training-type sft",
118+
"--base-model qwen3-8b --training-type dpo-lora --param-type hyper",
119+
"--base-model qwen3.7-plus-2026-05-26 --modality vision --reasoning-mode thinking",
120+
"--base-model qwen3-8b --param-type resource --mtu-spec-code MTU2",
121+
"--base-model qwen3-8b --output json",
122+
"--base-model qwen3-8b --dry-run",
123+
],
124+
notes: [
125+
{
126+
"en-US":
127+
"Queries GET /api/v1/fine-tunes/fine-tunes-params. Each hyper_params entry keeps server field names verbatim (recommend_value, defaultValue, is_advanced_parameter, ...).",
128+
"zh-CN":
129+
"调用 GET /api/v1/fine-tunes/fine-tunes-params。每条 hyper_params 原样保留服务端字段名(recommend_value、defaultValue、is_advanced_parameter 等)。",
130+
},
131+
{
132+
"en-US":
133+
"--modality / --reasoning-mode travel as one hyper_parameters query parameter (JSON). Supported values and combinations vary per model; invalid combinations are rejected by the server and surfaced as-is. Models without dynamic train options ignore these flags.",
134+
"zh-CN":
135+
"--modality / --reasoning-mode 会合并为单个 hyper_parameters query 参数(JSON)发送。各模型支持的取值与组合不同;非法组合由服务端拒绝并原样透传错误。未配置动态训练选项的模型可忽略这两个 flag。",
136+
},
137+
{
138+
"en-US":
139+
"resume_from_checkpoint in the output reports whether the training type supports resuming training.",
140+
"zh-CN": "输出中的 resume_from_checkpoint 表示该训练类型是否支持断点续训。",
141+
},
142+
],
143+
async run(ctx) {
144+
const { settings, flags } = ctx;
145+
146+
const trainingTypeCli = flags.trainingType || DEFAULT_TRAINING_TYPE;
147+
if (!isTrainingTypeCli(trainingTypeCli)) {
148+
throw new BailianError(
149+
`--training-type "${trainingTypeCli}" is not supported.`,
150+
ExitCode.USAGE,
151+
`Supported values: ${TRAINING_TYPES_CLI.join(", ")} (default: ${DEFAULT_TRAINING_TYPE}).`,
152+
);
153+
}
154+
const trainingType = TRAINING_TYPE_MAP[trainingTypeCli].server;
155+
const trainOptions = buildTrainOptions(flags.modality, flags.reasoningMode);
156+
157+
if (settings.dryRun) {
158+
const resultData = {
159+
action: "finetune.params",
160+
base_model: flags.baseModel,
161+
training_type: trainingType,
162+
...(flags.paramType ? { param_type: flags.paramType } : {}),
163+
...(flags.mtuSpecCode ? { mtu_spec_code: flags.mtuSpecCode } : {}),
164+
...(Object.keys(trainOptions).length > 0
165+
? { hyper_parameters: JSON.stringify(trainOptions) }
166+
: {}),
167+
...(flags.lang ? { lang: flags.lang } : {}),
168+
...(flags.workspaceId ? { workspace_id: flags.workspaceId } : {}),
169+
};
170+
ctx.output.result(
171+
resultData,
172+
ctx.settings.output === "text" ? { text: JSON.stringify(resultData, null, 2) } : undefined,
173+
);
174+
return;
175+
}
176+
177+
const response = await getFineTuneParams(ctx.client, {
178+
inputModel: flags.baseModel,
179+
trainingType,
180+
mtuSpecCode: flags.mtuSpecCode || undefined,
181+
paramType: flags.paramType || undefined,
182+
trainOptions,
183+
lang: flags.lang || undefined,
184+
workspaceId: flags.workspaceId || undefined,
185+
});
186+
187+
ctx.output.result(
188+
response,
189+
ctx.settings.output === "text" ? { text: JSON.stringify(response, null, 2) } : undefined,
190+
);
191+
},
192+
});

‎packages/commands/src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ export { default as finetuneExport } from "./commands/finetune/export.ts";
182182
export { default as finetuneWatch } from "./commands/finetune/watch.ts";
183183
export { default as finetuneCapability } from "./commands/finetune/capability.ts";
184184
export { default as finetunePrice } from "./commands/finetune/price.ts";
185+
export { default as finetuneParams } from "./commands/finetune/params.ts";
186+
export { default as finetuneMetrics } from "./commands/finetune/metrics.ts";
185187
export { default as finetuneCompressTemplates } from "./commands/finetune/compress-templates.ts";
186188
export { default as finetuneCompressCreate } from "./commands/finetune/compress-create.ts";
187189
export { default as finetuneCompressList } from "./commands/finetune/compress-list.ts";

0 commit comments

Comments
 (0)