@tuned-tensor/cli 0.4.16 → 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 CHANGED
@@ -55,9 +55,9 @@ tt models base
55
55
  tt balance
56
56
  ```
57
57
 
58
- For the full command reference, including dataset-backed runs, continued
59
- fine-tuning, evaluation caps, local model serving, configuration, and billing, see the
60
- [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).
61
61
 
62
62
  ## Development
63
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,7 +801,7 @@ 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("--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").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;
@@ -819,6 +829,8 @@ Eval Results (${data._evals.length}):`);
819
829
  if (cmdOpts.loraAlpha) hp.lora_alpha = Number(cmdOpts.loraAlpha);
820
830
  if (cmdOpts.maxEvalExamples) hp.max_eval_examples = Number(cmdOpts.maxEvalExamples);
821
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);
822
834
  if (cmdOpts.llmJudge === false) body.use_llm_judge = false;
823
835
  if (Object.keys(hp).length) body.hyperparameters = hp;
824
836
  const fullSpecId = await resolveSpecId(specId, opts);
@@ -2784,11 +2796,90 @@ function validateSpec(spec) {
2784
2796
  message: violations.length === 0 ? void 0 : violations.join("; ")
2785
2797
  });
2786
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
+ });
2787
2805
  return {
2788
2806
  valid: checks.every((c) => c.passed),
2789
2807
  checks
2790
2808
  };
2791
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
+ }
2792
2883
  function checkExamplesAgainstConstraints(spec) {
2793
2884
  const violations = [];
2794
2885
  for (const [i, ex] of spec.examples.entries()) {
@@ -2862,7 +2953,11 @@ function registerPushCommand(parent) {
2862
2953
  const opts = parent.opts();
2863
2954
  const filePath = resolve3(cmdOpts.file);
2864
2955
  const spec = loadSpec(cmdOpts.file);
2865
- const { id, eval_cases, ...rawBody } = spec;
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;
2866
2961
  const body = canonicalizeSpecBaseModel(rawBody);
2867
2962
  let data;
2868
2963
  if (id) {
@@ -2884,7 +2979,7 @@ function registerPushCommand(parent) {
2884
2979
 
2885
2980
  // src/index.ts
2886
2981
  var program = new Command();
2887
- program.name("tt").description("Tuned Tensor CLI \u2014 fine-tune and evaluate LLMs").version("0.4.16").option("-k, --api-key <key>", "API key (overrides stored key)").option(
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(
2888
2983
  "-u, --base-url <url>",
2889
2984
  "API base URL (default: https://tunedtensor.com)"
2890
2985
  ).option("--json", "Output raw JSON").option("--no-color", "Disable colors").hook("preAction", (_thisCommand, actionCommand) => {