@toloka-platform/sdk 0.12.2 → 0.13.0
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/dist/cli/toloka.js +153 -151
- 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/cli.md +17 -0
- package/openapi/gen/api.ts +142 -0
- package/openapi/gen/models/batchInferenceMode.ts +3 -0
- package/openapi/gen/models/batchInferenceModeOneOf.ts +4 -0
- package/openapi/gen/models/batchInferenceModel.ts +4 -0
- package/openapi/gen/models/batchInferenceModelOneOf.ts +6 -0
- package/openapi/gen/models/batchInferenceModelOneOfBaseModelId.ts +2 -0
- package/openapi/gen/models/batchInferenceModelOneOfThree.ts +5 -0
- package/openapi/gen/models/batchInferenceRun.ts +35 -0
- package/openapi/gen/models/batchInferenceRunBlockedReason.ts +2 -0
- package/openapi/gen/models/batchInferenceRunChargedAmount.ts +2 -0
- package/openapi/gen/models/batchInferenceRunError.ts +3 -0
- package/openapi/gen/models/batchInferenceRunErrorAnyOf.ts +6 -0
- package/openapi/gen/models/batchInferenceRunErrorAnyOfCode.ts +2 -0
- package/openapi/gen/models/batchInferenceRunId.ts +2 -0
- package/openapi/gen/models/batchInferenceRunMessage.ts +2 -0
- package/openapi/gen/models/batchInferenceRunOutput.ts +9 -0
- package/openapi/gen/models/batchInferenceRunOutputType.ts +2 -0
- package/openapi/gen/models/batchInferenceRunProgress.ts +3 -0
- package/openapi/gen/models/batchInferenceRunProgressAnyOf.ts +7 -0
- package/openapi/gen/models/batchInferenceRunProgressAnyOfPhase.ts +2 -0
- package/openapi/gen/models/batchInferenceRunProgressAnyOfRequests.ts +5 -0
- package/openapi/gen/models/batchInferenceRunQuote.ts +3 -0
- package/openapi/gen/models/batchInferenceRunQuoteAnyOf.ts +9 -0
- package/openapi/gen/models/batchInferenceRunQuoteAnyOfConfirmBefore.ts +2 -0
- package/openapi/gen/models/batchInferenceRunResult.ts +3 -0
- package/openapi/gen/models/batchInferenceRunResultAnyOf.ts +12 -0
- package/openapi/gen/models/batchInferenceRunResultAnyOfErrorCounts.ts +2 -0
- package/openapi/gen/models/batchInferenceRunResultAnyOfErrorSamplesItem.ts +6 -0
- package/openapi/gen/models/batchInferenceRunSampling.ts +9 -0
- package/openapi/gen/models/batchInferenceRunStatus.ts +2 -0
- package/openapi/gen/models/createBatchInferenceRunBody.ts +14 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilter.ts +14 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterExcludePartsItem.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterParts.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodyItemFilterSlice.ts +4 -0
- package/openapi/gen/models/createBatchInferenceRunBodyOutput.ts +4 -0
- package/openapi/gen/models/createBatchInferenceRunBodyOutputOneOf.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodyOutputOneOfTwo.ts +5 -0
- package/openapi/gen/models/createBatchInferenceRunBodySampling.ts +9 -0
- package/openapi/gen/models/datasetFieldOperation.ts +2 -1
- package/openapi/gen/models/datasetFieldWriteOperation.ts +8 -0
- package/openapi/gen/models/datasetFieldWriteOperationProgress.ts +3 -0
- package/openapi/gen/models/datasetFieldWriteOperationProgressAnyOf.ts +5 -0
- package/openapi/gen/models/index.ts +53 -0
- package/openapi/gen/models/listBatchInferenceRuns200.ts +7 -0
- package/openapi/gen/models/listBatchInferenceRunsParams.ts +5 -0
- package/package.json +2 -2
- package/skills/catalog.json +6 -6
- package/skills/toloka-build/references/workflow-and-cli.md +5 -1
- package/src/cli/analytics.ts +8 -0
- package/src/cli/batch-inference.ts +267 -0
- package/src/cli/commands/batch-inference.ts +116 -0
- package/src/cli/context.ts +14 -0
- package/src/cli/models.ts +1 -1
- package/src/cli/program.ts +2 -0
- package/src/cli/splits.ts +40 -0
- package/src/cli/trainings.ts +1 -19
- package/src/client.ts +49 -0
|
@@ -25,6 +25,35 @@ export * from './attachPipelineDatasetBodyItemFilterSlice';
|
|
|
25
25
|
export * from './baseModel';
|
|
26
26
|
export * from './baseModelId';
|
|
27
27
|
export * from './baseModelIdProperty';
|
|
28
|
+
export * from './batchInferenceMode';
|
|
29
|
+
export * from './batchInferenceModeOneOf';
|
|
30
|
+
export * from './batchInferenceModel';
|
|
31
|
+
export * from './batchInferenceModelOneOf';
|
|
32
|
+
export * from './batchInferenceModelOneOfBaseModelId';
|
|
33
|
+
export * from './batchInferenceModelOneOfThree';
|
|
34
|
+
export * from './batchInferenceRun';
|
|
35
|
+
export * from './batchInferenceRunBlockedReason';
|
|
36
|
+
export * from './batchInferenceRunChargedAmount';
|
|
37
|
+
export * from './batchInferenceRunError';
|
|
38
|
+
export * from './batchInferenceRunErrorAnyOf';
|
|
39
|
+
export * from './batchInferenceRunErrorAnyOfCode';
|
|
40
|
+
export * from './batchInferenceRunId';
|
|
41
|
+
export * from './batchInferenceRunMessage';
|
|
42
|
+
export * from './batchInferenceRunOutput';
|
|
43
|
+
export * from './batchInferenceRunOutputType';
|
|
44
|
+
export * from './batchInferenceRunProgress';
|
|
45
|
+
export * from './batchInferenceRunProgressAnyOf';
|
|
46
|
+
export * from './batchInferenceRunProgressAnyOfPhase';
|
|
47
|
+
export * from './batchInferenceRunProgressAnyOfRequests';
|
|
48
|
+
export * from './batchInferenceRunQuote';
|
|
49
|
+
export * from './batchInferenceRunQuoteAnyOf';
|
|
50
|
+
export * from './batchInferenceRunQuoteAnyOfConfirmBefore';
|
|
51
|
+
export * from './batchInferenceRunResult';
|
|
52
|
+
export * from './batchInferenceRunResultAnyOf';
|
|
53
|
+
export * from './batchInferenceRunResultAnyOfErrorCounts';
|
|
54
|
+
export * from './batchInferenceRunResultAnyOfErrorSamplesItem';
|
|
55
|
+
export * from './batchInferenceRunSampling';
|
|
56
|
+
export * from './batchInferenceRunStatus';
|
|
28
57
|
export * from './checkJob';
|
|
29
58
|
export * from './checkJobError';
|
|
30
59
|
export * from './checkJobResult';
|
|
@@ -78,6 +107,25 @@ export * from './codeNodeSettingsFieldProxies';
|
|
|
78
107
|
export * from './codeNodeSettingsFieldProxiesInputItem';
|
|
79
108
|
export * from './codeNodeSettingsFieldProxiesOutputItem';
|
|
80
109
|
export * from './copyPipelineBody';
|
|
110
|
+
export * from './createBatchInferenceRunBody';
|
|
111
|
+
export * from './createBatchInferenceRunBodyItemFilter';
|
|
112
|
+
export * from './createBatchInferenceRunBodyItemFilterExcludePartsItem';
|
|
113
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldSourcesItem';
|
|
114
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItem';
|
|
115
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItemOneOf';
|
|
116
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItemOneOfTwo';
|
|
117
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldValuesItem';
|
|
118
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldValuesItemOperator';
|
|
119
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItem';
|
|
120
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItemOneOf';
|
|
121
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItemOneOfTwo';
|
|
122
|
+
export * from './createBatchInferenceRunBodyItemFilterFieldValuesItemValue';
|
|
123
|
+
export * from './createBatchInferenceRunBodyItemFilterParts';
|
|
124
|
+
export * from './createBatchInferenceRunBodyItemFilterSlice';
|
|
125
|
+
export * from './createBatchInferenceRunBodyOutput';
|
|
126
|
+
export * from './createBatchInferenceRunBodyOutputOneOf';
|
|
127
|
+
export * from './createBatchInferenceRunBodyOutputOneOfTwo';
|
|
128
|
+
export * from './createBatchInferenceRunBodySampling';
|
|
81
129
|
export * from './createDatasetBody';
|
|
82
130
|
export * from './createDatasetBodyProfile';
|
|
83
131
|
export * from './createDatasetExportBody';
|
|
@@ -168,6 +216,9 @@ export * from './datasetFieldSplitAnyOfRecipeAnyOfBalanceByFieldId';
|
|
|
168
216
|
export * from './datasetFieldSplitAnyOfRecipeAnyOfSharesItem';
|
|
169
217
|
export * from './datasetFieldSplitAnyOfStatus';
|
|
170
218
|
export * from './datasetFieldSplitFillOperation';
|
|
219
|
+
export * from './datasetFieldWriteOperation';
|
|
220
|
+
export * from './datasetFieldWriteOperationProgress';
|
|
221
|
+
export * from './datasetFieldWriteOperationProgressAnyOf';
|
|
171
222
|
export * from './datasetId';
|
|
172
223
|
export * from './datasetItem';
|
|
173
224
|
export * from './datasetItemCurrentSubmissionId';
|
|
@@ -356,6 +407,8 @@ export * from './jsonSchemaTypeAnyOfItem';
|
|
|
356
407
|
export * from './listAudienceSpecializations200';
|
|
357
408
|
export * from './listAudienceSpecializationsParams';
|
|
358
409
|
export * from './listBaseModels200';
|
|
410
|
+
export * from './listBatchInferenceRuns200';
|
|
411
|
+
export * from './listBatchInferenceRunsParams';
|
|
359
412
|
export * from './listDatasetFields200';
|
|
360
413
|
export * from './listDatasetFieldsParams';
|
|
361
414
|
export * from './listDatasetItems200';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toloka-platform/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build, inspect, and operate Toloka pipeline projects as TypeScript source. The public API may change between minor versions before 1.0.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -77,6 +77,6 @@
|
|
|
77
77
|
"bun": ">=1.4.0"
|
|
78
78
|
},
|
|
79
79
|
"tolokaBuild": {
|
|
80
|
-
"commit": "
|
|
80
|
+
"commit": "63680479b4e4905b946650ebec6070af910e2340"
|
|
81
81
|
}
|
|
82
82
|
}
|
package/skills/catalog.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"testing",
|
|
12
12
|
"checks"
|
|
13
13
|
],
|
|
14
|
-
"version": "0.
|
|
15
|
-
"digest": "
|
|
14
|
+
"version": "0.13.0",
|
|
15
|
+
"digest": "1595dd10deec49f9f940445f26fe0dc0ed1383ffccb33e42d1ad05d282e71a5c",
|
|
16
16
|
"files": [
|
|
17
17
|
"SKILL.md",
|
|
18
18
|
"references/human-work.md",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"bug",
|
|
34
34
|
"report"
|
|
35
35
|
],
|
|
36
|
-
"version": "0.
|
|
36
|
+
"version": "0.13.0",
|
|
37
37
|
"digest": "70f0bd2c80b32f87896aeb27c363b8a2d37f40e22424f57fe1aad142e125ad67",
|
|
38
38
|
"files": [
|
|
39
39
|
"SKILL.md",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"getting-started",
|
|
51
51
|
"agent"
|
|
52
52
|
],
|
|
53
|
-
"version": "0.
|
|
53
|
+
"version": "0.13.0",
|
|
54
54
|
"digest": "2278adeb46a792356579395fcf73c42322cb23bcd0c61e20856ccabdad6762d1",
|
|
55
55
|
"files": [
|
|
56
56
|
"SKILL.md"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"sft",
|
|
68
68
|
"models"
|
|
69
69
|
],
|
|
70
|
-
"version": "0.
|
|
70
|
+
"version": "0.13.0",
|
|
71
71
|
"digest": "3ce10dee5d3c283d8b5fdea15134a58fc738c7af2c1bb19eb5a0bf70ea0bca13",
|
|
72
72
|
"files": [
|
|
73
73
|
"SKILL.md"
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"prompt",
|
|
85
85
|
"models"
|
|
86
86
|
],
|
|
87
|
-
"version": "0.
|
|
87
|
+
"version": "0.13.0",
|
|
88
88
|
"digest": "be8bce8d9cf7241ccb60d2b2a9b7cc07d7755a76e8ff8ec1b0847c49efbb3469",
|
|
89
89
|
"files": [
|
|
90
90
|
"SKILL.md"
|
|
@@ -35,7 +35,7 @@ Run `toloka --help` and `toloka <command> --help` for the exact commands and opt
|
|
|
35
35
|
- source reconciliation: `init`, `import`, `check`, `validate`, `plan`, `apply`, `status`, `secret set`;
|
|
36
36
|
- catalogs: `audience specialization list`, `generation-model list`, `ui components`;
|
|
37
37
|
- data and runs: `dataset` (`list`, `create`, `fields`, `fields add`, `columns`, `load`, `upload`, `split`, `slice`), `pipeline` (`list`, `run`, `status`, `runs`, `cost`, `results`, `cancel`);
|
|
38
|
-
- models and training: `base-model`, `model` (including `model version download`), `training
|
|
38
|
+
- models and training: `base-model`, `model` (including `model version download`), `training`, `batch-inference` (`run`, `list`, `status`, `confirm`, `resume`, `cancel`);
|
|
39
39
|
- skills and reports: `skill` (`list`, `search`, `install`), `report`.
|
|
40
40
|
|
|
41
41
|
`toloka audience specialization list` prints the global specialization catalog: `ID`, `NAME`, and `EXPERTS` columns (`--json` for the array). Run it before setting `audience.specializations` on a node, pick entries by name, and copy `ID` into `qualificationId` and `NAME` into `value`; never invent a `qualificationId`. `EXPERTS` counts ID-verified experts currently eligible for tasks; a `0` means the id is valid but nobody currently qualifies, so prefer another specialization or widen the audience.
|
|
@@ -58,6 +58,10 @@ Hyperparameters are optional flags on `training create` and `training update`; t
|
|
|
58
58
|
|
|
59
59
|
Use `training cost` to validate inputs and show the maximum charge before `training run`. A blocked estimate exits 1 and lists the issues on stderr instead of printing the estimate; fix them and recalculate. `training run` accepts that ready estimate JSON through `--estimate-file`; without it, the command calculates a fresh ready estimate itself before creating the run. `--version-name` defaults to the Training name; pass it when a Model Version with that name already exists. Use `training status`, `training logs`, `training metrics`, `training resume`, and `training cancel` with the returned Training Run ID. `training metrics` lists each metric's latest value while the run is still training; `--key` prints one metric's point history instead. `training cancel` before training starts is free; on a run that is already training it stops the GPU job, and the run is charged only for the GPU seconds used until then and produces no Model Version.
|
|
60
60
|
|
|
61
|
+
## Answer dataset rows with a model
|
|
62
|
+
|
|
63
|
+
`toloka batch-inference run --dataset <id> (--base-model <id> | --model-version <id>) (--output-field <name> | --output-dataset <name>) --max-output-tokens <n>` sends each selected row's `messages` to a qwen3-4b or qwen3-8b Base Model, or a Model Version trained from one, and writes one answer per row into a text column or a new dataset. An existing `--output-field` column must allow strings; one whose schema allows none is refused with "does not accept text", so pick or create a text column. `--filter field=value`, `--slice <id>` and `--parts <names>` (with `--split-column` when the dataset has several) narrow the rows; a slice answers the prompts it froze. `run` waits for the quote and prints the rows and the maximum charge: show them to the user and confirm only with their approval, through `--yes` on `run` or `toloka batch-inference confirm <run-id>` within 24 hours. Follow a run with `status --wait`. A `blocked` run names its reason: after `funds` or `payment`, `resume` continues it once the user adds funds; `billing_review` waits for support to reconcile the charge, and `resume` does not apply to it. `cancel` stops a run and charges the GPU time used, and `list` shows the project's runs. A scoped token needs `inference:deploy` (the `deploy` preset) to run, confirm, resume or cancel. `toloka batch-inference <command> --help` lists every flag.
|
|
64
|
+
|
|
61
65
|
## Check locally and validate against the platform
|
|
62
66
|
|
|
63
67
|
Run the complete local gate first, then validate every bound pipeline remotely:
|
package/src/cli/analytics.ts
CHANGED
|
@@ -108,6 +108,14 @@ const COMMAND_GROUPS: Readonly<Record<string, CommandGroup>> = {
|
|
|
108
108
|
metrics: true,
|
|
109
109
|
logs: true,
|
|
110
110
|
},
|
|
111
|
+
"batch-inference": {
|
|
112
|
+
run: true,
|
|
113
|
+
list: true,
|
|
114
|
+
status: true,
|
|
115
|
+
confirm: true,
|
|
116
|
+
resume: true,
|
|
117
|
+
cancel: true,
|
|
118
|
+
},
|
|
111
119
|
"base-model": { list: true, get: true },
|
|
112
120
|
"generation-model": { list: true },
|
|
113
121
|
model: {
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import type { BatchInferenceRun, PotatoClient } from "../client";
|
|
4
|
+
import type { BaseModelId, CreateBatchInferenceRunBody } from "../../openapi/gen/models";
|
|
5
|
+
import { assertCli } from "./cli-error";
|
|
6
|
+
import { resolveFieldValueFilters } from "./filters";
|
|
7
|
+
import { resolveModelVersion } from "./models";
|
|
8
|
+
import { resolveSplitParts } from "./splits";
|
|
9
|
+
import { formatTable, type TableIo as BatchInferenceIo, writeOutput } from "./table";
|
|
10
|
+
|
|
11
|
+
type BatchInferenceApi = Pick<PotatoClient, "batchInference">;
|
|
12
|
+
type BatchInferenceStartApi = Pick<
|
|
13
|
+
PotatoClient,
|
|
14
|
+
"batchInference" | "datasets" | "models" | "baseModels"
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
const QUOTE_WAIT_MS = 10 * 60 * 1_000;
|
|
18
|
+
const SETTLED_STATUSES: ReadonlySet<BatchInferenceRun["status"]> = new Set([
|
|
19
|
+
"awaiting_confirmation",
|
|
20
|
+
"blocked",
|
|
21
|
+
"completed",
|
|
22
|
+
"failed",
|
|
23
|
+
"canceled",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const sleep = (ms: number) =>
|
|
27
|
+
new Promise<void>((resolve) => {
|
|
28
|
+
setTimeout(resolve, ms);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const formatBatchInferenceRun = (run: BatchInferenceRun): string =>
|
|
32
|
+
formatTable([
|
|
33
|
+
["ID", run.id],
|
|
34
|
+
["Status", run.status],
|
|
35
|
+
["Dataset", run.datasetId],
|
|
36
|
+
["Model", run.model.type === "base_model" ? run.model.baseModelId : run.model.modelVersionId],
|
|
37
|
+
[
|
|
38
|
+
"Output",
|
|
39
|
+
run.output.type === "field"
|
|
40
|
+
? `field ${run.output.fieldId} on dataset ${run.output.datasetId}`
|
|
41
|
+
: `new dataset ${run.output.datasetId}`,
|
|
42
|
+
],
|
|
43
|
+
...(run.quote
|
|
44
|
+
? [
|
|
45
|
+
["Rows", String(run.quote.requests)],
|
|
46
|
+
["Skipped rows", String(run.quote.skippedRows)],
|
|
47
|
+
["Maximum charge", `$${run.quote.maximumCharge}`],
|
|
48
|
+
...(run.quote.confirmBefore ? [["Confirm before", run.quote.confirmBefore]] : []),
|
|
49
|
+
]
|
|
50
|
+
: []),
|
|
51
|
+
...(run.progress?.requests
|
|
52
|
+
? [["Progress", `${run.progress.requests.done}/${run.progress.requests.total} rows`]]
|
|
53
|
+
: []),
|
|
54
|
+
...(run.chargedAmount ? [["Charged", `$${run.chargedAmount}`]] : []),
|
|
55
|
+
...(run.result
|
|
56
|
+
? [
|
|
57
|
+
["Written", String(run.result.written)],
|
|
58
|
+
["Failed", String(run.result.failed)],
|
|
59
|
+
["Cut at max tokens", String(run.result.truncated)],
|
|
60
|
+
["Tokens", `${run.result.inputTokens} in, ${run.result.outputTokens} out`],
|
|
61
|
+
]
|
|
62
|
+
: []),
|
|
63
|
+
...(run.message ? [["Message", run.message]] : []),
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const waitUntil = async (
|
|
67
|
+
client: BatchInferenceApi,
|
|
68
|
+
batchInferenceRunId: string,
|
|
69
|
+
isReady: (run: BatchInferenceRun) => boolean,
|
|
70
|
+
pollIntervalMs: number,
|
|
71
|
+
timeoutMs: number,
|
|
72
|
+
): Promise<BatchInferenceRun> => {
|
|
73
|
+
const deadline = Date.now() + timeoutMs;
|
|
74
|
+
for (;;) {
|
|
75
|
+
const run = await client.batchInference.getRun({ batchInferenceRunId });
|
|
76
|
+
if (isReady(run)) return run;
|
|
77
|
+
assertCli(Date.now() < deadline, `Batch Inference Run ${run.id} is still ${run.status}.`, {
|
|
78
|
+
hint: { description: `Check it later with toloka batch-inference status ${run.id}.` },
|
|
79
|
+
});
|
|
80
|
+
await sleep(pollIntervalMs);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type StartBatchInferenceRunInput = {
|
|
85
|
+
projectId: string;
|
|
86
|
+
datasetId: string;
|
|
87
|
+
filter?: string;
|
|
88
|
+
slice?: string;
|
|
89
|
+
parts?: string[];
|
|
90
|
+
splitColumn?: string;
|
|
91
|
+
baseModel?: BaseModelId;
|
|
92
|
+
modelVersion?: string;
|
|
93
|
+
outputField?: string;
|
|
94
|
+
outputDataset?: string;
|
|
95
|
+
maxOutputTokens: number;
|
|
96
|
+
temperature?: number;
|
|
97
|
+
topP?: number;
|
|
98
|
+
stop: string[];
|
|
99
|
+
seed?: number;
|
|
100
|
+
samplesPerRow?: number;
|
|
101
|
+
idempotencyKey?: string;
|
|
102
|
+
yes: boolean;
|
|
103
|
+
json: boolean;
|
|
104
|
+
pollIntervalMs?: number;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const resolveItemFilter = async (
|
|
108
|
+
client: BatchInferenceStartApi,
|
|
109
|
+
input: StartBatchInferenceRunInput,
|
|
110
|
+
): Promise<CreateBatchInferenceRunBody["itemFilter"]> => {
|
|
111
|
+
assertCli(
|
|
112
|
+
input.splitColumn === undefined || input.parts !== undefined,
|
|
113
|
+
"--split-column needs --parts.",
|
|
114
|
+
{ hint: { description: "Pass --parts test to answer the rows of the test part." } },
|
|
115
|
+
);
|
|
116
|
+
const itemFilter = {
|
|
117
|
+
...(input.filter !== undefined &&
|
|
118
|
+
(await resolveFieldValueFilters(
|
|
119
|
+
client,
|
|
120
|
+
{ datasetId: input.datasetId, name: input.datasetId, nameIsUnique: false },
|
|
121
|
+
[input.filter],
|
|
122
|
+
))),
|
|
123
|
+
...(input.slice !== undefined && { slice: { sliceId: input.slice } }),
|
|
124
|
+
...(input.parts !== undefined && {
|
|
125
|
+
parts: await resolveSplitParts(client, input.datasetId, {
|
|
126
|
+
splitColumn: input.splitColumn,
|
|
127
|
+
parts: input.parts,
|
|
128
|
+
}),
|
|
129
|
+
}),
|
|
130
|
+
};
|
|
131
|
+
return Object.keys(itemFilter).length > 0 ? itemFilter : undefined;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const startBatchInferenceRun = async (
|
|
135
|
+
client: BatchInferenceStartApi,
|
|
136
|
+
input: StartBatchInferenceRunInput,
|
|
137
|
+
io: BatchInferenceIo,
|
|
138
|
+
): Promise<void> => {
|
|
139
|
+
assertCli(
|
|
140
|
+
(input.baseModel === undefined) !== (input.modelVersion === undefined),
|
|
141
|
+
"Pass exactly one of --base-model or --model-version.",
|
|
142
|
+
{
|
|
143
|
+
hint: {
|
|
144
|
+
description: "Run toloka base-model list or toloka model versions <model> to pick one.",
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
assertCli(
|
|
149
|
+
(input.outputField === undefined) !== (input.outputDataset === undefined),
|
|
150
|
+
"Pass exactly one of --output-field or --output-dataset.",
|
|
151
|
+
{
|
|
152
|
+
hint: {
|
|
153
|
+
description:
|
|
154
|
+
"--output-field writes a column on the dataset; --output-dataset creates a new dataset with the prompts and answers.",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
const itemFilter = await resolveItemFilter(client, input);
|
|
159
|
+
const model: CreateBatchInferenceRunBody["model"] = input.baseModel
|
|
160
|
+
? { type: "base_model", baseModelId: input.baseModel }
|
|
161
|
+
: {
|
|
162
|
+
type: "model_version",
|
|
163
|
+
modelVersionId: (await resolveModelVersion(client, input.projectId, input.modelVersion!))
|
|
164
|
+
.id,
|
|
165
|
+
};
|
|
166
|
+
const created = await client.batchInference.createRun({
|
|
167
|
+
projectId: input.projectId,
|
|
168
|
+
idempotencyKey: input.idempotencyKey ?? randomUUID(),
|
|
169
|
+
datasetId: input.datasetId,
|
|
170
|
+
...(itemFilter && { itemFilter }),
|
|
171
|
+
model,
|
|
172
|
+
sampling: {
|
|
173
|
+
maxOutputTokens: input.maxOutputTokens,
|
|
174
|
+
...(input.temperature !== undefined && { temperature: input.temperature }),
|
|
175
|
+
...(input.topP !== undefined && { topP: input.topP }),
|
|
176
|
+
...(input.stop.length > 0 && { stop: input.stop }),
|
|
177
|
+
...(input.seed !== undefined && { seed: input.seed }),
|
|
178
|
+
...(input.samplesPerRow !== undefined && { samplesPerRow: input.samplesPerRow }),
|
|
179
|
+
},
|
|
180
|
+
output:
|
|
181
|
+
input.outputField === undefined
|
|
182
|
+
? { type: "dataset", name: input.outputDataset! }
|
|
183
|
+
: { type: "field", fieldName: input.outputField },
|
|
184
|
+
});
|
|
185
|
+
const quoted = await waitUntil(
|
|
186
|
+
client,
|
|
187
|
+
created.id,
|
|
188
|
+
(run) => run.status !== "preparing",
|
|
189
|
+
input.pollIntervalMs ?? 1_000,
|
|
190
|
+
QUOTE_WAIT_MS,
|
|
191
|
+
);
|
|
192
|
+
assertCli(quoted.status === "awaiting_confirmation", formatBatchInferenceRun(quoted), {
|
|
193
|
+
hint: { description: "Fix the selection or the model, then start a new run." },
|
|
194
|
+
});
|
|
195
|
+
if (!input.yes) {
|
|
196
|
+
writeOutput(
|
|
197
|
+
io,
|
|
198
|
+
input.json,
|
|
199
|
+
quoted,
|
|
200
|
+
`${formatBatchInferenceRun(quoted)}\n\nConfirm the maximum charge with: toloka batch-inference confirm ${quoted.id}`,
|
|
201
|
+
);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const confirmed = await client.batchInference.confirmRun({ batchInferenceRunId: quoted.id });
|
|
205
|
+
writeOutput(io, input.json, confirmed, formatBatchInferenceRun(confirmed));
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const listBatchInferenceRuns = async (
|
|
209
|
+
client: BatchInferenceApi,
|
|
210
|
+
input: { projectId: string; json: boolean },
|
|
211
|
+
io: BatchInferenceIo,
|
|
212
|
+
): Promise<void> => {
|
|
213
|
+
const page = await client.batchInference.listRunsPage({
|
|
214
|
+
projectId: input.projectId,
|
|
215
|
+
limit: 100,
|
|
216
|
+
offset: 0,
|
|
217
|
+
});
|
|
218
|
+
writeOutput(
|
|
219
|
+
io,
|
|
220
|
+
input.json,
|
|
221
|
+
page.batchInferenceRuns,
|
|
222
|
+
page.batchInferenceRuns.length === 0
|
|
223
|
+
? "No Batch Inference Runs found."
|
|
224
|
+
: formatTable([
|
|
225
|
+
["ID", "STATUS", "DATASET", "MAXIMUM CHARGE", "CHARGED"],
|
|
226
|
+
...page.batchInferenceRuns.map((run) => [
|
|
227
|
+
run.id,
|
|
228
|
+
run.status,
|
|
229
|
+
run.datasetId,
|
|
230
|
+
run.quote ? `$${run.quote.maximumCharge}` : "-",
|
|
231
|
+
run.chargedAmount ? `$${run.chargedAmount}` : "-",
|
|
232
|
+
]),
|
|
233
|
+
]),
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const getBatchInferenceRun = async (
|
|
238
|
+
client: BatchInferenceApi,
|
|
239
|
+
input: { batchInferenceRunId: string; wait: boolean; json: boolean; pollIntervalMs?: number },
|
|
240
|
+
io: BatchInferenceIo,
|
|
241
|
+
): Promise<void> => {
|
|
242
|
+
const run = input.wait
|
|
243
|
+
? await waitUntil(
|
|
244
|
+
client,
|
|
245
|
+
input.batchInferenceRunId,
|
|
246
|
+
(candidate) => SETTLED_STATUSES.has(candidate.status),
|
|
247
|
+
input.pollIntervalMs ?? 5_000,
|
|
248
|
+
Number.POSITIVE_INFINITY,
|
|
249
|
+
)
|
|
250
|
+
: await client.batchInference.getRun({ batchInferenceRunId: input.batchInferenceRunId });
|
|
251
|
+
writeOutput(io, input.json, run, formatBatchInferenceRun(run));
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export const changeBatchInferenceRun = async (
|
|
255
|
+
client: BatchInferenceApi,
|
|
256
|
+
input: { batchInferenceRunId: string; action: "confirm" | "cancel" | "resume"; json: boolean },
|
|
257
|
+
io: BatchInferenceIo,
|
|
258
|
+
): Promise<void> => {
|
|
259
|
+
const request = { batchInferenceRunId: input.batchInferenceRunId };
|
|
260
|
+
const run =
|
|
261
|
+
input.action === "confirm"
|
|
262
|
+
? await client.batchInference.confirmRun(request)
|
|
263
|
+
: input.action === "cancel"
|
|
264
|
+
? await client.batchInference.cancelRun(request)
|
|
265
|
+
: await client.batchInference.resumeRun(request);
|
|
266
|
+
writeOutput(io, input.json, run, formatBatchInferenceRun(run));
|
|
267
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
changeBatchInferenceRun,
|
|
5
|
+
getBatchInferenceRun,
|
|
6
|
+
listBatchInferenceRuns,
|
|
7
|
+
startBatchInferenceRun,
|
|
8
|
+
type StartBatchInferenceRunInput,
|
|
9
|
+
} from "../batch-inference";
|
|
10
|
+
import {
|
|
11
|
+
collectValues,
|
|
12
|
+
nonNegativeInteger,
|
|
13
|
+
partNames,
|
|
14
|
+
positiveInteger,
|
|
15
|
+
resolveContext,
|
|
16
|
+
samplingTemperature,
|
|
17
|
+
samplingTopP,
|
|
18
|
+
type CommandHost,
|
|
19
|
+
} from "../context";
|
|
20
|
+
import { parseBaseModelId } from "../models";
|
|
21
|
+
import { assertProjectSelected } from "../project-context";
|
|
22
|
+
|
|
23
|
+
type RunOptions = Omit<StartBatchInferenceRunInput, "projectId" | "datasetId"> & {
|
|
24
|
+
dataset: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const registerBatchInferenceCommands = (program: Command, host: CommandHost): void => {
|
|
28
|
+
const { context, globals } = host;
|
|
29
|
+
const batchInference = program
|
|
30
|
+
.command("batch-inference")
|
|
31
|
+
.description("answer dataset rows with a base model or Model Version");
|
|
32
|
+
batchInference
|
|
33
|
+
.command("run")
|
|
34
|
+
.requiredOption("--dataset <id>", "Dataset whose chat messages are the prompts")
|
|
35
|
+
.option("--filter <field=value>", "only rows whose field equals the value")
|
|
36
|
+
.option("--slice <id>", "only rows of this ready Dataset Slice, with the prompts it froze")
|
|
37
|
+
.option(
|
|
38
|
+
"--parts <names>",
|
|
39
|
+
"only rows in these split parts, comma-separated, for example test",
|
|
40
|
+
partNames,
|
|
41
|
+
)
|
|
42
|
+
.option(
|
|
43
|
+
"--split-column <name-or-id>",
|
|
44
|
+
"split column that --parts names, when the dataset has more than one",
|
|
45
|
+
)
|
|
46
|
+
.option("--base-model <id>", "catalog Base Model that answers", parseBaseModelId)
|
|
47
|
+
.option("--model-version <id>", "Model Version that answers: mdv.<id> or Model@name")
|
|
48
|
+
.option("--output-field <name>", "text column on the dataset that receives the answers")
|
|
49
|
+
.option("--output-dataset <name>", "new dataset that receives the prompts and answers")
|
|
50
|
+
.requiredOption(
|
|
51
|
+
"--max-output-tokens <count>",
|
|
52
|
+
"token limit per answer, at most 8192",
|
|
53
|
+
positiveInteger,
|
|
54
|
+
)
|
|
55
|
+
.option(
|
|
56
|
+
"--temperature <value>",
|
|
57
|
+
"sampling temperature from 0 to 2 (default 0)",
|
|
58
|
+
samplingTemperature,
|
|
59
|
+
)
|
|
60
|
+
.option("--top-p <value>", "nucleus sampling threshold above 0 and at most 1", samplingTopP)
|
|
61
|
+
.option("--stop <text>", "stop sequence; repeat for up to four", collectValues, [])
|
|
62
|
+
.option("--seed <number>", "sampling seed", nonNegativeInteger)
|
|
63
|
+
.option(
|
|
64
|
+
"--samples-per-row <count>",
|
|
65
|
+
"answers per row, at most 16; above 1 needs --output-dataset and a temperature above 0",
|
|
66
|
+
positiveInteger,
|
|
67
|
+
)
|
|
68
|
+
.option("--idempotency-key <key>", "reuse the run of an earlier identical request")
|
|
69
|
+
.option("--yes", "confirm the quoted maximum charge and start right away", false)
|
|
70
|
+
.option("--json", "print JSON", false)
|
|
71
|
+
.description("quote a Batch Inference Run, then confirm it to start")
|
|
72
|
+
.action(async (options: RunOptions) => {
|
|
73
|
+
const { client, projectId } = await resolveContext(globals(), context);
|
|
74
|
+
assertProjectSelected(projectId);
|
|
75
|
+
const { dataset, ...rest } = options;
|
|
76
|
+
await startBatchInferenceRun(client, { ...rest, projectId, datasetId: dataset }, context.io);
|
|
77
|
+
});
|
|
78
|
+
batchInference
|
|
79
|
+
.command("list")
|
|
80
|
+
.option("--json", "print JSON", false)
|
|
81
|
+
.description("list the selected project's Batch Inference Runs")
|
|
82
|
+
.action(async (options: { json: boolean }) => {
|
|
83
|
+
const { client, projectId } = await resolveContext(globals(), context);
|
|
84
|
+
assertProjectSelected(projectId);
|
|
85
|
+
await listBatchInferenceRuns(client, { projectId, json: options.json }, context.io);
|
|
86
|
+
});
|
|
87
|
+
batchInference
|
|
88
|
+
.command("status")
|
|
89
|
+
.argument("<batch-inference-run-id>", "Batch Inference Run ID")
|
|
90
|
+
.option("--wait", "wait until the run finishes or needs you", false)
|
|
91
|
+
.option("--json", "print JSON", false)
|
|
92
|
+
.description("show one Batch Inference Run")
|
|
93
|
+
.action(async (batchInferenceRunId: string, options: { wait: boolean; json: boolean }) => {
|
|
94
|
+
const { client } = await resolveContext(globals(), context);
|
|
95
|
+
await getBatchInferenceRun(client, { batchInferenceRunId, ...options }, context.io);
|
|
96
|
+
});
|
|
97
|
+
for (const [action, description] of [
|
|
98
|
+
["confirm", "accept the quoted maximum charge and start the run"],
|
|
99
|
+
["resume", "resume a run blocked on funds or payment"],
|
|
100
|
+
["cancel", "cancel a run; answers already generated are kept and the GPU time used is charged"],
|
|
101
|
+
] as const) {
|
|
102
|
+
batchInference
|
|
103
|
+
.command(action)
|
|
104
|
+
.argument("<batch-inference-run-id>", "Batch Inference Run ID")
|
|
105
|
+
.option("--json", "print JSON", false)
|
|
106
|
+
.description(description)
|
|
107
|
+
.action(async (batchInferenceRunId: string, options: { json: boolean }) => {
|
|
108
|
+
const { client } = await resolveContext(globals(), context);
|
|
109
|
+
await changeBatchInferenceRun(
|
|
110
|
+
client,
|
|
111
|
+
{ batchInferenceRunId, action, json: options.json },
|
|
112
|
+
context.io,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
package/src/cli/context.ts
CHANGED
|
@@ -97,6 +97,20 @@ export const nonNegativeInteger = (raw: string): number => {
|
|
|
97
97
|
return value;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
export const samplingTemperature = (raw: string): number => {
|
|
101
|
+
const value = Number(raw);
|
|
102
|
+
if (raw.trim() === "" || !Number.isFinite(value) || value < 0 || value > 2)
|
|
103
|
+
throw new InvalidArgumentError("Expected a temperature from 0 to 2.");
|
|
104
|
+
return value;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const samplingTopP = (raw: string): number => {
|
|
108
|
+
const value = Number(raw);
|
|
109
|
+
if (raw.trim() === "" || !Number.isFinite(value) || value <= 0 || value > 1)
|
|
110
|
+
throw new InvalidArgumentError("Expected a top-p above 0 and at most 1.");
|
|
111
|
+
return value;
|
|
112
|
+
};
|
|
113
|
+
|
|
100
114
|
export const trainingMethod = (raw: string): "sft" | "gisting" => {
|
|
101
115
|
if (raw !== "sft" && raw !== "gisting") {
|
|
102
116
|
throw new InvalidArgumentError('Expected training method "sft" or "gisting".');
|
package/src/cli/models.ts
CHANGED
package/src/cli/program.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { registerSourceCommands } from "./commands/source";
|
|
|
23
23
|
import { registerStatusCommand } from "./commands/status";
|
|
24
24
|
import { registerTrainingCommands } from "./commands/training";
|
|
25
25
|
import { registerUiCommands } from "./commands/ui";
|
|
26
|
+
import { registerBatchInferenceCommands } from "./commands/batch-inference";
|
|
26
27
|
import { registerVersionCommand } from "./commands/version";
|
|
27
28
|
import type { TolokaEnvironment } from "./config";
|
|
28
29
|
import {
|
|
@@ -102,6 +103,7 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
102
103
|
registerSecretCommands(program, host);
|
|
103
104
|
registerModelCommands(program, host);
|
|
104
105
|
registerTrainingCommands(program, host);
|
|
106
|
+
registerBatchInferenceCommands(program, host);
|
|
105
107
|
registerDatasetCommands(program, host);
|
|
106
108
|
registerPipelineCommands(program, host);
|
|
107
109
|
registerStatusCommand(program, host);
|