@toloka-platform/sdk 0.2.0-preview.2 → 0.2.0-preview.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/cli/toloka.js +162 -154
- package/dist/cli/toloka.js.jsc +0 -0
- package/dist/cli/toloka.js.jsc.sha256 +1 -1
- package/dist/cli/toloka.js.sha256 +1 -1
- package/docs/authoring.md +18 -1
- package/docs/cli-v2.md +6 -4
- package/docs/cli.md +58 -11
- package/docs/getting-started.md +3 -3
- package/docs/limitations.md +1 -1
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/examples/face-image-collection/README.md +2 -2
- package/openapi/gen/api.ts +78 -0
- package/openapi/gen/models/baseModel.ts +1 -0
- package/openapi/gen/models/checkReport.ts +2 -1
- package/openapi/gen/models/checkReportAnyOfTwoseven.ts +5 -0
- package/openapi/gen/models/checkReportAnyOfTwosevenResultsItem.ts +6 -0
- package/openapi/gen/models/checkReportAnyOfTwosevenResultsItemVerdict.ts +2 -0
- package/openapi/gen/models/currentUser.ts +3 -0
- package/openapi/gen/models/currentUserTeamsItem.ts +6 -0
- package/openapi/gen/models/deliveryOrder.ts +9 -0
- package/openapi/gen/models/deliveryOrderClientName.ts +2 -0
- package/openapi/gen/models/getTrainingRunMetricHistoryParams.ts +4 -0
- package/openapi/gen/models/gistingTrainingConfiguration.ts +6 -2
- package/openapi/gen/models/gistingTrainingConfigurationBatchSize.ts +2 -0
- package/openapi/gen/models/gistingTrainingConfigurationGistTokens.ts +2 -0
- package/openapi/gen/models/gistingTrainingConfigurationInput.ts +6 -2
- package/openapi/gen/models/gistingTrainingConfigurationInputBatchSize.ts +2 -0
- package/openapi/gen/models/gistingTrainingConfigurationInputGistTokens.ts +2 -0
- package/openapi/gen/models/index.ts +28 -4
- package/openapi/gen/models/listDeliveryOrders200.ts +5 -0
- package/openapi/gen/models/sftTrainingConfiguration.ts +6 -3
- package/openapi/gen/models/sftTrainingConfigurationBatchSize.ts +2 -0
- package/openapi/gen/models/sftTrainingConfigurationInput.ts +6 -3
- package/openapi/gen/models/sftTrainingConfigurationInputBatchSize.ts +2 -0
- package/openapi/gen/models/sftTrainingConfigurationInputLoraRank.ts +2 -0
- package/openapi/gen/models/sftTrainingConfigurationLoraRank.ts +2 -0
- package/openapi/gen/models/trainingRunMetricHistory.ts +9 -0
- package/openapi/gen/models/trainingRunMetricHistoryMetric.ts +2 -0
- package/openapi/gen/models/trainingRunMetricHistoryPointsItem.ts +9 -0
- package/openapi/gen/models/trainingRunMetricHistoryPointsItemNonFinite.ts +2 -0
- package/openapi/gen/models/trainingRunMetricHistoryPointsItemValue.ts +2 -0
- package/openapi/gen/models/trainingRunMetrics.ts +9 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItem.ts +8 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemLatest.ts +9 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemLatestNonFinite.ts +2 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemLatestValue.ts +2 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemMetric.ts +2 -0
- package/openapi/gen/models/trainingRunMetricsUnavailableReason.ts +2 -0
- package/package.json +2 -2
- package/skills/catalog.json +55 -7
- package/skills/toloka-build/SKILL.md +35 -21
- package/skills/toloka-build/references/node-semantics.md +13 -11
- package/skills/toloka-build/references/workflow-and-cli.md +31 -20
- package/skills/toloka-fine-tuning/SKILL.md +155 -0
- package/skills/toloka-get-started/SKILL.md +70 -0
- package/skills/toloka-gisting/SKILL.md +166 -0
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/authoring/index.ts +14 -0
- package/src/checks/task-ui-typecheck.ts +12 -10
- package/src/cli/analytics.ts +88 -15
- package/src/cli/auth.ts +93 -21
- package/src/cli/auto-update.ts +36 -39
- package/src/cli/check.ts +88 -39
- package/src/cli/cli-entry.ts +3 -2
- package/src/cli/cli-error.ts +43 -0
- package/src/cli/commands/auth.ts +89 -0
- package/src/cli/commands/config.ts +28 -0
- package/src/cli/commands/dataset.ts +85 -0
- package/src/cli/commands/delivery-order.ts +18 -0
- package/src/cli/commands/models.ts +121 -0
- package/src/cli/commands/pipeline.ts +131 -0
- package/src/cli/commands/project.ts +168 -0
- package/src/cli/commands/report.ts +43 -0
- package/src/cli/commands/secret.ts +40 -0
- package/src/cli/commands/skill.ts +36 -0
- package/src/cli/commands/source.ts +204 -0
- package/src/cli/commands/status.ts +17 -0
- package/src/cli/commands/training.ts +261 -0
- package/src/cli/commands/version.ts +14 -0
- package/src/cli/config.ts +7 -8
- package/src/cli/context.ts +251 -0
- package/src/cli/credentials.ts +12 -4
- package/src/cli/data.ts +96 -34
- package/src/cli/delivery-orders.ts +47 -0
- package/src/cli/environment.ts +26 -0
- package/src/cli/file-system.ts +6 -2
- package/src/cli/format-error.ts +45 -2
- package/src/cli/init.ts +14 -11
- package/src/cli/models.ts +43 -12
- package/src/cli/program.ts +120 -1347
- package/src/cli/project-context.ts +126 -43
- package/src/cli/project.ts +80 -28
- package/src/cli/report.ts +24 -9
- package/src/cli/runs.ts +33 -8
- package/src/cli/sdk-version.ts +38 -0
- package/src/cli/settings.ts +13 -5
- package/src/cli/skill.ts +67 -24
- package/src/cli/source.ts +3 -2
- package/src/cli/terminal-io.ts +110 -0
- package/src/cli/trainings.ts +242 -57
- package/src/cli/version.ts +3 -3
- package/src/client.ts +21 -9
- package/src/importer.ts +32 -2
- package/src/transport.ts +13 -1
- package/openapi/gen/models/gistingTrainingConfigurationInputRegime.ts +0 -2
- package/openapi/gen/models/gistingTrainingConfigurationRegime.ts +0 -2
- package/openapi/gen/models/sftTrainingConfigurationInputRegime.ts +0 -2
- package/openapi/gen/models/sftTrainingConfigurationRegime.ts +0 -2
package/src/cli/trainings.ts
CHANGED
|
@@ -3,10 +3,21 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
3
3
|
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import type {
|
|
7
|
+
PotatoClient,
|
|
8
|
+
Training,
|
|
9
|
+
TrainingCostEstimate,
|
|
10
|
+
TrainingRun,
|
|
11
|
+
TrainingRunMetricHistory,
|
|
12
|
+
TrainingRunMetrics,
|
|
13
|
+
} from "../client";
|
|
9
14
|
import type { BaseModelId, TrainingConfigurationInput } from "../../openapi/gen/models";
|
|
15
|
+
import { assertCli, RETRY_OR_REPORT_HINT, TolokaCliError } from "./cli-error";
|
|
16
|
+
import type { CliErrorHint } from "./cli-error";
|
|
17
|
+
|
|
18
|
+
const ESTIMATE_FILE_HINT: CliErrorHint = {
|
|
19
|
+
description: "Regenerate it with toloka training cost <training> --json > <file>.",
|
|
20
|
+
};
|
|
10
21
|
|
|
11
22
|
type TrainingMethod = TrainingConfigurationInput["method"];
|
|
12
23
|
type TrainingSource = TrainingConfigurationInput["source"];
|
|
@@ -23,9 +34,12 @@ export type TrainingDraftOptions = {
|
|
|
23
34
|
validationDatasetId?: string;
|
|
24
35
|
trainingFilter?: string;
|
|
25
36
|
validationFilter?: string;
|
|
26
|
-
|
|
37
|
+
loraRank?: SftConfigurationInput["loraRank"];
|
|
38
|
+
gistTokens?: GistingConfigurationInput["gistTokens"];
|
|
27
39
|
epochs?: number;
|
|
28
|
-
|
|
40
|
+
learningRate?: number;
|
|
41
|
+
batchSize?: SftConfigurationInput["batchSize"];
|
|
42
|
+
maxSequenceLength?: number;
|
|
29
43
|
promptFilePath?: string;
|
|
30
44
|
fetch?: typeof globalThis.fetch;
|
|
31
45
|
};
|
|
@@ -33,6 +47,7 @@ export type TrainingDraftOptions = {
|
|
|
33
47
|
type TrainingApi = Pick<PotatoClient, "trainings">;
|
|
34
48
|
type TrainingDraftApi = Pick<PotatoClient, "trainings" | "files" | "datasets">;
|
|
35
49
|
type TrainingCostApi = Pick<PotatoClient, "trainings" | "waitFor">;
|
|
50
|
+
type TrainingRunApi = Pick<PotatoClient, "trainings" | "waitFor">;
|
|
36
51
|
const GISTING_PROMPT_MAX_BYTES = 256 * 1024;
|
|
37
52
|
type TrainingIo = { readonly stdout: (text: string) => void };
|
|
38
53
|
type TableRow = readonly string[];
|
|
@@ -64,6 +79,13 @@ const formatTraining = (training: Training): string =>
|
|
|
64
79
|
"Method",
|
|
65
80
|
training.configuration.method === "sft" ? "Supervised fine-tuning (SFT)" : "Prompt gisting",
|
|
66
81
|
],
|
|
82
|
+
training.configuration.method === "sft"
|
|
83
|
+
? ["LoRA rank", String(training.configuration.loraRank)]
|
|
84
|
+
: ["Gist tokens", String(training.configuration.gistTokens)],
|
|
85
|
+
["Epochs", String(training.configuration.epochs)],
|
|
86
|
+
["Learning rate", String(training.configuration.learningRate)],
|
|
87
|
+
["Batch size", String(training.configuration.batchSize)],
|
|
88
|
+
["Max sequence length", String(training.configuration.maxSequenceLength)],
|
|
67
89
|
["Output Model", training.modelId ?? "-"],
|
|
68
90
|
["Archived at", training.archivedAt ?? "-"],
|
|
69
91
|
["Created at", training.createdAt],
|
|
@@ -107,27 +129,32 @@ type ReadyEstimateFile = {
|
|
|
107
129
|
};
|
|
108
130
|
|
|
109
131
|
const parseReadyEstimateFile = (value: unknown): ReadyEstimateFile => {
|
|
110
|
-
|
|
132
|
+
assertCli(
|
|
111
133
|
typeof value === "object" && value !== null && !Array.isArray(value),
|
|
112
134
|
"The estimate file must contain a JSON object.",
|
|
135
|
+
{ hint: ESTIMATE_FILE_HINT },
|
|
113
136
|
);
|
|
114
|
-
|
|
137
|
+
assertCli(
|
|
115
138
|
"status" in value && value.status === "ready",
|
|
116
139
|
"The estimate file is not a ready Training Cost Estimate.",
|
|
140
|
+
{ hint: ESTIMATE_FILE_HINT },
|
|
117
141
|
);
|
|
118
|
-
|
|
142
|
+
assertCli(
|
|
119
143
|
"trainingId" in value && typeof value.trainingId === "string",
|
|
120
144
|
"The estimate file has no Training ID.",
|
|
145
|
+
{ hint: ESTIMATE_FILE_HINT },
|
|
121
146
|
);
|
|
122
|
-
|
|
147
|
+
assertCli(
|
|
123
148
|
"inputSetId" in value && typeof value.inputSetId === "string",
|
|
124
149
|
"The estimate file has no Input Set ID.",
|
|
150
|
+
{ hint: ESTIMATE_FILE_HINT },
|
|
125
151
|
);
|
|
126
|
-
|
|
152
|
+
assertCli(
|
|
127
153
|
"fingerprint" in value &&
|
|
128
154
|
typeof value.fingerprint === "string" &&
|
|
129
155
|
/^[0-9a-f]{64}$/.test(value.fingerprint),
|
|
130
156
|
"The estimate file has no valid fingerprint.",
|
|
157
|
+
{ hint: ESTIMATE_FILE_HINT },
|
|
131
158
|
);
|
|
132
159
|
|
|
133
160
|
return {
|
|
@@ -163,9 +190,10 @@ const resolveTraining = async (
|
|
|
163
190
|
): Promise<Training> => {
|
|
164
191
|
if (nameOrId.startsWith("trg.")) {
|
|
165
192
|
const training = await client.trainings.get({ trainingId: nameOrId });
|
|
166
|
-
|
|
193
|
+
assertCli(
|
|
167
194
|
training.projectId === projectId,
|
|
168
195
|
`Training ${nameOrId} was not found in project ${projectId}.`,
|
|
196
|
+
{ hint: { description: "Run toloka training list; a Training belongs to one project." } },
|
|
169
197
|
);
|
|
170
198
|
return training;
|
|
171
199
|
}
|
|
@@ -174,7 +202,9 @@ const resolveTraining = async (
|
|
|
174
202
|
for await (const training of client.trainings.listAll({ projectId, limit: 100 })) {
|
|
175
203
|
if (training.name === nameOrId) matches.push(training);
|
|
176
204
|
}
|
|
177
|
-
|
|
205
|
+
assertCli(matches.length === 1, `Training ${nameOrId} was not found or is ambiguous.`, {
|
|
206
|
+
hint: { description: "Run toloka training list and pass the training id (trg.…)." },
|
|
207
|
+
});
|
|
178
208
|
return matches[0]!;
|
|
179
209
|
};
|
|
180
210
|
|
|
@@ -191,12 +221,18 @@ const resolveSelection = async (
|
|
|
191
221
|
datasetId: string,
|
|
192
222
|
filter: string | undefined,
|
|
193
223
|
): Promise<TrainingSelection> => {
|
|
194
|
-
|
|
224
|
+
assertCli(datasetId, "A Dataset is required when specifying a filter.", {
|
|
225
|
+
hint: {
|
|
226
|
+
description:
|
|
227
|
+
"Pass --dataset together with --train-filter, and --validation-dataset together with --validation-filter.",
|
|
228
|
+
},
|
|
229
|
+
});
|
|
195
230
|
if (filter === undefined) return { datasetId };
|
|
196
231
|
const separator = filter.indexOf("=");
|
|
197
|
-
|
|
232
|
+
assertCli(
|
|
198
233
|
separator > 0 && separator < filter.length - 1,
|
|
199
234
|
"Dataset filters must use FIELD=VALUE syntax.",
|
|
235
|
+
{ hint: { description: "Write the filter as field=value, for example split=train." } },
|
|
200
236
|
);
|
|
201
237
|
const fieldName = filter.slice(0, separator);
|
|
202
238
|
const rawValue = filter.slice(separator + 1);
|
|
@@ -205,9 +241,10 @@ const resolveSelection = async (
|
|
|
205
241
|
if (field.name === fieldName) matches.push(field);
|
|
206
242
|
}
|
|
207
243
|
const [field] = matches;
|
|
208
|
-
|
|
244
|
+
assertCli(
|
|
209
245
|
field && matches.length === 1,
|
|
210
246
|
`Dataset field ${fieldName} was not found or is ambiguous.`,
|
|
247
|
+
{ hint: { description: "Run toloka dataset fields <dataset> to see the field names." } },
|
|
211
248
|
);
|
|
212
249
|
return {
|
|
213
250
|
datasetId,
|
|
@@ -232,14 +269,17 @@ const sourceFromOptions = (
|
|
|
232
269
|
options: Pick<TrainingDraftOptions, "baseModelId" | "modelVersionId">,
|
|
233
270
|
fallback?: TrainingSource,
|
|
234
271
|
): TrainingSource => {
|
|
235
|
-
|
|
272
|
+
assertCli(
|
|
236
273
|
!(options.baseModelId && options.modelVersionId),
|
|
237
274
|
"Choose exactly one of --base-model and --model-version.",
|
|
275
|
+
{ hint: { disabled: true } },
|
|
238
276
|
);
|
|
239
277
|
if (options.baseModelId) return { type: "base_model", baseModelId: options.baseModelId };
|
|
240
278
|
if (options.modelVersionId)
|
|
241
279
|
return { type: "model_version", modelVersionId: options.modelVersionId };
|
|
242
|
-
|
|
280
|
+
assertCli(fallback, "Choose exactly one of --base-model and --model-version.", {
|
|
281
|
+
hint: { disabled: true },
|
|
282
|
+
});
|
|
243
283
|
return fallback;
|
|
244
284
|
};
|
|
245
285
|
|
|
@@ -250,20 +290,29 @@ const uploadPromptFile = async (
|
|
|
250
290
|
fetchImplementation: typeof globalThis.fetch,
|
|
251
291
|
): Promise<string> => {
|
|
252
292
|
const metadata = await stat(filePath);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
293
|
+
assertCli(metadata.isFile(), "--prompt-file must point to a file.", {
|
|
294
|
+
hint: { description: "Pass the absolute path of the prompt text file." },
|
|
295
|
+
});
|
|
296
|
+
assertCli(metadata.size > 0, "The gisting prompt file is empty.", {
|
|
297
|
+
hint: { description: "Put the full prompt text into the file before creating the Training." },
|
|
298
|
+
});
|
|
299
|
+
assertCli(
|
|
256
300
|
metadata.size <= GISTING_PROMPT_MAX_BYTES,
|
|
257
301
|
"The gisting prompt file exceeds the 256 KiB limit.",
|
|
302
|
+
{ hint: { description: "Shorten the prompt to under 256 KiB." } },
|
|
258
303
|
);
|
|
259
304
|
const payload = await readFile(filePath);
|
|
260
305
|
let prompt: string;
|
|
261
306
|
try {
|
|
262
307
|
prompt = new TextDecoder("utf-8", { fatal: true }).decode(payload);
|
|
263
308
|
} catch {
|
|
264
|
-
throw new
|
|
309
|
+
throw new TolokaCliError("The gisting prompt file must contain valid UTF-8 text.", {
|
|
310
|
+
hint: { description: "Save the prompt file as UTF-8 text." },
|
|
311
|
+
});
|
|
265
312
|
}
|
|
266
|
-
|
|
313
|
+
assertCli(prompt.trim().length > 0, "The gisting prompt file is empty.", {
|
|
314
|
+
hint: { description: "Put the full prompt text into the file before creating the Training." },
|
|
315
|
+
});
|
|
267
316
|
const started = await client.files.startUpload({
|
|
268
317
|
projectId,
|
|
269
318
|
filename: basename(filePath),
|
|
@@ -275,7 +324,9 @@ const uploadPromptFile = async (
|
|
|
275
324
|
headers: { "x-ms-blob-type": "BlockBlob", "Content-Type": "text/plain; charset=utf-8" },
|
|
276
325
|
body: payload,
|
|
277
326
|
});
|
|
278
|
-
|
|
327
|
+
assertCli(response.ok, `Failed to upload gisting prompt: HTTP ${response.status}.`, {
|
|
328
|
+
hint: RETRY_OR_REPORT_HINT,
|
|
329
|
+
});
|
|
279
330
|
return (await client.files.finalizeUpload({ projectId, fileId: started.file.id })).id;
|
|
280
331
|
} catch (error) {
|
|
281
332
|
await client.files.delete({ projectId, fileId: started.file.id }).catch(() => undefined);
|
|
@@ -293,7 +344,9 @@ const resolveSelections = async ({
|
|
|
293
344
|
options: TrainingDraftOptions;
|
|
294
345
|
}): Promise<ResolvedSelections> => {
|
|
295
346
|
const trainingDatasetId = options.datasetId ?? current?.training.datasetId;
|
|
296
|
-
|
|
347
|
+
assertCli(trainingDatasetId, "--dataset is required.", {
|
|
348
|
+
hint: { description: "Pass --dataset <dataset-id>; toloka dataset list shows the ids." },
|
|
349
|
+
});
|
|
297
350
|
const training =
|
|
298
351
|
options.datasetId === undefined && options.trainingFilter === undefined && current
|
|
299
352
|
? current.training
|
|
@@ -309,6 +362,14 @@ const resolveSelections = async ({
|
|
|
309
362
|
return { training, validation };
|
|
310
363
|
};
|
|
311
364
|
|
|
365
|
+
const sharedHyperparameters = (options: TrainingDraftOptions) => ({
|
|
366
|
+
...(options.learningRate === undefined ? {} : { learningRate: options.learningRate }),
|
|
367
|
+
...(options.batchSize === undefined ? {} : { batchSize: options.batchSize }),
|
|
368
|
+
...(options.maxSequenceLength === undefined
|
|
369
|
+
? {}
|
|
370
|
+
: { maxSequenceLength: options.maxSequenceLength }),
|
|
371
|
+
});
|
|
372
|
+
|
|
312
373
|
const buildSftConfiguration = ({
|
|
313
374
|
source,
|
|
314
375
|
selections,
|
|
@@ -320,15 +381,28 @@ const buildSftConfiguration = ({
|
|
|
320
381
|
current: SftConfigurationInput | undefined;
|
|
321
382
|
options: TrainingDraftOptions;
|
|
322
383
|
}): SftConfigurationInput => {
|
|
323
|
-
|
|
384
|
+
assertCli(options.promptFilePath === undefined, "--prompt-file applies to gisting only.", {
|
|
385
|
+
hint: {
|
|
386
|
+
description: "Pass --method gisting to use --prompt-file, or omit --prompt-file for SFT.",
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
assertCli(
|
|
390
|
+
options.gistTokens === undefined,
|
|
391
|
+
"--gist-tokens applies to gisting; SFT takes --lora-rank.",
|
|
392
|
+
{
|
|
393
|
+
hint: {
|
|
394
|
+
description: "Use --lora-rank for SFT, or pass --method gisting to use --gist-tokens.",
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
);
|
|
324
398
|
return {
|
|
325
399
|
...current,
|
|
326
400
|
method: "sft",
|
|
327
401
|
source,
|
|
328
402
|
...selections,
|
|
329
|
-
...(options.
|
|
403
|
+
...(options.loraRank === undefined ? {} : { loraRank: options.loraRank }),
|
|
330
404
|
...(options.epochs === undefined ? {} : { epochs: options.epochs }),
|
|
331
|
-
...(options
|
|
405
|
+
...sharedHyperparameters(options),
|
|
332
406
|
};
|
|
333
407
|
};
|
|
334
408
|
|
|
@@ -347,7 +421,15 @@ const buildGistingConfiguration = async ({
|
|
|
347
421
|
current: GistingConfigurationInput | undefined;
|
|
348
422
|
options: TrainingDraftOptions;
|
|
349
423
|
}): Promise<GistingConfigurationInput> => {
|
|
350
|
-
|
|
424
|
+
assertCli(
|
|
425
|
+
options.loraRank === undefined,
|
|
426
|
+
"--lora-rank applies to SFT; gisting takes --gist-tokens.",
|
|
427
|
+
{
|
|
428
|
+
hint: {
|
|
429
|
+
description: "Use --gist-tokens for gisting, or omit --method gisting to create SFT.",
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
);
|
|
351
433
|
const promptFileId = options.promptFilePath
|
|
352
434
|
? await uploadPromptFile(
|
|
353
435
|
client,
|
|
@@ -356,15 +438,18 @@ const buildGistingConfiguration = async ({
|
|
|
356
438
|
options.fetch ?? globalThis.fetch,
|
|
357
439
|
)
|
|
358
440
|
: current?.promptFileId;
|
|
359
|
-
|
|
441
|
+
assertCli(promptFileId, "--prompt-file is required for gisting.", {
|
|
442
|
+
hint: { description: "Pass --prompt-file <absolute-path> with the prompt text." },
|
|
443
|
+
});
|
|
360
444
|
return {
|
|
361
445
|
...current,
|
|
362
446
|
method: "gisting",
|
|
363
447
|
source,
|
|
364
448
|
...selections,
|
|
365
449
|
promptFileId,
|
|
366
|
-
...(options.
|
|
450
|
+
...(options.gistTokens === undefined ? {} : { gistTokens: options.gistTokens }),
|
|
367
451
|
...(options.epochs === undefined ? {} : { epochs: options.epochs }),
|
|
452
|
+
...sharedHyperparameters(options),
|
|
368
453
|
};
|
|
369
454
|
};
|
|
370
455
|
|
|
@@ -445,8 +530,14 @@ export const createTraining = async (
|
|
|
445
530
|
},
|
|
446
531
|
io: TrainingIo,
|
|
447
532
|
): Promise<void> => {
|
|
448
|
-
|
|
449
|
-
|
|
533
|
+
assertCli(input.datasetId, "--dataset is required.", {
|
|
534
|
+
hint: { description: "Pass --dataset <dataset-id>; toloka dataset list shows the ids." },
|
|
535
|
+
});
|
|
536
|
+
assertCli(
|
|
537
|
+
!input.validationFilter || input.validationDatasetId,
|
|
538
|
+
"--validation-filter requires --validation-dataset.",
|
|
539
|
+
{ hint: { disabled: true } },
|
|
540
|
+
);
|
|
450
541
|
|
|
451
542
|
const configuration = await buildTrainingConfiguration({
|
|
452
543
|
client,
|
|
@@ -458,12 +549,7 @@ export const createTraining = async (
|
|
|
458
549
|
name: input.name,
|
|
459
550
|
configuration,
|
|
460
551
|
});
|
|
461
|
-
writeOutput(
|
|
462
|
-
io,
|
|
463
|
-
input.json,
|
|
464
|
-
training,
|
|
465
|
-
`Created training ${training.id}`,
|
|
466
|
-
);
|
|
552
|
+
writeOutput(io, input.json, training, `Created training ${training.id}`);
|
|
467
553
|
};
|
|
468
554
|
|
|
469
555
|
export const updateTraining = async (
|
|
@@ -485,19 +571,13 @@ export const updateTraining = async (
|
|
|
485
571
|
options: input,
|
|
486
572
|
}),
|
|
487
573
|
});
|
|
488
|
-
writeOutput(
|
|
489
|
-
io,
|
|
490
|
-
input.json,
|
|
491
|
-
updated,
|
|
492
|
-
`Updated training ${updated.id}`,
|
|
493
|
-
);
|
|
574
|
+
writeOutput(io, input.json, updated, `Updated training ${updated.id}`);
|
|
494
575
|
};
|
|
495
576
|
|
|
496
|
-
|
|
577
|
+
const prepareTrainingCostEstimate = async (
|
|
497
578
|
client: TrainingCostApi,
|
|
498
|
-
input: {
|
|
499
|
-
|
|
500
|
-
): Promise<void> => {
|
|
579
|
+
input: { nameOrId: string; projectId: string; idempotencyKey?: string },
|
|
580
|
+
): Promise<{ training: Training; estimate: TrainingCostEstimate }> => {
|
|
501
581
|
const training = await resolveTraining(client, input.projectId, input.nameOrId);
|
|
502
582
|
const inputSet = await client.trainings.prepareCostEstimateInputs({
|
|
503
583
|
trainingId: training.id,
|
|
@@ -511,33 +591,65 @@ export const estimateTrainingCost = async (
|
|
|
511
591
|
exportIds.map((exportId) => client.waitFor({ type: "datasetExport", exportId })),
|
|
512
592
|
);
|
|
513
593
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
594
|
+
return {
|
|
595
|
+
training,
|
|
596
|
+
estimate: await client.trainings.estimateCost({
|
|
597
|
+
trainingId: training.id,
|
|
598
|
+
inputSetId: inputSet.id,
|
|
599
|
+
}),
|
|
600
|
+
};
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
export const estimateTrainingCost = async (
|
|
604
|
+
client: TrainingCostApi,
|
|
605
|
+
input: { projectId: string; nameOrId: string; idempotencyKey?: string; json: boolean },
|
|
606
|
+
io: TrainingIo,
|
|
607
|
+
): Promise<void> => {
|
|
608
|
+
const { estimate } = await prepareTrainingCostEstimate(client, input);
|
|
609
|
+
assertCli(estimate.status === "ready", formatCostEstimate(estimate), {
|
|
610
|
+
hint: { description: "Fix the blocked issues above, then run toloka training cost again." },
|
|
517
611
|
});
|
|
518
612
|
writeOutput(io, input.json, estimate, formatCostEstimate(estimate));
|
|
519
613
|
};
|
|
520
614
|
|
|
521
615
|
export const createTrainingRun = async (
|
|
522
|
-
client:
|
|
616
|
+
client: TrainingRunApi,
|
|
523
617
|
input: {
|
|
524
618
|
projectId: string;
|
|
525
619
|
nameOrId: string;
|
|
526
|
-
estimateFile
|
|
527
|
-
versionName
|
|
620
|
+
estimateFile?: string;
|
|
621
|
+
versionName?: string;
|
|
528
622
|
idempotencyKey?: string;
|
|
529
623
|
json: boolean;
|
|
530
624
|
},
|
|
531
625
|
io: TrainingIo,
|
|
532
626
|
): Promise<void> => {
|
|
533
627
|
const training = await resolveTraining(client, input.projectId, input.nameOrId);
|
|
534
|
-
const estimate =
|
|
535
|
-
|
|
628
|
+
const estimate = input.estimateFile
|
|
629
|
+
? parseReadyEstimateFile(JSON.parse(await readFile(input.estimateFile, "utf8")))
|
|
630
|
+
: await (async () => {
|
|
631
|
+
const prepared = await prepareTrainingCostEstimate(client, {
|
|
632
|
+
projectId: input.projectId,
|
|
633
|
+
nameOrId: input.nameOrId,
|
|
634
|
+
});
|
|
635
|
+
assertCli(prepared.estimate.status === "ready", formatCostEstimate(prepared.estimate), {
|
|
636
|
+
hint: {
|
|
637
|
+
description: "Fix the blocked issues above, then run toloka training cost again.",
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
return parseReadyEstimateFile(prepared.estimate);
|
|
641
|
+
})();
|
|
642
|
+
assertCli(estimate.trainingId === training.id, "The estimate file belongs to another Training.", {
|
|
643
|
+
hint: {
|
|
644
|
+
description:
|
|
645
|
+
"Run toloka training cost <training> --json > <file> for this Training, get its maximumCharge approved, then re-run with that file.",
|
|
646
|
+
},
|
|
647
|
+
});
|
|
536
648
|
const run = await client.trainings.createRun({
|
|
537
649
|
trainingId: training.id,
|
|
538
650
|
inputSetId: estimate.inputSetId,
|
|
539
651
|
estimateFingerprint: estimate.fingerprint,
|
|
540
|
-
versionName: input.versionName,
|
|
652
|
+
versionName: input.versionName ?? training.name,
|
|
541
653
|
idempotencyKey: input.idempotencyKey ?? randomUUID(),
|
|
542
654
|
});
|
|
543
655
|
writeOutput(io, input.json, run, formatTrainingRun(run));
|
|
@@ -625,3 +737,76 @@ export const readTrainingRunLogs = async (
|
|
|
625
737
|
});
|
|
626
738
|
}
|
|
627
739
|
};
|
|
740
|
+
|
|
741
|
+
const formatMetricValue = (point: {
|
|
742
|
+
value: number | null;
|
|
743
|
+
nonFinite: "nan" | "positive_infinity" | "negative_infinity" | null;
|
|
744
|
+
}): string => {
|
|
745
|
+
if (point.nonFinite === "nan") return "NaN";
|
|
746
|
+
if (point.nonFinite === "positive_infinity") return "Infinity";
|
|
747
|
+
if (point.nonFinite === "negative_infinity") return "-Infinity";
|
|
748
|
+
return point.value === null ? "-" : String(point.value);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const METRICS_UNAVAILABLE: Record<"not_configured" | "unresolved", string> = {
|
|
752
|
+
not_configured:
|
|
753
|
+
"This Training Run has no metrics: it started before metrics were available, or the server records none.",
|
|
754
|
+
unresolved:
|
|
755
|
+
"Metrics are temporarily unreachable. The Training Run itself is unaffected; try again later.",
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const formatTrainingRunMetrics = (metrics: TrainingRunMetrics): string => {
|
|
759
|
+
if (!metrics.available) {
|
|
760
|
+
return metrics.unavailableReason === null
|
|
761
|
+
? "No metrics are available for this Training Run."
|
|
762
|
+
: METRICS_UNAVAILABLE[metrics.unavailableReason];
|
|
763
|
+
}
|
|
764
|
+
if (metrics.metrics.length === 0) return "No metrics have been reported yet.";
|
|
765
|
+
|
|
766
|
+
const rows = metrics.metrics.map((entry) => [
|
|
767
|
+
entry.key,
|
|
768
|
+
entry.metric ?? "-",
|
|
769
|
+
formatMetricValue(entry.latest),
|
|
770
|
+
String(entry.latest.step),
|
|
771
|
+
]);
|
|
772
|
+
const table = formatTable([["Metric", "Meaning", "Latest", "Step"], ...rows]);
|
|
773
|
+
return metrics.truncated ? `${table}\n\nMore metrics exist than the server returns.` : table;
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
const formatTrainingRunMetricHistory = (history: TrainingRunMetricHistory): string => {
|
|
777
|
+
const header = formatTable([
|
|
778
|
+
["Metric", history.key],
|
|
779
|
+
["Meaning", history.metric ?? "-"],
|
|
780
|
+
["Points", String(history.points.length)],
|
|
781
|
+
]);
|
|
782
|
+
const points = formatTable([
|
|
783
|
+
["Step", "Value"],
|
|
784
|
+
...history.points.map((point) => [String(point.step), formatMetricValue(point)]),
|
|
785
|
+
]);
|
|
786
|
+
const note = history.truncated
|
|
787
|
+
? "\n\nNot every recorded point is shown: the series is thinned to fit --resolution, and a very long run keeps only its most recent points."
|
|
788
|
+
: "";
|
|
789
|
+
return `${header}\n\n${points}${note}`;
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
export const readTrainingRunMetrics = async (
|
|
793
|
+
client: TrainingApi,
|
|
794
|
+
input: { trainingRunId: string; json: boolean },
|
|
795
|
+
io: TrainingIo,
|
|
796
|
+
): Promise<void> => {
|
|
797
|
+
const metrics = await client.trainings.getRunMetrics({ trainingRunId: input.trainingRunId });
|
|
798
|
+
writeOutput(io, input.json, metrics, formatTrainingRunMetrics(metrics));
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
export const readTrainingRunMetricHistory = async (
|
|
802
|
+
client: TrainingApi,
|
|
803
|
+
input: { trainingRunId: string; metricKey: string; resolution?: number; json: boolean },
|
|
804
|
+
io: TrainingIo,
|
|
805
|
+
): Promise<void> => {
|
|
806
|
+
const history = await client.trainings.getRunMetricHistory({
|
|
807
|
+
trainingRunId: input.trainingRunId,
|
|
808
|
+
metricKey: input.metricKey,
|
|
809
|
+
resolution: input.resolution,
|
|
810
|
+
});
|
|
811
|
+
writeOutput(io, input.json, history, formatTrainingRunMetricHistory(history));
|
|
812
|
+
};
|
package/src/cli/version.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { realpathSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { SDK_VERSION } from "./sdk-version";
|
|
6
|
+
import { assertCli, REPORT_HINT } from "./cli-error";
|
|
7
7
|
|
|
8
8
|
export type CliInstallChannel = "global-registry" | "linked" | "project-local";
|
|
9
9
|
|
|
@@ -36,7 +36,7 @@ const classifyInstallChannel = (
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export const cliVersionInfo = (entryPath = process.argv[1], options: VersionInfoOptions = {}) => {
|
|
39
|
-
|
|
39
|
+
assertCli(entryPath, "CLI entry path is unavailable", { hint: REPORT_HINT });
|
|
40
40
|
const globalBinDirectory =
|
|
41
41
|
options.globalBinDirectory ??
|
|
42
42
|
resolve(process.env.BUN_INSTALL ?? join(homedir(), ".bun"), "bin");
|
package/src/client.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
ImportPipelineBody,
|
|
26
26
|
InferredField as GeneratedInferredField,
|
|
27
27
|
GetTrainingRunLogsParams,
|
|
28
|
+
GetTrainingRunMetricHistoryParams,
|
|
28
29
|
ListDatasetFieldsParams,
|
|
29
30
|
ListModelVersionsParams,
|
|
30
31
|
ListDatasetItemsParams,
|
|
@@ -45,6 +46,8 @@ import type {
|
|
|
45
46
|
TrainingCostEstimate as GeneratedTrainingCostEstimate,
|
|
46
47
|
TrainingCostEstimateInputSet as GeneratedTrainingCostEstimateInputSet,
|
|
47
48
|
TrainingRun as GeneratedTrainingRun,
|
|
49
|
+
TrainingRunMetricHistory as GeneratedTrainingRunMetricHistory,
|
|
50
|
+
TrainingRunMetrics as GeneratedTrainingRunMetrics,
|
|
48
51
|
TrainingRunLogs as GeneratedTrainingRunLogs,
|
|
49
52
|
ReplaceTrainingConfigurationBody,
|
|
50
53
|
UpdateTrainingMetadataBody,
|
|
@@ -164,6 +167,8 @@ export type TrainingCostEstimateInputSet = BrandedResource<
|
|
|
164
167
|
export type TrainingCostEstimate = BrandResourceUnion<GeneratedTrainingCostEstimate>;
|
|
165
168
|
export type TrainingRun = BrandedResource<GeneratedTrainingRun, TrainingRunId>;
|
|
166
169
|
export type TrainingRunLogs = GeneratedTrainingRunLogs;
|
|
170
|
+
export type TrainingRunMetrics = GeneratedTrainingRunMetrics;
|
|
171
|
+
export type TrainingRunMetricHistory = GeneratedTrainingRunMetricHistory;
|
|
167
172
|
export type BaseModel = GeneratedBaseModel;
|
|
168
173
|
export type BaseModelId = GeneratedBaseModelId;
|
|
169
174
|
export type Pipeline = BrandedResource<GeneratedPipeline, PipelineId>;
|
|
@@ -223,12 +228,6 @@ export type WaitForTarget =
|
|
|
223
228
|
const createClientImplementation = (config: ClientConfig) => {
|
|
224
229
|
const requestOptions = createRequestOptions(config.baseUrl, config.apiKey, config.fetch);
|
|
225
230
|
|
|
226
|
-
const emptyJsonRequestOptions = (): RequestInit => {
|
|
227
|
-
const headers = new Headers(requestOptions.headers);
|
|
228
|
-
headers.set("Content-Type", "application/json");
|
|
229
|
-
return { ...requestOptions, headers, body: "{}" };
|
|
230
|
-
};
|
|
231
|
-
|
|
232
231
|
function waitFor(
|
|
233
232
|
target: DatasetUploadWaitTarget,
|
|
234
233
|
options?: WaitForOptions,
|
|
@@ -482,6 +481,14 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
482
481
|
...params
|
|
483
482
|
}: { trainingRunId: TrainingRunId } & GetTrainingRunLogsParams) =>
|
|
484
483
|
wire.getTrainingRunLogs(trainingRunId, params, requestOptions),
|
|
484
|
+
getRunMetrics: ({ trainingRunId }: { trainingRunId: TrainingRunId }) =>
|
|
485
|
+
wire.getTrainingRunMetrics(trainingRunId, requestOptions),
|
|
486
|
+
getRunMetricHistory: ({
|
|
487
|
+
trainingRunId,
|
|
488
|
+
metricKey,
|
|
489
|
+
...params
|
|
490
|
+
}: { trainingRunId: TrainingRunId; metricKey: string } & GetTrainingRunMetricHistoryParams) =>
|
|
491
|
+
wire.getTrainingRunMetricHistory(trainingRunId, metricKey, params, requestOptions),
|
|
485
492
|
};
|
|
486
493
|
|
|
487
494
|
const datasets = {
|
|
@@ -551,9 +558,9 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
551
558
|
startUpload: ({ datasetId, ...body }: { datasetId: DatasetId } & CreateDatasetUploadBody) =>
|
|
552
559
|
wire.createDatasetUpload(datasetId, body, requestOptions),
|
|
553
560
|
finalizeUpload: ({ uploadId }: { uploadId: UploadId }) =>
|
|
554
|
-
wire.finalizeDatasetUpload(uploadId,
|
|
561
|
+
wire.finalizeDatasetUpload(uploadId, requestOptions),
|
|
555
562
|
cancelUpload: ({ uploadId }: { uploadId: UploadId }) =>
|
|
556
|
-
wire.cancelDatasetUpload(uploadId,
|
|
563
|
+
wire.cancelDatasetUpload(uploadId, requestOptions),
|
|
557
564
|
getUpload: ({ uploadId }: { uploadId: UploadId }) =>
|
|
558
565
|
wire.getDatasetUpload(uploadId, requestOptions),
|
|
559
566
|
startExport: ({ datasetId, ...body }: { datasetId: DatasetId } & CreateDatasetExportBody) =>
|
|
@@ -564,7 +571,7 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
564
571
|
|
|
565
572
|
const runs = {
|
|
566
573
|
start: ({ pipelineId }: { pipelineId: PipelineId }) =>
|
|
567
|
-
wire.createPipelineRun(pipelineId,
|
|
574
|
+
wire.createPipelineRun(pipelineId, requestOptions),
|
|
568
575
|
get: ({ runId }: { runId: RunId }) => wire.getPipelineRun(runId, requestOptions),
|
|
569
576
|
cancel: ({ runId }: { runId: RunId }) => wire.cancelPipelineRun(runId, requestOptions),
|
|
570
577
|
listPage: ({ pipelineId, ...params }: { pipelineId: PipelineId } & ListPipelineRunsParams) =>
|
|
@@ -624,6 +631,10 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
624
631
|
wire.getPipelineCheck(checkRunId, requestOptions),
|
|
625
632
|
};
|
|
626
633
|
|
|
634
|
+
const deliveryOrders = {
|
|
635
|
+
list: async () => (await wire.listDeliveryOrders(requestOptions)).deliveryOrders,
|
|
636
|
+
};
|
|
637
|
+
|
|
627
638
|
return {
|
|
628
639
|
me: () => wire.getCurrentUser(requestOptions),
|
|
629
640
|
projects,
|
|
@@ -636,6 +647,7 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
636
647
|
secrets,
|
|
637
648
|
files,
|
|
638
649
|
checks,
|
|
650
|
+
deliveryOrders,
|
|
639
651
|
waitFor,
|
|
640
652
|
};
|
|
641
653
|
};
|