@tuned-tensor/cli 0.4.21 → 0.4.23

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 CHANGED
@@ -1,8 +1,11 @@
1
1
  # tt - Tuned Tensor CLI
2
2
 
3
- `tt` is the command-line tool for [Tuned Tensor](https://www.tunedtensor.com).
4
- Use it to define behaviour specs, validate them, launch fine-tuning runs,
5
- manage datasets, and download or serve trained models.
3
+ `tt` is the command-line client for the optional managed
4
+ [Tuned Tensor](https://www.tunedtensor.com) service. Use it to define behaviour
5
+ specs, launch managed fine-tuning runs, inspect paired baseline-vs-tuned
6
+ reports, and download or serve trained models. For local CUDA or DGX Spark
7
+ training without a Tuned Tensor account, use `tt-local` from
8
+ `@tuned-tensor/local`.
6
9
 
7
10
  The main CLI documentation lives at
8
11
  [tunedtensor.com/docs/cli](https://tunedtensor.com/docs/cli). This README is a
@@ -37,6 +40,7 @@ tt push
37
40
  tt runs estimate <spec-id>
38
41
  tt runs start <spec-id>
39
42
  tt runs watch <run-id>
43
+ tt runs report <run-id>
40
44
  ```
41
45
 
42
46
  To continue training from a completed fine-tuned model artifact:
package/dist/index.js CHANGED
@@ -625,6 +625,14 @@ function formatEvalScore(run) {
625
625
  function formatPercent(rate) {
626
626
  return rate == null ? void 0 : (rate * 100).toFixed(1) + "%";
627
627
  }
628
+ function formatPointDelta(delta) {
629
+ if (delta == null) return void 0;
630
+ const points = delta * 100;
631
+ return `${points >= 0 ? "+" : ""}${points.toFixed(1)} pp`;
632
+ }
633
+ function formatScore(score) {
634
+ return score == null ? "\u2014" : score.toFixed(2);
635
+ }
628
636
  function formatCountRate(count, total, rate) {
629
637
  if (count == null || total == null || rate == null) return void 0;
630
638
  return `${formatPercent(rate)} (${count}/${total})`;
@@ -644,6 +652,7 @@ function formatEstimateRange(estimate) {
644
652
  return `${formatMinutes(duration.estimated_minutes)} (${formatMinutes(duration.range_minutes.low)}-${formatMinutes(duration.range_minutes.high)})`;
645
653
  }
646
654
  function printRunEstimate(estimate) {
655
+ const billing = estimate.billing;
647
656
  printDetail([
648
657
  ["Estimated Time", formatEstimateRange(estimate)],
649
658
  ["Confidence", estimate.duration.confidence],
@@ -651,7 +660,20 @@ function printRunEstimate(estimate) {
651
660
  ["Basis", estimate.duration.basis.replaceAll("_", " ")],
652
661
  ["Estimated Cost", formatCents(estimate.estimated_cost_cents)],
653
662
  ["Training Tokens", `${(estimate.estimated_training_tokens / 1e3).toFixed(1)}k`],
654
- ["Epochs", String(estimate.estimated_epochs)]
663
+ ["Epochs", String(estimate.estimated_epochs)],
664
+ ["Plan", billing?.plan],
665
+ [
666
+ "Billing Source",
667
+ billing?.billing_source === "free_quota" ? "Free monthly quota" : billing?.billing_source === "credits" ? "Credits" : void 0
668
+ ],
669
+ [
670
+ "Free Runs",
671
+ billing ? `${billing.free_runs_remaining}/${billing.free_runs_monthly_limit} remaining` : void 0
672
+ ],
673
+ [
674
+ "Free Eligible",
675
+ billing ? billing.free_run_eligible ? "yes" : `no (${billing.free_run_ineligibility.join(", ") || "not eligible"})` : void 0
676
+ ]
655
677
  ]);
656
678
  console.log(
657
679
  "\nDuration is a rough historical range. Final cost uses provider-reported training tokens."
@@ -762,6 +784,131 @@ function printEvalOutputDiagnostics(diagnostics) {
762
784
  }
763
785
  }
764
786
  }
787
+ function compactWhitespace(value) {
788
+ return value.replace(/\s+/g, " ").trim();
789
+ }
790
+ function parseJsonish(value) {
791
+ if (!value) return value;
792
+ try {
793
+ return JSON.parse(value);
794
+ } catch {
795
+ return value;
796
+ }
797
+ }
798
+ function formatJsonish(value, max = 360) {
799
+ const parsed = parseJsonish(value);
800
+ const rendered = typeof parsed === "string" ? compactWhitespace(parsed) : JSON.stringify(parsed);
801
+ return truncate(rendered ?? "\u2014", max);
802
+ }
803
+ function formatPromptSummary(prompt, max = 260) {
804
+ const subject = prompt.match(/^Subject:\s*(.+)$/m)?.[1]?.trim();
805
+ const body = prompt.match(/^Body:\s*([\s\S]+)$/m)?.[1];
806
+ if (subject) {
807
+ const bodySummary = body ? ` | Body: ${compactWhitespace(body)}` : "";
808
+ return truncate(`Subject: ${subject}${bodySummary}`, max);
809
+ }
810
+ return truncate(compactWhitespace(prompt), max);
811
+ }
812
+ function indexResults(results) {
813
+ return new Map((results ?? []).map((result) => [result.prompt, result]));
814
+ }
815
+ function printReportMetrics(label, split) {
816
+ if (!split) return;
817
+ const comparison = split.comparison;
818
+ const baseline = split.baseline;
819
+ const candidate = split.candidate;
820
+ console.log(`
821
+ ${label} Metrics:`);
822
+ printDetail([
823
+ ["Base Avg", formatPercent(baseline?.avg_score)],
824
+ ["Tuned Avg", formatPercent(candidate?.avg_score)],
825
+ ["Avg Delta", formatPointDelta(comparison?.avg_score_delta)],
826
+ ["Base Pass", formatPercent(baseline?.pass_rate)],
827
+ ["Tuned Pass", formatPercent(candidate?.pass_rate)],
828
+ ["Pass Delta", formatPointDelta(comparison?.pass_rate_delta)],
829
+ ["Regressions", comparison?.regressions == null ? void 0 : String(comparison.regressions)],
830
+ ["Improvements", comparison?.improvements == null ? void 0 : String(comparison.improvements)],
831
+ ["Eval Rows", candidate?.eval_examples_used == null ? candidate?.total == null ? void 0 : String(candidate.total) : String(candidate.eval_examples_used)]
832
+ ]);
833
+ }
834
+ function printComparedExample(input) {
835
+ const { index, prompt, baseline, candidate, oldScore, newScore } = input;
836
+ const expected = candidate?.expected ?? baseline?.expected;
837
+ const scoreLine = oldScore != null || newScore != null ? oldScore == null ? `tuned score ${formatScore(newScore)}` : `score ${formatScore(oldScore)} -> ${formatScore(newScore)}` : `tuned score ${formatScore(candidate?.score)}`;
838
+ console.log(`
839
+ ${index}. ${scoreLine}`);
840
+ console.log(` Prompt: ${formatPromptSummary(prompt)}`);
841
+ console.log(` Expected: ${formatJsonish(expected)}`);
842
+ if (baseline?.actual != null) {
843
+ console.log(` Base: ${formatJsonish(baseline.actual)}`);
844
+ }
845
+ if (candidate?.actual != null) {
846
+ console.log(` Tuned: ${formatJsonish(candidate.actual)}`);
847
+ }
848
+ const reasoning = candidate?.reasoning ?? baseline?.reasoning;
849
+ if (reasoning) {
850
+ console.log(` Judge: ${truncate(compactWhitespace(reasoning), 420)}`);
851
+ }
852
+ }
853
+ function printReportExamples(label, split, mode, limit) {
854
+ if (!split || limit <= 0) return;
855
+ const baselineByPrompt = indexResults(split.baseline?.results);
856
+ const candidateByPrompt = indexResults(split.candidate?.results);
857
+ if (mode === "regressions") {
858
+ const regressions = split.comparison?.regressed_examples ?? [];
859
+ console.log(`
860
+ ${label} Regressions:`);
861
+ if (!regressions.length) {
862
+ console.log(" No regressed examples in the report.");
863
+ return;
864
+ }
865
+ regressions.slice(0, limit).forEach((example, i) => {
866
+ printComparedExample({
867
+ index: i + 1,
868
+ prompt: example.prompt,
869
+ baseline: baselineByPrompt.get(example.prompt),
870
+ candidate: candidateByPrompt.get(example.prompt),
871
+ oldScore: example.old_score,
872
+ newScore: example.new_score
873
+ });
874
+ });
875
+ return;
876
+ }
877
+ const failures = (split.candidate?.results ?? []).filter((result) => result.passed === false || (result.score ?? 1) < 1).sort((a, b) => (a.score ?? 0) - (b.score ?? 0));
878
+ console.log(`
879
+ ${label} Tuned Failures:`);
880
+ if (!failures.length) {
881
+ console.log(" No tuned failures in the report.");
882
+ return;
883
+ }
884
+ failures.slice(0, limit).forEach((candidate, i) => {
885
+ printComparedExample({
886
+ index: i + 1,
887
+ prompt: candidate.prompt,
888
+ baseline: baselineByPrompt.get(candidate.prompt),
889
+ candidate,
890
+ newScore: candidate.score
891
+ });
892
+ });
893
+ }
894
+ function printRunReport(report, options) {
895
+ printDetail([
896
+ ["Run", report.run_id ? shortId(report.run_id) : void 0],
897
+ ["Status", report.status ? formatStatus(report.status) : void 0],
898
+ ["Model", report.fine_tuned_model_id ? shortId(report.fine_tuned_model_id) : void 0]
899
+ ]);
900
+ const splits = [];
901
+ if (options.split === "primary" || options.split === "all") {
902
+ splits.push(["Primary", report]);
903
+ }
904
+ if (options.split === "test" || options.split === "all") {
905
+ splits.push(["Test", report.test]);
906
+ }
907
+ for (const [label, split] of splits) {
908
+ printReportMetrics(label, split);
909
+ printReportExamples(label, split, options.mode, options.limit);
910
+ }
911
+ }
765
912
  function addRunConfigurationOptions(command) {
766
913
  return command.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").option("--max-output-tokens <tokens>", "Desired evaluation output budget in tokens").option("--eval-reserved-output-tokens <tokens>", "Minimum evaluation output tokens reserved per row");
767
914
  }
@@ -959,6 +1106,33 @@ Eval Results (${data._evals.length}):`);
959
1106
  if (isJsonMode()) return printJson(data);
960
1107
  printDiagnostics(data);
961
1108
  });
1109
+ runs.command("report").description("Show run metrics and side-by-side eval output insights").argument("<id>", "Run ID (full UUID or 8+ char prefix)").option("--split <split>", "Split to show: primary, test, or all", "primary").option("--limit <n>", "Number of examples to show", "5").option("--mode <mode>", "Example mode: regressions or failures", "regressions").action(async (id, cmdOpts) => {
1110
+ const opts = parent.opts();
1111
+ const fullId = await resolveRunId(id, opts);
1112
+ const { data } = await get(
1113
+ `/runs/${fullId}/report`,
1114
+ void 0,
1115
+ opts
1116
+ );
1117
+ if (isJsonMode()) return printJson(data);
1118
+ const split = String(cmdOpts.split);
1119
+ if (!["primary", "test", "all"].includes(split)) {
1120
+ throw new Error("--split must be one of: primary, test, all");
1121
+ }
1122
+ const mode = String(cmdOpts.mode);
1123
+ if (!["regressions", "failures"].includes(mode)) {
1124
+ throw new Error("--mode must be one of: regressions, failures");
1125
+ }
1126
+ const limit = Number(cmdOpts.limit);
1127
+ if (!Number.isFinite(limit) || limit < 0) {
1128
+ throw new Error("--limit must be a non-negative number");
1129
+ }
1130
+ printRunReport(data, {
1131
+ split,
1132
+ mode,
1133
+ limit: Math.floor(limit)
1134
+ });
1135
+ });
962
1136
  }
963
1137
 
964
1138
  // src/commands/datasets.ts
@@ -3963,7 +4137,7 @@ function registerPushCommand(parent) {
3963
4137
 
3964
4138
  // src/index.ts
3965
4139
  var program = new Command();
3966
- program.name("tt").description("Tuned Tensor CLI \u2014 fine-tune and evaluate LLMs").version("0.4.21").option("-k, --api-key <key>", "API key (overrides stored key)").option(
4140
+ program.name("tt").description("Tuned Tensor CLI \u2014 fine-tune and evaluate LLMs").version("0.4.23").option("-k, --api-key <key>", "API key (overrides stored key)").option(
3967
4141
  "-u, --base-url <url>",
3968
4142
  "API base URL (default: https://tunedtensor.com)"
3969
4143
  ).option("--json", "Output raw JSON").option("--no-color", "Disable colors").hook("preAction", (_thisCommand, actionCommand) => {