Skip to content

Commit 82bdf9e

Browse files
committed
fix: resolve cr issue
1 parent 4383eeb commit 82bdf9e

20 files changed

Lines changed: 834 additions & 694 deletions

File tree

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,17 @@ import {
44
uploadDataset,
55
validateDataset,
66
parseDatasetSchemaFlag,
7+
formatIssue,
78
MAX_DATASET_BYTES,
89
BailianError,
910
ExitCode,
1011
type Config,
1112
type GlobalFlags,
1213
type DatasetFile,
13-
type ValidationResult,
1414
} from "bailian-cli-core";
1515
import { failIfMissing } from "../../output/prompt.ts";
1616
import { emitResult, emitBare } from "../../output/output.ts";
1717

18-
/**
19-
* Format a single validation issue as a one-line string.
20-
*/
21-
function formatIssue(issue: ValidationResult["errors"][number]): string {
22-
const where: string[] = [];
23-
if (issue.line !== undefined) where.push(`line ${issue.line}`);
24-
if (issue.path) where.push(issue.path);
25-
const tag = where.length ? ` [${where.join(" · ")}]` : "";
26-
return ` ${issue.severity.toUpperCase()} ${issue.code}${tag}: ${issue.message}`;
27-
}
28-
2918
export default defineCommand({
3019
name: "dataset upload",
3120
description: "Upload a dataset file (.jsonl) to Bailian",

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,16 @@ import {
33
detectOutputFormat,
44
validateDataset,
55
parseDatasetSchemaFlag,
6+
formatIssue,
67
BailianError,
78
ExitCode,
89
type Config,
910
type GlobalFlags,
10-
type ValidationIssue,
1111
type ValidationResult,
1212
} from "bailian-cli-core";
1313
import { failIfMissing } from "../../output/prompt.ts";
1414
import { emitResult, emitBare } from "../../output/output.ts";
1515

16-
function formatIssue(issue: ValidationIssue): string {
17-
const where: string[] = [];
18-
if (issue.line !== undefined) where.push(`line ${issue.line}`);
19-
if (issue.path) where.push(issue.path);
20-
const tag = where.length ? ` [${where.join(" · ")}]` : "";
21-
return ` ${issue.severity.toUpperCase()} ${issue.code}${tag}: ${issue.message}`;
22-
}
23-
2416
function formatStats(result: ValidationResult): string[] {
2517
const out: string[] = [];
2618
if (result.stats.totalRecords !== undefined) out.push(`records: ${result.stats.totalRecords}`);

packages/cli/src/commands/deploy/create.ts

Lines changed: 17 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,23 @@ import {
22
defineCommand,
33
detectOutputFormat,
44
createDeployment,
5-
listDeployableModels,
65
BailianError,
76
ExitCode,
87
type Config,
98
type GlobalFlags,
109
} from "bailian-cli-core";
1110
import { failIfMissing, promptConfirm } from "../../output/prompt.ts";
1211
import { emitResult, emitBare } from "../../output/output.ts";
12+
import { pickPlanStrategy } from "./plans.ts";
1313

1414
/**
1515
* `bl deploy create` — create a model deployment.
1616
*
17-
* Plan handling:
18-
* - lora (default): Token-billed; `capacity` is required by API but ignored.
19-
* - ptu: Token-billed (provisioned throughput); requires
20-
* `ptu_capacity` {input_tpm, output_tpm}. The doc says
21-
* these default to 10000/1000 when omitted, but the platform
22-
* currently rejects creation without them ("Miss ptu capacity
23-
* info"), so the CLI requires --input-tpm/--output-tpm for ptu.
24-
* - mu: Unit-based; requires `capacity`, `billing_method` and a
25-
* `template_id`. `billing_method` defaults to "POST_PAY"
26-
* (the only value the platform currently supports). If
27-
* --template-id is omitted, the CLI auto-picks the template
28-
* returned by GET /deployments/models whose charge_type
29-
* matches billing_method; --capacity defaults to that
30-
* template's `capacity_unit_per_instance` (the smallest
31-
* valid multiple of base_capacity).
17+
* Plan-specific behaviour (required flags / body assembly / confirm rows /
18+
* auto-pick) lives in `plans.ts` (`PlanStrategy` + `STRATEGIES`). This file
19+
* only handles the shared envelope: argument parsing, dispatch, dry-run,
20+
* confirmation prompt, and result formatting. Adding a new plan = one entry
21+
* in the strategy table; nothing here changes.
3222
*
3323
* `--model` (model identifier) and `--name` (console display name) are required.
3424
*/
@@ -116,119 +106,22 @@ export default defineCommand({
116106
if (!name) failIfMissing("name", "bl deploy create --model <model_name> --name <display_name>");
117107

118108
const plan = (flags.plan as string | undefined) || "lora";
119-
let templateId = flags.templateId as string | undefined;
120-
const inputTpm = flags.inputTpm as number | undefined;
121-
const outputTpm = flags.outputTpm as number | undefined;
122-
const thinkingOutputTpm = flags.thinkingOutputTpm as number | undefined;
123-
// mu-only: capacity (resource units) and billing_method (default POST_PAY,
124-
// the only value the platform currently supports per the deploy doc).
125-
let capacity = flags.capacity as number | undefined;
126-
const billingMethod = (flags.billingMethod as string | undefined) || "POST_PAY";
127-
128109
const format = detectOutputFormat(config.output);
129110

130-
// Validate plan. The catalog lists plan names like `ptu_v2`, but the create
131-
// endpoint only accepts `ptu` — so reject anything outside the supported set
132-
// with a clear message instead of letting the API fail with a vague error.
133-
const SUPPORTED_PLANS = ["lora", "ptu", "mu"] as const;
134-
if (!(SUPPORTED_PLANS as readonly string[]).includes(plan)) {
135-
throw new BailianError(
136-
`Unsupported plan "${plan}". Supported plans: ${SUPPORTED_PLANS.join(", ")}.`,
137-
ExitCode.USAGE,
138-
);
139-
}
140-
141-
// For plan=ptu, require throughput limits. The platform rejects creation
142-
// without an explicit ptu_capacity ("Miss ptu capacity info") even though
143-
// the doc lists 10000/1000 defaults.
144-
if (plan === "ptu") {
145-
if (inputTpm === undefined)
146-
failIfMissing(
147-
"input-tpm",
148-
"bl deploy create --plan ptu --model <m> --name <n> --input-tpm <n> --output-tpm <n>",
149-
);
150-
if (outputTpm === undefined)
151-
failIfMissing(
152-
"output-tpm",
153-
"bl deploy create --plan ptu --model <m> --name <n> --input-tpm <n> --output-tpm <n>",
154-
);
155-
}
156-
157-
// For plan=mu, auto-pick the template (preferring the one whose charge_type
158-
// matches billing_method) and default capacity to the template's unit.
159-
// Skip the catalog lookup when the user supplies --template-id explicitly —
160-
// the model may be a fine-tuned custom model not present in the base
161-
// catalog, and the lookup would otherwise throw a spurious error.
162-
let autoPickedTemplate = false;
163-
if (plan === "mu" && !config.dryRun && !templateId) {
164-
try {
165-
const resp = await listDeployableModels(config, {
166-
modelSource: "base",
167-
pageSize: 100,
168-
version: "v1.0",
169-
});
170-
const payload = resp.output ?? resp.data;
171-
const target = (payload?.models ?? []).find((m) => m.model_name === model);
172-
const muPlan = target?.plans?.find((p) => p.plan === "mu");
173-
const templates = muPlan?.templates ?? [];
174-
if (templates.length === 0) {
175-
throw new BailianError(
176-
`No mu-plan template found for model "${model}". ` +
177-
`Run \`bl deploy models --source base\` to inspect available models, ` +
178-
`or pass --template-id explicitly.`,
179-
ExitCode.USAGE,
180-
);
181-
}
182-
// POST_PAY → post_paid template; fall back to the first available.
183-
const wantChargeType = billingMethod === "POST_PAY" ? "post_paid" : "pre_paid";
184-
const picked = templates.find((t) => t.charge_type === wantChargeType) ?? templates[0];
185-
if (!picked?.template_id) {
186-
throw new BailianError(
187-
`No mu-plan template found for model "${model}". ` +
188-
`Run \`bl deploy models --source base\` to inspect available models, ` +
189-
`or pass --template-id explicitly.`,
190-
ExitCode.USAGE,
191-
);
192-
}
193-
templateId = picked.template_id;
194-
autoPickedTemplate = true;
195-
// capacity must be a multiple of base_capacity; default to the template's
196-
// unit (capacity_unit_per_instance) which is the smallest valid value.
197-
if (capacity === undefined) {
198-
capacity = picked.roles?.unified?.capacity_unit_per_instance ?? 1;
199-
}
200-
} catch (e) {
201-
if (e instanceof BailianError) throw e;
202-
throw new BailianError(
203-
`Failed to auto-pick template for plan=mu: ${(e as Error).message}. ` +
204-
`Pass --template-id explicitly.`,
205-
ExitCode.USAGE,
206-
);
207-
}
208-
}
209-
111+
// Plan-specific behaviour is owned by `plans.ts`. The strategy:
112+
// 1. Validates required flags (USAGE error if missing).
113+
// 2. Resolves the body fragment + confirm rows (mu may auto-pick a
114+
// template from the deployable-models catalog).
115+
// Anything outside the strategy table is rejected with a USAGE error.
116+
const strategy = pickPlanStrategy(plan);
117+
strategy.validateFlags(flags);
118+
const resolved = await strategy.resolve({ config, flags, model: model!, name: name! });
210119
const body: Record<string, unknown> = {
211120
model_name: model!,
212121
name: name!,
213122
plan,
123+
...resolved.body,
214124
};
215-
if (plan === "ptu") {
216-
const ptuCapacity: Record<string, number> = {
217-
input_tpm: inputTpm!,
218-
output_tpm: outputTpm!,
219-
};
220-
if (thinkingOutputTpm !== undefined) ptuCapacity.thinking_output_tpm = thinkingOutputTpm;
221-
body.ptu_capacity = ptuCapacity;
222-
} else if (plan === "mu") {
223-
// mu requires capacity, billing_method and template_id (auto-picked above
224-
// if --template-id was not supplied).
225-
body.capacity = capacity ?? 1;
226-
body.billing_method = billingMethod;
227-
if (templateId) body.template_id = templateId;
228-
} else {
229-
// lora: capacity required by API but ignored (per the working example).
230-
body.capacity = 1;
231-
}
232125

233126
if (config.dryRun) {
234127
emitResult({ action: "deploy.create", body }, format);
@@ -240,22 +133,9 @@ export default defineCommand({
240133
"Create deployment:",
241134
` model: ${model}`,
242135
` name: ${name}`,
243-
` plan: ${plan}${plan === "lora" ? " (Token-billed)" : plan === "ptu" ? " (Token-billed, provisioned throughput)" : ""}`,
136+
` plan: ${plan}${resolved.planLabelSuffix ?? ""}`,
137+
...resolved.confirmRows,
244138
];
245-
if (templateId) {
246-
const hint = autoPickedTemplate ? " (auto-picked)" : "";
247-
lines.push(` template_id: ${templateId}${hint}`);
248-
}
249-
if (plan === "mu") {
250-
lines.push(` capacity: ${capacity ?? 1}`);
251-
lines.push(` billing_method: ${billingMethod}`);
252-
}
253-
if (plan === "ptu") {
254-
lines.push(` input_tpm: ${inputTpm}`);
255-
lines.push(` output_tpm: ${outputTpm}`);
256-
if (thinkingOutputTpm !== undefined)
257-
lines.push(` thinking_output_tpm: ${thinkingOutputTpm}`);
258-
}
259139
process.stderr.write(lines.join("\n") + "\n");
260140
const ok = await promptConfirm({ message: "Proceed?", initialValue: true });
261141
if (!ok) {

0 commit comments

Comments
 (0)