@toloka-platform/sdk 0.2.0-preview.2 → 0.2.0-preview.3
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 +159 -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/authoring.md +18 -1
- package/docs/cli-v2.md +4 -3
- package/docs/cli.md +29 -10
- 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/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 +1 -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/index.ts +19 -0
- package/openapi/gen/models/listDeliveryOrders200.ts +5 -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 +10 -7
- package/skills/toloka-build/references/node-semantics.md +6 -4
- package/skills/toloka-build/references/workflow-and-cli.md +21 -13
- package/skills/toloka-fine-tuning/SKILL.md +72 -0
- package/skills/toloka-get-started/SKILL.md +55 -0
- package/skills/toloka-gisting/SKILL.md +76 -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 +52 -6
- package/src/cli/auth.ts +69 -16
- package/src/cli/auto-update.ts +24 -33
- package/src/cli/check.ts +12 -10
- package/src/cli/commands/auth.ts +89 -0
- package/src/cli/commands/config.ts +28 -0
- package/src/cli/commands/dataset.ts +75 -0
- package/src/cli/commands/delivery-order.ts +18 -0
- package/src/cli/commands/models.ts +122 -0
- package/src/cli/commands/pipeline.ts +132 -0
- package/src/cli/commands/project.ts +175 -0
- package/src/cli/commands/report.ts +39 -0
- package/src/cli/commands/secret.ts +39 -0
- package/src/cli/commands/skill.ts +36 -0
- package/src/cli/commands/source.ts +204 -0
- package/src/cli/commands/status.ts +18 -0
- package/src/cli/commands/training.ts +228 -0
- package/src/cli/commands/version.ts +14 -0
- package/src/cli/config.ts +5 -6
- package/src/cli/context.ts +213 -0
- package/src/cli/delivery-orders.ts +30 -0
- package/src/cli/environment.ts +26 -0
- package/src/cli/init.ts +1 -4
- package/src/cli/program.ts +168 -1344
- package/src/cli/project-context.ts +9 -11
- package/src/cli/sdk-version.ts +38 -0
- package/src/cli/skill.ts +6 -2
- package/src/cli/terminal-io.ts +103 -0
- package/src/cli/trainings.ts +118 -27
- package/src/cli/version.ts +1 -1
- package/src/client.ts +18 -0
- package/src/importer.ts +32 -2
|
@@ -29,6 +29,7 @@ export type ProjectCreateApi = {
|
|
|
29
29
|
readonly create: (input: {
|
|
30
30
|
readonly teamId: string;
|
|
31
31
|
readonly name?: string;
|
|
32
|
+
readonly deliveryOrderId?: string;
|
|
32
33
|
}) => Promise<ProjectRecord>;
|
|
33
34
|
readonly listAll: (params?: object) => AsyncIterable<ProjectRecord>;
|
|
34
35
|
};
|
|
@@ -41,16 +42,11 @@ export const NO_PROJECT_SELECTED_MESSAGE =
|
|
|
41
42
|
"No project selected. Run toloka project create for a new project, toloka project add for an existing project, or cd into a checkout.";
|
|
42
43
|
|
|
43
44
|
const NETWORK_MISMATCH_MESSAGE =
|
|
44
|
-
"project add writes the machine registry; unset --api-key / --base-url /
|
|
45
|
+
"project add writes the machine registry; unset --api-key / --base-url / TOLOKA_API_KEY / TOLOKA_BASE_URL, or run login against that platform first.";
|
|
45
46
|
|
|
46
47
|
const ALIAS_PATTERN = /^[^/\s]+$/u;
|
|
47
48
|
|
|
48
|
-
export type ProjectContextSource =
|
|
49
|
-
| "flag"
|
|
50
|
-
| "toloka-project-id"
|
|
51
|
-
| "potato-project-id"
|
|
52
|
-
| "toloka.json"
|
|
53
|
-
| "registry";
|
|
49
|
+
export type ProjectContextSource = "flag" | "toloka-project-id" | "toloka.json" | "registry";
|
|
54
50
|
|
|
55
51
|
export type ProjectContext = {
|
|
56
52
|
readonly id: string;
|
|
@@ -93,7 +89,7 @@ const effectiveNetworkBaseUrl = (
|
|
|
93
89
|
stored: StoredCredentials | undefined,
|
|
94
90
|
): string | undefined =>
|
|
95
91
|
firstNonEmpty(flags?.baseUrl) ??
|
|
96
|
-
firstNonEmpty(environment?.
|
|
92
|
+
firstNonEmpty(environment?.TOLOKA_BASE_URL) ??
|
|
97
93
|
resolveInternalBaseUrl(environment ?? {}) ??
|
|
98
94
|
firstNonEmpty(stored?.baseUrl);
|
|
99
95
|
|
|
@@ -144,7 +140,6 @@ export function resolveProjectContext(
|
|
|
144
140
|
}> = [
|
|
145
141
|
{ id: firstNonEmpty(flags.projectId), source: "flag" },
|
|
146
142
|
{ id: firstNonEmpty(environment.TOLOKA_PROJECT_ID), source: "toloka-project-id" },
|
|
147
|
-
{ id: firstNonEmpty(environment.POTATO_PROJECT_ID), source: "potato-project-id" },
|
|
148
143
|
{ id: firstNonEmpty(options.state?.projectId), source: "toloka.json" },
|
|
149
144
|
{
|
|
150
145
|
id:
|
|
@@ -183,10 +178,10 @@ const assertStoredNetworkMatches = (
|
|
|
183
178
|
environment: TolokaEnvironment,
|
|
184
179
|
stored: StoredCredentials,
|
|
185
180
|
) => {
|
|
186
|
-
const flagOrEnvKey = firstNonEmpty(flags.apiKey) ?? firstNonEmpty(environment.
|
|
181
|
+
const flagOrEnvKey = firstNonEmpty(flags.apiKey) ?? firstNonEmpty(environment.TOLOKA_API_KEY);
|
|
187
182
|
const flagOrEnvBase =
|
|
188
183
|
firstNonEmpty(flags.baseUrl) ??
|
|
189
|
-
firstNonEmpty(environment.
|
|
184
|
+
firstNonEmpty(environment.TOLOKA_BASE_URL) ??
|
|
190
185
|
resolveInternalBaseUrl(environment);
|
|
191
186
|
assert(flagOrEnvKey === undefined || flagOrEnvKey === stored.apiKey, NETWORK_MISMATCH_MESSAGE);
|
|
192
187
|
assert(
|
|
@@ -464,6 +459,7 @@ export async function createProject(options: {
|
|
|
464
459
|
readonly alias?: string;
|
|
465
460
|
readonly name?: string;
|
|
466
461
|
readonly teamId?: string;
|
|
462
|
+
readonly deliveryOrderId?: string;
|
|
467
463
|
readonly sourceRoot?: string;
|
|
468
464
|
readonly afterPreflight?: () => Promise<void>;
|
|
469
465
|
}): Promise<{ readonly output: string; readonly projectId: string; readonly alias: string }> {
|
|
@@ -489,9 +485,11 @@ export async function createProject(options: {
|
|
|
489
485
|
await options.afterPreflight?.();
|
|
490
486
|
|
|
491
487
|
const teamId = await resolveTeamId(options.client, options.teamId);
|
|
488
|
+
const deliveryOrderId = firstNonEmpty(options.deliveryOrderId);
|
|
492
489
|
const created = await options.client.projects.create({
|
|
493
490
|
teamId,
|
|
494
491
|
name: firstNonEmpty(options.name) ?? alias,
|
|
492
|
+
...(deliveryOrderId === undefined ? {} : { deliveryOrderId }),
|
|
495
493
|
});
|
|
496
494
|
|
|
497
495
|
try {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import packageMetadata from "../../package.json" with { type: "json" };
|
|
2
|
+
|
|
3
|
+
export const SDK_PACKAGE_NAME = packageMetadata.name;
|
|
4
|
+
export const SDK_VERSION = packageMetadata.version;
|
|
5
|
+
|
|
6
|
+
export type ReleaseChannel = "latest" | "preview";
|
|
7
|
+
|
|
8
|
+
export const parseSdkVersion = (version: string): readonly number[] | undefined => {
|
|
9
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-preview\.(\d+))?$/u.exec(version);
|
|
10
|
+
if (match === null) return undefined;
|
|
11
|
+
const [, major, minor, patch, preview] = match;
|
|
12
|
+
return [
|
|
13
|
+
Number(major),
|
|
14
|
+
Number(minor),
|
|
15
|
+
Number(patch),
|
|
16
|
+
preview === undefined ? Number.POSITIVE_INFINITY : Number(preview),
|
|
17
|
+
];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const releaseChannelFor = (version: string): ReleaseChannel | undefined => {
|
|
21
|
+
const parsed = parseSdkVersion(version);
|
|
22
|
+
if (parsed === undefined) return undefined;
|
|
23
|
+
return parsed[3] === Number.POSITIVE_INFINITY ? "latest" : "preview";
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const compareSdkVersions = (left: string, right: string): number | undefined => {
|
|
27
|
+
const parsedLeft = parseSdkVersion(left);
|
|
28
|
+
const parsedRight = parseSdkVersion(right);
|
|
29
|
+
if (parsedLeft === undefined || parsedRight === undefined) return undefined;
|
|
30
|
+
const length = Math.max(parsedLeft.length, parsedRight.length);
|
|
31
|
+
for (let index = 0; index < length; index += 1) {
|
|
32
|
+
const leftPart = parsedLeft[index] ?? 0;
|
|
33
|
+
const rightPart = parsedRight[index] ?? 0;
|
|
34
|
+
if (leftPart < rightPart) return -1;
|
|
35
|
+
if (leftPart > rightPart) return 1;
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
};
|
package/src/cli/skill.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { copyFile, mkdir, mkdtemp, readFile, realpath, rename, rm, stat } from "
|
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { assertSafeDestination, entryStats, hasErrorCode } from "./file-system";
|
|
6
6
|
import { formatCliError } from "./format-error";
|
|
7
|
+
import { releaseChannelFor, SDK_PACKAGE_NAME, SDK_VERSION } from "./sdk-version";
|
|
7
8
|
|
|
8
9
|
export const SKILL_CATALOG_MANIFEST = "catalog.json";
|
|
9
10
|
export const SKILL_INSTALL_ROOT = ".agents/skills";
|
|
@@ -15,6 +16,9 @@ const MAX_MANIFEST_BYTES = 1024 * 1024;
|
|
|
15
16
|
const MAX_SKILL_ID_LENGTH = 64;
|
|
16
17
|
const SKILL_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
17
18
|
const DIGEST_PATTERN = /^[a-f0-9]{64}$/u;
|
|
19
|
+
const UPGRADE_HINT = `bun add --global ${SDK_PACKAGE_NAME}@${
|
|
20
|
+
releaseChannelFor(SDK_VERSION) ?? "latest"
|
|
21
|
+
}`;
|
|
18
22
|
|
|
19
23
|
export type SkillDescriptor = {
|
|
20
24
|
readonly id: string;
|
|
@@ -269,7 +273,7 @@ export async function installSkill({
|
|
|
269
273
|
const skill = catalog.skills.find((candidate) => candidate.id === skillId);
|
|
270
274
|
assert(
|
|
271
275
|
skill,
|
|
272
|
-
`Skill ${JSON.stringify(skillId)} is not in the catalog. Run \`toloka skill list\` to see the available skills.`,
|
|
276
|
+
`Skill ${JSON.stringify(skillId)} is not in the catalog bundled with ${SDK_PACKAGE_NAME} ${SDK_VERSION}. Run \`toloka skill list\` to see the available skills; a newer release may ship it (${UPGRADE_HINT}).`,
|
|
273
277
|
);
|
|
274
278
|
const relativeDestination = `${SKILL_INSTALL_ROOT}/${skill.id}`;
|
|
275
279
|
await assertSafeDestination(sourceRoot, relativeDestination);
|
|
@@ -348,7 +352,7 @@ export async function skillSearchCommand({
|
|
|
348
352
|
return;
|
|
349
353
|
}
|
|
350
354
|
io.stdout(
|
|
351
|
-
`No skills match ${JSON.stringify(query)}. Run \`toloka skill list\` to see
|
|
355
|
+
`No skills match ${JSON.stringify(query)} in the catalog bundled with ${SDK_PACKAGE_NAME} ${SDK_VERSION}. Run \`toloka skill list\` to see it; a newer release may add more (${UPGRADE_HINT}).`,
|
|
352
356
|
);
|
|
353
357
|
}
|
|
354
358
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import type { AuthPrompt } from "./auth";
|
|
3
|
+
import type { CliIo } from "./context";
|
|
4
|
+
import { pickPipeline } from "./runs";
|
|
5
|
+
|
|
6
|
+
const readStandardInput = async (): Promise<string> => {
|
|
7
|
+
process.stdin.setEncoding("utf8");
|
|
8
|
+
const chunks: string[] = [];
|
|
9
|
+
for await (const chunk of process.stdin) chunks.push(String(chunk));
|
|
10
|
+
return chunks.join("");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const readMaskedLine = async (question: string): Promise<string> => {
|
|
14
|
+
process.stdout.write(question);
|
|
15
|
+
const wasRaw = process.stdin.isRaw;
|
|
16
|
+
process.stdin.setRawMode(true);
|
|
17
|
+
process.stdin.resume();
|
|
18
|
+
process.stdin.setEncoding("utf8");
|
|
19
|
+
|
|
20
|
+
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
|
21
|
+
let value = "";
|
|
22
|
+
|
|
23
|
+
const restore = () => {
|
|
24
|
+
process.stdin.off("data", onData);
|
|
25
|
+
process.stdin.off("end", onEnd);
|
|
26
|
+
process.stdin.off("error", onError);
|
|
27
|
+
process.stdin.setRawMode(wasRaw);
|
|
28
|
+
process.stdin.pause();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const finish = () => {
|
|
32
|
+
restore();
|
|
33
|
+
process.stdout.write("\n");
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const onEnd = () => {
|
|
37
|
+
finish();
|
|
38
|
+
reject(new Error("Standard input closed before an API key was entered."));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const onError = (error: Error) => {
|
|
42
|
+
finish();
|
|
43
|
+
reject(error);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const onData = (chunk: string) => {
|
|
47
|
+
for (const character of chunk) {
|
|
48
|
+
if (character === "\r" || character === "\n") {
|
|
49
|
+
finish();
|
|
50
|
+
resolve(value);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (character === "\u0003" || character === "\u0004") {
|
|
54
|
+
finish();
|
|
55
|
+
reject(
|
|
56
|
+
new Error(
|
|
57
|
+
character === "\u0003"
|
|
58
|
+
? "Canceled."
|
|
59
|
+
: "Standard input closed before an API key was entered.",
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (character === "\u007f" || character === "\b") {
|
|
65
|
+
if (value.length > 0) {
|
|
66
|
+
value = value.slice(0, -1);
|
|
67
|
+
process.stdout.write("\b \b");
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
value += character;
|
|
72
|
+
process.stdout.write("*");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
process.stdin.on("data", onData);
|
|
77
|
+
process.stdin.once("end", onEnd);
|
|
78
|
+
process.stdin.once("error", onError);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
return await promise;
|
|
82
|
+
} finally {
|
|
83
|
+
restore();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const promptFromTerminal: AuthPrompt = async (question, options = {}) => {
|
|
88
|
+
if (options.mask === true) return readMaskedLine(question);
|
|
89
|
+
|
|
90
|
+
const input = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
91
|
+
try {
|
|
92
|
+
return await input.question(question);
|
|
93
|
+
} finally {
|
|
94
|
+
input.close();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const terminalIo = (): CliIo => ({
|
|
99
|
+
stdout: (text) => console.log(text),
|
|
100
|
+
stderr: (text) => console.error(text),
|
|
101
|
+
readStdin: readStandardInput,
|
|
102
|
+
...(process.stdin.isTTY ? { prompt: promptFromTerminal, select: pickPipeline } : {}),
|
|
103
|
+
});
|
package/src/cli/trainings.ts
CHANGED
|
@@ -5,7 +5,14 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
|
|
6
6
|
import { assert } from "es-toolkit";
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
PotatoClient,
|
|
10
|
+
Training,
|
|
11
|
+
TrainingCostEstimate,
|
|
12
|
+
TrainingRun,
|
|
13
|
+
TrainingRunMetricHistory,
|
|
14
|
+
TrainingRunMetrics,
|
|
15
|
+
} from "../client";
|
|
9
16
|
import type { BaseModelId, TrainingConfigurationInput } from "../../openapi/gen/models";
|
|
10
17
|
|
|
11
18
|
type TrainingMethod = TrainingConfigurationInput["method"];
|
|
@@ -33,6 +40,7 @@ export type TrainingDraftOptions = {
|
|
|
33
40
|
type TrainingApi = Pick<PotatoClient, "trainings">;
|
|
34
41
|
type TrainingDraftApi = Pick<PotatoClient, "trainings" | "files" | "datasets">;
|
|
35
42
|
type TrainingCostApi = Pick<PotatoClient, "trainings" | "waitFor">;
|
|
43
|
+
type TrainingRunApi = Pick<PotatoClient, "trainings" | "waitFor">;
|
|
36
44
|
const GISTING_PROMPT_MAX_BYTES = 256 * 1024;
|
|
37
45
|
type TrainingIo = { readonly stdout: (text: string) => void };
|
|
38
46
|
type TableRow = readonly string[];
|
|
@@ -446,7 +454,10 @@ export const createTraining = async (
|
|
|
446
454
|
io: TrainingIo,
|
|
447
455
|
): Promise<void> => {
|
|
448
456
|
assert(input.datasetId, "--dataset is required.");
|
|
449
|
-
assert(
|
|
457
|
+
assert(
|
|
458
|
+
!input.validationFilter || input.validationDatasetId,
|
|
459
|
+
"--validation-filter requires --validation-dataset.",
|
|
460
|
+
);
|
|
450
461
|
|
|
451
462
|
const configuration = await buildTrainingConfiguration({
|
|
452
463
|
client,
|
|
@@ -458,12 +469,7 @@ export const createTraining = async (
|
|
|
458
469
|
name: input.name,
|
|
459
470
|
configuration,
|
|
460
471
|
});
|
|
461
|
-
writeOutput(
|
|
462
|
-
io,
|
|
463
|
-
input.json,
|
|
464
|
-
training,
|
|
465
|
-
`Created training ${training.id}`,
|
|
466
|
-
);
|
|
472
|
+
writeOutput(io, input.json, training, `Created training ${training.id}`);
|
|
467
473
|
};
|
|
468
474
|
|
|
469
475
|
export const updateTraining = async (
|
|
@@ -485,19 +491,13 @@ export const updateTraining = async (
|
|
|
485
491
|
options: input,
|
|
486
492
|
}),
|
|
487
493
|
});
|
|
488
|
-
writeOutput(
|
|
489
|
-
io,
|
|
490
|
-
input.json,
|
|
491
|
-
updated,
|
|
492
|
-
`Updated training ${updated.id}`,
|
|
493
|
-
);
|
|
494
|
+
writeOutput(io, input.json, updated, `Updated training ${updated.id}`);
|
|
494
495
|
};
|
|
495
496
|
|
|
496
|
-
|
|
497
|
+
const prepareTrainingCostEstimate = async (
|
|
497
498
|
client: TrainingCostApi,
|
|
498
|
-
input: {
|
|
499
|
-
|
|
500
|
-
): Promise<void> => {
|
|
499
|
+
input: { nameOrId: string; projectId: string; idempotencyKey?: string },
|
|
500
|
+
): Promise<{ training: Training; estimate: TrainingCostEstimate }> => {
|
|
501
501
|
const training = await resolveTraining(client, input.projectId, input.nameOrId);
|
|
502
502
|
const inputSet = await client.trainings.prepareCostEstimateInputs({
|
|
503
503
|
trainingId: training.id,
|
|
@@ -511,33 +511,53 @@ export const estimateTrainingCost = async (
|
|
|
511
511
|
exportIds.map((exportId) => client.waitFor({ type: "datasetExport", exportId })),
|
|
512
512
|
);
|
|
513
513
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
514
|
+
return {
|
|
515
|
+
training,
|
|
516
|
+
estimate: await client.trainings.estimateCost({
|
|
517
|
+
trainingId: training.id,
|
|
518
|
+
inputSetId: inputSet.id,
|
|
519
|
+
}),
|
|
520
|
+
};
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
export const estimateTrainingCost = async (
|
|
524
|
+
client: TrainingCostApi,
|
|
525
|
+
input: { projectId: string; nameOrId: string; idempotencyKey?: string; json: boolean },
|
|
526
|
+
io: TrainingIo,
|
|
527
|
+
): Promise<void> => {
|
|
528
|
+
const { estimate } = await prepareTrainingCostEstimate(client, input);
|
|
518
529
|
writeOutput(io, input.json, estimate, formatCostEstimate(estimate));
|
|
519
530
|
};
|
|
520
531
|
|
|
521
532
|
export const createTrainingRun = async (
|
|
522
|
-
client:
|
|
533
|
+
client: TrainingRunApi,
|
|
523
534
|
input: {
|
|
524
535
|
projectId: string;
|
|
525
536
|
nameOrId: string;
|
|
526
|
-
estimateFile
|
|
527
|
-
versionName
|
|
537
|
+
estimateFile?: string;
|
|
538
|
+
versionName?: string;
|
|
528
539
|
idempotencyKey?: string;
|
|
529
540
|
json: boolean;
|
|
530
541
|
},
|
|
531
542
|
io: TrainingIo,
|
|
532
543
|
): Promise<void> => {
|
|
533
544
|
const training = await resolveTraining(client, input.projectId, input.nameOrId);
|
|
534
|
-
const estimate =
|
|
545
|
+
const estimate = input.estimateFile
|
|
546
|
+
? parseReadyEstimateFile(JSON.parse(await readFile(input.estimateFile, "utf8")))
|
|
547
|
+
: await (async () => {
|
|
548
|
+
const prepared = await prepareTrainingCostEstimate(client, {
|
|
549
|
+
projectId: input.projectId,
|
|
550
|
+
nameOrId: input.nameOrId,
|
|
551
|
+
});
|
|
552
|
+
assert(prepared.estimate.status === "ready", formatCostEstimate(prepared.estimate));
|
|
553
|
+
return parseReadyEstimateFile(prepared.estimate);
|
|
554
|
+
})();
|
|
535
555
|
assert(estimate.trainingId === training.id, "The estimate file belongs to another Training.");
|
|
536
556
|
const run = await client.trainings.createRun({
|
|
537
557
|
trainingId: training.id,
|
|
538
558
|
inputSetId: estimate.inputSetId,
|
|
539
559
|
estimateFingerprint: estimate.fingerprint,
|
|
540
|
-
versionName: input.versionName,
|
|
560
|
+
versionName: input.versionName ?? training.name,
|
|
541
561
|
idempotencyKey: input.idempotencyKey ?? randomUUID(),
|
|
542
562
|
});
|
|
543
563
|
writeOutput(io, input.json, run, formatTrainingRun(run));
|
|
@@ -625,3 +645,74 @@ export const readTrainingRunLogs = async (
|
|
|
625
645
|
});
|
|
626
646
|
}
|
|
627
647
|
};
|
|
648
|
+
|
|
649
|
+
const formatMetricValue = (point: {
|
|
650
|
+
value: number | null;
|
|
651
|
+
nonFinite: "nan" | "positive_infinity" | "negative_infinity" | null;
|
|
652
|
+
}): string => {
|
|
653
|
+
if (point.nonFinite === "nan") return "NaN";
|
|
654
|
+
if (point.nonFinite === "positive_infinity") return "Infinity";
|
|
655
|
+
if (point.nonFinite === "negative_infinity") return "-Infinity";
|
|
656
|
+
return point.value === null ? "-" : String(point.value);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const METRICS_UNAVAILABLE: Record<"not_configured" | "unresolved", string> = {
|
|
660
|
+
not_configured: "This Training Run has no metrics: it started before metrics were available, or the server records none.",
|
|
661
|
+
unresolved: "Metrics are temporarily unreachable. The Training Run itself is unaffected; try again later.",
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const formatTrainingRunMetrics = (metrics: TrainingRunMetrics): string => {
|
|
665
|
+
if (!metrics.available) {
|
|
666
|
+
return metrics.unavailableReason === null
|
|
667
|
+
? "No metrics are available for this Training Run."
|
|
668
|
+
: METRICS_UNAVAILABLE[metrics.unavailableReason];
|
|
669
|
+
}
|
|
670
|
+
if (metrics.metrics.length === 0) return "No metrics have been reported yet.";
|
|
671
|
+
|
|
672
|
+
const rows = metrics.metrics.map((entry) => [
|
|
673
|
+
entry.key,
|
|
674
|
+
entry.metric ?? "-",
|
|
675
|
+
formatMetricValue(entry.latest),
|
|
676
|
+
String(entry.latest.step),
|
|
677
|
+
]);
|
|
678
|
+
const table = formatTable([["Metric", "Meaning", "Latest", "Step"], ...rows]);
|
|
679
|
+
return metrics.truncated ? `${table}\n\nMore metrics exist than the server returns.` : table;
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const formatTrainingRunMetricHistory = (history: TrainingRunMetricHistory): string => {
|
|
683
|
+
const header = formatTable([
|
|
684
|
+
["Metric", history.key],
|
|
685
|
+
["Meaning", history.metric ?? "-"],
|
|
686
|
+
["Points", String(history.points.length)],
|
|
687
|
+
]);
|
|
688
|
+
const points = formatTable([
|
|
689
|
+
["Step", "Value"],
|
|
690
|
+
...history.points.map((point) => [String(point.step), formatMetricValue(point)]),
|
|
691
|
+
]);
|
|
692
|
+
const note = history.truncated
|
|
693
|
+
? "\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."
|
|
694
|
+
: "";
|
|
695
|
+
return `${header}\n\n${points}${note}`;
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
export const readTrainingRunMetrics = async (
|
|
699
|
+
client: TrainingApi,
|
|
700
|
+
input: { trainingRunId: string; json: boolean },
|
|
701
|
+
io: TrainingIo,
|
|
702
|
+
): Promise<void> => {
|
|
703
|
+
const metrics = await client.trainings.getRunMetrics({ trainingRunId: input.trainingRunId });
|
|
704
|
+
writeOutput(io, input.json, metrics, formatTrainingRunMetrics(metrics));
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
export const readTrainingRunMetricHistory = async (
|
|
708
|
+
client: TrainingApi,
|
|
709
|
+
input: { trainingRunId: string; metricKey: string; resolution?: number; json: boolean },
|
|
710
|
+
io: TrainingIo,
|
|
711
|
+
): Promise<void> => {
|
|
712
|
+
const history = await client.trainings.getRunMetricHistory({
|
|
713
|
+
trainingRunId: input.trainingRunId,
|
|
714
|
+
metricKey: input.metricKey,
|
|
715
|
+
resolution: input.resolution,
|
|
716
|
+
});
|
|
717
|
+
writeOutput(io, input.json, history, formatTrainingRunMetricHistory(history));
|
|
718
|
+
};
|
package/src/cli/version.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { assert } from "es-toolkit";
|
|
6
|
-
import { SDK_VERSION } from "./
|
|
6
|
+
import { SDK_VERSION } from "./sdk-version";
|
|
7
7
|
|
|
8
8
|
export type CliInstallChannel = "global-registry" | "linked" | "project-local";
|
|
9
9
|
|
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>;
|
|
@@ -482,6 +487,14 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
482
487
|
...params
|
|
483
488
|
}: { trainingRunId: TrainingRunId } & GetTrainingRunLogsParams) =>
|
|
484
489
|
wire.getTrainingRunLogs(trainingRunId, params, requestOptions),
|
|
490
|
+
getRunMetrics: ({ trainingRunId }: { trainingRunId: TrainingRunId }) =>
|
|
491
|
+
wire.getTrainingRunMetrics(trainingRunId, requestOptions),
|
|
492
|
+
getRunMetricHistory: ({
|
|
493
|
+
trainingRunId,
|
|
494
|
+
metricKey,
|
|
495
|
+
...params
|
|
496
|
+
}: { trainingRunId: TrainingRunId; metricKey: string } & GetTrainingRunMetricHistoryParams) =>
|
|
497
|
+
wire.getTrainingRunMetricHistory(trainingRunId, metricKey, params, requestOptions),
|
|
485
498
|
};
|
|
486
499
|
|
|
487
500
|
const datasets = {
|
|
@@ -624,6 +637,10 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
624
637
|
wire.getPipelineCheck(checkRunId, requestOptions),
|
|
625
638
|
};
|
|
626
639
|
|
|
640
|
+
const deliveryOrders = {
|
|
641
|
+
list: async () => (await wire.listDeliveryOrders(requestOptions)).deliveryOrders,
|
|
642
|
+
};
|
|
643
|
+
|
|
627
644
|
return {
|
|
628
645
|
me: () => wire.getCurrentUser(requestOptions),
|
|
629
646
|
projects,
|
|
@@ -636,6 +653,7 @@ const createClientImplementation = (config: ClientConfig) => {
|
|
|
636
653
|
secrets,
|
|
637
654
|
files,
|
|
638
655
|
checks,
|
|
656
|
+
deliveryOrders,
|
|
639
657
|
waitFor,
|
|
640
658
|
};
|
|
641
659
|
};
|
package/src/importer.ts
CHANGED
|
@@ -263,6 +263,32 @@ function transformExam<Exam extends { gradedEntities: Array<{ targetEntity: stri
|
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
const generationCheckNames: readonly string[] = [
|
|
267
|
+
"ui",
|
|
268
|
+
"llmqa",
|
|
269
|
+
"qc_lint",
|
|
270
|
+
"ui_types_lint",
|
|
271
|
+
"exam_dataset",
|
|
272
|
+
"synthgen",
|
|
273
|
+
];
|
|
274
|
+
const quorumCheckNames: readonly string[] = [
|
|
275
|
+
"ui",
|
|
276
|
+
"llmqa",
|
|
277
|
+
"qc_lint",
|
|
278
|
+
"ui_types_lint",
|
|
279
|
+
"exam_dataset",
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
const normalizeImportedChecks = (
|
|
283
|
+
checks: Record<string, unknown[]> | undefined,
|
|
284
|
+
requiredNames: readonly string[],
|
|
285
|
+
): Record<string, unknown[]> | undefined => {
|
|
286
|
+
if (checks === undefined) return undefined;
|
|
287
|
+
const normalized = { ...checks };
|
|
288
|
+
for (const name of requiredNames) normalized[name] ??= [];
|
|
289
|
+
return normalized;
|
|
290
|
+
};
|
|
291
|
+
|
|
266
292
|
function authoringSettings(node: GraphNode, fieldReference: FieldReference): unknown {
|
|
267
293
|
switch (node.type) {
|
|
268
294
|
case "root": {
|
|
@@ -296,9 +322,11 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
|
|
|
296
322
|
};
|
|
297
323
|
}
|
|
298
324
|
case "generation": {
|
|
299
|
-
const { entities, qualityCriteria, exam, ...settings } = node.settings;
|
|
325
|
+
const { entities, qualityCriteria, exam, checks, ...settings } = node.settings;
|
|
326
|
+
const normalizedChecks = normalizeImportedChecks(checks, generationCheckNames);
|
|
300
327
|
return {
|
|
301
328
|
...settings,
|
|
329
|
+
...(normalizedChecks === undefined ? {} : { checks: normalizedChecks }),
|
|
302
330
|
entities: {
|
|
303
331
|
input: entities.input.map((entity) => transformEntity(entity, fieldReference)),
|
|
304
332
|
output: entities.output.map((entity) => transformEntity(entity, fieldReference)),
|
|
@@ -326,9 +354,11 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
|
|
|
326
354
|
};
|
|
327
355
|
}
|
|
328
356
|
case "quorum": {
|
|
329
|
-
const { entities, qualityCriteria, exam, ...settings } = node.settings;
|
|
357
|
+
const { entities, qualityCriteria, exam, checks, ...settings } = node.settings;
|
|
358
|
+
const normalizedChecks = normalizeImportedChecks(checks, quorumCheckNames);
|
|
330
359
|
return {
|
|
331
360
|
...settings,
|
|
361
|
+
...(normalizedChecks === undefined ? {} : { checks: normalizedChecks }),
|
|
332
362
|
entities: {
|
|
333
363
|
input: entities.input.map((entity) => transformEntity(entity, fieldReference)),
|
|
334
364
|
output: entities.output.map((entity) => transformEntity(entity, fieldReference)),
|