@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.
Files changed (73) hide show
  1. package/dist/cli/toloka.js +153 -151
  2. package/dist/cli/toloka.js.jsc +0 -0
  3. package/dist/cli/toloka.js.jsc.sha256 +1 -1
  4. package/dist/cli/toloka.js.sha256 +1 -1
  5. package/docs/cli.md +17 -0
  6. package/openapi/gen/api.ts +142 -0
  7. package/openapi/gen/models/batchInferenceMode.ts +3 -0
  8. package/openapi/gen/models/batchInferenceModeOneOf.ts +4 -0
  9. package/openapi/gen/models/batchInferenceModel.ts +4 -0
  10. package/openapi/gen/models/batchInferenceModelOneOf.ts +6 -0
  11. package/openapi/gen/models/batchInferenceModelOneOfBaseModelId.ts +2 -0
  12. package/openapi/gen/models/batchInferenceModelOneOfThree.ts +5 -0
  13. package/openapi/gen/models/batchInferenceRun.ts +35 -0
  14. package/openapi/gen/models/batchInferenceRunBlockedReason.ts +2 -0
  15. package/openapi/gen/models/batchInferenceRunChargedAmount.ts +2 -0
  16. package/openapi/gen/models/batchInferenceRunError.ts +3 -0
  17. package/openapi/gen/models/batchInferenceRunErrorAnyOf.ts +6 -0
  18. package/openapi/gen/models/batchInferenceRunErrorAnyOfCode.ts +2 -0
  19. package/openapi/gen/models/batchInferenceRunId.ts +2 -0
  20. package/openapi/gen/models/batchInferenceRunMessage.ts +2 -0
  21. package/openapi/gen/models/batchInferenceRunOutput.ts +9 -0
  22. package/openapi/gen/models/batchInferenceRunOutputType.ts +2 -0
  23. package/openapi/gen/models/batchInferenceRunProgress.ts +3 -0
  24. package/openapi/gen/models/batchInferenceRunProgressAnyOf.ts +7 -0
  25. package/openapi/gen/models/batchInferenceRunProgressAnyOfPhase.ts +2 -0
  26. package/openapi/gen/models/batchInferenceRunProgressAnyOfRequests.ts +5 -0
  27. package/openapi/gen/models/batchInferenceRunQuote.ts +3 -0
  28. package/openapi/gen/models/batchInferenceRunQuoteAnyOf.ts +9 -0
  29. package/openapi/gen/models/batchInferenceRunQuoteAnyOfConfirmBefore.ts +2 -0
  30. package/openapi/gen/models/batchInferenceRunResult.ts +3 -0
  31. package/openapi/gen/models/batchInferenceRunResultAnyOf.ts +12 -0
  32. package/openapi/gen/models/batchInferenceRunResultAnyOfErrorCounts.ts +2 -0
  33. package/openapi/gen/models/batchInferenceRunResultAnyOfErrorSamplesItem.ts +6 -0
  34. package/openapi/gen/models/batchInferenceRunSampling.ts +9 -0
  35. package/openapi/gen/models/batchInferenceRunStatus.ts +2 -0
  36. package/openapi/gen/models/createBatchInferenceRunBody.ts +14 -0
  37. package/openapi/gen/models/createBatchInferenceRunBodyItemFilter.ts +14 -0
  38. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterExcludePartsItem.ts +5 -0
  39. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItem.ts +6 -0
  40. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItem.ts +4 -0
  41. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
  42. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
  43. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItem.ts +12 -0
  44. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemOperator.ts +2 -0
  45. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItem.ts +4 -0
  46. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
  47. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
  48. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterFieldValuesItemValue.ts +2 -0
  49. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterParts.ts +5 -0
  50. package/openapi/gen/models/createBatchInferenceRunBodyItemFilterSlice.ts +4 -0
  51. package/openapi/gen/models/createBatchInferenceRunBodyOutput.ts +4 -0
  52. package/openapi/gen/models/createBatchInferenceRunBodyOutputOneOf.ts +5 -0
  53. package/openapi/gen/models/createBatchInferenceRunBodyOutputOneOfTwo.ts +5 -0
  54. package/openapi/gen/models/createBatchInferenceRunBodySampling.ts +9 -0
  55. package/openapi/gen/models/datasetFieldOperation.ts +2 -1
  56. package/openapi/gen/models/datasetFieldWriteOperation.ts +8 -0
  57. package/openapi/gen/models/datasetFieldWriteOperationProgress.ts +3 -0
  58. package/openapi/gen/models/datasetFieldWriteOperationProgressAnyOf.ts +5 -0
  59. package/openapi/gen/models/index.ts +53 -0
  60. package/openapi/gen/models/listBatchInferenceRuns200.ts +7 -0
  61. package/openapi/gen/models/listBatchInferenceRunsParams.ts +5 -0
  62. package/package.json +2 -2
  63. package/skills/catalog.json +6 -6
  64. package/skills/toloka-build/references/workflow-and-cli.md +5 -1
  65. package/src/cli/analytics.ts +8 -0
  66. package/src/cli/batch-inference.ts +267 -0
  67. package/src/cli/commands/batch-inference.ts +116 -0
  68. package/src/cli/context.ts +14 -0
  69. package/src/cli/models.ts +1 -1
  70. package/src/cli/program.ts +2 -0
  71. package/src/cli/splits.ts +40 -0
  72. package/src/cli/trainings.ts +1 -19
  73. 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';
@@ -0,0 +1,7 @@
1
+ import type { BatchInferenceRun } from './batchInferenceRun';
2
+ import type { Pagination } from './pagination';
3
+
4
+ export type ListBatchInferenceRuns200 = {
5
+ batchInferenceRuns: BatchInferenceRun[];
6
+ pagination: Pagination;
7
+ };
@@ -0,0 +1,5 @@
1
+
2
+ export type ListBatchInferenceRunsParams = {
3
+ limit?: number;
4
+ offset?: number;
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toloka-platform/sdk",
3
- "version": "0.12.2",
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": "72b9ea77f616c7f9c3c564c550eac42f703929b2"
80
+ "commit": "63680479b4e4905b946650ebec6070af910e2340"
81
81
  }
82
82
  }
@@ -11,8 +11,8 @@
11
11
  "testing",
12
12
  "checks"
13
13
  ],
14
- "version": "0.12.2",
15
- "digest": "7e6b79b9b85fbfc86cf98f33ebd30fdb4617707d22f7c24440bee2fedecb5f71",
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.12.2",
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.12.2",
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.12.2",
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.12.2",
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:
@@ -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
+ };
@@ -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
@@ -69,7 +69,7 @@ const resolveModel = async (
69
69
  return matches[0]!;
70
70
  };
71
71
 
72
- const resolveModelVersion = async (
72
+ export const resolveModelVersion = async (
73
73
  client: ModelApi,
74
74
  projectId: string | undefined,
75
75
  nameOrId: string,
@@ -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);