@tuned-tensor/cli 0.20.0 → 0.22.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.
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_LOCAL_SPEC_PATH,
4
+ DatasetNotDownloadedError,
4
5
  assertArtifactManifest,
5
6
  assertFoundationSpecReady,
6
7
  assertLocalRunInputReady,
7
8
  assertUsableModelArtifact,
8
9
  assessHardware,
10
+ auditRun,
9
11
  baseModelRevisionSchema,
10
12
  behaviorSpecExampleSchema,
11
13
  behaviorSpecSchema,
@@ -18,10 +20,19 @@ import {
18
20
  comparisonReportSchema,
19
21
  compileSpecToJsonl,
20
22
  createLocalStore,
23
+ createRunConsoleReporter,
24
+ datasetColumnsSchema,
21
25
  datasetFormatSchema,
26
+ datasetInputIdentity,
22
27
  datasetPrebuiltSchema,
28
+ datasetSourceSchema,
29
+ decisionHyperparametersSchema,
30
+ decisionLabels,
31
+ decisionPlaceholderIssues,
32
+ decisionQuestionSchema,
23
33
  defaultBaseModelRevision,
24
34
  defaultLocalHome,
35
+ ensureDatasetCached,
25
36
  evalExampleResultSchema,
26
37
  evalReportSchema,
27
38
  evalSplitSchema,
@@ -38,9 +49,14 @@ import {
38
49
  foundationPlaceholderIssues,
39
50
  generatedPlaceholderIssues,
40
51
  hardenExistingLocalStore,
52
+ huggingFaceDataset,
53
+ huggingFaceDatasetSourceSchema,
54
+ huggingFaceRepoIdSchema,
41
55
  initLocalRunnerConfigFile,
42
56
  initLocalSpecFile,
57
+ isDecisionSpecFile,
43
58
  isFoundationSpecFile,
59
+ isRepoRelativePath,
44
60
  isTerminalRunState,
45
61
  jsonFieldMetricsSchema,
46
62
  loadJsonFile,
@@ -49,6 +65,7 @@ import {
49
65
  loadRunRequest,
50
66
  localAdapterSpecFileSchema,
51
67
  localBehaviorSpecFileSchema,
68
+ localDecisionSpecFileSchema,
52
69
  localFoundationSpecFileSchema,
53
70
  localPathsOverlap,
54
71
  localRunnerConfigSchema,
@@ -60,6 +77,7 @@ import {
60
77
  parseLocalRunnerConfig,
61
78
  pipelineForRunInput,
62
79
  prefetchBaseModel,
80
+ prefetchHuggingFaceDataset,
63
81
  projectRuntimeSchema,
64
82
  pythonEnvironmentPath,
65
83
  reportInBackground,
@@ -71,8 +89,10 @@ import {
71
89
  runGpuProcess,
72
90
  runLocalFineTune,
73
91
  runLocalPipeline,
92
+ runProvenanceSchema,
74
93
  runReportSchema,
75
94
  runRequestFromLocalSpec,
95
+ safeName,
76
96
  sanitizeLogLine,
77
97
  specHash,
78
98
  specSnapshotSchema,
@@ -88,13 +108,13 @@ import {
88
108
  withHuggingFaceCacheEnvironment,
89
109
  withOfflineHuggingFaceCacheEnvironment,
90
110
  writePrivateJsonAtomic
91
- } from "./chunk-4Q54NHKR.js";
111
+ } from "./chunk-4SUXBHIY.js";
92
112
 
93
113
  // src/local-runtime/index.ts
94
114
  import { readFileSync as readFileSync3, realpathSync } from "fs";
95
115
  import { createHash as createHash2 } from "crypto";
96
116
  import { readFile as readFile2, stat } from "fs/promises";
97
- import { basename, dirname, join as join5, resolve as resolve3 } from "path";
117
+ import { basename, dirname, join as join5, relative, resolve as resolve3 } from "path";
98
118
  import { cwd } from "process";
99
119
  import { fileURLToPath as fileURLToPath2 } from "url";
100
120
 
@@ -926,8 +946,9 @@ Commands:
926
946
  validate [tunedtensor.json] [--config local-runner.json]
927
947
  run [tunedtensor.json] [--config local-runner.json] [--dry-run] [--verbose] [--quiet]
928
948
  serve <model-id|active|base|foundation> [--config local-runner.json] [--host 127.0.0.1] [--port 8000]
929
- runs list|get|events|report|compare [args] [--config local-runner.json]
949
+ runs list|get|events|report|compare|audit [args] [--config local-runner.json]
930
950
  models list|get|verify|prefetch|verify-base|active|activate|rollback|serve [args] [--config local-runner.json]
951
+ datasets prefetch|verify [tunedtensor.json]
931
952
 
932
953
  Global options:
933
954
  -h, --help Show help
@@ -972,7 +993,7 @@ var COMMAND_DEFINITIONS = {
972
993
  description: "Create a local tunedtensor.json behavior spec.",
973
994
  options: [
974
995
  { name: "--name", value: "name", description: "Behavior spec name" },
975
- { name: "--engine", value: "engine", description: "adapter (default) or foundation" },
996
+ { name: "--engine", value: "engine", description: "adapter (default), foundation or decision" },
976
997
  { name: "--model", value: "model", description: "Base model ID" },
977
998
  { name: "--output", value: "path", description: "Output spec path" },
978
999
  { name: "--profile", value: "profile", description: "Compatibility host profile (spark); creates only tunedtensor.json" },
@@ -1031,6 +1052,7 @@ var COMMAND_GROUPS = {
1031
1052
  get: { usage: "tt runs get <run-id> [--config path]", description: "Get a local run.", options: [CONFIG_OPTION], minPositionals: 1, maxPositionals: 1, missingPositionalsMessage: "runs get requires <run-id>" },
1032
1053
  events: { usage: "tt runs events <run-id> [--config path]", description: "List run events.", options: [CONFIG_OPTION], minPositionals: 1, maxPositionals: 1, missingPositionalsMessage: "runs events requires <run-id>" },
1033
1054
  report: { usage: "tt runs report <run-id> [--config path]", description: "Show the baseline-vs-tuned report, including deltas and regressions.", options: [CONFIG_OPTION], minPositionals: 1, maxPositionals: 1, missingPositionalsMessage: "runs report requires <run-id>" },
1055
+ audit: { usage: "tt runs audit <run-id> [--config path]", description: "Verify the run's event chain and report digest, and show its timeline and provenance.", options: [CONFIG_OPTION], minPositionals: 1, maxPositionals: 1, missingPositionalsMessage: "runs audit requires <run-id>" },
1034
1056
  compare: { usage: "tt runs compare <run-id-a> <run-id-b> [--config path]", description: "Compare two run reports.", options: [CONFIG_OPTION], minPositionals: 2, maxPositionals: 2, missingPositionalsMessage: "runs compare requires <run-id-a> <run-id-b>" }
1035
1057
  }
1036
1058
  },
@@ -1089,6 +1111,24 @@ var COMMAND_GROUPS = {
1089
1111
  missingPositionalsMessage: "models serve requires <model-id>"
1090
1112
  }
1091
1113
  }
1114
+ },
1115
+ datasets: {
1116
+ description: "Download or verify the Hugging Face dataset a behavior spec trains on.",
1117
+ defaultSubcommand: "prefetch",
1118
+ subcommands: {
1119
+ prefetch: {
1120
+ usage: "tt datasets prefetch [tunedtensor.json] [options]",
1121
+ description: "Download dataset_prebuilt.huggingface files into the local Hugging Face cache. Resumes after interruption.",
1122
+ options: [CONFIG_OPTION, VERBOSE_OPTION, QUIET_OPTION],
1123
+ maxPositionals: 1
1124
+ },
1125
+ verify: {
1126
+ usage: "tt datasets verify [tunedtensor.json] [options]",
1127
+ description: "Verify the cached dataset files and checksums without network access.",
1128
+ options: [CONFIG_OPTION, QUIET_OPTION],
1129
+ maxPositionals: 1
1130
+ }
1131
+ }
1092
1132
  }
1093
1133
  };
1094
1134
  function hasHelpFlag(argv) {
@@ -1226,6 +1266,7 @@ async function configSelectionFromArgv(argv, adjacentTo, input) {
1226
1266
  async function configFromArgv(argv, adjacentTo) {
1227
1267
  return (await configSelectionFromArgv(argv, adjacentTo)).config;
1228
1268
  }
1269
+ var DECISION_SERVE_MESSAGE = "Decision models are not served through vLLM. Load the tuned model directory with `laya.load(<model_dir>)`; see docs/local-runtime/decision.md.";
1229
1270
  async function loadCliBehaviorSpec(inputPath, runId) {
1230
1271
  const input = await loadLocalRunInput(inputPath, {
1231
1272
  ...runId ? { runId } : {}
@@ -1235,6 +1276,11 @@ async function loadCliBehaviorSpec(inputPath, runId) {
1235
1276
  `This spec uses engine "foundation". Use \`tt pipeline run --spec ${input.path}\`; the legacy adapter runner cannot execute foundation specs.`
1236
1277
  );
1237
1278
  }
1279
+ if (input.kind === "decision-spec") {
1280
+ throw new Error(
1281
+ `This spec uses engine "decision". Use \`tt pipeline run --spec ${input.path}\`; the legacy adapter runner cannot execute decision specs.`
1282
+ );
1283
+ }
1238
1284
  if (input.kind !== "spec") {
1239
1285
  throw new Error(`TT expects a tunedtensor.json behavior spec, not a full run request: ${input.path}`);
1240
1286
  }
@@ -1243,38 +1289,9 @@ async function loadCliBehaviorSpec(inputPath, runId) {
1243
1289
  function printJson(value) {
1244
1290
  console.log(JSON.stringify(value, null, 2));
1245
1291
  }
1246
- function shortValue(value) {
1247
- if (value === void 0 || value === null) return null;
1248
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1249
- return String(value);
1250
- }
1251
- if (Array.isArray(value)) return `[${value.length} items]`;
1252
- return null;
1253
- }
1254
- function formatEvent(event) {
1255
- const detailText = Object.entries(event.details ?? {}).filter(([key]) => key !== "metrics").map(([key, value]) => {
1256
- const formatted = key === "command" && Array.isArray(value) ? value.join(" ") : shortValue(value);
1257
- return formatted ? `${key}=${formatted}` : null;
1258
- }).filter((value) => Boolean(value)).slice(0, 5).join(" ");
1259
- return sanitizeLogLine(`[tt] ${event.stage}: ${event.message}${detailText ? ` (${detailText})` : ""}`);
1260
- }
1261
1292
  function createConsoleReporter(options) {
1262
1293
  if (options.quiet) return void 0;
1263
- let lastLogLine = "";
1264
- return {
1265
- verbose: options.verbose,
1266
- onEvent(event) {
1267
- process.stderr.write(`${formatEvent(event)}
1268
- `);
1269
- },
1270
- onLog(log) {
1271
- const line = sanitizeLogLine(`[tt] ${log.stage}${log.stream ? ` ${log.stream}` : ""}: ${log.message}`);
1272
- if (line === lastLogLine) return;
1273
- lastLogLine = line;
1274
- process.stderr.write(`${line}
1275
- `);
1276
- }
1277
- };
1294
+ return createRunConsoleReporter({ verbose: options.verbose });
1278
1295
  }
1279
1296
  async function verifyStoredModel(model, config) {
1280
1297
  const manifestPath = join5(model.artifact_dir, "artifact-manifest.json");
@@ -1405,6 +1422,7 @@ async function modelSystemPrompt(args) {
1405
1422
  if (isFoundationSpecFile(local.data)) {
1406
1423
  throw new Error("Foundation specs have no Hugging Face base model. Use `tt serve foundation --checkpoint <model-dir> --tokenizer <tokenizer.json>`.");
1407
1424
  }
1425
+ if (isDecisionSpecFile(local.data)) throw new Error(DECISION_SERVE_MESSAGE);
1408
1426
  spec = local.data;
1409
1427
  } else {
1410
1428
  const runRequestPath = join5(args.store.paths.runsDir, args.model.run_id, "request.json");
@@ -1556,6 +1574,7 @@ async function serveBaseModelFromCli(args) {
1556
1574
  if (spec && isFoundationSpecFile(spec)) {
1557
1575
  throw new Error("Foundation specs have no Hugging Face base model. Use `tt serve foundation --checkpoint <model-dir> --tokenizer <tokenizer.json>`.");
1558
1576
  }
1577
+ if (spec && isDecisionSpecFile(spec)) throw new Error(DECISION_SERVE_MESSAGE);
1559
1578
  let baseModel = spec?.base_model;
1560
1579
  if (!baseModel) {
1561
1580
  const store = createLocalStore(args.config.storeRoot);
@@ -1689,6 +1708,7 @@ async function main(argv) {
1689
1708
  const input = await loadLocalRunInput(inputPath);
1690
1709
  if (input.kind === "foundation-spec") {
1691
1710
  foundationSpec = input.spec;
1711
+ } else if (input.kind === "decision-spec") {
1692
1712
  } else if (input.kind === "spec") {
1693
1713
  request = input.request;
1694
1714
  } else {
@@ -1731,8 +1751,8 @@ async function main(argv) {
1731
1751
  throw new Error(`--profile must be spark, got: ${profile}`);
1732
1752
  }
1733
1753
  const engine = readOption(argv, "--engine") ?? "adapter";
1734
- if (engine !== "adapter" && engine !== "foundation") {
1735
- throw new Error(`--engine must be adapter or foundation, got: ${engine}`);
1754
+ if (engine !== "adapter" && engine !== "foundation" && engine !== "decision") {
1755
+ throw new Error(`--engine must be adapter, foundation or decision, got: ${engine}`);
1736
1756
  }
1737
1757
  if (engine === "foundation" && readOption(argv, "--model")) {
1738
1758
  throw new Error("Foundation specs do not take --model; they train a tokenizer and GPT from scratch.");
@@ -1740,9 +1760,9 @@ async function main(argv) {
1740
1760
  if (readOption(argv, "--config")) throw new Error("tt init uses one tunedtensor.json; put overrides in runtime/evaluation instead of --config.");
1741
1761
  const spec = await initLocalSpecFile({
1742
1762
  outputPath,
1743
- name: readOption(argv, "--name") ?? (engine === "foundation" ? "Foundation chat model" : "Local Tuned Tensor Spec"),
1763
+ name: readOption(argv, "--name") ?? (engine === "foundation" ? "Foundation chat model" : engine === "decision" ? "Local decision model" : "Local Tuned Tensor Spec"),
1744
1764
  engine,
1745
- baseModel: engine === "foundation" ? void 0 : readOption(argv, "--model") ?? "Qwen/Qwen3.5-2B",
1765
+ baseModel: engine === "foundation" ? void 0 : readOption(argv, "--model") ?? (engine === "decision" ? void 0 : "Qwen/Qwen3.5-2B"),
1746
1766
  force: hasFlag(argv, "--force")
1747
1767
  });
1748
1768
  const configPath = void 0;
@@ -1770,16 +1790,52 @@ async function main(argv) {
1770
1790
  });
1771
1791
  return;
1772
1792
  }
1793
+ if (loaded.kind === "decision-spec") {
1794
+ const validation = validateBehaviorSpec(loaded);
1795
+ if (!validation.valid) throw new Error(`Invalid decision spec:
1796
+ ${validation.errors.join("\n")}`);
1797
+ printJson({
1798
+ ok: true,
1799
+ input_path: loaded.path,
1800
+ engine: "decision",
1801
+ base_model: loaded.spec.base_model,
1802
+ warnings: validation.warnings,
1803
+ pipeline: "tt pipeline run --spec"
1804
+ });
1805
+ return;
1806
+ }
1773
1807
  if (loaded.kind !== "spec") {
1774
1808
  throw new Error(`TT expects a tunedtensor.json behavior spec, not a full run request: ${loaded.path}`);
1775
1809
  }
1776
1810
  const input = loaded;
1777
1811
  assertLocalRunInputReady(input.request);
1778
1812
  const configSelection = await configSelectionFromArgv(argv, inputPath);
1779
- const validated = await validateLocalFineTuneInput({
1780
- request: input.request,
1781
- config: configSelection.config
1782
- });
1813
+ const remoteDataset = huggingFaceDataset(input.request);
1814
+ let validated;
1815
+ try {
1816
+ validated = await validateLocalFineTuneInput({
1817
+ request: input.request,
1818
+ config: configSelection.config
1819
+ });
1820
+ } catch (error) {
1821
+ if (!(error instanceof DatasetNotDownloadedError) || !remoteDataset) throw error;
1822
+ const config2 = localRunnerConfigSchema.parse(configSelection.config);
1823
+ printJson({
1824
+ ok: true,
1825
+ input_path: input.path,
1826
+ config_path: configSelection.path ?? null,
1827
+ behavior_spec_id: input.request.behavior_spec_id,
1828
+ base_model: input.request.spec_snapshot.base_model,
1829
+ dataset_format: remoteDataset.format,
1830
+ dataset_status: "not_downloaded",
1831
+ dataset_huggingface: remoteDataset.huggingface,
1832
+ next_step: `tt datasets prefetch ${relative(cwd(), input.path) || input.path}`,
1833
+ artifact_root: config2.artifactRoot,
1834
+ store_root: config2.storeRoot,
1835
+ dry_run: config2.dryRun
1836
+ });
1837
+ return;
1838
+ }
1783
1839
  const request = validated.request;
1784
1840
  const config = validated.config;
1785
1841
  printJson({
@@ -1789,6 +1845,7 @@ async function main(argv) {
1789
1845
  behavior_spec_id: request.behavior_spec_id,
1790
1846
  base_model: request.spec_snapshot.base_model,
1791
1847
  dataset_format: request.dataset_prebuilt?.format ?? null,
1848
+ ...request.dataset_source ? { dataset_status: "cached", dataset_source: request.dataset_source } : {},
1792
1849
  artifact_root: config.artifactRoot,
1793
1850
  store_root: config.storeRoot,
1794
1851
  dry_run: config.dryRun
@@ -1805,15 +1862,16 @@ async function main(argv) {
1805
1862
  dryRun: hasFlag(argv, "--dry-run") ? true : configInput.dryRun
1806
1863
  });
1807
1864
  assertLocalRunInputReady(input.request);
1808
- const validated = await validateLocalFineTuneInput({
1809
- request: input.request,
1810
- config
1811
- });
1812
- let request = validated.request;
1813
1865
  const reporter = createConsoleReporter({
1814
1866
  verbose: hasFlag(argv, "--verbose"),
1815
1867
  quiet: hasFlag(argv, "--quiet")
1816
1868
  });
1869
+ const ensured = await ensureDatasetCached({ request: input.request, config, reporter });
1870
+ const validated = await validateLocalFineTuneInput({
1871
+ request: ensured.request,
1872
+ config
1873
+ });
1874
+ let request = validated.request;
1817
1875
  if (!config.dryRun && !config.paths.baseModel && !request.hyperparameters.base_model_revision) {
1818
1876
  const prefetch = await prefetchBaseModel({
1819
1877
  request,
@@ -1831,12 +1889,13 @@ async function main(argv) {
1831
1889
  }
1832
1890
  });
1833
1891
  }
1892
+ const specFile = /^\..+\.tt-local-/.test(basename(input.path)) ? void 0 : { path: input.path, sha256: specHash(await readFile2(input.path, "utf8")) };
1834
1893
  if (input.spec.pipeline) {
1835
- const result2 = await runLocalPipeline({ request, config, reporter, pipeline: pipelineForRunInput(input), projectSpec: input.spec });
1894
+ const result2 = await runLocalPipeline({ request, config, reporter, pipeline: pipelineForRunInput(input), projectSpec: input.spec, specFile });
1836
1895
  printJson(result2);
1837
1896
  return;
1838
1897
  }
1839
- const result = await runLocalFineTune({ request, config, reporter });
1898
+ const result = await runLocalFineTune({ request, config, reporter, specFile });
1840
1899
  printJson({
1841
1900
  status: result.report.status,
1842
1901
  run_id: result.report.run_id,
@@ -1884,6 +1943,14 @@ async function main(argv) {
1884
1943
  if (!id) throw new Error("runs report requires <run-id>");
1885
1944
  return printJson(await store.getRunReport(id));
1886
1945
  }
1946
+ if (subcommand === "audit") {
1947
+ const id = cli.positionals[0];
1948
+ if (!id) throw new Error("runs audit requires <run-id>");
1949
+ const audit = await auditRun(store, id);
1950
+ printJson(audit);
1951
+ if (audit.verdict === "tampered") process.exitCode = 2;
1952
+ return;
1953
+ }
1887
1954
  if (subcommand === "compare") {
1888
1955
  const idA = cli.positionals[0];
1889
1956
  const idB = cli.positionals[1];
@@ -1896,6 +1963,23 @@ async function main(argv) {
1896
1963
  }
1897
1964
  throw new Error(`Unknown runs command: ${subcommand}`);
1898
1965
  }
1966
+ if (command === "datasets") {
1967
+ const subcommand = cli.subcommand;
1968
+ const inputPath = resolve3(cli.positionals[0] ?? DEFAULT_LOCAL_SPEC_PATH);
1969
+ const config = await configFromArgv(argv, inputPath);
1970
+ const input = await loadCliBehaviorSpec(inputPath);
1971
+ if (subcommand !== "prefetch" && subcommand !== "verify") throw new Error(`Unknown datasets command: ${subcommand}`);
1972
+ const report = await prefetchHuggingFaceDataset({
1973
+ request: input.request,
1974
+ config,
1975
+ localOnly: subcommand === "verify",
1976
+ reporter: createConsoleReporter({
1977
+ verbose: hasFlag(argv, "--verbose"),
1978
+ quiet: hasFlag(argv, "--quiet")
1979
+ })
1980
+ });
1981
+ return printJson({ ...report, input_path: input.path });
1982
+ }
1899
1983
  if (command === "models") {
1900
1984
  const subcommand = cli.subcommand;
1901
1985
  const modelInputPath = subcommand === "prefetch" || subcommand === "verify-base" ? resolve3(cli.positionals[0] ?? DEFAULT_LOCAL_SPEC_PATH) : void 0;
@@ -2011,8 +2095,15 @@ export {
2011
2095
  comparisonReportSchema,
2012
2096
  compileSpecToJsonl,
2013
2097
  createLocalStore,
2098
+ datasetColumnsSchema,
2014
2099
  datasetFormatSchema,
2100
+ datasetInputIdentity,
2015
2101
  datasetPrebuiltSchema,
2102
+ datasetSourceSchema,
2103
+ decisionHyperparametersSchema,
2104
+ decisionLabels,
2105
+ decisionPlaceholderIssues,
2106
+ decisionQuestionSchema,
2016
2107
  defaultLocalHome,
2017
2108
  evalExampleResultSchema,
2018
2109
  evalReportSchema,
@@ -2032,9 +2123,13 @@ export {
2032
2123
  getActiveModel,
2033
2124
  getLocalRunnerInfo,
2034
2125
  hardenExistingLocalStore,
2126
+ huggingFaceDatasetSourceSchema,
2127
+ huggingFaceRepoIdSchema,
2035
2128
  initLocalRunnerConfigFile,
2036
2129
  initLocalSpecFile,
2130
+ isDecisionSpecFile,
2037
2131
  isFoundationSpecFile,
2132
+ isRepoRelativePath,
2038
2133
  isTerminalRunState,
2039
2134
  jsonFieldMetricsSchema,
2040
2135
  loadJsonFile,
@@ -2043,6 +2138,7 @@ export {
2043
2138
  loadRunRequest,
2044
2139
  localAdapterSpecFileSchema,
2045
2140
  localBehaviorSpecFileSchema,
2141
+ localDecisionSpecFileSchema,
2046
2142
  localFoundationSpecFileSchema,
2047
2143
  localPathsOverlap,
2048
2144
  localRunnerConfigSchema,
@@ -2057,8 +2153,10 @@ export {
2057
2153
  rollbackActiveModel,
2058
2154
  runLocalFineTune,
2059
2155
  runLocalPipeline,
2156
+ runProvenanceSchema,
2060
2157
  runReportSchema,
2061
2158
  runRequestFromLocalSpec,
2159
+ safeName,
2062
2160
  sanitizeLogLine,
2063
2161
  serveLocalModel,
2064
2162
  specHash,