@tuned-tensor/cli 0.4.15 → 0.4.17
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 +9 -3
- package/dist/index.js +102 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,6 +38,12 @@ tt runs start <spec-id>
|
|
|
38
38
|
tt runs watch <run-id>
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
To continue training from a completed fine-tuned model artifact:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tt runs start <spec-id> --parent-model <model-id>
|
|
45
|
+
```
|
|
46
|
+
|
|
41
47
|
Useful discovery commands:
|
|
42
48
|
|
|
43
49
|
```bash
|
|
@@ -49,9 +55,9 @@ tt models base
|
|
|
49
55
|
tt balance
|
|
50
56
|
```
|
|
51
57
|
|
|
52
|
-
For the full command reference, including dataset-backed runs,
|
|
53
|
-
|
|
54
|
-
[CLI docs](https://tunedtensor.com/docs/cli).
|
|
58
|
+
For the full command reference, including dataset-backed runs, long-example
|
|
59
|
+
policies, continued fine-tuning, evaluation caps, local model serving,
|
|
60
|
+
configuration, and billing, see the [CLI docs](https://tunedtensor.com/docs/cli).
|
|
55
61
|
|
|
56
62
|
## Development
|
|
57
63
|
|
package/dist/index.js
CHANGED
|
@@ -443,7 +443,8 @@ var SPEC_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
|
443
443
|
"system_prompt",
|
|
444
444
|
"guidelines",
|
|
445
445
|
"constraints",
|
|
446
|
-
"examples"
|
|
446
|
+
"examples",
|
|
447
|
+
"eval_cases"
|
|
447
448
|
]);
|
|
448
449
|
var RUN_INPUT_KEYS = ["run_id", "behavior_spec_id", "spec_snapshot", "run_number"];
|
|
449
450
|
var SpecBodyError = class extends Error {
|
|
@@ -583,6 +584,15 @@ function registerSpecsCommands(parent) {
|
|
|
583
584
|
|
|
584
585
|
// src/commands/runs.ts
|
|
585
586
|
import ora from "ora";
|
|
587
|
+
var LONG_EXAMPLE_POLICIES = ["error", "truncate", "skip"];
|
|
588
|
+
function parseLongExamplesPolicy(value) {
|
|
589
|
+
if (LONG_EXAMPLE_POLICIES.includes(value)) {
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
throw new Error(
|
|
593
|
+
`--long-examples must be one of: ${LONG_EXAMPLE_POLICIES.join(", ")}`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
586
596
|
function formatProgress(run) {
|
|
587
597
|
if (run.progress_pct == null && !run.stage_label) return void 0;
|
|
588
598
|
const label = run.stage_label ?? run.current_stage ?? "Progress";
|
|
@@ -791,11 +801,14 @@ Eval Results (${data._evals.length}):`);
|
|
|
791
801
|
);
|
|
792
802
|
}
|
|
793
803
|
});
|
|
794
|
-
runs.command("start").description("Start a new run for a behaviour spec").argument("<spec-id>", "Behaviour spec ID (full UUID or 8+ char prefix)").option("--no-augment", "Disable data augmentation").option("--no-llm-judge", "Disable LLM judging").option("--epochs <n>", "Number of training epochs").option("--lr <rate>", "Learning rate").option("--batch-size <n>", "Batch size").option("--dataset <id>", "Dataset ID to use instead of inline spec examples (full UUID or 4+ char prefix)").option("--train-ratio <ratio>", "Dataset training split ratio (default: 0.8 when any split ratio is set)").option("--validation-ratio <ratio>", "Dataset validation split ratio (default: 0.1 when any split ratio is set)").option("--test-ratio <ratio>", "Dataset test split ratio (default: 0.1 when any split ratio is set)").option("--lora-rank <n>", "LoRA rank").option("--lora-alpha <n>", "LoRA alpha").option("--max-eval-examples <n>", "Max examples for the primary eval pass").option("--max-test-eval-examples <n>", "Max examples for the secondary test eval pass").action(async (specId, cmdOpts) => {
|
|
804
|
+
runs.command("start").description("Start a new run for a behaviour spec").argument("<spec-id>", "Behaviour spec ID (full UUID or 8+ char prefix)").option("--no-augment", "Disable data augmentation").option("--no-llm-judge", "Disable LLM judging").option("--epochs <n>", "Number of training epochs").option("--lr <rate>", "Learning rate").option("--batch-size <n>", "Batch size").option("--dataset <id>", "Dataset ID to use instead of inline spec examples (full UUID or 4+ char prefix)").option("--parent-model <id>", "Fine-tuned model ID to continue training from (full UUID or 4+ char prefix)").option("--train-ratio <ratio>", "Dataset training split ratio (default: 0.8 when any split ratio is set)").option("--validation-ratio <ratio>", "Dataset validation split ratio (default: 0.1 when any split ratio is set)").option("--test-ratio <ratio>", "Dataset test split ratio (default: 0.1 when any split ratio is set)").option("--lora-rank <n>", "LoRA rank").option("--lora-alpha <n>", "LoRA alpha").option("--max-eval-examples <n>", "Max examples for the primary eval pass").option("--max-test-eval-examples <n>", "Max examples for the secondary test eval pass").option("--long-examples <policy>", "Long training row policy: error, truncate, or skip").option("--max-seq-length <tokens>", "Maximum training sequence length in tokens").action(async (specId, cmdOpts) => {
|
|
795
805
|
const opts = parent.opts();
|
|
796
806
|
const body = {};
|
|
797
807
|
if (cmdOpts.augment === false) body.augment = false;
|
|
798
808
|
if (cmdOpts.dataset) body.dataset_id = await resolveDatasetId(cmdOpts.dataset, opts);
|
|
809
|
+
if (cmdOpts.parentModel) {
|
|
810
|
+
body.parent_model_id = await resolveModelId(cmdOpts.parentModel, opts);
|
|
811
|
+
}
|
|
799
812
|
const splitRatioOptions = [
|
|
800
813
|
cmdOpts.trainRatio,
|
|
801
814
|
cmdOpts.validationRatio,
|
|
@@ -816,6 +829,8 @@ Eval Results (${data._evals.length}):`);
|
|
|
816
829
|
if (cmdOpts.loraAlpha) hp.lora_alpha = Number(cmdOpts.loraAlpha);
|
|
817
830
|
if (cmdOpts.maxEvalExamples) hp.max_eval_examples = Number(cmdOpts.maxEvalExamples);
|
|
818
831
|
if (cmdOpts.maxTestEvalExamples) hp.max_test_eval_examples = Number(cmdOpts.maxTestEvalExamples);
|
|
832
|
+
if (cmdOpts.longExamples) hp.long_examples = parseLongExamplesPolicy(cmdOpts.longExamples);
|
|
833
|
+
if (cmdOpts.maxSeqLength) hp.max_seq_length = Number(cmdOpts.maxSeqLength);
|
|
819
834
|
if (cmdOpts.llmJudge === false) body.use_llm_judge = false;
|
|
820
835
|
if (Object.keys(hp).length) body.hyperparameters = hp;
|
|
821
836
|
const fullSpecId = await resolveSpecId(specId, opts);
|
|
@@ -2781,11 +2796,90 @@ function validateSpec(spec) {
|
|
|
2781
2796
|
message: violations.length === 0 ? void 0 : violations.join("; ")
|
|
2782
2797
|
});
|
|
2783
2798
|
}
|
|
2799
|
+
const evalCaseErrors = validateEvalCases(spec);
|
|
2800
|
+
checks.push({
|
|
2801
|
+
name: "Eval cases valid",
|
|
2802
|
+
passed: evalCaseErrors.length === 0,
|
|
2803
|
+
message: evalCaseErrors.length === 0 ? spec.eval_cases?.length ? `${spec.eval_cases.length} executable eval case(s)` : void 0 : evalCaseErrors.join("; ")
|
|
2804
|
+
});
|
|
2784
2805
|
return {
|
|
2785
2806
|
valid: checks.every((c) => c.passed),
|
|
2786
2807
|
checks
|
|
2787
2808
|
};
|
|
2788
2809
|
}
|
|
2810
|
+
function validateEvalCases(spec) {
|
|
2811
|
+
const errors = [];
|
|
2812
|
+
const cases = spec.eval_cases ?? [];
|
|
2813
|
+
for (const [caseIndex, evalCase] of cases.entries()) {
|
|
2814
|
+
const label = `eval_cases[${caseIndex}]`;
|
|
2815
|
+
if (!evalCase || typeof evalCase !== "object") {
|
|
2816
|
+
errors.push(`${label} must be an object`);
|
|
2817
|
+
continue;
|
|
2818
|
+
}
|
|
2819
|
+
if (typeof evalCase.input !== "string" || evalCase.input.trim().length === 0) {
|
|
2820
|
+
errors.push(`${label}.input must be a non-empty string`);
|
|
2821
|
+
}
|
|
2822
|
+
if (evalCase.runtime !== "python") {
|
|
2823
|
+
errors.push(`${label}.runtime must be "python"`);
|
|
2824
|
+
}
|
|
2825
|
+
if (!Array.isArray(evalCase.tests) || evalCase.tests.length === 0) {
|
|
2826
|
+
errors.push(`${label}.tests must contain at least one test`);
|
|
2827
|
+
continue;
|
|
2828
|
+
}
|
|
2829
|
+
for (const [testIndex, test] of evalCase.tests.entries()) {
|
|
2830
|
+
const testLabel = `${label}.tests[${testIndex}]`;
|
|
2831
|
+
if (!test || typeof test !== "object") {
|
|
2832
|
+
errors.push(`${testLabel} must be an object`);
|
|
2833
|
+
continue;
|
|
2834
|
+
}
|
|
2835
|
+
if (test.name !== void 0 && (typeof test.name !== "string" || test.name.trim().length === 0)) {
|
|
2836
|
+
errors.push(`${testLabel}.name must be a non-empty string when provided`);
|
|
2837
|
+
}
|
|
2838
|
+
if (test.args !== void 0 && (!Array.isArray(test.args) || !test.args.every((arg) => typeof arg === "string"))) {
|
|
2839
|
+
errors.push(`${testLabel}.args must be an array of strings`);
|
|
2840
|
+
}
|
|
2841
|
+
if (test.stdin !== void 0 && typeof test.stdin !== "string") {
|
|
2842
|
+
errors.push(`${testLabel}.stdin must be a string`);
|
|
2843
|
+
}
|
|
2844
|
+
if (test.expected_exit_code !== void 0 && (typeof test.expected_exit_code !== "number" || !Number.isInteger(test.expected_exit_code) || test.expected_exit_code < 0 || test.expected_exit_code > 255)) {
|
|
2845
|
+
errors.push(`${testLabel}.expected_exit_code must be an integer between 0 and 255`);
|
|
2846
|
+
}
|
|
2847
|
+
if (test.expected_stdout !== void 0 && typeof test.expected_stdout !== "string") {
|
|
2848
|
+
errors.push(`${testLabel}.expected_stdout must be a string`);
|
|
2849
|
+
}
|
|
2850
|
+
validateFiles(test.files, `${testLabel}.files`, errors);
|
|
2851
|
+
validateFiles(test.expected_files, `${testLabel}.expected_files`, errors);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
return errors;
|
|
2855
|
+
}
|
|
2856
|
+
function validateFiles(files, label, errors) {
|
|
2857
|
+
if (files === void 0) return;
|
|
2858
|
+
if (!Array.isArray(files)) {
|
|
2859
|
+
errors.push(`${label} must be an array`);
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
for (const [index, file] of files.entries()) {
|
|
2863
|
+
const fileLabel = `${label}[${index}]`;
|
|
2864
|
+
if (!file || typeof file !== "object") {
|
|
2865
|
+
errors.push(`${fileLabel} must be an object`);
|
|
2866
|
+
continue;
|
|
2867
|
+
}
|
|
2868
|
+
const row = file;
|
|
2869
|
+
if (typeof row.path !== "string" || !isSafeRelativePath(row.path)) {
|
|
2870
|
+
errors.push(`${fileLabel}.path must be a safe relative path`);
|
|
2871
|
+
}
|
|
2872
|
+
if (row.content !== void 0 && typeof row.content !== "string") {
|
|
2873
|
+
errors.push(`${fileLabel}.content must be a string`);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
function isSafeRelativePath(value) {
|
|
2878
|
+
if (value.length === 0 || value.length > 240) return false;
|
|
2879
|
+
if (value.includes("\0") || value.startsWith("/")) return false;
|
|
2880
|
+
const normalized = value.replace(/\\/g, "/");
|
|
2881
|
+
return !normalized.split("/").some((part) => part === "" || part === "." || part === "..");
|
|
2882
|
+
}
|
|
2789
2883
|
function checkExamplesAgainstConstraints(spec) {
|
|
2790
2884
|
const violations = [];
|
|
2791
2885
|
for (const [i, ex] of spec.examples.entries()) {
|
|
@@ -2859,7 +2953,11 @@ function registerPushCommand(parent) {
|
|
|
2859
2953
|
const opts = parent.opts();
|
|
2860
2954
|
const filePath = resolve3(cmdOpts.file);
|
|
2861
2955
|
const spec = loadSpec(cmdOpts.file);
|
|
2862
|
-
const
|
|
2956
|
+
const evalCaseErrors = validateEvalCases(spec);
|
|
2957
|
+
if (evalCaseErrors.length > 0) {
|
|
2958
|
+
throw new Error(`Invalid eval_cases: ${evalCaseErrors.join("; ")}`);
|
|
2959
|
+
}
|
|
2960
|
+
const { id, ...rawBody } = spec;
|
|
2863
2961
|
const body = canonicalizeSpecBaseModel(rawBody);
|
|
2864
2962
|
let data;
|
|
2865
2963
|
if (id) {
|
|
@@ -2881,7 +2979,7 @@ function registerPushCommand(parent) {
|
|
|
2881
2979
|
|
|
2882
2980
|
// src/index.ts
|
|
2883
2981
|
var program = new Command();
|
|
2884
|
-
program.name("tt").description("Tuned Tensor CLI \u2014 fine-tune and evaluate LLMs").version("0.4.
|
|
2982
|
+
program.name("tt").description("Tuned Tensor CLI \u2014 fine-tune and evaluate LLMs").version("0.4.17").option("-k, --api-key <key>", "API key (overrides stored key)").option(
|
|
2885
2983
|
"-u, --base-url <url>",
|
|
2886
2984
|
"API base URL (default: https://tunedtensor.com)"
|
|
2887
2985
|
).option("--json", "Output raw JSON").option("--no-color", "Disable colors").hook("preAction", (_thisCommand, actionCommand) => {
|