@huggingface/tasks 0.21.35 → 0.21.37

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.
@@ -7,11 +7,12 @@ function nameWithoutNamespace(modelId) {
7
7
  return splitted.length === 1 ? splitted[0] : splitted[1];
8
8
  }
9
9
  const escapeStringForJson = (str) => JSON.stringify(str).slice(1, -1); // slice is needed to remove surrounding quotes added by JSON.stringify
10
+ const isValidIdentifier = (str) => /^[A-Za-z_]\w*$/.test(str);
10
11
  //#region snippets
11
12
  export const adapters = (model) => [
12
13
  `from adapters import AutoAdapterModel
13
14
 
14
- model = AutoAdapterModel.from_pretrained("${model.config?.adapter_transformers?.model_name}")
15
+ model = AutoAdapterModel.from_pretrained("${escapeStringForJson(model.config?.adapter_transformers?.model_name ?? "fill-in-model-name")}")
15
16
  model.load_adapter("${model.id}", set_active=True)`,
16
17
  ];
17
18
  const allennlpUnknown = (model) => [
@@ -63,7 +64,7 @@ result, message = detector.detect_watermark(watermarked_audio, sr)`;
63
64
  return [watermarkSnippet, detectorSnippet];
64
65
  };
65
66
  function get_base_diffusers_model(model) {
66
- return model.cardData?.base_model?.toString() ?? "fill-in-base-model";
67
+ return escapeStringForJson(model.cardData?.base_model?.toString() ?? "fill-in-base-model");
67
68
  }
68
69
  function get_prompt_from_diffusers_model(model) {
69
70
  const prompt = model.widgetData?.[0]?.text ?? model.cardData?.instance_prompt;
@@ -877,7 +878,8 @@ backbone = keras_hub.models.Backbone.from_preset("hf://${modelId}")
877
878
  `;
878
879
  export const keras_hub = (model) => {
879
880
  const modelId = model.id;
880
- const tasks = model.config?.keras_hub?.tasks ?? [];
881
+ // interpolated as a Python class name, so a non-identifier is treated as absent
882
+ const tasks = (model.config?.keras_hub?.tasks ?? []).filter(isValidIdentifier);
881
883
  const snippets = [];
882
884
  // First, generate tasks with examples
883
885
  for (const [task, snippet] of Object.entries(_keras_hub_tasks_with_example)) {
@@ -944,77 +946,219 @@ audio = m.generate("This high quality TTS model works without a GPU")
944
946
  import soundfile as sf
945
947
  sf.write('output.wav', audio, 24000)`,
946
948
  ];
947
- export const ltx = (model) => {
948
- const localDir = `models/${nameWithoutNamespace(model.id)}`;
949
- const install = `# Install the LTX-2 pipelines
949
+ /**
950
+ * Detect LTX-2.5 (split weights + Gemma 4 TE file) vs LTX-2.3
951
+ * (monolith checkpoint + separate Gemma 3 root). Prefer explicit tags / ids /
952
+ * base_model refs; fall back to 2.3 for unmarked legacy cards.
953
+ */
954
+ function _isLtx25Model(model) {
955
+ const refs = [model.id, ...(model.tags ?? [])];
956
+ const base = model.cardData?.base_model;
957
+ if (Array.isArray(base)) {
958
+ refs.push(...base);
959
+ }
960
+ else if (base) {
961
+ refs.push(base);
962
+ }
963
+ return refs.some((ref) => /ltx[-_]?2\.5/i.test(ref));
964
+ }
965
+ const _LTX_I2V_HINT = `# For image-to-video, add: --image path/to/image.jpg 0 0.8`;
966
+ const _LTX_GEMMA3_ROOT = "models/gemma-3-12b";
967
+ const _LTX_DEFAULT_PROMPT = "A beautiful sunset over the ocean";
968
+ function _ltxInstall(is25) {
969
+ // natten is only needed for the LTX-2.5 diffusion VAE (skipped automatically on Windows/macOS).
970
+ return `# Install the LTX-2 pipelines
950
971
  git clone https://github.com/Lightricks/LTX-2.git
951
972
  cd LTX-2
952
- uv sync --frozen`;
953
- // Every pipeline needs the Gemma text encoder, which lives in a separate repo.
954
- const download = `# Download the weights from this repo, plus the Gemma text encoder
955
- hf download ${model.id} --local-dir ${localDir}
956
- hf download google/gemma-3-12b-it-qat-q4_0-unquantized --local-dir models/gemma-3-12b`;
957
- // Add "--image <path> <frame_idx> <strength>" to any command below to condition on
958
- // an image (e.g. "--image image.jpg 0 0.8"), turning text-to-video into image-to-video.
959
- const imageToVideoHint = `# For image-to-video, add: --image path/to/image.jpg 0 0.8`;
960
- const tags = model.tags ?? [];
961
- // IC-LoRA: video-to-video / image-to-video with a control (reference) signal.
962
- // Checked before "lora" because an IC-LoRA repo may carry both tags.
973
+ uv sync ${is25 ? "--extra natten" : "--frozen"}`;
974
+ }
975
+ function _ltxRun(module, args, comment, options) {
976
+ const body = `uv run python -m ltx_pipelines.${module} \\\n ${args.join(" \\\n ")}`;
977
+ const parts = [`# ${comment}`, body];
978
+ if (options?.footer) {
979
+ parts.push(options.footer);
980
+ }
981
+ if (options?.hint) {
982
+ parts.push(_LTX_I2V_HINT);
983
+ }
984
+ return parts.join("\n");
985
+ }
986
+ const _LTX_DETAILING_LORA_REPO = "Lightricks/LTX-2.5-22b-IC-LoRA-Pixel-Spatial-Upscaler";
987
+ const _LTX_DETAILING_LORA_FILE = "ltx-2.5-22b-ic-lora-pixel-spatial-upscaler-x2-1.0.safetensors";
988
+ function _ltx25SplitArgs(paths) {
989
+ const args = [
990
+ `--transformer-path ${paths.transformer}`,
991
+ `--text-encoder-path ${paths.textEncoder}`,
992
+ `--video-vae-path ${paths.videoVae}`,
993
+ `--audio-vae-path ${paths.audioVae}`,
994
+ `--spatial-upsampler-path ${paths.spatialUpsampler}`,
995
+ ];
996
+ if (paths.temporalUpsampler) {
997
+ args.push(`--temporal-upsampler-path ${paths.temporalUpsampler}`);
998
+ }
999
+ return args;
1000
+ }
1001
+ function _ltx25RepoPaths(localDir) {
1002
+ const shared = {
1003
+ transformer: `${localDir}/diffusion_models/<distilled-transformer>.safetensors`,
1004
+ textEncoder: `${localDir}/text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors`,
1005
+ videoVae: `${localDir}/vae/<video-vae>.safetensors`,
1006
+ audioVae: `${localDir}/vae/<audio-vae>.safetensors`,
1007
+ spatialUpsampler: `${localDir}/latent_upscale_models/<spatial-upsampler>.safetensors`,
1008
+ };
1009
+ return {
1010
+ distilled: shared,
1011
+ // DFR runs on the distilled transformer; detailing IC-LoRA is required separately.
1012
+ dfr: {
1013
+ ...shared,
1014
+ temporalUpsampler: `${localDir}/latent_upscale_models/<temporal-upsampler>.safetensors`,
1015
+ },
1016
+ };
1017
+ }
1018
+ function _ltx25BasePlaceholderPaths() {
1019
+ return {
1020
+ transformer: "path/to/distilled-transformer.safetensors",
1021
+ textEncoder: "path/to/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors",
1022
+ videoVae: "path/to/video-vae.safetensors",
1023
+ audioVae: "path/to/audio-vae.safetensors",
1024
+ spatialUpsampler: "path/to/spatial-upsampler.safetensors",
1025
+ };
1026
+ }
1027
+ function _ltx25Download(modelId, localDir, kind) {
1028
+ if (kind === "adapter") {
1029
+ return `# Download the adapter weights from this repo
1030
+ # (base components come from Lightricks/LTX-2.5 — see Files and versions)
1031
+ hf download ${modelId} --local-dir ${localDir}`;
1032
+ }
1033
+ const detailingDir = `models/${_LTX_DETAILING_LORA_REPO.split("/")[1]}`;
1034
+ return `# Download weights from this repo
1035
+ # Substitute filenames from this repo's "Files and versions" if they differ
1036
+ hf download ${modelId} \\
1037
+ diffusion_models/<distilled-transformer>.safetensors \\
1038
+ text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors \\
1039
+ vae/<video-vae>.safetensors \\
1040
+ vae/<audio-vae>.safetensors \\
1041
+ latent_upscale_models/<spatial-upsampler>.safetensors \\
1042
+ latent_upscale_models/<temporal-upsampler>.safetensors \\
1043
+ --local-dir ${localDir}
1044
+ # DFR requires the detailing IC-LoRA (separate repo; strength is fixed at 0.5)
1045
+ hf download ${_LTX_DETAILING_LORA_REPO} --local-dir ${detailingDir}`;
1046
+ }
1047
+ function _ltx23Download(modelId, localDir) {
1048
+ return `# Download the weights from this repo, plus the Gemma text encoder
1049
+ hf download ${modelId} --local-dir ${localDir}
1050
+ hf download google/gemma-3-12b-it-qat-q4_0-unquantized --local-dir ${_LTX_GEMMA3_ROOT}`;
1051
+ }
1052
+ function _ltx25Snippets(model, localDir, tags) {
1053
+ const install = _ltxInstall(true);
1054
+ const loraArg = `--lora ${localDir}/<weights>.safetensors 1.0`;
1055
+ const basePaths = _ltx25BasePlaceholderPaths();
1056
+ if (tags.includes("ic-lora")) {
1057
+ return [
1058
+ install,
1059
+ _ltx25Download(model.id, localDir, "adapter"),
1060
+ _ltxRun("ic_lora", [
1061
+ ..._ltx25SplitArgs(basePaths),
1062
+ loraArg,
1063
+ "--video-conditioning reference.mp4 1.0",
1064
+ `--prompt "your prompt here"`,
1065
+ "--output-path output.mp4",
1066
+ ], "Video-to-video with the IC-LoRA (runs on the distilled LTX-2.5 base)"),
1067
+ ];
1068
+ }
1069
+ if (tags.includes("lora")) {
1070
+ return [
1071
+ install,
1072
+ _ltx25Download(model.id, localDir, "adapter"),
1073
+ _ltxRun("distilled", [..._ltx25SplitArgs(basePaths), loraArg, `--prompt "your prompt here"`, "--output-path output.mp4"], "Text/image-to-video with the LoRA on the distilled LTX-2.5 pipeline", { hint: true }),
1074
+ ];
1075
+ }
1076
+ const { distilled, dfr } = _ltx25RepoPaths(localDir);
1077
+ const detailingDir = `models/${_LTX_DETAILING_LORA_REPO.split("/")[1]}`;
1078
+ return [
1079
+ install,
1080
+ _ltx25Download(model.id, localDir, "base"),
1081
+ _ltxRun("distilled", [
1082
+ ..._ltx25SplitArgs(distilled),
1083
+ "--num-frames 121",
1084
+ `--prompt "${_LTX_DEFAULT_PROMPT}"`,
1085
+ "--output-path output.mp4",
1086
+ ], "Distilled LTX-2.5 pipeline (fast)", { hint: true }),
1087
+ _ltxRun("dfr_pipeline", [
1088
+ ..._ltx25SplitArgs(dfr),
1089
+ `--detailing-lora ${detailingDir}/${_LTX_DETAILING_LORA_FILE}`,
1090
+ "--spatial-upscalings 1",
1091
+ "--temporal-upscalings 1",
1092
+ "--height 1088",
1093
+ "--width 1920",
1094
+ "--num-frames 121",
1095
+ `--prompt "${_LTX_DEFAULT_PROMPT}"`,
1096
+ "--output-path output.mp4",
1097
+ ], "DFR pipeline (higher detail fidelity; optional temporal 2x/4x)", {
1098
+ footer: "# For 4K: --spatial-upscalings 2 --width 3840 --height 2176",
1099
+ hint: true,
1100
+ }),
1101
+ ];
1102
+ }
1103
+ function _ltx23Snippets(model, localDir, tags) {
1104
+ const install = _ltxInstall(false);
1105
+ const download = _ltx23Download(model.id, localDir);
1106
+ const loraArg = `--lora ${localDir}/<weights>.safetensors 1.0`;
1107
+ const gemma = `--gemma-root ${_LTX_GEMMA3_ROOT}`;
963
1108
  if (tags.includes("ic-lora")) {
964
1109
  return [
965
1110
  install,
966
1111
  download,
967
- `# Video-to-video with the IC-LoRA (runs on the distilled base model)
968
- uv run python -m ltx_pipelines.ic_lora \\
969
- --distilled-checkpoint-path path/to/distilled_checkpoint.safetensors \\
970
- --spatial-upsampler-path path/to/spatial_upsampler.safetensors \\
971
- --gemma-root models/gemma-3-12b \\
972
- --lora ${localDir}/<weights>.safetensors 1.0 \\
973
- --video-conditioning reference.mp4 1.0 \\
974
- --prompt "your prompt here" \\
975
- --output-path output.mp4`,
1112
+ _ltxRun("ic_lora", [
1113
+ "--distilled-checkpoint-path path/to/distilled_checkpoint.safetensors",
1114
+ "--spatial-upsampler-path path/to/spatial_upsampler.safetensors",
1115
+ gemma,
1116
+ loraArg,
1117
+ "--video-conditioning reference.mp4 1.0",
1118
+ `--prompt "your prompt here"`,
1119
+ "--output-path output.mp4",
1120
+ ], "Video-to-video with the IC-LoRA (runs on the distilled base model)"),
976
1121
  ];
977
1122
  }
978
- // Standard LoRA applied on top of the base pipeline.
979
1123
  if (tags.includes("lora")) {
980
1124
  return [
981
1125
  install,
982
1126
  download,
983
- `# Text/image-to-video with the LoRA on the HQ two-stage base pipeline
984
- uv run python -m ltx_pipelines.ti2vid_two_stages_hq \\
985
- --checkpoint-path path/to/checkpoint.safetensors \\
986
- --distilled-lora path/to/distilled_lora.safetensors 0.8 \\
987
- --spatial-upsampler-path path/to/spatial_upsampler.safetensors \\
988
- --gemma-root models/gemma-3-12b \\
989
- --lora ${localDir}/<weights>.safetensors 1.0 \\
990
- --prompt "your prompt here" \\
991
- --output-path output.mp4
992
- ${imageToVideoHint}`,
1127
+ _ltxRun("ti2vid_two_stages_hq", [
1128
+ "--checkpoint-path path/to/checkpoint.safetensors",
1129
+ "--distilled-lora path/to/distilled_lora.safetensors 0.8",
1130
+ "--spatial-upsampler-path path/to/spatial_upsampler.safetensors",
1131
+ gemma,
1132
+ loraArg,
1133
+ `--prompt "your prompt here"`,
1134
+ "--output-path output.mp4",
1135
+ ], "Text/image-to-video with the LoRA on the HQ two-stage base pipeline", { hint: true }),
993
1136
  ];
994
1137
  }
995
- // Base model: the fast (distilled) and HQ (two-stage) pipelines. Substitute the
996
- // .safetensors filenames with the ones listed under this repo's "Files and versions".
997
1138
  return [
998
1139
  install,
999
1140
  download,
1000
- `# Fast pipeline (distilled model, no distilled LoRA needed)
1001
- uv run python -m ltx_pipelines.distilled \\
1002
- --distilled-checkpoint-path ${localDir}/<distilled-checkpoint>.safetensors \\
1003
- --spatial-upsampler-path ${localDir}/<spatial-upsampler>.safetensors \\
1004
- --gemma-root models/gemma-3-12b \\
1005
- --prompt "A beautiful sunset over the ocean" \\
1006
- --output-path output.mp4
1007
- ${imageToVideoHint}`,
1008
- `# HQ pipeline (two-stage, higher quality)
1009
- uv run python -m ltx_pipelines.ti2vid_two_stages_hq \\
1010
- --checkpoint-path ${localDir}/<checkpoint>.safetensors \\
1011
- --distilled-lora ${localDir}/<distilled-lora>.safetensors 0.8 \\
1012
- --spatial-upsampler-path ${localDir}/<spatial-upsampler>.safetensors \\
1013
- --gemma-root models/gemma-3-12b \\
1014
- --prompt "A beautiful sunset over the ocean" \\
1015
- --output-path output.mp4
1016
- ${imageToVideoHint}`,
1141
+ _ltxRun("distilled", [
1142
+ `--distilled-checkpoint-path ${localDir}/<distilled-checkpoint>.safetensors`,
1143
+ `--spatial-upsampler-path ${localDir}/<spatial-upsampler>.safetensors`,
1144
+ gemma,
1145
+ `--prompt "${_LTX_DEFAULT_PROMPT}"`,
1146
+ "--output-path output.mp4",
1147
+ ], "Fast pipeline (distilled model, no distilled LoRA needed)", { hint: true }),
1148
+ _ltxRun("ti2vid_two_stages_hq", [
1149
+ `--checkpoint-path ${localDir}/<checkpoint>.safetensors`,
1150
+ `--distilled-lora ${localDir}/<distilled-lora>.safetensors 0.8`,
1151
+ `--spatial-upsampler-path ${localDir}/<spatial-upsampler>.safetensors`,
1152
+ gemma,
1153
+ `--prompt "${_LTX_DEFAULT_PROMPT}"`,
1154
+ "--output-path output.mp4",
1155
+ ], "HQ pipeline (two-stage, higher quality)", { hint: true }),
1017
1156
  ];
1157
+ }
1158
+ export const ltx = (model) => {
1159
+ const localDir = `models/${nameWithoutNamespace(model.id)}`;
1160
+ const tags = model.tags ?? [];
1161
+ return _isLtx25Model(model) ? _ltx25Snippets(model, localDir, tags) : _ltx23Snippets(model, localDir, tags);
1018
1162
  };
1019
1163
  export const lightning_ir = (model) => {
1020
1164
  if (model.tags.includes("bi-encoder")) {
@@ -1170,7 +1314,7 @@ model = MeshAnything(args)`,
1170
1314
  ];
1171
1315
  export const multimolecule = (model) => {
1172
1316
  const widgetExample = model.widgetData?.[0];
1173
- const exampleText = widgetExample?.text;
1317
+ const exampleText = escapeStringForJson(widgetExample?.text ?? "");
1174
1318
  const maskToken = model.mask_token ?? "<mask>";
1175
1319
  const sequence = exampleText?.replace(maskToken, "A");
1176
1320
  const snippets = [`pip install multimolecule`];
@@ -1225,8 +1369,9 @@ openasr transcribe audio.wav --model ${modelId}`,
1225
1369
  ];
1226
1370
  };
1227
1371
  export const paddlenlp = (model) => {
1228
- if (model.config?.architectures?.[0]) {
1229
- const architecture = model.config.architectures[0];
1372
+ const architecture = model.config?.architectures?.[0];
1373
+ // interpolated as a Python class name, so a non-identifier is treated as absent
1374
+ if (architecture && isValidIdentifier(architecture)) {
1230
1375
  return [
1231
1376
  [
1232
1377
  `from paddlenlp.transformers import AutoTokenizer, ${architecture}`,
@@ -1465,7 +1610,7 @@ const skopsPickle = (model, modelFile) => {
1465
1610
  from skops.hub_utils import download
1466
1611
  download("${model.id}", "path_to_folder")
1467
1612
  model = joblib.load(
1468
- "${modelFile}"
1613
+ "${escapeStringForJson(modelFile)}"
1469
1614
  )
1470
1615
  # only load pickle files from sources you trust
1471
1616
  # read more about it here https://skops.readthedocs.io/en/stable/persistence.html`,
@@ -1478,7 +1623,7 @@ from skops.io import load
1478
1623
  download("${model.id}", "path_to_folder")
1479
1624
  # make sure model file is in skops format
1480
1625
  # if model is a pickle file, make sure it's from a source you trust
1481
- model = load("path_to_folder/${modelFile}")`,
1626
+ model = load("path_to_folder/${escapeStringForJson(modelFile)}")`,
1482
1627
  ];
1483
1628
  };
1484
1629
  const skopsJobLib = (model) => {
@@ -1731,11 +1876,17 @@ model = BACKBONE_REGISTRY.build("${model.id}")`,
1731
1876
  const hasChatTemplate = (model) => model.config?.tokenizer_config?.chat_template !== undefined ||
1732
1877
  model.config?.processor_config?.chat_template !== undefined ||
1733
1878
  model.config?.chat_template_jinja !== undefined;
1879
+ // interpolated as a Python class name (and into RegExps in pruna_transformers), so a non-identifier is treated as absent
1880
+ const autoModelClass = (model) => {
1881
+ const autoModel = model.transformersInfo?.auto_model;
1882
+ return autoModel && isValidIdentifier(autoModel) ? autoModel : undefined;
1883
+ };
1734
1884
  export const transformers = (model) => {
1735
1885
  const info = model.transformersInfo;
1736
1886
  if (!info) {
1737
1887
  return [`# ⚠️ Type of model unknown`];
1738
1888
  }
1889
+ const auto_model = autoModelClass(model) ?? "AutoModel";
1739
1890
  const remote_code_snippet = model.tags.includes(TAG_CUSTOM_CODE) ? ", trust_remote_code=True" : "";
1740
1891
  const autoSnippet = [];
1741
1892
  if (info.processor) {
@@ -1744,7 +1895,7 @@ export const transformers = (model) => {
1744
1895
  : info.processor === "AutoFeatureExtractor"
1745
1896
  ? "extractor"
1746
1897
  : "processor";
1747
- autoSnippet.push("# Load model directly", `from transformers import ${info.processor}, ${info.auto_model}`, "", `${processorVarName} = ${info.processor}.from_pretrained("${model.id}"` + remote_code_snippet + ")", `model = ${info.auto_model}.from_pretrained("${model.id}"` + remote_code_snippet + ', device_map="auto")');
1898
+ autoSnippet.push("# Load model directly", `from transformers import ${info.processor}, ${auto_model}`, "", `${processorVarName} = ${info.processor}.from_pretrained("${model.id}"` + remote_code_snippet + ")", `model = ${auto_model}.from_pretrained("${model.id}"` + remote_code_snippet + ', device_map="auto")');
1748
1899
  if (model.tags.includes("conversational") && hasChatTemplate(model)) {
1749
1900
  if (model.tags.includes("image-text-to-text")) {
1750
1901
  autoSnippet.push("messages = [", [
@@ -1764,7 +1915,7 @@ export const transformers = (model) => {
1764
1915
  }
1765
1916
  }
1766
1917
  else {
1767
- autoSnippet.push("# Load model directly", `from transformers import ${info.auto_model}`, `model = ${info.auto_model}.from_pretrained("${model.id}"` + remote_code_snippet + ', device_map="auto")');
1918
+ autoSnippet.push("# Load model directly", `from transformers import ${auto_model}`, `model = ${auto_model}.from_pretrained("${model.id}"` + remote_code_snippet + ', device_map="auto")');
1768
1919
  }
1769
1920
  if (model.pipeline_tag && LIBRARY_TASK_MAPPING.transformers?.includes(model.pipeline_tag)) {
1770
1921
  const pipelineSnippet = ["# Use a pipeline as a high-level helper"];
@@ -1840,7 +1991,7 @@ export const peft = (model) => {
1840
1991
  `from peft import PeftModel
1841
1992
  from transformers import AutoModelFor${pefttask}
1842
1993
 
1843
- base_model = AutoModelFor${pefttask}.from_pretrained("${peftBaseModel}")
1994
+ base_model = AutoModelFor${pefttask}.from_pretrained("${escapeStringForJson(peftBaseModel)}")
1844
1995
  model = PeftModel.from_pretrained(base_model, "${model.id}")`,
1845
1996
  ];
1846
1997
  };
@@ -2230,18 +2381,18 @@ const pruna_diffusers = (model) => {
2230
2381
  .trim());
2231
2382
  };
2232
2383
  const pruna_transformers = (model) => {
2233
- const info = model.transformersInfo;
2384
+ const auto_model = autoModelClass(model);
2234
2385
  const transformersSnippets = transformers(model);
2235
2386
  // Replace pipeline with PrunaModel
2236
2387
  let processedSnippets = transformersSnippets.map((snippet) => snippet
2237
2388
  .replace(/from transformers import pipeline/g, "from pruna import PrunaModel")
2238
2389
  .replace(/pipeline\([^)]*\)/g, `PrunaModel.from_pretrained("${model.id}")`));
2239
2390
  // Additional cleanup if auto_model info is available
2240
- if (info?.auto_model) {
2391
+ if (auto_model) {
2241
2392
  processedSnippets = processedSnippets.map((snippet) => snippet
2242
- .replace(new RegExp(`from transformers import ${info.auto_model}\n?`, "g"), "")
2243
- .replace(new RegExp(`${info.auto_model}.from_pretrained`, "g"), "PrunaModel.from_pretrained")
2244
- .replace(new RegExp(`^.*from.*import.*(, *${info.auto_model})+.*$`, "gm"), (line) => line.replace(new RegExp(`, *${info.auto_model}`, "g"), "")));
2393
+ .replace(new RegExp(`from transformers import ${auto_model}\n?`, "g"), "")
2394
+ .replace(new RegExp(`${auto_model}.from_pretrained`, "g"), "PrunaModel.from_pretrained")
2395
+ .replace(new RegExp(`^.*from.*import.*(, *${auto_model})+.*$`, "gm"), (line) => line.replace(new RegExp(`, *${auto_model}`, "g"), "")));
2245
2396
  }
2246
2397
  return processedSnippets;
2247
2398
  };
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { llama_cpp_python } from "./model-libraries-snippets.js";
2
+ import { adapters, diffusers, keras_hub, llama_cpp_python, multimolecule, paddlenlp, peft, sklearn, transformers, } from "./model-libraries-snippets.js";
3
3
  describe("model-libraries-snippets", () => {
4
4
  it("llama_cpp_python conversational", async () => {
5
5
  const model = {
@@ -50,4 +50,35 @@ output = llm(
50
50
  )
51
51
  print(output)`);
52
52
  });
53
+ // a repo owner can put anything in config.json / the model card, so every interpolated value
54
+ // must either be escaped (string literals) or rejected (bare identifiers)
55
+ describe("repo-controlled values are not injectable", () => {
56
+ const PAYLOAD = `")\nimport os; os.system("id`;
57
+ it.each([
58
+ ["adapters", adapters, { config: { adapter_transformers: { model_name: PAYLOAD } } }],
59
+ ["diffusers", diffusers, { tags: ["lora"], cardData: { base_model: PAYLOAD, instance_prompt: PAYLOAD } }],
60
+ ["keras_hub", keras_hub, { config: { keras_hub: { tasks: [PAYLOAD, "TextClassifier"] } } }],
61
+ ["paddlenlp", paddlenlp, { config: { architectures: [PAYLOAD] } }],
62
+ ["peft", peft, { config: { peft: { base_model_name_or_path: PAYLOAD, task_type: "CAUSAL_LM" } } }],
63
+ ["multimolecule", multimolecule, { widgetData: [{ text: PAYLOAD }] }],
64
+ ["transformers", transformers, { transformersInfo: { auto_model: PAYLOAD, processor: "AutoTokenizer" } }],
65
+ [
66
+ "sklearn",
67
+ sklearn,
68
+ { tags: ["skops"], config: { sklearn: { model: { file: PAYLOAD }, model_format: "pickle" } } },
69
+ ],
70
+ ])("%s", (_name, snippetFn, model) => {
71
+ const snippet = snippetFn({ id: "user/model", tags: [], inference: "", ...model }).join("\n");
72
+ expect(snippet).not.toContain(PAYLOAD);
73
+ });
74
+ it("keras_hub keeps the valid task next to a rejected one", () => {
75
+ const model = {
76
+ id: "user/model",
77
+ tags: [],
78
+ inference: "",
79
+ config: { keras_hub: { tasks: [PAYLOAD, "TextClassifier"] } },
80
+ };
81
+ expect(keras_hub(model).join("\n")).toContain("keras_hub.models.TextClassifier.from_preset");
82
+ });
83
+ });
53
84
  });
@@ -760,7 +760,7 @@ export const MODEL_LIBRARIES_UI_ELEMENTS = {
760
760
  countDownloads: `path_extension:"pt"`,
761
761
  },
762
762
  ltx: {
763
- prettyLabel: "LTX.io",
763
+ prettyLabel: "LTX-2",
764
764
  repoName: "LTX-2",
765
765
  repoUrl: "https://github.com/Lightricks/LTX-2",
766
766
  docsUrl: "https://github.com/Lightricks/LTX-2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huggingface/tasks",
3
- "version": "0.21.35",
3
+ "version": "0.21.37",
4
4
  "description": "List of ML tasks for huggingface.co/tasks",
5
5
  "keywords": [
6
6
  "hub",
@@ -133,7 +133,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \\
133
133
  const snippet = snippetFunc(model);
134
134
 
135
135
  expect(snippet[0].content).toContain(`llama serve -hf bartowski/Llama-3.2-3B-Instruct-GGUF:{{QUANT_TAG}}`);
136
- expect(snippet[1].setup).toContain("npm install -g @mariozechner/pi-coding-agent");
136
+ expect(snippet[1].setup).toContain("npm install -g @earendil-works/pi-coding-agent");
137
137
  expect(snippet[1].content).toContain(`"id": "bartowski/Llama-3.2-3B-Instruct-GGUF:{{QUANT_TAG}}"`);
138
138
  expect(snippet[2].content).toContain("pi");
139
139
  });
@@ -155,7 +155,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \\
155
155
 
156
156
  expect(snippet[0].setup).toContain("uv tool install mlx-lm");
157
157
  expect(snippet[0].content).toContain('mlx_lm.server --model "mlx-community/Llama-3.2-3B-Instruct-mlx"');
158
- expect(snippet[1].setup).toContain("npm install -g @mariozechner/pi-coding-agent");
158
+ expect(snippet[1].setup).toContain("npm install -g @earendil-works/pi-coding-agent");
159
159
  expect(snippet[1].content).toContain('"baseUrl": "http://localhost:8080/v1"');
160
160
  expect(snippet[1].content).toContain('"id": "mlx-community/Llama-3.2-3B-Instruct-mlx"');
161
161
  expect(snippet[2].content).toContain("pi");
package/src/local-apps.ts CHANGED
@@ -509,7 +509,7 @@ const snippetPi = (model: ModelData, filepath?: string): LocalAppSnippet[] => {
509
509
  serverStep,
510
510
  {
511
511
  title: "Configure the model in Pi",
512
- setup: "# Install Pi:\nnpm install -g @mariozechner/pi-coding-agent",
512
+ setup: "# Install Pi:\nnpm install -g @earendil-works/pi-coding-agent",
513
513
  content: `# Add to ~/.pi/agent/models.json:\n${modelsJson}`,
514
514
  },
515
515
  {
@@ -820,7 +820,7 @@ export const LOCAL_APPS = {
820
820
  },
821
821
  pi: {
822
822
  prettyLabel: "Pi",
823
- docsUrl: "https://github.com/badlogic/pi-mono",
823
+ docsUrl: "https://github.com/earendil-works/pi",
824
824
  mainTask: "text-generation",
825
825
  displayOnModelPage: isToolCallingLocalAgentModel,
826
826
  snippet: snippetPi,
@@ -1,6 +1,16 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import type { ModelData } from "./model-data.js";
3
- import { llama_cpp_python } from "./model-libraries-snippets.js";
3
+ import {
4
+ adapters,
5
+ diffusers,
6
+ keras_hub,
7
+ llama_cpp_python,
8
+ multimolecule,
9
+ paddlenlp,
10
+ peft,
11
+ sklearn,
12
+ transformers,
13
+ } from "./model-libraries-snippets.js";
4
14
 
5
15
  describe("model-libraries-snippets", () => {
6
16
  it("llama_cpp_python conversational", async () => {
@@ -55,4 +65,39 @@ output = llm(
55
65
  )
56
66
  print(output)`);
57
67
  });
68
+
69
+ // a repo owner can put anything in config.json / the model card, so every interpolated value
70
+ // must either be escaped (string literals) or rejected (bare identifiers)
71
+ describe("repo-controlled values are not injectable", () => {
72
+ const PAYLOAD = `")\nimport os; os.system("id`;
73
+
74
+ it.each([
75
+ ["adapters", adapters, { config: { adapter_transformers: { model_name: PAYLOAD } } }],
76
+ ["diffusers", diffusers, { tags: ["lora"], cardData: { base_model: PAYLOAD, instance_prompt: PAYLOAD } }],
77
+ ["keras_hub", keras_hub, { config: { keras_hub: { tasks: [PAYLOAD, "TextClassifier"] } } }],
78
+ ["paddlenlp", paddlenlp, { config: { architectures: [PAYLOAD] } }],
79
+ ["peft", peft, { config: { peft: { base_model_name_or_path: PAYLOAD, task_type: "CAUSAL_LM" } } }],
80
+ ["multimolecule", multimolecule, { widgetData: [{ text: PAYLOAD }] }],
81
+ ["transformers", transformers, { transformersInfo: { auto_model: PAYLOAD, processor: "AutoTokenizer" } }],
82
+ [
83
+ "sklearn",
84
+ sklearn,
85
+ { tags: ["skops"], config: { sklearn: { model: { file: PAYLOAD }, model_format: "pickle" } } },
86
+ ],
87
+ ])("%s", (_name, snippetFn, model) => {
88
+ const snippet = snippetFn({ id: "user/model", tags: [], inference: "", ...model } as ModelData).join("\n");
89
+
90
+ expect(snippet).not.toContain(PAYLOAD);
91
+ });
92
+
93
+ it("keras_hub keeps the valid task next to a rejected one", () => {
94
+ const model = {
95
+ id: "user/model",
96
+ tags: [],
97
+ inference: "",
98
+ config: { keras_hub: { tasks: [PAYLOAD, "TextClassifier"] } },
99
+ };
100
+ expect(keras_hub(model as ModelData).join("\n")).toContain("keras_hub.models.TextClassifier.from_preset");
101
+ });
102
+ });
58
103
  });