From 174699b3876a2f8147b59c7075c72a0d8a1eab1a Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:02:42 -0400 Subject: [PATCH 001/103] cleanup jsdoc description --- packages/transformers/src/backends/onnx.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/transformers/src/backends/onnx.js b/packages/transformers/src/backends/onnx.js index 13b1a7482..f4302a873 100644 --- a/packages/transformers/src/backends/onnx.js +++ b/packages/transformers/src/backends/onnx.js @@ -1,10 +1,6 @@ /** - * @file Handler file for choosing the correct version of ONNX Runtime, based on the environment. - * Ideally, we could import the `onnxruntime-web` and `onnxruntime-node` packages only when needed, - * but dynamic imports don't seem to work with the current webpack version and/or configuration. - * This is possibly due to the experimental nature of top-level await statements. - * So, we just import both packages, and use the appropriate one based on the environment: - * - When running in node, we use `onnxruntime-node`. + * @file Handler file for choosing the correct version of ONNX Runtime, based on the environment: + * - When running in node, we use `onnxruntime-node` (`onnxruntime-web` is not bundled). * - When running in the browser, we use `onnxruntime-web` (`onnxruntime-node` is not bundled). * * This module is not directly exported, but can be accessed through the environment variables: From 3a1c923a18b42b3982ed473edf9c2fe37f7f98ee Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:37:35 -0400 Subject: [PATCH 002/103] Export GenerationConfig --- packages/transformers/src/transformers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/transformers/src/transformers.js b/packages/transformers/src/transformers.js index ef01569ef..1b77f07c6 100644 --- a/packages/transformers/src/transformers.js +++ b/packages/transformers/src/transformers.js @@ -45,6 +45,7 @@ export { PretrainedConfig, AutoConfig } from './configs.js'; export * from './generation/streamers.js'; export * from './generation/stopping_criteria.js'; export * from './generation/logits_process.js'; +export { GenerationConfig } from './generation/configuration_utils.js'; export { load_audio, read_audio, RawAudio } from './utils/audio.js'; export { load_image, RawImage } from './utils/image.js'; From ccef1d6a247da1c81495cf83a7274a34e0ad7847 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:38:20 -0400 Subject: [PATCH 003/103] set pipelines modules --- packages/transformers/src/pipelines/audio-classification.js | 4 ++++ .../src/pipelines/automatic-speech-recognition.js | 4 ++++ packages/transformers/src/pipelines/background-removal.js | 4 ++++ packages/transformers/src/pipelines/depth-estimation.js | 4 ++++ .../transformers/src/pipelines/document-question-answering.js | 4 ++++ packages/transformers/src/pipelines/feature-extraction.js | 4 ++++ packages/transformers/src/pipelines/fill-mask.js | 4 ++++ packages/transformers/src/pipelines/image-classification.js | 4 ++++ .../transformers/src/pipelines/image-feature-extraction.js | 4 ++++ packages/transformers/src/pipelines/image-segmentation.js | 4 ++++ packages/transformers/src/pipelines/image-to-image.js | 4 ++++ packages/transformers/src/pipelines/image-to-text.js | 4 ++++ packages/transformers/src/pipelines/object-detection.js | 4 ++++ packages/transformers/src/pipelines/question-answering.js | 4 ++++ packages/transformers/src/pipelines/summarization.js | 4 ++++ packages/transformers/src/pipelines/text-classification.js | 4 ++++ packages/transformers/src/pipelines/text-generation.js | 4 ++++ packages/transformers/src/pipelines/text-to-audio.js | 4 ++++ packages/transformers/src/pipelines/text2text-generation.js | 4 ++++ packages/transformers/src/pipelines/token-classification.js | 4 ++++ packages/transformers/src/pipelines/translation.js | 4 ++++ .../src/pipelines/zero-shot-audio-classification.js | 4 ++++ .../transformers/src/pipelines/zero-shot-classification.js | 4 ++++ .../src/pipelines/zero-shot-image-classification.js | 4 ++++ .../transformers/src/pipelines/zero-shot-object-detection.js | 4 ++++ 25 files changed, 100 insertions(+) diff --git a/packages/transformers/src/pipelines/audio-classification.js b/packages/transformers/src/pipelines/audio-classification.js index 60cd9ff10..95a1a3639 100644 --- a/packages/transformers/src/pipelines/audio-classification.js +++ b/packages/transformers/src/pipelines/audio-classification.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareAudios } from './_base.js'; import { Tensor, topk } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/automatic-speech-recognition.js b/packages/transformers/src/pipelines/automatic-speech-recognition.js index 238bb45a4..acbb4a389 100644 --- a/packages/transformers/src/pipelines/automatic-speech-recognition.js +++ b/packages/transformers/src/pipelines/automatic-speech-recognition.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareAudios } from './_base.js'; import { Tensor } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/background-removal.js b/packages/transformers/src/pipelines/background-removal.js index 37b84706c..6e27b6c82 100644 --- a/packages/transformers/src/pipelines/background-removal.js +++ b/packages/transformers/src/pipelines/background-removal.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { ImageSegmentationPipeline } from './image-segmentation.js'; import { prepareImages } from './_base.js'; import { RawImage } from '../utils/image.js'; diff --git a/packages/transformers/src/pipelines/depth-estimation.js b/packages/transformers/src/pipelines/depth-estimation.js index fd60aac1a..4dedc56a5 100644 --- a/packages/transformers/src/pipelines/depth-estimation.js +++ b/packages/transformers/src/pipelines/depth-estimation.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { RawImage } from '../utils/image.js'; diff --git a/packages/transformers/src/pipelines/document-question-answering.js b/packages/transformers/src/pipelines/document-question-answering.js index 091481174..ac15b7611 100644 --- a/packages/transformers/src/pipelines/document-question-answering.js +++ b/packages/transformers/src/pipelines/document-question-answering.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { Tensor } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/feature-extraction.js b/packages/transformers/src/pipelines/feature-extraction.js index 4a2214e1c..ebc0149c7 100644 --- a/packages/transformers/src/pipelines/feature-extraction.js +++ b/packages/transformers/src/pipelines/feature-extraction.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { Tensor, mean_pooling, quantize_embeddings } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/fill-mask.js b/packages/transformers/src/pipelines/fill-mask.js index da2d1a82f..b22278f43 100644 --- a/packages/transformers/src/pipelines/fill-mask.js +++ b/packages/transformers/src/pipelines/fill-mask.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { Tensor, topk } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/image-classification.js b/packages/transformers/src/pipelines/image-classification.js index f9374e896..bc1904fbf 100644 --- a/packages/transformers/src/pipelines/image-classification.js +++ b/packages/transformers/src/pipelines/image-classification.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { Tensor, topk } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/image-feature-extraction.js b/packages/transformers/src/pipelines/image-feature-extraction.js index 25687f3a6..d362d6e01 100644 --- a/packages/transformers/src/pipelines/image-feature-extraction.js +++ b/packages/transformers/src/pipelines/image-feature-extraction.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { Tensor } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/image-segmentation.js b/packages/transformers/src/pipelines/image-segmentation.js index 566ced417..91f2dde5a 100644 --- a/packages/transformers/src/pipelines/image-segmentation.js +++ b/packages/transformers/src/pipelines/image-segmentation.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { RawImage } from '../utils/image.js'; diff --git a/packages/transformers/src/pipelines/image-to-image.js b/packages/transformers/src/pipelines/image-to-image.js index 66324e4b6..5da9818be 100644 --- a/packages/transformers/src/pipelines/image-to-image.js +++ b/packages/transformers/src/pipelines/image-to-image.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { RawImage } from '../utils/image.js'; diff --git a/packages/transformers/src/pipelines/image-to-text.js b/packages/transformers/src/pipelines/image-to-text.js index ddd0e3d07..7b0c677ae 100644 --- a/packages/transformers/src/pipelines/image-to-text.js +++ b/packages/transformers/src/pipelines/image-to-text.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { Tensor } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/object-detection.js b/packages/transformers/src/pipelines/object-detection.js index a1fed1789..3c45c4f8b 100644 --- a/packages/transformers/src/pipelines/object-detection.js +++ b/packages/transformers/src/pipelines/object-detection.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages, get_bounding_box } from './_base.js'; /** diff --git a/packages/transformers/src/pipelines/question-answering.js b/packages/transformers/src/pipelines/question-answering.js index fdfe9e1ae..254f15413 100644 --- a/packages/transformers/src/pipelines/question-answering.js +++ b/packages/transformers/src/pipelines/question-answering.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { product } from '../utils/core.js'; diff --git a/packages/transformers/src/pipelines/summarization.js b/packages/transformers/src/pipelines/summarization.js index c35a871d3..82ba829a3 100644 --- a/packages/transformers/src/pipelines/summarization.js +++ b/packages/transformers/src/pipelines/summarization.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Text2TextGenerationPipeline } from './text2text-generation.js'; /** diff --git a/packages/transformers/src/pipelines/text-classification.js b/packages/transformers/src/pipelines/text-classification.js index dc3a8ec57..fb85a139f 100644 --- a/packages/transformers/src/pipelines/text-classification.js +++ b/packages/transformers/src/pipelines/text-classification.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { Tensor, topk } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/text-generation.js b/packages/transformers/src/pipelines/text-generation.js index fb9871ac9..31ae8221e 100644 --- a/packages/transformers/src/pipelines/text-generation.js +++ b/packages/transformers/src/pipelines/text-generation.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { Tensor } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/text-to-audio.js b/packages/transformers/src/pipelines/text-to-audio.js index aab19b2cb..e36481a73 100644 --- a/packages/transformers/src/pipelines/text-to-audio.js +++ b/packages/transformers/src/pipelines/text-to-audio.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { Tensor } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/text2text-generation.js b/packages/transformers/src/pipelines/text2text-generation.js index b313dd91f..ef2c822c9 100644 --- a/packages/transformers/src/pipelines/text2text-generation.js +++ b/packages/transformers/src/pipelines/text2text-generation.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { Tensor } from '../utils/tensor.js'; diff --git a/packages/transformers/src/pipelines/token-classification.js b/packages/transformers/src/pipelines/token-classification.js index 70914606e..6d3fffc4e 100644 --- a/packages/transformers/src/pipelines/token-classification.js +++ b/packages/transformers/src/pipelines/token-classification.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { max, softmax } from '../utils/maths.js'; diff --git a/packages/transformers/src/pipelines/translation.js b/packages/transformers/src/pipelines/translation.js index a69115d37..60eaf7c50 100644 --- a/packages/transformers/src/pipelines/translation.js +++ b/packages/transformers/src/pipelines/translation.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Text2TextGenerationPipeline } from './text2text-generation.js'; /** diff --git a/packages/transformers/src/pipelines/zero-shot-audio-classification.js b/packages/transformers/src/pipelines/zero-shot-audio-classification.js index 20a9184af..7aaa04d9d 100644 --- a/packages/transformers/src/pipelines/zero-shot-audio-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-audio-classification.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareAudios } from './_base.js'; import { softmax } from '../utils/maths.js'; diff --git a/packages/transformers/src/pipelines/zero-shot-classification.js b/packages/transformers/src/pipelines/zero-shot-classification.js index df0bbb92e..e4807d1e6 100644 --- a/packages/transformers/src/pipelines/zero-shot-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-classification.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline } from './_base.js'; import { softmax } from '../utils/maths.js'; diff --git a/packages/transformers/src/pipelines/zero-shot-image-classification.js b/packages/transformers/src/pipelines/zero-shot-image-classification.js index 80a62f197..a80309874 100644 --- a/packages/transformers/src/pipelines/zero-shot-image-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-image-classification.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages } from './_base.js'; import { softmax } from '../utils/maths.js'; diff --git a/packages/transformers/src/pipelines/zero-shot-object-detection.js b/packages/transformers/src/pipelines/zero-shot-object-detection.js index b96c6a1cd..6fdef0cba 100644 --- a/packages/transformers/src/pipelines/zero-shot-object-detection.js +++ b/packages/transformers/src/pipelines/zero-shot-object-detection.js @@ -1,3 +1,7 @@ +/** + * @module pipelines + */ + import { Pipeline, prepareImages, get_bounding_box } from './_base.js'; /** From 25cdaf16cc4fdb6fe61177fd311fb4ac1627c2c5 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:39:25 -0400 Subject: [PATCH 004/103] formatting --- .../voxtral_realtime/modeling_voxtral_realtime.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/transformers/src/models/voxtral_realtime/modeling_voxtral_realtime.js b/packages/transformers/src/models/voxtral_realtime/modeling_voxtral_realtime.js index d4c41ce3a..75f242235 100644 --- a/packages/transformers/src/models/voxtral_realtime/modeling_voxtral_realtime.js +++ b/packages/transformers/src/models/voxtral_realtime/modeling_voxtral_realtime.js @@ -53,11 +53,11 @@ function createEncoderState(model, input_features) { } const padding_cls = DataTypeMap[padding_type]; - const enc_padding_cache = new Tensor( - padding_type, - new padding_cls(PADDING_CACHE_CHANNELS * CONV1_LEFT_PAD), - [1, PADDING_CACHE_CHANNELS, CONV1_LEFT_PAD], - ); + const enc_padding_cache = new Tensor(padding_type, new padding_cls(PADDING_CACHE_CHANNELS * CONV1_LEFT_PAD), [ + 1, + PADDING_CACHE_CHANNELS, + CONV1_LEFT_PAD, + ]); // Set up iterator from input_features const chunks_iter = input_features[Symbol.asyncIterator]?.() ?? input_features[Symbol.iterator]?.(); From 59152c3d58b3f9875605390282988e03149263c9 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:39:38 -0400 Subject: [PATCH 005/103] Update tokenization_utils.js --- packages/transformers/src/tokenization_utils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index 29de6f186..75a2f968d 100644 --- a/packages/transformers/src/tokenization_utils.js +++ b/packages/transformers/src/tokenization_utils.js @@ -228,6 +228,9 @@ function getSpecialTokens(tokenizer) { * @typedef {TTokenize extends false ? string : TReturnDict extends false ? BatchEncodingItem : BatchEncoding>} ApplyChatTemplateReturn */ +/** + * `PreTrainedTokenizer` is the base class for all tokenizers in Transformers.js. + */ export class PreTrainedTokenizer extends /** @type {new (tokenizerJSON: Object, tokenizerConfig: Object) => PreTrainedTokenizerCallback} */ ( Callable From 34a529395221b0ca4e3e3b3fe5dd8ee979a6f2ca Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:40:02 -0400 Subject: [PATCH 006/103] set module --- packages/transformers/src/cache_utils.js | 4 ++++ .../transformers/src/models/auto/feature_extraction_auto.js | 4 ++++ .../transformers/src/models/auto/image_processing_auto.js | 4 ++++ packages/transformers/src/models/auto/processing_auto.js | 4 ++++ packages/transformers/src/models/auto/tokenization_auto.js | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/packages/transformers/src/cache_utils.js b/packages/transformers/src/cache_utils.js index 4fb16992c..77563711b 100644 --- a/packages/transformers/src/cache_utils.js +++ b/packages/transformers/src/cache_utils.js @@ -1,3 +1,7 @@ +/** + * @module models + */ + import { Tensor } from './utils/tensor.js'; /** diff --git a/packages/transformers/src/models/auto/feature_extraction_auto.js b/packages/transformers/src/models/auto/feature_extraction_auto.js index 476596257..b69afcd7b 100644 --- a/packages/transformers/src/models/auto/feature_extraction_auto.js +++ b/packages/transformers/src/models/auto/feature_extraction_auto.js @@ -1,3 +1,7 @@ +/** + * @module processors + */ + import { FEATURE_EXTRACTOR_NAME, GITHUB_ISSUE_URL } from '../../utils/constants.js'; import { getModelJSON } from '../../utils/hub.js'; import { FeatureExtractor } from '../../feature_extraction_utils.js'; diff --git a/packages/transformers/src/models/auto/image_processing_auto.js b/packages/transformers/src/models/auto/image_processing_auto.js index 820dfacc5..70ee60b3b 100644 --- a/packages/transformers/src/models/auto/image_processing_auto.js +++ b/packages/transformers/src/models/auto/image_processing_auto.js @@ -1,3 +1,7 @@ +/** + * @module processors + */ + import { getModelJSON } from '../../utils/hub.js'; import { ImageProcessor } from '../../image_processors_utils.js'; import * as AllImageProcessors from '../image_processors.js'; diff --git a/packages/transformers/src/models/auto/processing_auto.js b/packages/transformers/src/models/auto/processing_auto.js index c0f444e97..2e105cc7c 100644 --- a/packages/transformers/src/models/auto/processing_auto.js +++ b/packages/transformers/src/models/auto/processing_auto.js @@ -1,3 +1,7 @@ +/** + * @module processors + */ + import { IMAGE_PROCESSOR_NAME } from '../../utils/constants.js'; import { getModelJSON } from '../../utils/hub.js'; import { Processor } from '../../processing_utils.js'; diff --git a/packages/transformers/src/models/auto/tokenization_auto.js b/packages/transformers/src/models/auto/tokenization_auto.js index 62f3c613e..06f8062ff 100644 --- a/packages/transformers/src/models/auto/tokenization_auto.js +++ b/packages/transformers/src/models/auto/tokenization_auto.js @@ -1,3 +1,7 @@ +/** + * @module tokenizers + */ + import { PreTrainedTokenizer, loadTokenizer } from '../../tokenization_utils.js'; import * as AllTokenizers from '../tokenizers.js'; import { logger } from '../../utils/logger.js'; From ea32d942c3fdd5ed5f46289761b36c65ce9ff0aa Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:48:21 -0400 Subject: [PATCH 007/103] Custom jsdoc to markdown conversion --- packages/transformers/docs/jsdoc-conf.json | 6 - .../transformers/docs/plugins/preprocess.js | 129 ----- .../transformers/docs/scripts/generate.js | 97 ++-- .../transformers/docs/scripts/lib/exports.mjs | 42 ++ packages/transformers/docs/scripts/lib/ir.mjs | 202 +++++++ .../transformers/docs/scripts/lib/parse.mjs | 150 +++++ .../docs/scripts/lib/render-api.mjs | 345 ++++++++++++ .../docs/scripts/lib/structure.mjs | 99 ++++ packages/transformers/package.json | 1 - pnpm-lock.yaml | 526 ------------------ 10 files changed, 873 insertions(+), 724 deletions(-) delete mode 100644 packages/transformers/docs/jsdoc-conf.json delete mode 100644 packages/transformers/docs/plugins/preprocess.js create mode 100644 packages/transformers/docs/scripts/lib/exports.mjs create mode 100644 packages/transformers/docs/scripts/lib/ir.mjs create mode 100644 packages/transformers/docs/scripts/lib/parse.mjs create mode 100644 packages/transformers/docs/scripts/lib/render-api.mjs create mode 100644 packages/transformers/docs/scripts/lib/structure.mjs diff --git a/packages/transformers/docs/jsdoc-conf.json b/packages/transformers/docs/jsdoc-conf.json deleted file mode 100644 index 0b0d58dec..000000000 --- a/packages/transformers/docs/jsdoc-conf.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "source": { - "excludePattern": "" - }, - "plugins": ["./plugins/preprocess.js"] -} diff --git a/packages/transformers/docs/plugins/preprocess.js b/packages/transformers/docs/plugins/preprocess.js deleted file mode 100644 index a2776014e..000000000 --- a/packages/transformers/docs/plugins/preprocess.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * JSDoc plugin to transform TypeScript-style type expressions into JSDoc-compatible syntax. - */ - -function extractBalancedBraces(text, start) { - if (text[start] !== "{") return null; - let depth = 1, - i = start + 1; - while (i < text.length && depth > 0) { - if (text[i] === "{") depth++; - else if (text[i] === "}") depth--; - i++; - } - return depth === 0 ? { content: text.slice(start + 1, i - 1), endIndex: i } : null; -} - -function stripLeadingGenericParams(expr) { - if (expr[0] !== "<") return expr; - let depth = 1, - i = 1; - while (i < expr.length && depth > 0) { - if (expr[i] === "<") depth++; - else if (expr[i] === ">") depth--; - i++; - } - return depth === 0 ? expr.slice(i) : expr; -} - -function hasTopLevelConditional(expr) { - let angle = 0, - paren = 0, - brace = 0, - bracket = 0; - - for (let i = 0; i < expr.length; ++i) { - const ch = expr[i]; - if (ch === "<") angle++; - else if (ch === ">") angle = Math.max(0, angle - 1); - else if (ch === "(") paren++; - else if (ch === ")") paren = Math.max(0, paren - 1); - else if (ch === "{") brace++; - else if (ch === "}") brace = Math.max(0, brace - 1); - else if (ch === "[") bracket++; - else if (ch === "]") bracket = Math.max(0, bracket - 1); - else if (ch === "?" && angle === 0 && paren === 0 && brace === 0 && bracket === 0) { - return true; - } - } - return false; -} - -function transformType(expr) { - let result = expr, - prev = ""; - while (result !== prev) { - prev = result; - result = stripLeadingGenericParams(result); // (...) -> (...) - if (/^Promise p?.slice(1) || "any") // import() -> Type - .replace(/\w+\[['"][^\]]+['"]\]\s+extends\s+[^}]+/g, "any") // X['p'] extends ... -> any - .replace(/(\w+)(?:<[^>]+>)?\[[^\]]+\]/g, "$1") // Type[x] or Type[x] -> Type - .replace(/keyof\s+typeof\s+\w+/g, "string") // keyof typeof X -> string - .replace(/typeof\s+\w+/g, "Object") // typeof X -> Object - .replace(/\binfer\s+\w+/g, "any") // infer K -> any - .replace(/\breadonly\s+/g, "") // readonly T -> T - .replace(/\(\s*\w[\w<>, ]*\s+extends\s+\w+\s*\?[^)]*\)/g, "any") // (X extends Y ? A : B) -> any - .replace(/(? any (unwrap parens around simple types, not after words like "function") - .replace(/(\w+)\?\s*:/g, "$1:") // x?: T -> x: T - .replace(/;\s*([}\n])/g, " $1") - .replace(/;\s+/g, ", ") // semicolons -> commas - .replace(/\{\s*\[\w+\s+in\s+[^\]]+\][^}]*\}/g, "Object") // mapped types -> Object - .replace(/new\s*\([^)]*\)\s*=>\s*\{[^}]*\}/g, "Function") // new () => {...} -> Function - .replace(/new\s*\([^)]*\)\s*=>\s*\w+/g, "Function") // new () => T -> Function - .replace(/\([^()]*\)\s*=>\s*\w[\w<>|[\], ]*/g, "Function") // () => T -> Function - .replace(/\{[^{}]*\}\[\]/g, "Array") // {x:T}[] -> Array - .replace(/(\w+)<[^>]+>\[\]/g, "Array.<$1>") // T[] -> Array. - .replace(/\([^()]+\)\[\]/g, "Array") // (A|B)[] -> Array - .replace(/(\w+)\[\]/g, "Array") // T[] -> Array (simple) - .replace(/\[\w+\]/g, "Array") // [T] single-element tuple -> Array - .replace(/\[[^\[\]]*,[^\[\]]*\]/g, "Array") // tuples with commas -> Array - .replace(/\bnew\s+([A-Z]\w*)\b/g, "$1") // new Type -> Type - .replace(/,?\s*\[\s*\w+\s*:\s*\w+\s*\]\s*:\s*\w+/g, "") // [key: string]: any -> (removed) - .replace(/\(\s*(\w+)\s*&\s*\{\s*\}\s*\)/g, "$1") // (string & {}) -> string - .replace(/\s*&\s*\{\s*\}/g, ""); // string & {} -> string - if (!result.includes("=>")) result = result.replace(/\s*&\s*/g, "|"); // A & B -> A|B - } - return result; -} - -function transformComment(comment) { - const tagRegex = - /@(?:type|param|returns?|typedef|property|prop|callback|template|augments|extends|class|constructor|member|var|const|enum|throws?)[^\S\n]*\{/g; - let result = "", - lastIndex = 0, - match; - while ((match = tagRegex.exec(comment)) !== null) { - const braceStart = match.index + match[0].length - 1; - result += comment.slice(lastIndex, braceStart); - const extracted = extractBalancedBraces(comment, braceStart); - if (extracted) { - result += "{" + transformType(extracted.content) + "}"; - lastIndex = tagRegex.lastIndex = extracted.endIndex; - } else { - result += "{"; - lastIndex = braceStart + 1; - } - } - return result + comment.slice(lastIndex); -} - -export const handlers = { - beforeParse: (e) => { - e.source = e.source - .replace(/\/\*\*[\s\S]*?\*\//g, transformComment) // transform JSDoc comments - .replace(/\b(\d+)n\b/g, "BigInt($1)"); // BigInt literals - }, -}; diff --git a/packages/transformers/docs/scripts/generate.js b/packages/transformers/docs/scripts/generate.js index f651032a1..5c432ce86 100644 --- a/packages/transformers/docs/scripts/generate.js +++ b/packages/transformers/docs/scripts/generate.js @@ -1,79 +1,52 @@ -// Based on [this tutorial](https://github.com/jsdoc2md/jsdoc-to-markdown/wiki/How-to-create-one-output-file-per-class). +// Generate per-module API markdown from the library's JSDoc comments. +// We only document items that are available in the public API. import fs from "node:fs"; import path from "node:path"; import url from "node:url"; -import jsdoc2md from "jsdoc-to-markdown"; +import { extractEntities } from "./lib/structure.mjs"; +import { buildIR } from "./lib/ir.mjs"; +import { renderModule } from "./lib/render-api.mjs"; +import { collectPublicExports } from "./lib/exports.mjs"; const docs = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); const root = path.dirname(docs); +const srcDir = path.join(root, "src"); +const outputDir = path.join(root, "docs", "source", "api"); -// jsdoc config file -const conf = path.join(docs, "jsdoc-conf.json"); +const fileEntities = collectJsFiles(srcDir).map((file) => ({ + file, + entities: extractEntities(fs.readFileSync(file, "utf8"), file), +})); -// input and output paths -const inputFile = path.join(root, "/src/**/*.js"); -const outputDir = path.join(root, "/docs/source/api/"); +const ir = buildIR(fileEntities); +const publicNames = collectPublicExports(path.join(srcDir, "transformers.js")); -// get template data -const templateData = await jsdoc2md.getTemplateData({ - files: inputFile, - configure: conf, - "no-cache": true, -}); +clearExistingMarkdown(); -// reduce templateData to an array of module names -const moduleNames = templateData.reduce((moduleNames, identifier) => { - if (identifier.kind === "module") { - moduleNames.push(identifier.name); - } - return moduleNames; -}, []); +for (const mod of ir.modules) { + const output = renderModule(mod, ir, { publicNames }); + const outputPath = path.resolve(outputDir, `${mod.name}.md`); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, output); + const empty = output.trim().split("\n").length <= 2 ? " (empty — no public content)" : ""; + console.log(`wrote ${mod.name}.md${empty}`); +} -// Clear all existing .md files from output directory (recursively) -if (fs.existsSync(outputDir)) { - const existingFiles = fs.readdirSync(outputDir, { recursive: true }); - for (const file of existingFiles) { - if (file.endsWith(".md")) { - fs.unlinkSync(path.join(outputDir, file)); - } +function clearExistingMarkdown() { + if (!fs.existsSync(outputDir)) return; + for (const entry of fs.readdirSync(outputDir, { recursive: true })) { + if (entry.endsWith(".md")) fs.unlinkSync(path.join(outputDir, entry)); } } -// create a documentation file for each module -for (const moduleName of moduleNames) { - const template = `{{#module name="${moduleName}"}}{{>docs}}{{/module}}`; - console.log(`rendering ${moduleName}, template: ${template}`); - let output = await jsdoc2md.render({ - data: templateData, - template: template, - "heading-depth": 1, - "no-gfm": true, - "name-format": "backticks", - "no-cache": true, - separators: true, - configure: conf, - }); - - // Post-processing - output = output.replace(/(^#+\s.+)/gm, "$1\n"); // Add new line after each header - - // Remove tags from headers - output = output.replace(/^#+\s.+$/gm, (match) => match.replace(/<\/?code>/g, "")); - - // Replace all generated marker names with ids (for linking), and add group class - output = output.replace(/<\/a>/g, ''); - - // Unescape some of the characters which jsdoc2md escapes: - // TODO: May need to extend this list - output = output.replace(/\\([|_&*])/gm, "$1"); - - output = output.replaceAll("new exports.", "new "); - - const outputPath = path.resolve(outputDir, `${moduleName}.md`); - - console.log(`Writing to ${outputPath}`); - fs.mkdirSync(path.dirname(outputPath), { recursive: true }); - fs.writeFileSync(outputPath, output); +function collectJsFiles(dir) { + const out = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) out.push(...collectJsFiles(full)); + else if (entry.name.endsWith(".js")) out.push(full); + } + return out; } diff --git a/packages/transformers/docs/scripts/lib/exports.mjs b/packages/transformers/docs/scripts/lib/exports.mjs new file mode 100644 index 000000000..5089cd747 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/exports.mjs @@ -0,0 +1,42 @@ +// Resolve the symbol names the library exposes via `src/transformers.js`. +// Everything outside this set is treated as internal and excluded from docs. + +import fs from "node:fs"; +import path from "node:path"; + +const STAR_REEXPORT = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g; +const NAMED_EXPORT = /export\s*\{\s*([^}]+)\s*\}\s*(?:from\s*['"][^'"]+['"])?/g; +const DECL_EXPORT = /export\s+(?:default\s+)?(?:async\s+)?(?:class|function\s*\*?|const|let|var)\s+([A-Za-z_$][\w$]*)/g; + +export function collectPublicExports(entryFile) { + const names = new Set(); + walk(path.resolve(entryFile), new Set(), names); + return names; +} + +function walk(file, visited, names) { + if (visited.has(file) || !fs.existsSync(file)) return; + visited.add(file); + + const source = stripComments(fs.readFileSync(file, "utf8")); + const dir = path.dirname(file); + + for (const [, specifier] of source.matchAll(STAR_REEXPORT)) { + if (!specifier.startsWith(".")) continue; + walk(path.resolve(dir, specifier), visited, names); + } + + for (const [, list] of source.matchAll(NAMED_EXPORT)) { + for (const spec of list.split(",")) { + const parts = spec.trim().split(/\s+as\s+/); + const exported = (parts[1] ?? parts[0]).trim(); + if (exported) names.add(exported); + } + } + + for (const [, name] of source.matchAll(DECL_EXPORT)) names.add(name); +} + +function stripComments(source) { + return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/[^\n]*/g, "$1"); +} diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs new file mode 100644 index 000000000..f30c07826 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -0,0 +1,202 @@ +// Group per-file entities (from structure.mjs) into per-module IR. A module is +// one `@module ` declaration — its output is `docs/api/.md`. + +export function buildIR(fileEntities) { + const modules = new Map(); + + for (const { entities } of fileEntities) { + const mod = resolveModule(entities); + if (!mod) continue; + const entry = modules.get(mod.name) ?? newModule(mod.name); + if (!entry.description && mod.description) entry.description = mod.description; + entry.examples.push(...mod.examples); + ingest(entities, entry); + modules.set(mod.name, entry); + } + + return { modules: [...modules.values()], typedefIndex: buildTypedefIndex(modules) }; +} + +// Classes win (authoritative for a name). Typedefs fill gaps. Re-export +// aliases like `@typedef {import('x').Foo} Foo` register last so they can't +// shadow the canonical definition. +function buildTypedefIndex(modules) { + const index = new Map(); + for (const mod of modules.values()) { + for (const cls of mod.classes) index.set(cls.name, mod.name); + } + for (const mod of modules.values()) { + for (const cb of mod.callbacks) index.has(cb.name) || index.set(cb.name, mod.name); + for (const td of mod.typedefs) { + if (!isAliasTypedef(td)) index.has(td.name) || index.set(td.name, mod.name); + } + } + for (const mod of modules.values()) { + for (const td of mod.typedefs) { + if (isAliasTypedef(td)) index.has(td.name) || index.set(td.name, mod.name); + } + } + return index; +} + +function isAliasTypedef(td) { + if (!td.type) return false; + return td.type.replace(/import\(['"][^'"]+['"]\)\./g, "").trim() === td.name; +} + +function resolveModule(entities) { + const moduleTag = entities.module?.tags.find((t) => t.tag === "module"); + if (!moduleTag) return null; + const fileTag = entities.module.tags.find((t) => t.tag === "file"); + return { + name: moduleTag.name, + description: fileTag?.description || entities.module.description || "", + examples: entities.module.tags.filter((t) => t.tag === "example").map(normalizeExample), + }; +} + +function newModule(name) { + return { + name, + description: "", + examples: [], + classes: [], + functions: [], + typedefs: [], + callbacks: [], + constants: [], + }; +} + +function ingest(entities, mod) { + for (const block of entities.typedefs) { + if (!isPrivate(block)) collectTypedefsFromBlock(block, mod); + } + for (const cls of entities.classes) { + if (isPrivate(cls)) continue; + mod.classes.push({ + name: cls.name, + description: cls.description, + examples: tagsOf(cls, "example").map(normalizeExample), + skillExamples: tagsOf(cls, "skillExample").map((t) => t.task), + members: cls.members.filter((m) => !isPrivate(m)).map(buildMember), + }); + } + for (const fn of entities.functions) { + if (!isPrivate(fn)) mod.functions.push(buildCallable(fn)); + } + for (const v of entities.variables) { + if (isPrivate(v)) continue; + mod.constants.push({ + name: v.name, + type: typeOf(v), + description: v.description, + examples: tagsOf(v, "example").map(normalizeExample), + }); + } +} + +// A single @typedef/@callback block may declare multiple types; @property tags +// that follow each typedef attach to it, up to the next typedef/callback. +function collectTypedefsFromBlock(block, mod) { + let current = null; + let first = true; + const flush = () => { + if (!current) return; + if (current.kind === "typedef") mod.typedefs.push(current); + else mod.callbacks.push(current); + current = null; + }; + for (const tag of block.tags) { + if (tag.tag === "typedef") { + flush(); + current = { + kind: "typedef", + name: tag.name, + type: tag.type, + description: tag.description || (first ? block.description : ""), + properties: [], + }; + first = false; + } else if (tag.tag === "callback") { + flush(); + current = { + kind: "callback", + name: tag.name, + description: block.description, + params: tagsOf(block, "param").map(normalizeParam), + returns: pickReturns(block), + }; + first = false; + } else if ((tag.tag === "property" || tag.tag === "prop") && current?.kind === "typedef") { + current.properties.push(normalizeParam(tag)); + } + } + flush(); +} + +function buildMember(m) { + if (m.kind === "method") return { ...buildCallable(m), kind: "method" }; + return { + kind: m.kind, + name: m.name, + description: m.description, + type: typeOf(m), + defaultValue: m.tags.find((t) => t.tag === "default")?.value ?? m.initializer ?? null, + deprecated: m.tags.some((t) => t.tag === "deprecated"), + }; +} + +function buildCallable(fn) { + return { + name: fn.name, + description: fn.description, + params: tagsOf(fn, "param").map(normalizeParam), + returns: pickReturns(fn), + throws: tagsOf(fn, "throws").map((t) => ({ type: t.type, description: t.description })), + examples: tagsOf(fn, "example").map(normalizeExample), + skillExamples: tagsOf(fn, "skillExample").map((t) => t.task), + deprecated: fn.tags.some((t) => t.tag === "deprecated"), + }; +} + +function tagsOf(entity, name) { + return entity.tags.filter((t) => t.tag === name); +} + +function typeOf(entity) { + return entity.tags.find((t) => t.tag === "type")?.type ?? null; +} + +function pickReturns(entity) { + const r = entity.tags.find((t) => t.tag === "returns"); + return r ? { type: r.type, description: r.description || "" } : null; +} + +function normalizeParam(tag) { + return { + name: tag.name, + type: tag.type, + optional: !!tag.optional, + defaultValue: tag.defaultValue ?? null, + description: tag.description || "", + }; +} + +const FENCE_BLOCK = /```(\w+)?\n([\s\S]*?)\n```/; +const EXAMPLE_PREFIX = /^(?:\*\*Example[^*]*\*\*|Example)[:\s]*/; + +function normalizeExample(tag) { + const body = tag.body || ""; + const m = body.match(FENCE_BLOCK); + if (!m) return { title: "", language: "javascript", code: body.trim() }; + return { + title: body.slice(0, m.index).trim().replace(EXAMPLE_PREFIX, "").trim(), + language: m[1] || "javascript", + code: m[2].trim(), + }; +} + +function isPrivate(entity) { + return entity.tags?.some((t) => t.tag === "private"); +} diff --git a/packages/transformers/docs/scripts/lib/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs new file mode 100644 index 000000000..777b92262 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -0,0 +1,150 @@ +// JSDoc tag parser. Given a raw `/** ... */` comment, returns +// `{ description, tags }`. The TS-compiler pass in structure.mjs decides +// which comments attach to which AST node; this module only parses content. + +const TAG_START = /^@[A-Za-z]/; +const FENCE = /^```/; +const IDENT_PAT = /[A-Za-z_$][\w$]*/.source; +const DOTTED_PAT = /[A-Za-z_$][\w$.]*/.source; + +export function parseJsDoc(raw) { + if (!raw) return null; + const segments = splitIntoSegments(stripCommentMarkers(raw).split("\n")); + return { + description: segments[0].trim(), + tags: segments.slice(1).map(parseTag), + }; +} + +function stripCommentMarkers(raw) { + return raw + .replace(/^\/\*\*\s*\n?/, "") + .replace(/\s*\*\/$/, "") + .split("\n") + .map((line) => line.replace(/^\s*\* ?/, "")) + .join("\n") + .trimEnd(); +} + +// First segment is the free-form description; each subsequent segment starts +// with `@tag` at column 0. Tags inside fenced code stay with their parent. +function splitIntoSegments(lines) { + const segments = [""]; + let inFence = false; + for (const line of lines) { + if (FENCE.test(line.trim())) inFence = !inFence; + if (!inFence && TAG_START.test(line)) { + segments.push(line); + } else { + const i = segments.length - 1; + segments[i] += segments[i] ? "\n" + line : line; + } + } + return segments; +} + +function parseTag(raw) { + const nameMatch = raw.match(/^@([A-Za-z]+)/); + if (!nameMatch) return { tag: "unknown", raw }; + const tag = nameMatch[1]; + let rest = raw.slice(nameMatch[0].length).replace(/^[ \t]+/, ""); + + let type = null; + if (rest.startsWith("{")) { + const extracted = extractBalancedBraces(rest, 0); + if (extracted) { + type = extracted.content.trim(); + rest = rest.slice(extracted.endIndex).replace(/^[ \t]+/, ""); + } + } + + switch (tag) { + case "param": + case "property": + case "prop": + return { tag, type, ...parseNameAndRest(rest) }; + case "return": + case "returns": + return { tag: "returns", type, description: trimDash(rest) }; + case "throws": + return { tag, type, description: trimDash(rest) }; + case "typedef": { + const m = rest.match(new RegExp(`^(${IDENT_PAT})\\s*([\\s\\S]*)$`)); + return { tag, type, name: m?.[1] ?? null, description: m?.[2].trim() ?? "" }; + } + case "callback": + return { tag, name: rest.trim().split(/\s+/)[0] || null }; + case "template": + return { tag, type, name: rest.trim() }; + case "type": + return { tag, type }; + case "default": + return { tag, value: rest.trim() }; + case "module": + return { tag, name: rest.trim() }; + case "file": + case "fileoverview": + return { tag: "file", description: rest.trim() }; + case "see": + return { tag, description: rest.trim() }; + case "example": + return { tag, body: rest }; + case "skillExample": + return { tag, task: rest.trim() }; + case "skillCategory": + return { tag, slug: rest.trim() }; + case "doctest": + return { tag }; + default: + return { tag, type, description: rest.trim() }; + } +} + +// Parse `[name=default] description` or `name description`. +function parseNameAndRest(rest) { + if (rest.startsWith("[")) { + const close = findMatchingBracket(rest, 0); + if (close !== -1) { + const inner = rest.slice(1, close); + const eq = inner.indexOf("="); + return { + name: eq >= 0 ? inner.slice(0, eq).trim() : inner.trim(), + defaultValue: eq >= 0 ? inner.slice(eq + 1).trim() : null, + optional: true, + description: trimDash(rest.slice(close + 1)), + }; + } + } + const m = rest.match(new RegExp(`^(${DOTTED_PAT})\\s*([\\s\\S]*)$`)); + return { + name: m?.[1] ?? null, + defaultValue: null, + optional: false, + description: trimDash(m?.[2] ?? rest), + }; +} + +export function extractBalancedBraces(text, start) { + if (text[start] !== "{") return null; + let depth = 1; + let i = start + 1; + while (i < text.length && depth > 0) { + if (text[i] === "{") depth++; + else if (text[i] === "}") depth--; + i++; + } + return depth === 0 ? { content: text.slice(start + 1, i - 1), endIndex: i } : null; +} + +function findMatchingBracket(text, start) { + let depth = 0; + for (let i = start; i < text.length; i++) { + if (text[i] === "[") depth++; + else if (text[i] === "]" && --depth === 0) return i; + } + return -1; +} + +function trimDash(text) { + return text.replace(/^[ \t]*-?\s*/, "").trim(); +} diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs new file mode 100644 index 000000000..96d6b1280 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -0,0 +1,345 @@ +// Render a module's IR into readable markdown. Principles: +// - Prefer typedef names over their expanded TS structure +// - Never emit raw HTML tables or `<code>` escaping +// - Filter to the library's public export surface + +export function renderModule(mod, ir, opts = {}) { + const ctx = { typedefIndex: ir.typedefIndex, moduleName: mod.name }; + const publicNames = opts.publicNames ?? null; + + const classes = filterPublic(mod.classes, publicNames); + const functions = filterPublic(mod.functions, publicNames); + const constants = dedupeByName(filterPublic(mod.constants, publicNames)); + + const out = []; + out.push(`# ${mod.name}`, ""); + if (mod.description) out.push(mod.description.trim(), ""); + for (const ex of mod.examples) out.push(...renderExample(ex)); + + if (classes.length) { + out.push("## Classes", ""); + for (const cls of classes) out.push(...renderClass(cls, ctx)); + } + if (functions.length) { + out.push("## Functions", ""); + for (const fn of functions) out.push(...renderFunction(fn, ctx, 3)); + } + if (constants.length) { + out.push("## Constants", ""); + for (const c of constants) out.push(...renderConstant(c, ctx)); + } + if (mod.typedefs.length) { + const rendered = mod.typedefs.flatMap((td) => renderTypedef(td, ctx)); + if (rendered.length) out.push("## Type Definitions", "", ...rendered); + } + if (mod.callbacks.length) { + out.push("## Callbacks", ""); + for (const cb of mod.callbacks) out.push(...renderCallback(cb, ctx)); + } + + return ( + expandInlineLinks(out.join("\n")) + .replace(/\n{3,}/g, "\n\n") + .trimEnd() + "\n" + ); +} + +function filterPublic(items, publicNames) { + return publicNames ? items.filter((it) => publicNames.has(it.name)) : items; +} + +function dedupeByName(items) { + const seen = new Set(); + return items.filter((it) => (seen.has(it.name) ? false : seen.add(it.name))); +} + +function renderExample(ex) { + const lines = []; + if (ex.title) lines.push(`**Example:** ${ex.title}`); + lines.push("```" + ex.language, ex.code, "```", ""); + return lines; +} + +// `{@link url}` / `{@link url Text}` / `{@link Symbol}` -> markdown. +function expandInlineLinks(text) { + return text.replace(/\{@link\s+([^}\s]+)(?:\s+([^}]+))?\}/g, (_, target, label) => { + const displayed = (label ?? target).trim(); + return /^https?:\/\//.test(target) ? `[${displayed}](${target})` : `\`${displayed}\``; + }); +} + +// ---------- classes, functions, callbacks ---------- + +function renderClass(cls, ctx) { + const lines = [`### ${cls.name}`, ""]; + if (cls.description) lines.push(cls.description.trim(), ""); + for (const ex of cls.examples) lines.push(...renderExample(ex)); + + for (const m of cls.members) { + if (m.kind === "method") { + if (!shouldRenderMethod(m)) continue; + lines.push(...renderFunction(m, ctx, 4, cls.name)); + } else { + lines.push(...renderField(m, ctx, cls.name)); + } + } + return lines; +} + +function renderField(f, ctx, parent) { + const type = f.type ? ` : ${renderType(f.type, ctx)}` : ""; + const lines = [`#### \`${parent}.${f.name}\`${type}`, ""]; + if (f.deprecated) lines.push("> **Deprecated**", ""); + if (f.description) lines.push(f.description.trim(), ""); + if (f.defaultValue != null) lines.push(`**Default:** \`${f.defaultValue}\``, ""); + return lines; +} + +// Internal-looking (`_`-prefixed) methods need prose to earn a spot — bare +// param/return signatures are usually implementation details. +function shouldRenderMethod(m) { + if (m.name.startsWith("_") && !m.description) return false; + return m.description || m.params?.length || m.returns?.description || m.returns?.type || m.examples?.length || m.throws?.length; +} + +function renderFunction(fn, ctx, depth, parent = null) { + const lines = [`${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; + if (fn.deprecated) lines.push("> **Deprecated**", ""); + if (fn.description) lines.push(fn.description.trim(), ""); + if (fn.params?.length) { + lines.push("**Parameters**", "", ...renderParamList(fn.params, ctx), ""); + } + if (fn.returns?.type || fn.returns?.description) { + const type = fn.returns.type ? renderType(fn.returns.type, ctx) : ""; + const desc = fn.returns.description ? ` — ${fn.returns.description.trim()}` : ""; + lines.push(`**Returns:** ${type}${desc}`, ""); + } + if (fn.throws?.length) { + lines.push("**Throws**", ""); + for (const t of fn.throws) { + const type = t.type ? `\`${prettifyTypeString(t.type)}\`` : ""; + const sep = type && t.description ? " — " : ""; + lines.push(`- ${type}${sep}${t.description?.trim() ?? ""}`); + } + lines.push(""); + } + for (const ex of fn.examples) lines.push(...renderExample(ex)); + return lines; +} + +function signature(fn, parent) { + const params = (fn.params || []) + .filter((p) => p.name && !p.name.includes(".")) + .map((p) => (p.optional ? `[${p.name}]` : p.name)) + .join(", "); + const owner = parent ? `${parent}.` : ""; + return `\`${owner}${fn.name}(${params})\``; +} + +function renderCallback(cb, ctx) { + const lines = [`### ${cb.name}`, ""]; + if (cb.description) lines.push(cb.description.trim(), ""); + if (cb.params?.length) { + lines.push("**Parameters**", "", ...renderParamList(cb.params, ctx), ""); + } + if (cb.returns?.type || cb.returns?.description) { + const type = cb.returns.type ? renderType(cb.returns.type, ctx) : ""; + const desc = cb.returns.description ? ` — ${cb.returns.description}` : ""; + lines.push(`**Returns:** ${type}${desc}`, ""); + } + return lines; +} + +// ---------- parameter lists ---------- + +// Nested params (`foo.bar`) fold under their parent as sub-bullets. +function renderParamList(params, ctx) { + const roots = []; + const byName = new Map(); + for (const p of params) { + if (!p.name) continue; + const node = { ...p, children: [] }; + const dot = p.name.lastIndexOf("."); + const parent = dot >= 0 ? byName.get(p.name.slice(0, dot)) : null; + if (parent) parent.children.push(node); + else roots.push(node); + byName.set(p.name, node); + } + return roots.flatMap((p) => renderParamNode(p, ctx, 0)); +} + +function renderParamNode(p, ctx, indent) { + const pad = " ".repeat(indent); + const contPad = " ".repeat(indent + 1); + const type = p.type ? ` (${renderType(p.type, ctx)})` : ""; + const opt = p.optional ? " _optional_" : ""; + const def = p.defaultValue != null ? ` — defaults to \`${p.defaultValue}\`` : ""; + const desc = p.description ? ` — ${indentContinuations(p.description.trim(), contPad)}` : ""; + const line = `${pad}- \`${simpleName(p.name)}\`${type}${opt}${def}${desc}`; + return [line, ...p.children.flatMap((c) => renderParamNode(c, ctx, indent + 1))]; +} + +// Indent every line after the first so multi-line descriptions (including +// bulleted continuations) render inside their parent list item. +function indentContinuations(text, pad) { + const lines = text.split("\n"); + if (lines.length === 1) return text; + return ( + lines[0] + + "\n" + + lines + .slice(1) + .map((l) => pad + l.replace(/^\s+/, "")) + .join("\n") + ); +} + +function simpleName(name) { + const dot = name.lastIndexOf("."); + return dot >= 0 ? name.slice(dot + 1) : name; +} + +// ---------- constants & typedefs ---------- + +function renderConstant(c, ctx) { + const type = c.type ? ` : ${renderType(c.type, ctx)}` : ""; + const lines = [`### \`${c.name}\`${type}`, ""]; + if (c.description) lines.push(c.description.trim(), ""); + for (const ex of c.examples) lines.push(...renderExample(ex)); + return lines; +} + +function renderTypedef(td, ctx) { + const displayed = td.type ? renderType(td.type, { ...ctx, selfName: td.name }) : ""; + const isSelfReference = displayed === `\`${td.name}\``; + + // A typedef whose only job is to re-export a name (`@typedef {import('x').Foo} Foo`) + // adds no information — the canonical page is linked from wherever the name appears. + if (!td.description && !td.properties?.length && isSelfReference) return []; + + const lines = [`### ${td.name}`, ""]; + if (td.description) lines.push(td.description.trim(), ""); + if (displayed && displayed.length < 120 && !displayed.startsWith("`{") && !isSelfReference) { + lines.push(`_Type:_ ${displayed}`, ""); + } + if (td.properties?.length) { + lines.push("**Properties**", "", ...renderParamList(td.properties, ctx), ""); + } + return lines; +} + +// ---------- type rendering ---------- + +const GENERIC_WRAPPERS = /^(Promise|Array|Record|Map|Set|Iterable|AsyncIterable|Partial|Readonly)<(.+)>$/; +const SIMPLE_NAME = new RegExp(`^[A-Za-z_$][\\w$.]*$`); +const INDEXED_NAME = /^([A-Za-z_$][\w$.]*)\[[^\]]+\]$/; + +// Turn a raw JSDoc type string into readable markdown. Prefers author-written +// names over expanded TS structures; unknown gnarly types become `object`. +export function renderType(raw, ctx, opts = {}) { + const pretty = prettifyTypeString(raw); + if (opts.noLink) return `\`${pretty}\``; + + const parts = splitTopLevel(pretty, "|"); + if (parts.length > 1) return parts.map((p) => renderType(p.trim(), ctx)).join(" | "); + + if (SIMPLE_NAME.test(pretty)) return linkIfKnown(pretty, ctx) ?? `\`${pretty}\``; + + const indexed = pretty.match(INDEXED_NAME); + if (indexed) { + const linked = linkIfKnown(indexed[1], ctx); + if (linked) return linked; + } + + const wrapper = pretty.match(GENERIC_WRAPPERS); + if (wrapper) { + const innerParts = splitTopLevel(wrapper[2], ","); + const rendered = innerParts.map((p) => renderType(p.trim(), ctx)).join(", "); + return `\`${wrapper[1]}\`<${rendered}>`; + } + + return `\`${pretty}\``; +} + +function linkIfKnown(name, ctx) { + if (!ctx.typedefIndex?.has(name) || name === ctx.selfName) return null; + const anchor = name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); + return `[\`${name}\`](./${ctx.typedefIndex.get(name)}.md#${anchor})`; +} + +// Split `text` on `sep`, ignoring separators inside brackets, braces, parens, +// angle brackets, or string literals. +function splitTopLevel(text, sep) { + const out = []; + let depth = 0; + let inStr = null; + let buf = ""; + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + buf += ch; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + buf += ch; + } else if ("<({[".includes(ch)) { + depth++; + buf += ch; + } else if (">)}]".includes(ch)) { + depth--; + buf += ch; + } else if (depth === 0 && ch === sep) { + out.push(buf); + buf = ""; + } else { + buf += ch; + } + } + if (buf) out.push(buf); + return out.length > 1 ? out : [text]; +} + +// Strip noisy TS constructs from a type string without rewriting structure. +// Anything too complex to read inline collapses to its outermost wrapper. +export function prettifyTypeString(raw) { + if (!raw) return ""; + let s = raw.trim(); + + s = stripLeading(s, "<", ">"); // (...) + s = s.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); + s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); + + if (s.length > 120 || isGnarly(s)) { + const outer = s.match(/^([A-Za-z_$][\w$.]*)(?:<|\s|$)/); + return outer ? outer[1] : "object"; + } + return s.replace(/\s+/g, " ").trim(); +} + +function stripLeading(s, open, close) { + if (s[0] !== open) return s; + let depth = 1; + let i = 1; + while (i < s.length && depth > 0) { + if (s[i] === open) depth++; + else if (s[i] === close) depth--; + i++; + } + return depth === 0 ? s.slice(i).trim() : s; +} + +// Conditional types (`A extends B ? X : Y`), mapped types (`{ [K in ...] }`), +// and `infer` are unreadable inline. +function isGnarly(s) { + if (/\b(?:infer|extends)\b/.test(s)) return true; + let angle = 0; + for (let i = 0; i < s.length; i++) { + if (s[i] === "<") angle++; + else if (s[i] === ">") angle = Math.max(0, angle - 1); + else if (angle === 0 && (s[i] === "?" || (s[i] === "[" && s[i + 1] === "K"))) return true; + } + return false; +} diff --git a/packages/transformers/docs/scripts/lib/structure.mjs b/packages/transformers/docs/scripts/lib/structure.mjs new file mode 100644 index 000000000..615043cea --- /dev/null +++ b/packages/transformers/docs/scripts/lib/structure.mjs @@ -0,0 +1,99 @@ +// Walk a JS file with the TypeScript compiler and produce the structured +// entities (module header, classes with members, free functions, top-level +// constants, typedefs) that the IR layer groups into per-module docs. + +import ts from "typescript"; +import { parseJsDoc } from "./parse.mjs"; + +const FILE_LEVEL_TAGS = new Set(["module", "file", "typedef", "callback"]); + +export function extractEntities(source, file) { + const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const entities = { module: null, classes: [], functions: [], variables: [], typedefs: [] }; + + // File-level blocks (`@module` / `@file` / standalone `@typedef` / `@callback`) + // can't be discovered via node-attached JSDoc — TS attaches them to the next + // statement. Scan the raw source and classify by tag. + for (const m of source.matchAll(/\/\*\*[\s\S]*?\*\//g)) { + if (isInsideLineComment(source, m.index)) continue; + const parsed = parseJsDoc(m[0]); + if (!parsed) continue; + const tags = new Set(parsed.tags.map((t) => t.tag)); + if (tags.has("module") || tags.has("file")) entities.module ??= parsed; + if (tags.has("typedef") || tags.has("callback")) entities.typedefs.push(parsed); + } + + ts.forEachChild(sf, (node) => { + if (ts.isClassDeclaration(node) && node.name) { + entities.classes.push(buildClassEntity(node, sf)); + } else if (ts.isFunctionDeclaration(node) && node.name) { + const parsed = parseNodeDoc(node); + if (parsed && !hasFileLevelTag(parsed)) { + entities.functions.push({ name: node.name.text, ...parsed }); + } + } else if (ts.isVariableStatement(node)) { + const parsed = parseNodeDoc(node); + if (!parsed || hasFileLevelTag(parsed)) return; + for (const decl of node.declarationList.declarations) { + if (ts.isIdentifier(decl.name)) entities.variables.push({ name: decl.name.text, ...parsed }); + } + } + }); + + return entities; +} + +function buildClassEntity(node, sf) { + const parsed = parseNodeDoc(node) ?? { description: "", tags: [] }; + const members = []; + for (const m of node.members) { + const member = buildMemberEntity(m, sf); + if (member) members.push(member); + } + return { name: node.name.text, ...parsed, members }; +} + +function buildMemberEntity(node, sf) { + const parsed = parseNodeDoc(node); + if (!parsed) return null; + + if (ts.isConstructorDeclaration(node)) { + return { kind: "method", name: "constructor", ...parsed }; + } + if (ts.isMethodDeclaration(node) && node.name) { + return { kind: "method", name: node.name.getText(sf), ...parsed }; + } + if (ts.isPropertyDeclaration(node) && node.name) { + return { + kind: "field", + name: node.name.getText(sf), + initializer: node.initializer?.getText(sf) ?? null, + ...parsed, + }; + } + if ((ts.isGetAccessor(node) || ts.isSetAccessor(node)) && node.name) { + return { + kind: ts.isGetAccessor(node) ? "getter" : "setter", + name: node.name.getText(sf), + ...parsed, + }; + } + return null; +} + +// `ts.getJSDocCommentsAndTags` may return multiple blocks; the nearest/latest +// one is the canonical JSDoc for the node. +function parseNodeDoc(node) { + const docs = ts.getJSDocCommentsAndTags(node).filter((d) => ts.isJSDoc(d)); + if (!docs.length) return null; + return parseJsDoc(docs[docs.length - 1].getFullText()); +} + +function hasFileLevelTag(parsed) { + return parsed.tags.some((t) => FILE_LEVEL_TAGS.has(t.tag)); +} + +function isInsideLineComment(source, offset) { + const lineStart = source.lastIndexOf("\n", offset - 1) + 1; + return /^\s*\/\//.test(source.slice(lineStart, offset)); +} diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 8ea694b11..d20dcbfa0 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -68,7 +68,6 @@ "esbuild": "^0.27.2", "jest": "^30.2.0", "jest-environment-node": "^30.2.0", - "jsdoc-to-markdown": "^9.1.3", "typescript": "5.9.3" }, "files": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bea34792e..f8a8e38f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,9 +51,6 @@ importers: jest-environment-node: specifier: ^30.2.0 version: 30.2.0 - jsdoc-to-markdown: - specifier: ^9.1.3 - version: 9.1.3 typescript: specifier: 5.9.3 version: 5.9.3 @@ -644,25 +641,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jsdoc/salty@0.2.9': - resolution: {integrity: sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==} - engines: {node: '>=v12.0.0'} - '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -737,15 +718,6 @@ packages: '@types/jest@30.0.0': resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} - '@types/linkify-it@5.0.0': - resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - - '@types/markdown-it@14.1.2': - resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} - - '@types/mdurl@2.0.0': - resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - '@types/node@24.10.9': resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} @@ -894,13 +866,6 @@ packages: argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-back@6.2.2: - resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} - engines: {node: '>=12.17'} - babel-jest@30.2.0: resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -933,9 +898,6 @@ packages: resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==} hasBin: true - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. @@ -961,15 +923,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - cache-point@3.0.1: - resolution: {integrity: sha512-itTIMLEKbh6Dw5DruXbxAgcyLnh/oPGVLBfTPqBOftASxHe8bAeXy7JkO4F0LvHqht7XqP5O/09h5UcHS2w0FA==} - engines: {node: '>=12.17'} - peerDependencies: - '@75lb/nature': latest - peerDependenciesMeta: - '@75lb/nature': - optional: true - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -985,14 +938,6 @@ packages: caniuse-lite@1.0.30001766: resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} - catharsis@0.9.0: - resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} - engines: {node: '>= 10'} - - chalk-template@0.4.0: - resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} - engines: {node: '>=12'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1026,29 +971,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - command-line-args@6.0.1: - resolution: {integrity: sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==} - engines: {node: '>=12.20'} - peerDependencies: - '@75lb/nature': latest - peerDependenciesMeta: - '@75lb/nature': - optional: true - - command-line-usage@7.0.3: - resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} - engines: {node: '>=12.20.0'} - - common-sequence@3.0.0: - resolution: {integrity: sha512-g/CgSYk93y+a1IKm50tKl7kaT/OjjTYVQlEbUlt/49ZLV1mcKpUU7iyDiqTAeLdb4QDtQfq3ako8y8v//fzrWQ==} - engines: {node: '>=12.17'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - config-master@3.1.0: - resolution: {integrity: sha512-n7LBL1zBzYdTpF1mx5DNcZnZn05CWIdsdvtPL4MosvqbBUK3Rq6VWEtGUuF3Y0s9/CIhMejezqlSkP6TnCJ/9g==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1056,10 +981,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - current-module-paths@1.1.3: - resolution: {integrity: sha512-7AH+ZTRKikdK4s1RmY0l6067UD/NZc7p3zZVZxvmnH80G31kr0y0W0E6ibYM4IS01MEm8DiC5FnTcgcgkbFHoA==} - engines: {node: '>=12.17'} - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1100,15 +1021,6 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - dmd@7.1.1: - resolution: {integrity: sha512-Ap2HP6iuOek7eShReDLr9jluNJm9RMZESlt29H/Xs1qrVMkcS9X6m5h1mBC56WMxNiSo0wvjGICmZlYUSFjwZQ==} - engines: {node: '>=12.17'} - peerDependencies: - '@75lb/nature': latest - peerDependenciesMeta: - '@75lb/nature': - optional: true - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1125,10 +1037,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -1177,41 +1085,16 @@ packages: resolution: {integrity: sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - file-set@5.3.0: - resolution: {integrity: sha512-FKCxdjLX0J6zqTWdT0RXIxNF/n7MyXXnsSUp0syLEOCKdexvPZ02lNNv2a+gpK9E3hzUYF3+eFZe32ci7goNUg==} - engines: {node: '>=12.17'} - peerDependencies: - '@75lb/nature': latest - peerDependenciesMeta: - '@75lb/nature': - optional: true - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-replace@5.0.2: - resolution: {integrity: sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==} - engines: {node: '>=14'} - peerDependencies: - '@75lb/nature': latest - peerDependenciesMeta: - '@75lb/nature': - optional: true - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1247,10 +1130,6 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true @@ -1277,11 +1156,6 @@ packages: guid-typescript@1.0.9: resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1315,10 +1189,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1327,10 +1197,6 @@ packages: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1500,37 +1366,6 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - js2xmlparser@4.0.2: - resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} - - jsdoc-api@9.3.5: - resolution: {integrity: sha512-TQwh1jA8xtCkIbVwm/XA3vDRAa5JjydyKx1cC413Sh3WohDFxcMdwKSvn4LOsq2xWyAmOU/VnSChTQf6EF0R8g==} - engines: {node: '>=12.17'} - peerDependencies: - '@75lb/nature': latest - peerDependenciesMeta: - '@75lb/nature': - optional: true - - jsdoc-parse@6.2.5: - resolution: {integrity: sha512-8JaSNjPLr2IuEY4Das1KM6Z4oLHZYUnjRrr27hKSa78Cj0i5Lur3DzNnCkz+DfrKBDoljGMoWOiBVQbtUZJBPw==} - engines: {node: '>=12'} - - jsdoc-to-markdown@9.1.3: - resolution: {integrity: sha512-i9wi+6WHX0WKziv0ar88T8h7OmxA0LWdQaV23nY6uQyKvdUPzVt0o6YAaOceFuKRF5Rvlju5w/KnZBfdpDAlnw==} - engines: {node: '>=12.17'} - hasBin: true - peerDependencies: - '@75lb/nature': latest - peerDependenciesMeta: - '@75lb/nature': - optional: true - - jsdoc@4.0.5: - resolution: {integrity: sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==} - engines: {node: '>=12.0.0'} - hasBin: true - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1547,9 +1382,6 @@ packages: engines: {node: '>=6'} hasBin: true - klaw@3.0.0: - resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} - leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -1557,19 +1389,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -1586,35 +1409,13 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - markdown-it-anchor@8.6.7: - resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} - peerDependencies: - '@types/markdown-it': '*' - markdown-it: '*' - - markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} - hasBin: true - - marked@4.3.0: - resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} - engines: {node: '>= 12'} - hasBin: true - matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} - mdurl@2.0.0: - resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1630,18 +1431,10 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1653,9 +1446,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -1674,10 +1464,6 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object-to-spawn-args@2.0.1: - resolution: {integrity: sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==} - engines: {node: '>=8.0.0'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1772,16 +1558,9 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} - punycode.js@2.3.1: - resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} - engines: {node: '>=6'} - pure-rand@7.0.1: resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -1789,9 +1568,6 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - requizzle@0.2.4: - resolution: {integrity: sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==} - resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -1800,17 +1576,10 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -1850,15 +1619,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - sort-array@5.1.1: - resolution: {integrity: sha512-EltS7AIsNlAFIM9cayrgKrM6XP94ATWwXP4LCL4IQbvbYhELSt2hZTrixg+AaQwnWFs/JGJgqU3rxMcNNWxGAA==} - engines: {node: '>=12.17'} - peerDependencies: - '@75lb/nature': ^0.1.1 - peerDependenciesMeta: - '@75lb/nature': - optional: true - source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -1920,10 +1680,6 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - table-layout@4.1.1: - resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} - engines: {node: '>=12.17'} - test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -1955,21 +1711,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - typical@7.3.0: - resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} - engines: {node: '>=12.17'} - - uc.micro@2.1.0: - resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - - underscore@1.13.7: - resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} - undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -1986,14 +1727,6 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} - walk-back@2.0.1: - resolution: {integrity: sha512-Nb6GvBR8UWX1D+Le+xUq0+Q1kFmRBIWVrfLnQAOmcpEzA9oAxwJ9gIr36t9TWYfzvWRvuMtjHiVsJYEkXWaTAQ==} - engines: {node: '>=0.10.0'} - - walk-back@5.1.1: - resolution: {integrity: sha512-e/FRLDVdZQWFrAzU6Hdvpm7D7m2ina833gIKLptQykRK49mmCYHLHq7UqjPDbxbKLZkTkW1rFqbengdE3sLfdw==} - engines: {node: '>=12.17'} - walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -2002,13 +1735,6 @@ packages: engines: {node: '>= 8'} hasBin: true - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wordwrapjs@5.1.1: - resolution: {integrity: sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==} - engines: {node: '>=12.17'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -2024,9 +1750,6 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - xmlcreate@2.0.4: - resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2648,10 +2371,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jsdoc/salty@0.2.9': - dependencies: - lodash: 4.17.23 - '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.1 @@ -2659,18 +2378,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - '@pkgjs/parseargs@0.11.0': optional: true @@ -2750,15 +2457,6 @@ snapshots: expect: 30.2.0 pretty-format: 30.2.0 - '@types/linkify-it@5.0.0': {} - - '@types/markdown-it@14.1.2': - dependencies: - '@types/linkify-it': 5.0.0 - '@types/mdurl': 2.0.0 - - '@types/mdurl@2.0.0': {} - '@types/node@24.10.9': dependencies: undici-types: 7.16.0 @@ -2861,10 +2559,6 @@ snapshots: dependencies: sprintf-js: 1.0.3 - argparse@2.0.1: {} - - array-back@6.2.2: {} - babel-jest@30.2.0(@babel/core@7.28.6): dependencies: '@babel/core': 7.28.6 @@ -2921,8 +2615,6 @@ snapshots: baseline-browser-mapping@2.9.18: {} - bluebird@3.7.2: {} - boolean@3.2.0: {} brace-expansion@1.1.12: @@ -2952,10 +2644,6 @@ snapshots: buffer-from@1.1.2: {} - cache-point@3.0.1: - dependencies: - array-back: 6.2.2 - callsites@3.1.0: {} camelcase@5.3.1: {} @@ -2964,14 +2652,6 @@ snapshots: caniuse-lite@1.0.30001766: {} - catharsis@0.9.0: - dependencies: - lodash: 4.17.23 - - chalk-template@0.4.0: - dependencies: - chalk: 4.1.2 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2999,28 +2679,8 @@ snapshots: color-name@1.1.4: {} - command-line-args@6.0.1: - dependencies: - array-back: 6.2.2 - find-replace: 5.0.2 - lodash.camelcase: 4.3.0 - typical: 7.3.0 - - command-line-usage@7.0.3: - dependencies: - array-back: 6.2.2 - chalk-template: 0.4.0 - table-layout: 4.1.1 - typical: 7.3.0 - - common-sequence@3.0.0: {} - concat-map@0.0.1: {} - config-master@3.1.0: - dependencies: - walk-back: 2.0.1 - convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -3029,8 +2689,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - current-module-paths@1.1.3: {} - debug@4.4.3: dependencies: ms: 2.1.3 @@ -3057,16 +2715,6 @@ snapshots: detect-node@2.1.0: {} - dmd@7.1.1: - dependencies: - array-back: 6.2.2 - cache-point: 3.0.1 - common-sequence: 3.0.0 - file-set: 5.3.0 - handlebars: 4.7.8 - marked: 4.3.0 - walk-back: 5.1.1 - eastasianwidth@0.2.0: {} electron-to-chromium@1.5.279: {} @@ -3077,8 +2725,6 @@ snapshots: emoji-regex@9.2.2: {} - entities@4.5.0: {} - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -3149,35 +2795,16 @@ snapshots: jest-mock: 30.2.0 jest-util: 30.2.0 - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - fb-watchman@2.0.2: dependencies: bser: 2.1.1 - file-set@5.3.0: - dependencies: - array-back: 6.2.2 - fast-glob: 3.3.3 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - find-replace@5.0.2: {} - find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -3203,10 +2830,6 @@ snapshots: get-stream@6.0.1: {} - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob@10.5.0: dependencies: foreground-child: 3.3.1 @@ -3245,15 +2868,6 @@ snapshots: guid-typescript@1.0.9: {} - handlebars@4.7.8: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -3280,16 +2894,10 @@ snapshots: is-arrayish@0.2.1: {} - is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} is-generator-fn@2.1.0: {} - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - is-number@7.0.0: {} is-stream@2.0.1: {} @@ -3651,57 +3259,6 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js2xmlparser@4.0.2: - dependencies: - xmlcreate: 2.0.4 - - jsdoc-api@9.3.5: - dependencies: - array-back: 6.2.2 - cache-point: 3.0.1 - current-module-paths: 1.1.3 - file-set: 5.3.0 - jsdoc: 4.0.5 - object-to-spawn-args: 2.0.1 - walk-back: 5.1.1 - - jsdoc-parse@6.2.5: - dependencies: - array-back: 6.2.2 - find-replace: 5.0.2 - sort-array: 5.1.1 - transitivePeerDependencies: - - '@75lb/nature' - - jsdoc-to-markdown@9.1.3: - dependencies: - array-back: 6.2.2 - command-line-args: 6.0.1 - command-line-usage: 7.0.3 - config-master: 3.1.0 - dmd: 7.1.1 - jsdoc-api: 9.3.5 - jsdoc-parse: 6.2.5 - walk-back: 5.1.1 - - jsdoc@4.0.5: - dependencies: - '@babel/parser': 7.28.6 - '@jsdoc/salty': 0.2.9 - '@types/markdown-it': 14.1.2 - bluebird: 3.7.2 - catharsis: 0.9.0 - escape-string-regexp: 2.0.0 - js2xmlparser: 4.0.2 - klaw: 3.0.0 - markdown-it: 14.1.0 - markdown-it-anchor: 8.6.7(@types/markdown-it@14.1.2)(markdown-it@14.1.0) - marked: 4.3.0 - mkdirp: 1.0.4 - requizzle: 0.2.4 - strip-json-comments: 3.1.1 - underscore: 1.13.7 - jsesc@3.1.0: {} json-parse-even-better-errors@2.3.1: {} @@ -3710,26 +3267,14 @@ snapshots: json5@2.2.3: {} - klaw@3.0.0: - dependencies: - graceful-fs: 4.2.11 - leven@3.1.0: {} lines-and-columns@1.2.4: {} - linkify-it@5.0.0: - dependencies: - uc.micro: 2.1.0 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 - lodash.camelcase@4.3.0: {} - - lodash@4.17.23: {} - long@5.3.2: {} lru-cache@10.4.3: {} @@ -3746,32 +3291,12 @@ snapshots: dependencies: tmpl: 1.0.5 - markdown-it-anchor@8.6.7(@types/markdown-it@14.1.2)(markdown-it@14.1.0): - dependencies: - '@types/markdown-it': 14.1.2 - markdown-it: 14.1.0 - - markdown-it@14.1.0: - dependencies: - argparse: 2.0.1 - entities: 4.5.0 - linkify-it: 5.0.0 - mdurl: 2.0.0 - punycode.js: 2.3.1 - uc.micro: 2.1.0 - - marked@4.3.0: {} - matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 - mdurl@2.0.0: {} - merge-stream@2.0.0: {} - merge2@1.4.1: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -3787,20 +3312,14 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist@1.2.8: {} - minipass@7.1.2: {} - mkdirp@1.0.4: {} - ms@2.1.3: {} napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} - neo-async@2.6.2: {} - node-int64@0.4.0: {} node-releases@2.0.27: {} @@ -3813,8 +3332,6 @@ snapshots: object-keys@1.1.1: {} - object-to-spawn-args@2.0.1: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -3913,28 +3430,18 @@ snapshots: '@types/node': 24.10.9 long: 5.3.2 - punycode.js@2.3.1: {} - pure-rand@7.0.1: {} - queue-microtask@1.2.3: {} - react-is@18.3.1: {} require-directory@2.1.1: {} - requizzle@0.2.4: - dependencies: - lodash: 4.17.23 - resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 resolve-from@5.0.0: {} - reusify@1.1.0: {} - roarr@2.15.4: dependencies: boolean: 3.2.0 @@ -3944,10 +3451,6 @@ snapshots: semver-compare: 1.0.0 sprintf-js: 1.1.3 - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - semver-compare@1.0.0: {} semver@6.3.1: {} @@ -4001,11 +3504,6 @@ snapshots: slash@3.0.0: {} - sort-array@5.1.1: - dependencies: - array-back: 6.2.2 - typical: 7.3.0 - source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -4064,11 +3562,6 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - table-layout@4.1.1: - dependencies: - array-back: 6.2.2 - wordwrapjs: 5.1.1 - test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 @@ -4092,15 +3585,6 @@ snapshots: typescript@5.9.3: {} - typical@7.3.0: {} - - uc.micro@2.1.0: {} - - uglify-js@3.19.3: - optional: true - - underscore@1.13.7: {} - undici-types@7.16.0: {} unrs-resolver@1.11.1: @@ -4139,10 +3623,6 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - walk-back@2.0.1: {} - - walk-back@5.1.1: {} - walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -4151,10 +3631,6 @@ snapshots: dependencies: isexe: 2.0.0 - wordwrap@1.0.0: {} - - wordwrapjs@5.1.1: {} - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -4174,8 +3650,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - xmlcreate@2.0.4: {} - y18n@5.0.8: {} yallist@3.1.1: {} From 6e7647fd530b56c2561fc88461fbfff579fb5869 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:28:05 -0400 Subject: [PATCH 008/103] document NoBadWordsLogitsProcessor --- packages/transformers/src/generation/logits_process.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/transformers/src/generation/logits_process.js b/packages/transformers/src/generation/logits_process.js index 647a30806..f3a308cd8 100644 --- a/packages/transformers/src/generation/logits_process.js +++ b/packages/transformers/src/generation/logits_process.js @@ -557,6 +557,9 @@ export class MinNewTokensLengthLogitsProcessor extends LogitsProcessor { } } +/** + * LogitsProcessor that enforces that specified sequences will never be selected. + */ export class NoBadWordsLogitsProcessor extends LogitsProcessor { /** * Create a `NoBadWordsLogitsProcessor`. From ee47f0d9fe82697d079d3f90e43a2d17059832d2 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:29:55 -0400 Subject: [PATCH 009/103] document StoppingCriteriaList --- packages/transformers/src/generation/stopping_criteria.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/transformers/src/generation/stopping_criteria.js b/packages/transformers/src/generation/stopping_criteria.js index 6451f6cd1..82b4b20d3 100644 --- a/packages/transformers/src/generation/stopping_criteria.js +++ b/packages/transformers/src/generation/stopping_criteria.js @@ -25,6 +25,8 @@ export class StoppingCriteria extends Callable { } } /** + * A list of `StoppingCriteria` that stops generation when any one of them returns `true`. + * Pass an instance via `stopping_criteria` to any generation call to combine multiple stop conditions. */ export class StoppingCriteriaList extends Callable { /** From 9096f55cbd502283060cadcafbf6233f905a050a Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:50:30 -0400 Subject: [PATCH 010/103] Update audio.js --- packages/transformers/src/utils/audio.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/transformers/src/utils/audio.js b/packages/transformers/src/utils/audio.js index c2af80753..418da9818 100644 --- a/packages/transformers/src/utils/audio.js +++ b/packages/transformers/src/utils/audio.js @@ -824,6 +824,16 @@ function writeString(view, offset, string) { } } +/** + * An audio buffer paired with its sampling rate. + * + * @example + * import { RawAudio } from '@huggingface/transformers'; + * const samples = new Float32Array(16000); // 1 second of silence @ 16 kHz + * const audio = new RawAudio(samples, 16000); + * const blob = audio.toBlob(); // WAV blob for upload or playback + * await audio.save('out.wav'); // Saves the audio as a WAV file named 'out.wav' + */ export class RawAudio { /** * Create a new `RawAudio` object. From cd3df7c9870ee9d130d135df49a9feaebbffdf90 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:51:59 -0400 Subject: [PATCH 011/103] Update random.js --- packages/transformers/src/utils/random.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/transformers/src/utils/random.js b/packages/transformers/src/utils/random.js index 76319beae..2656319fe 100644 --- a/packages/transformers/src/utils/random.js +++ b/packages/transformers/src/utils/random.js @@ -105,6 +105,7 @@ export class Random { * then applies the standard MT19937 tempering transform. * * @returns {number} A random integer in the range [0, 2^32 - 1]. + * @private */ _int32() { const mt = this._mt; @@ -210,8 +211,20 @@ function _weightedIndexWith(randomFn, weights) { return weights.length - 1; // floating-point guard } -// Global default instance: mirrors the module-level functions in Python's `random` module. const _default = new Random(); + +/** + * The default PRNG instance, mirroring Python's module-level `random` functions. + * It shares a single global state, so if you want to generate independent sequences, + * construct your own `new random.Random(seed)` instead. + * + * @example + * import { random } from '@huggingface/transformers'; + * random.seed(42); + * random.random(); // 0.6394267984578837 (matches Python) + * random.gauss(0, 1); // normal-distributed value + * random.choices(['a', 'b'], [3, 1]); // weighted pick + */ export const random = Object.freeze({ Random, seed: _default.seed.bind(_default), From d0512757cc56a0d44ebfff8bc5798e7a23e34c6f Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:54:28 -0400 Subject: [PATCH 012/103] unprivate _int32 --- packages/transformers/src/utils/random.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/transformers/src/utils/random.js b/packages/transformers/src/utils/random.js index 2656319fe..0c822a5c1 100644 --- a/packages/transformers/src/utils/random.js +++ b/packages/transformers/src/utils/random.js @@ -105,7 +105,6 @@ export class Random { * then applies the standard MT19937 tempering transform. * * @returns {number} A random integer in the range [0, 2^32 - 1]. - * @private */ _int32() { const mt = this._mt; From 8e3584e0e8a7e27c1459987d85b03489c9afa2a8 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:55:08 -0400 Subject: [PATCH 013/103] Update streamers.js --- packages/transformers/src/generation/streamers.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/transformers/src/generation/streamers.js b/packages/transformers/src/generation/streamers.js index 813dd959b..d12d37b48 100644 --- a/packages/transformers/src/generation/streamers.js +++ b/packages/transformers/src/generation/streamers.js @@ -15,6 +15,9 @@ const is_chinese_char = (cp) => (cp >= 0xf900 && cp <= 0xfaff) || (cp >= 0x2f800 && cp <= 0x2fa1f); +/** + * Abstract base class for output streamers. + */ export class BaseStreamer { /** * Function that is called by `.generate()` to push new tokens From f8a8c420e7897c983da1f497ee405ba71d478917 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:56:03 -0400 Subject: [PATCH 014/103] Update image.js --- packages/transformers/src/utils/image.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index d7d8c7fcd..2c18035e7 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -71,6 +71,14 @@ const CONTENT_TYPE_MAP = new Map([ ['gif', 'image/gif'], ]); +/** + * Represents an image stored as a raw pixel buffer. + * + * @example + * import { RawImage } from '@huggingface/transformers'; + * const image = await RawImage.read('https://example.com/photo.jpg'); + * console.log(image.width, image.height, image.channels); + */ export class RawImage { /** * Create a new `RawImage` object. From 84abd06b9b8e159064cdb6be9000c603e6dd21a1 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:56:11 -0400 Subject: [PATCH 015/103] Update tensor.js --- packages/transformers/src/utils/tensor.js | 25 +++++++++-------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 335cd25eb..c49550b25 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -22,6 +22,15 @@ import { random } from './random.js'; * @typedef {import('./maths.js').AnyTypedArray | any[]} DataArray */ +/** + * A typed multi-dimensional array. + * + * @example + * import { Tensor } from '@huggingface/transformers'; + * const tensor = new Tensor('float32', [1, 2, 3, 4, 5, 6], [2, 3]); + * tensor.dims; // [2, 3] + * tensor.tolist(); // [[1, 2, 3], [4, 5, 6]] + */ export class Tensor { /** * Dimensions of the tensor. @@ -136,6 +145,7 @@ export class Tensor { * Index into a Tensor object. * @param {number} index The index to access. * @returns {Tensor} The data at the specified index. + * @private */ _getitem(index) { const [iterLength, ...iterDims] = this.dims; @@ -150,21 +160,6 @@ export class Tensor { } } - /** - * @param {number|bigint} item The item to search for in the tensor - * @returns {number} The index of the first occurrence of item in the tensor data. - */ - indexOf(item) { - const this_data = this.data; - for (let index = 0; index < this_data.length; ++index) { - // Note: == instead of === so we can match Ints with BigInts - if (this_data[index] == item) { - return index; - } - } - return -1; - } - /** * @param {number} index * @param {number} iterSize From 94a51f9f478b92eeba48e6dc9e10b4e1decfc878 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:58:04 -0400 Subject: [PATCH 016/103] Update _toctree.yml --- packages/transformers/docs/source/_toctree.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/transformers/docs/source/_toctree.yml b/packages/transformers/docs/source/_toctree.yml index 76d779249..8a4abb51a 100644 --- a/packages/transformers/docs/source/_toctree.yml +++ b/packages/transformers/docs/source/_toctree.yml @@ -65,8 +65,6 @@ title: Configuration - local: api/generation/logits_process title: Logits Processors - - local: api/generation/logits_sampler - title: Logits Samplers - local: api/generation/stopping_criteria title: Stopping Criteria - local: api/generation/streamers @@ -88,8 +86,6 @@ title: Tensor - local: api/utils/maths title: Maths - - local: api/utils/logger - title: Logger - local: api/utils/random title: Random title: Utilities From 9de1380f2813f9faa844b731e865b69aef2f1aec Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:13:43 -0400 Subject: [PATCH 017/103] use load_image/load_audio where possible --- .../transformers/src/feature_extraction_utils.js | 2 +- .../transformers/src/models/auto/processing_auto.js | 6 +++++- .../transformers/src/models/clap/modeling_clap.js | 4 ++-- .../transformers/src/models/clip/modeling_clip.js | 8 ++++---- .../src/models/clipseg/modeling_clipseg.js | 4 ++-- .../src/models/donut_swin/modeling_donut_swin.js | 8 ++++---- packages/transformers/src/models/dpt/modeling_dpt.js | 4 ++-- .../transformers/src/models/glpn/modeling_glpn.js | 4 ++-- .../src/models/hubert/modeling_hubert.js | 4 ++-- .../src/models/pyannote/modeling_pyannote.js | 4 ++-- packages/transformers/src/models/sam/modeling_sam.js | 4 ++-- .../src/models/siglip/modeling_siglip.js | 8 ++++---- .../src/models/wav2vec2/modeling_wav2vec2.js | 4 ++-- .../transformers/src/models/wavlm/modeling_wavlm.js | 12 ++++++------ packages/transformers/src/processing_utils.js | 4 ++-- 15 files changed, 42 insertions(+), 38 deletions(-) diff --git a/packages/transformers/src/feature_extraction_utils.js b/packages/transformers/src/feature_extraction_utils.js index eed106497..97f033ed5 100644 --- a/packages/transformers/src/feature_extraction_utils.js +++ b/packages/transformers/src/feature_extraction_utils.js @@ -47,7 +47,7 @@ export function validate_audio_inputs(audio, feature_extractor) { if (!(audio instanceof Float32Array || audio instanceof Float64Array)) { throw new Error( `${feature_extractor} expects input to be a Float32Array or a Float64Array, but got ${audio?.constructor?.name ?? typeof audio} instead. ` + - `If using the feature extractor directly, remember to use \`read_audio(url, sampling_rate)\` to obtain the raw audio data of the file/url.`, + `If using the feature extractor directly, remember to use \`load_audio(url, sampling_rate)\` to obtain the raw audio data of the file/url.`, ); } } diff --git a/packages/transformers/src/models/auto/processing_auto.js b/packages/transformers/src/models/auto/processing_auto.js index 2e105cc7c..1ae3279a3 100644 --- a/packages/transformers/src/models/auto/processing_auto.js +++ b/packages/transformers/src/models/auto/processing_auto.js @@ -20,13 +20,17 @@ import * as AllFeatureExtractors from '../feature_extractors.js'; * * **Example:** Load a processor using `from_pretrained`. * ```javascript + * import { AutoProcessor } from '@huggingface/transformers'; + * * let processor = await AutoProcessor.from_pretrained('openai/whisper-tiny.en'); * ``` * * **Example:** Run an image through a processor. * ```javascript + * import { AutoProcessor, load_image } from '@huggingface/transformers'; + * * let processor = await AutoProcessor.from_pretrained('Xenova/clip-vit-base-patch16'); - * let image = await RawImage.read('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); + * let image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); * let image_inputs = await processor(image); * // { * // "pixel_values": { diff --git a/packages/transformers/src/models/clap/modeling_clap.js b/packages/transformers/src/models/clap/modeling_clap.js index dacbe9665..9f13cf562 100644 --- a/packages/transformers/src/models/clap/modeling_clap.js +++ b/packages/transformers/src/models/clap/modeling_clap.js @@ -47,14 +47,14 @@ export class ClapTextModelWithProjection extends ClapPreTrainedModel { * **Example:** Compute audio embeddings with `ClapAudioModelWithProjection`. * * ```javascript - * import { AutoProcessor, ClapAudioModelWithProjection, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, ClapAudioModelWithProjection, load_audio } from '@huggingface/transformers'; * * // Load processor and audio model * const processor = await AutoProcessor.from_pretrained('Xenova/clap-htsat-unfused'); * const audio_model = await ClapAudioModelWithProjection.from_pretrained('Xenova/clap-htsat-unfused'); * * // Read audio and run processor - * const audio = await read_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/cat_meow.wav'); + * const audio = await load_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/cat_meow.wav'); * const audio_inputs = await processor(audio); * * // Compute embeddings diff --git a/packages/transformers/src/models/clip/modeling_clip.js b/packages/transformers/src/models/clip/modeling_clip.js index a4806d9b5..3841873ac 100644 --- a/packages/transformers/src/models/clip/modeling_clip.js +++ b/packages/transformers/src/models/clip/modeling_clip.js @@ -8,7 +8,7 @@ export class CLIPPreTrainedModel extends PreTrainedModel {} * **Example:** Perform zero-shot image classification with a `CLIPModel`. * * ```javascript - * import { AutoTokenizer, AutoProcessor, CLIPModel, RawImage } from '@huggingface/transformers'; + * import { AutoTokenizer, AutoProcessor, CLIPModel, load_image } from '@huggingface/transformers'; * * // Load tokenizer, processor, and model * const tokenizer = await AutoTokenizer.from_pretrained('Xenova/clip-vit-base-patch16'); @@ -20,7 +20,7 @@ export class CLIPPreTrainedModel extends PreTrainedModel {} * const text_inputs = tokenizer(texts, { padding: true, truncation: true }); * * // Read image and run processor - * const image = await RawImage.read('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); + * const image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); * const image_inputs = await processor(image); * * // Run model with both text and pixel inputs @@ -118,14 +118,14 @@ export class CLIPVisionModel extends CLIPPreTrainedModel { * **Example:** Compute vision embeddings with `CLIPVisionModelWithProjection`. * * ```javascript - * import { AutoProcessor, CLIPVisionModelWithProjection, RawImage } from '@huggingface/transformers'; + * import { AutoProcessor, CLIPVisionModelWithProjection, load_image } from '@huggingface/transformers'; * * // Load processor and vision model * const processor = await AutoProcessor.from_pretrained('Xenova/clip-vit-base-patch16'); * const vision_model = await CLIPVisionModelWithProjection.from_pretrained('Xenova/clip-vit-base-patch16'); * * // Read image and run processor - * const image = await RawImage.read('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); + * const image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); * const image_inputs = await processor(image); * * // Compute embeddings diff --git a/packages/transformers/src/models/clipseg/modeling_clipseg.js b/packages/transformers/src/models/clipseg/modeling_clipseg.js index 7993dc0b1..9f4cad2f2 100644 --- a/packages/transformers/src/models/clipseg/modeling_clipseg.js +++ b/packages/transformers/src/models/clipseg/modeling_clipseg.js @@ -10,7 +10,7 @@ export class CLIPSegModel extends CLIPSegPreTrainedModel {} * **Example:** Perform zero-shot image segmentation with a `CLIPSegForImageSegmentation` model. * * ```javascript - * import { AutoTokenizer, AutoProcessor, CLIPSegForImageSegmentation, RawImage } from '@huggingface/transformers'; + * import { AutoTokenizer, AutoProcessor, CLIPSegForImageSegmentation, RawImage, load_image } from '@huggingface/transformers'; * * // Load tokenizer, processor, and model * const tokenizer = await AutoTokenizer.from_pretrained('Xenova/clipseg-rd64-refined'); @@ -22,7 +22,7 @@ export class CLIPSegModel extends CLIPSegPreTrainedModel {} * const text_inputs = tokenizer(texts, { padding: true, truncation: true }); * * // Read image and run processor - * const image = await RawImage.read('https://github.com/timojl/clipseg/blob/master/example_image.jpg?raw=true'); + * const image = await load_image('https://github.com/timojl/clipseg/blob/master/example_image.jpg?raw=true'); * const image_inputs = await processor(image); * * // Run model with both text and pixel inputs diff --git a/packages/transformers/src/models/donut_swin/modeling_donut_swin.js b/packages/transformers/src/models/donut_swin/modeling_donut_swin.js index b8cdad008..b43088ea0 100644 --- a/packages/transformers/src/models/donut_swin/modeling_donut_swin.js +++ b/packages/transformers/src/models/donut_swin/modeling_donut_swin.js @@ -8,7 +8,7 @@ export class DonutSwinPreTrainedModel extends PreTrainedModel {} * **Example:** Step-by-step Document Parsing. * * ```javascript - * import { AutoProcessor, AutoTokenizer, AutoModelForVision2Seq, RawImage } from '@huggingface/transformers'; + * import { AutoProcessor, AutoTokenizer, AutoModelForVision2Seq, load_image } from '@huggingface/transformers'; * * // Choose model to use * const model_id = 'Xenova/donut-base-finetuned-cord-v2'; @@ -16,7 +16,7 @@ export class DonutSwinPreTrainedModel extends PreTrainedModel {} * // Prepare image inputs * const processor = await AutoProcessor.from_pretrained(model_id); * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/receipt.png'; - * const image = await RawImage.read(url); + * const image = await load_image(url); * const image_inputs = await processor(image); * * // Prepare decoder inputs @@ -43,7 +43,7 @@ export class DonutSwinPreTrainedModel extends PreTrainedModel {} * **Example:** Step-by-step Document Visual Question Answering (DocVQA) * * ```javascript - * import { AutoProcessor, AutoTokenizer, AutoModelForVision2Seq, RawImage } from '@huggingface/transformers'; + * import { AutoProcessor, AutoTokenizer, AutoModelForVision2Seq, load_image } from '@huggingface/transformers'; * * // Choose model to use * const model_id = 'Xenova/donut-base-finetuned-docvqa'; @@ -51,7 +51,7 @@ export class DonutSwinPreTrainedModel extends PreTrainedModel {} * // Prepare image inputs * const processor = await AutoProcessor.from_pretrained(model_id); * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/invoice.png'; - * const image = await RawImage.read(url); + * const image = await load_image(url); * const image_inputs = await processor(image); * * // Prepare decoder inputs diff --git a/packages/transformers/src/models/dpt/modeling_dpt.js b/packages/transformers/src/models/dpt/modeling_dpt.js index 76770fe48..28995887f 100644 --- a/packages/transformers/src/models/dpt/modeling_dpt.js +++ b/packages/transformers/src/models/dpt/modeling_dpt.js @@ -12,7 +12,7 @@ export class DPTModel extends DPTPreTrainedModel {} * * **Example:** Depth estimation w/ `Xenova/dpt-hybrid-midas`. * ```javascript - * import { DPTForDepthEstimation, AutoProcessor, RawImage, interpolate_4d } from '@huggingface/transformers'; + * import { DPTForDepthEstimation, AutoProcessor, RawImage, load_image, interpolate_4d } from '@huggingface/transformers'; * * // Load model and processor * const model_id = 'Xenova/dpt-hybrid-midas'; @@ -21,7 +21,7 @@ export class DPTModel extends DPTPreTrainedModel {} * * // Load image from URL * const url = 'http://images.cocodataset.org/val2017/000000039769.jpg'; - * const image = await RawImage.read(url); + * const image = await load_image(url); * * // Prepare image for the model * const inputs = await processor(image); diff --git a/packages/transformers/src/models/glpn/modeling_glpn.js b/packages/transformers/src/models/glpn/modeling_glpn.js index 2849bd3e1..2cb171797 100644 --- a/packages/transformers/src/models/glpn/modeling_glpn.js +++ b/packages/transformers/src/models/glpn/modeling_glpn.js @@ -8,7 +8,7 @@ export class GLPNPreTrainedModel extends PreTrainedModel {} export class GLPNModel extends GLPNPreTrainedModel {} /** - * import { GLPNForDepthEstimation, AutoProcessor, RawImage, interpolate_4d } from '@huggingface/transformers'; + * import { GLPNForDepthEstimation, AutoProcessor, RawImage, load_image, interpolate_4d } from '@huggingface/transformers'; * * // Load model and processor * const model_id = 'Xenova/glpn-kitti'; @@ -17,7 +17,7 @@ export class GLPNModel extends GLPNPreTrainedModel {} * * // Load image from URL * const url = 'http://images.cocodataset.org/val2017/000000039769.jpg'; - * const image = await RawImage.read(url); + * const image = await load_image(url); * * // Prepare image for the model * const inputs = await processor(image); diff --git a/packages/transformers/src/models/hubert/modeling_hubert.js b/packages/transformers/src/models/hubert/modeling_hubert.js index c2284dc55..d47f76804 100644 --- a/packages/transformers/src/models/hubert/modeling_hubert.js +++ b/packages/transformers/src/models/hubert/modeling_hubert.js @@ -11,11 +11,11 @@ export class HubertPreTrainedModel extends PreTrainedModel {} * **Example:** Load and run a `HubertModel` for feature extraction. * * ```javascript - * import { AutoProcessor, AutoModel, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, AutoModel, load_audio } from '@huggingface/transformers'; * * // Read and preprocess audio * const processor = await AutoProcessor.from_pretrained('Xenova/hubert-base-ls960'); - * const audio = await read_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav', 16000); + * const audio = await load_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav', 16000); * const inputs = await processor(audio); * * // Load and run model with inputs diff --git a/packages/transformers/src/models/pyannote/modeling_pyannote.js b/packages/transformers/src/models/pyannote/modeling_pyannote.js index fa5ecae0b..98a141d69 100644 --- a/packages/transformers/src/models/pyannote/modeling_pyannote.js +++ b/packages/transformers/src/models/pyannote/modeling_pyannote.js @@ -14,7 +14,7 @@ export class PyAnnoteModel extends PyAnnotePreTrainedModel {} * **Example:** Load and run a `PyAnnoteForAudioFrameClassification` for speaker diarization. * * ```javascript - * import { AutoProcessor, AutoModelForAudioFrameClassification, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, AutoModelForAudioFrameClassification, load_audio } from '@huggingface/transformers'; * * // Load model and processor * const model_id = 'onnx-community/pyannote-segmentation-3.0'; @@ -23,7 +23,7 @@ export class PyAnnoteModel extends PyAnnotePreTrainedModel {} * * // Read and preprocess audio * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/mlk.wav'; - * const audio = await read_audio(url, processor.feature_extractor.config.sampling_rate); + * const audio = await load_audio(url, processor.feature_extractor.config.sampling_rate); * const inputs = await processor(audio); * * // Run model with inputs diff --git a/packages/transformers/src/models/sam/modeling_sam.js b/packages/transformers/src/models/sam/modeling_sam.js index 4ec034032..5a7ae90c2 100644 --- a/packages/transformers/src/models/sam/modeling_sam.js +++ b/packages/transformers/src/models/sam/modeling_sam.js @@ -28,13 +28,13 @@ export class SamPreTrainedModel extends PreTrainedModel {} * * **Example:** Perform mask generation w/ `Xenova/sam-vit-base`. * ```javascript - * import { SamModel, AutoProcessor, RawImage } from '@huggingface/transformers'; + * import { SamModel, AutoProcessor, load_image } from '@huggingface/transformers'; * * const model = await SamModel.from_pretrained('Xenova/sam-vit-base'); * const processor = await AutoProcessor.from_pretrained('Xenova/sam-vit-base'); * * const img_url = 'https://huggingface.co/ybelkada/segment-anything/resolve/main/assets/car.png'; - * const raw_image = await RawImage.read(img_url); + * const raw_image = await load_image(img_url); * const input_points = [[[450, 600]]] // 2D localization of a window * * const inputs = await processor(raw_image, { input_points }); diff --git a/packages/transformers/src/models/siglip/modeling_siglip.js b/packages/transformers/src/models/siglip/modeling_siglip.js index 29d8457fb..5f5fefab7 100644 --- a/packages/transformers/src/models/siglip/modeling_siglip.js +++ b/packages/transformers/src/models/siglip/modeling_siglip.js @@ -9,7 +9,7 @@ export class SiglipPreTrainedModel extends PreTrainedModel {} * **Example:** Perform zero-shot image classification with a `SiglipModel`. * * ```javascript - * import { AutoTokenizer, AutoProcessor, SiglipModel, RawImage } from '@huggingface/transformers'; + * import { AutoTokenizer, AutoProcessor, SiglipModel, load_image } from '@huggingface/transformers'; * * // Load tokenizer, processor, and model * const tokenizer = await AutoTokenizer.from_pretrained('Xenova/siglip-base-patch16-224'); @@ -21,7 +21,7 @@ export class SiglipPreTrainedModel extends PreTrainedModel {} * const text_inputs = tokenizer(texts, { padding: 'max_length', truncation: true }); * * // Read image and run processor - * const image = await RawImage.read('http://images.cocodataset.org/val2017/000000039769.jpg'); + * const image = await load_image('http://images.cocodataset.org/val2017/000000039769.jpg'); * const image_inputs = await processor(image); * * // Run model with both text and pixel inputs @@ -91,14 +91,14 @@ export class SiglipTextModel extends SiglipPreTrainedModel { * **Example:** Compute vision embeddings with `SiglipVisionModel`. * * ```javascript - * import { AutoProcessor, SiglipVisionModel, RawImage } from '@huggingface/transformers'; + * import { AutoProcessor, SiglipVisionModel, load_image } from '@huggingface/transformers'; * * // Load processor and vision model * const processor = await AutoProcessor.from_pretrained('Xenova/siglip-base-patch16-224'); * const vision_model = await SiglipVisionModel.from_pretrained('Xenova/siglip-base-patch16-224'); * * // Read image and run processor - * const image = await RawImage.read('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); + * const image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); * const image_inputs = await processor(image); * * // Compute embeddings diff --git a/packages/transformers/src/models/wav2vec2/modeling_wav2vec2.js b/packages/transformers/src/models/wav2vec2/modeling_wav2vec2.js index fcd69cb82..a5fff3177 100644 --- a/packages/transformers/src/models/wav2vec2/modeling_wav2vec2.js +++ b/packages/transformers/src/models/wav2vec2/modeling_wav2vec2.js @@ -10,11 +10,11 @@ export class Wav2Vec2PreTrainedModel extends PreTrainedModel {} * **Example:** Load and run a `Wav2Vec2Model` for feature extraction. * * ```javascript - * import { AutoProcessor, AutoModel, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, AutoModel, load_audio } from '@huggingface/transformers'; * * // Read and preprocess audio * const processor = await AutoProcessor.from_pretrained('Xenova/mms-300m'); - * const audio = await read_audio('https://huggingface.co/datasets/Narsil/asr_dummy/resolve/main/mlk.flac', 16000); + * const audio = await load_audio('https://huggingface.co/datasets/Narsil/asr_dummy/resolve/main/mlk.flac', 16000); * const inputs = await processor(audio); * * // Run model with inputs diff --git a/packages/transformers/src/models/wavlm/modeling_wavlm.js b/packages/transformers/src/models/wavlm/modeling_wavlm.js index 2ec28f2f3..f0b17d952 100644 --- a/packages/transformers/src/models/wavlm/modeling_wavlm.js +++ b/packages/transformers/src/models/wavlm/modeling_wavlm.js @@ -29,11 +29,11 @@ export class WavLMPreTrainedModel extends PreTrainedModel {} * **Example:** Load and run a `WavLMModel` for feature extraction. * * ```javascript - * import { AutoProcessor, AutoModel, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, AutoModel, load_audio } from '@huggingface/transformers'; * * // Read and preprocess audio * const processor = await AutoProcessor.from_pretrained('Xenova/wavlm-base'); - * const audio = await read_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav', 16000); + * const audio = await load_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav', 16000); * const inputs = await processor(audio); * * // Run model with inputs @@ -84,12 +84,12 @@ export class WavLMForSequenceClassification extends WavLMPreTrainedModel { * * **Example:** Extract speaker embeddings with `WavLMForXVector`. * ```javascript - * import { AutoProcessor, AutoModel, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, AutoModel, load_audio } from '@huggingface/transformers'; * * // Read and preprocess audio * const processor = await AutoProcessor.from_pretrained('Xenova/wavlm-base-plus-sv'); * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; - * const audio = await read_audio(url, 16000); + * const audio = await load_audio(url, 16000); * const inputs = await processor(audio); * * // Run model with inputs @@ -127,12 +127,12 @@ export class WavLMForXVector extends WavLMPreTrainedModel { * * **Example:** Perform speaker diarization with `WavLMForAudioFrameClassification`. * ```javascript - * import { AutoProcessor, AutoModelForAudioFrameClassification, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, AutoModelForAudioFrameClassification, load_audio } from '@huggingface/transformers'; * * // Read and preprocess audio * const processor = await AutoProcessor.from_pretrained('Xenova/wavlm-base-plus-sd'); * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; - * const audio = await read_audio(url, 16000); + * const audio = await load_audio(url, 16000); * const inputs = await processor(audio); * * // Run model with inputs diff --git a/packages/transformers/src/processing_utils.js b/packages/transformers/src/processing_utils.js index b28be3e6a..1e2c49cb0 100644 --- a/packages/transformers/src/processing_utils.js +++ b/packages/transformers/src/processing_utils.js @@ -3,10 +3,10 @@ * * **Example:** Using a `WhisperProcessor` to prepare an audio input for a model. * ```javascript - * import { AutoProcessor, read_audio } from '@huggingface/transformers'; + * import { AutoProcessor, load_audio } from '@huggingface/transformers'; * * const processor = await AutoProcessor.from_pretrained('openai/whisper-tiny.en'); - * const audio = await read_audio('https://huggingface.co/datasets/Narsil/asr_dummy/resolve/main/mlk.flac', 16000); + * const audio = await load_audio('https://huggingface.co/datasets/Narsil/asr_dummy/resolve/main/mlk.flac', 16000); * const { input_features } = await processor(audio); * // Tensor { * // data: Float32Array(240000) [0.4752984642982483, 0.5597258806228638, 0.56434166431427, ...], From e1e44930a775c144ac079207f52eca9f187082ee Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:13:57 -0400 Subject: [PATCH 018/103] Update random.js --- packages/transformers/src/utils/random.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/transformers/src/utils/random.js b/packages/transformers/src/utils/random.js index 0c822a5c1..4b7966c5c 100644 --- a/packages/transformers/src/utils/random.js +++ b/packages/transformers/src/utils/random.js @@ -37,8 +37,10 @@ import { apis } from '../env.js'; * affect any other instance or the global helper functions. * * @example - * const rng1 = new Random(42); - * const rng2 = new Random(42); + * import { random } from '@huggingface/transformers'; + * + * const rng1 = new random.Random(42); + * const rng2 = new random.Random(42); * rng1.random() === rng2.random(); // true (same seed, independent state) */ export class Random { From d4ea25a4abfaf200988cb06d5574c1b7e3adf536 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:42:18 -0400 Subject: [PATCH 019/103] update model ids --- packages/transformers/src/models/auto/processing_auto.js | 2 +- packages/transformers/src/processing_utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/transformers/src/models/auto/processing_auto.js b/packages/transformers/src/models/auto/processing_auto.js index 1ae3279a3..33d41bd78 100644 --- a/packages/transformers/src/models/auto/processing_auto.js +++ b/packages/transformers/src/models/auto/processing_auto.js @@ -22,7 +22,7 @@ import * as AllFeatureExtractors from '../feature_extractors.js'; * ```javascript * import { AutoProcessor } from '@huggingface/transformers'; * - * let processor = await AutoProcessor.from_pretrained('openai/whisper-tiny.en'); + * let processor = await AutoProcessor.from_pretrained('onnx-community/whisper-tiny.en'); * ``` * * **Example:** Run an image through a processor. diff --git a/packages/transformers/src/processing_utils.js b/packages/transformers/src/processing_utils.js index 1e2c49cb0..91dc569b7 100644 --- a/packages/transformers/src/processing_utils.js +++ b/packages/transformers/src/processing_utils.js @@ -5,7 +5,7 @@ * ```javascript * import { AutoProcessor, load_audio } from '@huggingface/transformers'; * - * const processor = await AutoProcessor.from_pretrained('openai/whisper-tiny.en'); + * const processor = await AutoProcessor.from_pretrained('onnx-community/whisper-tiny.en'); * const audio = await load_audio('https://huggingface.co/datasets/Narsil/asr_dummy/resolve/main/mlk.flac', 16000); * const { input_features } = await processor(audio); * // Tensor { From 2fa2e74039e54cff8fdf1843c8321ea48e3d7507 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:53:47 -0400 Subject: [PATCH 020/103] Document all AutoModelXXX classes --- .../src/models/auto/modeling_auto.js | 174 +++++++++++++++++- 1 file changed, 168 insertions(+), 6 deletions(-) diff --git a/packages/transformers/src/models/auto/modeling_auto.js b/packages/transformers/src/models/auto/modeling_auto.js index 030c51acd..97c5d33a6 100644 --- a/packages/transformers/src/models/auto/modeling_auto.js +++ b/packages/transformers/src/models/auto/modeling_auto.js @@ -142,6 +142,8 @@ class PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModel } from '@huggingface/transformers'; + * * const model = await AutoModel.from_pretrained('Xenova/bert-base-uncased'); */ export class AutoModel extends PretrainedMixin { @@ -156,6 +158,8 @@ export class AutoModel extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForSequenceClassification } from '@huggingface/transformers'; + * * const model = await AutoModelForSequenceClassification.from_pretrained('Xenova/distilbert-base-uncased-finetuned-sst-2-english'); */ export class AutoModelForSequenceClassification extends PretrainedMixin { @@ -167,6 +171,8 @@ export class AutoModelForSequenceClassification extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForTokenClassification } from '@huggingface/transformers'; + * * const model = await AutoModelForTokenClassification.from_pretrained('Xenova/distilbert-base-multilingual-cased-ner-hrl'); */ export class AutoModelForTokenClassification extends PretrainedMixin { @@ -178,6 +184,8 @@ export class AutoModelForTokenClassification extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForSeq2SeqLM } from '@huggingface/transformers'; + * * const model = await AutoModelForSeq2SeqLM.from_pretrained('Xenova/t5-small'); */ export class AutoModelForSeq2SeqLM extends PretrainedMixin { @@ -189,7 +197,9 @@ export class AutoModelForSeq2SeqLM extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example - * const model = await AutoModelForSpeechSeq2Seq.from_pretrained('openai/whisper-tiny.en'); + * import { AutoModelForSpeechSeq2Seq } from '@huggingface/transformers'; + * + * const model = await AutoModelForSpeechSeq2Seq.from_pretrained('onnx-community/whisper-tiny.en'); */ export class AutoModelForSpeechSeq2Seq extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_SPEECH_SEQ_2_SEQ_MAPPING_NAMES]; @@ -200,7 +210,9 @@ export class AutoModelForSpeechSeq2Seq extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example - * const model = await AutoModelForTextToSpectrogram.from_pretrained('microsoft/speecht5_tts'); + * import { AutoModelForTextToSpectrogram } from '@huggingface/transformers'; + * + * const model = await AutoModelForTextToSpectrogram.from_pretrained('Xenova/speecht5_tts'); */ export class AutoModelForTextToSpectrogram extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_TEXT_TO_SPECTROGRAM_MAPPING_NAMES]; @@ -211,7 +223,9 @@ export class AutoModelForTextToSpectrogram extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example - * const model = await AutoModelForTextToSpectrogram.from_pretrained('facebook/mms-tts-eng'); + * import { AutoModelForTextToWaveform } from '@huggingface/transformers'; + * + * const model = await AutoModelForTextToWaveform.from_pretrained('Xenova/mms-tts-eng'); */ export class AutoModelForTextToWaveform extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_TEXT_TO_WAVEFORM_MAPPING_NAMES]; @@ -222,6 +236,8 @@ export class AutoModelForTextToWaveform extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForCausalLM } from '@huggingface/transformers'; + * * const model = await AutoModelForCausalLM.from_pretrained('Xenova/gpt2'); */ export class AutoModelForCausalLM extends PretrainedMixin { @@ -233,6 +249,8 @@ export class AutoModelForCausalLM extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForMaskedLM } from '@huggingface/transformers'; + * * const model = await AutoModelForMaskedLM.from_pretrained('Xenova/bert-base-uncased'); */ export class AutoModelForMaskedLM extends PretrainedMixin { @@ -244,6 +262,8 @@ export class AutoModelForMaskedLM extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForQuestionAnswering } from '@huggingface/transformers'; + * * const model = await AutoModelForQuestionAnswering.from_pretrained('Xenova/distilbert-base-cased-distilled-squad'); */ export class AutoModelForQuestionAnswering extends PretrainedMixin { @@ -255,6 +275,8 @@ export class AutoModelForQuestionAnswering extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForVision2Seq } from '@huggingface/transformers'; + * * const model = await AutoModelForVision2Seq.from_pretrained('Xenova/vit-gpt2-image-captioning'); */ export class AutoModelForVision2Seq extends PretrainedMixin { @@ -266,6 +288,8 @@ export class AutoModelForVision2Seq extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForImageClassification } from '@huggingface/transformers'; + * * const model = await AutoModelForImageClassification.from_pretrained('Xenova/vit-base-patch16-224'); */ export class AutoModelForImageClassification extends PretrainedMixin { @@ -277,6 +301,8 @@ export class AutoModelForImageClassification extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForImageSegmentation } from '@huggingface/transformers'; + * * const model = await AutoModelForImageSegmentation.from_pretrained('Xenova/detr-resnet-50-panoptic'); */ export class AutoModelForImageSegmentation extends PretrainedMixin { @@ -288,7 +314,9 @@ export class AutoModelForImageSegmentation extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example - * const model = await AutoModelForSemanticSegmentation.from_pretrained('nvidia/segformer-b3-finetuned-cityscapes-1024-1024'); + * import { AutoModelForSemanticSegmentation } from '@huggingface/transformers'; + * + * const model = await AutoModelForSemanticSegmentation.from_pretrained('Xenova/segformer-b0-finetuned-ade-512-512'); */ export class AutoModelForSemanticSegmentation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_SEMANTIC_SEGMENTATION_MAPPING_NAMES]; @@ -299,7 +327,9 @@ export class AutoModelForSemanticSegmentation extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example - * const model = await AutoModelForUniversalSegmentation.from_pretrained('hf-internal-testing/tiny-random-MaskFormerForInstanceSegmentation'); + * import { AutoModelForUniversalSegmentation } from '@huggingface/transformers'; + * + * const model = await AutoModelForUniversalSegmentation.from_pretrained('Xenova/detr-resnet-50-panoptic'); */ export class AutoModelForUniversalSegmentation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_UNIVERSAL_SEGMENTATION_MAPPING_NAMES]; @@ -310,12 +340,23 @@ export class AutoModelForUniversalSegmentation extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example + * import { AutoModelForObjectDetection } from '@huggingface/transformers'; + * * const model = await AutoModelForObjectDetection.from_pretrained('Xenova/detr-resnet-50'); */ export class AutoModelForObjectDetection extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_OBJECT_DETECTION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained zero-shot object detection models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForZeroShotObjectDetection } from '@huggingface/transformers'; + * + * const model = await AutoModelForZeroShotObjectDetection.from_pretrained('Xenova/owlvit-base-patch32'); + */ export class AutoModelForZeroShotObjectDetection extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_ZERO_SHOT_OBJECT_DETECTION_MAPPING_NAMES]; } @@ -325,60 +366,181 @@ export class AutoModelForZeroShotObjectDetection extends PretrainedMixin { * The chosen model class is determined by the type specified in the model config. * * @example - * const model = await AutoModelForMaskGeneration.from_pretrained('Xenova/sam-vit-base'); + * import { AutoModelForMaskGeneration } from '@huggingface/transformers'; + * + * const model = await AutoModelForMaskGeneration.from_pretrained('Xenova/slimsam-77-uniform'); */ export class AutoModelForMaskGeneration extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_MASK_GENERATION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained connectionist temporal classification (CTC) models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForCTC } from '@huggingface/transformers'; + * + * const model = await AutoModelForCTC.from_pretrained('Xenova/wav2vec2-base-960h'); + */ export class AutoModelForCTC extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_CTC_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained audio classification models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForAudioClassification } from '@huggingface/transformers'; + * + * const model = await AutoModelForAudioClassification.from_pretrained('Xenova/wav2vec2-base-superb-ks'); + */ export class AutoModelForAudioClassification extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_CLASSIFICATION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained speaker embedding models (X-Vector) with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForXVector } from '@huggingface/transformers'; + * + * const model = await AutoModelForXVector.from_pretrained('Xenova/wavlm-base-plus-sv'); + */ export class AutoModelForXVector extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_XVECTOR_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained audio frame (token) classification models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForAudioFrameClassification } from '@huggingface/transformers'; + * + * const model = await AutoModelForAudioFrameClassification.from_pretrained('onnx-community/pyannote-segmentation-3.0'); + */ export class AutoModelForAudioFrameClassification extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_FRAME_CLASSIFICATION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained document question answering models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForDocumentQuestionAnswering } from '@huggingface/transformers'; + * + * const model = await AutoModelForDocumentQuestionAnswering.from_pretrained('Xenova/donut-base-finetuned-docvqa'); + */ export class AutoModelForDocumentQuestionAnswering extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_DOCUMENT_QUESTION_ANSWERING_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained image matting models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForImageMatting } from '@huggingface/transformers'; + * + * const model = await AutoModelForImageMatting.from_pretrained('Xenova/vitmatte-small-composition-1k'); + */ export class AutoModelForImageMatting extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_MATTING_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained image-to-image models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForImageToImage } from '@huggingface/transformers'; + * + * const model = await AutoModelForImageToImage.from_pretrained('Xenova/swin2SR-classical-sr-x2-64'); + */ export class AutoModelForImageToImage extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_TO_IMAGE_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained depth estimation models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForDepthEstimation } from '@huggingface/transformers'; + * + * const model = await AutoModelForDepthEstimation.from_pretrained('onnx-community/depth-anything-v2-small-ONNX'); + */ export class AutoModelForDepthEstimation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_DEPTH_ESTIMATION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained surface-normal estimation models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForNormalEstimation } from '@huggingface/transformers'; + * + * const model = await AutoModelForNormalEstimation.from_pretrained('onnx-community/sapiens-normal-0.3b'); + */ export class AutoModelForNormalEstimation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_NORMAL_ESTIMATION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained pose estimation models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForPoseEstimation } from '@huggingface/transformers'; + * + * const model = await AutoModelForPoseEstimation.from_pretrained('onnx-community/vitpose-base-simple'); + */ export class AutoModelForPoseEstimation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_POSE_ESTIMATION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained image feature extraction models with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForImageFeatureExtraction } from '@huggingface/transformers'; + * + * const model = await AutoModelForImageFeatureExtraction.from_pretrained('onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX'); + */ export class AutoModelForImageFeatureExtraction extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_FEATURE_EXTRACTION_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained vision-language models that map images and text to text + * (image+text-to-text) with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForImageTextToText } from '@huggingface/transformers'; + * + * const model = await AutoModelForImageTextToText.from_pretrained('onnx-community/LFM2.5-VL-450M-ONNX'); + */ export class AutoModelForImageTextToText extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES]; } +/** + * Helper class which is used to instantiate pretrained audio-language models that map audio and text to text + * (audio+text-to-text) with the `from_pretrained` function. + * The chosen model class is determined by the type specified in the model config. + * + * @example + * import { AutoModelForAudioTextToText } from '@huggingface/transformers'; + * + * const model = await AutoModelForAudioTextToText.from_pretrained('onnx-community/Voxtral-Mini-4B-Realtime-2602-ONNX'); + */ export class AutoModelForAudioTextToText extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_TEXT_TO_TEXT_MAPPING_NAMES]; } From bd5e68de4b8179ed89013b89268c69d92f78567b Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:56:58 -0400 Subject: [PATCH 021/103] update jsdoc --- .../src/models/auto/feature_extraction_auto.js | 13 +++++++++++++ .../src/models/auto/image_processing_auto.js | 11 +++++++++++ packages/transformers/src/utils/image.js | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/transformers/src/models/auto/feature_extraction_auto.js b/packages/transformers/src/models/auto/feature_extraction_auto.js index b69afcd7b..673448b7d 100644 --- a/packages/transformers/src/models/auto/feature_extraction_auto.js +++ b/packages/transformers/src/models/auto/feature_extraction_auto.js @@ -7,6 +7,19 @@ import { getModelJSON } from '../../utils/hub.js'; import { FeatureExtractor } from '../../feature_extraction_utils.js'; import * as AllFeatureExtractors from '../feature_extractors.js'; +/** + * Helper class which is used to instantiate pretrained feature extractors with the `from_pretrained` function. + * The chosen feature extractor class is determined by the type specified in the preprocessor config. + * Typically used for audio models. + * + * @example + * import { AutoFeatureExtractor, load_audio } from '@huggingface/transformers'; + * + * const extractor = await AutoFeatureExtractor.from_pretrained('onnx-community/whisper-tiny.en'); + * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; + * const audio = await load_audio(url, 16000); + * const { input_features } = await extractor(audio); + */ export class AutoFeatureExtractor { /** @type {typeof FeatureExtractor.from_pretrained} */ static async from_pretrained(pretrained_model_name_or_path, options = {}) { diff --git a/packages/transformers/src/models/auto/image_processing_auto.js b/packages/transformers/src/models/auto/image_processing_auto.js index 70ee60b3b..c588c328f 100644 --- a/packages/transformers/src/models/auto/image_processing_auto.js +++ b/packages/transformers/src/models/auto/image_processing_auto.js @@ -8,6 +8,17 @@ import * as AllImageProcessors from '../image_processors.js'; import { GITHUB_ISSUE_URL, IMAGE_PROCESSOR_NAME } from '../../utils/constants.js'; import { logger } from '../../utils/logger.js'; +/** + * Helper class which is used to instantiate pretrained image processors with the `from_pretrained` function. + * The chosen image processor class is determined by the type specified in the preprocessor config. + * + * @example + * import { AutoImageProcessor, load_image } from '@huggingface/transformers'; + * + * const processor = await AutoImageProcessor.from_pretrained('Xenova/clip-vit-base-patch16'); + * const image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/artemis.jpeg'); + * const { pixel_values } = await processor(image); + */ export class AutoImageProcessor { /** @type {typeof ImageProcessor.from_pretrained} */ static async from_pretrained(pretrained_model_name_or_path, options = {}) { diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index 2c18035e7..b55daf0e9 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -76,7 +76,7 @@ const CONTENT_TYPE_MAP = new Map([ * * @example * import { RawImage } from '@huggingface/transformers'; - * const image = await RawImage.read('https://example.com/photo.jpg'); + * const image = await RawImage.read('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/artemis.jpeg'); * console.log(image.width, image.height, image.channels); */ export class RawImage { From 0559d252b9c42b3230b23f476ed7280e2710408d Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:57:41 -0400 Subject: [PATCH 022/103] bump jinja.js fix lfm2.5 vl template --- packages/transformers/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index d20dcbfa0..d5a3a1dca 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -55,7 +55,7 @@ }, "homepage": "https://github.com/huggingface/transformers.js#readme", "dependencies": { - "@huggingface/jinja": "^0.5.6", + "@huggingface/jinja": "^0.5.8", "@huggingface/tokenizers": "^0.1.3", "onnxruntime-node": "1.24.3", "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8a8e38f2..7b98e50e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: packages/transformers: dependencies: '@huggingface/jinja': - specifier: ^0.5.6 - version: 0.5.6 + specifier: ^0.5.8 + version: 0.5.8 '@huggingface/tokenizers': specifier: ^0.1.3 version: 0.1.3 @@ -387,8 +387,8 @@ packages: cpu: [x64] os: [win32] - '@huggingface/jinja@0.5.6': - resolution: {integrity: sha512-MyMWyLnjqo+KRJYSH7oWNbsOn5onuIvfXYPcc0WOGxU0eHUV7oAYUoQTl2BMdu7ml+ea/bu11UM+EshbeHwtIA==} + '@huggingface/jinja@0.5.8': + resolution: {integrity: sha512-ZdElB7DPS7QQS8ZnFc5RPPtkg+eN11z8AmIZWAyes6pSbwXqiFB/POVevvm01begdSX1ho9Gxln/F6qlQMsuaA==} engines: {node: '>=18'} '@huggingface/tokenizers@0.1.3': @@ -2054,7 +2054,7 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@huggingface/jinja@0.5.6': {} + '@huggingface/jinja@0.5.8': {} '@huggingface/tokenizers@0.1.3': {} From ac20e294d78af93bc13acf85935537932cd0411a Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:15:22 -0400 Subject: [PATCH 023/103] Update tensor.js --- packages/transformers/src/utils/tensor.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index c49550b25..8d1cecbe1 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -25,11 +25,13 @@ import { random } from './random.js'; /** * A typed multi-dimensional array. * - * @example + * **Example:** + * ```javascript * import { Tensor } from '@huggingface/transformers'; * const tensor = new Tensor('float32', [1, 2, 3, 4, 5, 6], [2, 3]); * tensor.dims; // [2, 3] * tensor.tolist(); // [[1, 2, 3], [4, 5, 6]] + * ``` */ export class Tensor { /** @@ -939,13 +941,6 @@ export class Tensor { /** * This creates a nested array of a given type and depth (see examples). - * - * @example - * NestArray; // string[] - * @example - * NestArray; // number[][] - * @example - * NestArray; // string[][][] etc. * @template T * @template {number} Depth * @template {never[]} [Acc=[]] @@ -955,11 +950,13 @@ export class Tensor { /** * Reshapes a 1-dimensional array into an n-dimensional array, according to the provided dimensions. * - * @example + * **Example:** + * ```javascript * reshape([10 ], [1 ]); // Type: number[] Value: [10] * reshape([1, 2, 3, 4 ], [2, 2 ]); // Type: number[][] Value: [[1, 2], [3, 4]] * reshape([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]); // Type: number[][][] Value: [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] * reshape([1, 2, 3, 4, 5, 6, 7, 8], [4, 2 ]); // Type: number[][] Value: [[1, 2], [3, 4], [5, 6], [7, 8]] + * ``` * @param {T[]|DataArray} data The input array to reshape. * @param {DIM} dimensions The target shape/dimensions. * @template T From 7aa3953269be67715b82137c5ba0dd3ea3bb9dc7 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:15:31 -0400 Subject: [PATCH 024/103] Update random.js --- packages/transformers/src/utils/random.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/transformers/src/utils/random.js b/packages/transformers/src/utils/random.js index 4b7966c5c..b80a5521f 100644 --- a/packages/transformers/src/utils/random.js +++ b/packages/transformers/src/utils/random.js @@ -36,12 +36,14 @@ import { apis } from '../env.js'; * Each instance has its own independent state, so seeding one instance does not * affect any other instance or the global helper functions. * - * @example + * **Example:** + * ```javascript * import { random } from '@huggingface/transformers'; * * const rng1 = new random.Random(42); * const rng2 = new random.Random(42); * rng1.random() === rng2.random(); // true (same seed, independent state) + * ``` */ export class Random { constructor(seed) { @@ -219,12 +221,14 @@ const _default = new Random(); * It shares a single global state, so if you want to generate independent sequences, * construct your own `new random.Random(seed)` instead. * - * @example + * **Example:** + * ```javascript * import { random } from '@huggingface/transformers'; * random.seed(42); * random.random(); // 0.6394267984578837 (matches Python) * random.gauss(0, 1); // normal-distributed value * random.choices(['a', 'b'], [3, 1]); // weighted pick + * ``` */ export const random = Object.freeze({ Random, From 1b388c25f2ae0fbf5c5d47a9647d76cb9396231f Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:17:48 -0400 Subject: [PATCH 025/103] replace example jsdoc with markdown --- packages/transformers/src/configs.js | 4 +- packages/transformers/src/env.js | 5 +- .../models/auto/feature_extraction_auto.js | 4 +- .../src/models/auto/image_processing_auto.js | 4 +- .../src/models/auto/modeling_auto.js | 124 +++++++++++++----- packages/transformers/src/utils/audio.js | 4 +- packages/transformers/src/utils/image.js | 4 +- packages/transformers/src/utils/logger.js | 4 +- .../src/utils/model_registry/ModelRegistry.js | 52 ++++++-- 9 files changed, 153 insertions(+), 52 deletions(-) diff --git a/packages/transformers/src/configs.js b/packages/transformers/src/configs.js index 516a3df71..75ee3576f 100644 --- a/packages/transformers/src/configs.js +++ b/packages/transformers/src/configs.js @@ -507,8 +507,10 @@ export class PretrainedConfig { /** * Helper class which is used to instantiate pretrained configs with the `from_pretrained` function. * - * @example + * **Example:** + * ```javascript * const config = await AutoConfig.from_pretrained('Xenova/bert-base-uncased'); + * ``` */ export class AutoConfig { /** @type {typeof PretrainedConfig.from_pretrained} */ diff --git a/packages/transformers/src/env.js b/packages/transformers/src/env.js index 25b3cf3d2..21f6d2335 100644 --- a/packages/transformers/src/env.js +++ b/packages/transformers/src/env.js @@ -174,7 +174,8 @@ const DEFAULT_FETCH = typeof globalThis.fetch === 'function' ? globalThis.fetch. * Each level is represented by a number, where higher numbers include all lower level messages. * Use these values to set `env.logLevel`. * - * @example + * **Example:** + * ```javascript * import { env, LogLevel } from '@huggingface/transformers'; * * // Set log level to show only errors @@ -185,7 +186,7 @@ const DEFAULT_FETCH = typeof globalThis.fetch === 'function' ? globalThis.fetch. * * // Disable all logging * env.logLevel = LogLevel.NONE; - * + * ``` */ export const LogLevel = Object.freeze({ /** All messages including debug output (value: 10) */ diff --git a/packages/transformers/src/models/auto/feature_extraction_auto.js b/packages/transformers/src/models/auto/feature_extraction_auto.js index 673448b7d..9d0c2b6db 100644 --- a/packages/transformers/src/models/auto/feature_extraction_auto.js +++ b/packages/transformers/src/models/auto/feature_extraction_auto.js @@ -12,13 +12,15 @@ import * as AllFeatureExtractors from '../feature_extractors.js'; * The chosen feature extractor class is determined by the type specified in the preprocessor config. * Typically used for audio models. * - * @example + * **Example:** + * ```javascript * import { AutoFeatureExtractor, load_audio } from '@huggingface/transformers'; * * const extractor = await AutoFeatureExtractor.from_pretrained('onnx-community/whisper-tiny.en'); * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; * const audio = await load_audio(url, 16000); * const { input_features } = await extractor(audio); + * ``` */ export class AutoFeatureExtractor { /** @type {typeof FeatureExtractor.from_pretrained} */ diff --git a/packages/transformers/src/models/auto/image_processing_auto.js b/packages/transformers/src/models/auto/image_processing_auto.js index c588c328f..48a4e2171 100644 --- a/packages/transformers/src/models/auto/image_processing_auto.js +++ b/packages/transformers/src/models/auto/image_processing_auto.js @@ -12,12 +12,14 @@ import { logger } from '../../utils/logger.js'; * Helper class which is used to instantiate pretrained image processors with the `from_pretrained` function. * The chosen image processor class is determined by the type specified in the preprocessor config. * - * @example + * **Example:** + * ```javascript * import { AutoImageProcessor, load_image } from '@huggingface/transformers'; * * const processor = await AutoImageProcessor.from_pretrained('Xenova/clip-vit-base-patch16'); * const image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/artemis.jpeg'); * const { pixel_values } = await processor(image); + * ``` */ export class AutoImageProcessor { /** @type {typeof ImageProcessor.from_pretrained} */ diff --git a/packages/transformers/src/models/auto/modeling_auto.js b/packages/transformers/src/models/auto/modeling_auto.js index 97c5d33a6..1f15a4b20 100644 --- a/packages/transformers/src/models/auto/modeling_auto.js +++ b/packages/transformers/src/models/auto/modeling_auto.js @@ -141,10 +141,12 @@ class PretrainedMixin { * Helper class which is used to instantiate pretrained models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModel } from '@huggingface/transformers'; * * const model = await AutoModel.from_pretrained('Xenova/bert-base-uncased'); + * ``` */ export class AutoModel extends PretrainedMixin { /** @type {Map[]} */ @@ -157,10 +159,12 @@ export class AutoModel extends PretrainedMixin { * Helper class which is used to instantiate pretrained sequence classification models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForSequenceClassification } from '@huggingface/transformers'; * * const model = await AutoModelForSequenceClassification.from_pretrained('Xenova/distilbert-base-uncased-finetuned-sst-2-english'); + * ``` */ export class AutoModelForSequenceClassification extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_SEQUENCE_CLASSIFICATION_MAPPING_NAMES]; @@ -170,10 +174,12 @@ export class AutoModelForSequenceClassification extends PretrainedMixin { * Helper class which is used to instantiate pretrained token classification models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForTokenClassification } from '@huggingface/transformers'; * * const model = await AutoModelForTokenClassification.from_pretrained('Xenova/distilbert-base-multilingual-cased-ner-hrl'); + * ``` */ export class AutoModelForTokenClassification extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_TOKEN_CLASSIFICATION_MAPPING_NAMES]; @@ -183,10 +189,12 @@ export class AutoModelForTokenClassification extends PretrainedMixin { * Helper class which is used to instantiate pretrained sequence-to-sequence models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForSeq2SeqLM } from '@huggingface/transformers'; * * const model = await AutoModelForSeq2SeqLM.from_pretrained('Xenova/t5-small'); + * ``` */ export class AutoModelForSeq2SeqLM extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_SEQ_TO_SEQ_CAUSAL_LM_MAPPING_NAMES]; @@ -196,10 +204,12 @@ export class AutoModelForSeq2SeqLM extends PretrainedMixin { * Helper class which is used to instantiate pretrained sequence-to-sequence speech-to-text models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForSpeechSeq2Seq } from '@huggingface/transformers'; * * const model = await AutoModelForSpeechSeq2Seq.from_pretrained('onnx-community/whisper-tiny.en'); + * ``` */ export class AutoModelForSpeechSeq2Seq extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_SPEECH_SEQ_2_SEQ_MAPPING_NAMES]; @@ -209,10 +219,12 @@ export class AutoModelForSpeechSeq2Seq extends PretrainedMixin { * Helper class which is used to instantiate pretrained sequence-to-sequence text-to-spectrogram models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForTextToSpectrogram } from '@huggingface/transformers'; * * const model = await AutoModelForTextToSpectrogram.from_pretrained('Xenova/speecht5_tts'); + * ``` */ export class AutoModelForTextToSpectrogram extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_TEXT_TO_SPECTROGRAM_MAPPING_NAMES]; @@ -222,10 +234,12 @@ export class AutoModelForTextToSpectrogram extends PretrainedMixin { * Helper class which is used to instantiate pretrained text-to-waveform models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForTextToWaveform } from '@huggingface/transformers'; * * const model = await AutoModelForTextToWaveform.from_pretrained('Xenova/mms-tts-eng'); + * ``` */ export class AutoModelForTextToWaveform extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_TEXT_TO_WAVEFORM_MAPPING_NAMES]; @@ -235,10 +249,12 @@ export class AutoModelForTextToWaveform extends PretrainedMixin { * Helper class which is used to instantiate pretrained causal language models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForCausalLM } from '@huggingface/transformers'; * * const model = await AutoModelForCausalLM.from_pretrained('Xenova/gpt2'); + * ``` */ export class AutoModelForCausalLM extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_CAUSAL_LM_MAPPING_NAMES]; @@ -248,10 +264,12 @@ export class AutoModelForCausalLM extends PretrainedMixin { * Helper class which is used to instantiate pretrained masked language models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForMaskedLM } from '@huggingface/transformers'; * * const model = await AutoModelForMaskedLM.from_pretrained('Xenova/bert-base-uncased'); + * ``` */ export class AutoModelForMaskedLM extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_MASKED_LM_MAPPING_NAMES]; @@ -261,10 +279,12 @@ export class AutoModelForMaskedLM extends PretrainedMixin { * Helper class which is used to instantiate pretrained question answering models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForQuestionAnswering } from '@huggingface/transformers'; * * const model = await AutoModelForQuestionAnswering.from_pretrained('Xenova/distilbert-base-cased-distilled-squad'); + * ``` */ export class AutoModelForQuestionAnswering extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_QUESTION_ANSWERING_MAPPING_NAMES]; @@ -274,10 +294,12 @@ export class AutoModelForQuestionAnswering extends PretrainedMixin { * Helper class which is used to instantiate pretrained vision-to-sequence models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForVision2Seq } from '@huggingface/transformers'; * * const model = await AutoModelForVision2Seq.from_pretrained('Xenova/vit-gpt2-image-captioning'); + * ``` */ export class AutoModelForVision2Seq extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_VISION_2_SEQ_MAPPING_NAMES]; @@ -287,10 +309,12 @@ export class AutoModelForVision2Seq extends PretrainedMixin { * Helper class which is used to instantiate pretrained image classification models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForImageClassification } from '@huggingface/transformers'; * * const model = await AutoModelForImageClassification.from_pretrained('Xenova/vit-base-patch16-224'); + * ``` */ export class AutoModelForImageClassification extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES]; @@ -300,10 +324,12 @@ export class AutoModelForImageClassification extends PretrainedMixin { * Helper class which is used to instantiate pretrained image segmentation models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForImageSegmentation } from '@huggingface/transformers'; * * const model = await AutoModelForImageSegmentation.from_pretrained('Xenova/detr-resnet-50-panoptic'); + * ``` */ export class AutoModelForImageSegmentation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_SEGMENTATION_MAPPING_NAMES]; @@ -313,10 +339,12 @@ export class AutoModelForImageSegmentation extends PretrainedMixin { * Helper class which is used to instantiate pretrained image segmentation models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForSemanticSegmentation } from '@huggingface/transformers'; * * const model = await AutoModelForSemanticSegmentation.from_pretrained('Xenova/segformer-b0-finetuned-ade-512-512'); + * ``` */ export class AutoModelForSemanticSegmentation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_SEMANTIC_SEGMENTATION_MAPPING_NAMES]; @@ -326,10 +354,12 @@ export class AutoModelForSemanticSegmentation extends PretrainedMixin { * Helper class which is used to instantiate pretrained universal image segmentation models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForUniversalSegmentation } from '@huggingface/transformers'; * * const model = await AutoModelForUniversalSegmentation.from_pretrained('Xenova/detr-resnet-50-panoptic'); + * ``` */ export class AutoModelForUniversalSegmentation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_UNIVERSAL_SEGMENTATION_MAPPING_NAMES]; @@ -339,10 +369,12 @@ export class AutoModelForUniversalSegmentation extends PretrainedMixin { * Helper class which is used to instantiate pretrained object detection models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForObjectDetection } from '@huggingface/transformers'; * * const model = await AutoModelForObjectDetection.from_pretrained('Xenova/detr-resnet-50'); + * ``` */ export class AutoModelForObjectDetection extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_OBJECT_DETECTION_MAPPING_NAMES]; @@ -352,10 +384,12 @@ export class AutoModelForObjectDetection extends PretrainedMixin { * Helper class which is used to instantiate pretrained zero-shot object detection models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForZeroShotObjectDetection } from '@huggingface/transformers'; * * const model = await AutoModelForZeroShotObjectDetection.from_pretrained('Xenova/owlvit-base-patch32'); + * ``` */ export class AutoModelForZeroShotObjectDetection extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_ZERO_SHOT_OBJECT_DETECTION_MAPPING_NAMES]; @@ -365,10 +399,12 @@ export class AutoModelForZeroShotObjectDetection extends PretrainedMixin { * Helper class which is used to instantiate pretrained mask generation models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForMaskGeneration } from '@huggingface/transformers'; * * const model = await AutoModelForMaskGeneration.from_pretrained('Xenova/slimsam-77-uniform'); + * ``` */ export class AutoModelForMaskGeneration extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_MASK_GENERATION_MAPPING_NAMES]; @@ -378,10 +414,12 @@ export class AutoModelForMaskGeneration extends PretrainedMixin { * Helper class which is used to instantiate pretrained connectionist temporal classification (CTC) models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForCTC } from '@huggingface/transformers'; * * const model = await AutoModelForCTC.from_pretrained('Xenova/wav2vec2-base-960h'); + * ``` */ export class AutoModelForCTC extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_CTC_MAPPING_NAMES]; @@ -391,10 +429,12 @@ export class AutoModelForCTC extends PretrainedMixin { * Helper class which is used to instantiate pretrained audio classification models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForAudioClassification } from '@huggingface/transformers'; * * const model = await AutoModelForAudioClassification.from_pretrained('Xenova/wav2vec2-base-superb-ks'); + * ``` */ export class AutoModelForAudioClassification extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_CLASSIFICATION_MAPPING_NAMES]; @@ -404,10 +444,12 @@ export class AutoModelForAudioClassification extends PretrainedMixin { * Helper class which is used to instantiate pretrained speaker embedding models (X-Vector) with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForXVector } from '@huggingface/transformers'; * * const model = await AutoModelForXVector.from_pretrained('Xenova/wavlm-base-plus-sv'); + * ``` */ export class AutoModelForXVector extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_XVECTOR_MAPPING_NAMES]; @@ -417,10 +459,12 @@ export class AutoModelForXVector extends PretrainedMixin { * Helper class which is used to instantiate pretrained audio frame (token) classification models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForAudioFrameClassification } from '@huggingface/transformers'; * * const model = await AutoModelForAudioFrameClassification.from_pretrained('onnx-community/pyannote-segmentation-3.0'); + * ``` */ export class AutoModelForAudioFrameClassification extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_FRAME_CLASSIFICATION_MAPPING_NAMES]; @@ -430,10 +474,12 @@ export class AutoModelForAudioFrameClassification extends PretrainedMixin { * Helper class which is used to instantiate pretrained document question answering models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForDocumentQuestionAnswering } from '@huggingface/transformers'; * * const model = await AutoModelForDocumentQuestionAnswering.from_pretrained('Xenova/donut-base-finetuned-docvqa'); + * ``` */ export class AutoModelForDocumentQuestionAnswering extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_DOCUMENT_QUESTION_ANSWERING_MAPPING_NAMES]; @@ -443,10 +489,12 @@ export class AutoModelForDocumentQuestionAnswering extends PretrainedMixin { * Helper class which is used to instantiate pretrained image matting models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForImageMatting } from '@huggingface/transformers'; * * const model = await AutoModelForImageMatting.from_pretrained('Xenova/vitmatte-small-composition-1k'); + * ``` */ export class AutoModelForImageMatting extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_MATTING_MAPPING_NAMES]; @@ -456,10 +504,12 @@ export class AutoModelForImageMatting extends PretrainedMixin { * Helper class which is used to instantiate pretrained image-to-image models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForImageToImage } from '@huggingface/transformers'; * * const model = await AutoModelForImageToImage.from_pretrained('Xenova/swin2SR-classical-sr-x2-64'); + * ``` */ export class AutoModelForImageToImage extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_TO_IMAGE_MAPPING_NAMES]; @@ -469,10 +519,12 @@ export class AutoModelForImageToImage extends PretrainedMixin { * Helper class which is used to instantiate pretrained depth estimation models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForDepthEstimation } from '@huggingface/transformers'; * * const model = await AutoModelForDepthEstimation.from_pretrained('onnx-community/depth-anything-v2-small-ONNX'); + * ``` */ export class AutoModelForDepthEstimation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_DEPTH_ESTIMATION_MAPPING_NAMES]; @@ -482,10 +534,12 @@ export class AutoModelForDepthEstimation extends PretrainedMixin { * Helper class which is used to instantiate pretrained surface-normal estimation models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForNormalEstimation } from '@huggingface/transformers'; * * const model = await AutoModelForNormalEstimation.from_pretrained('onnx-community/sapiens-normal-0.3b'); + * ``` */ export class AutoModelForNormalEstimation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_NORMAL_ESTIMATION_MAPPING_NAMES]; @@ -495,10 +549,12 @@ export class AutoModelForNormalEstimation extends PretrainedMixin { * Helper class which is used to instantiate pretrained pose estimation models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForPoseEstimation } from '@huggingface/transformers'; * * const model = await AutoModelForPoseEstimation.from_pretrained('onnx-community/vitpose-base-simple'); + * ``` */ export class AutoModelForPoseEstimation extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_POSE_ESTIMATION_MAPPING_NAMES]; @@ -508,10 +564,12 @@ export class AutoModelForPoseEstimation extends PretrainedMixin { * Helper class which is used to instantiate pretrained image feature extraction models with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForImageFeatureExtraction } from '@huggingface/transformers'; * * const model = await AutoModelForImageFeatureExtraction.from_pretrained('onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX'); + * ``` */ export class AutoModelForImageFeatureExtraction extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_FEATURE_EXTRACTION_MAPPING_NAMES]; @@ -522,10 +580,12 @@ export class AutoModelForImageFeatureExtraction extends PretrainedMixin { * (image+text-to-text) with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForImageTextToText } from '@huggingface/transformers'; * * const model = await AutoModelForImageTextToText.from_pretrained('onnx-community/LFM2.5-VL-450M-ONNX'); + * ``` */ export class AutoModelForImageTextToText extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_IMAGE_TEXT_TO_TEXT_MAPPING_NAMES]; @@ -536,10 +596,12 @@ export class AutoModelForImageTextToText extends PretrainedMixin { * (audio+text-to-text) with the `from_pretrained` function. * The chosen model class is determined by the type specified in the model config. * - * @example + * **Example:** + * ```javascript * import { AutoModelForAudioTextToText } from '@huggingface/transformers'; * * const model = await AutoModelForAudioTextToText.from_pretrained('onnx-community/Voxtral-Mini-4B-Realtime-2602-ONNX'); + * ``` */ export class AutoModelForAudioTextToText extends PretrainedMixin { static MODEL_CLASS_MAPPINGS = [MODEL_MAPPINGS.MODEL_FOR_AUDIO_TEXT_TO_TEXT_MAPPING_NAMES]; diff --git a/packages/transformers/src/utils/audio.js b/packages/transformers/src/utils/audio.js index 418da9818..dc25ab543 100644 --- a/packages/transformers/src/utils/audio.js +++ b/packages/transformers/src/utils/audio.js @@ -827,12 +827,14 @@ function writeString(view, offset, string) { /** * An audio buffer paired with its sampling rate. * - * @example + * **Example:** + * ```javascript * import { RawAudio } from '@huggingface/transformers'; * const samples = new Float32Array(16000); // 1 second of silence @ 16 kHz * const audio = new RawAudio(samples, 16000); * const blob = audio.toBlob(); // WAV blob for upload or playback * await audio.save('out.wav'); // Saves the audio as a WAV file named 'out.wav' + * ``` */ export class RawAudio { /** diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index b55daf0e9..688d5e9e5 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -74,10 +74,12 @@ const CONTENT_TYPE_MAP = new Map([ /** * Represents an image stored as a raw pixel buffer. * - * @example + * **Example:** + * ```javascript * import { RawImage } from '@huggingface/transformers'; * const image = await RawImage.read('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/artemis.jpeg'); * console.log(image.width, image.height, image.channels); + * ``` */ export class RawImage { /** diff --git a/packages/transformers/src/utils/logger.js b/packages/transformers/src/utils/logger.js index ec5940410..57c24d1f7 100644 --- a/packages/transformers/src/utils/logger.js +++ b/packages/transformers/src/utils/logger.js @@ -9,12 +9,14 @@ import { env, LogLevel } from '../env.js'; /** * Logger that respects the configured log level in env.logLevel. * - * @example + * **Example:** + * ```javascript * import { logger } from './utils/logger.js'; * logger.info('Model loaded successfully'); * logger.warn('Deprecated method used'); * logger.error('Failed to load model'); * logger.debug('Token count:', tokens.length); + * ``` */ export const logger = { /** diff --git a/packages/transformers/src/utils/model_registry/ModelRegistry.js b/packages/transformers/src/utils/model_registry/ModelRegistry.js index 1179a92f6..48357d3c8 100644 --- a/packages/transformers/src/utils/model_registry/ModelRegistry.js +++ b/packages/transformers/src/utils/model_registry/ModelRegistry.js @@ -129,9 +129,11 @@ export class ModelRegistry { * @param {boolean} [options.include_processor=true] - Whether to check for processor files * @returns {Promise} Array of file paths * - * @example + * **Example:** + * ```javascript * const files = await ModelRegistry.get_files('onnx-community/gpt2-ONNX'); * console.log(files); // ['config.json', 'tokenizer.json', 'onnx/model_q4.onnx', ...] + * ``` */ static async get_files(modelId, options = {}) { return get_files(modelId, options); @@ -150,9 +152,11 @@ export class ModelRegistry { * @param {string} [options.model_file_name=null] - Override the model file name (excluding .onnx suffix) * @returns {Promise} Array of file paths * - * @example + * **Example:** + * ```javascript * const files = await ModelRegistry.get_pipeline_files('text-generation', 'onnx-community/gpt2-ONNX'); * console.log(files); // ['config.json', 'tokenizer.json', 'onnx/model_q4.onnx', ...] + * ``` */ static async get_pipeline_files(task, modelId, options = {}) { return get_pipeline_files(task, modelId, options); @@ -169,9 +173,11 @@ export class ModelRegistry { * @param {string} [options.model_file_name=null] - Override the model file name (excluding .onnx suffix) * @returns {Promise} Array of model file paths * - * @example + * **Example:** + * ```javascript * const files = await ModelRegistry.get_model_files('onnx-community/bert-base-uncased-ONNX'); * console.log(files); // ['config.json', 'onnx/model_q4.onnx', 'generation_config.json'] + * ``` */ static async get_model_files(modelId, options = {}) { return get_model_files(modelId, options); @@ -183,9 +189,11 @@ export class ModelRegistry { * @param {string} modelId - The model id * @returns {Promise} Array of tokenizer file paths * - * @example + * **Example:** + * ```javascript * const files = await ModelRegistry.get_tokenizer_files('onnx-community/gpt2-ONNX'); * console.log(files); // ['tokenizer.json', 'tokenizer_config.json'] + * ``` */ static async get_tokenizer_files(modelId) { return get_tokenizer_files(modelId); @@ -197,9 +205,11 @@ export class ModelRegistry { * @param {string} modelId - The model id * @returns {Promise} Array of processor file paths * - * @example + * **Example:** + * ```javascript * const files = await ModelRegistry.get_processor_files('onnx-community/vit-base-patch16-224-ONNX'); * console.log(files); // ['preprocessor_config.json'] + * ``` */ static async get_processor_files(modelId) { return get_processor_files(modelId); @@ -221,9 +231,11 @@ export class ModelRegistry { * @param {boolean} [options.local_files_only=false] - Only check local files * @returns {Promise} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']) * - * @example + * **Example:** + * ```javascript * const dtypes = await ModelRegistry.get_available_dtypes('onnx-community/all-MiniLM-L6-v2-ONNX'); * console.log(dtypes); // ['fp32', 'fp16', 'int8', 'uint8', 'q8', 'q4'] + * ``` */ static async get_available_dtypes(modelId, options = {}) { return get_available_dtypes(modelId, options); @@ -243,9 +255,11 @@ export class ModelRegistry { * @param {import('../devices.js').DeviceType|Record} [options.device=null] - Override device * @returns {Promise} Whether all required files are cached * - * @example + * **Example:** + * ```javascript * const cached = await ModelRegistry.is_cached('onnx-community/bert-base-uncased-ONNX'); * console.log(cached); // true or false + * ``` */ static async is_cached(modelId, options = {}) { return is_cached(modelId, options); @@ -264,10 +278,12 @@ export class ModelRegistry { * @param {import('../devices.js').DeviceType|Record} [options.device=null] - Override device * @returns {Promise} Object with allCached boolean and files array with cache status * - * @example + * **Example:** + * ```javascript * const status = await ModelRegistry.is_cached_files('onnx-community/bert-base-uncased-ONNX'); * console.log(status.allCached); // true or false * console.log(status.files); // [{ file: 'config.json', cached: true }, ...] + * ``` */ static async is_cached_files(modelId, options = {}) { return is_cached_files(modelId, options); @@ -288,9 +304,11 @@ export class ModelRegistry { * @param {import('../devices.js').DeviceType|Record} [options.device=null] - Override device * @returns {Promise} Whether all required files are cached * - * @example + * **Example:** + * ```javascript * const cached = await ModelRegistry.is_pipeline_cached('text-generation', 'onnx-community/gpt2-ONNX'); * console.log(cached); // true or false + * ``` */ static async is_pipeline_cached(task, modelId, options = {}) { return is_pipeline_cached(task, modelId, options); @@ -310,10 +328,12 @@ export class ModelRegistry { * @param {import('../devices.js').DeviceType|Record} [options.device=null] - Override device * @returns {Promise} Object with allCached boolean and files array with cache status * - * @example + * **Example:** + * ```javascript * const status = await ModelRegistry.is_pipeline_cached_files('text-generation', 'onnx-community/gpt2-ONNX'); * console.log(status.allCached); // true or false * console.log(status.files); // [{ file: 'config.json', cached: true }, ...] + * ``` */ static async is_pipeline_cached_files(task, modelId, options = {}) { return is_pipeline_cached_files(task, modelId, options); @@ -327,9 +347,11 @@ export class ModelRegistry { * @param {import('../hub.js').PretrainedOptions} [options] - Optional parameters * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} File metadata * - * @example + * **Example:** + * ```javascript * const metadata = await ModelRegistry.get_file_metadata('onnx-community/gpt2-ONNX', 'config.json'); * console.log(metadata.exists, metadata.size); // true, 665 + * ``` */ static async get_file_metadata(path_or_repo_id, filename, options = {}) { return get_file_metadata(path_or_repo_id, filename, options); @@ -350,9 +372,11 @@ export class ModelRegistry { * @param {boolean} [options.include_processor=true] - Whether to clear processor files * @returns {Promise} Object with deletion statistics and file status * - * @example + * **Example:** + * ```javascript * const result = await ModelRegistry.clear_cache('onnx-community/bert-base-uncased-ONNX'); * console.log(`Deleted ${result.filesDeleted} of ${result.filesCached} cached files`); + * ``` */ static async clear_cache(modelId, options = {}) { return clear_cache(modelId, options); @@ -372,9 +396,11 @@ export class ModelRegistry { * @param {import('../devices.js').DeviceType|Record} [options.device] - Override device * @returns {Promise} Object with deletion statistics and file status * - * @example + * **Example:** + * ```javascript * const result = await ModelRegistry.clear_pipeline_cache('text-generation', 'onnx-community/gpt2-ONNX'); * console.log(`Deleted ${result.filesDeleted} of ${result.filesCached} cached files`); + * ``` */ static async clear_pipeline_cache(task, modelId, options = {}) { return clear_pipeline_cache(task, modelId, options); From d9a03c3823ab07c92aab01b67c25f86636668f1d Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:24:06 -0400 Subject: [PATCH 026/103] automatic skills generation --- .../docs/scripts/generate-skill.js | 20 + .../transformers/docs/scripts/generate.js | 35 +- .../transformers/docs/scripts/lib/exports.mjs | 102 ++++- packages/transformers/docs/scripts/lib/ir.mjs | 48 +-- .../transformers/docs/scripts/lib/load.mjs | 34 ++ .../docs/scripts/lib/render-skill.mjs | 379 ++++++++++++++++++ .../transformers/docs/scripts/lib/tasks.mjs | 68 ++++ packages/transformers/package.json | 1 + 8 files changed, 625 insertions(+), 62 deletions(-) create mode 100644 packages/transformers/docs/scripts/generate-skill.js create mode 100644 packages/transformers/docs/scripts/lib/load.mjs create mode 100644 packages/transformers/docs/scripts/lib/render-skill.mjs create mode 100644 packages/transformers/docs/scripts/lib/tasks.mjs diff --git a/packages/transformers/docs/scripts/generate-skill.js b/packages/transformers/docs/scripts/generate-skill.js new file mode 100644 index 000000000..d68b294fc --- /dev/null +++ b/packages/transformers/docs/scripts/generate-skill.js @@ -0,0 +1,20 @@ +// Generate the `.ai/skills/transformers-js/` tree from the library's JSDoc. +// Injects generated task recipes into the hand-written SKILL.md (preserving +// editorial prose) and rewrites the fully-generated reference files. + +import path from "node:path"; +import url from "node:url"; + +import { loadProject } from "./lib/load.mjs"; +import { renderSkill } from "./lib/render-skill.mjs"; + +const docs = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); +const packageRoot = path.dirname(docs); +const repoRoot = path.resolve(packageRoot, "..", ".."); +const skillDir = path.join(repoRoot, ".ai", "skills", "transformers-js"); + +const { ir, tasks, publicNames } = loadProject(packageRoot); + +renderSkill({ ir, tasks, publicNames, skillDir }); + +console.log(`wrote skill to ${path.relative(repoRoot, skillDir) || skillDir}`); diff --git a/packages/transformers/docs/scripts/generate.js b/packages/transformers/docs/scripts/generate.js index 5c432ce86..8c93a0da7 100644 --- a/packages/transformers/docs/scripts/generate.js +++ b/packages/transformers/docs/scripts/generate.js @@ -5,33 +5,32 @@ import fs from "node:fs"; import path from "node:path"; import url from "node:url"; -import { extractEntities } from "./lib/structure.mjs"; -import { buildIR } from "./lib/ir.mjs"; +import { loadProject } from "./lib/load.mjs"; import { renderModule } from "./lib/render-api.mjs"; -import { collectPublicExports } from "./lib/exports.mjs"; const docs = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); const root = path.dirname(docs); -const srcDir = path.join(root, "src"); const outputDir = path.join(root, "docs", "source", "api"); -const fileEntities = collectJsFiles(srcDir).map((file) => ({ - file, - entities: extractEntities(fs.readFileSync(file, "utf8"), file), -})); - -const ir = buildIR(fileEntities); -const publicNames = collectPublicExports(path.join(srcDir, "transformers.js")); +const { ir, publicNames } = loadProject(root); clearExistingMarkdown(); for (const mod of ir.modules) { + if (!hasPublicContent(mod, publicNames)) { + console.log(`skipped ${mod.name}.md — no public content`); + continue; + } const output = renderModule(mod, ir, { publicNames }); const outputPath = path.resolve(outputDir, `${mod.name}.md`); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, output); - const empty = output.trim().split("\n").length <= 2 ? " (empty — no public content)" : ""; - console.log(`wrote ${mod.name}.md${empty}`); + console.log(`wrote ${mod.name}.md`); +} + +function hasPublicContent(mod, publicNames) { + const isPublic = (item) => publicNames.has(item.name); + return mod.classes.some(isPublic) || mod.functions.some(isPublic) || mod.constants.some(isPublic) || mod.typedefs.length > 0 || mod.callbacks.length > 0; } function clearExistingMarkdown() { @@ -40,13 +39,3 @@ function clearExistingMarkdown() { if (entry.endsWith(".md")) fs.unlinkSync(path.join(outputDir, entry)); } } - -function collectJsFiles(dir) { - const out = []; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) out.push(...collectJsFiles(full)); - else if (entry.name.endsWith(".js")) out.push(full); - } - return out; -} diff --git a/packages/transformers/docs/scripts/lib/exports.mjs b/packages/transformers/docs/scripts/lib/exports.mjs index 5089cd747..954f33a34 100644 --- a/packages/transformers/docs/scripts/lib/exports.mjs +++ b/packages/transformers/docs/scripts/lib/exports.mjs @@ -1,12 +1,16 @@ // Resolve the symbol names the library exposes via `src/transformers.js`. // Everything outside this set is treated as internal and excluded from docs. +// +// Public names are collected from: +// - `export * from './path'` re-exports (walked recursively) +// - `export { a, b as c }` named exports +// - `export foo` direct declarations +// - Properties of exported `Object.freeze({ X, Y, ... })` namespace objects, +// so e.g. `random.Random` counts as public when `random` is exported. import fs from "node:fs"; import path from "node:path"; - -const STAR_REEXPORT = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g; -const NAMED_EXPORT = /export\s*\{\s*([^}]+)\s*\}\s*(?:from\s*['"][^'"]+['"])?/g; -const DECL_EXPORT = /export\s+(?:default\s+)?(?:async\s+)?(?:class|function\s*\*?|const|let|var)\s+([A-Za-z_$][\w$]*)/g; +import ts from "typescript"; export function collectPublicExports(entryFile) { const names = new Set(); @@ -18,25 +22,91 @@ function walk(file, visited, names) { if (visited.has(file) || !fs.existsSync(file)) return; visited.add(file); - const source = stripComments(fs.readFileSync(file, "utf8")); + const source = fs.readFileSync(file, "utf8"); + const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); const dir = path.dirname(file); - for (const [, specifier] of source.matchAll(STAR_REEXPORT)) { - if (!specifier.startsWith(".")) continue; - walk(path.resolve(dir, specifier), visited, names); + ts.forEachChild(sf, (node) => visitTopLevel(node, sf, dir, visited, names)); +} + +function visitTopLevel(node, sf, dir, visited, names) { + // `export * from './path'` + if (ts.isExportDeclaration(node) && !node.exportClause && node.moduleSpecifier) { + const specifier = stripQuotes(node.moduleSpecifier.getText(sf)); + if (specifier.startsWith(".")) walk(path.resolve(dir, specifier), visited, names); + return; + } + + // `export { a, b as c }` or `export { a, b as c } from './path'` + if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) { + const specifier = node.moduleSpecifier ? stripQuotes(node.moduleSpecifier.getText(sf)) : null; + const importedNames = node.exportClause.elements.map((e) => (e.propertyName ?? e.name).text); + for (const spec of node.exportClause.elements) names.add(spec.name.text); + if (specifier?.startsWith(".")) { + const target = path.resolve(dir, specifier); + addNamespaceMembersFromFile(target, importedNames, names); + } + return; + } + + if (!hasExportModifier(node)) return; + + if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node)) { + if (node.name) names.add(node.name.text); + return; } - for (const [, list] of source.matchAll(NAMED_EXPORT)) { - for (const spec of list.split(",")) { - const parts = spec.trim().split(/\s+as\s+/); - const exported = (parts[1] ?? parts[0]).trim(); - if (exported) names.add(exported); + if (ts.isVariableStatement(node)) { + for (const decl of node.declarationList.declarations) { + if (!ts.isIdentifier(decl.name)) continue; + names.add(decl.name.text); + addFrozenNamespaceMembers(decl.initializer, names); } } +} + +// Look up the named exports in `file` and, for each one whose initializer is +// `Object.freeze({ ... })`, pull its shorthand-property names into `names`. +function addNamespaceMembersFromFile(file, wanted, names) { + if (!fs.existsSync(file)) return; + const source = fs.readFileSync(file, "utf8"); + const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const wantedSet = new Set(wanted); + ts.forEachChild(sf, (node) => { + if (!ts.isVariableStatement(node) || !hasExportModifier(node)) return; + for (const decl of node.declarationList.declarations) { + if (ts.isIdentifier(decl.name) && wantedSet.has(decl.name.text)) { + addFrozenNamespaceMembers(decl.initializer, names); + } + } + }); +} + +// If the initializer is `Object.freeze({ ... })`, also treat each shorthand +// property name inside the object literal as publicly reachable via the +// exported namespace (e.g. `random.Random`). +function addFrozenNamespaceMembers(init, names) { + const literal = unwrapObjectFreeze(init); + if (!literal) return; + for (const prop of literal.properties) { + if (ts.isShorthandPropertyAssignment(prop)) names.add(prop.name.text); + } +} + +function unwrapObjectFreeze(init) { + if (!init || !ts.isCallExpression(init)) return null; + const callee = init.expression; + const isFreeze = + ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && callee.expression.text === "Object" && callee.name.text === "freeze"; + if (!isFreeze) return null; + const arg = init.arguments[0]; + return arg && ts.isObjectLiteralExpression(arg) ? arg : null; +} - for (const [, name] of source.matchAll(DECL_EXPORT)) names.add(name); +function hasExportModifier(node) { + return !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword); } -function stripComments(source) { - return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/[^\n]*/g, "$1"); +function stripQuotes(s) { + return s.replace(/^['"`]|['"`]$/g, ""); } diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index f30c07826..df5d7fcaa 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -48,11 +48,9 @@ function resolveModule(entities) { const moduleTag = entities.module?.tags.find((t) => t.tag === "module"); if (!moduleTag) return null; const fileTag = entities.module.tags.find((t) => t.tag === "file"); - return { - name: moduleTag.name, - description: fileTag?.description || entities.module.description || "", - examples: entities.module.tags.filter((t) => t.tag === "example").map(normalizeExample), - }; + const raw = fileTag?.description || entities.module.description || ""; + const { description, examples } = gatherExamples(raw); + return { name: moduleTag.name, description, examples }; } function newModule(name) { @@ -74,10 +72,11 @@ function ingest(entities, mod) { } for (const cls of entities.classes) { if (isPrivate(cls)) continue; + const { description, examples } = gatherExamples(cls.description); mod.classes.push({ name: cls.name, - description: cls.description, - examples: tagsOf(cls, "example").map(normalizeExample), + description, + examples, skillExamples: tagsOf(cls, "skillExample").map((t) => t.task), members: cls.members.filter((m) => !isPrivate(m)).map(buildMember), }); @@ -87,11 +86,12 @@ function ingest(entities, mod) { } for (const v of entities.variables) { if (isPrivate(v)) continue; + const { description, examples } = gatherExamples(v.description); mod.constants.push({ name: v.name, type: typeOf(v), - description: v.description, - examples: tagsOf(v, "example").map(normalizeExample), + description, + examples, }); } } @@ -148,13 +148,14 @@ function buildMember(m) { } function buildCallable(fn) { + const { description, examples } = gatherExamples(fn.description); return { name: fn.name, - description: fn.description, + description, params: tagsOf(fn, "param").map(normalizeParam), returns: pickReturns(fn), throws: tagsOf(fn, "throws").map((t) => ({ type: t.type, description: t.description })), - examples: tagsOf(fn, "example").map(normalizeExample), + examples, skillExamples: tagsOf(fn, "skillExample").map((t) => t.task), deprecated: fn.tags.some((t) => t.tag === "deprecated"), }; @@ -183,18 +184,19 @@ function normalizeParam(tag) { }; } -const FENCE_BLOCK = /```(\w+)?\n([\s\S]*?)\n```/; -const EXAMPLE_PREFIX = /^(?:\*\*Example[^*]*\*\*|Example)[:\s]*/; - -function normalizeExample(tag) { - const body = tag.body || ""; - const m = body.match(FENCE_BLOCK); - if (!m) return { title: "", language: "javascript", code: body.trim() }; - return { - title: body.slice(0, m.index).trim().replace(EXAMPLE_PREFIX, "").trim(), - language: m[1] || "javascript", - code: m[2].trim(), - }; +// Canonical example format: `**Example:** \n```lang\n<code>\n```` inside +// any JSDoc description body. Extracted into a structured `examples` array so +// renderers can emit them consistently; the source lines are stripped from +// the description so they're not rendered twice. +const INLINE_EXAMPLE = /\*\*Example:\*\*\s*([^\n]*)\n+```(\w+)?\n([\s\S]*?)\n```/g; + +function gatherExamples(description) { + const examples = []; + const cleaned = (description || "").replace(INLINE_EXAMPLE, (_, title, lang, code) => { + examples.push({ title: title.trim(), language: lang || "javascript", code: code.trim() }); + return ""; + }); + return { description: cleaned.replace(/\n{3,}/g, "\n\n").trim(), examples }; } function isPrivate(entity) { diff --git a/packages/transformers/docs/scripts/lib/load.mjs b/packages/transformers/docs/scripts/lib/load.mjs new file mode 100644 index 000000000..6c49e88e2 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/load.mjs @@ -0,0 +1,34 @@ +// Shared loader: reads every `.js` file under `src/`, extracts TS-backed +// entities, builds the IR, and returns it along with the public-export set +// and the task catalog. Both the API-markdown and skill renderers consume it. + +import fs from "node:fs"; +import path from "node:path"; + +import { extractEntities } from "./structure.mjs"; +import { buildIR } from "./ir.mjs"; +import { collectPublicExports } from "./exports.mjs"; +import { extractTaskCatalog } from "./tasks.mjs"; + +export function loadProject(root) { + const srcDir = path.join(root, "src"); + const fileEntities = collectJsFiles(srcDir).map((file) => ({ + file, + entities: extractEntities(fs.readFileSync(file, "utf8"), file), + })); + return { + ir: buildIR(fileEntities), + publicNames: collectPublicExports(path.join(srcDir, "transformers.js")), + tasks: extractTaskCatalog(path.join(srcDir, "pipelines", "index.js")), + }; +} + +function collectJsFiles(dir) { + const out = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) out.push(...collectJsFiles(full)); + else if (entry.name.endsWith(".js")) out.push(full); + } + return out; +} diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs new file mode 100644 index 000000000..8814741e6 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -0,0 +1,379 @@ +// Render the skill files in `.ai/skills/transformers-js/`. Two kinds of output: +// +// 1. Generated fragments injected into hand-written `SKILL.md` between +// `<!-- @generated:start id=X -->` and `<!-- @generated:end id=X -->` +// sentinels. Prose outside the markers is preserved across runs. +// +// 2. Fully generated files under `references/`. These carry a banner at the +// top and are rewritten in full on every run. + +import fs from "node:fs"; +import path from "node:path"; + +const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-skill.js -->"; + +export function renderSkill({ ir, tasks, publicNames, skillDir }) { + const ctx = { ir, tasks, publicNames: publicNames ?? null }; + + // Rewrite fully-generated reference files. + const refDir = path.join(skillDir, "references"); + fs.mkdirSync(refDir, { recursive: true }); + fs.writeFileSync(path.join(refDir, "TASK_EXAMPLES.md"), renderTaskExamples(ctx)); + fs.writeFileSync(path.join(refDir, "API_SUMMARY.md"), renderApiSummary(ctx)); + + // Expand `<!-- @generated:start id=... -->` markers in every hand-written + // markdown file under the skill directory. Prose outside markers is preserved. + for (const file of walkMarkdown(skillDir)) { + const original = fs.readFileSync(file, "utf8"); + if (!original.includes("@generated:start")) continue; + const injected = injectMarkers(original, ctx); + if (injected !== original) fs.writeFileSync(file, injected); + } +} + +function walkMarkdown(dir) { + const out = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) out.push(...walkMarkdown(full)); + else if (entry.name.endsWith(".md")) out.push(full); + } + return out; +} + +// ---------- marker injection ---------- + +const MARKER_RE = /(<!-- @generated:start id=([^\s]+) -->)([\s\S]*?)(<!-- @generated:end id=\2 -->)/g; + +function injectMarkers(original, ctx) { + return original.replace(MARKER_RE, (match, startTag, id, _body, endTag) => { + const content = resolveMarker(id, ctx); + if (content == null) { + console.warn(`skill: unknown marker id "${id}"`); + return match; + } + return `${startTag}\n${content.trimEnd()}\n${endTag}`; + }); +} + +// Dispatch a marker id to its generator. Supported ids: +// task-list full list of supported pipeline tasks +// task:<id> recipe for a single task +// typedef:<Name> properties table for a named typedef +// class:<Name> class description + fields + methods summary +// fields:<ClassName> properties table built from a class's fields +// examples:<module> all inline `**Example:**` blocks from `@module <module>` +function resolveMarker(id, ctx) { + if (id === "task-list") return renderTaskList(ctx); + + const [kind, arg] = splitOnce(id, ":"); + switch (kind) { + case "task": { + const info = ctx.tasks.supportedTasks.get(arg); + return info ? renderTaskRecipe(arg, info, ctx) : null; + } + case "typedef": + return renderTypedefTable(arg, ctx); + case "class": + return renderClassSummary(arg, ctx); + case "fields": + return renderFieldsTable(arg, ctx); + case "examples": + return renderModuleExamples(arg, ctx); + default: + return null; + } +} + +function renderModuleExamples(moduleName, ctx) { + const mod = ctx.ir.modules.find((m) => m.name === moduleName); + if (!mod) { + console.warn(`skill: module "${moduleName}" not found`); + return ""; + } + if (!mod.examples.length) { + console.warn(`skill: module "${moduleName}" has no example blocks`); + return ""; + } + const lines = []; + for (const ex of mod.examples) { + if (ex.title) lines.push(`**Example:** ${ex.title}`, ""); + lines.push("```" + ex.language, ex.code, "```", ""); + } + return lines.join("\n").trimEnd(); +} + +function renderFieldsTable(className, ctx) { + const cls = findClass(ctx.ir, className); + if (!cls) { + console.warn(`skill: class "${className}" not found`); + return ""; + } + const fields = cls.members.filter((m) => m.kind === "field"); + return ( + propertiesTable( + fields.map((f) => ({ + name: f.name, + type: f.type, + optional: f.defaultValue != null, // class fields with initializers are effectively optional + defaultValue: f.defaultValue, + description: f.description, + })), + ) || warnEmpty(className) + ); +} + +function splitOnce(s, sep) { + const i = s.indexOf(sep); + return i < 0 ? [s, ""] : [s.slice(0, i), s.slice(i + 1)]; +} + +// ---------- generated content ---------- + +function renderTaskList({ tasks }) { + const lines = []; + for (const [taskId, info] of tasks.supportedTasks) { + const aliases = aliasesFor(taskId, tasks); + const aliasSuffix = aliases.length ? ` _(alias: ${aliases.map((a) => `\`${a}\``).join(", ")})_` : ""; + lines.push(`- \`${taskId}\`${aliasSuffix} — default model: \`${info.defaultModel}\``); + } + return lines.join("\n"); +} + +function renderTaskRecipe(taskId, info, ctx) { + const cls = findClass(ctx.ir, info.pipelineClass); + const aliases = aliasesFor(taskId, ctx.tasks); + const lines = [`**Default model:** \`${info.defaultModel}\``]; + if (aliases.length) lines.push(`**Aliases:** ${aliases.map((a) => `\`${a}\``).join(", ")}`); + lines.push(""); + if (cls?.description) lines.push(cls.description.trim(), ""); + for (const ex of cls?.examples ?? []) { + if (ex.title) lines.push(`**Example:** ${ex.title}`); + lines.push("```" + ex.language, ex.code, "```", ""); + } + return lines.join("\n").trimEnd(); +} + +function renderTypedefTable(name, ctx) { + const td = findTypedef(ctx.ir, name); + if (!td) { + console.warn(`skill: typedef "${name}" not found`); + return ""; + } + + // Discriminated union (`A | B | C`): render one table per variant. + const variants = splitUnion(td.type); + if (variants.length > 1) { + const parts = []; + for (const variantName of variants) { + const table = propertiesTable(collectProperties(variantName, ctx.ir)); + if (!table) continue; + parts.push(`**\`${variantName}\`**`, "", table, ""); + } + return parts.length ? parts.join("\n").trimEnd() : warnEmpty(name); + } + + const props = collectProperties(name, ctx.ir); + return propertiesTable(props) || warnEmpty(name); +} + +function warnEmpty(name) { + console.warn(`skill: typedef "${name}" has no properties to render`); + return ""; +} + +function propertiesTable(props) { + if (!props.length) return ""; + const lines = ["| Option | Type | Description |", "|--------|------|-------------|"]; + for (const p of props) { + const type = p.type ? renderTypedefType(p.type) : ""; + const nameCell = p.optional ? `\`${p.name}\`?` : `\`${p.name}\``; + const desc = prepareCell(p.description) + (p.defaultValue != null ? ` _(default: \`${p.defaultValue}\`)_` : ""); + lines.push(`| ${nameCell} | ${type} | ${desc} |`); + } + return lines.join("\n"); +} + +// Split a type string on top-level `|`, returning just the referenced names. +// Returns [] if the type doesn't look like a union of simple names. +function splitUnion(type) { + if (!type) return []; + const parts = []; + let depth = 0; + let buf = ""; + for (const ch of type) { + if ("<({[".includes(ch)) depth++; + else if (">)}]".includes(ch)) depth--; + if (depth === 0 && ch === "|") { + parts.push(buf.trim()); + buf = ""; + } else { + buf += ch; + } + } + if (buf.trim()) parts.push(buf.trim()); + if (parts.length < 2) return []; + if (!parts.every((p) => /^[A-Za-z_$][\w$.]*$/.test(p))) return []; + return parts; +} + +function renderClassSummary(name, ctx) { + const cls = findClass(ctx.ir, name); + if (!cls) { + console.warn(`skill: class "${name}" not found`); + return ""; + } + const lines = []; + if (cls.description) lines.push(cls.description.trim(), ""); + const fields = cls.members.filter((m) => m.kind !== "method"); + const methods = cls.members.filter((m) => m.kind === "method" && m.description); + if (fields.length) { + lines.push("**Fields**", ""); + for (const f of fields) { + const type = f.type ? ` (\`${f.type}\`)` : ""; + lines.push(`- \`${f.name}\`${type}${f.description ? ` — ${firstLine(f.description)}` : ""}`); + } + lines.push(""); + } + if (methods.length) { + lines.push("**Methods**", ""); + for (const m of methods) { + // Only show top-level params in the summary signature — `options.foo` + // rows are nested options, covered by the linked typedef. + const params = (m.params ?? []) + .filter((p) => p.name && !p.name.includes(".")) + .map((p) => (p.optional ? `[${p.name}]` : p.name)) + .join(", "); + const ret = m.returns?.type ? ` → \`${prettifyReturnType(m.returns.type)}\`` : ""; + lines.push(`- \`${m.name}(${params})\`${ret} — ${firstLine(m.description)}`); + } + lines.push(""); + } + return lines.join("\n").trimEnd(); +} + +function prettifyReturnType(raw) { + return raw + .replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1") + .replace(/\s+/g, " ") + .trim(); +} + +// Flatten a typedef into its effective properties. If the typedef's type is +// an intersection like `A & B`, collect properties from each referenced +// typedef in declaration order (de-duplicated by name). +function collectProperties(name, ir) { + const typedef = findTypedef(ir, name); + if (!typedef) return []; + if (typedef.properties?.length) return typedef.properties; + + const parts = (typedef.type ?? "") + .split("&") + .map((s) => s.trim()) + .filter(Boolean); + if (parts.length < 2) return []; + + const seen = new Map(); + for (const part of parts) { + for (const p of collectProperties(part, ir)) { + if (!seen.has(p.name)) seen.set(p.name, p); + } + } + return [...seen.values()]; +} + +// Use the IR's cross-reference index (which already prefers canonical +// definitions over re-export aliases) to find the authoritative typedef. +function findTypedef(ir, name) { + const modName = ir.typedefIndex.get(name); + if (!modName) return null; + const mod = ir.modules.find((m) => m.name === modName); + return mod?.typedefs.find((t) => t.name === name) ?? null; +} + +// Compact display: inside a markdown table cell we want inline code spans. +// Collapse `import('./x.js').Foo` to `Foo`, long inline object types to +// `object`, and strip newlines. +function renderTypedefType(raw) { + let compact = raw + .replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1") + .replace(/\s+/g, " ") + .trim(); + if (compact.length > 60 && (compact.startsWith("{") || /[({=]/.test(compact))) { + compact = "object"; + } + return "`" + compact.replace(/`/g, "\\`") + "`"; +} + +// Cells can't contain newlines or un-escaped pipes. `{@link url}` gets turned +// into a plain URL so agents can still see it. +function prepareCell(text) { + return (text || "") + .replace(/\{@link\s+([^}\s]+)(?:\s+[^}]+)?\}/g, "$1") + .replace(/\|/g, "\\|") + .replace(/\n+/g, " ") + .trim(); +} + +function renderTaskExamples(ctx) { + const lines = [ + GENERATED_BANNER, + "", + "# Task Examples", + "", + "Runnable recipes for every task supported by the `pipeline()` API, extracted", + "directly from each pipeline class's JSDoc.", + "", + ]; + for (const [taskId, info] of ctx.tasks.supportedTasks) { + lines.push(`## \`${taskId}\``, "", renderTaskRecipe(taskId, info, ctx), ""); + } + return finalize(lines); +} + +function renderApiSummary(ctx) { + const lines = [GENERATED_BANNER, "", "# API Summary", "", "Condensed index of publicly exported classes and functions.", ""]; + for (const mod of ctx.ir.modules) { + const classes = filterPublic(mod.classes, ctx.publicNames); + const functions = filterPublic(mod.functions, ctx.publicNames); + if (!classes.length && !functions.length) continue; + lines.push(`## \`${mod.name}\``, ""); + for (const cls of classes) lines.push(`- **\`${cls.name}\`** — ${firstLine(cls.description)}`); + for (const fn of functions) lines.push(`- \`${fn.name}()\` — ${firstLine(fn.description)}`); + lines.push(""); + } + return finalize(lines); +} + +function finalize(lines) { + return ( + lines + .join("\n") + .replace(/\n{3,}/g, "\n\n") + .trimEnd() + "\n" + ); +} + +function filterPublic(items, publicNames) { + return publicNames ? items.filter((it) => publicNames.has(it.name)) : items; +} + +function aliasesFor(taskId, tasks) { + return [...tasks.aliases.entries()].filter(([, t]) => t === taskId).map(([a]) => a); +} + +// ---------- helpers ---------- + +function findClass(ir, name) { + for (const mod of ir.modules) { + const cls = mod.classes.find((c) => c.name === name); + if (cls) return cls; + } + return null; +} + +function firstLine(text) { + if (!text) return "_(undocumented)_"; + const line = text.split("\n", 1)[0].trim(); + return line.endsWith(".") ? line : line + "."; +} diff --git a/packages/transformers/docs/scripts/lib/tasks.mjs b/packages/transformers/docs/scripts/lib/tasks.mjs new file mode 100644 index 000000000..1abad0969 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/tasks.mjs @@ -0,0 +1,68 @@ +// Extract the task catalog (`SUPPORTED_TASKS` + `TASK_ALIASES`) from the +// library source. The skill renderer uses this to emit one task recipe per +// supported pipeline task, with its canonical pipeline class and default model. + +import ts from "typescript"; +import fs from "node:fs"; + +export function extractTaskCatalog(indexFile) { + const source = fs.readFileSync(indexFile, "utf8"); + const sf = ts.createSourceFile(indexFile, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + + const supportedTasks = new Map(); // task-id -> { pipelineClass, defaultModel, type } + const aliases = new Map(); // alias -> task-id + + ts.forEachChild(sf, (node) => { + if (!ts.isVariableStatement(node)) return; + for (const decl of node.declarationList.declarations) { + if (!ts.isIdentifier(decl.name)) continue; + const literal = unwrapObjectFreeze(decl.initializer); + if (!literal) continue; + if (decl.name.text === "SUPPORTED_TASKS") parseSupportedTasks(literal, sf, supportedTasks); + else if (decl.name.text === "TASK_ALIASES") parseAliases(literal, sf, aliases); + } + }); + + return { supportedTasks, aliases }; +} + +// `Object.freeze({...})` -> the ObjectLiteralExpression argument, or null. +function unwrapObjectFreeze(init) { + if (!init || !ts.isCallExpression(init)) return null; + const arg = init.arguments[0]; + return arg && ts.isObjectLiteralExpression(arg) ? arg : null; +} + +function parseSupportedTasks(literal, sf, out) { + for (const prop of literal.properties) { + if (!ts.isPropertyAssignment(prop)) continue; + const task = stripQuotes(prop.name.getText(sf)); + if (!ts.isObjectLiteralExpression(prop.initializer)) continue; + const entry = { pipelineClass: null, defaultModel: null, type: null }; + for (const p of prop.initializer.properties) { + if (!ts.isPropertyAssignment(p)) continue; + const key = p.name.getText(sf); + if (key === "pipeline") entry.pipelineClass = p.initializer.getText(sf); + else if (key === "type") entry.type = stripQuotes(p.initializer.getText(sf)); + else if (key === "default" && ts.isObjectLiteralExpression(p.initializer)) { + for (const dp of p.initializer.properties) { + if (ts.isPropertyAssignment(dp) && dp.name.getText(sf) === "model") { + entry.defaultModel = stripQuotes(dp.initializer.getText(sf)); + } + } + } + } + out.set(task, entry); + } +} + +function parseAliases(literal, sf, out) { + for (const prop of literal.properties) { + if (!ts.isPropertyAssignment(prop)) continue; + out.set(stripQuotes(prop.name.getText(sf)), stripQuotes(prop.initializer.getText(sf))); + } +} + +function stripQuotes(s) { + return s.replace(/^['"`]|['"`]$/g, ""); +} diff --git a/packages/transformers/package.json b/packages/transformers/package.json index d5a3a1dca..9e9518de4 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -30,6 +30,7 @@ "test": "node --experimental-vm-modules --expose-gc node_modules/jest/bin/jest.js --verbose --logHeapUsage", "readme": "python ./docs/scripts/build_readme.py", "docs-api": "node ./docs/scripts/generate.js", + "docs-skill": "node ./docs/scripts/generate-skill.js", "docs-build": "doc-builder build transformers.js ./docs/source/ --not_python_module --build_dir ./docs/build/", "docs-preview": "doc-builder preview transformers.js ./docs/source/ --not_python_module" }, From 6c691b485a5ce04ab6e285f680c49023ff5e4b0e Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:32:37 -0400 Subject: [PATCH 027/103] improve docs --- .../transformers/docs/scripts/generate.js | 17 ++-- packages/transformers/docs/scripts/lib/ir.mjs | 2 +- .../docs/scripts/lib/render-api.mjs | 49 +++++++--- .../docs/scripts/lib/render-skill.mjs | 98 ++++++++++++++----- .../src/models/auto/modeling_auto.js | 31 ------ .../src/pipelines/text-generation.js | 2 +- packages/transformers/src/transformers.js | 33 +++++-- packages/transformers/src/utils/audio.js | 7 +- packages/transformers/src/utils/core.js | 7 +- packages/transformers/src/utils/image.js | 7 +- packages/transformers/src/utils/maths.js | 6 +- packages/transformers/src/utils/random.js | 1 + packages/transformers/src/utils/tensor.js | 8 +- 13 files changed, 167 insertions(+), 101 deletions(-) diff --git a/packages/transformers/docs/scripts/generate.js b/packages/transformers/docs/scripts/generate.js index 8c93a0da7..c9fdf4a35 100644 --- a/packages/transformers/docs/scripts/generate.js +++ b/packages/transformers/docs/scripts/generate.js @@ -17,20 +17,25 @@ const { ir, publicNames } = loadProject(root); clearExistingMarkdown(); for (const mod of ir.modules) { - if (!hasPublicContent(mod, publicNames)) { + const rendered = renderModule(mod, ir, { publicNames }); + if (!hasPublicBody(rendered)) { console.log(`skipped ${mod.name}.md — no public content`); continue; } - const output = renderModule(mod, ir, { publicNames }); const outputPath = path.resolve(outputDir, `${mod.name}.md`); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); - fs.writeFileSync(outputPath, output); + fs.writeFileSync(outputPath, rendered); console.log(`wrote ${mod.name}.md`); } -function hasPublicContent(mod, publicNames) { - const isPublic = (item) => publicNames.has(item.name); - return mod.classes.some(isPublic) || mod.functions.some(isPublic) || mod.constants.some(isPublic) || mod.typedefs.length > 0 || mod.callbacks.length > 0; +// A module earns a page when its render produces either a section +// (classes/functions/constants/typedefs) or a description that links out — the +// latter covers index / entry-point modules that exist to orient the reader. +// Modules that reduce to a bare title + prose get skipped. +function hasPublicBody(markdown) { + if (/^## /m.test(markdown)) return true; + const body = markdown.replace(/^# [^\n]+\n/, ""); + return /\]\(/.test(body); } function clearExistingMarkdown() { diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index df5d7fcaa..ae98803b6 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -200,5 +200,5 @@ function gatherExamples(description) { } function isPrivate(entity) { - return entity.tags?.some((t) => t.tag === "private"); + return entity.tags?.some((t) => t.tag === "private" || t.tag === "internal"); } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 96d6b1280..7b88f31af 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -29,7 +29,8 @@ export function renderModule(mod, ir, opts = {}) { for (const c of constants) out.push(...renderConstant(c, ctx)); } if (mod.typedefs.length) { - const rendered = mod.typedefs.flatMap((td) => renderTypedef(td, ctx)); + const publicTypedefs = mod.typedefs.filter((td) => !isInternalTypedef(td)); + const rendered = publicTypedefs.flatMap((td) => renderTypedef(td, ctx)); if (rendered.length) out.push("## Type Definitions", "", ...rendered); } if (mod.callbacks.length) { @@ -60,6 +61,13 @@ function renderExample(ex) { return lines; } +// Descriptions carried over from the Python library sometimes start with a +// reST-style `[`TypeName`]` cross-reference that JavaScript readers can't follow. +// Strip the artifact from the start of the first paragraph; leave the rest alone. +function cleanDescription(text) { + return text.trim().replace(/^\[`?([A-Za-z_$][\w$.]*)`?\]\s*/, ""); +} + // `{@link url}` / `{@link url Text}` / `{@link Symbol}` -> markdown. function expandInlineLinks(text) { return text.replace(/\{@link\s+([^}\s]+)(?:\s+([^}]+))?\}/g, (_, target, label) => { @@ -72,7 +80,7 @@ function expandInlineLinks(text) { function renderClass(cls, ctx) { const lines = [`### ${cls.name}`, ""]; - if (cls.description) lines.push(cls.description.trim(), ""); + if (cls.description) lines.push(cleanDescription(cls.description), ""); for (const ex of cls.examples) lines.push(...renderExample(ex)); for (const m of cls.members) { @@ -95,17 +103,18 @@ function renderField(f, ctx, parent) { return lines; } -// Internal-looking (`_`-prefixed) methods need prose to earn a spot — bare -// param/return signatures are usually implementation details. +// `_`-prefixed methods are the library's convention for internal / subclass-only +// hooks — they're not part of the user-facing API. Exception: `constructor` is +// kept even though it has no leading underscore. function shouldRenderMethod(m) { - if (m.name.startsWith("_") && !m.description) return false; + if (m.name.startsWith("_")) return false; return m.description || m.params?.length || m.returns?.description || m.returns?.type || m.examples?.length || m.throws?.length; } function renderFunction(fn, ctx, depth, parent = null) { const lines = [`${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; if (fn.deprecated) lines.push("> **Deprecated**", ""); - if (fn.description) lines.push(fn.description.trim(), ""); + if (fn.description) lines.push(cleanDescription(fn.description), ""); if (fn.params?.length) { lines.push("**Parameters**", "", ...renderParamList(fn.params, ctx), ""); } @@ -138,7 +147,7 @@ function signature(fn, parent) { function renderCallback(cb, ctx) { const lines = [`### ${cb.name}`, ""]; - if (cb.description) lines.push(cb.description.trim(), ""); + if (cb.description) lines.push(cleanDescription(cb.description), ""); if (cb.params?.length) { lines.push("**Parameters**", "", ...renderParamList(cb.params, ctx), ""); } @@ -204,22 +213,36 @@ function simpleName(name) { function renderConstant(c, ctx) { const type = c.type ? ` : ${renderType(c.type, ctx)}` : ""; const lines = [`### \`${c.name}\`${type}`, ""]; - if (c.description) lines.push(c.description.trim(), ""); + if (c.description) lines.push(cleanDescription(c.description), ""); for (const ex of c.examples) lines.push(...renderExample(ex)); return lines; } +// Skip typedefs that exist purely to thread generic parameters through the +// type system (`@typedef {T} Name`, `_`-prefixed names) or that are opaque +// placeholders with no useful content to show a reader. +function isInternalTypedef(td) { + if (td.name.startsWith("_")) return true; + const type = (td.type ?? "").trim(); + if (/^[A-Z]$/.test(type) && !td.description && !td.properties?.length) return true; + if (type === "object" && !td.description && !td.properties?.length) return true; + return false; +} + function renderTypedef(td, ctx) { const displayed = td.type ? renderType(td.type, { ...ctx, selfName: td.name }) : ""; const isSelfReference = displayed === `\`${td.name}\``; + const isGenericPassthrough = /^`[A-Z]`$/.test(displayed); + const typeIsShowable = displayed && displayed.length < 120 && !displayed.startsWith("`{") && !isSelfReference && !isGenericPassthrough; - // A typedef whose only job is to re-export a name (`@typedef {import('x').Foo} Foo`) - // adds no information — the canonical page is linked from wherever the name appears. - if (!td.description && !td.properties?.length && isSelfReference) return []; + // Don't emit an empty `### Name` heading. A typedef needs *something* the + // reader can't derive from the name alone: a description, properties, or a + // concise displayable type. + if (!td.description && !td.properties?.length && !typeIsShowable) return []; const lines = [`### ${td.name}`, ""]; - if (td.description) lines.push(td.description.trim(), ""); - if (displayed && displayed.length < 120 && !displayed.startsWith("`{") && !isSelfReference) { + if (td.description) lines.push(cleanDescription(td.description), ""); + if (typeIsShowable) { lines.push(`_Type:_ ${displayed}`, ""); } if (td.properties?.length) { diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index 8814741e6..db2e2f64a 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -11,6 +11,7 @@ import fs from "node:fs"; import path from "node:path"; const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-skill.js -->"; +const DOCS_SITE = "https://huggingface.co/docs/transformers.js/api"; export function renderSkill({ ir, tasks, publicNames, skillDir }) { const ctx = { ir, tasks, publicNames: publicNames ?? null }; @@ -18,11 +19,11 @@ export function renderSkill({ ir, tasks, publicNames, skillDir }) { // Rewrite fully-generated reference files. const refDir = path.join(skillDir, "references"); fs.mkdirSync(refDir, { recursive: true }); - fs.writeFileSync(path.join(refDir, "TASK_EXAMPLES.md"), renderTaskExamples(ctx)); - fs.writeFileSync(path.join(refDir, "API_SUMMARY.md"), renderApiSummary(ctx)); + fs.writeFileSync(path.join(refDir, "TASKS.md"), absolutize(renderTasks(ctx))); // Expand `<!-- @generated:start id=... -->` markers in every hand-written // markdown file under the skill directory. Prose outside markers is preserved. + // Only the generated blocks get absolutized — hand-authored prose is left alone. for (const file of walkMarkdown(skillDir)) { const original = fs.readFileSync(file, "utf8"); if (!original.includes("@generated:start")) continue; @@ -31,6 +32,16 @@ export function renderSkill({ ir, tasks, publicNames, skillDir }) { } } +// Skill files live outside the docs site, so relative `./foo.md#bar` links — +// inherited from JSDoc descriptions and from our typedef cross-reference +// renderer — have to be rewritten to the public docs URL. `.md` becomes the +// extensionless path the site uses. +function absolutize(markdown) { + return markdown.replace(/\]\(\.\/([^)\s]+?)\.md(#[^)\s]*)?\)/g, (_, page, anchor = "") => { + return `](${DOCS_SITE}/${page}${anchor})`; + }); +} + function walkMarkdown(dir) { const out = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { @@ -52,7 +63,7 @@ function injectMarkers(original, ctx) { console.warn(`skill: unknown marker id "${id}"`); return match; } - return `${startTag}\n${content.trimEnd()}\n${endTag}`; + return `${startTag}\n${absolutize(content).trimEnd()}\n${endTag}`; }); } @@ -231,7 +242,7 @@ function renderClassSummary(name, ctx) { lines.push("**Fields**", ""); for (const f of fields) { const type = f.type ? ` (\`${f.type}\`)` : ""; - lines.push(`- \`${f.name}\`${type}${f.description ? ` — ${firstLine(f.description)}` : ""}`); + lines.push(`- \`${f.name}\`${type}${f.description ? ` — ${firstSentence(f.description)}` : ""}`); } lines.push(""); } @@ -245,7 +256,7 @@ function renderClassSummary(name, ctx) { .map((p) => (p.optional ? `[${p.name}]` : p.name)) .join(", "); const ret = m.returns?.type ? ` → \`${prettifyReturnType(m.returns.type)}\`` : ""; - lines.push(`- \`${m.name}(${params})\`${ret} — ${firstLine(m.description)}`); + lines.push(`- \`${m.name}(${params})\`${ret} — ${firstSentence(m.description)}`); } lines.push(""); } @@ -315,34 +326,57 @@ function prepareCell(text) { .trim(); } -function renderTaskExamples(ctx) { +// Per-task recipe page. Grouped by modality so readers can find the task they +// want quickly, with a table of contents up front. +function renderTasks(ctx) { const lines = [ GENERATED_BANNER, "", - "# Task Examples", + "# Tasks", + "", + "Runnable recipes for every task exposed through the `pipeline()` API, grouped by modality.", + "Each section is pulled from the pipeline class's JSDoc, so the examples stay in sync with the library.", "", - "Runnable recipes for every task supported by the `pipeline()` API, extracted", - "directly from each pipeline class's JSDoc.", + "## Contents", "", ]; - for (const [taskId, info] of ctx.tasks.supportedTasks) { - lines.push(`## \`${taskId}\``, "", renderTaskRecipe(taskId, info, ctx), ""); + const groups = groupTasksByModality(ctx.tasks.supportedTasks); + for (const [group, ids] of groups) { + lines.push(`**${group}** — ${ids.map((id) => `[\`${id}\`](#${taskAnchor(id)})`).join(" · ")}`, ""); + } + for (const [group, ids] of groups) { + lines.push(`## ${group}`, ""); + for (const id of ids) { + const info = ctx.tasks.supportedTasks.get(id); + lines.push(`### \`${id}\``, "", renderTaskRecipe(id, info, ctx), ""); + } } return finalize(lines); } -function renderApiSummary(ctx) { - const lines = [GENERATED_BANNER, "", "# API Summary", "", "Condensed index of publicly exported classes and functions.", ""]; - for (const mod of ctx.ir.modules) { - const classes = filterPublic(mod.classes, ctx.publicNames); - const functions = filterPublic(mod.functions, ctx.publicNames); - if (!classes.length && !functions.length) continue; - lines.push(`## \`${mod.name}\``, ""); - for (const cls of classes) lines.push(`- **\`${cls.name}\`** — ${firstLine(cls.description)}`); - for (const fn of functions) lines.push(`- \`${fn.name}()\` — ${firstLine(fn.description)}`); - lines.push(""); +function taskAnchor(id) { + return id.toLowerCase().replace(/[^a-z0-9]+/g, "-"); +} + +// Keyword-based grouping keeps the TOC stable without hard-coding per-task +// metadata. A task falls into the first modality whose keyword matches its id. +// Order matters: `text-to-audio` must hit "Audio" before "Text". +const MODALITY_RULES = [ + ["Audio", /audio|speech|asr|tts/], + ["Vision", /image|vision|depth|object|segment|background|document/], + ["Text", /text|translation|summar|generation|question|fill|mask|classification|ner/], + ["Embeddings", /feature|embedding/], +]; + +function groupTasksByModality(supportedTasks) { + const groups = new Map(MODALITY_RULES.map(([name]) => [name, []])); + groups.set("Other", []); + for (const id of supportedTasks.keys()) { + const group = MODALITY_RULES.find(([, re]) => re.test(id))?.[0] ?? "Other"; + groups.get(group).push(id); } - return finalize(lines); + for (const [name, ids] of [...groups]) if (!ids.length) groups.delete(name); + return groups; } function finalize(lines) { @@ -372,8 +406,22 @@ function findClass(ir, name) { return null; } -function firstLine(text) { +// Extract the first complete sentence. A line break inside a paragraph is not +// a sentence boundary, so we re-flow a paragraph onto one line before cutting. +function firstSentence(text) { if (!text) return "_(undocumented)_"; - const line = text.split("\n", 1)[0].trim(); - return line.endsWith(".") ? line : line + "."; + const paragraph = text + .split(/\n\s*\n/, 1)[0] + .replace(/\s+/g, " ") + .trim(); + const sanitized = stripDocArtifacts(paragraph); + const match = sanitized.match(/^(.+?[.!?])(?=\s|$)/); + const sentence = (match ? match[1] : sanitized).trim(); + return sentence.endsWith(".") || sentence.endsWith("!") || sentence.endsWith("?") ? sentence : sentence + "."; +} + +// Descriptions copied from the Python library sometimes start with +// `[`TypeName`]` (reST cross-reference syntax). Drop the leading artifact. +function stripDocArtifacts(text) { + return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\]\s*/, ""); } diff --git a/packages/transformers/src/models/auto/modeling_auto.js b/packages/transformers/src/models/auto/modeling_auto.js index 1f15a4b20..a6ba8a25b 100644 --- a/packages/transformers/src/models/auto/modeling_auto.js +++ b/packages/transformers/src/models/auto/modeling_auto.js @@ -139,7 +139,6 @@ class PretrainedMixin { /** * Helper class which is used to instantiate pretrained models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -157,7 +156,6 @@ export class AutoModel extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained sequence classification models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -172,7 +170,6 @@ export class AutoModelForSequenceClassification extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained token classification models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -187,7 +184,6 @@ export class AutoModelForTokenClassification extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained sequence-to-sequence models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -202,7 +198,6 @@ export class AutoModelForSeq2SeqLM extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained sequence-to-sequence speech-to-text models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -217,7 +212,6 @@ export class AutoModelForSpeechSeq2Seq extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained sequence-to-sequence text-to-spectrogram models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -232,7 +226,6 @@ export class AutoModelForTextToSpectrogram extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained text-to-waveform models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -247,7 +240,6 @@ export class AutoModelForTextToWaveform extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained causal language models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -262,7 +254,6 @@ export class AutoModelForCausalLM extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained masked language models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -277,7 +268,6 @@ export class AutoModelForMaskedLM extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained question answering models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -292,7 +282,6 @@ export class AutoModelForQuestionAnswering extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained vision-to-sequence models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -307,7 +296,6 @@ export class AutoModelForVision2Seq extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained image classification models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -322,7 +310,6 @@ export class AutoModelForImageClassification extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained image segmentation models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -337,7 +324,6 @@ export class AutoModelForImageSegmentation extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained image segmentation models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -352,7 +338,6 @@ export class AutoModelForSemanticSegmentation extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained universal image segmentation models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -367,7 +352,6 @@ export class AutoModelForUniversalSegmentation extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained object detection models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -382,7 +366,6 @@ export class AutoModelForObjectDetection extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained zero-shot object detection models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -397,7 +380,6 @@ export class AutoModelForZeroShotObjectDetection extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained mask generation models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -412,7 +394,6 @@ export class AutoModelForMaskGeneration extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained connectionist temporal classification (CTC) models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -427,7 +408,6 @@ export class AutoModelForCTC extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained audio classification models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -442,7 +422,6 @@ export class AutoModelForAudioClassification extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained speaker embedding models (X-Vector) with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -457,7 +436,6 @@ export class AutoModelForXVector extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained audio frame (token) classification models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -472,7 +450,6 @@ export class AutoModelForAudioFrameClassification extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained document question answering models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -487,7 +464,6 @@ export class AutoModelForDocumentQuestionAnswering extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained image matting models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -502,7 +478,6 @@ export class AutoModelForImageMatting extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained image-to-image models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -517,7 +492,6 @@ export class AutoModelForImageToImage extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained depth estimation models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -532,7 +506,6 @@ export class AutoModelForDepthEstimation extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained surface-normal estimation models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -547,7 +520,6 @@ export class AutoModelForNormalEstimation extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained pose estimation models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -562,7 +534,6 @@ export class AutoModelForPoseEstimation extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained image feature extraction models with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -578,7 +549,6 @@ export class AutoModelForImageFeatureExtraction extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained vision-language models that map images and text to text * (image+text-to-text) with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript @@ -594,7 +564,6 @@ export class AutoModelForImageTextToText extends PretrainedMixin { /** * Helper class which is used to instantiate pretrained audio-language models that map audio and text to text * (audio+text-to-text) with the `from_pretrained` function. - * The chosen model class is determined by the type specified in the model config. * * **Example:** * ```javascript diff --git a/packages/transformers/src/pipelines/text-generation.js b/packages/transformers/src/pipelines/text-generation.js index 31ae8221e..900841165 100644 --- a/packages/transformers/src/pipelines/text-generation.js +++ b/packages/transformers/src/pipelines/text-generation.js @@ -54,7 +54,7 @@ function isChat(x) { /** * Language generation pipeline using any `ModelWithLMHead` or `ModelForCausalLM`. * This pipeline predicts the words that will follow a specified text prompt. - * NOTE: For the full list of generation parameters, see [`GenerationConfig`](./utils/generation#module_utils/generation.GenerationConfig). + * For the full list of generation parameters, see [`GenerationConfig`](./generation/configuration_utils.md#generationconfig). * * **Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). * ```javascript diff --git a/packages/transformers/src/transformers.js b/packages/transformers/src/transformers.js index 1b77f07c6..84f1e2881 100644 --- a/packages/transformers/src/transformers.js +++ b/packages/transformers/src/transformers.js @@ -1,13 +1,30 @@ /** - * @file Entry point for the Transformers.js library. Only the exports from this file - * are available to the end user, and are grouped as follows: + * @file Entry point for the Transformers.js library. Everything re-exported + * from this file is part of the public API. * - * 1. [Environment variables](./env) - * 2. [Pipelines](./pipelines) - * 3. [Models](./models) - * 4. [Tokenizers](./tokenizers) - * 5. [Processors](./processors) - * 6. [Configs](./configs) + * **High-level** + * - [`pipeline()`](./pipelines.md#pipeline) — the one-call entry point for every task. + * - [Environment](./env.md) — global configuration (`env`, `LogLevel`). + * + * **Model loading** + * - [Pipelines](./pipelines.md) — task-specific pipeline classes. + * - [Models](./models.md) — `AutoModel*` classes and the base `PreTrainedModel`. + * - [Tokenizers](./tokenizers.md) — `AutoTokenizer` and friends. + * - [Processors](./processors.md) — feature extractors and image/audio processors. + * - [Configs](./configs.md) — `AutoConfig` and `PretrainedConfig`. + * + * **Generation** + * - [Generation config](./generation/configuration_utils.md) — `GenerationConfig` fields. + * - [Logits processors](./generation/logits_process.md) — sampling and constraint logits processors. + * - [Stopping criteria](./generation/stopping_criteria.md) — when generation halts. + * - [Streamers](./generation/streamers.md) — token streaming. + * + * **Utilities** + * - [Tensors](./utils/tensor.md) — `Tensor`, shape ops, math, I/O. + * - [Audio](./utils/audio.md) — `RawAudio`, `load_audio`. + * - [Images](./utils/image.md) — `RawImage`. + * - [Model registry](./utils/model_registry.md) — cache and file inspection. + * - [Random](./utils/random.md) — seedable MT19937 PRNG. * * @module transformers */ diff --git a/packages/transformers/src/utils/audio.js b/packages/transformers/src/utils/audio.js index dc25ab543..71649b184 100644 --- a/packages/transformers/src/utils/audio.js +++ b/packages/transformers/src/utils/audio.js @@ -1,8 +1,8 @@ /** - * @file Helper module for audio processing. + * @file Audio I/O helpers. * - * These functions and classes are only used internally, - * meaning an end-user shouldn't need to access anything here. + * Decode audio files and URLs into the `Float32Array` pipelines expect, + * and wrap generated waveforms in `RawAudio` for playback or saving. * * @module utils/audio */ @@ -76,6 +76,7 @@ export async function load_audio(url, sampling_rate) { /** * @deprecated Use {@link load_audio} instead. + * @internal */ export const read_audio = load_audio; diff --git a/packages/transformers/src/utils/core.js b/packages/transformers/src/utils/core.js index 5a545826e..e897d1423 100644 --- a/packages/transformers/src/utils/core.js +++ b/packages/transformers/src/utils/core.js @@ -1,8 +1,9 @@ /** - * @file Core utility functions/classes for Transformers.js. + * @file Shared types that describe model loading progress. * - * These are only used internally, meaning an end-user shouldn't - * need to access anything here. + * `ProgressInfo` and its discriminated-union variants describe the payload + * passed to a `progress_callback` so callers can render download UIs, log + * byte counts, and react to the `ready` event. * * @module utils/core */ diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index 688d5e9e5..3da3f36cc 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -1,8 +1,9 @@ /** - * @file Helper module for image processing. + * @file Image I/O and manipulation. * - * These functions and classes are only used internally, - * meaning an end-user shouldn't need to access anything here. + * `RawImage` wraps a raw pixel buffer with width, height, and channel metadata; + * use `load_image()` to decode from paths, URLs, or Blobs, and the instance + * methods to resize, convert, and save. * * @module utils/image */ diff --git a/packages/transformers/src/utils/maths.js b/packages/transformers/src/utils/maths.js index 8a46edb57..1ba3865c3 100644 --- a/packages/transformers/src/utils/maths.js +++ b/packages/transformers/src/utils/maths.js @@ -1,8 +1,6 @@ /** - * @file Helper module for mathematical processing. - * - * These functions and classes are only used internally, - * meaning an end-user shouldn't need to access anything here. + * @file Numerical helpers — softmax, dot product, cosine similarity, and the + * typed-array utilities shared across the library. * * @module utils/maths */ diff --git a/packages/transformers/src/utils/random.js b/packages/transformers/src/utils/random.js index b80a5521f..fbeca3166 100644 --- a/packages/transformers/src/utils/random.js +++ b/packages/transformers/src/utils/random.js @@ -109,6 +109,7 @@ export class Random { * then applies the standard MT19937 tempering transform. * * @returns {number} A random integer in the range [0, 2^32 - 1]. + * @internal */ _int32() { const mt = this._mt; diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 8d1cecbe1..049a9a866 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -1,8 +1,10 @@ /** - * @file Helper module for `Tensor` processing. + * @file Tensors and tensor operations. * - * These functions and classes are only used internally, - * meaning an end-user shouldn't need to access anything here. + * `Tensor` is the typed n-dimensional array used throughout the library for + * model inputs and outputs. The functions in this module create, transform, + * and combine tensors — shape manipulation, slicing, reductions, math ops, + * and the `.tolist()` / `.item()` escape hatches back to plain JavaScript. * * @module utils/tensor */ From f587689ca906079a60f57992766d457b13a4dd28 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:39:30 -0400 Subject: [PATCH 028/103] add ai contents --- .ai/AGENTS.md | 46 + .ai/skills/transformers-js/SKILL.md | 160 +++ .../references/CONFIGURATION.md | 172 ++++ .../references/PIPELINE_OPTIONS.md | 224 +++++ .../transformers-js/references/TASKS.md | 929 ++++++++++++++++++ 5 files changed, 1531 insertions(+) create mode 100644 .ai/AGENTS.md create mode 100644 .ai/skills/transformers-js/SKILL.md create mode 100644 .ai/skills/transformers-js/references/CONFIGURATION.md create mode 100644 .ai/skills/transformers-js/references/PIPELINE_OPTIONS.md create mode 100644 .ai/skills/transformers-js/references/TASKS.md diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md new file mode 100644 index 000000000..9aac02cb7 --- /dev/null +++ b/.ai/AGENTS.md @@ -0,0 +1,46 @@ +# Agent guidelines for transformers.js + +This file governs AI-assisted contributions to the `huggingface/transformers.js` +repository. Agentic users must read and follow it before proposing changes. + +## Available skills + +- [`transformers-js`](skills/transformers-js/SKILL.md) — how to use the library + itself. Load this skill when working on code that calls `@huggingface/transformers`. + +## Contributing + +Before opening a pull request: + +- **Check for existing work.** Search open PRs and issues (`gh pr list`, `gh issue list`) + for the area you're touching. Don't duplicate someone else's in-progress work. +- **Coordinate on the issue first.** If an issue exists, comment on it before drafting a + PR. If approval from the issue author or a maintainer is unclear, stop and ask. +- **No low-value busywork.** Reformatting, renaming, and cosmetic-only PRs that don't + fix a reported problem or implement a requested feature are discouraged. +- **Run the full test suite locally.** `pnpm test` at the package root must pass before + you submit. Don't skip hooks with `--no-verify`. +- **Accountability.** AI-assisted patches are the responsibility of the human submitter. + Review the diff yourself before pushing. + +## Local setup + +```bash +pnpm install +pnpm --filter @huggingface/transformers test +pnpm --filter @huggingface/transformers typegen +pnpm --filter @huggingface/transformers docs-api +``` + +## Documentation generation + +The `docs/source/api/` markdown is auto-generated from JSDoc comments in `src/**/*.js` +by [`docs/scripts/generate.js`](../packages/transformers/docs/scripts/generate.js). +Regenerate after any JSDoc change: + +```bash +pnpm --filter @huggingface/transformers docs-api +``` + +The `.ai/skills/transformers-js/references/TASK_EXAMPLES.md` and `API_SUMMARY.md` files +are also auto-generated (see `docs-skill`). Do not edit them by hand. diff --git a/.ai/skills/transformers-js/SKILL.md b/.ai/skills/transformers-js/SKILL.md new file mode 100644 index 000000000..f4db1ffc6 --- /dev/null +++ b/.ai/skills/transformers-js/SKILL.md @@ -0,0 +1,160 @@ +--- +name: transformers-js +description: Run state-of-the-art machine learning models directly in JavaScript. `@huggingface/transformers` supports text, vision, audio, and multimodal tasks in browsers and Node.js / Bun / Deno, with WebGPU or WASM execution. +license: Apache-2.0 +metadata: + author: huggingface + repository: https://github.com/huggingface/transformers.js +compatibility: Node.js 18+ (or equivalent Bun / Deno), or a modern browser with ES modules. WebGPU requires runtime and hardware support; WASM is the fallback. Model downloads from the Hugging Face Hub require network access unless you ship models locally. +--- + +# transformers.js + +ML inference for JavaScript, without a Python server. Supports text, vision, audio, +and multimodal tasks through a single `pipeline()` entry point. + +## Install + +```bash +npm install @huggingface/transformers +``` + +## Quick start + +```javascript +import { pipeline } from "@huggingface/transformers"; + +const classifier = await pipeline("sentiment-analysis"); +const output = await classifier("I love transformers!"); +// [{ label: "POSITIVE", score: 0.9998 }] +``` + +`pipeline(task, model?, options?)` is the one function you need 90% of the time. +Passing no `model` uses the default for that task. + +## Supported tasks + +<!-- @generated:start id=task-list --> +- `text-classification` _(alias: `sentiment-analysis`)_ — default model: `Xenova/distilbert-base-uncased-finetuned-sst-2-english` +- `token-classification` _(alias: `ner`)_ — default model: `Xenova/bert-base-multilingual-cased-ner-hrl` +- `question-answering` — default model: `Xenova/distilbert-base-cased-distilled-squad` +- `fill-mask` — default model: `onnx-community/ettin-encoder-32m-ONNX` +- `summarization` — default model: `Xenova/distilbart-cnn-6-6` +- `translation` — default model: `Xenova/t5-small` +- `text2text-generation` — default model: `Xenova/flan-t5-small` +- `text-generation` — default model: `onnx-community/Qwen3-0.6B-ONNX` +- `zero-shot-classification` — default model: `Xenova/distilbert-base-uncased-mnli` +- `audio-classification` — default model: `Xenova/wav2vec2-base-superb-ks` +- `zero-shot-audio-classification` — default model: `Xenova/clap-htsat-unfused` +- `automatic-speech-recognition` _(alias: `asr`)_ — default model: `Xenova/whisper-tiny.en` +- `text-to-audio` _(alias: `text-to-speech`)_ — default model: `onnx-community/Supertonic-TTS-ONNX` +- `image-to-text` — default model: `Xenova/vit-gpt2-image-captioning` +- `image-classification` — default model: `Xenova/vit-base-patch16-224` +- `image-segmentation` — default model: `Xenova/detr-resnet-50-panoptic` +- `background-removal` — default model: `Xenova/modnet` +- `zero-shot-image-classification` — default model: `Xenova/clip-vit-base-patch32` +- `object-detection` — default model: `Xenova/detr-resnet-50` +- `zero-shot-object-detection` — default model: `Xenova/owlvit-base-patch32` +- `document-question-answering` — default model: `Xenova/donut-base-finetuned-docvqa` +- `image-to-image` — default model: `Xenova/swin2SR-classical-sr-x2-64` +- `depth-estimation` — default model: `onnx-community/depth-anything-v2-small` +- `feature-extraction` _(alias: `embeddings`)_ — default model: `onnx-community/all-MiniLM-L6-v2-ONNX` +- `image-feature-extraction` — default model: `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX` +<!-- @generated:end id=task-list --> + +For full recipes — every task, grouped by modality, with runnable code — +see [`references/TASKS.md`](references/TASKS.md). + +## Choosing a model + +Browse models compatible with transformers.js on the Hub: +<https://huggingface.co/models?library=transformers.js> + +Filter by task with the `pipeline_tag` parameter, e.g. +<https://huggingface.co/models?library=transformers.js&pipeline_tag=text-generation>. + +### Quantization + +Most pipelines accept a `dtype` option. Smaller dtypes download and run faster +at the cost of some accuracy: + +| `dtype` | Size | Use when | +|----------|-----------|----------------------------------------------------| +| `fp32` | Largest | Maximum accuracy, Node.js with lots of RAM | +| `fp16` | ~50% of fp32 | GPU / WebGPU inference | +| `q8` | ~25% of fp32 | Good default for browsers | +| `q4` | ~12% of fp32 | Tight memory budgets, large language models | + +```javascript +const pipe = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX", { + dtype: "q4", +}); +``` + +### Device + +Default is CPU/WASM. Pass `device: "webgpu"` to run on the GPU when available: + +```javascript +const pipe = await pipeline("sentiment-analysis", null, { device: "webgpu" }); +``` + +## Memory management + +Pipelines hold onto model weights and backend sessions. **Always call +`pipe.dispose()`** when you're done with one — especially in long-running +servers, before loading a replacement, or on component unmount. + +```javascript +const pipe = await pipeline("sentiment-analysis"); +try { + const result = await pipe("Great!"); +} finally { + await pipe.dispose(); +} +``` + +## Configuration + +The [`env`](https://huggingface.co/docs/transformers.js/api/env) export lets +you control model sources, caching, logging, and the fetch function. + +```javascript +import { env, LogLevel } from "@huggingface/transformers"; + +env.allowRemoteModels = true; +env.useFSCache = true; // Node.js: cache downloaded models on disk +env.useBrowserCache = true; // Browser: cache via the Cache API +env.logLevel = LogLevel.WARNING; +``` + +See [`references/CONFIGURATION.md`](references/CONFIGURATION.md) for the full +set of environment options, cache management, and private / gated models. + +## Pipeline options + +Every pipeline accepts a `progress_callback` for download progress plus options +controlling device, dtype, and caching. Task-specific call options (e.g. +`top_k`, `max_new_tokens`, streaming, chat templates) live with each task in +[`references/TASKS.md`](references/TASKS.md). Common options and their types: +[`references/PIPELINE_OPTIONS.md`](references/PIPELINE_OPTIONS.md). + +## Things to never do + +- **Don't reuse a disposed pipeline.** Create a new one with `pipeline(...)` after `dispose()`. +- **Don't recreate pipelines inside hot loops.** Create once, call many times. +- **Don't block startup on model downloads.** Show progress via `progress_callback`. +- **Don't fabricate model IDs.** Confirm a model exists on the Hub and has ONNX files + (look for an `onnx/` directory in the repo) before suggesting it to a user. + +## Reference documentation + +- Official site: <https://huggingface.co/docs/transformers.js> +- API reference: <https://huggingface.co/docs/transformers.js/api/pipelines> +- Examples repo: <https://github.com/huggingface/transformers.js-examples> + +This skill's local references: + +- [`TASKS.md`](references/TASKS.md) — recipes for every task, grouped by modality _(generated)_ +- [`CONFIGURATION.md`](references/CONFIGURATION.md) — `env` options, caching, model inspection +- [`PIPELINE_OPTIONS.md`](references/PIPELINE_OPTIONS.md) — common pipeline options, dtype, device, generation parameters diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md new file mode 100644 index 000000000..8ba19ba27 --- /dev/null +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -0,0 +1,172 @@ +# Configuration + +All global configuration lives on the `env` object exported from the package. +Mutating fields on `env` changes library-wide behavior at runtime. + +```javascript +import { env, LogLevel } from "@huggingface/transformers"; +``` + +## Quick examples + +<!-- @generated:start id=examples:env --> +**Example:** Disable remote models. + +```javascript +import { env } from '@huggingface/transformers'; +env.allowRemoteModels = false; +``` + +**Example:** Set local model path. + +```javascript +import { env } from '@huggingface/transformers'; +env.localModelPath = '/path/to/local/models/'; +``` + +**Example:** Set cache directory. + +```javascript +import { env } from '@huggingface/transformers'; +env.cacheDir = '/path/to/cache/directory/'; +``` +<!-- @generated:end id=examples:env --> + +## All options + +<!-- @generated:start id=typedef:TransformersEnvironment --> +| Option | Type | Description | +|--------|------|-------------| +| `version` | `string` | This version of Transformers.js. | +| `backends` | `object` | Expose environment variables of different backends, allowing users to set these variables if they want to. | +| `logLevel` | `number` | The logging level. Use LogLevel enum values. Defaults to LogLevel.ERROR. | +| `allowRemoteModels` | `boolean` | Whether to allow loading of remote files, defaults to `true`. If set to `false`, it will have the same effect as setting `local_files_only=true` when loading pipelines, models, tokenizers, processors, etc. | +| `remoteHost` | `string` | Host URL to load models from. Defaults to the Hugging Face Hub. | +| `remotePathTemplate` | `string` | Path template to fill in and append to `remoteHost` when loading models. | +| `allowLocalModels` | `boolean` | Whether to allow loading of local files, defaults to `false` if running in-browser, and `true` otherwise. If set to `false`, it will skip the local file check and try to load the model from the remote host. | +| `localModelPath` | `string` | Path to load local models from. Defaults to `/models/`. | +| `useFS` | `boolean` | Whether to use the file system to load files. By default, it is `true` if available. | +| `useBrowserCache` | `boolean` | Whether to use Cache API to cache models. By default, it is `true` if available. | +| `useFSCache` | `boolean` | Whether to use the file system to cache files. By default, it is `true` if available. | +| `cacheDir` | `string|null` | The directory to use for caching files with the file system. By default, it is `./.cache`. | +| `useCustomCache` | `boolean` | Whether to use a custom cache system (defined by `customCache`), defaults to `false`. | +| `customCache` | `CacheInterface|null` | The custom cache to use. Defaults to `null`. Note: this must be an object which implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. | +| `useWasmCache` | `boolean` | Whether to pre-load and cache WASM binaries and the WASM factory (.mjs) for ONNX Runtime. Defaults to `true` when cache is available. This can improve performance and enables offline usage by avoiding repeated downloads. | +| `cacheKey` | `string` | The cache key to use for storing models and WASM binaries. Defaults to 'transformers-cache'. | +| `experimental_useCrossOriginStorage` | `boolean` | Whether to use the Cross-Origin Storage API to cache model files across origins, allowing different sites to share the same cached model weights. Defaults to `false`. Requires the Cross-Origin Storage Chrome extension: https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih. The `experimental_` prefix indicates that the underlying browser API is not yet standardised and may change or be removed without a major version bump. For more information, see https://github.com/WICG/cross-origin-storage. | +| `fetch` | `(input: string | URL, init?: any) => Promise<any>` | The fetch function to use. Defaults to `fetch`. | +<!-- @generated:end id=typedef:TransformersEnvironment --> + +## Log levels + +Pass one of these values to `env.logLevel`: + +| Value | Numeric | +|----------------|---------| +| `LogLevel.DEBUG` | `10` | +| `LogLevel.INFO` | `20` | +| `LogLevel.WARNING` | `30` (default) | +| `LogLevel.ERROR` | `40` | +| `LogLevel.NONE` | `50` | + +Higher numbers suppress more output. Setting `env.logLevel` also propagates to +ONNX Runtime, so you get matching verbosity from the inference backend. + +## Common patterns + +**Development (fast iteration with remote models):** + +```javascript +env.allowRemoteModels = true; +env.useFSCache = true; +env.logLevel = LogLevel.INFO; +``` + +**Production Node.js server (air-gapped, local only):** + +```javascript +env.allowRemoteModels = false; +env.allowLocalModels = true; +env.localModelPath = "/opt/models"; +``` + +**Browser app with a CDN mirror:** + +```javascript +env.remoteHost = "https://cdn.example.com"; +env.useBrowserCache = true; +``` + +## Custom fetch (private / gated models, retries, etc.) + +Override `env.fetch` to inject auth headers, retry logic, or abort signals: + +```javascript +env.fetch = (url, init) => + fetch(url, { + ...init, + headers: { ...init?.headers, Authorization: `Bearer ${process.env.HF_TOKEN}` }, + }); +``` + +## Custom cache backends + +```javascript +env.useCustomCache = true; +env.customCache = { + async match(key) { /* return Response or undefined */ }, + async put(key, response) { /* persist */ }, +}; +``` + +The cache must implement the Web Cache API's `match` and `put` methods. + +## Inspecting models before loading + +`ModelRegistry` reports which files a model needs, whether they're cached +locally, which dtypes the model ships with, and can clear caches selectively. +Useful for pre-flight UI and disk management. + +```javascript +import { ModelRegistry } from "@huggingface/transformers"; + +const task = "feature-extraction"; +const modelId = "onnx-community/all-MiniLM-L6-v2-ONNX"; + +const cached = await ModelRegistry.is_pipeline_cached(task, modelId); +if (!cached) { + const files = await ModelRegistry.get_pipeline_files(task, modelId); + // ask the user to confirm before `pipeline(...)` downloads these. +} + +const dtypes = await ModelRegistry.get_available_dtypes(modelId); +const dtype = ["q4", "q8", "fp16", "fp32"].find((d) => dtypes.includes(d)); +const pipe = await pipeline(task, modelId, { dtype }); +``` + +<!-- @generated:start id=class:ModelRegistry --> +Static class for cache and file management operations. + +**Methods** + +- `get_files(modelId, [options])` → `Promise<string[]>` — Get all files (model, tokenizer, processor) needed for a model. +- `get_pipeline_files(task, modelId, [options])` → `Promise<string[]>` — Get all files needed for a specific pipeline task. +- `get_model_files(modelId, [options])` → `Promise<string[]>` — Get model files needed for a specific model. +- `get_tokenizer_files(modelId)` → `Promise<string[]>` — Get tokenizer files needed for a specific model. +- `get_processor_files(modelId)` → `Promise<string[]>` — Get processor files needed for a specific model. +- `get_available_dtypes(modelId, [options])` → `Promise<string[]>` — Detects which quantization levels (dtypes) are available for a model by checking which ONNX files exist on the hub or locally. +- `is_cached(modelId, [options])` → `Promise<boolean>` — Quickly checks if a model is fully cached by verifying `config.json` is present, then confirming all required files are cached. +- `is_cached_files(modelId, [options])` → `Promise<CacheCheckResult>` — Checks if all files for a given model are already cached, with per-file detail. +- `is_pipeline_cached(task, modelId, [options])` → `Promise<boolean>` — Quickly checks if all files for a specific pipeline task are cached by verifying `config.json` is present, then confirming all required files are cached. +- `is_pipeline_cached_files(task, modelId, [options])` → `Promise<CacheCheckResult>` — Checks if all files for a specific pipeline task are already cached, with per-file detail. +- `get_file_metadata(path_or_repo_id, filename, [options])` → `Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>` — Get metadata for a specific file without downloading it. +- `clear_cache(modelId, [options])` → `Promise<CacheClearResult>` — Clears all cached files for a given model. +- `clear_pipeline_cache(task, modelId, [options])` → `Promise<CacheClearResult>` — Clears all cached files for a specific pipeline task. +<!-- @generated:end id=class:ModelRegistry --> + +To reclaim disk space for a specific model or task: + +```javascript +await ModelRegistry.clear_cache(modelId); +await ModelRegistry.clear_pipeline_cache(task, modelId); +``` diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md new file mode 100644 index 000000000..11cf4a65d --- /dev/null +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -0,0 +1,224 @@ +# Pipeline options + +The `pipeline(task, model?, options?)` factory accepts a common set of options +across every task. Task-specific call options are documented on each pipeline +class in the [API reference](https://huggingface.co/docs/transformers.js/api/pipelines). + +## Common options + +<!-- @generated:start id=typedef:PretrainedModelOptions --> +| Option | Type | Description | +|--------|------|-------------| +| `progress_callback`? | `ProgressCallback` | If specified, this function will be called during model construction, to provide the user with progress updates. _(default: `null`)_ | +| `config`? | `PretrainedConfig` | Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when: - The model is a model provided by the library (loaded with the *model id* string of a pretrained model). - The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | +| `cache_dir`? | `string` | Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. _(default: `null`)_ | +| `local_files_only`? | `boolean` | Whether or not to only look at local files (e.g., not try downloading the model). _(default: `false`)_ | +| `revision`? | `string` | The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git. NOTE: This setting is ignored for local requests. _(default: `'main'`)_ | +| `subfolder`? | `string` | In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can specify the folder name here. _(default: `'onnx'`)_ | +| `model_file_name`? | `string` | If specified, load the model with this name (excluding the dtype and .onnx suffixes). Currently only valid for encoder- or decoder-only models. _(default: `null`)_ | +| `device`? | `DeviceType|Record<string, DeviceType>` | The device to run the model on. If not specified, the device will be chosen from the environment settings. _(default: `null`)_ | +| `dtype`? | `DataType|Record<string, DataType>` | The data type to use for the model. If not specified, the data type will be chosen from the environment settings. _(default: `null`)_ | +| `use_external_data_format`? | `ExternalData|Record<string, ExternalData>` | Whether to load the model using the external data format (used for models >= 2GB in size). _(default: `false`)_ | +| `session_options`? | `InferenceSession.SessionOptions` | (Optional) User-specified session options passed to the runtime. If not provided, suitable defaults will be chosen. | +<!-- @generated:end id=typedef:PretrainedModelOptions --> + +## Progress tracking + +```javascript +const pipe = await pipeline("sentiment-analysis", null, { + progress_callback: (info) => { + if (info.status === "progress") { + console.log(`${info.file}: ${info.progress.toFixed(1)}%`); + } + }, +}); +``` + +### `ProgressInfo` shape + +<!-- @generated:start id=typedef:ProgressInfo --> +**`InitiateProgressInfo`** + +| Option | Type | Description | +|--------|------|-------------| +| `status` | `'initiate'` | | +| `name` | `string` | The model id or directory path. | +| `file` | `string` | The name of the file. | + +**`DownloadProgressInfo`** + +| Option | Type | Description | +|--------|------|-------------| +| `status` | `'download'` | | +| `name` | `string` | The model id or directory path. | +| `file` | `string` | The name of the file. | + +**`ProgressStatusInfo`** + +| Option | Type | Description | +|--------|------|-------------| +| `status` | `'progress'` | | +| `name` | `string` | The model id or directory path. | +| `file` | `string` | The name of the file. | +| `progress` | `number` | A number between 0 and 100. | +| `loaded` | `number` | The number of bytes loaded. | +| `total` | `number` | The total number of bytes to be loaded. | + +**`DoneProgressInfo`** + +| Option | Type | Description | +|--------|------|-------------| +| `status` | `'done'` | | +| `name` | `string` | The model id or directory path. | +| `file` | `string` | The name of the file. | + +**`ReadyProgressInfo`** + +| Option | Type | Description | +|--------|------|-------------| +| `status` | `'ready'` | | +| `task` | `string` | The loaded task. | +| `model` | `string` | The loaded model. | + +**`TotalProgressInfo`** + +| Option | Type | Description | +|--------|------|-------------| +| `status` | `'progress_total'` | | +| `name` | `string` | The model id or directory path. | +| `progress` | `number` | A number between 0 and 100. | +| `loaded` | `number` | The number of bytes loaded. | +| `total` | `number` | The total number of bytes to be loaded. | +| `files` | `FilesLoadingMap` | A mapping of file names to their loading progress. | +<!-- @generated:end id=typedef:ProgressInfo --> + +## Device selection + +```javascript +// Default — CPU via WASM (most compatible) +await pipeline("sentiment-analysis", null); + +// GPU via WebGPU (Chrome 113+, fastest for big models) +await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX", { + device: "webgpu", + dtype: "fp16", +}); + +// Node.js with onnxruntime-node +await pipeline("sentiment-analysis", null, { device: "cpu" }); +``` + +Detect WebGPU support before attempting to use it: + +```javascript +const hasWebGPU = typeof navigator !== "undefined" && "gpu" in navigator; +const pipe = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX", { + device: hasWebGPU ? "webgpu" : "wasm", + dtype: hasWebGPU ? "fp16" : "q8", +}); +``` + +## Task-specific call options + +Once created, each pipeline is itself callable with task-specific options: + +```javascript +const classifier = await pipeline("text-classification"); +await classifier("great movie", { top_k: 3 }); + +const generator = await pipeline("text-generation"); +await generator("Once upon a time", { max_new_tokens: 100, temperature: 0.7 }); + +const translator = await pipeline("translation", "Xenova/nllb-200-distilled-600M"); +await translator("Hello", { src_lang: "eng_Latn", tgt_lang: "fra_Latn" }); +``` + +Task-specific recipes and every call option for each pipeline are in +[`TASKS.md`](./TASKS.md). + +## Generation parameters + +Every field below can be passed as an option to a `text-generation`, +`text2text-generation`, `translation`, or `summarization` pipeline call, or +anywhere `generate()` is invoked directly. + +<!-- @generated:start id=fields:GenerationConfig --> +| Option | Type | Description | +|--------|------|-------------| +| `max_length`? | `number` | The maximum length the generated tokens can have. Corresponds to the length of the input prompt + `max_new_tokens`. Its effect is overridden by `max_new_tokens`, if also set. _(default: `20`)_ | +| `max_new_tokens`? | `number` | The maximum numbers of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | +| `min_length`? | `number` | The minimum length of the sequence to be generated. Corresponds to the length of the input prompt + `min_new_tokens`. Its effect is overridden by `min_new_tokens`, if also set. _(default: `0`)_ | +| `min_new_tokens`? | `number` | The minimum numbers of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | +| `early_stopping`? | `boolean|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values: - `true`, where the generation stops as soon as there are `num_beams` complete candidates; - `false`, where an heuristic is applied and the generation stops when is it very unlikely to find better candidates; - `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | +| `max_time`? | `number` | The maximum amount of time you allow the computation to run for in seconds. Generation will still finish the current pass after allocated time has been passed. _(default: `null`)_ | +| `do_sample`? | `boolean` | Whether or not to use sampling; use greedy decoding otherwise. _(default: `false`)_ | +| `num_beams`? | `number` | Number of beams for beam search. 1 means no beam search. _(default: `1`)_ | +| `num_beam_groups`? | `number` | Number of groups to divide `num_beams` into in order to ensure diversity among different groups of beams. See [this paper](https://huggingface.co/papers/1610.02424) for more details. _(default: `1`)_ | +| `penalty_alpha`? | `number` | The values balance the model confidence and the degeneration penalty in contrastive search decoding. _(default: `null`)_ | +| `use_cache`? | `boolean` | Whether or not the model should use the past last key/values attentions (if applicable to the model) to speed up decoding. _(default: `true`)_ | +| `temperature`? | `number` | The value used to modulate the next token probabilities. _(default: `1.0`)_ | +| `top_k`? | `number` | The number of highest probability vocabulary tokens to keep for top-k-filtering. _(default: `50`)_ | +| `top_p`? | `number` | If set to float < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or higher are kept for generation. _(default: `1.0`)_ | +| `typical_p`? | `number` | Local typicality measures how similar the conditional probability of predicting a target token next is to the expected conditional probability of predicting a random token next, given the partial text already generated. If set to float < 1, the smallest set of the most locally typical tokens with probabilities that add up to `typical_p` or higher are kept for generation. See [this paper](https://huggingface.co/papers/2202.00666) for more details. _(default: `1.0`)_ | +| `epsilon_cutoff`? | `number` | If set to float strictly between 0 and 1, only tokens with a conditional probability greater than `epsilon_cutoff` will be sampled. In the paper, suggested values range from 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | +| `eta_cutoff`? | `number` | Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to float strictly between 0 and 1, a token is only considered if it is greater than either `eta_cutoff` or `sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits)))`. The latter term is intuitively the expected next token probability, scaled by `sqrt(eta_cutoff)`. In the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | +| `diversity_penalty`? | `number` | This value is subtracted from a beam's score if it generates a token same as any beam from other group at a particular time. Note that `diversity_penalty` is only effective if `group beam search` is enabled. _(default: `0.0`)_ | +| `repetition_penalty`? | `number` | The parameter for repetition penalty. 1.0 means no penalty. See [this paper](https://huggingface.co/papers/1909.05858) for more details. _(default: `1.0`)_ | +| `encoder_repetition_penalty`? | `number` | The paramater for encoder_repetition_penalty. An exponential penalty on sequences that are not in the original input. 1.0 means no penalty. _(default: `1.0`)_ | +| `length_penalty`? | `number` | Exponential penalty to the length that is used with beam-based generation. It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. _(default: `1.0`)_ | +| `no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size can only occur once. _(default: `0`)_ | +| `bad_words_ids`? | `number[][]` | List of token ids that are not allowed to be generated. In order to get the token ids of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | +| `force_words_ids`? | `number[][]|number[][][]` | List of token ids that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | +| `renormalize_logits`? | `boolean` | Whether to renormalize the logits after applying all the logits processors or warpers (including the custom ones). It's highly recommended to set this flag to `true` as the search algorithms suppose the score logits are normalized but some logit processors or warpers break the normalization. _(default: `false`)_ | +| `constraints`? | `Object[]` | Custom constraints that can be added to the generation to ensure that the output will contain the use of certain tokens as defined by `Constraint` objects, in the most sensible way possible. _(default: `null`)_ | +| `forced_bos_token_id`? | `number` | The id of the token to force as the first generated token after the `decoder_start_token_id`. Useful for multilingual models like mBART where the first generated token needs to be the target language token. _(default: `null`)_ | +| `forced_eos_token_id`? | `number|number[]` | The id of the token to force as the last generated token when `max_length` is reached. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | +| `remove_invalid_values`? | `boolean` | Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method to crash. Note that using `remove_invalid_values` can slow down generation. _(default: `false`)_ | +| `exponential_decay_length_penalty`? | `[number, number]` | This Tuple adds an exponentially increasing length penalty, after a certain amount of tokens have been generated. The tuple shall consist of: `(start_index, decay_factor)` where `start_index` indicates where penalty starts and `decay_factor` represents the factor of exponential decay. _(default: `null`)_ | +| `suppress_tokens`? | `number[]` | A list of tokens that will be suppressed at generation. The `SuppressTokens` logit processor will set their log probs to `-inf` so that they are not sampled. _(default: `null`)_ | +| `streamer`? | `TextStreamer` | A streamer that will be used to stream the generation. _(default: `null`)_ | +| `begin_suppress_tokens`? | `number[]` | A list of tokens that will be suppressed at the beginning of the generation. The `SuppressBeginTokens` logit processor will set their log probs to `-inf` so that they are not sampled. _(default: `null`)_ | +| `forced_decoder_ids`? | `[number, number][]` | A list of pairs of integers which indicates a mapping from generation indices to token indices that will be forced before sampling. For example, `[[1, 123]]` means the second generated token will always be a token of index 123. _(default: `null`)_ | +| `guidance_scale`? | `number` | The guidance scale for classifier free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages the model to generate samples that are more closely linked to the input prompt, usually at the expense of poorer quality. _(default: `null`)_ | +| `num_return_sequences`? | `number` | The number of independently computed returned sequences for each element in the batch. _(default: `1`)_ | +| `output_attentions`? | `boolean` | Whether or not to return the attentions tensors of all attention layers. See `attentions` under returned tensors for more details. _(default: `false`)_ | +| `output_hidden_states`? | `boolean` | Whether or not to return the hidden states of all layers. See `hidden_states` under returned tensors for more details. _(default: `false`)_ | +| `output_scores`? | `boolean` | Whether or not to return the prediction scores. See `scores` under returned tensors for more details. _(default: `false`)_ | +| `return_dict_in_generate`? | `boolean` | Whether or not to return a `ModelOutput` instead of a plain tuple. _(default: `false`)_ | +| `pad_token_id`? | `number` | The id of the *padding* token. _(default: `null`)_ | +| `bos_token_id`? | `number` | The id of the *beginning-of-sequence* token. _(default: `null`)_ | +| `eos_token_id`? | `number|number[]` | The id of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | +| `encoder_no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. _(default: `0`)_ | +| `decoder_start_token_id`? | `number` | If an encoder-decoder model starts decoding with a different token than *bos*, the id of that token. _(default: `null`)_ | +| `generation_kwargs`? | `Object` | Additional generation kwargs will be forwarded to the `generate` function of the model. Kwargs that are not present in `generate`'s signature will be used in the model forward pass. _(default: ``)_ | +<!-- @generated:end id=fields:GenerationConfig --> + +### Streaming tokens as they're produced + +```javascript +import { pipeline, TextStreamer } from "@huggingface/transformers"; + +const generator = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX"); +const streamer = new TextStreamer(generator.tokenizer, { + skip_prompt: true, + skip_special_tokens: true, + callback_function: (text) => process.stdout.write(text), +}); + +await generator("Tell me a short story.", { max_new_tokens: 200, streamer }); +``` + +For whisper-style chunk / token / finalize callbacks, use `WhisperTextStreamer`. + +### KV cache reuse across calls + +Passing the cache from one call to the next skips re-encoding the prefix: + +```javascript +import { pipeline, DynamicCache } from "@huggingface/transformers"; + +const generator = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX"); +const cache = new DynamicCache(); +await generator("Hello,", { max_new_tokens: 20, past_key_values: cache }); +await generator(" how are you?", { max_new_tokens: 20, past_key_values: cache }); +``` diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md new file mode 100644 index 000000000..c4ac1182f --- /dev/null +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -0,0 +1,929 @@ +<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-skill.js --> + +# Tasks + +Runnable recipes for every task exposed through the `pipeline()` API, grouped by modality. +Each section is pulled from the pipeline class's JSDoc, so the examples stay in sync with the library. + +## Contents + +**Audio** — [`audio-classification`](#audio-classification) · [`zero-shot-audio-classification`](#zero-shot-audio-classification) · [`automatic-speech-recognition`](#automatic-speech-recognition) · [`text-to-audio`](#text-to-audio) + +**Vision** — [`image-to-text`](#image-to-text) · [`image-classification`](#image-classification) · [`image-segmentation`](#image-segmentation) · [`background-removal`](#background-removal) · [`zero-shot-image-classification`](#zero-shot-image-classification) · [`object-detection`](#object-detection) · [`zero-shot-object-detection`](#zero-shot-object-detection) · [`document-question-answering`](#document-question-answering) · [`image-to-image`](#image-to-image) · [`depth-estimation`](#depth-estimation) · [`image-feature-extraction`](#image-feature-extraction) + +**Text** — [`text-classification`](#text-classification) · [`token-classification`](#token-classification) · [`question-answering`](#question-answering) · [`fill-mask`](#fill-mask) · [`summarization`](#summarization) · [`translation`](#translation) · [`text2text-generation`](#text2text-generation) · [`text-generation`](#text-generation) · [`zero-shot-classification`](#zero-shot-classification) + +**Embeddings** — [`feature-extraction`](#feature-extraction) + +## Audio + +### `audio-classification` + +**Default model:** `Xenova/wav2vec2-base-superb-ks` + +Audio classification pipeline using any `AutoModelForAudioClassification`. +This pipeline predicts the class of a raw waveform or an audio file. + +**Example:** Perform audio classification with `Xenova/wav2vec2-large-xlsr-53-gender-recognition-librispeech`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('audio-classification', 'Xenova/wav2vec2-large-xlsr-53-gender-recognition-librispeech'); +const audio = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; +const output = await classifier(audio); +// [ +// { label: 'male', score: 0.9981542229652405 }, +// { label: 'female', score: 0.001845747814513743 } +// ] +``` + +**Example:** Perform audio classification with `Xenova/ast-finetuned-audioset-10-10-0.4593` and return top 4 results. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('audio-classification', 'Xenova/ast-finetuned-audioset-10-10-0.4593'); +const audio = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/cat_meow.wav'; +const output = await classifier(audio, { top_k: 4 }); +// [ +// { label: 'Meow', score: 0.5617874264717102 }, +// { label: 'Cat', score: 0.22365376353263855 }, +// { label: 'Domestic animals, pets', score: 0.1141069084405899 }, +// { label: 'Animal', score: 0.08985692262649536 }, +// ] +``` + +### `zero-shot-audio-classification` + +**Default model:** `Xenova/clap-htsat-unfused` + +Zero shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you +provide an audio and a set of `candidate_labels`. + +**Example**: Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('zero-shot-audio-classification', 'Xenova/clap-htsat-unfused'); +const audio = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/dog_barking.wav'; +const candidate_labels = ['dog', 'vaccum cleaner']; +const scores = await classifier(audio, candidate_labels); +// [ +// { score: 0.9993992447853088, label: 'dog' }, +// { score: 0.0006007603369653225, label: 'vaccum cleaner' } +// ] +``` + +### `automatic-speech-recognition` + +**Default model:** `Xenova/whisper-tiny.en` +**Aliases:** `asr` + +Pipeline that aims at extracting spoken text contained within some audio. + +**Example:** Transcribe English. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny.en'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; +const output = await transcriber(url); +// { text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country." } +``` + +**Example:** Transcribe English w/ timestamps. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny.en'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; +const output = await transcriber(url, { return_timestamps: true }); +// { +// text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country." +// chunks: [ +// { timestamp: [0, 8], text: " And so my fellow Americans ask not what your country can do for you" } +// { timestamp: [8, 11], text: " ask what you can do for your country." } +// ] +// } +``` + +**Example:** Transcribe English w/ word-level timestamps. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny.en'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; +const output = await transcriber(url, { return_timestamps: 'word' }); +// { +// "text": " And so my fellow Americans ask not what your country can do for you ask what you can do for your country.", +// "chunks": [ +// { "text": " And", "timestamp": [0, 0.78] }, +// { "text": " so", "timestamp": [0.78, 1.06] }, +// { "text": " my", "timestamp": [1.06, 1.46] }, +// ... +// { "text": " for", "timestamp": [9.72, 9.92] }, +// { "text": " your", "timestamp": [9.92, 10.22] }, +// { "text": " country.", "timestamp": [10.22, 13.5] } +// ] +// } +``` + +**Example:** Transcribe French. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-small'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/french-audio.mp3'; +const output = await transcriber(url, { language: 'french', task: 'transcribe' }); +// { text: " J'adore, j'aime, je n'aime pas, je déteste." } +``` + +**Example:** Translate French to English. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-small'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/french-audio.mp3'; +const output = await transcriber(url, { language: 'french', task: 'translate' }); +// { text: " I love, I like, I don't like, I hate." } +``` + +**Example:** Transcribe/translate audio longer than 30 seconds. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny.en'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/ted_60.wav'; +const output = await transcriber(url, { chunk_length_s: 30, stride_length_s: 5 }); +// { text: " So in college, I was a government major, which means [...] So I'd start off light and I'd bump it up" } +``` + +### `text-to-audio` + +**Default model:** `onnx-community/Supertonic-TTS-ONNX` +**Aliases:** `text-to-speech` + +Text-to-audio generation pipeline using any `AutoModelForTextToWaveform` or `AutoModelForTextToSpectrogram`. +This pipeline generates an audio file from an input text and optional other conditional inputs. + +**Example:** Generate audio from text with `onnx-community/Supertonic-TTS-ONNX`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const synthesizer = await pipeline('text-to-speech', 'onnx-community/Supertonic-TTS-ONNX'); +const speaker_embeddings = 'https://huggingface.co/onnx-community/Supertonic-TTS-ONNX/resolve/main/voices/F1.bin'; +const output = await synthesizer('Hello there, how are you doing?', { speaker_embeddings }); +// RawAudio { +// audio: Float32Array(95232) [-0.000482565927086398, -0.0004853440332226455, ...], +// sampling_rate: 44100 +// } + +// Optional: Save the audio to a .wav file or Blob +await output.save('output.wav'); // You can also use `output.toBlob()` to access the audio as a Blob +``` + +**Example:** Multilingual speech generation with `Xenova/mms-tts-fra`. See [here](https://huggingface.co/models?pipeline_tag=text-to-speech&other=vits&sort=trending) for the full list of available languages (1107). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const synthesizer = await pipeline('text-to-speech', 'Xenova/mms-tts-fra'); +const output = await synthesizer('Bonjour'); +// RawAudio { +// audio: Float32Array(23808) [-0.00037693005288019776, 0.0003325853613205254, ...], +// sampling_rate: 16000 +// } +``` + +## Vision + +### `image-to-text` + +**Default model:** `Xenova/vit-gpt2-image-captioning` + +Image To Text pipeline using a `AutoModelForVision2Seq`. This pipeline predicts a caption for a given image. + +**Example:** Generate a caption for an image w/ `Xenova/vit-gpt2-image-captioning`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const captioner = await pipeline('image-to-text', 'Xenova/vit-gpt2-image-captioning'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/cats.jpg'; +const output = await captioner(url); +// [{ generated_text: 'a cat laying on a couch with another cat' }] +``` + +**Example:** Optical Character Recognition (OCR) w/ `Xenova/trocr-small-handwritten`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const captioner = await pipeline('image-to-text', 'Xenova/trocr-small-handwritten'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/handwriting.jpg'; +const output = await captioner(url); +// [{ generated_text: 'Mr. Brown commented icily.' }] +``` + +### `image-classification` + +**Default model:** `Xenova/vit-base-patch16-224` + +Image classification pipeline using any `AutoModelForImageClassification`. +This pipeline predicts the class of an image. + +**Example:** Classify an image. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('image-classification', 'Xenova/vit-base-patch16-224'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/tiger.jpg'; +const output = await classifier(url); +// [ +// { label: 'tiger, Panthera tigris', score: 0.632695734500885 }, +// ] +``` + +**Example:** Classify an image and return top `n` classes. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('image-classification', 'Xenova/vit-base-patch16-224'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/tiger.jpg'; +const output = await classifier(url, { top_k: 3 }); +// [ +// { label: 'tiger, Panthera tigris', score: 0.632695734500885 }, +// { label: 'tiger cat', score: 0.3634825646877289 }, +// { label: 'lion, king of beasts, Panthera leo', score: 0.00045060308184474707 }, +// ] +``` + +**Example:** Classify an image and return all classes. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('image-classification', 'Xenova/vit-base-patch16-224'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/tiger.jpg'; +const output = await classifier(url, { top_k: 0 }); +// [ +// { label: 'tiger, Panthera tigris', score: 0.632695734500885 }, +// { label: 'tiger cat', score: 0.3634825646877289 }, +// { label: 'lion, king of beasts, Panthera leo', score: 0.00045060308184474707 }, +// { label: 'jaguar, panther, Panthera onca, Felis onca', score: 0.00035465499968267977 }, +// ... +// ] +``` + +### `image-segmentation` + +**Default model:** `Xenova/detr-resnet-50-panoptic` + +Image segmentation pipeline using any `AutoModelForXXXSegmentation`. +This pipeline predicts masks of objects and their classes. + +**Example:** Perform image segmentation with `Xenova/detr-resnet-50-panoptic`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const segmenter = await pipeline('image-segmentation', 'Xenova/detr-resnet-50-panoptic'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/cats.jpg'; +const output = await segmenter(url); +// [ +// { label: 'remote', score: 0.9984649419784546, mask: RawImage { ... } }, +// { label: 'cat', score: 0.9994316101074219, mask: RawImage { ... } } +// ] +``` + +### `background-removal` + +**Default model:** `Xenova/modnet` + +Background removal pipeline using certain `AutoModelForXXXSegmentation`. +This pipeline removes the backgrounds of images. + +**Example:** Perform background removal with `Xenova/modnet`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const segmenter = await pipeline('background-removal', 'Xenova/modnet'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/portrait-of-woman_small.jpg'; +const output = await segmenter(url); +// RawImage { data: Uint8ClampedArray(648000) [ ... ], width: 360, height: 450, channels: 4 } +``` + +### `zero-shot-image-classification` + +**Default model:** `Xenova/clip-vit-base-patch32` + +Zero shot image classification pipeline. This pipeline predicts the class of +an image when you provide an image and a set of `candidate_labels`. + +**Example:** Zero shot image classification w/ `Xenova/clip-vit-base-patch32`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('zero-shot-image-classification', 'Xenova/clip-vit-base-patch32'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/tiger.jpg'; +const output = await classifier(url, ['tiger', 'horse', 'dog']); +// [ +// { score: 0.9993917942047119, label: 'tiger' }, +// { score: 0.0003519294841680676, label: 'horse' }, +// { score: 0.0002562698791734874, label: 'dog' } +// ] +``` + +### `object-detection` + +**Default model:** `Xenova/detr-resnet-50` + +Object detection pipeline using any `AutoModelForObjectDetection`. +This pipeline predicts bounding boxes of objects and their classes. + +**Example:** Run object-detection with `Xenova/detr-resnet-50`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50'); +const img = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/cats.jpg'; +const output = await detector(img, { threshold: 0.9 }); +// [{ +// score: 0.9976370930671692, +// label: "remote", +// box: { xmin: 31, ymin: 68, xmax: 190, ymax: 118 } +// }, +// ... +// { +// score: 0.9984092116355896, +// label: "cat", +// box: { xmin: 331, ymin: 19, xmax: 649, ymax: 371 } +// }] +``` + +### `zero-shot-object-detection` + +**Default model:** `Xenova/owlvit-base-patch32` + +Zero-shot object detection pipeline. This pipeline predicts bounding boxes of +objects when you provide an image and a set of `candidate_labels`. + +**Example:** Zero-shot object detection w/ `Xenova/owlvit-base-patch32`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const detector = await pipeline('zero-shot-object-detection', 'Xenova/owlvit-base-patch32'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/astronaut.png'; +const candidate_labels = ['human face', 'rocket', 'helmet', 'american flag']; +const output = await detector(url, candidate_labels); +// [ +// { +// score: 0.24392342567443848, +// label: 'human face', +// box: { xmin: 180, ymin: 67, xmax: 274, ymax: 175 } +// }, +// { +// score: 0.15129457414150238, +// label: 'american flag', +// box: { xmin: 0, ymin: 4, xmax: 106, ymax: 513 } +// }, +// { +// score: 0.13649864494800568, +// label: 'helmet', +// box: { xmin: 277, ymin: 337, xmax: 511, ymax: 511 } +// }, +// { +// score: 0.10262022167444229, +// label: 'rocket', +// box: { xmin: 352, ymin: -1, xmax: 463, ymax: 287 } +// } +// ] +``` + +**Example:** Zero-shot object detection w/ `Xenova/owlvit-base-patch32` (returning top 4 matches and setting a threshold). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const detector = await pipeline('zero-shot-object-detection', 'Xenova/owlvit-base-patch32'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/beach.png'; +const candidate_labels = ['hat', 'book', 'sunglasses', 'camera']; +const output = await detector(url, candidate_labels, { top_k: 4, threshold: 0.05 }); +// [ +// { +// score: 0.1606510728597641, +// label: 'sunglasses', +// box: { xmin: 347, ymin: 229, xmax: 429, ymax: 264 } +// }, +// { +// score: 0.08935828506946564, +// label: 'hat', +// box: { xmin: 38, ymin: 174, xmax: 258, ymax: 364 } +// }, +// { +// score: 0.08530698716640472, +// label: 'camera', +// box: { xmin: 187, ymin: 350, xmax: 260, ymax: 411 } +// }, +// { +// score: 0.08349756896495819, +// label: 'book', +// box: { xmin: 261, ymin: 280, xmax: 494, ymax: 425 } +// } +// ] +``` + +### `document-question-answering` + +**Default model:** `Xenova/donut-base-finetuned-docvqa` + +Document Question Answering pipeline using any `AutoModelForDocumentQuestionAnswering`. +The inputs/outputs are similar to the (extractive) question answering pipeline; however, +the pipeline takes an image (and optional OCR'd words/boxes) as input instead of text context. + +**Example:** Answer questions about a document with `Xenova/donut-base-finetuned-docvqa`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const qa_pipeline = await pipeline('document-question-answering', 'Xenova/donut-base-finetuned-docvqa'); +const image = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/invoice.png'; +const question = 'What is the invoice number?'; +const output = await qa_pipeline(image, question); +// [{ answer: 'us-001' }] +``` + +### `image-to-image` + +**Default model:** `Xenova/swin2SR-classical-sr-x2-64` + +Image to Image pipeline using any `AutoModelForImageToImage`. This pipeline generates an image based on a previous image input. + +**Example:** Super-resolution w/ `Xenova/swin2SR-classical-sr-x2-64` +```javascript +import { pipeline } from '@huggingface/transformers'; + +const upscaler = await pipeline('image-to-image', 'Xenova/swin2SR-classical-sr-x2-64'); +const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/butterfly.jpg'; +const output = await upscaler(url); +// RawImage { +// data: Uint8Array(786432) [ 41, 31, 24, 43, ... ], +// width: 512, +// height: 512, +// channels: 3 +// } +``` + +### `depth-estimation` + +**Default model:** `onnx-community/depth-anything-v2-small` + +Depth estimation pipeline using any `AutoModelForDepthEstimation`. This pipeline predicts the depth of an image. + +**Example:** Depth estimation w/ `onnx-community/depth-anything-v2-small` +```javascript +import { pipeline } from '@huggingface/transformers'; + +const depth_estimator = await pipeline('depth-estimation', 'onnx-community/depth-anything-v2-small'); +const image = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/cats.jpg'; +const output = await depth_estimator(image); +// { +// predicted_depth: Tensor { +// dims: [ 480, 640 ], +// type: 'float32', +// data: Float32Array(307200) [ 2.6300313472747803, 2.5856235027313232, 2.620532751083374, ... ], +// size: 307200 +// }, +// depth: RawImage { +// data: Uint8Array(307200) [ 106, 104, 106, ... ], +// width: 640, +// height: 480, +// channels: 1 +// } +// } +``` + +### `image-feature-extraction` + +**Default model:** `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX` + +Image feature extraction pipeline using no model head. This pipeline extracts the hidden +states from the base transformer, which can be used as features in downstream tasks. + +**Example:** Perform image feature extraction with `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const image_feature_extractor = await pipeline('image-feature-extraction', 'onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX'); +const image = 'https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png'; +const features = await image_feature_extractor(image); +// Tensor { +// dims: [ 1, 201, 384 ], +// type: 'float32', +// data: Float32Array(77184) [ ... ], +// size: 77184 +// } +``` + +**Example:** Compute image embeddings with `Xenova/clip-vit-base-patch32`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const image_feature_extractor = await pipeline('image-feature-extraction', 'Xenova/clip-vit-base-patch32'); +const image = 'https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png'; +const features = await image_feature_extractor(image); +// Tensor { +// dims: [ 1, 512 ], +// type: 'float32', +// data: Float32Array(512) [ ... ], +// size: 512 +// } +``` + +## Text + +### `text-classification` + +**Default model:** `Xenova/distilbert-base-uncased-finetuned-sst-2-english` +**Aliases:** `sentiment-analysis` + +Text classification pipeline using any `ModelForSequenceClassification`. + +**Example:** Sentiment-analysis w/ `Xenova/distilbert-base-uncased-finetuned-sst-2-english`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('sentiment-analysis', 'Xenova/distilbert-base-uncased-finetuned-sst-2-english'); +const output = await classifier('I love transformers!'); +// [{ label: 'POSITIVE', score: 0.999788761138916 }] +``` + +**Example:** Multilingual sentiment-analysis w/ `Xenova/bert-base-multilingual-uncased-sentiment` (and return top 5 classes). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('sentiment-analysis', 'Xenova/bert-base-multilingual-uncased-sentiment'); +const output = await classifier('Le meilleur film de tous les temps.', { top_k: 5 }); +// [ +// { label: '5 stars', score: 0.9610759615898132 }, +// { label: '4 stars', score: 0.03323351591825485 }, +// { label: '3 stars', score: 0.0036155181005597115 }, +// { label: '1 star', score: 0.0011325967498123646 }, +// { label: '2 stars', score: 0.0009423971059732139 } +// ] +``` + +**Example:** Toxic comment classification w/ `Xenova/toxic-bert` (and return all classes). +```javascript +const classifier = await pipeline('text-classification', 'Xenova/toxic-bert'); +const output = await classifier('I hate you!', { top_k: null }); +// [ +// { label: 'toxic', score: 0.9593140482902527 }, +// { label: 'insult', score: 0.16187334060668945 }, +// { label: 'obscene', score: 0.03452680632472038 }, +// { label: 'identity_hate', score: 0.0223250575363636 }, +// { label: 'threat', score: 0.019197041168808937 }, +// { label: 'severe_toxic', score: 0.005651099607348442 } +// ] +``` + +### `token-classification` + +**Default model:** `Xenova/bert-base-multilingual-cased-ner-hrl` +**Aliases:** `ner` + +Named Entity Recognition pipeline using any `ModelForTokenClassification`. + +**Example:** Perform named entity recognition with `Xenova/bert-base-NER`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('token-classification', 'Xenova/bert-base-NER'); +const output = await classifier('My name is Sarah and I live in London'); +// [ +// { entity: 'B-PER', score: 0.9980202913284302, index: 4, word: 'Sarah' }, +// { entity: 'B-LOC', score: 0.9994474053382874, index: 9, word: 'London' } +// ] +``` + +**Example:** Perform named entity recognition with `Xenova/bert-base-NER` (and return all labels). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('token-classification', 'Xenova/bert-base-NER'); +const output = await classifier('Sarah lives in the United States of America', { ignore_labels: [] }); +// [ +// { entity: 'B-PER', score: 0.9966587424278259, index: 1, word: 'Sarah' }, +// { entity: 'O', score: 0.9987385869026184, index: 2, word: 'lives' }, +// { entity: 'O', score: 0.9990072846412659, index: 3, word: 'in' }, +// { entity: 'O', score: 0.9988298416137695, index: 4, word: 'the' }, +// { entity: 'B-LOC', score: 0.9995510578155518, index: 5, word: 'United' }, +// { entity: 'I-LOC', score: 0.9990395307540894, index: 6, word: 'States' }, +// { entity: 'I-LOC', score: 0.9986724853515625, index: 7, word: 'of' }, +// { entity: 'I-LOC', score: 0.9975294470787048, index: 8, word: 'America' } +// ] +``` + +**Example:** Group adjacent BIO/BIOES tokens into entity spans using `aggregation_strategy: "simple"`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('token-classification', 'Xenova/bert-base-NER'); +const output = await classifier('My name is Sarah and I live in London', { aggregation_strategy: 'simple' }); +// [ +// { entity_group: 'PER', score: 0.9985477924346924, word: 'Sarah' }, +// { entity_group: 'LOC', score: 0.999621570110321, word: 'London' } +// ] +``` + +### `question-answering` + +**Default model:** `Xenova/distilbert-base-cased-distilled-squad` + +Question Answering pipeline using any `ModelForQuestionAnswering`. + +**Example:** Run question answering with `Xenova/distilbert-base-uncased-distilled-squad`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const answerer = await pipeline('question-answering', 'Xenova/distilbert-base-uncased-distilled-squad'); +const question = 'Who was Jim Henson?'; +const context = 'Jim Henson was a nice puppet.'; +const output = await answerer(question, context); +// { +// answer: "a nice puppet", +// score: 0.5768911502526741 +// } +``` + +### `fill-mask` + +**Default model:** `onnx-community/ettin-encoder-32m-ONNX` + +Masked language modeling prediction pipeline using any `ModelWithLMHead`. + +**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `onnx-community/ettin-encoder-32m-ONNX`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const unmasker = await pipeline('fill-mask', 'onnx-community/ettin-encoder-32m-ONNX'); +const output = await unmasker('The capital of France is [MASK].'); +// [ +// { score: 0.5151872038841248, token: 7785, token_str: ' Paris', sequence: 'The capital of France is Paris.' }, +// { score: 0.033725105226039886, token: 42268, token_str: ' Lyon', sequence: 'The capital of France is Lyon.' }, +// { score: 0.031234024092555046, token: 23397, token_str: ' Nancy', sequence: 'The capital of France is Nancy.' }, +// { score: 0.02075139433145523, token: 30167, token_str: ' Brussels', sequence: 'The capital of France is Brussels.' }, +// { score: 0.018962178379297256, token: 31955, token_str: ' Geneva', sequence: 'The capital of France is Geneva.' } +// ] +``` + +**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-uncased`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const unmasker = await pipeline('fill-mask', 'Xenova/bert-base-cased'); +const output = await unmasker('The goal of life is [MASK].'); +// [ +// { score: 0.11368396878242493, sequence: "The goal of life is survival.", token: 8115, token_str: "survival" }, +// { score: 0.053510840982198715, sequence: "The goal of life is love.", token: 1567, token_str: "love" }, +// { score: 0.05041185021400452, sequence: "The goal of life is happiness.", token: 9266, token_str: "happiness" }, +// { score: 0.033218126744031906, sequence: "The goal of life is freedom.", token: 4438, token_str: "freedom" }, +// { score: 0.03301157429814339, sequence: "The goal of life is success.", token: 2244, token_str: "success" }, +// ] +``` + +**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return top result). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const unmasker = await pipeline('fill-mask', 'Xenova/bert-base-cased'); +const output = await unmasker('The Milky Way is a [MASK] galaxy.', { top_k: 1 }); +// [{ score: 0.5982972383499146, sequence: "The Milky Way is a spiral galaxy.", token: 14061, token_str: "spiral" }] +``` + +### `summarization` + +**Default model:** `Xenova/distilbart-cnn-6-6` + +A pipeline for summarization tasks, inheriting from Text2TextGenerationPipeline. + +**Example:** Summarization w/ `Xenova/distilbart-cnn-6-6`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const summarizer = await pipeline('summarization', 'Xenova/distilbart-cnn-6-6'); +const text = 'The tower is 324 metres (1,063 ft) tall, about the same height as an 81-storey building, ' + + 'and the tallest structure in Paris. Its base is square, measuring 125 metres (410 ft) on each side. ' + + 'During its construction, the Eiffel Tower surpassed the Washington Monument to become the tallest ' + + 'man-made structure in the world, a title it held for 41 years until the Chrysler Building in New ' + + 'York City was finished in 1930. It was the first structure to reach a height of 300 metres. Due to ' + + 'the addition of a broadcasting aerial at the top of the tower in 1957, it is now taller than the ' + + 'Chrysler Building by 5.2 metres (17 ft). Excluding transmitters, the Eiffel Tower is the second ' + + 'tallest free-standing structure in France after the Millau Viaduct.'; +const output = await summarizer(text, { + max_new_tokens: 100, +}); +// [{ summary_text: ' The Eiffel Tower is about the same height as an 81-storey building and the tallest structure in Paris. It is the second tallest free-standing structure in France after the Millau Viaduct.' }] +``` + +### `translation` + +**Default model:** `Xenova/t5-small` + +Translates text from one language to another. + +**Example:** Multilingual translation w/ `Xenova/nllb-200-distilled-600M`. + +See [here](https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200) +for the full list of languages and their corresponding codes. + +```javascript +import { pipeline } from '@huggingface/transformers'; + +const translator = await pipeline('translation', 'Xenova/nllb-200-distilled-600M'); +const output = await translator('जीवन एक चॉकलेट बॉक्स की तरह है।', { + src_lang: 'hin_Deva', // Hindi + tgt_lang: 'fra_Latn', // French +}); +// [{ translation_text: 'La vie est comme une boîte à chocolat.' }] +``` + +**Example:** Multilingual translation w/ `Xenova/m2m100_418M`. + +See [here](https://huggingface.co/facebook/m2m100_418M#languages-covered) +for the full list of languages and their corresponding codes. + +```javascript +import { pipeline } from '@huggingface/transformers'; + +const translator = await pipeline('translation', 'Xenova/m2m100_418M'); +const output = await translator('生活就像一盒巧克力。', { + src_lang: 'zh', // Chinese + tgt_lang: 'en', // English +}); +// [{ translation_text: 'Life is like a box of chocolate.' }] +``` + +**Example:** Multilingual translation w/ `Xenova/mbart-large-50-many-to-many-mmt`. + +See [here](https://huggingface.co/facebook/mbart-large-50-many-to-many-mmt#languages-covered) +for the full list of languages and their corresponding codes. + +```javascript +import { pipeline } from '@huggingface/transformers'; + +const translator = await pipeline('translation', 'Xenova/mbart-large-50-many-to-many-mmt'); +const output = await translator('संयुक्त राष्ट्र के प्रमुख का कहना है कि सीरिया में कोई सैन्य समाधान नहीं है', { + src_lang: 'hi_IN', // Hindi + tgt_lang: 'fr_XX', // French +}); +// [{ translation_text: 'Le chef des Nations affirme qu 'il n 'y a military solution in Syria.' }] +``` + +### `text2text-generation` + +**Default model:** `Xenova/flan-t5-small` + +Text2TextGenerationPipeline class for generating text using a model that performs text-to-text generation tasks. + +**Example:** Text-to-text generation w/ `Xenova/LaMini-Flan-T5-783M`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const generator = await pipeline('text2text-generation', 'Xenova/LaMini-Flan-T5-783M'); +const output = await generator('how can I become more healthy?', { + max_new_tokens: 100, +}); +// [{ generated_text: "To become more healthy, you can: 1. Eat a balanced diet with plenty of fruits, vegetables, whole grains, lean proteins, and healthy fats. 2. Stay hydrated by drinking plenty of water. 3. Get enough sleep and manage stress levels. 4. Avoid smoking and excessive alcohol consumption. 5. Regularly exercise and maintain a healthy weight. 6. Practice good hygiene and sanitation. 7. Seek medical attention if you experience any health issues." }] +``` + +### `text-generation` + +**Default model:** `onnx-community/Qwen3-0.6B-ONNX` + +Language generation pipeline using any `ModelWithLMHead` or `ModelForCausalLM`. +This pipeline predicts the words that will follow a specified text prompt. +For the full list of generation parameters, see [`GenerationConfig`](https://huggingface.co/docs/transformers.js/api/generation/configuration_utils#generationconfig). + +**Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const generator = await pipeline('text-generation', 'onnx-community/SmolLM2-135M-ONNX'); +const text = 'Once upon a time,'; +const output = await generator(text, { max_new_tokens: 8 }); +// [{ generated_text: 'Once upon a time, there was a little girl named Lily.' }] +``` + +**Example:** Chat completion with `onnx-community/Qwen3-0.6B-ONNX`. +```javascript +import { pipeline, TextStreamer } from '@huggingface/transformers'; + +// Create a text generation pipeline +const generator = await pipeline( + 'text-generation', + 'onnx-community/Qwen3-0.6B-ONNX', + { dtype: 'q4f16' }, +); + +// Define the list of messages +const messages = [ + { role: 'system', content: 'You are a helpful assistant.' }, + { role: 'user', content: 'Write me a poem about Machine Learning.' }, +]; + +// Generate a response +const output = await generator(messages, { + max_new_tokens: 512, + do_sample: false, + streamer: new TextStreamer(generator.tokenizer, { skip_prompt: true, skip_special_tokens: true }), +}); +console.log(output[0].generated_text.at(-1)?.content); +``` + +### `zero-shot-classification` + +**Default model:** `Xenova/distilbert-base-uncased-mnli` + +NLI-based zero-shot classification pipeline using a `ModelForSequenceClassification` +trained on NLI (natural language inference) tasks. Equivalent of `text-classification` +pipelines, but these models don't require a hardcoded number of potential classes, they +can be chosen at runtime. It usually means it's slower but it is **much** more flexible. + +**Example:** Zero shot classification with `Xenova/mobilebert-uncased-mnli`. +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('zero-shot-classification', 'Xenova/mobilebert-uncased-mnli'); +const text = 'Last week I upgraded my iOS version and ever since then my phone has been overheating whenever I use your app.'; +const labels = [ 'mobile', 'billing', 'website', 'account access' ]; +const output = await classifier(text, labels); +// { +// sequence: 'Last week I upgraded my iOS version and ever since then my phone has been overheating whenever I use your app.', +// labels: [ 'mobile', 'website', 'billing', 'account access' ], +// scores: [ 0.5562091040482018, 0.1843621307860853, 0.13942646639336376, 0.12000229877234923 ] +// } +``` + +**Example:** Zero shot classification with `Xenova/nli-deberta-v3-xsmall` (multi-label). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const classifier = await pipeline('zero-shot-classification', 'Xenova/nli-deberta-v3-xsmall'); +const text = 'I have a problem with my iphone that needs to be resolved asap!'; +const labels = [ 'urgent', 'not urgent', 'phone', 'tablet', 'computer' ]; +const output = await classifier(text, labels, { multi_label: true }); +// { +// sequence: 'I have a problem with my iphone that needs to be resolved asap!', +// labels: [ 'urgent', 'phone', 'computer', 'tablet', 'not urgent' ], +// scores: [ 0.9958870956360275, 0.9923963400697035, 0.002333537946160235, 0.0015134138567598765, 0.0010699384208377163 ] +// } +``` + +## Embeddings + +### `feature-extraction` + +**Default model:** `onnx-community/all-MiniLM-L6-v2-ONNX` +**Aliases:** `embeddings` + +Feature extraction pipeline using no model head. This pipeline extracts the hidden +states from the base transformer, which can be used as features in downstream tasks. + +**Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` (without pooling or normalization). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const extractor = await pipeline('feature-extraction', 'onnx-community/all-MiniLM-L6-v2-ONNX'); +const output = await extractor('This is a simple test.'); +// Tensor { +// type: 'float32', +// data: Float32Array [0.2157987803220749, -0.09140099585056305, ...], +// dims: [1, 8, 384] +// } + +// You can convert this Tensor to a nested JavaScript array using `.tolist()`: +console.log(output.tolist()); +``` + +**Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` (with pooling and normalization). +```javascript +import { pipeline } from '@huggingface/transformers'; + +const extractor = await pipeline('feature-extraction', 'onnx-community/all-MiniLM-L6-v2-ONNX'); +const output = await extractor('This is a simple test.', { pooling: 'mean', normalize: true }); +// Tensor { +// type: 'float32', +// data: Float32Array [0.09528215229511261, -0.024730168282985687, ...], +// dims: [1, 384] +// } + +// You can convert this Tensor to a nested JavaScript array using `.tolist()`: +console.log(output.tolist()); +``` + +**Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` models (with pooling and binary quantization). +```javascript +const extractor = await pipeline('feature-extraction', 'onnx-community/all-MiniLM-L6-v2-ONNX'); +const output = await extractor('This is a simple test.', { pooling: 'mean', quantize: true, precision: 'binary' }); +// Tensor { +// type: 'int8', +// data: Int8Array [49, 108, 25, ...], +// dims: [1, 48] +// } + +// You can convert this Tensor to a nested JavaScript array using `.tolist()`: +console.log(output.tolist()); +``` From 295bafe37dccd781c7c7dd214c7fee55bfb855ef Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 24 Apr 2026 21:44:01 -0400 Subject: [PATCH 029/103] improve --- .../references/CONFIGURATION.md | 10 +- .../references/PIPELINE_OPTIONS.md | 2 +- .../transformers-js/references/TASKS.md | 2 +- packages/transformers/docs/scripts/lib/ir.mjs | 12 +++ .../transformers/docs/scripts/lib/parse.mjs | 12 ++- .../docs/scripts/lib/render-api.mjs | 99 +++++++++++++++---- packages/transformers/src/configs.js | 6 +- packages/transformers/src/env.js | 43 ++++---- .../src/generation/configuration_utils.js | 4 + .../src/generation/logits_process.js | 7 ++ .../src/generation/stopping_criteria.js | 7 ++ .../transformers/src/generation/streamers.js | 16 +++ .../models/auto/feature_extraction_auto.js | 10 +- .../src/models/auto/image_processing_auto.js | 5 +- .../src/models/auto/processing_auto.js | 33 ++----- .../src/pipelines/text-generation.js | 2 +- packages/transformers/src/processing_utils.js | 15 ++- .../transformers/src/tokenization_utils.js | 26 +++-- packages/transformers/src/transformers.js | 42 ++++---- packages/transformers/src/utils/image.js | 4 +- 20 files changed, 240 insertions(+), 117 deletions(-) diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index 8ba19ba27..c40499706 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -10,21 +10,15 @@ import { env, LogLevel } from "@huggingface/transformers"; ## Quick examples <!-- @generated:start id=examples:env --> -**Example:** Disable remote models. +**Example:** Load models from your own server and disable remote downloads. ```javascript import { env } from '@huggingface/transformers'; env.allowRemoteModels = false; -``` - -**Example:** Set local model path. - -```javascript -import { env } from '@huggingface/transformers'; env.localModelPath = '/path/to/local/models/'; ``` -**Example:** Set cache directory. +**Example:** Point the filesystem cache at a custom directory (Node.js). ```javascript import { env } from '@huggingface/transformers'; diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 11cf4a65d..9c94ca8e9 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -190,7 +190,7 @@ anywhere `generate()` is invoked directly. | `eos_token_id`? | `number|number[]` | The id of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | | `encoder_no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. _(default: `0`)_ | | `decoder_start_token_id`? | `number` | If an encoder-decoder model starts decoding with a different token than *bos*, the id of that token. _(default: `null`)_ | -| `generation_kwargs`? | `Object` | Additional generation kwargs will be forwarded to the `generate` function of the model. Kwargs that are not present in `generate`'s signature will be used in the model forward pass. _(default: ``)_ | +| `generation_kwargs`? | `Object` | Additional generation kwargs will be forwarded to the `generate` function of the model. Kwargs that are not present in `generate`'s signature will be used in the model forward pass. _(default: `{}`)_ | <!-- @generated:end id=fields:GenerationConfig --> ### Streaming tokens as they're produced diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index c4ac1182f..7d91fcb37 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -795,7 +795,7 @@ const output = await generator('how can I become more healthy?', { Language generation pipeline using any `ModelWithLMHead` or `ModelForCausalLM`. This pipeline predicts the words that will follow a specified text prompt. -For the full list of generation parameters, see [`GenerationConfig`](https://huggingface.co/docs/transformers.js/api/generation/configuration_utils#generationconfig). +For the full list of generation parameters, see `GenerationConfig`. **Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). ```javascript diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index ae98803b6..4195152a5 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -86,6 +86,12 @@ function ingest(entities, mod) { } for (const v of entities.variables) { if (isPrivate(v)) continue; + // A `const` annotated with `@param` / `@returns` is a function alias — + // hoist it into the functions section so it renders with a proper signature. + if (tagsOf(v, "param").length || tagsOf(v, "returns").length) { + mod.functions.push(buildCallable(v)); + continue; + } const { description, examples } = gatherExamples(v.description); mod.constants.push({ name: v.name, @@ -155,6 +161,12 @@ function buildCallable(fn) { params: tagsOf(fn, "param").map(normalizeParam), returns: pickReturns(fn), throws: tagsOf(fn, "throws").map((t) => ({ type: t.type, description: t.description })), + // `@template {Constraint} Name` maps the generic name to its constraint; + // used by the renderer to resolve generic parameter names to something + // readable instead of a bare `any`. + templates: tagsOf(fn, "template") + .map((t) => ({ name: t.name, type: t.type })) + .filter((t) => t.name), examples, skillExamples: tagsOf(fn, "skillExample").map((t) => t.task), deprecated: fn.tags.some((t) => t.tag === "deprecated"), diff --git a/packages/transformers/docs/scripts/lib/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs index 777b92262..c978665b4 100644 --- a/packages/transformers/docs/scripts/lib/parse.mjs +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -49,8 +49,10 @@ function parseTag(raw) { const tag = nameMatch[1]; let rest = raw.slice(nameMatch[0].length).replace(/^[ \t]+/, ""); + // `@default` is the only tag whose payload starts with a brace but is *not* + // a JSDoc type expression — skip the type extraction for it. let type = null; - if (rest.startsWith("{")) { + if (rest.startsWith("{") && tag !== "default") { const extracted = extractBalancedBraces(rest, 0); if (extracted) { type = extracted.content.trim(); @@ -74,8 +76,12 @@ function parseTag(raw) { } case "callback": return { tag, name: rest.trim().split(/\s+/)[0] || null }; - case "template": - return { tag, type, name: rest.trim() }; + case "template": { + // `@template {Constraint} Name description` — just the first identifier + // after any optional constraint is the template-parameter name. + const m = rest.match(/^([A-Za-z_$][\w$]*)/); + return { tag, type, name: m?.[1] ?? null }; + } case "type": return { tag, type }; case "default": diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 7b88f31af..1e619e0ed 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -64,8 +64,15 @@ function renderExample(ex) { // Descriptions carried over from the Python library sometimes start with a // reST-style `[`TypeName`]` cross-reference that JavaScript readers can't follow. // Strip the artifact from the start of the first paragraph; leave the rest alone. +// Also normalize `@see Symbol` / `@see {@link Symbol}` to inline markup so it +// reads as prose rather than raw JSDoc tags — `{@link ...}` is expanded later +// by `expandInlineLinks`. function cleanDescription(text) { - return text.trim().replace(/^\[`?([A-Za-z_$][\w$.]*)`?\]\s*/, ""); + return text + .trim() + .replace(/^\[`?([A-Za-z_$][\w$.]*)`?\]\s*/, "") + .replace(/@see\s+(?=\{@link)/g, "") + .replace(/@see\s+`?([A-Za-z_$][\w$.]*)`?/g, "`$1`"); } // `{@link url}` / `{@link url Text}` / `{@link Symbol}` -> markdown. @@ -95,11 +102,15 @@ function renderClass(cls, ctx) { } function renderField(f, ctx, parent) { + // Skip undocumented, untyped field entries — pure placeholders with no + // reader value. Deprecation flags keep a field visible as a warning. + if (!f.type && !f.description && !f.deprecated && f.defaultValue == null) return []; + const type = f.type ? ` : ${renderType(f.type, ctx)}` : ""; const lines = [`#### \`${parent}.${f.name}\`${type}`, ""]; if (f.deprecated) lines.push("> **Deprecated**", ""); - if (f.description) lines.push(f.description.trim(), ""); - if (f.defaultValue != null) lines.push(`**Default:** \`${f.defaultValue}\``, ""); + if (f.description) lines.push(cleanDescription(f.description), ""); + if (f.defaultValue != null && f.defaultValue !== "") lines.push(`**Default:** \`${f.defaultValue}\``, ""); return lines; } @@ -113,15 +124,22 @@ function shouldRenderMethod(m) { function renderFunction(fn, ctx, depth, parent = null) { const lines = [`${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; + // Resolve generic type parameters (`@template {Constraint} T`) inside this + // function's parameter/return types. Without the constraint map, a `T` + // would render as `any`. + const templateMap = new Map(); + for (const t of fn.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); + const fnCtx = templateMap.size ? { ...ctx, templates: templateMap } : ctx; + if (fn.deprecated) lines.push("> **Deprecated**", ""); if (fn.description) lines.push(cleanDescription(fn.description), ""); if (fn.params?.length) { - lines.push("**Parameters**", "", ...renderParamList(fn.params, ctx), ""); + lines.push("**Parameters**", "", ...renderParamList(fn.params, fnCtx), ""); } if (fn.returns?.type || fn.returns?.description) { - const type = fn.returns.type ? renderType(fn.returns.type, ctx) : ""; - const desc = fn.returns.description ? ` — ${fn.returns.description.trim()}` : ""; - lines.push(`**Returns:** ${type}${desc}`, ""); + const type = fn.returns.type ? renderType(fn.returns.type, fnCtx) : ""; + const desc = fn.returns.description ? (type ? ` — ${fn.returns.description.trim()}` : fn.returns.description.trim()) : ""; + lines.push(`**Returns:** ${type}${desc}`.trim(), ""); } if (fn.throws?.length) { lines.push("**Throws**", ""); @@ -224,16 +242,33 @@ function renderConstant(c, ctx) { function isInternalTypedef(td) { if (td.name.startsWith("_")) return true; const type = (td.type ?? "").trim(); - if (/^[A-Z]$/.test(type) && !td.description && !td.properties?.length) return true; - if (type === "object" && !td.description && !td.properties?.length) return true; + // `@typedef {object} Foo` with no body — nothing to show. + if ((type === "object" || type === "Object") && !td.description && !td.properties?.length) return true; + // `@typedef {GenericParam} Foo` — bare alias with no description or body. + if (/^[A-Za-z_$][\w$.]*$/.test(type) && !td.description && !td.properties?.length) return true; return false; } +// Generic type parameter names (T, K, V, TItem, TReturnTensor, ...) — show as +// `any` in rendered types so the reader doesn't chase an undefined symbol. +function isGenericParamName(name) { + return /^T[A-Z][A-Za-z]*$/.test(name) || /^[TKV]$/.test(name); +} + function renderTypedef(td, ctx) { const displayed = td.type ? renderType(td.type, { ...ctx, selfName: td.name }) : ""; const isSelfReference = displayed === `\`${td.name}\``; - const isGenericPassthrough = /^`[A-Z]`$/.test(displayed); - const typeIsShowable = displayed && displayed.length < 120 && !displayed.startsWith("`{") && !isSelfReference && !isGenericPassthrough; + const isGenericPassthrough = /^`[A-Z][A-Za-z]?`$/.test(displayed); + // Collapsed fallbacks (`object`, `unknown`, `any`) carry no real information + // — showing `_Type:_ `object`` adds noise without helping the reader. + const isOpaque = displayed === "`object`" || displayed === "`unknown`" || displayed === "`any`"; + // Unions and intersections are worth showing even when long — every variant + // is a named type the reader can click through to. Opaque inline object + // literals (`{...}`) stay hidden. + const isUnionOrIntersection = / \| | & /.test(displayed); + const fitsInline = displayed.length < 120; + const typeIsShowable = + displayed && (isUnionOrIntersection || fitsInline) && !displayed.startsWith("`{") && !isSelfReference && !isGenericPassthrough && !isOpaque; // Don't emit an empty `### Name` heading. A typedef needs *something* the // reader can't derive from the name alone: a description, properties, or a @@ -263,10 +298,27 @@ export function renderType(raw, ctx, opts = {}) { const pretty = prettifyTypeString(raw); if (opts.noLink) return `\`${pretty}\``; - const parts = splitTopLevel(pretty, "|"); - if (parts.length > 1) return parts.map((p) => renderType(p.trim(), ctx)).join(" | "); - - if (SIMPLE_NAME.test(pretty)) return linkIfKnown(pretty, ctx) ?? `\`${pretty}\``; + // Unions split first. `_`-prefixed variants (internal types from intersections) + // are filtered out so they don't leak into the public docs. + const unionParts = splitTopLevel(pretty, "|") + .map((p) => p.trim()) + .filter((p) => !/^_[A-Za-z]/.test(p)); + if (unionParts.length > 1) return unionParts.map((p) => renderType(p, ctx)).join(" | "); + if (unionParts.length === 1 && unionParts[0] !== pretty.trim()) return renderType(unionParts[0], ctx); + + // Same for intersections (`A & B`): drop internal variants before joining. + const intersectParts = splitTopLevel(pretty, "&") + .map((p) => p.trim()) + .filter((p) => !/^_[A-Za-z]/.test(p)); + if (intersectParts.length > 1) return intersectParts.map((p) => renderType(p, ctx)).join(" & "); + if (intersectParts.length === 1 && intersectParts[0] !== pretty.trim()) return renderType(intersectParts[0], ctx); + + if (SIMPLE_NAME.test(pretty)) { + // `@template {Constraint} T` — render `T` as its constraint when we know it. + if (ctx.templates?.has(pretty)) return renderType(ctx.templates.get(pretty), { ...ctx, templates: undefined }); + if (isGenericParamName(pretty)) return "`any`"; + return linkIfKnown(pretty, ctx) ?? `\`${pretty}\``; + } const indexed = pretty.match(INDEXED_NAME); if (indexed) { @@ -326,7 +378,10 @@ function splitTopLevel(text, sep) { } // Strip noisy TS constructs from a type string without rewriting structure. -// Anything too complex to read inline collapses to its outermost wrapper. +// Conditional/mapped/infer types collapse to their outermost wrapper; simple +// unions of names or long generic lists are preserved so the renderer can +// split them into individual links. +const TS_UTILITY_NAMES = new Set(["Parameters", "ReturnType", "ConstructorParameters", "InstanceType", "ThisType"]); export function prettifyTypeString(raw) { if (!raw) return ""; let s = raw.trim(); @@ -335,8 +390,11 @@ export function prettifyTypeString(raw) { s = s.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); - if (s.length > 120 || isGnarly(s)) { + if (isGnarly(s)) { const outer = s.match(/^([A-Za-z_$][\w$.]*)(?:<|\s|$)/); + // Built-in TS utility types are meaningless shorn of their arguments — the + // reader is better served by `unknown` than by a naked `Parameters`. + if (outer && TS_UTILITY_NAMES.has(outer[1])) return "unknown"; return outer ? outer[1] : "object"; } return s.replace(/\s+/g, " ").trim(); @@ -355,9 +413,14 @@ function stripLeading(s, open, close) { } // Conditional types (`A extends B ? X : Y`), mapped types (`{ [K in ...] }`), -// and `infer` are unreadable inline. +// and `infer` are unreadable inline. So are indexed accesses into typeof +// expressions (`Parameters<X['foo']>[0]`) — readable names these are not. function isGnarly(s) { if (/\b(?:infer|extends)\b/.test(s)) return true; + // Any bracketed indexing like `X['foo']` or `X[0]` inside the type string — + // including inside `Parameters<...>` / `ReturnType<...>` — makes it too + // noisy to render verbatim. + if (/\[['"0-9]/.test(s)) return true; let angle = 0; for (let i = 0; i < s.length; i++) { if (s[i] === "<") angle++; diff --git a/packages/transformers/src/configs.js b/packages/transformers/src/configs.js index 75ee3576f..ce5f42f3f 100644 --- a/packages/transformers/src/configs.js +++ b/packages/transformers/src/configs.js @@ -466,7 +466,7 @@ export class PretrainedConfig { 'transformers.js_config'; /** - * Create a new PreTrainedTokenizer instance. + * Create a new `PretrainedConfig` from a parsed `config.json` object. * @param {Object} configJSON The JSON of the config. */ constructor(configJSON) { @@ -505,9 +505,9 @@ export class PretrainedConfig { } /** - * Helper class which is used to instantiate pretrained configs with the `from_pretrained` function. + * Loads a model config from a pretrained id. Thin alias for + * `PretrainedConfig.from_pretrained`. * - * **Example:** * ```javascript * const config = await AutoConfig.from_pretrained('Xenova/bert-base-uncased'); * ``` diff --git a/packages/transformers/src/env.js b/packages/transformers/src/env.js index 21f6d2335..77c9b7c31 100644 --- a/packages/transformers/src/env.js +++ b/packages/transformers/src/env.js @@ -1,19 +1,16 @@ /** - * @file Module used to configure Transformers.js. + * @file Global configuration for the library. Mutate fields on the exported + * `env` object at startup to change where models are loaded from, how files + * are cached, and how verbose logging is. * - * **Example:** Disable remote models. + * **Example:** Load models from your own server and disable remote downloads. * ```javascript * import { env } from '@huggingface/transformers'; * env.allowRemoteModels = false; - * ``` - * - * **Example:** Set local model path. - * ```javascript - * import { env } from '@huggingface/transformers'; * env.localModelPath = '/path/to/local/models/'; * ``` * - * **Example:** Set cache directory. + * **Example:** Point the filesystem cache at a custom directory (Node.js). * ```javascript * import { env } from '@huggingface/transformers'; * env.cacheDir = '/path/to/cache/directory/'; @@ -169,23 +166,21 @@ const localModelPath = RUNNING_LOCALLY ? path.join(dirname__, DEFAULT_LOCAL_MODE const DEFAULT_FETCH = typeof globalThis.fetch === 'function' ? globalThis.fetch.bind(globalThis) : undefined; /** - * Log levels for controlling output verbosity. + * Log-level enum. Assign to `env.logLevel` to control how verbose the library + * is. Higher values silence more: `DEBUG` (10) surfaces everything, + * `NONE` (50) suppresses all output. Default is `WARNING` (30). * - * Each level is represented by a number, where higher numbers include all lower level messages. - * Use these values to set `env.logLevel`. + * | Level | Value | Shows | + * |-----------|-------|-------------------------------------------| + * | `DEBUG` | 10 | Every message, including debug traces. | + * | `INFO` | 20 | Errors, warnings, and info messages. | + * | `WARNING` | 30 | Errors and warnings. | + * | `ERROR` | 40 | Only errors. | + * | `NONE` | 50 | Nothing. | * - * **Example:** * ```javascript * import { env, LogLevel } from '@huggingface/transformers'; - * - * // Set log level to show only errors * env.logLevel = LogLevel.ERROR; - * - * // Set log level to show errors, warnings, and info - * env.logLevel = LogLevel.INFO; - * - * // Disable all logging - * env.logLevel = LogLevel.NONE; * ``` */ export const LogLevel = Object.freeze({ @@ -202,7 +197,7 @@ export const LogLevel = Object.freeze({ }); /** - * Global variable given visible to users to control execution. This provides users a simple way to configure Transformers.js. + * Shape of the `env` object. Every field is mutable. * @typedef {Object} TransformersEnvironment * @property {string} version This version of Transformers.js. * @property {{onnx: Partial<import('onnxruntime-common').Env> & { setLogLevel?: (logLevel: number) => void }}} backends Expose environment variables of different backends, @@ -234,7 +229,11 @@ export const LogLevel = Object.freeze({ */ let logLevel = LogLevel.WARNING; // Default log level -/** @type {TransformersEnvironment} */ +/** + * The global configuration object. See `TransformersEnvironment` below for the + * full set of fields. + * @type {TransformersEnvironment} + */ export const env = { version: VERSION, diff --git a/packages/transformers/src/generation/configuration_utils.js b/packages/transformers/src/generation/configuration_utils.js index da2789135..d47e906c6 100644 --- a/packages/transformers/src/generation/configuration_utils.js +++ b/packages/transformers/src/generation/configuration_utils.js @@ -1,4 +1,8 @@ /** + * @file Configuration for text generation. + * + * `GenerationConfig` holds the parameters that control `generate()`. + * * @module generation/configuration_utils */ diff --git a/packages/transformers/src/generation/logits_process.js b/packages/transformers/src/generation/logits_process.js index f3a308cd8..a03611f69 100644 --- a/packages/transformers/src/generation/logits_process.js +++ b/packages/transformers/src/generation/logits_process.js @@ -1,4 +1,11 @@ /** + * @file Logits processors applied during token generation. + * + * A `LogitsProcessor` rewrites the probability distribution over the next token — + * suppressing specific ids, forcing certain tokens at the start or end, + * penalising repetition, and so on. `LogitsProcessorList` composes many + * processors; pass one via the `logits_processor` argument of `generate()`. + * * @module generation/logits_process */ diff --git a/packages/transformers/src/generation/stopping_criteria.js b/packages/transformers/src/generation/stopping_criteria.js index 82b4b20d3..5f7aa1d2a 100644 --- a/packages/transformers/src/generation/stopping_criteria.js +++ b/packages/transformers/src/generation/stopping_criteria.js @@ -1,4 +1,11 @@ /** + * @file Stopping criteria for controlling when generation halts. + * + * Each criterion returns an array of booleans — one per sequence in the batch — + * signalling which sequences should stop emitting tokens. Combine multiple + * criteria with `StoppingCriteriaList`, and pass that to `generate()` via + * the `stopping_criteria` argument. + * * @module generation/stopping_criteria */ diff --git a/packages/transformers/src/generation/streamers.js b/packages/transformers/src/generation/streamers.js index d12d37b48..675f6580b 100644 --- a/packages/transformers/src/generation/streamers.js +++ b/packages/transformers/src/generation/streamers.js @@ -1,4 +1,20 @@ /** + * @file Streamers for surfacing generated tokens as they are produced. + * + * Pass a `TextStreamer` (or `WhisperTextStreamer` for audio transcription) via + * the `streamer` argument of `generate()` to receive decoded text as tokens + * are emitted — useful for chat UIs and incremental transcription. + * + * @example + * import { pipeline, TextStreamer } from '@huggingface/transformers'; + * + * const generator = await pipeline('text-generation', 'onnx-community/Qwen3-0.6B-ONNX'); + * const streamer = new TextStreamer(generator.tokenizer, { + * skip_prompt: true, + * callback_function: (text) => process.stdout.write(text), + * }); + * await generator('Tell me a joke about JavaScript.', { max_new_tokens: 64, streamer }); + * * @module generation/streamers */ diff --git a/packages/transformers/src/models/auto/feature_extraction_auto.js b/packages/transformers/src/models/auto/feature_extraction_auto.js index 9d0c2b6db..2ff8d7d5c 100644 --- a/packages/transformers/src/models/auto/feature_extraction_auto.js +++ b/packages/transformers/src/models/auto/feature_extraction_auto.js @@ -8,17 +8,15 @@ import { FeatureExtractor } from '../../feature_extraction_utils.js'; import * as AllFeatureExtractors from '../feature_extractors.js'; /** - * Helper class which is used to instantiate pretrained feature extractors with the `from_pretrained` function. - * The chosen feature extractor class is determined by the type specified in the preprocessor config. - * Typically used for audio models. + * Loads a feature extractor from a pretrained id. The concrete class is + * selected from the `feature_extractor_type` in `preprocessor_config.json`. + * Most commonly used for audio models. * - * **Example:** * ```javascript * import { AutoFeatureExtractor, load_audio } from '@huggingface/transformers'; * * const extractor = await AutoFeatureExtractor.from_pretrained('onnx-community/whisper-tiny.en'); - * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; - * const audio = await load_audio(url, 16000); + * const audio = await load_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav', 16000); * const { input_features } = await extractor(audio); * ``` */ diff --git a/packages/transformers/src/models/auto/image_processing_auto.js b/packages/transformers/src/models/auto/image_processing_auto.js index 48a4e2171..07d451aa0 100644 --- a/packages/transformers/src/models/auto/image_processing_auto.js +++ b/packages/transformers/src/models/auto/image_processing_auto.js @@ -9,10 +9,9 @@ import { GITHUB_ISSUE_URL, IMAGE_PROCESSOR_NAME } from '../../utils/constants.js import { logger } from '../../utils/logger.js'; /** - * Helper class which is used to instantiate pretrained image processors with the `from_pretrained` function. - * The chosen image processor class is determined by the type specified in the preprocessor config. + * Loads an image processor from a pretrained id. The concrete class is + * selected from the `image_processor_type` in `preprocessor_config.json`. * - * **Example:** * ```javascript * import { AutoImageProcessor, load_image } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/models/auto/processing_auto.js b/packages/transformers/src/models/auto/processing_auto.js index 33d41bd78..60ded9141 100644 --- a/packages/transformers/src/models/auto/processing_auto.js +++ b/packages/transformers/src/models/auto/processing_auto.js @@ -15,37 +15,24 @@ import * as AllFeatureExtractors from '../feature_extractors.js'; */ /** - * Helper class which is used to instantiate pretrained processors with the `from_pretrained` function. - * The chosen processor class is determined by the type specified in the processor config. + * Loads a processor from a pretrained id. Unlike `AutoImageProcessor` and + * `AutoFeatureExtractor`, `AutoProcessor` returns a multi-modal [`Processor`](#processor) + * that bundles together a tokenizer, image processor, and/or feature extractor + * — use it when a single model needs more than one. * - * **Example:** Load a processor using `from_pretrained`. + * **Example:** Load a Whisper processor (tokenizer + audio feature extractor). * ```javascript * import { AutoProcessor } from '@huggingface/transformers'; - * - * let processor = await AutoProcessor.from_pretrained('onnx-community/whisper-tiny.en'); + * const processor = await AutoProcessor.from_pretrained('onnx-community/whisper-tiny.en'); * ``` * - * **Example:** Run an image through a processor. + * **Example:** Run an image through a CLIP processor. * ```javascript * import { AutoProcessor, load_image } from '@huggingface/transformers'; * - * let processor = await AutoProcessor.from_pretrained('Xenova/clip-vit-base-patch16'); - * let image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); - * let image_inputs = await processor(image); - * // { - * // "pixel_values": { - * // "dims": [ 1, 3, 224, 224 ], - * // "type": "float32", - * // "data": Float32Array [ -1.558687686920166, -1.558687686920166, -1.5440893173217773, ... ], - * // "size": 150528 - * // }, - * // "original_sizes": [ - * // [ 533, 800 ] - * // ], - * // "reshaped_input_sizes": [ - * // [ 224, 224 ] - * // ] - * // } + * const processor = await AutoProcessor.from_pretrained('Xenova/clip-vit-base-patch16'); + * const image = await load_image('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/football-match.jpg'); + * const { pixel_values } = await processor(image); * ``` */ export class AutoProcessor { diff --git a/packages/transformers/src/pipelines/text-generation.js b/packages/transformers/src/pipelines/text-generation.js index 900841165..73d3e9fba 100644 --- a/packages/transformers/src/pipelines/text-generation.js +++ b/packages/transformers/src/pipelines/text-generation.js @@ -54,7 +54,7 @@ function isChat(x) { /** * Language generation pipeline using any `ModelWithLMHead` or `ModelForCausalLM`. * This pipeline predicts the words that will follow a specified text prompt. - * For the full list of generation parameters, see [`GenerationConfig`](./generation/configuration_utils.md#generationconfig). + * For the full list of generation parameters, see `GenerationConfig`. * * **Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). * ```javascript diff --git a/packages/transformers/src/processing_utils.js b/packages/transformers/src/processing_utils.js index 91dc569b7..685a7615a 100644 --- a/packages/transformers/src/processing_utils.js +++ b/packages/transformers/src/processing_utils.js @@ -1,7 +1,15 @@ /** - * @file Processors are used to prepare inputs (e.g., text, image or audio) for a model. + * @file Processors turn raw inputs (images, audio, text) into the tensor + * shapes a model expects. Pipelines pick the right processor automatically; + * call one directly only when you need to preprocess without running + * inference. * - * **Example:** Using a `WhisperProcessor` to prepare an audio input for a model. + * Three `Auto*` entry points cover the common cases: + * - `AutoProcessor` — multi-modal (tokenizer + image/audio), e.g. Whisper, CLIP. + * - `AutoImageProcessor` — vision-only models. + * - `AutoFeatureExtractor` — audio-only models. + * + * **Example:** Prepare audio for Whisper. * ```javascript * import { AutoProcessor, load_audio } from '@huggingface/transformers'; * @@ -71,6 +79,7 @@ export class Processor extends Callable { } /** + * Delegates to the underlying tokenizer's `apply_chat_template`. * @param {Parameters<PreTrainedTokenizer['apply_chat_template']>[0]} messages * @param {Parameters<PreTrainedTokenizer['apply_chat_template']>[1]} options * @returns {ReturnType<PreTrainedTokenizer['apply_chat_template']>} @@ -87,6 +96,7 @@ export class Processor extends Callable { } /** + * Decode a batch of tokenized sequences via the underlying tokenizer. * @param {Parameters<PreTrainedTokenizer['batch_decode']>} args * @returns {ReturnType<PreTrainedTokenizer['batch_decode']>} */ @@ -98,6 +108,7 @@ export class Processor extends Callable { } /** + * Decode a single tokenized sequence via the underlying tokenizer. * @param {Parameters<PreTrainedTokenizer['decode']>} args * @returns {ReturnType<PreTrainedTokenizer['decode']>} */ diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index 75a2f968d..acb37f420 100644 --- a/packages/transformers/src/tokenization_utils.js +++ b/packages/transformers/src/tokenization_utils.js @@ -1,5 +1,11 @@ /** - * @file Tokenization utilities + * @file Tokenizers turn text into the integer ids a model understands, and + * decode model output back into strings. Use `AutoTokenizer.from_pretrained()` + * to load the right implementation for a model id — the class is chosen from + * the tokenizer's `tokenizer_config.json`. + * + * For chat-trained models, `tokenizer.apply_chat_template()` renders an + * OpenAI-style message list into the model's native prompt format. * * @module tokenizers */ @@ -80,9 +86,10 @@ const SPECIAL_TOKEN_ATTRIBUTES = [ */ /** + * A single content block inside a chat message. Extend the union to add + * custom types (e.g. `AudioContent`) when targeting a specific model. + * * @typedef {TextContent | ImageContent | { type: string & {}, [key: string]: any }} MessageContent - * Base type for message content. This is a discriminated union that can be extended with additional content types. - * Example: `@typedef {TextContent | ImageContent | AudioContent} MessageContent` */ /** @@ -179,18 +186,23 @@ function getSpecialTokens(tokenizer) { */ /** + * The object returned from `tokenizer(text)`. The fields are a `Tensor` by + * default, or an `Array` when `return_tensor: false` is passed. + * * @template TItem * @typedef {Object} BatchEncoding - * @property {TItem} input_ids List of token ids to be fed to a model. - * @property {TItem} attention_mask List of indices specifying which tokens should be attended to by the model. - * @property {TItem} [token_type_ids] List of token type ids to be fed to a model. + * @property {TItem} input_ids Token ids to be fed to the model. + * @property {TItem} attention_mask Mask indicating which tokens should be attended to (1) versus padded (0). + * @property {TItem} [token_type_ids] Segment ids, present only for tokenizers that distinguish sequence A vs B (e.g. BERT). */ /** + * Options passed to `tokenizer(text, options)`. + * * @template {string|string[]} TText * @template {boolean} [TReturnTensor=true] * @typedef {Object} TokenizerCallOptions - * @property {TText extends string ? string|null : string[]|null} [text_pair=null] Optional second sequence to be encoded. If set, must be the same type as text. + * @property {TText extends string ? string|null : string[]|null} [text_pair=null] Optional second sequence to be encoded. Must match the shape of `text` — string when `text` is a string, array when `text` is an array. * @property {boolean|'max_length'} [padding=false] Whether to pad the input sequences. * @property {boolean} [add_special_tokens=true] Whether or not to add the special tokens associated with the corresponding model. * @property {boolean|null} [truncation=null] Whether to truncate the input sequences. diff --git a/packages/transformers/src/transformers.js b/packages/transformers/src/transformers.js index 84f1e2881..6584d6ffc 100644 --- a/packages/transformers/src/transformers.js +++ b/packages/transformers/src/transformers.js @@ -1,30 +1,36 @@ /** - * @file Entry point for the Transformers.js library. Everything re-exported - * from this file is part of the public API. + * @file Public API of `@huggingface/transformers`. Everything re-exported from + * this file is considered stable — other imports are internal and may change. * - * **High-level** + * **Start here** * - [`pipeline()`](./pipelines.md#pipeline) — the one-call entry point for every task. - * - [Environment](./env.md) — global configuration (`env`, `LogLevel`). + * - [Environment](./env.md) — `env` fields and `LogLevel` enum. * * **Model loading** - * - [Pipelines](./pipelines.md) — task-specific pipeline classes. - * - [Models](./models.md) — `AutoModel*` classes and the base `PreTrainedModel`. - * - [Tokenizers](./tokenizers.md) — `AutoTokenizer` and friends. - * - [Processors](./processors.md) — feature extractors and image/audio processors. - * - [Configs](./configs.md) — `AutoConfig` and `PretrainedConfig`. + * - [Pipelines](./pipelines.md) — task-specific pipeline classes (`TextGenerationPipeline`, etc.). + * - [Models](./models.md) — `AutoModel*` classes (one per task). + * - [Tokenizers](./tokenizers.md) — `AutoTokenizer`, chat templates, `Message`. + * - [Processors](./processors.md) — `AutoProcessor`, `AutoImageProcessor`, `AutoFeatureExtractor`. + * - [Configs](./configs.md) — `AutoConfig` / `PretrainedConfig`. * * **Generation** - * - [Generation config](./generation/configuration_utils.md) — `GenerationConfig` fields. - * - [Logits processors](./generation/logits_process.md) — sampling and constraint logits processors. - * - [Stopping criteria](./generation/stopping_criteria.md) — when generation halts. - * - [Streamers](./generation/streamers.md) — token streaming. + * - [Generation config](./generation/configuration_utils.md) — sampling and beam-search parameters. + * - [Generation parameters](./generation/parameters.md) — full shape of `generate()` arguments. + * - [Logits processors](./generation/logits_process.md) — modify next-token probabilities. + * - [Stopping criteria](./generation/stopping_criteria.md) — control when generation halts. + * - [Streamers](./generation/streamers.md) — receive tokens as they're produced. * - * **Utilities** + * **Data types and I/O** * - [Tensors](./utils/tensor.md) — `Tensor`, shape ops, math, I/O. - * - [Audio](./utils/audio.md) — `RawAudio`, `load_audio`. - * - [Images](./utils/image.md) — `RawImage`. - * - [Model registry](./utils/model_registry.md) — cache and file inspection. - * - [Random](./utils/random.md) — seedable MT19937 PRNG. + * - [Images](./utils/image.md) — `RawImage`, `load_image()`. + * - [Audio](./utils/audio.md) — `RawAudio`, `load_audio()`. + * - `RawVideo` / `load_video()` — _(experimental, exported from the top-level package)_. + * + * **Utilities** + * - [Hub options](./utils/hub.md) — shared `from_pretrained()` option shapes. + * - [Maths](./utils/maths.md) — `softmax`, `cos_sim`, typed-array helpers. + * - [Model registry](./utils/model_registry.md) — inspect or clear the model cache. + * - [Random](./utils/random.md) — seedable MT19937 PRNG matching Python's `random`. * * @module transformers */ diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index 3da3f36cc..a079b1fcd 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -824,6 +824,8 @@ export class RawImage { } /** - * Helper function to load an image from a URL, path, etc. + * Load an image from a URL, file path, `Blob`, `HTMLCanvasElement`, or + * `OffscreenCanvas`. Equivalent to `RawImage.read`. + * @type {typeof RawImage.read} */ export const load_image = RawImage.read.bind(RawImage); From 2e48589a85d6ca492baa25f5b00e0a57edc6439b Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:30:58 -0400 Subject: [PATCH 030/103] document video.js --- packages/transformers/src/utils/video.js | 43 ++++++++++++++++++++---- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/transformers/src/utils/video.js b/packages/transformers/src/utils/video.js index e81e1f97e..4bf1517a6 100644 --- a/packages/transformers/src/utils/video.js +++ b/packages/transformers/src/utils/video.js @@ -1,10 +1,24 @@ +/** + * @file Browser video loading helpers. + * + * `load_video()` samples frames from a video source into `RawImage` frames so + * vision-language models can consume short clips. Video decoding currently + * relies on browser media APIs. + * + * @module utils/video + */ + import { RawImage } from './image.js'; import { env, apis } from '../env.js'; +/** + * A decoded video frame and its timestamp, in seconds. + */ export class RawVideoFrame { /** - * @param {RawImage} image - * @param {number} timestamp + * Create a video frame. + * @param {RawImage} image The decoded image for this frame. + * @param {number} timestamp The frame timestamp, in seconds. */ constructor(image, timestamp) { this.image = image; @@ -12,10 +26,14 @@ export class RawVideoFrame { } } +/** + * A sampled video represented as decoded frames plus total duration. + */ export class RawVideo { /** - * @param {RawVideoFrame[]|RawImage[]} frames - * @param {number} duration + * Create a video from decoded frames. + * @param {RawVideoFrame[]|RawImage[]} frames Frames with timestamps, or images to space uniformly across `duration`. + * @param {number} duration Duration in seconds. */ constructor(frames, duration) { if (frames.length > 0 && frames[0] instanceof RawImage) { @@ -26,20 +44,33 @@ export class RawVideo { this.duration = duration; } + /** + * Width of the video frames, in pixels. + * @returns {number} + */ get width() { return this.frames[0].image.width; } + + /** + * Height of the video frames, in pixels. + * @returns {number} + */ get height() { return this.frames[0].image.height; } + /** + * Effective sampled frame rate. + * @returns {number} + */ get fps() { return this.frames.length / this.duration; } } /** - * Loads a video. + * Load and sample frames from a video. * * @param {string|Blob|HTMLVideoElement} src The video to process. * @param {Object} [options] Optional parameters. @@ -54,7 +85,7 @@ export async function load_video(src, { num_frames = null, fps = null } = {}) { } // TODO: Support efficiently loading all frames using the WebCodecs API. - // Specfically, https://developer.mozilla.org/en-US/docs/Web/API/VideoDecoder + // Specifically, https://developer.mozilla.org/en-US/docs/Web/API/VideoDecoder if (num_frames == null && fps == null) { throw new Error('Either num_frames or fps must be provided.'); } From 92e81280d5ba964b031bb2f3206fdd30d0855df5 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:39:10 -0400 Subject: [PATCH 031/103] use permute instead of transpose where necessary --- .../transformers/src/models/gemma4/image_processing_gemma4.js | 4 ++-- packages/transformers/src/models/whisper/modeling_whisper.js | 2 +- packages/transformers/src/utils/image.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/transformers/src/models/gemma4/image_processing_gemma4.js b/packages/transformers/src/models/gemma4/image_processing_gemma4.js index cfbd6cabd..5594eeda9 100644 --- a/packages/transformers/src/models/gemma4/image_processing_gemma4.js +++ b/packages/transformers/src/models/gemma4/image_processing_gemma4.js @@ -43,8 +43,8 @@ function get_aspect_ratio_preserving_size(height, width, patch_size, max_patches * Convert HWC image data to patches and position IDs, then pad. * * Patchification produces the same layout as Python's: - * CHW.reshape(C, pH, ps, pW, ps).transpose(1, 3, 2, 4, 0).reshape(pH*pW, ps*ps*C) - * The transpose yields (pH, pW, ps_h, ps_w, C), so within each patch the + * CHW.reshape(C, pH, ps, pW, ps).permute(1, 3, 2, 4, 0).reshape(pH*pW, ps*ps*C) + * The permute yields (pH, pW, ps_h, ps_w, C), so within each patch the * flattened order is (dy, dx, c) — which is exactly HWC order. * This lets us read directly from HWC source data without a CHW conversion. * diff --git a/packages/transformers/src/models/whisper/modeling_whisper.js b/packages/transformers/src/models/whisper/modeling_whisper.js index d0d6f0b5a..0e46f0652 100644 --- a/packages/transformers/src/models/whisper/modeling_whisper.js +++ b/packages/transformers/src/models/whisper/modeling_whisper.js @@ -431,7 +431,7 @@ export class WhisperForConditionalGeneration extends WhisperPreTrainedModel { ? cross_attentions[l].slice(null, h, null, [0, num_frames]) : cross_attentions[l].slice(null, h); }), - ).transpose(1, 0, 2, 3); + ).permute(1, 0, 2, 3); const [std, calculatedMean] = std_mean(weights, -2, 0, true); diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index a079b1fcd..82318df15 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -203,7 +203,7 @@ export class RawImage { } if (channel_format === 'CHW') { - tensor = tensor.transpose(1, 2, 0); + tensor = tensor.permute(1, 2, 0); } else if (channel_format === 'HWC') { // Do nothing } else { From e4a54552b1123a405437c8029a91d02fd07bb323 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:39:20 -0400 Subject: [PATCH 032/103] link to video docs --- packages/transformers/src/transformers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transformers/src/transformers.js b/packages/transformers/src/transformers.js index 6584d6ffc..b4a625c33 100644 --- a/packages/transformers/src/transformers.js +++ b/packages/transformers/src/transformers.js @@ -24,7 +24,7 @@ * - [Tensors](./utils/tensor.md) — `Tensor`, shape ops, math, I/O. * - [Images](./utils/image.md) — `RawImage`, `load_image()`. * - [Audio](./utils/audio.md) — `RawAudio`, `load_audio()`. - * - `RawVideo` / `load_video()` — _(experimental, exported from the top-level package)_. + * - [Video](./utils/video.md) — `RawVideo`, `RawVideoFrame`, `load_video()` _(experimental)_. * * **Utilities** * - [Hub options](./utils/hub.md) — shared `from_pretrained()` option shapes. From 400ea6c4f5e39b842fbd423f43164a3f68bff899 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:47:20 -0400 Subject: [PATCH 033/103] deduplicate tokenization docs --- .../transformers/src/tokenization_utils.js | 41 +++++-------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index acb37f420..8c5b11b5c 100644 --- a/packages/transformers/src/tokenization_utils.js +++ b/packages/transformers/src/tokenization_utils.js @@ -220,14 +220,18 @@ function getSpecialTokens(tokenizer) { * @template {boolean} [TReturnTensor=true] * @template {boolean} [TReturnDict=true] * @typedef {Object} ApplyChatTemplateOptions - * @property {string|null} [chat_template=null] A Jinja template to use for this conversion. - * @property {Object[]|null} [tools=null] A list of tools (callable functions) that will be accessible to the model. - * @property {Record<string, string>[]|null} [documents=null] Documents that will be accessible to the model. + * @property {string|null} [chat_template=null] A Jinja template to use for this conversion. If omitted, the model's chat template is used. + * @property {Object[]|null} [tools=null] JSON Schema tool definitions exposed to templates that support function calling. + * See the [chat templating guide](https://huggingface.co/docs/transformers/main/en/chat_templating#automated-function-conversion-for-tool-use). + * @property {Record<string, string>[]|null} [documents=null] Documents exposed to templates that support retrieval-augmented generation. + * See the [RAG section](https://huggingface.co/docs/transformers/main/en/chat_templating#arguments-for-RAG) of the chat templating guide. * @property {boolean} [add_generation_prompt=false] Whether to end the prompt with the token(s) that indicate the start of an assistant message. + * The template must support this argument for it to have any effect. * @property {TTokenize} [tokenize=true] Whether to tokenize the output. If false, the output will be a string. * @property {boolean} [padding=false] Whether to pad sequences to the maximum length. Has no effect if tokenize is false. * @property {boolean} [truncation=false] Whether to truncate sequences to the maximum length. Has no effect if tokenize is false. - * @property {number|null} [max_length=null] Maximum length (in tokens) to use for padding or truncation. Has no effect if tokenize is false. + * @property {number|null} [max_length=null] Maximum length (in tokens) to use for padding or truncation. If omitted, the tokenizer's `max_length` is used. + * Has no effect if tokenize is false. * @property {TReturnTensor} [return_tensor=true] Whether to return the output as a Tensor or an Array. Has no effect if tokenize is false. * @property {TReturnDict} [return_dict=true] Whether to return a dictionary with named outputs. Has no effect if tokenize is false. * @property {Object} [tokenizer_kwargs={}] Additional options to pass to the tokenizer. @@ -725,33 +729,8 @@ export class PreTrainedTokenizer * @template {boolean} [TTokenize=true] * @template {boolean} [TReturnTensor=true] * @template {boolean} [TReturnDict=true] - * @param {Object} [options] An optional object containing the following properties: - * @param {string|null} [options.chat_template=null] A Jinja template to use for this conversion. If - * this is not passed, the model's chat template will be used instead. - * @param {Object[]} [options.tools=null] - * A list of tools (callable functions) that will be accessible to the model. If the template does not - * support function calling, this argument will have no effect. Each tool should be passed as a JSON Schema, - * giving the name, description and argument types for the tool. See our - * [chat templating guide](https://huggingface.co/docs/transformers/main/en/chat_templating#automated-function-conversion-for-tool-use) - * for more information. - * @param {Record<string, string>[]} [options.documents=null] - * A list of dicts representing documents that will be accessible to the model if it is performing RAG - * (retrieval-augmented generation). If the template does not support RAG, this argument will have no - * effect. We recommend that each document should be a dict containing "title" and "text" keys. Please - * see the RAG section of the [chat templating guide](https://huggingface.co/docs/transformers/main/en/chat_templating#arguments-for-RAG) - * for examples of passing documents with chat templates. - * @param {boolean} [options.add_generation_prompt=false] Whether to end the prompt with the token(s) that indicate - * the start of an assistant message. This is useful when you want to generate a response from the model. - * Note that this argument will be passed to the chat template, and so it must be supported in the - * template for this argument to have any effect. - * @param {TTokenize} [options.tokenize=true] Whether to tokenize the output. If false, the output will be a string. - * @param {boolean} [options.padding=false] Whether to pad sequences to the maximum length. Has no effect if tokenize is false. - * @param {boolean} [options.truncation=false] Whether to truncate sequences to the maximum length. Has no effect if tokenize is false. - * @param {number|null} [options.max_length=null] Maximum length (in tokens) to use for padding or truncation. Has no effect if tokenize is false. - * If not specified, the tokenizer's `max_length` attribute will be used as a default. - * @param {TReturnTensor} [options.return_tensor=true] Whether to return the output as a Tensor or an Array. Has no effect if tokenize is false. - * @param {TReturnDict} [options.return_dict=true] Whether to return a dictionary with named outputs. Has no effect if tokenize is false. - * @param {Object} [options.tokenizer_kwargs={}] Additional options to pass to the tokenizer. + * @param {ApplyChatTemplateOptions<TTokenize, TReturnTensor, TReturnDict>} [options] Options controlling + * template rendering and tokenization. * @returns {ApplyChatTemplateReturn<TTokenize, TReturnTensor, TReturnDict>} The tokenized output. */ apply_chat_template( From ccf3229f5d9ef37526606010ea0e98b9f1967f65 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:54:29 -0400 Subject: [PATCH 034/103] update docs --- packages/transformers/src/cache_utils.js | 5 ++ packages/transformers/src/configs.js | 2 + .../src/feature_extraction_utils.js | 10 ++-- .../src/image_processors_utils.js | 25 ++++++---- .../src/models/auto/modeling_auto.js | 5 +- .../src/models/auto/tokenization_auto.js | 2 +- .../transformers/src/models/modeling_utils.js | 50 ++++++++++++++----- packages/transformers/src/processing_utils.js | 13 ++--- 8 files changed, 79 insertions(+), 33 deletions(-) diff --git a/packages/transformers/src/cache_utils.js b/packages/transformers/src/cache_utils.js index 77563711b..bfe356ab3 100644 --- a/packages/transformers/src/cache_utils.js +++ b/packages/transformers/src/cache_utils.js @@ -78,6 +78,11 @@ class _DynamicCache { } /** + * Mutable cache of decoder past key/value tensors used by `generate()`. + * + * Pass a `DynamicCache` through generation options when you need to preserve + * cache tensors across calls or inspect them from `return_dict_in_generate`. + * * @typedef {Record<string, Tensor> & _DynamicCache} DynamicCache */ diff --git a/packages/transformers/src/configs.js b/packages/transformers/src/configs.js index ce5f42f3f..5d96c461c 100644 --- a/packages/transformers/src/configs.js +++ b/packages/transformers/src/configs.js @@ -509,6 +509,8 @@ export class PretrainedConfig { * `PretrainedConfig.from_pretrained`. * * ```javascript + * import { AutoConfig } from '@huggingface/transformers'; + * * const config = await AutoConfig.from_pretrained('Xenova/bert-base-uncased'); * ``` */ diff --git a/packages/transformers/src/feature_extraction_utils.js b/packages/transformers/src/feature_extraction_utils.js index 97f033ed5..ea256b080 100644 --- a/packages/transformers/src/feature_extraction_utils.js +++ b/packages/transformers/src/feature_extraction_utils.js @@ -1,13 +1,17 @@ +/** + * @module processors + */ + import { FEATURE_EXTRACTOR_NAME } from './utils/constants.js'; import { Callable } from './utils/generic.js'; import { getModelJSON } from './utils/hub.js'; /** - * Base class for feature extractors. + * Base class for audio feature extractors. */ export class FeatureExtractor extends Callable { /** - * Constructs a new FeatureExtractor instance. + * Create a feature extractor from a parsed `preprocessor_config.json`. * * @param {Object} config The configuration for the feature extractor. */ @@ -29,7 +33,7 @@ export class FeatureExtractor extends Callable { * - A path to a *directory* containing feature_extractor files, e.g., `./my_model_directory/`. * @param {import('./utils/hub.js').PretrainedOptions} options Additional options for loading the feature_extractor. * - * @returns {Promise<FeatureExtractor>} A new instance of the Feature Extractor class. + * @returns {Promise<FeatureExtractor>} A new feature extractor instance. */ static async from_pretrained(pretrained_model_name_or_path, options = {}) { const config = await getModelJSON(pretrained_model_name_or_path, FEATURE_EXTRACTOR_NAME, true, options); diff --git a/packages/transformers/src/image_processors_utils.js b/packages/transformers/src/image_processors_utils.js index b29601025..63c0caab6 100644 --- a/packages/transformers/src/image_processors_utils.js +++ b/packages/transformers/src/image_processors_utils.js @@ -1,3 +1,7 @@ +/** + * @module processors + */ + import { Callable } from './utils/generic.js'; import { Tensor, interpolate, stack } from './utils/tensor.js'; import { bankers_round, max, min, softmax } from './utils/maths.js'; @@ -60,7 +64,7 @@ function enforce_size_divisibility([width, height], divisor) { * Converts bounding boxes from center format to corners format. * * @param {number[]} arr The coordinate for the center of the box and its width, height dimensions (center_x, center_y, width, height) - * @returns {number[]} The coodinates for the top-left and bottom-right corners of the box (top_left_x, top_left_y, bottom_right_x, bottom_right_y) + * @returns {number[]} The coordinates for the top-left and bottom-right corners of the box (top_left_x, top_left_y, bottom_right_x, bottom_right_y) */ export function center_to_corners_format([centerX, centerY, width, height]) { return [centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height / 2]; @@ -554,17 +558,20 @@ export function post_process_instance_segmentation(outputs, threshold = 0.5, tar * Can be overridden by `do_center_crop` in the `preprocess` method. * @property {boolean} [do_thumbnail] Whether to resize the image using thumbnail method. * @property {boolean} [keep_aspect_ratio] If `true`, the image is resized to the largest possible size such that the aspect ratio is preserved. - * Can be overidden by `keep_aspect_ratio` in `preprocess`. + * Can be overridden by `keep_aspect_ratio` in `preprocess`. * @property {number} [ensure_multiple_of] If `do_resize` is `true`, the image is resized to a size that is a multiple of this value. - * Can be overidden by `ensure_multiple_of` in `preprocess`. + * Can be overridden by `ensure_multiple_of` in `preprocess`. * * @property {number[]} [mean] The mean values for image normalization (same as `image_mean`). * @property {number[]} [std] The standard deviation values for image normalization (same as `image_std`). */ +/** + * Base class for image processors. + */ export class ImageProcessor extends Callable { /** - * Constructs a new `ImageProcessor`. + * Create an image processor from a parsed `preprocessor_config.json`. * @param {ImageProcessorConfig} config The configuration object. */ constructor(config) { @@ -776,7 +783,7 @@ export class ImageProcessor extends Callable { } /** - * Rescale the image' pixel values by `this.rescale_factor`. + * Rescale the image pixel values by `this.rescale_factor`. * @param {Float32Array} pixelData The pixel data to rescale. * @returns {void} */ @@ -1036,10 +1043,8 @@ export class ImageProcessor extends Callable { } /** - * Calls the feature extraction process on an array of images, - * preprocesses each image, and concatenates the resulting - * features into a single Tensor. - * @param {RawImage[]} images The image(s) to extract features from. + * Preprocess one or more images and batch the result into `pixel_values`. + * @param {RawImage|RawImage[]} images The image or images to preprocess. * @param {...any} args Additional arguments. * @returns {Promise<ImageProcessorResult>} An object containing the concatenated pixel values (and other metadata) of the preprocessed images. */ @@ -1080,7 +1085,7 @@ export class ImageProcessor extends Callable { * - A path to a *directory* containing processor files, e.g., `./my_model_directory/`. * @param {import('./utils/hub.js').PretrainedOptions} options Additional options for loading the processor. * - * @returns {Promise<ImageProcessor>} A new instance of the Processor class. + * @returns {Promise<ImageProcessor>} A new image processor instance. */ static async from_pretrained(pretrained_model_name_or_path, options = {}) { const preprocessorConfig = await getModelJSON( diff --git a/packages/transformers/src/models/auto/modeling_auto.js b/packages/transformers/src/models/auto/modeling_auto.js index a6ba8a25b..c890b0864 100644 --- a/packages/transformers/src/models/auto/modeling_auto.js +++ b/packages/transformers/src/models/auto/modeling_auto.js @@ -148,7 +148,10 @@ class PretrainedMixin { * ``` */ export class AutoModel extends PretrainedMixin { - /** @type {Map<string, Object>[]} */ + /** + * @internal + * @type {Map<string, Object>[]} + */ // @ts-ignore static MODEL_CLASS_MAPPINGS = MODEL_CLASS_TYPE_MAPPING.map((x) => x[0]); static BASE_IF_FAIL = true; diff --git a/packages/transformers/src/models/auto/tokenization_auto.js b/packages/transformers/src/models/auto/tokenization_auto.js index 06f8062ff..80670a722 100644 --- a/packages/transformers/src/models/auto/tokenization_auto.js +++ b/packages/transformers/src/models/auto/tokenization_auto.js @@ -40,7 +40,7 @@ export class AutoTokenizer { * - A path to a *directory* containing tokenizer files, e.g., `./my_model_directory/`. * @param {import('../../tokenization_utils.js').PretrainedTokenizerOptions} options Additional options for loading the tokenizer. * - * @returns {Promise<PreTrainedTokenizer>} A new instance of the PreTrainedTokenizer class. + * @returns {Promise<PreTrainedTokenizer>} The loaded tokenizer. */ static async from_pretrained( pretrained_model_name_or_path, diff --git a/packages/transformers/src/models/modeling_utils.js b/packages/transformers/src/models/modeling_utils.js index 51487cec2..42cbd851b 100644 --- a/packages/transformers/src/models/modeling_utils.js +++ b/packages/transformers/src/models/modeling_utils.js @@ -1,3 +1,13 @@ +/** + * @file Base model class and shared runtime helpers. + * + * `PreTrainedModel` owns inference sessions, model configuration, direct forward + * calls, and token generation. Architecture-specific model classes extend it, + * while most applications load it through an `AutoModel*` class or a pipeline. + * + * @module models + */ + import { Callable } from '../utils/generic.js'; import { constructSessions, sessionRun } from './session.js'; import { AutoConfig, getCacheNames } from '../configs.js'; @@ -197,7 +207,7 @@ export const MODEL_NAME_TO_CLASS_MAPPING = new Map(); export const MODEL_CLASS_TO_NAME_MAPPING = new Map(); /** - * A base class for pre-trained models that provides the model configuration and an ONNX session. + * A base class for pre-trained models that provides the model configuration and inference sessions. */ export class PreTrainedModel extends Callable { main_input_name = 'input_ids'; @@ -206,7 +216,7 @@ export class PreTrainedModel extends Callable { _return_dict_in_generate_keys = null; /** - * Creates a new instance of the `PreTrainedModel` class. + * Create a model from configuration and inference sessions. * @param {import('../configs.js').PretrainedConfig} config The model configuration. * @param {Record<string, any>} sessions The inference sessions for the model. * @param {Record<string, Object>} configs Additional configuration files (e.g., generation_config.json). @@ -235,7 +245,7 @@ export class PreTrainedModel extends Callable { /** * Disposes of all the ONNX sessions that were created during inference. - * @returns {Promise<unknown[]>} An array of promises, one for each ONNX session that is being disposed. + * @returns {Promise<void[]>} Resolves after each session has been released. * @todo Use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry */ async dispose() { @@ -259,7 +269,7 @@ export class PreTrainedModel extends Callable { * - A path to a *directory* containing model weights, e.g., `./my_model_directory/`. * @param {import('../utils/hub.js').PretrainedModelOptions} options Additional options for loading the model. * - * @returns {Promise<PreTrainedModel>} A new instance of the `PreTrainedModel` class. + * @returns {Promise<PreTrainedModel>} A model instance with ready inference sessions. */ static async from_pretrained( pretrained_model_name_or_path, @@ -360,20 +370,18 @@ export class PreTrainedModel extends Callable { } /** - * Runs the model with the provided inputs - * @param {Object} model_inputs Object containing input tensors - * @returns {Promise<Object>} Object containing output tensors + * Runs the model with the provided inputs. + * @param {Object} model_inputs Object containing input tensors. + * @returns {Promise<Object>} Object containing output tensors. */ async _call(model_inputs) { return await this.forward(model_inputs); } /** - * Forward method for a pretrained model. If not overridden by a subclass, the correct forward method - * will be chosen based on the model type. + * Run the model's forward pass. * @param {Object} model_inputs The input data to the model in the format specified in the ONNX model. * @returns {Promise<Object>} The output data from the model in the format specified in the ONNX model. - * @throws {Error} This method must be implemented in subclasses. */ async forward(model_inputs) { return await this._forward(this, model_inputs); @@ -800,7 +808,7 @@ export class PreTrainedModel extends Callable { } else if (Array.isArray(decoder_start_token_id)) { if (decoder_start_token_id.length !== batch_size) { throw new Error( - `\`decoder_start_token_id\` expcted to have length ${batch_size} but got ${decoder_start_token_id.length}`, + `\`decoder_start_token_id\` expected to have length ${batch_size} but got ${decoder_start_token_id.length}`, ); } decoder_input_ids = decoder_start_token_id; @@ -830,7 +838,7 @@ export class PreTrainedModel extends Callable { } /** - * Generates sequences of token ids for models with a language modeling head. + * Generate token sequences with a language-modeling head. * @param {import('../generation/parameters.js').GenerationFunctionParameters} options * @returns {Promise<ModelOutput|Tensor>} The output of the model, which can contain the generated token ids, attentions, and scores. */ @@ -1081,14 +1089,32 @@ export class PreTrainedModel extends Callable { return output[outputName]; } + /** + * Encode image inputs into features for multimodal generation. + * @param {any} inputs Vision encoder inputs. + * @returns {Promise<any>} Image features. + * @internal + */ async encode_image(inputs) { return this._encode_input('vision_encoder', inputs, 'image_features'); } + /** + * Encode token ids into embeddings for multimodal generation. + * @param {any} inputs Text encoder inputs. + * @returns {Promise<any>} Text embeddings. + * @internal + */ async encode_text(inputs) { return this._encode_input('embed_tokens', inputs, 'inputs_embeds'); } + /** + * Encode audio inputs into features for multimodal generation. + * @param {any} inputs Audio encoder inputs. + * @returns {Promise<any>} Audio features. + * @internal + */ async encode_audio(inputs) { return this._encode_input('audio_encoder', inputs, 'audio_features'); } diff --git a/packages/transformers/src/processing_utils.js b/packages/transformers/src/processing_utils.js index 685a7615a..edb94d74e 100644 --- a/packages/transformers/src/processing_utils.js +++ b/packages/transformers/src/processing_utils.js @@ -37,7 +37,8 @@ import { getModelJSON, getModelText } from './utils/hub.js'; */ /** - * Represents a Processor that extracts features from an input. + * Multi-modal preprocessor that delegates to the tokenizer, image processor, + * and/or feature extractor required by a model. */ export class Processor extends Callable { static classes = ['image_processor_class', 'tokenizer_class', 'feature_extractor_class']; @@ -45,10 +46,10 @@ export class Processor extends Callable { static uses_chat_template_file = false; /** - * Creates a new Processor with the given components - * @param {Object} config - * @param {Record<string, Object>} components - * @param {string} chat_template + * Create a processor from parsed config and its component preprocessors. + * @param {Object} config Processor configuration. + * @param {Record<string, Object>} components Loaded tokenizer, image processor, and/or feature extractor. + * @param {string|null} chat_template Optional chat template loaded from the model repo. */ constructor(config, components, chat_template) { super(); @@ -147,7 +148,7 @@ export class Processor extends Callable { * - A path to a *directory* containing processor files, e.g., `./my_model_directory/`. * @param {PretrainedProcessorOptions} options Additional options for loading the processor. * - * @returns {Promise<Processor>} A new instance of the Processor class. + * @returns {Promise<Processor>} A new processor instance. */ static async from_pretrained(pretrained_model_name_or_path, options = {}) { const [config, components, chat_template] = await Promise.all([ From a5ac9f0e3eaac026f970d8965f027d99509144e3 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:09:03 -0400 Subject: [PATCH 035/103] improve docs parsing --- packages/transformers/docs/scripts/lib/ir.mjs | 85 ++++++++++++++++++- .../docs/scripts/lib/render-api.mjs | 58 ++++++++++++- .../docs/scripts/lib/type-refs.mjs | 71 ++++++++++++++++ 3 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 packages/transformers/docs/scripts/lib/type-refs.mjs diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index 4195152a5..c9d068439 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -1,6 +1,8 @@ // Group per-file entities (from structure.mjs) into per-module IR. A module is // one `@module <name>` declaration — its output is `docs/api/<name>.md`. +import { callableReferenceKey, parseUtilityType, UTILITY_TYPES } from "./type-refs.mjs"; + export function buildIR(fileEntities) { const modules = new Map(); @@ -14,7 +16,9 @@ export function buildIR(fileEntities) { modules.set(mod.name, entry); } - return { modules: [...modules.values()], typedefIndex: buildTypedefIndex(modules) }; + const moduleList = [...modules.values()]; + resolveCallableAliases(moduleList); + return { modules: moduleList, typedefIndex: buildTypedefIndex(modules) }; } // Classes win (authoritative for a name). Typedefs fill gaps. Re-export @@ -160,6 +164,7 @@ function buildCallable(fn) { description, params: tagsOf(fn, "param").map(normalizeParam), returns: pickReturns(fn), + aliasType: typeOf(fn), throws: tagsOf(fn, "throws").map((t) => ({ type: t.type, description: t.description })), // `@template {Constraint} Name` maps the generic name to its constraint; // used by the renderer to resolve generic parameter names to something @@ -173,6 +178,84 @@ function buildCallable(fn) { }; } +function resolveCallableAliases(modules) { + const callableIndex = buildCallableIndex(modules); + + for (const callable of allCallables(modules)) { + const target = findCallable(callable.aliasType, callableIndex); + if (target && target !== callable) inheritCallable(callable, target); + } + + for (const callable of allCallables(modules)) { + for (const param of callable.params ?? []) { + param.type = resolveUtilityType(param.type, callableIndex) ?? param.type; + } + if (callable.returns?.type) { + callable.returns.type = resolveUtilityType(callable.returns.type, callableIndex) ?? callable.returns.type; + } + } +} + +function buildCallableIndex(modules) { + const index = new Map(); + for (const mod of modules) { + for (const fn of mod.functions) index.set(fn.name, fn); + for (const cls of mod.classes) { + for (const m of cls.members) { + if (m.kind === "method") index.set(`${cls.name}.${m.name}`, m); + } + } + } + return index; +} + +function* allCallables(modules) { + for (const mod of modules) { + yield* mod.functions; + for (const cls of mod.classes) { + for (const m of cls.members) { + if (m.kind === "method") yield m; + } + } + } +} + +function inheritCallable(callable, target) { + if (!callable.description) callable.description = target.description; + if (!callable.params?.length && target.params?.length) callable.params = clone(target.params); + if (!callable.returns && target.returns) callable.returns = clone(target.returns); + if (!callable.throws?.length && target.throws?.length) callable.throws = clone(target.throws); + if (!callable.templates?.length && target.templates?.length) callable.templates = clone(target.templates); + if (!callable.examples?.length && target.examples?.length) callable.examples = clone(target.examples); +} + +function resolveUtilityType(type, callableIndex) { + const utility = parseUtilityType(type); + if (!utility) return null; + + const target = findCallable(utility.target, callableIndex); + if (!target) return null; + + if (utility.kind === UTILITY_TYPES.PARAMETERS && utility.index != null) { + return target.params?.[utility.index]?.type ?? null; + } + + if (utility.kind === UTILITY_TYPES.RETURN_TYPE && utility.index == null) { + return target.returns?.type ?? null; + } + + return null; +} + +function findCallable(ref, callableIndex) { + const key = callableReferenceKey(ref); + return key ? callableIndex.get(key) : null; +} + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + function tagsOf(entity, name) { return entity.tags.filter((t) => t.tag === name); } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 1e619e0ed..c273b5f5b 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -3,6 +3,8 @@ // - Never emit raw HTML tables or `<code>` escaping // - Filter to the library's public export surface +import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; + export function renderModule(mod, ir, opts = {}) { const ctx = { typedefIndex: ir.typedefIndex, moduleName: mod.name }; const publicNames = opts.publicNames ?? null; @@ -289,6 +291,7 @@ function renderTypedef(td, ctx) { // ---------- type rendering ---------- const GENERIC_WRAPPERS = /^(Promise|Array|Record|Map|Set|Iterable|AsyncIterable|Partial|Readonly)<(.+)>$/; +const NAMED_GENERIC = /^([A-Za-z_$][\w$.]*)<(.+)>$/; const SIMPLE_NAME = new RegExp(`^[A-Za-z_$][\\w$.]*$`); const INDEXED_NAME = /^([A-Za-z_$][\w$.]*)\[[^\]]+\]$/; @@ -298,6 +301,9 @@ export function renderType(raw, ctx, opts = {}) { const pretty = prettifyTypeString(raw); if (opts.noLink) return `\`${pretty}\``; + const utility = parseUtilityType(pretty); + if (utility) return renderUtilityType(utility, ctx); + // Unions split first. `_`-prefixed variants (internal types from intersections) // are filtered out so they don't leak into the public docs. const unionParts = splitTopLevel(pretty, "|") @@ -313,6 +319,10 @@ export function renderType(raw, ctx, opts = {}) { if (intersectParts.length > 1) return intersectParts.map((p) => renderType(p, ctx)).join(" & "); if (intersectParts.length === 1 && intersectParts[0] !== pretty.trim()) return renderType(intersectParts[0], ctx); + if (pretty.endsWith("[]")) { + return renderArrayType(pretty.slice(0, -2), ctx); + } + if (SIMPLE_NAME.test(pretty)) { // `@template {Constraint} T` — render `T` as its constraint when we know it. if (ctx.templates?.has(pretty)) return renderType(ctx.templates.get(pretty), { ...ctx, templates: undefined }); @@ -333,16 +343,57 @@ export function renderType(raw, ctx, opts = {}) { return `\`${wrapper[1]}\`<${rendered}>`; } + const namedGeneric = pretty.match(NAMED_GENERIC); + if (namedGeneric && ctx.typedefIndex?.has(namedGeneric[1])) { + const innerParts = splitTopLevel(namedGeneric[2], ","); + const rendered = innerParts.map((p) => renderGenericArgument(p.trim(), ctx)).join(", "); + const outer = linkIfKnown(namedGeneric[1], ctx) ?? `\`${namedGeneric[1]}\``; + return `${outer}<${rendered}>`; + } + return `\`${pretty}\``; } +function renderGenericArgument(raw, ctx) { + if (SIMPLE_NAME.test(raw) && isGenericParamName(raw) && !ctx.templates?.has(raw)) { + return `\`${raw}\``; + } + return renderType(raw, ctx); +} + +function renderArrayType(innerRaw, ctx) { + const rendered = renderType(innerRaw, ctx); + const code = rendered.match(/^`([^`]+)`$/); + return code ? `\`${code[1]}[]\`` : `${rendered}[]`; +} + function linkIfKnown(name, ctx) { + if (!ctx.typedefIndex?.has(name) || name === ctx.selfName) return null; + return linkKnownName(name, name, ctx); +} + +function linkKnownName(name, label, ctx) { if (!ctx.typedefIndex?.has(name) || name === ctx.selfName) return null; const anchor = name .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); - return `[\`${name}\`](./${ctx.typedefIndex.get(name)}.md#${anchor})`; + return `[\`${label}\`](./${ctx.typedefIndex.get(name)}.md#${anchor})`; +} + +function renderUtilityType(utility, ctx) { + const target = renderCallableReference(utility.target, ctx) ?? `\`${prettifyTypeString(utility.target)}\``; + const suffix = utility.index == null ? "" : `[\`${utility.index}\`]`; + return `\`${utility.kind}\`<${target}>${suffix}`; +} + +function renderCallableReference(raw, ctx) { + const ref = parseCallableReference(raw); + if (!ref) return null; + if (ref.method) { + return linkKnownName(ref.owner, `${ref.owner}.${ref.method}`, ctx) ?? `\`${ref.owner}.${ref.method}\``; + } + return linkIfKnown(ref.owner, ctx) ?? `\`${ref.owner}\``; } // Split `text` on `sep`, ignoring separators inside brackets, braces, parens, @@ -381,7 +432,6 @@ function splitTopLevel(text, sep) { // Conditional/mapped/infer types collapse to their outermost wrapper; simple // unions of names or long generic lists are preserved so the renderer can // split them into individual links. -const TS_UTILITY_NAMES = new Set(["Parameters", "ReturnType", "ConstructorParameters", "InstanceType", "ThisType"]); export function prettifyTypeString(raw) { if (!raw) return ""; let s = raw.trim(); @@ -390,6 +440,10 @@ export function prettifyTypeString(raw) { s = s.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); + if (isRenderableUtilityType(s)) { + return s.replace(/\s+/g, " ").trim(); + } + if (isGnarly(s)) { const outer = s.match(/^([A-Za-z_$][\w$.]*)(?:<|\s|$)/); // Built-in TS utility types are meaningless shorn of their arguments — the diff --git a/packages/transformers/docs/scripts/lib/type-refs.mjs b/packages/transformers/docs/scripts/lib/type-refs.mjs new file mode 100644 index 000000000..a76ff0dc1 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/type-refs.mjs @@ -0,0 +1,71 @@ +const IDENTIFIER = String.raw`[A-Za-z_$][\w$]*`; + +export const UTILITY_TYPES = Object.freeze({ + PARAMETERS: "Parameters", + RETURN_TYPE: "ReturnType", + CONSTRUCTOR_PARAMETERS: "ConstructorParameters", + INSTANCE_TYPE: "InstanceType", + THIS_TYPE: "ThisType", +}); + +const RENDERABLE_UTILITY_NAMES = [ + UTILITY_TYPES.PARAMETERS, + UTILITY_TYPES.RETURN_TYPE, + UTILITY_TYPES.CONSTRUCTOR_PARAMETERS, +]; +const RENDERABLE_UTILITY_PATTERN = new RegExp( + `^(${RENDERABLE_UTILITY_NAMES.join("|")})<(.+)>(?:\\[(\\d+)\\])?$`, +); + +export const TS_UTILITY_NAMES = new Set(Object.values(UTILITY_TYPES)); + +const CALLABLE_REF_PATTERNS = [ + { + regex: new RegExp(`^typeof\\s+(${IDENTIFIER})\\.(${IDENTIFIER})$`), + parse: (m) => ({ owner: m[1], method: m[2] }), + }, + { + regex: new RegExp(`^typeof\\s+(${IDENTIFIER})$`), + parse: (m) => ({ owner: m[1], method: null }), + }, + { + regex: new RegExp(`^(${IDENTIFIER})\\[['"]([^'"]+)['"]\\]$`), + parse: (m) => ({ owner: m[1], method: m[2] }), + }, + { + regex: new RegExp(`^(${IDENTIFIER})\\.(${IDENTIFIER})$`), + parse: (m) => ({ owner: m[1], method: m[2] }), + }, +]; + +export function parseUtilityType(raw) { + if (!raw) return null; + const match = raw.trim().match(RENDERABLE_UTILITY_PATTERN); + return match + ? { + kind: match[1], + target: match[2], + index: match[3] == null ? null : Number(match[3]), + } + : null; +} + +export function isRenderableUtilityType(raw) { + return parseUtilityType(raw) !== null; +} + +export function parseCallableReference(raw) { + if (!raw) return null; + const text = raw.trim(); + for (const { regex, parse } of CALLABLE_REF_PATTERNS) { + const match = text.match(regex); + if (match) return parse(match); + } + return null; +} + +export function callableReferenceKey(raw) { + const ref = parseCallableReference(raw); + if (!ref) return null; + return ref.method ? `${ref.owner}.${ref.method}` : ref.owner; +} From d4f4556c9ac25a4297e6ddc7271aa25dccdfecde Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:09:08 -0400 Subject: [PATCH 036/103] add video to docs --- packages/transformers/docs/source/_toctree.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/transformers/docs/source/_toctree.yml b/packages/transformers/docs/source/_toctree.yml index 8a4abb51a..13fde33fa 100644 --- a/packages/transformers/docs/source/_toctree.yml +++ b/packages/transformers/docs/source/_toctree.yml @@ -82,6 +82,8 @@ title: Image - local: api/utils/audio title: Audio + - local: api/utils/video + title: Video - local: api/utils/tensor title: Tensor - local: api/utils/maths From c5fe49beceb00fc9d255f80dd12a5a17e0800e3c Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:24:17 -0400 Subject: [PATCH 037/103] improvements --- .../references/CONFIGURATION.md | 6 +- .../references/PIPELINE_OPTIONS.md | 14 ++-- .../transformers/docs/scripts/lib/exports.mjs | 16 +---- .../transformers/docs/scripts/lib/js-ast.mjs | 15 ++++ .../docs/scripts/lib/render-api.mjs | 72 ++++++++++++++----- .../docs/scripts/lib/render-skill.mjs | 2 +- .../transformers/docs/scripts/lib/tasks.mjs | 13 +--- 7 files changed, 83 insertions(+), 55 deletions(-) create mode 100644 packages/transformers/docs/scripts/lib/js-ast.mjs diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index c40499706..2734aff3c 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -42,13 +42,13 @@ env.cacheDir = '/path/to/cache/directory/'; | `useFS` | `boolean` | Whether to use the file system to load files. By default, it is `true` if available. | | `useBrowserCache` | `boolean` | Whether to use Cache API to cache models. By default, it is `true` if available. | | `useFSCache` | `boolean` | Whether to use the file system to cache files. By default, it is `true` if available. | -| `cacheDir` | `string|null` | The directory to use for caching files with the file system. By default, it is `./.cache`. | +| `cacheDir` | `string\|null` | The directory to use for caching files with the file system. By default, it is `./.cache`. | | `useCustomCache` | `boolean` | Whether to use a custom cache system (defined by `customCache`), defaults to `false`. | -| `customCache` | `CacheInterface|null` | The custom cache to use. Defaults to `null`. Note: this must be an object which implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. | +| `customCache` | `CacheInterface\|null` | The custom cache to use. Defaults to `null`. Note: this must be an object which implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. | | `useWasmCache` | `boolean` | Whether to pre-load and cache WASM binaries and the WASM factory (.mjs) for ONNX Runtime. Defaults to `true` when cache is available. This can improve performance and enables offline usage by avoiding repeated downloads. | | `cacheKey` | `string` | The cache key to use for storing models and WASM binaries. Defaults to 'transformers-cache'. | | `experimental_useCrossOriginStorage` | `boolean` | Whether to use the Cross-Origin Storage API to cache model files across origins, allowing different sites to share the same cached model weights. Defaults to `false`. Requires the Cross-Origin Storage Chrome extension: https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih. The `experimental_` prefix indicates that the underlying browser API is not yet standardised and may change or be removed without a major version bump. For more information, see https://github.com/WICG/cross-origin-storage. | -| `fetch` | `(input: string | URL, init?: any) => Promise<any>` | The fetch function to use. Defaults to `fetch`. | +| `fetch` | `(input: string \| URL, init?: any) => Promise<any>` | The fetch function to use. Defaults to `fetch`. | <!-- @generated:end id=typedef:TransformersEnvironment --> ## Log levels diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 9c94ca8e9..4cf72812b 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -16,9 +16,9 @@ class in the [API reference](https://huggingface.co/docs/transformers.js/api/pip | `revision`? | `string` | The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git. NOTE: This setting is ignored for local requests. _(default: `'main'`)_ | | `subfolder`? | `string` | In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can specify the folder name here. _(default: `'onnx'`)_ | | `model_file_name`? | `string` | If specified, load the model with this name (excluding the dtype and .onnx suffixes). Currently only valid for encoder- or decoder-only models. _(default: `null`)_ | -| `device`? | `DeviceType|Record<string, DeviceType>` | The device to run the model on. If not specified, the device will be chosen from the environment settings. _(default: `null`)_ | -| `dtype`? | `DataType|Record<string, DataType>` | The data type to use for the model. If not specified, the data type will be chosen from the environment settings. _(default: `null`)_ | -| `use_external_data_format`? | `ExternalData|Record<string, ExternalData>` | Whether to load the model using the external data format (used for models >= 2GB in size). _(default: `false`)_ | +| `device`? | `DeviceType\|Record<string, DeviceType>` | The device to run the model on. If not specified, the device will be chosen from the environment settings. _(default: `null`)_ | +| `dtype`? | `DataType\|Record<string, DataType>` | The data type to use for the model. If not specified, the data type will be chosen from the environment settings. _(default: `null`)_ | +| `use_external_data_format`? | `ExternalData\|Record<string, ExternalData>` | Whether to load the model using the external data format (used for models >= 2GB in size). _(default: `false`)_ | | `session_options`? | `InferenceSession.SessionOptions` | (Optional) User-specified session options passed to the runtime. If not provided, suitable defaults will be chosen. | <!-- @generated:end id=typedef:PretrainedModelOptions --> @@ -149,7 +149,7 @@ anywhere `generate()` is invoked directly. | `max_new_tokens`? | `number` | The maximum numbers of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | | `min_length`? | `number` | The minimum length of the sequence to be generated. Corresponds to the length of the input prompt + `min_new_tokens`. Its effect is overridden by `min_new_tokens`, if also set. _(default: `0`)_ | | `min_new_tokens`? | `number` | The minimum numbers of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | -| `early_stopping`? | `boolean|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values: - `true`, where the generation stops as soon as there are `num_beams` complete candidates; - `false`, where an heuristic is applied and the generation stops when is it very unlikely to find better candidates; - `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | +| `early_stopping`? | `boolean\|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values: - `true`, where the generation stops as soon as there are `num_beams` complete candidates; - `false`, where an heuristic is applied and the generation stops when is it very unlikely to find better candidates; - `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | | `max_time`? | `number` | The maximum amount of time you allow the computation to run for in seconds. Generation will still finish the current pass after allocated time has been passed. _(default: `null`)_ | | `do_sample`? | `boolean` | Whether or not to use sampling; use greedy decoding otherwise. _(default: `false`)_ | | `num_beams`? | `number` | Number of beams for beam search. 1 means no beam search. _(default: `1`)_ | @@ -168,11 +168,11 @@ anywhere `generate()` is invoked directly. | `length_penalty`? | `number` | Exponential penalty to the length that is used with beam-based generation. It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. _(default: `1.0`)_ | | `no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size can only occur once. _(default: `0`)_ | | `bad_words_ids`? | `number[][]` | List of token ids that are not allowed to be generated. In order to get the token ids of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | -| `force_words_ids`? | `number[][]|number[][][]` | List of token ids that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | +| `force_words_ids`? | `number[][]\|number[][][]` | List of token ids that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | | `renormalize_logits`? | `boolean` | Whether to renormalize the logits after applying all the logits processors or warpers (including the custom ones). It's highly recommended to set this flag to `true` as the search algorithms suppose the score logits are normalized but some logit processors or warpers break the normalization. _(default: `false`)_ | | `constraints`? | `Object[]` | Custom constraints that can be added to the generation to ensure that the output will contain the use of certain tokens as defined by `Constraint` objects, in the most sensible way possible. _(default: `null`)_ | | `forced_bos_token_id`? | `number` | The id of the token to force as the first generated token after the `decoder_start_token_id`. Useful for multilingual models like mBART where the first generated token needs to be the target language token. _(default: `null`)_ | -| `forced_eos_token_id`? | `number|number[]` | The id of the token to force as the last generated token when `max_length` is reached. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | +| `forced_eos_token_id`? | `number\|number[]` | The id of the token to force as the last generated token when `max_length` is reached. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | | `remove_invalid_values`? | `boolean` | Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method to crash. Note that using `remove_invalid_values` can slow down generation. _(default: `false`)_ | | `exponential_decay_length_penalty`? | `[number, number]` | This Tuple adds an exponentially increasing length penalty, after a certain amount of tokens have been generated. The tuple shall consist of: `(start_index, decay_factor)` where `start_index` indicates where penalty starts and `decay_factor` represents the factor of exponential decay. _(default: `null`)_ | | `suppress_tokens`? | `number[]` | A list of tokens that will be suppressed at generation. The `SuppressTokens` logit processor will set their log probs to `-inf` so that they are not sampled. _(default: `null`)_ | @@ -187,7 +187,7 @@ anywhere `generate()` is invoked directly. | `return_dict_in_generate`? | `boolean` | Whether or not to return a `ModelOutput` instead of a plain tuple. _(default: `false`)_ | | `pad_token_id`? | `number` | The id of the *padding* token. _(default: `null`)_ | | `bos_token_id`? | `number` | The id of the *beginning-of-sequence* token. _(default: `null`)_ | -| `eos_token_id`? | `number|number[]` | The id of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | +| `eos_token_id`? | `number\|number[]` | The id of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | | `encoder_no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. _(default: `0`)_ | | `decoder_start_token_id`? | `number` | If an encoder-decoder model starts decoding with a different token than *bos*, the id of that token. _(default: `null`)_ | | `generation_kwargs`? | `Object` | Additional generation kwargs will be forwarded to the `generate` function of the model. Kwargs that are not present in `generate`'s signature will be used in the model forward pass. _(default: `{}`)_ | diff --git a/packages/transformers/docs/scripts/lib/exports.mjs b/packages/transformers/docs/scripts/lib/exports.mjs index 954f33a34..c0492514a 100644 --- a/packages/transformers/docs/scripts/lib/exports.mjs +++ b/packages/transformers/docs/scripts/lib/exports.mjs @@ -12,6 +12,8 @@ import fs from "node:fs"; import path from "node:path"; import ts from "typescript"; +import { stripQuotes, unwrapObjectFreeze } from "./js-ast.mjs"; + export function collectPublicExports(entryFile) { const names = new Set(); walk(path.resolve(entryFile), new Set(), names); @@ -93,20 +95,6 @@ function addFrozenNamespaceMembers(init, names) { } } -function unwrapObjectFreeze(init) { - if (!init || !ts.isCallExpression(init)) return null; - const callee = init.expression; - const isFreeze = - ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && callee.expression.text === "Object" && callee.name.text === "freeze"; - if (!isFreeze) return null; - const arg = init.arguments[0]; - return arg && ts.isObjectLiteralExpression(arg) ? arg : null; -} - function hasExportModifier(node) { return !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword); } - -function stripQuotes(s) { - return s.replace(/^['"`]|['"`]$/g, ""); -} diff --git a/packages/transformers/docs/scripts/lib/js-ast.mjs b/packages/transformers/docs/scripts/lib/js-ast.mjs new file mode 100644 index 000000000..b219b04de --- /dev/null +++ b/packages/transformers/docs/scripts/lib/js-ast.mjs @@ -0,0 +1,15 @@ +import ts from "typescript"; + +export function stripQuotes(text) { + return text.replace(/^['"`]|['"`]$/g, ""); +} + +export function unwrapObjectFreeze(init) { + if (!init || !ts.isCallExpression(init)) return null; + const callee = init.expression; + const isFreeze = + ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && callee.expression.text === "Object" && callee.name.text === "freeze"; + if (!isFreeze) return null; + const arg = init.arguments[0]; + return arg && ts.isObjectLiteralExpression(arg) ? arg : null; +} diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index c273b5f5b..5058fa1bf 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -3,11 +3,17 @@ // - Never emit raw HTML tables or `<code>` escaping // - Filter to the library's public export surface +import path from "node:path"; + import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; export function renderModule(mod, ir, opts = {}) { - const ctx = { typedefIndex: ir.typedefIndex, moduleName: mod.name }; const publicNames = opts.publicNames ?? null; + const ctx = { + typedefIndex: ir.typedefIndex, + moduleName: mod.name, + renderedNames: buildRenderedNameIndex(ir, publicNames), + }; const classes = filterPublic(mod.classes, publicNames); const functions = filterPublic(mod.functions, publicNames); @@ -31,8 +37,7 @@ export function renderModule(mod, ir, opts = {}) { for (const c of constants) out.push(...renderConstant(c, ctx)); } if (mod.typedefs.length) { - const publicTypedefs = mod.typedefs.filter((td) => !isInternalTypedef(td)); - const rendered = publicTypedefs.flatMap((td) => renderTypedef(td, ctx)); + const rendered = mod.typedefs.flatMap((td) => renderTypedef(td, ctx)); if (rendered.length) out.push("## Type Definitions", "", ...rendered); } if (mod.callbacks.length) { @@ -47,6 +52,19 @@ export function renderModule(mod, ir, opts = {}) { ); } +function buildRenderedNameIndex(ir, publicNames) { + const names = new Set(); + const ctx = { typedefIndex: ir.typedefIndex, renderedNames: names }; + for (const mod of ir.modules) { + for (const cls of filterPublic(mod.classes, publicNames)) names.add(cls.name); + for (const cb of mod.callbacks) names.add(cb.name); + for (const td of mod.typedefs) { + if (shouldRenderTypedef(td, { ...ctx, moduleName: mod.name })) names.add(td.name); + } + } + return names; +} + function filterPublic(items, publicNames) { return publicNames ? items.filter((it) => publicNames.has(it.name)) : items; } @@ -258,6 +276,28 @@ function isGenericParamName(name) { } function renderTypedef(td, ctx) { + if (!shouldRenderTypedef(td, ctx)) return []; + + const { displayed, typeIsShowable } = typedefRenderInfo(td, ctx); + + const lines = [`### ${td.name}`, ""]; + if (td.description) lines.push(cleanDescription(td.description), ""); + if (typeIsShowable) { + lines.push(`_Type:_ ${displayed}`, ""); + } + if (td.properties?.length) { + lines.push("**Properties**", "", ...renderParamList(td.properties, ctx), ""); + } + return lines; +} + +function shouldRenderTypedef(td, ctx) { + if (isInternalTypedef(td)) return false; + const { typeIsShowable } = typedefRenderInfo(td, ctx); + return !!(td.description || td.properties?.length || typeIsShowable); +} + +function typedefRenderInfo(td, ctx) { const displayed = td.type ? renderType(td.type, { ...ctx, selfName: td.name }) : ""; const isSelfReference = displayed === `\`${td.name}\``; const isGenericPassthrough = /^`[A-Z][A-Za-z]?`$/.test(displayed); @@ -272,20 +312,7 @@ function renderTypedef(td, ctx) { const typeIsShowable = displayed && (isUnionOrIntersection || fitsInline) && !displayed.startsWith("`{") && !isSelfReference && !isGenericPassthrough && !isOpaque; - // Don't emit an empty `### Name` heading. A typedef needs *something* the - // reader can't derive from the name alone: a description, properties, or a - // concise displayable type. - if (!td.description && !td.properties?.length && !typeIsShowable) return []; - - const lines = [`### ${td.name}`, ""]; - if (td.description) lines.push(cleanDescription(td.description), ""); - if (typeIsShowable) { - lines.push(`_Type:_ ${displayed}`, ""); - } - if (td.properties?.length) { - lines.push("**Properties**", "", ...renderParamList(td.properties, ctx), ""); - } - return lines; + return { displayed, typeIsShowable }; } // ---------- type rendering ---------- @@ -373,12 +400,19 @@ function linkIfKnown(name, ctx) { } function linkKnownName(name, label, ctx) { - if (!ctx.typedefIndex?.has(name) || name === ctx.selfName) return null; + const moduleName = ctx.typedefIndex?.get(name); + if (!moduleName || name === ctx.selfName || !ctx.renderedNames?.has(name)) return null; const anchor = name .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); - return `[\`${label}\`](./${ctx.typedefIndex.get(name)}.md#${anchor})`; + return `[\`${label}\`](${moduleHref(ctx.moduleName, moduleName)}#${anchor})`; +} + +function moduleHref(fromModule, toModule) { + let rel = path.posix.relative(path.posix.dirname(fromModule), toModule); + if (!rel.startsWith(".")) rel = `./${rel}`; + return `${rel}.md`; } function renderUtilityType(utility, ctx) { diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index db2e2f64a..1cb32934b 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -313,7 +313,7 @@ function renderTypedefType(raw) { if (compact.length > 60 && (compact.startsWith("{") || /[({=]/.test(compact))) { compact = "object"; } - return "`" + compact.replace(/`/g, "\\`") + "`"; + return "`" + compact.replace(/[`|]/g, (ch) => `\\${ch}`) + "`"; } // Cells can't contain newlines or un-escaped pipes. `{@link url}` gets turned diff --git a/packages/transformers/docs/scripts/lib/tasks.mjs b/packages/transformers/docs/scripts/lib/tasks.mjs index 1abad0969..c465179be 100644 --- a/packages/transformers/docs/scripts/lib/tasks.mjs +++ b/packages/transformers/docs/scripts/lib/tasks.mjs @@ -5,6 +5,8 @@ import ts from "typescript"; import fs from "node:fs"; +import { stripQuotes, unwrapObjectFreeze } from "./js-ast.mjs"; + export function extractTaskCatalog(indexFile) { const source = fs.readFileSync(indexFile, "utf8"); const sf = ts.createSourceFile(indexFile, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); @@ -26,13 +28,6 @@ export function extractTaskCatalog(indexFile) { return { supportedTasks, aliases }; } -// `Object.freeze({...})` -> the ObjectLiteralExpression argument, or null. -function unwrapObjectFreeze(init) { - if (!init || !ts.isCallExpression(init)) return null; - const arg = init.arguments[0]; - return arg && ts.isObjectLiteralExpression(arg) ? arg : null; -} - function parseSupportedTasks(literal, sf, out) { for (const prop of literal.properties) { if (!ts.isPropertyAssignment(prop)) continue; @@ -62,7 +57,3 @@ function parseAliases(literal, sf, out) { out.set(stripQuotes(prop.name.getText(sf)), stripQuotes(prop.initializer.getText(sf))); } } - -function stripQuotes(s) { - return s.replace(/^['"`]|['"`]$/g, ""); -} From 6bf631a17bd9522142d6a895af39a8e5a71fc923 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:51:21 -0400 Subject: [PATCH 038/103] Update zero-shot-audio-classification.js --- .../src/pipelines/zero-shot-audio-classification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transformers/src/pipelines/zero-shot-audio-classification.js b/packages/transformers/src/pipelines/zero-shot-audio-classification.js index 7aaa04d9d..8512dc3fd 100644 --- a/packages/transformers/src/pipelines/zero-shot-audio-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-audio-classification.js @@ -40,7 +40,7 @@ import { softmax } from '../utils/maths.js'; * Zero shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you * provide an audio and a set of `candidate_labels`. * - * **Example**: Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. + * **Example:** Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * From f44cea34b444a707b7df20076640b6b5e9d3f29e Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:51:26 -0400 Subject: [PATCH 039/103] Update TASKS.md --- .ai/skills/transformers-js/references/TASKS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index 7d91fcb37..43cb6fc96 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -59,7 +59,7 @@ const output = await classifier(audio, { top_k: 4 }); Zero shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you provide an audio and a set of `candidate_labels`. -**Example**: Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. +**Example:** Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. ```javascript import { pipeline } from '@huggingface/transformers'; From 8451da969dfd1dd163d550f509342d7cc6588ba0 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:52:05 -0400 Subject: [PATCH 040/103] update generator --- .ai/skills/transformers-js/SKILL.md | 50 +++++------ packages/transformers/docs/scripts/lib/ir.mjs | 27 +++++- .../docs/scripts/lib/render-api.mjs | 85 ++++++++++++++++--- .../docs/scripts/lib/render-skill.mjs | 9 +- 4 files changed, 128 insertions(+), 43 deletions(-) diff --git a/.ai/skills/transformers-js/SKILL.md b/.ai/skills/transformers-js/SKILL.md index f4db1ffc6..580fab8d5 100644 --- a/.ai/skills/transformers-js/SKILL.md +++ b/.ai/skills/transformers-js/SKILL.md @@ -35,31 +35,31 @@ Passing no `model` uses the default for that task. ## Supported tasks <!-- @generated:start id=task-list --> -- `text-classification` _(alias: `sentiment-analysis`)_ — default model: `Xenova/distilbert-base-uncased-finetuned-sst-2-english` -- `token-classification` _(alias: `ner`)_ — default model: `Xenova/bert-base-multilingual-cased-ner-hrl` -- `question-answering` — default model: `Xenova/distilbert-base-cased-distilled-squad` -- `fill-mask` — default model: `onnx-community/ettin-encoder-32m-ONNX` -- `summarization` — default model: `Xenova/distilbart-cnn-6-6` -- `translation` — default model: `Xenova/t5-small` -- `text2text-generation` — default model: `Xenova/flan-t5-small` -- `text-generation` — default model: `onnx-community/Qwen3-0.6B-ONNX` -- `zero-shot-classification` — default model: `Xenova/distilbert-base-uncased-mnli` -- `audio-classification` — default model: `Xenova/wav2vec2-base-superb-ks` -- `zero-shot-audio-classification` — default model: `Xenova/clap-htsat-unfused` -- `automatic-speech-recognition` _(alias: `asr`)_ — default model: `Xenova/whisper-tiny.en` -- `text-to-audio` _(alias: `text-to-speech`)_ — default model: `onnx-community/Supertonic-TTS-ONNX` -- `image-to-text` — default model: `Xenova/vit-gpt2-image-captioning` -- `image-classification` — default model: `Xenova/vit-base-patch16-224` -- `image-segmentation` — default model: `Xenova/detr-resnet-50-panoptic` -- `background-removal` — default model: `Xenova/modnet` -- `zero-shot-image-classification` — default model: `Xenova/clip-vit-base-patch32` -- `object-detection` — default model: `Xenova/detr-resnet-50` -- `zero-shot-object-detection` — default model: `Xenova/owlvit-base-patch32` -- `document-question-answering` — default model: `Xenova/donut-base-finetuned-docvqa` -- `image-to-image` — default model: `Xenova/swin2SR-classical-sr-x2-64` -- `depth-estimation` — default model: `onnx-community/depth-anything-v2-small` -- `feature-extraction` _(alias: `embeddings`)_ — default model: `onnx-community/all-MiniLM-L6-v2-ONNX` -- `image-feature-extraction` — default model: `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX` +- [`text-classification`](references/TASKS.md#text-classification) _(alias: `sentiment-analysis`)_ — default model: `Xenova/distilbert-base-uncased-finetuned-sst-2-english` +- [`token-classification`](references/TASKS.md#token-classification) _(alias: `ner`)_ — default model: `Xenova/bert-base-multilingual-cased-ner-hrl` +- [`question-answering`](references/TASKS.md#question-answering) — default model: `Xenova/distilbert-base-cased-distilled-squad` +- [`fill-mask`](references/TASKS.md#fill-mask) — default model: `onnx-community/ettin-encoder-32m-ONNX` +- [`summarization`](references/TASKS.md#summarization) — default model: `Xenova/distilbart-cnn-6-6` +- [`translation`](references/TASKS.md#translation) — default model: `Xenova/t5-small` +- [`text2text-generation`](references/TASKS.md#text2text-generation) — default model: `Xenova/flan-t5-small` +- [`text-generation`](references/TASKS.md#text-generation) — default model: `onnx-community/Qwen3-0.6B-ONNX` +- [`zero-shot-classification`](references/TASKS.md#zero-shot-classification) — default model: `Xenova/distilbert-base-uncased-mnli` +- [`audio-classification`](references/TASKS.md#audio-classification) — default model: `Xenova/wav2vec2-base-superb-ks` +- [`zero-shot-audio-classification`](references/TASKS.md#zero-shot-audio-classification) — default model: `Xenova/clap-htsat-unfused` +- [`automatic-speech-recognition`](references/TASKS.md#automatic-speech-recognition) _(alias: `asr`)_ — default model: `Xenova/whisper-tiny.en` +- [`text-to-audio`](references/TASKS.md#text-to-audio) _(alias: `text-to-speech`)_ — default model: `onnx-community/Supertonic-TTS-ONNX` +- [`image-to-text`](references/TASKS.md#image-to-text) — default model: `Xenova/vit-gpt2-image-captioning` +- [`image-classification`](references/TASKS.md#image-classification) — default model: `Xenova/vit-base-patch16-224` +- [`image-segmentation`](references/TASKS.md#image-segmentation) — default model: `Xenova/detr-resnet-50-panoptic` +- [`background-removal`](references/TASKS.md#background-removal) — default model: `Xenova/modnet` +- [`zero-shot-image-classification`](references/TASKS.md#zero-shot-image-classification) — default model: `Xenova/clip-vit-base-patch32` +- [`object-detection`](references/TASKS.md#object-detection) — default model: `Xenova/detr-resnet-50` +- [`zero-shot-object-detection`](references/TASKS.md#zero-shot-object-detection) — default model: `Xenova/owlvit-base-patch32` +- [`document-question-answering`](references/TASKS.md#document-question-answering) — default model: `Xenova/donut-base-finetuned-docvqa` +- [`image-to-image`](references/TASKS.md#image-to-image) — default model: `Xenova/swin2SR-classical-sr-x2-64` +- [`depth-estimation`](references/TASKS.md#depth-estimation) — default model: `onnx-community/depth-anything-v2-small` +- [`feature-extraction`](references/TASKS.md#feature-extraction) _(alias: `embeddings`)_ — default model: `onnx-community/all-MiniLM-L6-v2-ONNX` +- [`image-feature-extraction`](references/TASKS.md#image-feature-extraction) — default model: `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX` <!-- @generated:end id=task-list --> For full recipes — every task, grouped by modality, with runnable code — diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index c9d068439..c2716d652 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -187,9 +187,7 @@ function resolveCallableAliases(modules) { } for (const callable of allCallables(modules)) { - for (const param of callable.params ?? []) { - param.type = resolveUtilityType(param.type, callableIndex) ?? param.type; - } + callable.params = resolveUtilityParams(callable.params ?? [], callableIndex); if (callable.returns?.type) { callable.returns.type = resolveUtilityType(callable.returns.type, callableIndex) ?? callable.returns.type; } @@ -247,6 +245,23 @@ function resolveUtilityType(type, callableIndex) { return null; } +function resolveUtilityParams(params, callableIndex) { + const resolved = []; + for (const param of params) { + const utility = parseUtilityType(param.type); + const target = utility && findCallable(utility.target, callableIndex); + if (utility?.kind === UTILITY_TYPES.PARAMETERS && utility.index == null && target?.params?.length) { + resolved.push(...clone(target.params)); + continue; + } + resolved.push({ + ...param, + type: resolveUtilityType(param.type, callableIndex) ?? param.type, + }); + } + return resolved; +} + function findCallable(ref, callableIndex) { const key = callableReferenceKey(ref); return key ? callableIndex.get(key) : null; @@ -275,10 +290,14 @@ function normalizeParam(tag) { type: tag.type, optional: !!tag.optional, defaultValue: tag.defaultValue ?? null, - description: tag.description || "", + description: cleanParamDescription(tag.description || ""), }; } +function cleanParamDescription(description) { + return description.replace(/^\([^)]*\boptional\b[^)]*\):\s*/i, "").trim(); +} + // Canonical example format: `**Example:** <title>\n```lang\n<code>\n```` inside // any JSDoc description body. Extracted into a structured `examples` array so // renderers can emit them consistently; the source lines are stripped from diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 5058fa1bf..04cc62ecb 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -13,6 +13,7 @@ export function renderModule(mod, ir, opts = {}) { typedefIndex: ir.typedefIndex, moduleName: mod.name, renderedNames: buildRenderedNameIndex(ir, publicNames), + callableLinks: buildCallableLinkIndex(ir, publicNames), }; const classes = filterPublic(mod.classes, publicNames); @@ -46,7 +47,7 @@ export function renderModule(mod, ir, opts = {}) { } return ( - expandInlineLinks(out.join("\n")) + expandInlineLinks(out.join("\n"), ctx) .replace(/\n{3,}/g, "\n\n") .trimEnd() + "\n" ); @@ -65,6 +66,23 @@ function buildRenderedNameIndex(ir, publicNames) { return names; } +function buildCallableLinkIndex(ir, publicNames) { + const links = new Map(); + for (const mod of ir.modules) { + for (const fn of filterPublic(mod.functions, publicNames)) { + links.set(fn.name, { moduleName: mod.name, anchor: callableAnchor(fn.name) }); + } + for (const cls of filterPublic(mod.classes, publicNames)) { + for (const m of cls.members) { + if (m.kind === "method" && shouldRenderMethod(m)) { + links.set(`${cls.name}.${m.name}`, { moduleName: mod.name, anchor: callableAnchor(m.name, cls.name) }); + } + } + } + } + return links; +} + function filterPublic(items, publicNames) { return publicNames ? items.filter((it) => publicNames.has(it.name)) : items; } @@ -96,11 +114,13 @@ function cleanDescription(text) { } // `{@link url}` / `{@link url Text}` / `{@link Symbol}` -> markdown. -function expandInlineLinks(text) { - return text.replace(/\{@link\s+([^}\s]+)(?:\s+([^}]+))?\}/g, (_, target, label) => { - const displayed = (label ?? target).trim(); - return /^https?:\/\//.test(target) ? `[${displayed}](${target})` : `\`${displayed}\``; - }); +function expandInlineLinks(text, ctx) { + return text + .replace(/\{@link\s+([^}\s]+)(?:\s+([^}]+))?\}/g, (_, target, label) => { + const displayed = (label ?? target).trim(); + return /^https?:\/\//.test(target) ? `[${displayed}](${target})` : (linkReference(target, displayed, ctx) ?? `\`${displayed}\``); + }) + .replace(/\[`([A-Za-z_$][\w$]*)`\](?!\()/g, (match, name) => linkIfKnown(name, ctx) ?? match); } // ---------- classes, functions, callbacks ---------- @@ -122,6 +142,7 @@ function renderClass(cls, ctx) { } function renderField(f, ctx, parent) { + if (f.name.startsWith("_")) return []; // Skip undocumented, untyped field entries — pure placeholders with no // reader value. Deprecation flags keep a field visible as a warning. if (!f.type && !f.description && !f.deprecated && f.defaultValue == null) return []; @@ -143,7 +164,7 @@ function shouldRenderMethod(m) { } function renderFunction(fn, ctx, depth, parent = null) { - const lines = [`${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; + const lines = [`<a id="${callableAnchor(fn.name, parent)}"></a>`, "", `${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; // Resolve generic type parameters (`@template {Constraint} T`) inside this // function's parameter/return types. Without the constraint map, a `T` // would render as `any`. @@ -303,14 +324,20 @@ function typedefRenderInfo(td, ctx) { const isGenericPassthrough = /^`[A-Z][A-Za-z]?`$/.test(displayed); // Collapsed fallbacks (`object`, `unknown`, `any`) carry no real information // — showing `_Type:_ `object`` adds noise without helping the reader. - const isOpaque = displayed === "`object`" || displayed === "`unknown`" || displayed === "`any`"; + const isOpaque = ["`object`", "`Object`", "`unknown`", "`any`"].includes(displayed); // Unions and intersections are worth showing even when long — every variant // is a named type the reader can click through to. Opaque inline object // literals (`{...}`) stay hidden. const isUnionOrIntersection = / \| | & /.test(displayed); const fitsInline = displayed.length < 120; const typeIsShowable = - displayed && (isUnionOrIntersection || fitsInline) && !displayed.startsWith("`{") && !isSelfReference && !isGenericPassthrough && !isOpaque; + displayed && + !td.properties?.length && + (isUnionOrIntersection || fitsInline) && + !displayed.startsWith("`{") && + !isSelfReference && + !isGenericPassthrough && + !isOpaque; return { displayed, typeIsShowable }; } @@ -321,6 +348,7 @@ const GENERIC_WRAPPERS = /^(Promise|Array|Record|Map|Set|Iterable|AsyncIterable| const NAMED_GENERIC = /^([A-Za-z_$][\w$.]*)<(.+)>$/; const SIMPLE_NAME = new RegExp(`^[A-Za-z_$][\\w$.]*$`); const INDEXED_NAME = /^([A-Za-z_$][\w$.]*)\[[^\]]+\]$/; +const TUPLE = /^\[(.*)\]$/; // Turn a raw JSDoc type string into readable markdown. Prefers author-written // names over expanded TS structures; unknown gnarly types become `object`. @@ -350,6 +378,9 @@ export function renderType(raw, ctx, opts = {}) { return renderArrayType(pretty.slice(0, -2), ctx); } + const tuple = pretty.match(TUPLE); + if (tuple) return renderTupleType(tuple[1], ctx); + if (SIMPLE_NAME.test(pretty)) { // `@template {Constraint} T` — render `T` as its constraint when we know it. if (ctx.templates?.has(pretty)) return renderType(ctx.templates.get(pretty), { ...ctx, templates: undefined }); @@ -394,6 +425,13 @@ function renderArrayType(innerRaw, ctx) { return code ? `\`${code[1]}[]\`` : `${rendered}[]`; } +function renderTupleType(innerRaw, ctx) { + const parts = splitTopLevel(innerRaw, ","); + if (parts.length === 1 && !parts[0].trim()) return "`[]`"; + const rendered = parts.map((p) => renderType(p.trim(), ctx)).join(", "); + return `[${rendered}]`; +} + function linkIfKnown(name, ctx) { if (!ctx.typedefIndex?.has(name) || name === ctx.selfName) return null; return linkKnownName(name, name, ctx); @@ -409,12 +447,36 @@ function linkKnownName(name, label, ctx) { return `[\`${label}\`](${moduleHref(ctx.moduleName, moduleName)}#${anchor})`; } +function linkCallable(ref, label, ctx) { + const link = ctx.callableLinks?.get(ref); + if (!link) return null; + return `[\`${label}\`](${moduleHref(ctx.moduleName, link.moduleName)}#${link.anchor})`; +} + +function linkReference(raw, label, ctx) { + const ref = parseCallableReference(raw); + if (ref?.method) return linkCallable(`${ref.owner}.${ref.method}`, label, ctx) ?? linkKnownName(ref.owner, label, ctx); + if (ref) return linkCallable(ref.owner, label, ctx) ?? linkKnownName(ref.owner, label, ctx); + return linkKnownName(raw, label, ctx); +} + function moduleHref(fromModule, toModule) { let rel = path.posix.relative(path.posix.dirname(fromModule), toModule); if (!rel.startsWith(".")) rel = `./${rel}`; return `${rel}.md`; } +function callableAnchor(name, parent = null) { + return anchorForName(parent ? `${parent}-${name}` : name); +} + +function anchorForName(name) { + return name + .toLowerCase() + .replace(/[^a-z0-9_]+/g, "-") + .replace(/^-|-$/g, ""); +} + function renderUtilityType(utility, ctx) { const target = renderCallableReference(utility.target, ctx) ?? `\`${prettifyTypeString(utility.target)}\``; const suffix = utility.index == null ? "" : `[\`${utility.index}\`]`; @@ -425,9 +487,12 @@ function renderCallableReference(raw, ctx) { const ref = parseCallableReference(raw); if (!ref) return null; if (ref.method) { + const key = `${ref.owner}.${ref.method}`; + const linked = linkCallable(key, key, ctx); + if (linked) return linked; return linkKnownName(ref.owner, `${ref.owner}.${ref.method}`, ctx) ?? `\`${ref.owner}.${ref.method}\``; } - return linkIfKnown(ref.owner, ctx) ?? `\`${ref.owner}\``; + return linkCallable(ref.owner, ref.owner, ctx) ?? linkIfKnown(ref.owner, ctx) ?? `\`${ref.owner}\``; } // Split `text` on `sep`, ignoring separators inside brackets, braces, parens, diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index 1cb32934b..e54a38817 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -146,7 +146,7 @@ function renderTaskList({ tasks }) { for (const [taskId, info] of tasks.supportedTasks) { const aliases = aliasesFor(taskId, tasks); const aliasSuffix = aliases.length ? ` _(alias: ${aliases.map((a) => `\`${a}\``).join(", ")})_` : ""; - lines.push(`- \`${taskId}\`${aliasSuffix} — default model: \`${info.defaultModel}\``); + lines.push(`- [\`${taskId}\`](references/TASKS.md#${taskAnchor(taskId)})${aliasSuffix} — default model: \`${info.defaultModel}\``); } return lines.join("\n"); } @@ -197,7 +197,7 @@ function propertiesTable(props) { if (!props.length) return ""; const lines = ["| Option | Type | Description |", "|--------|------|-------------|"]; for (const p of props) { - const type = p.type ? renderTypedefType(p.type) : ""; + const type = p.type ? renderTypedefType(p.type, { table: true }) : ""; const nameCell = p.optional ? `\`${p.name}\`?` : `\`${p.name}\``; const desc = prepareCell(p.description) + (p.defaultValue != null ? ` _(default: \`${p.defaultValue}\`)_` : ""); lines.push(`| ${nameCell} | ${type} | ${desc} |`); @@ -305,7 +305,7 @@ function findTypedef(ir, name) { // Compact display: inside a markdown table cell we want inline code spans. // Collapse `import('./x.js').Foo` to `Foo`, long inline object types to // `object`, and strip newlines. -function renderTypedefType(raw) { +function renderTypedefType(raw, { table = false } = {}) { let compact = raw .replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1") .replace(/\s+/g, " ") @@ -313,7 +313,8 @@ function renderTypedefType(raw) { if (compact.length > 60 && (compact.startsWith("{") || /[({=]/.test(compact))) { compact = "object"; } - return "`" + compact.replace(/[`|]/g, (ch) => `\\${ch}`) + "`"; + const escaped = compact.replace(table ? /[`|]/g : /`/g, (ch) => `\\${ch}`); + return "`" + escaped + "`"; } // Cells can't contain newlines or un-escaped pipes. `{@link url}` gets turned From 7a2ee02b40280fe780e8e982b0bb55204af94ad0 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 13:16:35 -0400 Subject: [PATCH 041/103] improve rendering of callable objects --- packages/transformers/docs/scripts/lib/ir.mjs | 275 +++++++++++++++++- .../docs/scripts/lib/render-api.mjs | 15 +- 2 files changed, 280 insertions(+), 10 deletions(-) diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index c2716d652..a71c476e5 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -1,22 +1,30 @@ // Group per-file entities (from structure.mjs) into per-module IR. A module is // one `@module <name>` declaration — its output is `docs/api/<name>.md`. +import path from "node:path"; import { callableReferenceKey, parseUtilityType, UTILITY_TYPES } from "./type-refs.mjs"; export function buildIR(fileEntities) { const modules = new Map(); + const records = fileEntities.map(({ file, entities }) => ({ file, entities, module: resolveModule(entities) })); + const moduleByFile = new Map(records.filter((r) => r.module).map((r) => [r.file, r.module.name])); + const typeBlocksByFile = buildTypeBlocksByFile(records); - for (const { entities } of fileEntities) { - const mod = resolveModule(entities); + for (const { file, entities, module: mod } of records) { if (!mod) continue; const entry = modules.get(mod.name) ?? newModule(mod.name); if (!entry.description && mod.description) entry.description = mod.description; entry.examples.push(...mod.examples); ingest(entities, entry); + for (const helperFile of importedHelperFiles(entities, file, moduleByFile, typeBlocksByFile)) { + entry.helperFiles.add(helperFile); + } modules.set(mod.name, entry); } const moduleList = [...modules.values()]; + attachImportedHelperTypes(moduleList, typeBlocksByFile); + attachClassCallables(moduleList); resolveCallableAliases(moduleList); return { modules: moduleList, typedefIndex: buildTypedefIndex(modules) }; } @@ -67,9 +75,78 @@ function newModule(name) { typedefs: [], callbacks: [], constants: [], + helperFiles: new Set(), }; } +function buildTypeBlocksByFile(records) { + const byFile = new Map(); + for (const { file, entities } of records) { + const bucket = { typedefs: [], callbacks: [] }; + for (const block of entities.typedefs) { + if (!isPrivate(block)) collectTypedefsFromBlock(block, bucket); + } + byFile.set(file, bucket); + } + return byFile; +} + +function importedHelperFiles(entities, file, moduleByFile, typeBlocksByFile) { + const out = new Set(); + for (const block of entities.typedefs) { + for (const tag of block.tags) { + if (tag.tag !== "typedef") continue; + const ref = parseImportedTypeRef(tag.type, file); + if (!ref || moduleByFile.has(ref.file) || !typeBlocksByFile.has(ref.file)) continue; + out.add(ref.file); + } + } + return out; +} + +function attachImportedHelperTypes(modules, typeBlocksByFile) { + for (const mod of modules) { + if (!mod.helperFiles.size) continue; + const helpers = { typedefs: [], callbacks: [] }; + for (const file of [...mod.helperFiles].sort()) { + const block = typeBlocksByFile.get(file); + if (!block) continue; + mergeByName(helpers.typedefs, block.typedefs); + mergeByName(helpers.callbacks, block.callbacks); + } + mod.typedefs = dedupeTypes([...helpers.typedefs, ...mod.typedefs]); + mod.callbacks = dedupeTypes([...helpers.callbacks, ...mod.callbacks]); + } +} + +function mergeByName(target, items) { + const seen = new Set(target.map((item) => item.name)); + for (const item of items) { + if (!item.name || seen.has(item.name)) continue; + target.push(clone(item)); + seen.add(item.name); + } +} + +function dedupeTypes(items) { + const seen = new Set(); + return items.filter((item) => { + if (!item.name || seen.has(item.name)) return false; + seen.add(item.name); + return true; + }); +} + +function parseImportedTypeRef(type, fromFile) { + const m = type?.trim().match(/^import\(['"]([^'"]+)['"]\)\.([A-Za-z_$][\w$]*)$/); + if (!m || !m[1].startsWith(".")) return null; + return { file: resolveJsPath(path.resolve(path.dirname(fromFile), m[1])), name: m[2] }; +} + +function resolveJsPath(file) { + return path.extname(file) ? file : `${file}.js`; +} + function ingest(entities, mod) { for (const block of entities.typedefs) { if (!isPrivate(block)) collectTypedefsFromBlock(block, mod); @@ -83,6 +160,7 @@ function ingest(entities, mod) { examples, skillExamples: tagsOf(cls, "skillExample").map((t) => t.task), members: cls.members.filter((m) => !isPrivate(m)).map(buildMember), + callable: null, }); } for (const fn of entities.functions) { @@ -120,13 +198,20 @@ function collectTypedefsFromBlock(block, mod) { for (const tag of block.tags) { if (tag.tag === "typedef") { flush(); - current = { - kind: "typedef", + const callable = parseCallableTypedef({ name: tag.name, type: tag.type, description: tag.description || (first ? block.description : ""), - properties: [], - }; + }); + current = callable + ? { ...callable, kind: "callback" } + : { + kind: "typedef", + name: tag.name, + type: tag.type, + description: tag.description || (first ? block.description : ""), + properties: [], + }; first = false; } else if (tag.tag === "callback") { flush(); @@ -178,6 +263,182 @@ function buildCallable(fn) { }; } +function attachClassCallables(modules) { + for (const mod of modules) { + const callbacks = new Map(mod.callbacks.map((cb) => [cb.name, cb])); + + for (const cls of mod.classes) { + const callMember = cls.members.find((m) => m.kind === "method" && m.name === "_call"); + const callbackName = `${cls.name}Callback`; + const callback = callbacks.get(callbackName); + const source = callback ?? (callMember && hasCallableShape(callMember) ? callMember : null); + if (!source) continue; + + cls.callable = { + ...normalizeCallable(source), + name: cls.name, + description: "", + deprecated: false, + }; + } + } +} + +function normalizeCallable(callable) { + const cloned = clone(callable); + return { + ...cloned, + params: cloned.params ?? [], + returns: cloned.returns ?? null, + aliasType: cloned.aliasType ?? null, + throws: cloned.throws ?? [], + templates: cloned.templates ?? [], + examples: cloned.examples ?? [], + skillExamples: cloned.skillExamples ?? [], + }; +} + +function hasCallableShape(callable) { + return !!(callable.params?.length || callable.returns?.type || callable.returns?.description || callable.aliasType); +} + +function parseCallableTypedef(td) { + const parsed = parseFunctionType(td.type); + if (!parsed) return null; + return { + name: td.name, + description: td.description, + params: parsed.params, + returns: parsed.returns, + aliasType: null, + throws: [], + templates: parsed.templates, + examples: [], + skillExamples: [], + deprecated: false, + }; +} + +function parseFunctionType(raw) { + if (!raw) return null; + let text = raw.trim(); + const templates = []; + + if (text.startsWith("<")) { + const end = matchingBracket(text, 0, "<", ">"); + if (end === -1) return null; + templates.push(...parseTemplateList(text.slice(1, end))); + text = text.slice(end + 1).trim(); + } + + if (!text.startsWith("(")) return null; + const paramsEnd = matchingBracket(text, 0, "(", ")"); + if (paramsEnd === -1 || text.slice(paramsEnd + 1).trimStart().slice(0, 2) !== "=>") return null; + + const params = splitTopLevel(text.slice(1, paramsEnd), ",") + .map((part) => parseFunctionTypeParam(part.trim())) + .filter(Boolean); + const returnType = text.slice(paramsEnd + 1).trimStart().slice(2).trim(); + + return { + templates, + params, + returns: returnType ? { type: returnType, description: "" } : null, + }; +} + +function parseTemplateList(raw) { + return splitTopLevel(raw, ",") + .map((part) => { + const m = part.trim().match(/^([A-Za-z_$][\w$]*)(?:\s+extends\s+(.+))?$/); + return m ? { name: m[1], type: m[2]?.trim() ?? null } : null; + }) + .filter(Boolean); +} + +function parseFunctionTypeParam(raw) { + if (!raw) return null; + const colon = findTopLevel(raw, ":"); + if (colon === -1) return { name: raw.replace(/\?$/, ""), type: null, optional: raw.endsWith("?"), defaultValue: null, description: "" }; + const name = raw.slice(0, colon).trim(); + return { + name: name.replace(/\?$/, ""), + type: raw.slice(colon + 1).trim(), + optional: name.endsWith("?"), + defaultValue: null, + description: "", + }; +} + +function matchingBracket(text, start, open, close) { + if (text[start] !== open) return -1; + let depth = 1; + let inStr = null; + for (let i = start + 1; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + } else if (ch === open) { + depth++; + } else if (ch === close && --depth === 0) { + return i; + } + } + return -1; +} + +function findTopLevel(text, needle) { + let depth = 0; + let inStr = null; + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + } else if ("<({[".includes(ch)) { + depth++; + } else if (">)}]".includes(ch)) { + depth--; + } else if (depth === 0 && ch === needle) { + return i; + } + } + return -1; +} + +function splitTopLevel(text, sep) { + const out = []; + let depth = 0; + let inStr = null; + let buf = ""; + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + buf += ch; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + buf += ch; + } else if ("<({[".includes(ch)) { + depth++; + buf += ch; + } else if (">)}]".includes(ch)) { + depth--; + buf += ch; + } else if (depth === 0 && ch === sep) { + out.push(buf); + buf = ""; + } else { + buf += ch; + } + } + if (buf) out.push(buf); + return out.length > 1 ? out : [text]; +} + function resolveCallableAliases(modules) { const callableIndex = buildCallableIndex(modules); @@ -199,6 +460,7 @@ function buildCallableIndex(modules) { for (const mod of modules) { for (const fn of mod.functions) index.set(fn.name, fn); for (const cls of mod.classes) { + if (cls.callable) index.set(cls.name, cls.callable); for (const m of cls.members) { if (m.kind === "method") index.set(`${cls.name}.${m.name}`, m); } @@ -211,6 +473,7 @@ function* allCallables(modules) { for (const mod of modules) { yield* mod.functions; for (const cls of mod.classes) { + if (cls.callable) yield cls.callable; for (const m of cls.members) { if (m.kind === "method") yield m; } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 04cc62ecb..82e6d5446 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -129,6 +129,9 @@ function renderClass(cls, ctx) { const lines = [`### ${cls.name}`, ""]; if (cls.description) lines.push(cleanDescription(cls.description), ""); for (const ex of cls.examples) lines.push(...renderExample(ex)); + if (cls.callable) { + lines.push(...renderFunction({ ...cls.callable, displayName: cls.name, anchorName: `${cls.name}-call` }, ctx, 4)); + } for (const m of cls.members) { if (m.kind === "method") { @@ -164,7 +167,7 @@ function shouldRenderMethod(m) { } function renderFunction(fn, ctx, depth, parent = null) { - const lines = [`<a id="${callableAnchor(fn.name, parent)}"></a>`, "", `${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; + const lines = [`<a id="${callableAnchor(fn.anchorName ?? fn.name, parent)}"></a>`, "", `${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; // Resolve generic type parameters (`@template {Constraint} T`) inside this // function's parameter/return types. Without the constraint map, a `T` // would render as `any`. @@ -201,17 +204,21 @@ function signature(fn, parent) { .map((p) => (p.optional ? `[${p.name}]` : p.name)) .join(", "); const owner = parent ? `${parent}.` : ""; - return `\`${owner}${fn.name}(${params})\``; + return `\`${owner}${fn.displayName ?? fn.name}(${params})\``; } function renderCallback(cb, ctx) { const lines = [`### ${cb.name}`, ""]; + const templateMap = new Map(); + for (const t of cb.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); + const cbCtx = templateMap.size ? { ...ctx, templates: templateMap } : ctx; + if (cb.description) lines.push(cleanDescription(cb.description), ""); if (cb.params?.length) { - lines.push("**Parameters**", "", ...renderParamList(cb.params, ctx), ""); + lines.push("**Parameters**", "", ...renderParamList(cb.params, cbCtx), ""); } if (cb.returns?.type || cb.returns?.description) { - const type = cb.returns.type ? renderType(cb.returns.type, ctx) : ""; + const type = cb.returns.type ? renderType(cb.returns.type, cbCtx) : ""; const desc = cb.returns.description ? ` — ${cb.returns.description}` : ""; lines.push(`**Returns:** ${type}${desc}`, ""); } From 05c29dadc8eb8ebfbc6c08ce6c07688df57f6815 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 13:24:48 -0400 Subject: [PATCH 042/103] use js script instead of py for readme generation --- .../{build_readme.py => build_readme.js} | 118 ++++++++---------- packages/transformers/package.json | 2 +- 2 files changed, 51 insertions(+), 69 deletions(-) rename packages/transformers/docs/scripts/{build_readme.py => build_readme.js} (55%) diff --git a/packages/transformers/docs/scripts/build_readme.py b/packages/transformers/docs/scripts/build_readme.js similarity index 55% rename from packages/transformers/docs/scripts/build_readme.py rename to packages/transformers/docs/scripts/build_readme.js index 033e79bb5..eb24b5333 100644 --- a/packages/transformers/docs/scripts/build_readme.py +++ b/packages/transformers/docs/scripts/build_readme.js @@ -1,6 +1,35 @@ +import fs from "node:fs"; + +const DOCS_BASE_URL = "https://huggingface.co/docs/transformers.js"; + +const FILES_TO_INCLUDE = { + intro: "./docs/snippets/0_introduction.snippet", + quickTour: "./docs/snippets/1_quick-tour.snippet", + installation: "./docs/snippets/2_installation.snippet", + customUsage: "./docs/snippets/3_custom-usage.snippet", + tasks: "./docs/snippets/4_supported-tasks.snippet", + models: "./docs/snippets/5_supported-models.snippet", +}; + +// Links that should point somewhere other than the direct docs URL. +const CUSTOM_LINK_MAP = { + "/custom_usage#convert-your-models-to-onnx": "#convert-your-models-to-onnx", + "./api/env": `${DOCS_BASE_URL}/api/env`, + "./guides/webgpu": `${DOCS_BASE_URL}/guides/webgpu`, + "./guides/dtypes": `${DOCS_BASE_URL}/guides/dtypes`, +}; + +function main() { + const snippets = Object.fromEntries( + Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(file, "utf8")]), + ); + + const readme = fixLinks(renderTemplate(snippets)); + fs.writeFileSync("README.md", readme, "utf8"); +} -import re -README_TEMPLATE = """ +function renderTemplate({ intro, installation, quickTour, customUsage, tasks, models }) { + return ` <p align="center"> <br/> @@ -20,19 +49,19 @@ <a href="https://huggingface.co/docs/transformers.js/index"><img alt="Documentation" src="https://img.shields.io/website/http/huggingface.co/docs/transformers.js/index.svg?down_color=red&down_message=offline&up_message=online"></a> </p> -{intro} +${intro} ## Installation -{installation} +${installation} ## Quick tour -{quick_tour} +${quickTour} ## Custom usage -{custom_usage} +${customUsage} ## Supported tasks/models @@ -40,69 +69,22 @@ To find compatible models on the Hub, select the "transformers.js" library tag in the filter menu (or visit [this link](https://huggingface.co/models?library=transformers.js)). You can refine your search by selecting the task you're interested in (e.g., [text-classification](https://huggingface.co/models?pipeline_tag=text-classification&library=transformers.js)). -{tasks} - -{models} -""" - - -FILES_TO_INCLUDE = dict( - intro='./docs/snippets/0_introduction.snippet', - quick_tour='./docs/snippets/1_quick-tour.snippet', - installation='./docs/snippets/2_installation.snippet', - custom_usage='./docs/snippets/3_custom-usage.snippet', - tasks='./docs/snippets/4_supported-tasks.snippet', - models='./docs/snippets/5_supported-models.snippet', -) - -DOCS_BASE_URL = 'https://huggingface.co/docs/transformers.js' +${tasks} -# Map of custom links to replace, typically used for links to other sections of the README. -CUSTOM_LINK_MAP = { - '/custom_usage#convert-your-models-to-onnx': '#convert-your-models-to-onnx', - './api/env': DOCS_BASE_URL + '/api/env', - './guides/webgpu': DOCS_BASE_URL + '/guides/webgpu', - './guides/dtypes': DOCS_BASE_URL + '/guides/dtypes', +${models} +`; } +function fixLinks(markdown) { + return markdown.replace(/(?<=\])\((.+?)\)/gm, (_, rawLink) => { + let link = rawLink; + if (link in CUSTOM_LINK_MAP) { + link = CUSTOM_LINK_MAP[link]; + } else if (link.startsWith("/")) { + link = `${DOCS_BASE_URL}${link}`; + } + return `(${link})`; + }); +} -def main(): - - file_data = {} - for key, file_path in FILES_TO_INCLUDE.items(): - with open(file_path, encoding='utf-8') as f: - file_data[key] = f.read() - - # Fix links: - # NOTE: This regex does not match all markdown links, but works for the ones we need to replace. - LINK_RE = r'(?<=\])\((.+?)\)' - - def replace_fn(match): - link = match.group(1) - - if link in CUSTOM_LINK_MAP: - link = CUSTOM_LINK_MAP[link] - - elif link.startswith('/'): - # Link to docs - link = DOCS_BASE_URL + link - - elif link.startswith('./'): - # Relative link to file - pass - - elif link.startswith('http'): - # Link to external site - pass - - return f'({link})' - - result = README_TEMPLATE.format(**file_data) - result = re.sub(LINK_RE, replace_fn, result, count=0, flags=re.MULTILINE) - - with open('README.md', 'w', encoding='utf-8') as f: - f.write(result) - - -if __name__ == '__main__': - main() +main(); diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 9e9518de4..1b55a3b5c 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -28,7 +28,7 @@ "dev": "node scripts/dev.mjs", "build": "node scripts/build.mjs && pnpm typegen", "test": "node --experimental-vm-modules --expose-gc node_modules/jest/bin/jest.js --verbose --logHeapUsage", - "readme": "python ./docs/scripts/build_readme.py", + "readme": "node ./docs/scripts/build_readme.js", "docs-api": "node ./docs/scripts/generate.js", "docs-skill": "node ./docs/scripts/generate-skill.js", "docs-build": "doc-builder build transformers.js ./docs/source/ --not_python_module --build_dir ./docs/build/", From b74f3b8eaa514769b0bdd83fda52784306018880 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 13:40:43 -0400 Subject: [PATCH 043/103] update --- .../transformers/docs/scripts/build_readme.js | 40 +++++++++++--- .../docs/scripts/lib/api-links.mjs | 52 +++++++++++++++++++ .../docs/scripts/lib/render-api.mjs | 34 ++++-------- packages/transformers/package.json | 2 +- packages/transformers/src/transformers.js | 2 +- 5 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 packages/transformers/docs/scripts/lib/api-links.mjs diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index eb24b5333..b6750e734 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -1,6 +1,13 @@ import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; -const DOCS_BASE_URL = "https://huggingface.co/docs/transformers.js"; +import { DOCS_BASE_URL, buildApiSymbolLinks } from "./lib/api-links.mjs"; +import { loadProject } from "./lib/load.mjs"; + +const scriptFile = url.fileURLToPath(import.meta.url); +const docsDir = path.dirname(path.dirname(scriptFile)); +const packageRoot = path.dirname(docsDir); const FILES_TO_INCLUDE = { intro: "./docs/snippets/0_introduction.snippet", @@ -11,7 +18,10 @@ const FILES_TO_INCLUDE = { models: "./docs/snippets/5_supported-models.snippet", }; -// Links that should point somewhere other than the direct docs URL. +const PIPELINE_API_LINK_PREFIX = `${DOCS_BASE_URL}/api/pipelines#module_pipelines.`; + +// Links that should point somewhere other than the direct docs URL. Most are +// README-local anchors or guide/API pages referenced by snippets. const CUSTOM_LINK_MAP = { "/custom_usage#convert-your-models-to-onnx": "#convert-your-models-to-onnx", "./api/env": `${DOCS_BASE_URL}/api/env`, @@ -20,12 +30,26 @@ const CUSTOM_LINK_MAP = { }; function main() { + const { out } = parseArgs(process.argv.slice(2)); + const { ir, publicNames } = loadProject(packageRoot); + const apiLinks = buildApiSymbolLinks(ir, publicNames); const snippets = Object.fromEntries( - Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(file, "utf8")]), + Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(path.join(packageRoot, file), "utf8")]), ); - const readme = fixLinks(renderTemplate(snippets)); - fs.writeFileSync("README.md", readme, "utf8"); + const readme = fixLinks(renderTemplate(snippets), apiLinks); + fs.writeFileSync(path.resolve(packageRoot, out), readme, "utf8"); +} + +function parseArgs(args) { + const outIndex = args.indexOf("--out"); + if (outIndex !== -1 && !args[outIndex + 1]) { + throw new Error("Expected a path after --out."); + } + + return { + out: outIndex === -1 ? "README.md" : args[outIndex + 1], + }; } function renderTemplate({ intro, installation, quickTour, customUsage, tasks, models }) { @@ -75,11 +99,15 @@ ${models} `; } -function fixLinks(markdown) { +function fixLinks(markdown, apiLinks) { + // This is not a complete Markdown parser, just the narrow link rewrite + // needed by the README snippets. return markdown.replace(/(?<=\])\((.+?)\)/gm, (_, rawLink) => { let link = rawLink; if (link in CUSTOM_LINK_MAP) { link = CUSTOM_LINK_MAP[link]; + } else if (link.startsWith(PIPELINE_API_LINK_PREFIX)) { + link = apiLinks.get(link.slice(PIPELINE_API_LINK_PREFIX.length)) ?? link; } else if (link.startsWith("/")) { link = `${DOCS_BASE_URL}${link}`; } diff --git a/packages/transformers/docs/scripts/lib/api-links.mjs b/packages/transformers/docs/scripts/lib/api-links.mjs new file mode 100644 index 000000000..06c3c3587 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/api-links.mjs @@ -0,0 +1,52 @@ +export const DOCS_BASE_URL = "https://huggingface.co/docs/transformers.js"; + +export function buildApiSymbolLinks(ir, publicNames, baseUrl = DOCS_BASE_URL) { + const links = new Map(); + const ambiguous = new Set(); + for (const mod of ir.modules) { + for (const cls of mod.classes) { + if (!isPublic(cls, publicNames)) continue; + addLink(links, ambiguous, cls.name, apiUrl(mod.name, cls.name, baseUrl)); + } + for (const fn of mod.functions) { + if (!isPublic(fn, publicNames)) continue; + addLink(links, ambiguous, fn.name, apiUrl(mod.name, fn.name, baseUrl)); + } + for (const constant of mod.constants) { + if (!isPublic(constant, publicNames)) continue; + addLink(links, ambiguous, constant.name, apiUrl(mod.name, constant.name, baseUrl)); + } + for (const td of mod.typedefs) addLink(links, ambiguous, td.name, apiUrl(mod.name, td.name, baseUrl)); + for (const cb of mod.callbacks) addLink(links, ambiguous, cb.name, apiUrl(mod.name, cb.name, baseUrl)); + } + return links; +} + +export function apiUrl(moduleName, symbolName, baseUrl = DOCS_BASE_URL) { + return `${baseUrl}/api/${moduleName}#${apiSymbolAnchor(moduleName, symbolName)}`; +} + +export function apiSymbolAnchor(moduleName, symbolName) { + return `module_${moduleName}.${symbolName}`; +} + +export function apiMemberAnchor(moduleName, parentName, memberName) { + return apiSymbolAnchor(moduleName, `${parentName}.${memberName}`); +} + +function isPublic(item, publicNames) { + return !publicNames || publicNames.has(item.name); +} + +function addLink(links, ambiguous, name, link) { + if (ambiguous.has(name)) return; + + const previous = links.get(name); + if (previous && previous !== link) { + links.delete(name); + ambiguous.add(name); + return; + } + + links.set(name, link); +} diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 82e6d5446..3b725b1c9 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -5,6 +5,7 @@ import path from "node:path"; +import { apiMemberAnchor, apiSymbolAnchor } from "./api-links.mjs"; import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; export function renderModule(mod, ir, opts = {}) { @@ -70,12 +71,12 @@ function buildCallableLinkIndex(ir, publicNames) { const links = new Map(); for (const mod of ir.modules) { for (const fn of filterPublic(mod.functions, publicNames)) { - links.set(fn.name, { moduleName: mod.name, anchor: callableAnchor(fn.name) }); + links.set(fn.name, { moduleName: mod.name, anchor: apiSymbolAnchor(mod.name, fn.name) }); } for (const cls of filterPublic(mod.classes, publicNames)) { for (const m of cls.members) { if (m.kind === "method" && shouldRenderMethod(m)) { - links.set(`${cls.name}.${m.name}`, { moduleName: mod.name, anchor: callableAnchor(m.name, cls.name) }); + links.set(`${cls.name}.${m.name}`, { moduleName: mod.name, anchor: apiMemberAnchor(mod.name, cls.name, m.name) }); } } } @@ -126,11 +127,11 @@ function expandInlineLinks(text, ctx) { // ---------- classes, functions, callbacks ---------- function renderClass(cls, ctx) { - const lines = [`### ${cls.name}`, ""]; + const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, cls.name)}"></a>`, "", `### ${cls.name}`, ""]; if (cls.description) lines.push(cleanDescription(cls.description), ""); for (const ex of cls.examples) lines.push(...renderExample(ex)); if (cls.callable) { - lines.push(...renderFunction({ ...cls.callable, displayName: cls.name, anchorName: `${cls.name}-call` }, ctx, 4)); + lines.push(...renderFunction({ ...cls.callable, displayName: cls.name, anchorName: `${cls.name}.call` }, ctx, 4)); } for (const m of cls.members) { @@ -167,7 +168,8 @@ function shouldRenderMethod(m) { } function renderFunction(fn, ctx, depth, parent = null) { - const lines = [`<a id="${callableAnchor(fn.anchorName ?? fn.name, parent)}"></a>`, "", `${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; + const anchor = parent ? apiMemberAnchor(ctx.moduleName, parent, fn.anchorName ?? fn.name) : apiSymbolAnchor(ctx.moduleName, fn.anchorName ?? fn.name); + const lines = [`<a id="${anchor}"></a>`, "", `${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; // Resolve generic type parameters (`@template {Constraint} T`) inside this // function's parameter/return types. Without the constraint map, a `T` // would render as `any`. @@ -208,7 +210,7 @@ function signature(fn, parent) { } function renderCallback(cb, ctx) { - const lines = [`### ${cb.name}`, ""]; + const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, cb.name)}"></a>`, "", `### ${cb.name}`, ""]; const templateMap = new Map(); for (const t of cb.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); const cbCtx = templateMap.size ? { ...ctx, templates: templateMap } : ctx; @@ -278,7 +280,7 @@ function simpleName(name) { function renderConstant(c, ctx) { const type = c.type ? ` : ${renderType(c.type, ctx)}` : ""; - const lines = [`### \`${c.name}\`${type}`, ""]; + const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, c.name)}"></a>`, "", `### \`${c.name}\`${type}`, ""]; if (c.description) lines.push(cleanDescription(c.description), ""); for (const ex of c.examples) lines.push(...renderExample(ex)); return lines; @@ -308,7 +310,7 @@ function renderTypedef(td, ctx) { const { displayed, typeIsShowable } = typedefRenderInfo(td, ctx); - const lines = [`### ${td.name}`, ""]; + const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, td.name)}"></a>`, "", `### ${td.name}`, ""]; if (td.description) lines.push(cleanDescription(td.description), ""); if (typeIsShowable) { lines.push(`_Type:_ ${displayed}`, ""); @@ -447,10 +449,7 @@ function linkIfKnown(name, ctx) { function linkKnownName(name, label, ctx) { const moduleName = ctx.typedefIndex?.get(name); if (!moduleName || name === ctx.selfName || !ctx.renderedNames?.has(name)) return null; - const anchor = name - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-|-$/g, ""); + const anchor = apiSymbolAnchor(moduleName, name); return `[\`${label}\`](${moduleHref(ctx.moduleName, moduleName)}#${anchor})`; } @@ -473,17 +472,6 @@ function moduleHref(fromModule, toModule) { return `${rel}.md`; } -function callableAnchor(name, parent = null) { - return anchorForName(parent ? `${parent}-${name}` : name); -} - -function anchorForName(name) { - return name - .toLowerCase() - .replace(/[^a-z0-9_]+/g, "-") - .replace(/^-|-$/g, ""); -} - function renderUtilityType(utility, ctx) { const target = renderCallableReference(utility.target, ctx) ?? `\`${prettifyTypeString(utility.target)}\``; const suffix = utility.index == null ? "" : `[\`${utility.index}\`]`; diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 1b55a3b5c..c3dcb7fcf 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -28,7 +28,7 @@ "dev": "node scripts/dev.mjs", "build": "node scripts/build.mjs && pnpm typegen", "test": "node --experimental-vm-modules --expose-gc node_modules/jest/bin/jest.js --verbose --logHeapUsage", - "readme": "node ./docs/scripts/build_readme.js", + "readme": "node ./docs/scripts/build_readme.js --out ../../README.md", "docs-api": "node ./docs/scripts/generate.js", "docs-skill": "node ./docs/scripts/generate-skill.js", "docs-build": "doc-builder build transformers.js ./docs/source/ --not_python_module --build_dir ./docs/build/", diff --git a/packages/transformers/src/transformers.js b/packages/transformers/src/transformers.js index b4a625c33..47099b1b3 100644 --- a/packages/transformers/src/transformers.js +++ b/packages/transformers/src/transformers.js @@ -3,7 +3,7 @@ * this file is considered stable — other imports are internal and may change. * * **Start here** - * - [`pipeline()`](./pipelines.md#pipeline) — the one-call entry point for every task. + * - [`pipeline()`](./pipelines.md#module_pipelines.pipeline) — the one-call entry point for every task. * - [Environment](./env.md) — `env` fields and `LogLevel` enum. * * **Model loading** From fdc5fbf0f3ff73bcca814f372dca057e2024eb63 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 13:53:30 -0400 Subject: [PATCH 044/103] cleanup --- .../transformers/docs/scripts/generate-all.js | 16 +++++ .../docs/scripts/generate-skill.js | 21 +----- .../transformers/docs/scripts/generate.js | 47 +----------- .../docs/scripts/lib/generate-api.mjs | 43 +++++++++++ .../docs/scripts/lib/generate-skill.mjs | 17 +++++ .../transformers/docs/scripts/lib/paths.mjs | 12 ++++ .../docs/scripts/lib/validate.mjs | 71 +++++++++++++++++++ .../transformers/docs/scripts/validate.js | 7 ++ packages/transformers/package.json | 2 + 9 files changed, 174 insertions(+), 62 deletions(-) create mode 100644 packages/transformers/docs/scripts/generate-all.js create mode 100644 packages/transformers/docs/scripts/lib/generate-api.mjs create mode 100644 packages/transformers/docs/scripts/lib/generate-skill.mjs create mode 100644 packages/transformers/docs/scripts/lib/paths.mjs create mode 100644 packages/transformers/docs/scripts/lib/validate.mjs create mode 100644 packages/transformers/docs/scripts/validate.js diff --git a/packages/transformers/docs/scripts/generate-all.js b/packages/transformers/docs/scripts/generate-all.js new file mode 100644 index 000000000..01f2b2ceb --- /dev/null +++ b/packages/transformers/docs/scripts/generate-all.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +import { generateApiDocs } from "./lib/generate-api.mjs"; +import { generateSkillDocs } from "./lib/generate-skill.mjs"; +import { loadProject } from "./lib/load.mjs"; +import { packageRoot } from "./lib/paths.mjs"; +import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mjs"; + +const project = loadProject(packageRoot); + +generateApiDocs({ project }); +generateSkillDocs({ project }); + +const validation = validateGeneratedDocs(); +console.log(formatValidationResult(validation)); +if (!validation.ok) process.exitCode = 1; diff --git a/packages/transformers/docs/scripts/generate-skill.js b/packages/transformers/docs/scripts/generate-skill.js index d68b294fc..abe1d3ae6 100644 --- a/packages/transformers/docs/scripts/generate-skill.js +++ b/packages/transformers/docs/scripts/generate-skill.js @@ -1,20 +1,5 @@ -// Generate the `.ai/skills/transformers-js/` tree from the library's JSDoc. -// Injects generated task recipes into the hand-written SKILL.md (preserving -// editorial prose) and rewrites the fully-generated reference files. +#!/usr/bin/env node -import path from "node:path"; -import url from "node:url"; +import { generateSkillDocs } from "./lib/generate-skill.mjs"; -import { loadProject } from "./lib/load.mjs"; -import { renderSkill } from "./lib/render-skill.mjs"; - -const docs = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); -const packageRoot = path.dirname(docs); -const repoRoot = path.resolve(packageRoot, "..", ".."); -const skillDir = path.join(repoRoot, ".ai", "skills", "transformers-js"); - -const { ir, tasks, publicNames } = loadProject(packageRoot); - -renderSkill({ ir, tasks, publicNames, skillDir }); - -console.log(`wrote skill to ${path.relative(repoRoot, skillDir) || skillDir}`); +generateSkillDocs(); diff --git a/packages/transformers/docs/scripts/generate.js b/packages/transformers/docs/scripts/generate.js index c9fdf4a35..e9d279301 100644 --- a/packages/transformers/docs/scripts/generate.js +++ b/packages/transformers/docs/scripts/generate.js @@ -1,46 +1,5 @@ -// Generate per-module API markdown from the library's JSDoc comments. -// We only document items that are available in the public API. +#!/usr/bin/env node -import fs from "node:fs"; -import path from "node:path"; -import url from "node:url"; +import { generateApiDocs } from "./lib/generate-api.mjs"; -import { loadProject } from "./lib/load.mjs"; -import { renderModule } from "./lib/render-api.mjs"; - -const docs = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); -const root = path.dirname(docs); -const outputDir = path.join(root, "docs", "source", "api"); - -const { ir, publicNames } = loadProject(root); - -clearExistingMarkdown(); - -for (const mod of ir.modules) { - const rendered = renderModule(mod, ir, { publicNames }); - if (!hasPublicBody(rendered)) { - console.log(`skipped ${mod.name}.md — no public content`); - continue; - } - const outputPath = path.resolve(outputDir, `${mod.name}.md`); - fs.mkdirSync(path.dirname(outputPath), { recursive: true }); - fs.writeFileSync(outputPath, rendered); - console.log(`wrote ${mod.name}.md`); -} - -// A module earns a page when its render produces either a section -// (classes/functions/constants/typedefs) or a description that links out — the -// latter covers index / entry-point modules that exist to orient the reader. -// Modules that reduce to a bare title + prose get skipped. -function hasPublicBody(markdown) { - if (/^## /m.test(markdown)) return true; - const body = markdown.replace(/^# [^\n]+\n/, ""); - return /\]\(/.test(body); -} - -function clearExistingMarkdown() { - if (!fs.existsSync(outputDir)) return; - for (const entry of fs.readdirSync(outputDir, { recursive: true })) { - if (entry.endsWith(".md")) fs.unlinkSync(path.join(outputDir, entry)); - } -} +generateApiDocs(); diff --git a/packages/transformers/docs/scripts/lib/generate-api.mjs b/packages/transformers/docs/scripts/lib/generate-api.mjs new file mode 100644 index 000000000..c9fe56ef1 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/generate-api.mjs @@ -0,0 +1,43 @@ +import fs from "node:fs"; +import path from "node:path"; + +import { apiOutputDir, packageRoot } from "./paths.mjs"; +import { loadProject } from "./load.mjs"; +import { renderModule } from "./render-api.mjs"; + +export function generateApiDocs({ project = loadProject(packageRoot), outputDir = apiOutputDir, log = console.log } = {}) { + clearExistingMarkdown(outputDir); + + const written = []; + const skipped = []; + + for (const mod of project.ir.modules) { + const rendered = renderModule(mod, project.ir, { publicNames: project.publicNames }); + if (!hasPublicBody(rendered)) { + skipped.push(mod.name); + log(`skipped ${mod.name}.md — no public content`); + continue; + } + + const outputPath = path.resolve(outputDir, `${mod.name}.md`); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, rendered); + written.push(path.relative(outputDir, outputPath)); + log(`wrote ${mod.name}.md`); + } + + return { written, skipped }; +} + +function hasPublicBody(markdown) { + if (/^## /m.test(markdown)) return true; + const body = markdown.replace(/^# [^\n]+\n/, ""); + return /\]\(/.test(body); +} + +function clearExistingMarkdown(outputDir) { + if (!fs.existsSync(outputDir)) return; + for (const entry of fs.readdirSync(outputDir, { recursive: true })) { + if (entry.endsWith(".md")) fs.unlinkSync(path.join(outputDir, entry)); + } +} diff --git a/packages/transformers/docs/scripts/lib/generate-skill.mjs b/packages/transformers/docs/scripts/lib/generate-skill.mjs new file mode 100644 index 000000000..2da31b06d --- /dev/null +++ b/packages/transformers/docs/scripts/lib/generate-skill.mjs @@ -0,0 +1,17 @@ +import path from "node:path"; + +import { packageRoot, repoRoot, skillDir as defaultSkillDir } from "./paths.mjs"; +import { loadProject } from "./load.mjs"; +import { renderSkill } from "./render-skill.mjs"; + +export function generateSkillDocs({ project = loadProject(packageRoot), skillDir = defaultSkillDir, log = console.log } = {}) { + renderSkill({ + ir: project.ir, + tasks: project.tasks, + publicNames: project.publicNames, + skillDir, + }); + + log(`wrote skill to ${path.relative(repoRoot, skillDir) || skillDir}`); + return { skillDir }; +} diff --git a/packages/transformers/docs/scripts/lib/paths.mjs b/packages/transformers/docs/scripts/lib/paths.mjs new file mode 100644 index 000000000..0f9ce9b8c --- /dev/null +++ b/packages/transformers/docs/scripts/lib/paths.mjs @@ -0,0 +1,12 @@ +import path from "node:path"; +import url from "node:url"; + +const libDir = path.dirname(url.fileURLToPath(import.meta.url)); + +export const docsDir = path.resolve(libDir, "..", ".."); +export const packageRoot = path.dirname(docsDir); +export const repoRoot = path.resolve(packageRoot, "..", ".."); + +export const apiOutputDir = path.join(docsDir, "source", "api"); +export const toctreePath = path.join(docsDir, "source", "_toctree.yml"); +export const skillDir = path.join(repoRoot, ".ai", "skills", "transformers-js"); diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs new file mode 100644 index 000000000..b56ee1c9e --- /dev/null +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -0,0 +1,71 @@ +import fs from "node:fs"; +import path from "node:path"; + +import { apiOutputDir, toctreePath } from "./paths.mjs"; + +export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toctreePath } = {}) { + const generatedApiPages = listApiPages(outputDir); + const linkedApiPages = readToctreeApiPages(tocPath); + + const unlisted = difference(generatedApiPages, linkedApiPages); + const stale = difference(linkedApiPages, generatedApiPages); + + return { + ok: unlisted.length === 0 && stale.length === 0, + unlisted, + stale, + }; +} + +export function formatValidationResult(result) { + if (result.ok) return "validated docs toctree"; + + const lines = ["docs validation failed"]; + if (result.unlisted.length) { + lines.push("", "Generated API pages missing from _toctree.yml:"); + for (const page of result.unlisted) lines.push(`- ${page}`); + } + if (result.stale.length) { + lines.push("", "API pages listed in _toctree.yml but not generated:"); + for (const page of result.stale) lines.push(`- ${page}`); + } + return lines.join("\n"); +} + +function listApiPages(outputDir) { + return listMarkdown(outputDir) + .map((file) => toApiPage(outputDir, file)) + .sort(); +} + +function listMarkdown(dir) { + if (!fs.existsSync(dir)) return []; + + const out = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) out.push(...listMarkdown(full)); + else if (entry.name.endsWith(".md")) out.push(full); + } + return out; +} + +function toApiPage(outputDir, file) { + const relative = path.relative(outputDir, file).replaceAll(path.sep, "/").replace(/\.md$/, ""); + return `api/${relative}`; +} + +function readToctreeApiPages(tocPath) { + if (!fs.existsSync(tocPath)) return []; + + const pages = []; + const re = /^\s*-\s+local:\s+(api\/\S+)\s*$/gm; + const text = fs.readFileSync(tocPath, "utf8"); + for (const match of text.matchAll(re)) pages.push(match[1]); + return pages.sort(); +} + +function difference(a, b) { + const right = new Set(b); + return a.filter((item) => !right.has(item)); +} diff --git a/packages/transformers/docs/scripts/validate.js b/packages/transformers/docs/scripts/validate.js new file mode 100644 index 000000000..77e1c3cad --- /dev/null +++ b/packages/transformers/docs/scripts/validate.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mjs"; + +const validation = validateGeneratedDocs(); +console.log(formatValidationResult(validation)); +if (!validation.ok) process.exitCode = 1; diff --git a/packages/transformers/package.json b/packages/transformers/package.json index c3dcb7fcf..b07c22884 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -29,8 +29,10 @@ "build": "node scripts/build.mjs && pnpm typegen", "test": "node --experimental-vm-modules --expose-gc node_modules/jest/bin/jest.js --verbose --logHeapUsage", "readme": "node ./docs/scripts/build_readme.js --out ../../README.md", + "docs-generate": "node ./docs/scripts/generate-all.js", "docs-api": "node ./docs/scripts/generate.js", "docs-skill": "node ./docs/scripts/generate-skill.js", + "docs-check": "node ./docs/scripts/validate.js", "docs-build": "doc-builder build transformers.js ./docs/source/ --not_python_module --build_dir ./docs/build/", "docs-preview": "doc-builder preview transformers.js ./docs/source/ --not_python_module" }, From 69937d37d553283574b0232a9b256a894426832a Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:00:17 -0400 Subject: [PATCH 045/103] use single docs generation command --- .ai/skills/transformers-js/references/TASKS.md | 2 +- packages/transformers/docs/scripts/generate-skill.js | 5 ----- packages/transformers/docs/scripts/generate.js | 5 ----- packages/transformers/docs/scripts/lib/render-skill.mjs | 2 +- packages/transformers/docs/scripts/validate.js | 7 ------- packages/transformers/package.json | 3 --- 6 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 packages/transformers/docs/scripts/generate-skill.js delete mode 100644 packages/transformers/docs/scripts/generate.js delete mode 100644 packages/transformers/docs/scripts/validate.js diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index 43cb6fc96..2c71a4ae5 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -1,4 +1,4 @@ -<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-skill.js --> +<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-all.js --> # Tasks diff --git a/packages/transformers/docs/scripts/generate-skill.js b/packages/transformers/docs/scripts/generate-skill.js deleted file mode 100644 index abe1d3ae6..000000000 --- a/packages/transformers/docs/scripts/generate-skill.js +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -import { generateSkillDocs } from "./lib/generate-skill.mjs"; - -generateSkillDocs(); diff --git a/packages/transformers/docs/scripts/generate.js b/packages/transformers/docs/scripts/generate.js deleted file mode 100644 index e9d279301..000000000 --- a/packages/transformers/docs/scripts/generate.js +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -import { generateApiDocs } from "./lib/generate-api.mjs"; - -generateApiDocs(); diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index e54a38817..5830842bc 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -10,7 +10,7 @@ import fs from "node:fs"; import path from "node:path"; -const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-skill.js -->"; +const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-all.js -->"; const DOCS_SITE = "https://huggingface.co/docs/transformers.js/api"; export function renderSkill({ ir, tasks, publicNames, skillDir }) { diff --git a/packages/transformers/docs/scripts/validate.js b/packages/transformers/docs/scripts/validate.js deleted file mode 100644 index 77e1c3cad..000000000 --- a/packages/transformers/docs/scripts/validate.js +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node - -import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mjs"; - -const validation = validateGeneratedDocs(); -console.log(formatValidationResult(validation)); -if (!validation.ok) process.exitCode = 1; diff --git a/packages/transformers/package.json b/packages/transformers/package.json index b07c22884..4d5d7d011 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -30,9 +30,6 @@ "test": "node --experimental-vm-modules --expose-gc node_modules/jest/bin/jest.js --verbose --logHeapUsage", "readme": "node ./docs/scripts/build_readme.js --out ../../README.md", "docs-generate": "node ./docs/scripts/generate-all.js", - "docs-api": "node ./docs/scripts/generate.js", - "docs-skill": "node ./docs/scripts/generate-skill.js", - "docs-check": "node ./docs/scripts/validate.js", "docs-build": "doc-builder build transformers.js ./docs/source/ --not_python_module --build_dir ./docs/build/", "docs-preview": "doc-builder preview transformers.js ./docs/source/ --not_python_module" }, From 03b4044a65a3a7c125a6456b5251c554dd88aa81 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:02:38 -0400 Subject: [PATCH 046/103] Update pipelines.md --- packages/transformers/docs/source/pipelines.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/transformers/docs/source/pipelines.md b/packages/transformers/docs/source/pipelines.md index 024f51c21..81f82c021 100644 --- a/packages/transformers/docs/source/pipelines.md +++ b/packages/transformers/docs/source/pipelines.md @@ -45,8 +45,6 @@ const result = await classifier([ You can also specify a different model to use for the pipeline by passing it as the second argument to the `pipeline()` function. For example, to use a different model for sentiment analysis (like one trained to predict sentiment of a review as a number of stars between 1 and 5), you can do: -<!-- TODO: REPLACE 'nlptown/bert-base-multilingual-uncased-sentiment' with 'nlptown/bert-base-multilingual-uncased-sentiment'--> - ```javascript const reviewer = await pipeline( "sentiment-analysis", @@ -63,8 +61,6 @@ Transformers.js supports loading any model hosted on the Hugging Face Hub, provi The `pipeline()` function is a great way to quickly use a pretrained model for inference, as it takes care of all the preprocessing and postprocessing for you. For example, if you want to perform Automatic Speech Recognition (ASR) using OpenAI's Whisper model, you can do: -<!-- TODO: Replace 'Xenova/whisper-small.en' with 'openai/whisper-small.en' --> - ```javascript // Create a pipeline for Automatic Speech Recognition const transcriber = await pipeline( @@ -112,12 +108,10 @@ const transcriber = await pipeline( For the full list of options, check out the [PretrainedOptions](./api/utils/hub#module_utils/hub..PretrainedOptions) documentation. -### Running +### Runtime parameters Many pipelines have additional options that you can specify. For example, when using a model that does multilingual translation, you can specify the source and target languages like this: -<!-- TODO: Replace 'Xenova/nllb-200-distilled-600M' with 'facebook/nllb-200-distilled-600M' --> - ```javascript // Create a pipeline for translation const translator = await pipeline( @@ -144,8 +138,6 @@ When using models that support auto-regressive generation, you can specify gener For example, to generate a poem using `LaMini-Flan-T5-783M`, you can do: -<!-- TODO: Replace 'Xenova/LaMini-Flan-T5-783M' with 'MBZUAI/LaMini-Flan-T5-783M' --> - ```javascript // Create a pipeline for text2text-generation const poet = await pipeline( From 0b8cb77373d9678d866036e823535dd53fc0abce Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:15:14 -0400 Subject: [PATCH 047/103] fix typos --- packages/transformers/docs/source/pipelines.md | 4 ++-- packages/transformers/docs/source/tutorials/vanilla-js.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/transformers/docs/source/pipelines.md b/packages/transformers/docs/source/pipelines.md index 81f82c021..227d030f3 100644 --- a/packages/transformers/docs/source/pipelines.md +++ b/packages/transformers/docs/source/pipelines.md @@ -106,7 +106,7 @@ const transcriber = await pipeline( ); ``` -For the full list of options, check out the [PretrainedOptions](./api/utils/hub#module_utils/hub..PretrainedOptions) documentation. +For the full list of options, check out the [PretrainedOptions](./api/utils/hub#module_utils/hub.PretrainedOptions) documentation. ### Runtime parameters @@ -134,7 +134,7 @@ const result2 = await translator(result[0].translation_text, { // [ { translation_text: 'I like to walk my dog.' } ] ``` -When using models that support auto-regressive generation, you can specify generation parameters like the number of new tokens, sampling methods, temperature, repetition penalty, and much more. For a full list of available parameters, see to the [GenerationConfig](./api/utils/generation#module_utils/generation.GenerationConfig) class. +When using models that support auto-regressive generation, you can specify generation parameters like the number of new tokens, sampling methods, temperature, repetition penalty, and much more. For a full list of available parameters, see the [GenerationConfig](./api/generation/configuration_utils#module_generation/configuration_utils.GenerationConfig) class. For example, to generate a poem using `LaMini-Flan-T5-783M`, you can do: diff --git a/packages/transformers/docs/source/tutorials/vanilla-js.md b/packages/transformers/docs/source/tutorials/vanilla-js.md index 049d085a3..fa14a5710 100644 --- a/packages/transformers/docs/source/tutorials/vanilla-js.md +++ b/packages/transformers/docs/source/tutorials/vanilla-js.md @@ -128,7 +128,7 @@ const status = document.getElementById("status"); ## Step 3: Create an object detection pipeline -We’re finally ready to create our object detection pipeline! As a reminder, a [pipeline](../pipelines). is a high-level interface provided by the library to perform a specific task. In our case, we will instantiate an object detection pipeline with the `pipeline()` helper function. +We’re finally ready to create our object detection pipeline! As a reminder, a [pipeline](../pipelines) is a high-level interface provided by the library to perform a specific task. In our case, we will instantiate an object detection pipeline with the `pipeline()` helper function. Since this can take some time (especially the first time when we have to download the ~40MB model), we first update the `status` paragraph so that the user knows that we’re about to load the model. @@ -138,7 +138,7 @@ status.textContent = "Loading model..."; <Tip> -To keep this tutorial simple, we'll be loading and running the model in the main (UI) thread. This is not recommended for production applications, since the UI will freeze when we're performing these actions. This is because JavaScript is a single-threaded language. To overcome this, you can use a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to download and run the model in the background. However, we’re not going to do cover that in this tutorial... +To keep this tutorial simple, we'll be loading and running the model in the main (UI) thread. This is not recommended for production applications, since the UI will freeze when we're performing these actions. This is because JavaScript is a single-threaded language. To overcome this, you can use a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to download and run the model in the background. However, we’re not going to cover that in this tutorial... </Tip> From 9bbfee84817ccc1eed73f93c35c4e905da401625 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:16:41 -0400 Subject: [PATCH 048/103] fix links --- README.md | 11 +- .../transformers/docs/scripts/build_readme.js | 5 +- .../docs/scripts/lib/validate.mjs | 100 +++++++++++++++++- .../docs/snippets/0_introduction.snippet | 2 +- .../docs/snippets/5_supported-models.snippet | 10 +- .../transformers/docs/source/guides/dtypes.md | 4 +- .../source/guides/node-audio-processing.md | 2 +- .../transformers/docs/source/guides/webgpu.md | 12 +-- .../docs/source/tutorials/next.md | 4 +- .../docs/source/tutorials/node.md | 2 +- 10 files changed, 125 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 19e8eca4b..c5c090c1e 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Transformers.js is designed to be functionally equivalent to Hugging Face's [tra - 🗣️ **Audio**: automatic speech recognition, audio classification, and text-to-speech. - 🐙 **Multimodal**: embeddings, zero-shot audio classification, zero-shot image classification, and zero-shot object detection. -Transformers.js uses [ONNX Runtime](https://onnxruntime.ai/) to run models in the browser. The best part about it, is that you can easily [convert](#convert-your-models-to-onnx) your pretrained PyTorch, TensorFlow, or JAX models to ONNX using [🤗 Optimum](https://github.com/huggingface/optimum#onnx--onnx-runtime). +Transformers.js uses [ONNX Runtime](https://onnxruntime.ai/) to run models in the browser. The best part about it, is that you can easily [convert](#convert-your-models-to-onnx) your pretrained PyTorch, TensorFlow, or JAX models to ONNX using [🤗 Optimum](https://github.com/huggingface/optimum#onnx--onnx-runtime). For more information, check out the full [documentation](https://huggingface.co/docs/transformers.js). @@ -249,7 +249,7 @@ To find compatible models on the Hub, select the "transformers.js" library tag i 1. **[BERT](https://huggingface.co/docs/transformers/model_doc/bert)** (from Google) released with the paper [BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding](https://huggingface.co/papers/1810.04805) by Jacob Devlin, Ming-Wei Chang, Kenton Lee and Kristina Toutanova. 1. **[Blenderbot](https://huggingface.co/docs/transformers/model_doc/blenderbot)** (from Facebook) released with the paper [Recipes for building an open-domain chatbot](https://huggingface.co/papers/2004.13637) by Stephen Roller, Emily Dinan, Naman Goyal, Da Ju, Mary Williamson, Yinhan Liu, Jing Xu, Myle Ott, Kurt Shuster, Eric M. Smith, Y-Lan Boureau, Jason Weston. 1. **[BlenderbotSmall](https://huggingface.co/docs/transformers/model_doc/blenderbot-small)** (from Facebook) released with the paper [Recipes for building an open-domain chatbot](https://huggingface.co/papers/2004.13637) by Stephen Roller, Emily Dinan, Naman Goyal, Da Ju, Mary Williamson, Yinhan Liu, Jing Xu, Myle Ott, Kurt Shuster, Eric M. Smith, Y-Lan Boureau, Jason Weston. -1. **[BLOOM](https://huggingface.co/docs/transformers/model_doc/bloom)** (from BigScience workshop) released by the [BigScience Workshop](https://bigscience.huggingface.co/). +1. **[BLOOM](https://huggingface.co/docs/transformers/model_doc/bloom)** (from BigScience workshop) released by the [BigScience Workshop](https://huggingface.co/bigscience). 1. **[CamemBERT](https://huggingface.co/docs/transformers/model_doc/camembert)** (from Inria/Facebook/Sorbonne) released with the paper [CamemBERT: a Tasty French Language Model](https://huggingface.co/papers/1911.03894) by Louis Martin*, Benjamin Muller*, Pedro Javier Ortiz Suárez*, Yoann Dupont, Laurent Romary, Éric Villemonte de la Clergerie, Djamé Seddah and Benoît Sagot. 1. **[CHMv2](https://huggingface.co/docs/transformers/main/model_doc/chmv2)** (from Meta) released with the paper [CHMv2: Improvements in Global Canopy Height Mapping using DINOv3](https://huggingface.co/papers/2603.06382) by John Brandt, Seungeun Yi, Jamie Tolan, Xinyuan Li, Peter Potapov, Jessica Ertel, Justine Spore, Huy V. Vo, Michaël Ramamonjisoa, Patrick Labatut, Piotr Bojanowski, Camille Couprie. 1. **Chatterbox** (from Resemble AI) released with the repository [Chatterbox TTS](https://github.com/resemble-ai/chatterbox) by the Resemble AI team. @@ -278,7 +278,7 @@ To find compatible models on the Hub, select the "transformers.js" library tag i 1. **[DINOv2](https://huggingface.co/docs/transformers/model_doc/dinov2)** (from Meta AI) released with the paper [DINOv2: Learning Robust Visual Features without Supervision](https://huggingface.co/papers/2304.07193) by Maxime Oquab, Timothée Darcet, Théo Moutakanni, Huy Vo, Marc Szafraniec, Vasil Khalidov, Pierre Fernandez, Daniel Haziza, Francisco Massa, Alaaeldin El-Nouby, Mahmoud Assran, Nicolas Ballas, Wojciech Galuba, Russell Howes, Po-Yao Huang, Shang-Wen Li, Ishan Misra, Michael Rabbat, Vasu Sharma, Gabriel Synnaeve, Hu Xu, Hervé Jegou, Julien Mairal, Patrick Labatut, Armand Joulin, Piotr Bojanowski. 1. **[DINOv2 with Registers](https://huggingface.co/docs/transformers/model_doc/dinov2_with_registers)** (from Meta AI) released with the paper [DINOv2 with Registers](https://huggingface.co/papers/2309.16588) by Timothée Darcet, Maxime Oquab, Julien Mairal, Piotr Bojanowski. 1. **[DINOv3](https://huggingface.co/docs/transformers/model_doc/dinov3)** (from Meta AI) released with the paper [DINOv3](https://huggingface.co/papers/2508.10104) by Oriane Siméoni, Huy V. Vo, Maximilian Seitzer, Federico Baldassarre, Maxime Oquab, Cijo Jose, Vasil Khalidov, Marc Szafraniec, Seungeun Yi, Michaël Ramamonjisoa, Francisco Massa, Daniel Haziza, Luca Wehrstedt, Jianyuan Wang, Timothée Darcet, Théo Moutakanni, Leonel Sentana, Claire Roberts, Andrea Vedaldi, Jamie Tolan, John Brandt, Camille Couprie, Julien Mairal, Hervé Jégou, Patrick Labatut, Piotr Bojanowski. -1. **[DistilBERT](https://huggingface.co/docs/transformers/model_doc/distilbert)** (from HuggingFace), released together with the paper [DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter](https://huggingface.co/papers/1910.01108) by Victor Sanh, Lysandre Debut and Thomas Wolf. The same method has been applied to compress GPT2 into [DistilGPT2](https://github.com/huggingface/transformers/tree/main/examples/research_projects/distillation), RoBERTa into [DistilRoBERTa](https://github.com/huggingface/transformers/tree/main/examples/research_projects/distillation), Multilingual BERT into [DistilmBERT](https://github.com/huggingface/transformers/tree/main/examples/research_projects/distillation) and a German version of DistilBERT. +1. **[DistilBERT](https://huggingface.co/docs/transformers/model_doc/distilbert)** (from HuggingFace), released together with the paper [DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter](https://huggingface.co/papers/1910.01108) by Victor Sanh, Lysandre Debut and Thomas Wolf. The same method has been applied to compress GPT2 into DistilGPT2, RoBERTa into DistilRoBERTa, Multilingual BERT into DistilmBERT and a German version of DistilBERT. 1. **[DiT](https://huggingface.co/docs/transformers/model_doc/dit)** (from Microsoft Research) released with the paper [DiT: Self-supervised Pre-training for Document Image Transformer](https://huggingface.co/papers/2203.02378) by Junlong Li, Yiheng Xu, Tengchao Lv, Lei Cui, Cha Zhang, Furu Wei. 1. **[Donut](https://huggingface.co/docs/transformers/model_doc/donut)** (from NAVER), released together with the paper [OCR-free Document Understanding Transformer](https://huggingface.co/papers/2111.15664) by Geewook Kim, Teakgyu Hong, Moonbin Yim, Jeongyeon Nam, Jinyoung Park, Jinyeong Yim, Wonseok Hwang, Sangdoo Yun, Dongyoon Han, Seunghyun Park. 1. **[DPT](https://huggingface.co/docs/transformers/master/model_doc/dpt)** (from Intel Labs) released with the paper [Vision Transformers for Dense Prediction](https://huggingface.co/papers/2103.13413) by René Ranftl, Alexey Bochkovskiy, Vladlen Koltun. @@ -306,7 +306,7 @@ To find compatible models on the Hub, select the "transformers.js" library tag i 1. **[GPT Neo](https://huggingface.co/docs/transformers/model_doc/gpt_neo)** (from EleutherAI) released in the repository [EleutherAI/gpt-neo](https://github.com/EleutherAI/gpt-neo) by Sid Black, Stella Biderman, Leo Gao, Phil Wang and Connor Leahy. 1. **[GPT NeoX](https://huggingface.co/docs/transformers/model_doc/gpt_neox)** (from EleutherAI) released with the paper [GPT-NeoX-20B: An Open-Source Autoregressive Language Model](https://huggingface.co/papers/2204.06745) by Sid Black, Stella Biderman, Eric Hallahan, Quentin Anthony, Leo Gao, Laurence Golding, Horace He, Connor Leahy, Kyle McDonell, Jason Phang, Michael Pieler, USVSN Sai Prashanth, Shivanshu Purohit, Laria Reynolds, Jonathan Tow, Ben Wang, Samuel Weinbach 1. **[GPT OSS](https://huggingface.co/docs/transformers/model_doc/gpt_oss)** (from OpenAI) released with the blog [Introducing gpt-oss](https://openai.com/index/introducing-gpt-oss/) by Sandhini Agarwal, Lama Ahmad, Jason Ai, Sam Altman, Andy Applebaum, Edwin Arbus, Rahul K. Arora, Yu Bai, Bowen Baker, Haiming Bao, Boaz Barak, Ally Bennett, Tyler Bertao, Nivedita Brett, Eugene Brevdo, Greg Brockman, Sebastien Bubeck, Che Chang, Kai Chen, Mark Chen, Enoch Cheung, Aidan Clark, Dan Cook, Marat Dukhan, Casey Dvorak, Kevin Fives, Vlad Fomenko, Timur Garipov, Kristian Georgiev, Mia Glaese, Tarun Gogineni, Adam Goucher, Lukas Gross, Katia Gil Guzman, John Hallman, Jackie Hehir, Johannes Heidecke, Alec Helyar, Haitang Hu, Romain Huet, Jacob Huh, Saachi Jain, Zach Johnson, Chris Koch, Irina Kofman, Dominik Kundel, Jason Kwon, Volodymyr Kyrylov, Elaine Ya Le, Guillaume Leclerc, James Park Lennon, Scott Lessans, Mario Lezcano-Casado, Yuanzhi Li, Zhuohan Li, Ji Lin, Jordan Liss, Lily (Xiaoxuan) Liu, Jiancheng Liu, Kevin Lu, Chris Lu, Zoran Martinovic, Lindsay McCallum, Josh McGrath, Scott McKinney, Aidan McLaughlin, Song Mei, Steve Mostovoy, Tong Mu, Gideon Myles, Alexander Neitz, Alex Nichol, Jakub Pachocki, Alex Paino, Dana Palmie, Ashley Pantuliano, Giambattista Parascandolo, Jongsoo Park, Leher Pathak, Carolina Paz, Ludovic Peran, Dmitry Pimenov, Michelle Pokrass, Elizabeth Proehl, Huida Qiu, Gaby Raila, Filippo Raso, Hongyu Ren, Kimmy Richardson, David Robinson, Bob Rotsted, Hadi Salman, Suvansh Sanjeev, Max Schwarzer, D. Sculley, Harshit Sikchi, Kendal Simon, Karan Singhal, Yang Song, Dane Stuckey, Zhiqing Sun, Philippe Tillet, Sam Toizer, Foivos Tsimpourlas, Nikhil Vyas, Eric Wallace, Xin Wang, Miles Wang, Olivia Watkins, Kevin Weil, Amy Wendling, Kevin Whinnery, Cedric Whitney, Hannah Wong, Lin Yang, Yu Yang, Michihiro Yasunaga, Kristen Ying, Wojciech Zaremba, Wenting Zhan, Cyril Zhang, Brian Zhang, Eddie Zhang, Shengjia Zhao. -1. **[GPT-2](https://huggingface.co/docs/transformers/model_doc/gpt2)** (from OpenAI) released with the paper [Language Models are Unsupervised Multitask Learners](https://blog.openai.com/better-language-models/) by Alec Radford*, Jeffrey Wu*, Rewon Child, David Luan, Dario Amodei** and Ilya Sutskever**. +1. **[GPT-2](https://huggingface.co/docs/transformers/model_doc/gpt2)** (from OpenAI) released with the paper [Language Models are Unsupervised Multitask Learners](https://openai.com/index/better-language-models/) by Alec Radford*, Jeffrey Wu*, Rewon Child, David Luan, Dario Amodei** and Ilya Sutskever**. 1. **[GPT-J](https://huggingface.co/docs/transformers/model_doc/gptj)** (from EleutherAI) released in the repository [kingoflolz/mesh-transformer-jax](https://github.com/kingoflolz/mesh-transformer-jax/) by Ben Wang and Aran Komatsuzaki. 1. **[GPTBigCode](https://huggingface.co/docs/transformers/model_doc/gpt_bigcode)** (from BigCode) released with the paper [SantaCoder: don't reach for the stars!](https://huggingface.co/papers/2301.03988) by Loubna Ben Allal, Raymond Li, Denis Kocetkov, Chenghao Mou, Christopher Akiki, Carlos Munoz Ferrandis, Niklas Muennighoff, Mayank Mishra, Alex Gu, Manan Dey, Logesh Kumar Umapathi, Carolyn Jane Anderson, Yangtian Zi, Joel Lamy Poirier, Hailey Schoelkopf, Sergey Troshin, Dmitry Abulkhanov, Manuel Romero, Michael Lappert, Francesco De Toni, Bernardo García del Río, Qian Liu, Shamik Bose, Urvashi Bhattacharyya, Terry Yue Zhuo, Ian Yu, Paulo Villegas, Marco Zocca, Sourab Mangrulkar, David Lansky, Huu Nguyen, Danish Contractor, Luis Villa, Jia Li, Dzmitry Bahdanau, Yacine Jernite, Sean Hughes, Daniel Fried, Arjun Guha, Harm de Vries, Leandro von Werra. 1. **[Granite](https://huggingface.co/docs/transformers/main/model_doc/granite)** (from IBM) released with the paper [Power Scheduler: A Batch Size and Token Number Agnostic Learning Rate Scheduler](https://huggingface.co/papers/2408.13359) by Yikang Shen, Matthew Stallone, Mayank Mishra, Gaoyuan Zhang, Shawn Tan, Aditya Prasad, Adriana Meza Soria, David D. Cox, Rameswar Panda. @@ -420,7 +420,7 @@ To find compatible models on the Hub, select the "transformers.js" library tag i 1. **SNAC** (from Papla Media, ETH Zurich) released with the paper [SNAC: Multi-Scale Neural Audio Codec](https://huggingface.co/papers/2410.14411) by Hubert Siuzdak, Florian Grötschla, Luca A. Lanzendörfer. 1. **[SpeechT5](https://huggingface.co/docs/transformers/model_doc/speecht5)** (from Microsoft Research) released with the paper [SpeechT5: Unified-Modal Encoder-Decoder Pre-Training for Spoken Language Processing](https://huggingface.co/papers/2110.07205) by Junyi Ao, Rui Wang, Long Zhou, Chengyi Wang, Shuo Ren, Yu Wu, Shujie Liu, Tom Ko, Qing Li, Yu Zhang, Zhihua Wei, Yao Qian, Jinyu Li, Furu Wei. 1. **[SqueezeBERT](https://huggingface.co/docs/transformers/model_doc/squeezebert)** (from Berkeley) released with the paper [SqueezeBERT: What can computer vision teach NLP about efficient neural networks?](https://huggingface.co/papers/2006.11316) by Forrest N. Iandola, Albert E. Shaw, Ravi Krishna, and Kurt W. Keutzer. -1. **[StableLm](https://huggingface.co/docs/transformers/model_doc/stablelm)** (from Stability AI) released with the paper [StableLM 3B 4E1T (Technical Report)](https://stability.wandb.io/stability-llm/stable-lm/reports/StableLM-3B-4E1T--VmlldzoyMjU4?accessToken=u3zujipenkx5g7rtcj9qojjgxpconyjktjkli2po09nffrffdhhchq045vp0wyfo) by Jonathan Tow, Marco Bellagente, Dakota Mahan, Carlos Riquelme Ruiz, Duy Phung, Maksym Zhuravinskyi, Nathan Cooper, Nikhil Pinnaparaju, Reshinth Adithyan, and James Baicoianu. +1. **[StableLm](https://huggingface.co/docs/transformers/model_doc/stablelm)** (from Stability AI) released with the paper [StableLM 3B 4E1T (Technical Report)](https://github.com/Stability-AI/StableLM#stablelm-3b-4e1t) by Jonathan Tow, Marco Bellagente, Dakota Mahan, Carlos Riquelme Ruiz, Duy Phung, Maksym Zhuravinskyi, Nathan Cooper, Nikhil Pinnaparaju, Reshinth Adithyan, and James Baicoianu. 1. **[Starcoder2](https://huggingface.co/docs/transformers/main/model_doc/starcoder2)** (from BigCode team) released with the paper [StarCoder 2 and The Stack v2: The Next Generation](https://huggingface.co/papers/2402.19173) by Anton Lozhkov, Raymond Li, Loubna Ben Allal, Federico Cassano, Joel Lamy-Poirier, Nouamane Tazi, Ao Tang, Dmytro Pykhtar, Jiawei Liu, Yuxiang Wei, Tianyang Liu, Max Tian, Denis Kocetkov, Arthur Zucker, Younes Belkada, Zijian Wang, Qian Liu, Dmitry Abulkhanov, Indraneil Paul, Zhuang Li, Wen-Ding Li, Megan Risdal, Jia Li, Jian Zhu, Terry Yue Zhuo, Evgenii Zheltonozhskii, Nii Osae Osae Dade, Wenhao Yu, Lucas Krauß, Naman Jain, Yixuan Su, Xuanli He, Manan Dey, Edoardo Abati, Yekun Chai, Niklas Muennighoff, Xiangru Tang, Muhtasham Oblokulov, Christopher Akiki, Marc Marone, Chenghao Mou, Mayank Mishra, Alex Gu, Binyuan Hui, Tri Dao, Armel Zebaze, Olivier Dehaene, Nicolas Patry, Canwen Xu, Julian McAuley, Han Hu, Torsten Scholak, Sebastien Paquet, Jennifer Robinson, Carolyn Jane Anderson, Nicolas Chapados, Mostofa Patwary, Nima Tajbakhsh, Yacine Jernite, Carlos Muñoz Ferrandis, Lingming Zhang, Sean Hughes, Thomas Wolf, Arjun Guha, Leandro von Werra, and Harm de Vries. 1. **StyleTTS 2** (from Columbia University) released with the paper [StyleTTS 2: Towards Human-Level Text-to-Speech through Style Diffusion and Adversarial Training with Large Speech Language Models](https://huggingface.co/papers/2306.07691) by Yinghao Aaron Li, Cong Han, Vinay S. Raghavan, Gavin Mischler, Nima Mesgarani. 1. **Supertonic** (from Supertone) released with the paper [SupertonicTTS: Towards Highly Efficient and Streamlined Text-to-Speech System](https://huggingface.co/papers/2503.23108) by Hyeongju Kim, Jinhyeok Yang, Yechan Yu, Seunghun Ji, Jacob Morton, Frederik Bous, Joon Byun, Juheon Lee. @@ -450,3 +450,4 @@ To find compatible models on the Hub, select the "transformers.js" library tag i 1. **[XLM-RoBERTa](https://huggingface.co/docs/transformers/model_doc/xlm-roberta)** (from Facebook AI), released together with the paper [Unsupervised Cross-lingual Representation Learning at Scale](https://huggingface.co/papers/1911.02116) by Alexis Conneau*, Kartikay Khandelwal*, Naman Goyal, Vishrav Chaudhary, Guillaume Wenzek, Francisco Guzmán, Edouard Grave, Myle Ott, Luke Zettlemoyer and Veselin Stoyanov. 1. **[YOLOS](https://huggingface.co/docs/transformers/model_doc/yolos)** (from Huazhong University of Science & Technology) released with the paper [You Only Look at One Sequence: Rethinking Transformer in Vision through Object Detection](https://huggingface.co/papers/2106.00666) by Yuxin Fang, Bencheng Liao, Xinggang Wang, Jiemin Fang, Jiyang Qi, Rui Wu, Jianwei Niu, Wenyu Liu. 1. **[Youtu-LLM](https://huggingface.co/docs/transformers/model_doc/youtu)** (from the Tencent Youtu Team) released with the paper [Youtu-LLM: Unlocking the Native Agentic Potential for Lightweight Large Language Models](https://huggingface.co/papers/2512.24618) by Junru Lu, Jiarui Qin, Lingfeng Qiao, Yinghui Li, Xinyi Dai, Bo Ke, Jianfeng He, Ruizhi Qiao, Di Yin, Xing Sun, Yunsheng Wu, Yinsong Liu, Shuangyin Liu, Mingkong Tang, Haodong Lin, Jiayi Kuang, Fanxu Meng, Xiaojuan Tang, Yunjia Xi, Junjie Huang, Haotong Yang, Zhenyi Shen, Yangning Li, Qianwen Zhang, Yifei Yu, Siyu An, Junnan Dong, Qiufeng Wang, Jie Wang, Keyu Chen, Wei Wen, Taian Guo, Zhifeng Shen, Daohai Yu, Jiahao Li, Ke Li, Zongyi Li, Xiaoyu Tan. + diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index b6750e734..4b94e667b 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -23,6 +23,7 @@ const PIPELINE_API_LINK_PREFIX = `${DOCS_BASE_URL}/api/pipelines#module_pipeline // Links that should point somewhere other than the direct docs URL. Most are // README-local anchors or guide/API pages referenced by snippets. const CUSTOM_LINK_MAP = { + "./custom_usage#convert-your-models-to-onnx": "#convert-your-models-to-onnx", "/custom_usage#convert-your-models-to-onnx": "#convert-your-models-to-onnx", "./api/env": `${DOCS_BASE_URL}/api/env`, "./guides/webgpu": `${DOCS_BASE_URL}/guides/webgpu`, @@ -33,9 +34,7 @@ function main() { const { out } = parseArgs(process.argv.slice(2)); const { ir, publicNames } = loadProject(packageRoot); const apiLinks = buildApiSymbolLinks(ir, publicNames); - const snippets = Object.fromEntries( - Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(path.join(packageRoot, file), "utf8")]), - ); + const snippets = Object.fromEntries(Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(path.join(packageRoot, file), "utf8")])); const readme = fixLinks(renderTemplate(snippets), apiLinks); fs.writeFileSync(path.resolve(packageRoot, out), readme, "utf8"); diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs index b56ee1c9e..fbd15c17c 100644 --- a/packages/transformers/docs/scripts/lib/validate.mjs +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -6,14 +6,17 @@ import { apiOutputDir, toctreePath } from "./paths.mjs"; export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toctreePath } = {}) { const generatedApiPages = listApiPages(outputDir); const linkedApiPages = readToctreeApiPages(tocPath); + const sourceDir = path.dirname(tocPath); + const brokenLinks = validateInternalLinks(sourceDir); const unlisted = difference(generatedApiPages, linkedApiPages); const stale = difference(linkedApiPages, generatedApiPages); return { - ok: unlisted.length === 0 && stale.length === 0, + ok: unlisted.length === 0 && stale.length === 0 && brokenLinks.length === 0, unlisted, stale, + brokenLinks, }; } @@ -29,6 +32,13 @@ export function formatValidationResult(result) { lines.push("", "API pages listed in _toctree.yml but not generated:"); for (const page of result.stale) lines.push(`- ${page}`); } + if (result.brokenLinks.length) { + lines.push("", "Broken local markdown links:"); + for (const link of result.brokenLinks) { + const anchor = link.anchor ? `#${link.anchor}` : ""; + lines.push(`- ${link.file}: ${link.target} -> ${link.resolved}${anchor} (${link.type})`); + } + } return lines.join("\n"); } @@ -69,3 +79,91 @@ function difference(a, b) { const right = new Set(b); return a.filter((item) => !right.has(item)); } + +function validateInternalLinks(sourceDir) { + const files = listMarkdown(sourceDir); + const fileSet = new Set(files.map((file) => relativeMarkdownPath(sourceDir, file))); + const anchorsByFile = new Map(files.map((file) => [relativeMarkdownPath(sourceDir, file), collectAnchors(file)])); + const issues = []; + + for (const file of files) { + const rel = relativeMarkdownPath(sourceDir, file); + const text = readMarkdownWithIncludes(file); + + for (const match of text.matchAll(MARKDOWN_LINK_RE)) { + const target = match[1] ?? match[2]; + if (!isLocalLink(target)) continue; + + const [targetPath, rawAnchor = ""] = target.split("#"); + let resolved = rel; + if (targetPath) { + resolved = resolveMarkdownTarget(rel, targetPath); + if (!fileSet.has(resolved)) { + issues.push({ type: "missing-file", file: rel, target, resolved }); + continue; + } + } + + if (rawAnchor) { + const anchor = decodeURIComponent(rawAnchor); + if (!anchorsByFile.get(resolved)?.has(anchor)) { + issues.push({ type: "missing-anchor", file: rel, target, resolved, anchor }); + } + } + } + } + + return issues; +} + +const MARKDOWN_LINK_RE = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; + +function isLocalLink(target) { + return !!target && !/^[a-z][a-z0-9+.-]*:/i.test(target) && !target.startsWith("//"); +} + +function resolveMarkdownTarget(fromFile, target) { + let resolved = path.posix.normalize(path.posix.join(path.posix.dirname(fromFile), target)); + if (!path.posix.extname(resolved)) resolved += ".md"; + return resolved; +} + +function relativeMarkdownPath(sourceDir, file) { + return path.relative(sourceDir, file).replaceAll(path.sep, "/"); +} + +function collectAnchors(file) { + const text = readMarkdownWithIncludes(file); + const anchors = new Set([""]); + + for (const match of text.matchAll(/<a\s+id=["']([^"']+)["']/g)) anchors.add(match[1]); + for (const match of text.matchAll(/^#{1,6}\s+(.+)$/gm)) anchors.add(slugHeading(match[1])); + + return anchors; +} + +function readMarkdownWithIncludes(file, seen = new Set()) { + if (seen.has(file)) return ""; + seen.add(file); + + const text = fs.readFileSync(file, "utf8"); + return text.replace(/<include>\s*([\s\S]*?)\s*<\/include>/g, (match, rawConfig) => { + const includePath = rawConfig.match(/"path"\s*:\s*"([^"]+)"/)?.[1]; + if (!includePath) return match; + + const resolved = path.resolve(path.dirname(file), includePath); + return fs.existsSync(resolved) ? readMarkdownWithIncludes(resolved, seen) : match; + }); +} + +function slugHeading(text) { + return text + .toLowerCase() + .trim() + .replace(/<[^>]+>/g, "") + .replace(/[`*_~]/g, "") + .replace(/&[a-z]+;/g, "") + .replace(/[^a-z0-9\s-]/g, "") + .replace(/\s+/g, "-") + .replace(/-+/g, "-"); +} diff --git a/packages/transformers/docs/snippets/0_introduction.snippet b/packages/transformers/docs/snippets/0_introduction.snippet index 34d71bccb..ae082613e 100644 --- a/packages/transformers/docs/snippets/0_introduction.snippet +++ b/packages/transformers/docs/snippets/0_introduction.snippet @@ -11,6 +11,6 @@ Transformers.js is designed to be functionally equivalent to Hugging Face's [tra - 🗣️ **Audio**: automatic speech recognition, audio classification, and text-to-speech. - 🐙 **Multimodal**: embeddings, zero-shot audio classification, zero-shot image classification, and zero-shot object detection. -Transformers.js uses [ONNX Runtime](https://onnxruntime.ai/) to run models in the browser. The best part about it, is that you can easily [convert](#convert-your-models-to-onnx) your pretrained PyTorch, TensorFlow, or JAX models to ONNX using [🤗 Optimum](https://github.com/huggingface/optimum#onnx--onnx-runtime). +Transformers.js uses [ONNX Runtime](https://onnxruntime.ai/) to run models in the browser. The best part about it, is that you can easily [convert](./custom_usage#convert-your-models-to-onnx) your pretrained PyTorch, TensorFlow, or JAX models to ONNX using [🤗 Optimum](https://github.com/huggingface/optimum#onnx--onnx-runtime). For more information, check out the full [documentation](https://huggingface.co/docs/transformers.js). diff --git a/packages/transformers/docs/snippets/5_supported-models.snippet b/packages/transformers/docs/snippets/5_supported-models.snippet index b33eccbac..9f6c53f26 100644 --- a/packages/transformers/docs/snippets/5_supported-models.snippet +++ b/packages/transformers/docs/snippets/5_supported-models.snippet @@ -10,7 +10,7 @@ 1. **[BERT](https://huggingface.co/docs/transformers/model_doc/bert)** (from Google) released with the paper [BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding](https://huggingface.co/papers/1810.04805) by Jacob Devlin, Ming-Wei Chang, Kenton Lee and Kristina Toutanova. 1. **[Blenderbot](https://huggingface.co/docs/transformers/model_doc/blenderbot)** (from Facebook) released with the paper [Recipes for building an open-domain chatbot](https://huggingface.co/papers/2004.13637) by Stephen Roller, Emily Dinan, Naman Goyal, Da Ju, Mary Williamson, Yinhan Liu, Jing Xu, Myle Ott, Kurt Shuster, Eric M. Smith, Y-Lan Boureau, Jason Weston. 1. **[BlenderbotSmall](https://huggingface.co/docs/transformers/model_doc/blenderbot-small)** (from Facebook) released with the paper [Recipes for building an open-domain chatbot](https://huggingface.co/papers/2004.13637) by Stephen Roller, Emily Dinan, Naman Goyal, Da Ju, Mary Williamson, Yinhan Liu, Jing Xu, Myle Ott, Kurt Shuster, Eric M. Smith, Y-Lan Boureau, Jason Weston. -1. **[BLOOM](https://huggingface.co/docs/transformers/model_doc/bloom)** (from BigScience workshop) released by the [BigScience Workshop](https://bigscience.huggingface.co/). +1. **[BLOOM](https://huggingface.co/docs/transformers/model_doc/bloom)** (from BigScience workshop) released by the [BigScience Workshop](https://huggingface.co/bigscience). 1. **[CamemBERT](https://huggingface.co/docs/transformers/model_doc/camembert)** (from Inria/Facebook/Sorbonne) released with the paper [CamemBERT: a Tasty French Language Model](https://huggingface.co/papers/1911.03894) by Louis Martin*, Benjamin Muller*, Pedro Javier Ortiz Suárez*, Yoann Dupont, Laurent Romary, Éric Villemonte de la Clergerie, Djamé Seddah and Benoît Sagot. 1. **[CHMv2](https://huggingface.co/docs/transformers/main/model_doc/chmv2)** (from Meta) released with the paper [CHMv2: Improvements in Global Canopy Height Mapping using DINOv3](https://huggingface.co/papers/2603.06382) by John Brandt, Seungeun Yi, Jamie Tolan, Xinyuan Li, Peter Potapov, Jessica Ertel, Justine Spore, Huy V. Vo, Michaël Ramamonjisoa, Patrick Labatut, Piotr Bojanowski, Camille Couprie. 1. **Chatterbox** (from Resemble AI) released with the repository [Chatterbox TTS](https://github.com/resemble-ai/chatterbox) by the Resemble AI team. @@ -39,7 +39,7 @@ 1. **[DINOv2](https://huggingface.co/docs/transformers/model_doc/dinov2)** (from Meta AI) released with the paper [DINOv2: Learning Robust Visual Features without Supervision](https://huggingface.co/papers/2304.07193) by Maxime Oquab, Timothée Darcet, Théo Moutakanni, Huy Vo, Marc Szafraniec, Vasil Khalidov, Pierre Fernandez, Daniel Haziza, Francisco Massa, Alaaeldin El-Nouby, Mahmoud Assran, Nicolas Ballas, Wojciech Galuba, Russell Howes, Po-Yao Huang, Shang-Wen Li, Ishan Misra, Michael Rabbat, Vasu Sharma, Gabriel Synnaeve, Hu Xu, Hervé Jegou, Julien Mairal, Patrick Labatut, Armand Joulin, Piotr Bojanowski. 1. **[DINOv2 with Registers](https://huggingface.co/docs/transformers/model_doc/dinov2_with_registers)** (from Meta AI) released with the paper [DINOv2 with Registers](https://huggingface.co/papers/2309.16588) by Timothée Darcet, Maxime Oquab, Julien Mairal, Piotr Bojanowski. 1. **[DINOv3](https://huggingface.co/docs/transformers/model_doc/dinov3)** (from Meta AI) released with the paper [DINOv3](https://huggingface.co/papers/2508.10104) by Oriane Siméoni, Huy V. Vo, Maximilian Seitzer, Federico Baldassarre, Maxime Oquab, Cijo Jose, Vasil Khalidov, Marc Szafraniec, Seungeun Yi, Michaël Ramamonjisoa, Francisco Massa, Daniel Haziza, Luca Wehrstedt, Jianyuan Wang, Timothée Darcet, Théo Moutakanni, Leonel Sentana, Claire Roberts, Andrea Vedaldi, Jamie Tolan, John Brandt, Camille Couprie, Julien Mairal, Hervé Jégou, Patrick Labatut, Piotr Bojanowski. -1. **[DistilBERT](https://huggingface.co/docs/transformers/model_doc/distilbert)** (from HuggingFace), released together with the paper [DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter](https://huggingface.co/papers/1910.01108) by Victor Sanh, Lysandre Debut and Thomas Wolf. The same method has been applied to compress GPT2 into [DistilGPT2](https://github.com/huggingface/transformers/tree/main/examples/research_projects/distillation), RoBERTa into [DistilRoBERTa](https://github.com/huggingface/transformers/tree/main/examples/research_projects/distillation), Multilingual BERT into [DistilmBERT](https://github.com/huggingface/transformers/tree/main/examples/research_projects/distillation) and a German version of DistilBERT. +1. **[DistilBERT](https://huggingface.co/docs/transformers/model_doc/distilbert)** (from HuggingFace), released together with the paper [DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter](https://huggingface.co/papers/1910.01108) by Victor Sanh, Lysandre Debut and Thomas Wolf. The same method has been applied to compress GPT2 into DistilGPT2, RoBERTa into DistilRoBERTa, Multilingual BERT into DistilmBERT and a German version of DistilBERT. 1. **[DiT](https://huggingface.co/docs/transformers/model_doc/dit)** (from Microsoft Research) released with the paper [DiT: Self-supervised Pre-training for Document Image Transformer](https://huggingface.co/papers/2203.02378) by Junlong Li, Yiheng Xu, Tengchao Lv, Lei Cui, Cha Zhang, Furu Wei. 1. **[Donut](https://huggingface.co/docs/transformers/model_doc/donut)** (from NAVER), released together with the paper [OCR-free Document Understanding Transformer](https://huggingface.co/papers/2111.15664) by Geewook Kim, Teakgyu Hong, Moonbin Yim, Jeongyeon Nam, Jinyoung Park, Jinyeong Yim, Wonseok Hwang, Sangdoo Yun, Dongyoon Han, Seunghyun Park. 1. **[DPT](https://huggingface.co/docs/transformers/master/model_doc/dpt)** (from Intel Labs) released with the paper [Vision Transformers for Dense Prediction](https://huggingface.co/papers/2103.13413) by René Ranftl, Alexey Bochkovskiy, Vladlen Koltun. @@ -67,7 +67,7 @@ 1. **[GPT Neo](https://huggingface.co/docs/transformers/model_doc/gpt_neo)** (from EleutherAI) released in the repository [EleutherAI/gpt-neo](https://github.com/EleutherAI/gpt-neo) by Sid Black, Stella Biderman, Leo Gao, Phil Wang and Connor Leahy. 1. **[GPT NeoX](https://huggingface.co/docs/transformers/model_doc/gpt_neox)** (from EleutherAI) released with the paper [GPT-NeoX-20B: An Open-Source Autoregressive Language Model](https://huggingface.co/papers/2204.06745) by Sid Black, Stella Biderman, Eric Hallahan, Quentin Anthony, Leo Gao, Laurence Golding, Horace He, Connor Leahy, Kyle McDonell, Jason Phang, Michael Pieler, USVSN Sai Prashanth, Shivanshu Purohit, Laria Reynolds, Jonathan Tow, Ben Wang, Samuel Weinbach 1. **[GPT OSS](https://huggingface.co/docs/transformers/model_doc/gpt_oss)** (from OpenAI) released with the blog [Introducing gpt-oss](https://openai.com/index/introducing-gpt-oss/) by Sandhini Agarwal, Lama Ahmad, Jason Ai, Sam Altman, Andy Applebaum, Edwin Arbus, Rahul K. Arora, Yu Bai, Bowen Baker, Haiming Bao, Boaz Barak, Ally Bennett, Tyler Bertao, Nivedita Brett, Eugene Brevdo, Greg Brockman, Sebastien Bubeck, Che Chang, Kai Chen, Mark Chen, Enoch Cheung, Aidan Clark, Dan Cook, Marat Dukhan, Casey Dvorak, Kevin Fives, Vlad Fomenko, Timur Garipov, Kristian Georgiev, Mia Glaese, Tarun Gogineni, Adam Goucher, Lukas Gross, Katia Gil Guzman, John Hallman, Jackie Hehir, Johannes Heidecke, Alec Helyar, Haitang Hu, Romain Huet, Jacob Huh, Saachi Jain, Zach Johnson, Chris Koch, Irina Kofman, Dominik Kundel, Jason Kwon, Volodymyr Kyrylov, Elaine Ya Le, Guillaume Leclerc, James Park Lennon, Scott Lessans, Mario Lezcano-Casado, Yuanzhi Li, Zhuohan Li, Ji Lin, Jordan Liss, Lily (Xiaoxuan) Liu, Jiancheng Liu, Kevin Lu, Chris Lu, Zoran Martinovic, Lindsay McCallum, Josh McGrath, Scott McKinney, Aidan McLaughlin, Song Mei, Steve Mostovoy, Tong Mu, Gideon Myles, Alexander Neitz, Alex Nichol, Jakub Pachocki, Alex Paino, Dana Palmie, Ashley Pantuliano, Giambattista Parascandolo, Jongsoo Park, Leher Pathak, Carolina Paz, Ludovic Peran, Dmitry Pimenov, Michelle Pokrass, Elizabeth Proehl, Huida Qiu, Gaby Raila, Filippo Raso, Hongyu Ren, Kimmy Richardson, David Robinson, Bob Rotsted, Hadi Salman, Suvansh Sanjeev, Max Schwarzer, D. Sculley, Harshit Sikchi, Kendal Simon, Karan Singhal, Yang Song, Dane Stuckey, Zhiqing Sun, Philippe Tillet, Sam Toizer, Foivos Tsimpourlas, Nikhil Vyas, Eric Wallace, Xin Wang, Miles Wang, Olivia Watkins, Kevin Weil, Amy Wendling, Kevin Whinnery, Cedric Whitney, Hannah Wong, Lin Yang, Yu Yang, Michihiro Yasunaga, Kristen Ying, Wojciech Zaremba, Wenting Zhan, Cyril Zhang, Brian Zhang, Eddie Zhang, Shengjia Zhao. -1. **[GPT-2](https://huggingface.co/docs/transformers/model_doc/gpt2)** (from OpenAI) released with the paper [Language Models are Unsupervised Multitask Learners](https://blog.openai.com/better-language-models/) by Alec Radford*, Jeffrey Wu*, Rewon Child, David Luan, Dario Amodei** and Ilya Sutskever**. +1. **[GPT-2](https://huggingface.co/docs/transformers/model_doc/gpt2)** (from OpenAI) released with the paper [Language Models are Unsupervised Multitask Learners](https://openai.com/index/better-language-models/) by Alec Radford*, Jeffrey Wu*, Rewon Child, David Luan, Dario Amodei** and Ilya Sutskever**. 1. **[GPT-J](https://huggingface.co/docs/transformers/model_doc/gptj)** (from EleutherAI) released in the repository [kingoflolz/mesh-transformer-jax](https://github.com/kingoflolz/mesh-transformer-jax/) by Ben Wang and Aran Komatsuzaki. 1. **[GPTBigCode](https://huggingface.co/docs/transformers/model_doc/gpt_bigcode)** (from BigCode) released with the paper [SantaCoder: don't reach for the stars!](https://huggingface.co/papers/2301.03988) by Loubna Ben Allal, Raymond Li, Denis Kocetkov, Chenghao Mou, Christopher Akiki, Carlos Munoz Ferrandis, Niklas Muennighoff, Mayank Mishra, Alex Gu, Manan Dey, Logesh Kumar Umapathi, Carolyn Jane Anderson, Yangtian Zi, Joel Lamy Poirier, Hailey Schoelkopf, Sergey Troshin, Dmitry Abulkhanov, Manuel Romero, Michael Lappert, Francesco De Toni, Bernardo García del Río, Qian Liu, Shamik Bose, Urvashi Bhattacharyya, Terry Yue Zhuo, Ian Yu, Paulo Villegas, Marco Zocca, Sourab Mangrulkar, David Lansky, Huu Nguyen, Danish Contractor, Luis Villa, Jia Li, Dzmitry Bahdanau, Yacine Jernite, Sean Hughes, Daniel Fried, Arjun Guha, Harm de Vries, Leandro von Werra. 1. **[Granite](https://huggingface.co/docs/transformers/main/model_doc/granite)** (from IBM) released with the paper [Power Scheduler: A Batch Size and Token Number Agnostic Learning Rate Scheduler](https://huggingface.co/papers/2408.13359) by Yikang Shen, Matthew Stallone, Mayank Mishra, Gaoyuan Zhang, Shawn Tan, Aditya Prasad, Adriana Meza Soria, David D. Cox, Rameswar Panda. @@ -181,7 +181,7 @@ 1. **SNAC** (from Papla Media, ETH Zurich) released with the paper [SNAC: Multi-Scale Neural Audio Codec](https://huggingface.co/papers/2410.14411) by Hubert Siuzdak, Florian Grötschla, Luca A. Lanzendörfer. 1. **[SpeechT5](https://huggingface.co/docs/transformers/model_doc/speecht5)** (from Microsoft Research) released with the paper [SpeechT5: Unified-Modal Encoder-Decoder Pre-Training for Spoken Language Processing](https://huggingface.co/papers/2110.07205) by Junyi Ao, Rui Wang, Long Zhou, Chengyi Wang, Shuo Ren, Yu Wu, Shujie Liu, Tom Ko, Qing Li, Yu Zhang, Zhihua Wei, Yao Qian, Jinyu Li, Furu Wei. 1. **[SqueezeBERT](https://huggingface.co/docs/transformers/model_doc/squeezebert)** (from Berkeley) released with the paper [SqueezeBERT: What can computer vision teach NLP about efficient neural networks?](https://huggingface.co/papers/2006.11316) by Forrest N. Iandola, Albert E. Shaw, Ravi Krishna, and Kurt W. Keutzer. -1. **[StableLm](https://huggingface.co/docs/transformers/model_doc/stablelm)** (from Stability AI) released with the paper [StableLM 3B 4E1T (Technical Report)](https://stability.wandb.io/stability-llm/stable-lm/reports/StableLM-3B-4E1T--VmlldzoyMjU4?accessToken=u3zujipenkx5g7rtcj9qojjgxpconyjktjkli2po09nffrffdhhchq045vp0wyfo) by Jonathan Tow, Marco Bellagente, Dakota Mahan, Carlos Riquelme Ruiz, Duy Phung, Maksym Zhuravinskyi, Nathan Cooper, Nikhil Pinnaparaju, Reshinth Adithyan, and James Baicoianu. +1. **[StableLm](https://huggingface.co/docs/transformers/model_doc/stablelm)** (from Stability AI) released with the paper [StableLM 3B 4E1T (Technical Report)](https://github.com/Stability-AI/StableLM#stablelm-3b-4e1t) by Jonathan Tow, Marco Bellagente, Dakota Mahan, Carlos Riquelme Ruiz, Duy Phung, Maksym Zhuravinskyi, Nathan Cooper, Nikhil Pinnaparaju, Reshinth Adithyan, and James Baicoianu. 1. **[Starcoder2](https://huggingface.co/docs/transformers/main/model_doc/starcoder2)** (from BigCode team) released with the paper [StarCoder 2 and The Stack v2: The Next Generation](https://huggingface.co/papers/2402.19173) by Anton Lozhkov, Raymond Li, Loubna Ben Allal, Federico Cassano, Joel Lamy-Poirier, Nouamane Tazi, Ao Tang, Dmytro Pykhtar, Jiawei Liu, Yuxiang Wei, Tianyang Liu, Max Tian, Denis Kocetkov, Arthur Zucker, Younes Belkada, Zijian Wang, Qian Liu, Dmitry Abulkhanov, Indraneil Paul, Zhuang Li, Wen-Ding Li, Megan Risdal, Jia Li, Jian Zhu, Terry Yue Zhuo, Evgenii Zheltonozhskii, Nii Osae Osae Dade, Wenhao Yu, Lucas Krauß, Naman Jain, Yixuan Su, Xuanli He, Manan Dey, Edoardo Abati, Yekun Chai, Niklas Muennighoff, Xiangru Tang, Muhtasham Oblokulov, Christopher Akiki, Marc Marone, Chenghao Mou, Mayank Mishra, Alex Gu, Binyuan Hui, Tri Dao, Armel Zebaze, Olivier Dehaene, Nicolas Patry, Canwen Xu, Julian McAuley, Han Hu, Torsten Scholak, Sebastien Paquet, Jennifer Robinson, Carolyn Jane Anderson, Nicolas Chapados, Mostofa Patwary, Nima Tajbakhsh, Yacine Jernite, Carlos Muñoz Ferrandis, Lingming Zhang, Sean Hughes, Thomas Wolf, Arjun Guha, Leandro von Werra, and Harm de Vries. 1. **StyleTTS 2** (from Columbia University) released with the paper [StyleTTS 2: Towards Human-Level Text-to-Speech through Style Diffusion and Adversarial Training with Large Speech Language Models](https://huggingface.co/papers/2306.07691) by Yinghao Aaron Li, Cong Han, Vinay S. Raghavan, Gavin Mischler, Nima Mesgarani. 1. **Supertonic** (from Supertone) released with the paper [SupertonicTTS: Towards Highly Efficient and Streamlined Text-to-Speech System](https://huggingface.co/papers/2503.23108) by Hyeongju Kim, Jinhyeok Yang, Yechan Yu, Seunghun Ji, Jacob Morton, Frederik Bous, Joon Byun, Juheon Lee. @@ -210,4 +210,4 @@ 1. **[XLM](https://huggingface.co/docs/transformers/model_doc/xlm)** (from Facebook) released together with the paper [Cross-lingual Language Model Pretraining](https://huggingface.co/papers/1901.07291) by Guillaume Lample and Alexis Conneau. 1. **[XLM-RoBERTa](https://huggingface.co/docs/transformers/model_doc/xlm-roberta)** (from Facebook AI), released together with the paper [Unsupervised Cross-lingual Representation Learning at Scale](https://huggingface.co/papers/1911.02116) by Alexis Conneau*, Kartikay Khandelwal*, Naman Goyal, Vishrav Chaudhary, Guillaume Wenzek, Francisco Guzmán, Edouard Grave, Myle Ott, Luke Zettlemoyer and Veselin Stoyanov. 1. **[YOLOS](https://huggingface.co/docs/transformers/model_doc/yolos)** (from Huazhong University of Science & Technology) released with the paper [You Only Look at One Sequence: Rethinking Transformer in Vision through Object Detection](https://huggingface.co/papers/2106.00666) by Yuxin Fang, Bencheng Liao, Xinggang Wang, Jiemin Fang, Jiyang Qi, Rui Wu, Jianwei Niu, Wenyu Liu. -1. **[Youtu-LLM](https://huggingface.co/docs/transformers/model_doc/youtu)** (from the Tencent Youtu Team) released with the paper [Youtu-LLM: Unlocking the Native Agentic Potential for Lightweight Large Language Models](https://huggingface.co/papers/2512.24618) by Junru Lu, Jiarui Qin, Lingfeng Qiao, Yinghui Li, Xinyi Dai, Bo Ke, Jianfeng He, Ruizhi Qiao, Di Yin, Xing Sun, Yunsheng Wu, Yinsong Liu, Shuangyin Liu, Mingkong Tang, Haodong Lin, Jiayi Kuang, Fanxu Meng, Xiaojuan Tang, Yunjia Xi, Junjie Huang, Haotong Yang, Zhenyi Shen, Yangning Li, Qianwen Zhang, Yifei Yu, Siyu An, Junnan Dong, Qiufeng Wang, Jie Wang, Keyu Chen, Wei Wen, Taian Guo, Zhifeng Shen, Daohai Yu, Jiahao Li, Ke Li, Zongyi Li, Xiaoyu Tan. \ No newline at end of file +1. **[Youtu-LLM](https://huggingface.co/docs/transformers/model_doc/youtu)** (from the Tencent Youtu Team) released with the paper [Youtu-LLM: Unlocking the Native Agentic Potential for Lightweight Large Language Models](https://huggingface.co/papers/2512.24618) by Junru Lu, Jiarui Qin, Lingfeng Qiao, Yinghui Li, Xinyi Dai, Bo Ke, Jianfeng He, Ruizhi Qiao, Di Yin, Xing Sun, Yunsheng Wu, Yinsong Liu, Shuangyin Liu, Mingkong Tang, Haodong Lin, Jiayi Kuang, Fanxu Meng, Xiaojuan Tang, Yunjia Xi, Junjie Huang, Haotong Yang, Zhenyi Shen, Yangning Li, Qianwen Zhang, Yifei Yu, Siyu An, Junnan Dong, Qiufeng Wang, Jie Wang, Keyu Chen, Wei Wen, Taian Guo, Zhifeng Shen, Daohai Yu, Jiahao Li, Ke Li, Zongyi Li, Xiaoyu Tan. diff --git a/packages/transformers/docs/source/guides/dtypes.md b/packages/transformers/docs/source/guides/dtypes.md index b3e24727c..087cf1249 100644 --- a/packages/transformers/docs/source/guides/dtypes.md +++ b/packages/transformers/docs/source/guides/dtypes.md @@ -15,7 +15,7 @@ The list of available quantizations depends on the model, but some common ones a ## Basic usage -**Example:** Run Qwen2.5-0.5B-Instruct in 4-bit quantization ([demo](https://v2.scrimba.com/s0dlcpv0ci)) +**Example:** Run Qwen2.5-0.5B-Instruct in 4-bit quantization ```js import { pipeline } from "@huggingface/transformers"; @@ -67,7 +67,7 @@ const generator = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-O Some encoder-decoder models, like Whisper or Florence-2, are extremely sensitive to quantization settings: especially of the encoder. For this reason, we added the ability to select per-module dtypes, which can be done by providing a mapping from module name to dtype. -**Example:** Run Florence-2 on WebGPU ([demo](https://v2.scrimba.com/s0pdm485fo)) +**Example:** Run Florence-2 on WebGPU ```js import { Florence2ForConditionalGeneration } from "@huggingface/transformers"; diff --git a/packages/transformers/docs/source/guides/node-audio-processing.md b/packages/transformers/docs/source/guides/node-audio-processing.md index 2f82d78f6..a983b0b16 100644 --- a/packages/transformers/docs/source/guides/node-audio-processing.md +++ b/packages/transformers/docs/source/guides/node-audio-processing.md @@ -12,7 +12,7 @@ This tutorial will be written as an ES module, but you can easily adapt it to us **Useful links:** -- [Source code](https://github.com/huggingface/transformers.js/tree/main/examples/node-audio-processing) +- [Source code](https://github.com/huggingface/transformers.js-examples/tree/main/node-audio-processing) - [Documentation](https://huggingface.co/docs/transformers.js) ## Prerequisites diff --git a/packages/transformers/docs/source/guides/webgpu.md b/packages/transformers/docs/source/guides/webgpu.md index 3d6c2adb0..be6a3c637 100644 --- a/packages/transformers/docs/source/guides/webgpu.md +++ b/packages/transformers/docs/source/guides/webgpu.md @@ -3,19 +3,19 @@ WebGPU is a new web standard for accelerated graphics and compute. The [API](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) enables web developers to use the underlying system's GPU to carry out high-performance computations directly in the browser. WebGPU is the successor to [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) and provides significantly better performance, because it allows for more direct interaction with modern GPUs. Lastly, it supports general-purpose GPU computations, which makes it just perfect for machine learning! > [!WARNING] -> As of October 2024, global WebGPU support is around 70% (according to [caniuse.com](https://caniuse.com/webgpu)), meaning some users may not be able to use the API. +> As of March 2026, global WebGPU support is around 85% (according to [caniuse.com](https://caniuse.com/webgpu)), meaning some users may not be able to use the API. > > If the following demos do not work in your browser, you may need to enable it using a feature flag: > > - Firefox: with the `dom.webgpu.enabled` flag (see [here](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Experimental_features#:~:text=tested%20by%20Firefox.-,WebGPU%20API,-The%20WebGPU%20API)). -> - Safari: with the `WebGPU` feature flag (see [here](https://webkit.org/blog/14879/webgpu-now-available-for-testing-in-safari-technology-preview/)). +> - Safari: support is version-dependent, with partial support in recent desktop Safari and support in recent iOS Safari. > - Older Chromium browsers (on Windows, macOS, Linux): with the `enable-unsafe-webgpu` flag (see [here](https://developer.chrome.com/docs/web-platform/webgpu/troubleshooting-tips)). -## Usage in Transformers.js v3 +## Usage Thanks to our collaboration with [ONNX Runtime Web](https://www.npmjs.com/package/onnxruntime-web), enabling WebGPU acceleration is as simple as setting `device: 'webgpu'` when loading a model. Let's see some examples! -**Example:** Compute text embeddings on WebGPU ([demo](https://v2.scrimba.com/s06a2smeej)) +**Example:** Compute text embeddings on WebGPU ```js import { pipeline } from "@huggingface/transformers"; @@ -37,7 +37,7 @@ console.log(embeddings.tolist()); // ] ``` -**Example:** Perform automatic speech recognition with OpenAI whisper on WebGPU ([demo](https://v2.scrimba.com/s0oi76h82g)) +**Example:** Perform automatic speech recognition with OpenAI whisper on WebGPU ```js import { pipeline } from "@huggingface/transformers"; @@ -57,7 +57,7 @@ console.log(output); // { text: ' And so my fellow Americans ask not what your country can do for you, ask what you can do for your country.' } ``` -**Example:** Perform image classification with MobileNetV4 on WebGPU ([demo](https://v2.scrimba.com/s0fv2uab1t)) +**Example:** Perform image classification with MobileNetV4 on WebGPU ```js import { pipeline } from "@huggingface/transformers"; diff --git a/packages/transformers/docs/source/tutorials/next.md b/packages/transformers/docs/source/tutorials/next.md index ff0dbf9bc..80d74c19a 100644 --- a/packages/transformers/docs/source/tutorials/next.md +++ b/packages/transformers/docs/source/tutorials/next.md @@ -9,7 +9,7 @@ The final product will look something like this: Useful links: - Demo site: [client-side](https://huggingface.co/spaces/Xenova/next-example-app) or [server-side](https://huggingface.co/spaces/Xenova/next-server-example-app) -- Source code: [client-side](https://github.com/huggingface/transformers.js/tree/main/examples/next-client) or [server-side](https://github.com/huggingface/transformers.js/tree/main/examples/next-server) +- Source code: [client-side](https://github.com/huggingface/transformers.js-examples/tree/main/next-client) or [server-side](https://github.com/huggingface/transformers.js-examples/tree/main/next-server) ## Prerequisites @@ -412,7 +412,7 @@ Visit the URL shown in the terminal (e.g., [http://localhost:3000/](http://local For this demo, we will build and deploy our application to [Hugging Face Spaces](https://huggingface.co/docs/hub/spaces). If you haven't already, you can create a free Hugging Face account [here](https://huggingface.co/join). -1. Create a new `Dockerfile` in your project's root folder. You can use our [example Dockerfile](https://github.com/huggingface/transformers.js/blob/main/examples/next-server/Dockerfile) as a template. +1. Create a new `Dockerfile` in your project's root folder. You can use our [example Dockerfile](https://github.com/huggingface/transformers.js-examples/blob/main/next-server/Dockerfile) as a template. 2. Visit [https://huggingface.co/new-space](https://huggingface.co/new-space) and fill in the form. Remember to select "Docker" as the space type (you can choose the "Blank" Docker template). 3. Click the "Create space" button at the bottom of the page. 4. Go to "Files" → "Add file" → "Upload files". Drag the files from your project folder (excluding `node_modules` and `.next`, if present) into the upload box and click "Upload". After they have uploaded, scroll down to the button and click "Commit changes to main". diff --git a/packages/transformers/docs/source/tutorials/node.md b/packages/transformers/docs/source/tutorials/node.md index dec6f3fd6..a187ad57d 100644 --- a/packages/transformers/docs/source/tutorials/node.md +++ b/packages/transformers/docs/source/tutorials/node.md @@ -19,7 +19,7 @@ Although you can always use the [Python library](https://github.com/huggingface/ **Useful links:** -- Source code ([ESM](https://github.com/huggingface/transformers.js/tree/main/examples/node/esm/app.js) or [CommonJS](https://github.com/huggingface/transformers.js/tree/main/examples/node/commonjs/app.js)) +- Source code ([ESM](https://github.com/huggingface/transformers.js-examples/tree/main/node-esm) or [CommonJS](https://github.com/huggingface/transformers.js-examples/tree/main/node-cjs)) - [Documentation](https://huggingface.co/docs/transformers.js) ## Prerequisites From c7fee9d76120aedc4b98e83e3803eed421f997e3 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:17:02 -0400 Subject: [PATCH 049/103] formatting --- packages/transformers/docs/scripts/lib/ir.mjs | 15 +++++++++++++-- .../transformers/docs/scripts/lib/type-refs.mjs | 10 ++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index a71c476e5..225b5a0d6 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -333,12 +333,23 @@ function parseFunctionType(raw) { if (!text.startsWith("(")) return null; const paramsEnd = matchingBracket(text, 0, "(", ")"); - if (paramsEnd === -1 || text.slice(paramsEnd + 1).trimStart().slice(0, 2) !== "=>") return null; + if ( + paramsEnd === -1 || + text + .slice(paramsEnd + 1) + .trimStart() + .slice(0, 2) !== "=>" + ) + return null; const params = splitTopLevel(text.slice(1, paramsEnd), ",") .map((part) => parseFunctionTypeParam(part.trim())) .filter(Boolean); - const returnType = text.slice(paramsEnd + 1).trimStart().slice(2).trim(); + const returnType = text + .slice(paramsEnd + 1) + .trimStart() + .slice(2) + .trim(); return { templates, diff --git a/packages/transformers/docs/scripts/lib/type-refs.mjs b/packages/transformers/docs/scripts/lib/type-refs.mjs index a76ff0dc1..46d8390f2 100644 --- a/packages/transformers/docs/scripts/lib/type-refs.mjs +++ b/packages/transformers/docs/scripts/lib/type-refs.mjs @@ -8,14 +8,8 @@ export const UTILITY_TYPES = Object.freeze({ THIS_TYPE: "ThisType", }); -const RENDERABLE_UTILITY_NAMES = [ - UTILITY_TYPES.PARAMETERS, - UTILITY_TYPES.RETURN_TYPE, - UTILITY_TYPES.CONSTRUCTOR_PARAMETERS, -]; -const RENDERABLE_UTILITY_PATTERN = new RegExp( - `^(${RENDERABLE_UTILITY_NAMES.join("|")})<(.+)>(?:\\[(\\d+)\\])?$`, -); +const RENDERABLE_UTILITY_NAMES = [UTILITY_TYPES.PARAMETERS, UTILITY_TYPES.RETURN_TYPE, UTILITY_TYPES.CONSTRUCTOR_PARAMETERS]; +const RENDERABLE_UTILITY_PATTERN = new RegExp(`^(${RENDERABLE_UTILITY_NAMES.join("|")})<(.+)>(?:\\[(\\d+)\\])?$`); export const TS_UTILITY_NAMES = new Set(Object.values(UTILITY_TYPES)); From 4b70566f3ddb3a562f9e8ba86c13c24f8d25912e Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:26:53 -0400 Subject: [PATCH 050/103] cleanup --- .../transformers/docs/scripts/build_readme.js | 5 +++ .../docs/scripts/lib/render-api.mjs | 1 + .../docs/snippets/3_custom-usage.snippet | 4 +-- .../transformers/docs/source/guides/dtypes.md | 16 ++++++--- .../transformers/docs/source/guides/webgpu.md | 2 +- .../docs/source/integrations/vercel-ai-sdk.md | 33 +++++++++++-------- .../docs/source/tutorials/vanilla-js.md | 2 +- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index 4b94e667b..867e091b4 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -36,6 +36,7 @@ function main() { const apiLinks = buildApiSymbolLinks(ir, publicNames); const snippets = Object.fromEntries(Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(path.join(packageRoot, file), "utf8")])); + snippets.customUsage = demoteHeadings(snippets.customUsage); const readme = fixLinks(renderTemplate(snippets), apiLinks); fs.writeFileSync(path.resolve(packageRoot, out), readme, "utf8"); } @@ -98,6 +99,10 @@ ${models} `; } +function demoteHeadings(markdown) { + return markdown.replace(/^#{1,5}(?=\s)/gm, "#$&"); +} + function fixLinks(markdown, apiLinks) { // This is not a complete Markdown parser, just the narrow link rewrite // needed by the README snippets. diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 3b725b1c9..9dab6684f 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -49,6 +49,7 @@ export function renderModule(mod, ir, opts = {}) { return ( expandInlineLinks(out.join("\n"), ctx) + .replace(/[ \t]+$/gm, "") .replace(/\n{3,}/g, "\n\n") .trimEnd() + "\n" ); diff --git a/packages/transformers/docs/snippets/3_custom-usage.snippet b/packages/transformers/docs/snippets/3_custom-usage.snippet index ca48358e0..d5ca268ae 100644 --- a/packages/transformers/docs/snippets/3_custom-usage.snippet +++ b/packages/transformers/docs/snippets/3_custom-usage.snippet @@ -2,7 +2,7 @@ By default, Transformers.js uses [hosted pretrained models](https://huggingface.co/models?library=transformers.js) and [precompiled WASM binaries](https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0/dist/), which should work out-of-the-box. You can customize this as follows: -### Settings +## Settings ```javascript import { env } from '@huggingface/transformers'; @@ -19,6 +19,6 @@ env.backends.onnx.wasm.wasmPaths = '/path/to/files/'; For a full list of available settings, check out the [API Reference](./api/env). -### Convert your models to ONNX +## Convert your models to ONNX We recommend using [Optimum](https://github.com/huggingface/optimum-onnx) to convert your PyTorch models to ONNX in a single command. For the full list of supported architectures, check out the [Optimum documentation](https://huggingface.co/docs/optimum-onnx/onnx/overview). diff --git a/packages/transformers/docs/source/guides/dtypes.md b/packages/transformers/docs/source/guides/dtypes.md index 087cf1249..79fab3ec6 100644 --- a/packages/transformers/docs/source/guides/dtypes.md +++ b/packages/transformers/docs/source/guides/dtypes.md @@ -5,7 +5,7 @@ Before Transformers.js v3, we used the `quantized` option to specify whether to The list of available quantizations depends on the model, but some common ones are: full-precision (`"fp32"`), half-precision (`"fp16"`), 8-bit (`"q8"`, `"int8"`, `"uint8"`), and 4-bit (`"q4"`, `"bnb4"`, `"q4f16"`). <p align="center"> - <picture> + <picture> <source media="(prefers-color-scheme: dark)" srcset="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/transformersjs-v3/dtypes-dark.jpg" style="max-width: 100%;"> <source media="(prefers-color-scheme: light)" srcset="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/transformersjs-v3/dtypes-light.jpg" style="max-width: 100%;"> <img alt="Available dtypes for mixedbread-ai/mxbai-embed-xsmall-v1" src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/transformersjs-v3/dtypes-dark.jpg" style="max-width: 100%;"> @@ -45,7 +45,9 @@ Not sure which quantizations a model offers? Use `ModelRegistry.get_available_dt ```js import { ModelRegistry } from "@huggingface/transformers"; -const dtypes = await ModelRegistry.get_available_dtypes("onnx-community/all-MiniLM-L6-v2-ONNX"); +const dtypes = await ModelRegistry.get_available_dtypes( + "onnx-community/all-MiniLM-L6-v2-ONNX", +); console.log(dtypes); // e.g., [ 'fp32', 'fp16', 'int8', 'uint8', 'q8', 'q4' ] ``` @@ -54,13 +56,19 @@ This checks which ONNX files exist on the Hugging Face Hub for each dtype. For m You can use this to build UIs that let users pick a quantization level, or to automatically select the smallest available dtype: ```js -const dtypes = await ModelRegistry.get_available_dtypes("onnx-community/Qwen3-0.6B-ONNX"); +const dtypes = await ModelRegistry.get_available_dtypes( + "onnx-community/Qwen3-0.6B-ONNX", +); // Pick the smallest available quantization, falling back to fp32 const preferred = ["q4", "q8", "fp16", "fp32"]; const dtype = preferred.find((d) => dtypes.includes(d)) ?? "fp32"; -const generator = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX", { dtype }); +const generator = await pipeline( + "text-generation", + "onnx-community/Qwen3-0.6B-ONNX", + { dtype }, +); ``` ## Per-module dtypes diff --git a/packages/transformers/docs/source/guides/webgpu.md b/packages/transformers/docs/source/guides/webgpu.md index be6a3c637..964049268 100644 --- a/packages/transformers/docs/source/guides/webgpu.md +++ b/packages/transformers/docs/source/guides/webgpu.md @@ -2,7 +2,7 @@ WebGPU is a new web standard for accelerated graphics and compute. The [API](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) enables web developers to use the underlying system's GPU to carry out high-performance computations directly in the browser. WebGPU is the successor to [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) and provides significantly better performance, because it allows for more direct interaction with modern GPUs. Lastly, it supports general-purpose GPU computations, which makes it just perfect for machine learning! -> [!WARNING] +> [!WARNING] > As of March 2026, global WebGPU support is around 85% (according to [caniuse.com](https://caniuse.com/webgpu)), meaning some users may not be able to use the API. > > If the following demos do not work in your browser, you may need to enable it using a feature flag: diff --git a/packages/transformers/docs/source/integrations/vercel-ai-sdk.md b/packages/transformers/docs/source/integrations/vercel-ai-sdk.md index 9f5d54154..02dfed955 100644 --- a/packages/transformers/docs/source/integrations/vercel-ai-sdk.md +++ b/packages/transformers/docs/source/integrations/vercel-ai-sdk.md @@ -6,7 +6,7 @@ This guide covers the core concepts and API patterns. For a full step-by-step pr ## Why use the Vercel AI SDK with Transformers.js? -The `@browser-ai/transformers-js` provider builds on top of `@huggingface/transformers` to give you a standard AI SDK interface — handling Web Worker setup, message passing, progress tracking, streaming, interrupt handling, and state management, so you can use the same `streamText`, `generateText`, and `useChat` APIs you'd use with any other AI SDK provider. +The `@browser-ai/transformers-js` provider builds on top of `@huggingface/transformers` to give you a standard AI SDK interface — handling Web Worker setup, message passing, progress tracking, streaming, interrupt handling, and state management, so you can use the same `streamText`, `generateText`, and `useChat` APIs you'd use with any other AI SDK provider. Read more about this [here](https://www.browser-ai.dev/docs/ai-sdk-v6/transformers-js/why). ## Installation @@ -15,10 +15,10 @@ Read more about this [here](https://www.browser-ai.dev/docs/ai-sdk-v6/transforme npm install @browser-ai/transformers-js @huggingface/transformers ai @ai-sdk/react ``` -| @browser-ai/transformers-js | AI SDK | Notes | -|---|---|---| -| v2.0.0+ | v6.x | Current stable | -| v1.0.0 | v5.x | Legacy | +| @browser-ai/transformers-js | AI SDK | Notes | +| --------------------------- | ------ | -------------- | +| v2.0.0+ | v6.x | Current stable | +| v1.0.0 | v5.x | Legacy | ## Text generation @@ -208,17 +208,18 @@ When using the `useChat` hook, you create a [custom transport](https://ai-sdk.de ```typescript import { - ChatTransport, UIMessageChunk, streamText, - convertToModelMessages, ChatRequestOptions, + ChatTransport, + UIMessageChunk, + streamText, + convertToModelMessages, + ChatRequestOptions, } from "ai"; import { TransformersJSLanguageModel, TransformersUIMessage, } from "@browser-ai/transformers-js"; -export class TransformersChatTransport - implements ChatTransport<TransformersUIMessage> -{ +export class TransformersChatTransport implements ChatTransport<TransformersUIMessage> { constructor(private readonly model: TransformersJSLanguageModel) {} async sendMessages( @@ -250,11 +251,16 @@ Then use it in your component: ```typescript import { useChat } from "@ai-sdk/react"; -import { transformersJS, TransformersUIMessage } from "@browser-ai/transformers-js"; +import { + transformersJS, + TransformersUIMessage, +} from "@browser-ai/transformers-js"; const model = transformersJS("HuggingFaceTB/SmolLM2-360M-Instruct", { device: "webgpu", - worker: new Worker(new URL("./worker.ts", import.meta.url), { type: "module" }), + worker: new Worker(new URL("./worker.ts", import.meta.url), { + type: "module", + }), }); const { sendMessage, messages, stop } = useChat<TransformersUIMessage>({ @@ -268,7 +274,8 @@ If the device doesn't support in-browser inference, you can fall back to a serve ```typescript import { - transformersJS, TransformersUIMessage, + transformersJS, + TransformersUIMessage, doesBrowserSupportTransformersJS, } from "@browser-ai/transformers-js"; diff --git a/packages/transformers/docs/source/tutorials/vanilla-js.md b/packages/transformers/docs/source/tutorials/vanilla-js.md index fa14a5710..3d96acebf 100644 --- a/packages/transformers/docs/source/tutorials/vanilla-js.md +++ b/packages/transformers/docs/source/tutorials/vanilla-js.md @@ -210,7 +210,7 @@ async function detect(img) { <Tip> -NOTE: The `detect` function needs to be asynchronous, since we’ll `await` the result of the the model. +NOTE: The `detect` function needs to be asynchronous, since we’ll `await` the result of the model. </Tip> From 5cb33e3ce411802133285955df23117fb34795e3 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:35:17 -0400 Subject: [PATCH 051/103] Update AGENTS.md --- .ai/AGENTS.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 9aac02cb7..460d2ad44 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -29,18 +29,28 @@ Before opening a pull request: pnpm install pnpm --filter @huggingface/transformers test pnpm --filter @huggingface/transformers typegen -pnpm --filter @huggingface/transformers docs-api +pnpm --filter @huggingface/transformers docs-generate ``` ## Documentation generation -The `docs/source/api/` markdown is auto-generated from JSDoc comments in `src/**/*.js` -by [`docs/scripts/generate.js`](../packages/transformers/docs/scripts/generate.js). -Regenerate after any JSDoc change: +Run the full documentation generator after any JSDoc, docs snippet, task metadata, +or generated skill content change: ```bash -pnpm --filter @huggingface/transformers docs-api +pnpm --filter @huggingface/transformers docs-generate ``` -The `.ai/skills/transformers-js/references/TASK_EXAMPLES.md` and `API_SUMMARY.md` files -are also auto-generated (see `docs-skill`). Do not edit them by hand. +`docs-generate` runs [`docs/scripts/generate-all.js`](../packages/transformers/docs/scripts/generate-all.js), +which generates: + +- `packages/transformers/docs/source/api/**/*.md` from JSDoc comments in + `packages/transformers/src/**/*.js`. +- `.ai/skills/transformers-js/references/TASK_EXAMPLES.md` and + `.ai/skills/transformers-js/references/API_SUMMARY.md`. + +Do not edit generated API pages or generated skill reference files by hand. Update +the source JSDoc, docs snippets, or generator modules instead. + +The generator also validates generated API pages against `docs/source/_toctree.yml` +and checks local Markdown links and anchors under `docs/source/`. From c610f769d73863068a6027161e15ccaaa26f46ab Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:43:24 -0400 Subject: [PATCH 052/103] consistency checks --- .ai/AGENTS.md | 4 +-- .github/ISSUE_TEMPLATE/1_bug-report.yml | 2 +- CONTRIBUTING.md | 25 ++++++++++--------- README.md | 2 +- .../transformers/docs/scripts/build_readme.js | 2 +- packages/transformers/tests/types/_base.ts | 4 +-- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 460d2ad44..6bf789c88 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -46,8 +46,8 @@ which generates: - `packages/transformers/docs/source/api/**/*.md` from JSDoc comments in `packages/transformers/src/**/*.js`. -- `.ai/skills/transformers-js/references/TASK_EXAMPLES.md` and - `.ai/skills/transformers-js/references/API_SUMMARY.md`. +- Generated sections in `.ai/skills/transformers-js/SKILL.md`. +- `.ai/skills/transformers-js/references/TASKS.md`. Do not edit generated API pages or generated skill reference files by hand. Update the source JSDoc, docs snippets, or generator modules instead. diff --git a/.github/ISSUE_TEMPLATE/1_bug-report.yml b/.github/ISSUE_TEMPLATE/1_bug-report.yml index f30a68ca9..b093bf2e2 100644 --- a/.github/ISSUE_TEMPLATE/1_bug-report.yml +++ b/.github/ISSUE_TEMPLATE/1_bug-report.yml @@ -43,7 +43,7 @@ body: placeholder: | Steps to reproduce the behavior: - + 1. 2. 3. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c98dc60eb..f7a7d45d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -267,17 +267,18 @@ Follow the steps below to start contributing: the pull request. ### Pull request checklist -☐ The pull request title should summarize your contribution. -☐ If your pull request addresses an issue, please mention the issue number in the pull -request description to make sure they are linked (and people viewing the issue know you -are working on it). -☐ To indicate a work in progress please prefix the title with `[WIP]`. These are -useful to avoid duplicated work, and to differentiate it from PRs ready to be merged. -☐ Make sure existing tests pass (`pnpm test`). -☐ Make sure the build completes successfully (`pnpm build`). -☐ Make sure your code is [formatted properly with Prettier](#code-formatting) (`pnpm format:check`). -☐ If adding a new feature, also add tests for it. -☐ If your changes affect user-facing functionality, update the relevant documentation. + +- ☐ The pull request title should summarize your contribution. +- ☐ If your pull request addresses an issue, please mention the issue number in the pull + request description to make sure they are linked (and people viewing the issue know you + are working on it). +- ☐ To indicate a work in progress please prefix the title with `[WIP]`. These are + useful to avoid duplicated work, and to differentiate it from PRs ready to be merged. +- ☐ Make sure existing tests pass (`pnpm test`). +- ☐ Make sure the build completes successfully (`pnpm build`). +- ☐ Make sure your code is [formatted properly with Prettier](#code-formatting) (`pnpm format:check`). +- ☐ If adding a new feature, also add tests for it. +- ☐ If your changes affect user-facing functionality, update the relevant documentation. ### Tests We are using [Jest](https://jestjs.io/) to execute unit-tests. All tests can be found in `packages/transformers/tests` and have to end with `.test.js` @@ -364,4 +365,4 @@ The recommended way to develop and test changes is to use the watch mode build a 4. Test your changes in your test project. The changes will be automatically reflected since the package is linked via the `file:` protocol. -This workflow allows for rapid iteration and testing during development. \ No newline at end of file +This workflow allows for rapid iteration and testing during development. diff --git a/README.md b/README.md index c5c090c1e..57e4cb0cc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ <p align="center"> <br/> - <picture> + <picture> <source media="(prefers-color-scheme: dark)" srcset="https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/transformersjs-dark.svg" width="500" style="max-width: 100%;"> <source media="(prefers-color-scheme: light)" srcset="https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/transformersjs-light.svg" width="500" style="max-width: 100%;"> <img alt="transformers.js javascript library logo" src="https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/transformersjs-light.svg" width="500" style="max-width: 100%;"> diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index 867e091b4..29c9ec63c 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -57,7 +57,7 @@ function renderTemplate({ intro, installation, quickTour, customUsage, tasks, mo <p align="center"> <br/> - <picture> + <picture> <source media="(prefers-color-scheme: dark)" srcset="https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/transformersjs-dark.svg" width="500" style="max-width: 100%;"> <source media="(prefers-color-scheme: light)" srcset="https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/transformersjs-light.svg" width="500" style="max-width: 100%;"> <img alt="transformers.js javascript library logo" src="https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/transformersjs-light.svg" width="500" style="max-width: 100%;"> diff --git a/packages/transformers/tests/types/_base.ts b/packages/transformers/tests/types/_base.ts index 06744e42a..27ba126f2 100644 --- a/packages/transformers/tests/types/_base.ts +++ b/packages/transformers/tests/types/_base.ts @@ -1,6 +1,6 @@ // Checks if type X and Y are exactly the same -type Equal<X, Y> = - (<T>() => T extends X ? 1 : 2) extends +type Equal<X, Y> = + (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false; // Throws a type error if T is not `true` From 6a682fcd2f2e306e62b099b62d3a34013d2fa791 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:54:52 -0400 Subject: [PATCH 053/103] fix commands --- .github/workflows/documentation.yml | 2 +- .github/workflows/pr-documentation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index fc376b0dc..1e269a087 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -13,7 +13,7 @@ jobs: commit_sha: ${{ github.sha }} package: transformers.js path_to_docs: transformers.js/packages/transformers/docs/source - pre_command: cd transformers.js && corepack enable && ONNXRUNTIME_NODE_INSTALL=skip pnpm install --frozen-lockfile && pnpm --filter @huggingface/transformers docs-api + pre_command: cd transformers.js && corepack enable && ONNXRUNTIME_NODE_INSTALL=skip pnpm install --frozen-lockfile && pnpm --filter @huggingface/transformers docs-generate additional_args: --not_python_module secrets: hf_token: ${{ secrets.HF_DOC_BUILD_PUSH }} diff --git a/.github/workflows/pr-documentation.yml b/.github/workflows/pr-documentation.yml index af5dd3832..f3db7300c 100644 --- a/.github/workflows/pr-documentation.yml +++ b/.github/workflows/pr-documentation.yml @@ -15,5 +15,5 @@ jobs: pr_number: ${{ github.event.number }} package: transformers.js path_to_docs: transformers.js/packages/transformers/docs/source - pre_command: cd transformers.js && corepack enable && ONNXRUNTIME_NODE_INSTALL=skip pnpm install --frozen-lockfile && pnpm --filter @huggingface/transformers docs-api + pre_command: cd transformers.js && corepack enable && ONNXRUNTIME_NODE_INSTALL=skip pnpm install --frozen-lockfile && pnpm --filter @huggingface/transformers docs-generate additional_args: --not_python_module From 8b908951d4dddadc79b13ec3b55b80f30770b5e0 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sat, 25 Apr 2026 15:21:39 -0400 Subject: [PATCH 054/103] fix escaping --- packages/transformers/docs/scripts/lib/render-api.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 9dab6684f..cf8497167 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -534,6 +534,7 @@ export function prettifyTypeString(raw) { s = stripLeading(s, "<", ">"); // <T extends X>(...) s = s.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); + s = s.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, "'$1'"); if (isRenderableUtilityType(s)) { return s.replace(/\s+/g, " ").trim(); From 56e8d45d93dc24aefe5bab878d64a760d7544f2b Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 6 May 2026 16:30:30 -0400 Subject: [PATCH 055/103] update logLevel default in docs --- .ai/skills/transformers-js/references/CONFIGURATION.md | 2 +- packages/transformers/src/env.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index 2734aff3c..b90ba771d 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -33,7 +33,7 @@ env.cacheDir = '/path/to/cache/directory/'; |--------|------|-------------| | `version` | `string` | This version of Transformers.js. | | `backends` | `object` | Expose environment variables of different backends, allowing users to set these variables if they want to. | -| `logLevel` | `number` | The logging level. Use LogLevel enum values. Defaults to LogLevel.ERROR. | +| `logLevel` | `number` | The logging level. Use LogLevel enum values. Defaults to LogLevel.WARNING. | | `allowRemoteModels` | `boolean` | Whether to allow loading of remote files, defaults to `true`. If set to `false`, it will have the same effect as setting `local_files_only=true` when loading pipelines, models, tokenizers, processors, etc. | | `remoteHost` | `string` | Host URL to load models from. Defaults to the Hugging Face Hub. | | `remotePathTemplate` | `string` | Path template to fill in and append to `remoteHost` when loading models. | diff --git a/packages/transformers/src/env.js b/packages/transformers/src/env.js index 77c9b7c31..5de38d6e7 100644 --- a/packages/transformers/src/env.js +++ b/packages/transformers/src/env.js @@ -202,7 +202,7 @@ export const LogLevel = Object.freeze({ * @property {string} version This version of Transformers.js. * @property {{onnx: Partial<import('onnxruntime-common').Env> & { setLogLevel?: (logLevel: number) => void }}} backends Expose environment variables of different backends, * allowing users to set these variables if they want to. - * @property {number} logLevel The logging level. Use LogLevel enum values. Defaults to LogLevel.ERROR. + * @property {number} logLevel The logging level. Use LogLevel enum values. Defaults to LogLevel.WARNING. * @property {boolean} allowRemoteModels Whether to allow loading of remote files, defaults to `true`. * If set to `false`, it will have the same effect as setting `local_files_only=true` when loading pipelines, models, tokenizers, processors, etc. * @property {string} remoteHost Host URL to load models from. Defaults to the Hugging Face Hub. From 5ec598c29872197b98ea3b1a3af76ec2046cf453 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 6 May 2026 16:47:19 -0400 Subject: [PATCH 056/103] typos --- packages/transformers/src/generation/configuration_utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/transformers/src/generation/configuration_utils.js b/packages/transformers/src/generation/configuration_utils.js index d47e906c6..2d68b45db 100644 --- a/packages/transformers/src/generation/configuration_utils.js +++ b/packages/transformers/src/generation/configuration_utils.js @@ -23,7 +23,7 @@ export class GenerationConfig { max_length = 20; /** - * The maximum numbers of tokens to generate, ignoring the number of tokens in the prompt. + * The maximum number of tokens to generate, ignoring the number of tokens in the prompt. * @type {number} * @default null */ @@ -39,7 +39,7 @@ export class GenerationConfig { min_length = 0; /** - * The minimum numbers of tokens to generate, ignoring the number of tokens in the prompt. + * The minimum number of tokens to generate, ignoring the number of tokens in the prompt. * @type {number} * @default null */ @@ -167,7 +167,7 @@ export class GenerationConfig { repetition_penalty = 1.0; /** - * The paramater for encoder_repetition_penalty. + * The parameter for encoder_repetition_penalty. * An exponential penalty on sequences that are not in the original input. * 1.0 means no penalty. * @type {number} From 23e347472d79bf032b284afb16afb783e3d70b89 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 6 May 2026 16:48:33 -0400 Subject: [PATCH 057/103] newlines in tables --- .../transformers-js/references/PIPELINE_OPTIONS.md | 10 +++++----- .../transformers/docs/scripts/lib/render-skill.mjs | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 4cf72812b..ceb4cc9dd 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -10,7 +10,7 @@ class in the [API reference](https://huggingface.co/docs/transformers.js/api/pip | Option | Type | Description | |--------|------|-------------| | `progress_callback`? | `ProgressCallback` | If specified, this function will be called during model construction, to provide the user with progress updates. _(default: `null`)_ | -| `config`? | `PretrainedConfig` | Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when: - The model is a model provided by the library (loaded with the *model id* string of a pretrained model). - The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | +| `config`? | `PretrainedConfig` | Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when:<br />- The model is a model provided by the library (loaded with the *model id* string of a pretrained model).<br />- The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | | `cache_dir`? | `string` | Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. _(default: `null`)_ | | `local_files_only`? | `boolean` | Whether or not to only look at local files (e.g., not try downloading the model). _(default: `false`)_ | | `revision`? | `string` | The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git. NOTE: This setting is ignored for local requests. _(default: `'main'`)_ | @@ -146,10 +146,10 @@ anywhere `generate()` is invoked directly. | Option | Type | Description | |--------|------|-------------| | `max_length`? | `number` | The maximum length the generated tokens can have. Corresponds to the length of the input prompt + `max_new_tokens`. Its effect is overridden by `max_new_tokens`, if also set. _(default: `20`)_ | -| `max_new_tokens`? | `number` | The maximum numbers of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | +| `max_new_tokens`? | `number` | The maximum number of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | | `min_length`? | `number` | The minimum length of the sequence to be generated. Corresponds to the length of the input prompt + `min_new_tokens`. Its effect is overridden by `min_new_tokens`, if also set. _(default: `0`)_ | -| `min_new_tokens`? | `number` | The minimum numbers of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | -| `early_stopping`? | `boolean\|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values: - `true`, where the generation stops as soon as there are `num_beams` complete candidates; - `false`, where an heuristic is applied and the generation stops when is it very unlikely to find better candidates; - `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | +| `min_new_tokens`? | `number` | The minimum number of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | +| `early_stopping`? | `boolean\|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values:<br />- `true`, where the generation stops as soon as there are `num_beams` complete candidates;<br />- `false`, where an heuristic is applied and the generation stops when is it very unlikely to find better candidates;<br />- `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | | `max_time`? | `number` | The maximum amount of time you allow the computation to run for in seconds. Generation will still finish the current pass after allocated time has been passed. _(default: `null`)_ | | `do_sample`? | `boolean` | Whether or not to use sampling; use greedy decoding otherwise. _(default: `false`)_ | | `num_beams`? | `number` | Number of beams for beam search. 1 means no beam search. _(default: `1`)_ | @@ -164,7 +164,7 @@ anywhere `generate()` is invoked directly. | `eta_cutoff`? | `number` | Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to float strictly between 0 and 1, a token is only considered if it is greater than either `eta_cutoff` or `sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits)))`. The latter term is intuitively the expected next token probability, scaled by `sqrt(eta_cutoff)`. In the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | | `diversity_penalty`? | `number` | This value is subtracted from a beam's score if it generates a token same as any beam from other group at a particular time. Note that `diversity_penalty` is only effective if `group beam search` is enabled. _(default: `0.0`)_ | | `repetition_penalty`? | `number` | The parameter for repetition penalty. 1.0 means no penalty. See [this paper](https://huggingface.co/papers/1909.05858) for more details. _(default: `1.0`)_ | -| `encoder_repetition_penalty`? | `number` | The paramater for encoder_repetition_penalty. An exponential penalty on sequences that are not in the original input. 1.0 means no penalty. _(default: `1.0`)_ | +| `encoder_repetition_penalty`? | `number` | The parameter for encoder_repetition_penalty. An exponential penalty on sequences that are not in the original input. 1.0 means no penalty. _(default: `1.0`)_ | | `length_penalty`? | `number` | Exponential penalty to the length that is used with beam-based generation. It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. _(default: `1.0`)_ | | `no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size can only occur once. _(default: `0`)_ | | `bad_words_ids`? | `number[][]` | List of token ids that are not allowed to be generated. In order to get the token ids of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index 5830842bc..bd10b9fa3 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -323,6 +323,8 @@ function prepareCell(text) { return (text || "") .replace(/\{@link\s+([^}\s]+)(?:\s+[^}]+)?\}/g, "$1") .replace(/\|/g, "\\|") + .replace(/:\s*\n\s*-\s+/g, ":<br />- ") + .replace(/\n\s*-\s+/g, "<br />- ") .replace(/\n+/g, " ") .trim(); } From 59b21acc3bb6b92050086b4e87e387665fa0d316 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 6 May 2026 17:30:03 -0400 Subject: [PATCH 058/103] docs improvements --- .../references/PIPELINE_OPTIONS.md | 60 +++++++++---------- .../docs/source/tutorials/node.md | 2 +- .../docs/source/tutorials/react.md | 2 +- .../src/generation/configuration_utils.js | 51 ++++++++-------- .../src/generation/logits_process.js | 36 +++++------ .../src/generation/stopping_criteria.js | 8 +-- packages/transformers/src/utils/core.js | 12 ++-- packages/transformers/src/utils/hub.js | 2 +- 8 files changed, 86 insertions(+), 87 deletions(-) diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index ceb4cc9dd..d1890b5eb 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -12,7 +12,7 @@ class in the [API reference](https://huggingface.co/docs/transformers.js/api/pip | `progress_callback`? | `ProgressCallback` | If specified, this function will be called during model construction, to provide the user with progress updates. _(default: `null`)_ | | `config`? | `PretrainedConfig` | Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when:<br />- The model is a model provided by the library (loaded with the *model id* string of a pretrained model).<br />- The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | | `cache_dir`? | `string` | Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. _(default: `null`)_ | -| `local_files_only`? | `boolean` | Whether or not to only look at local files (e.g., not try downloading the model). _(default: `false`)_ | +| `local_files_only`? | `boolean` | Whether to only look at local files (e.g., not try downloading the model). _(default: `false`)_ | | `revision`? | `string` | The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git. NOTE: This setting is ignored for local requests. _(default: `'main'`)_ | | `subfolder`? | `string` | In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can specify the folder name here. _(default: `'onnx'`)_ | | `model_file_name`? | `string` | If specified, load the model with this name (excluding the dtype and .onnx suffixes). Currently only valid for encoder- or decoder-only models. _(default: `null`)_ | @@ -41,7 +41,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| -| `status` | `'initiate'` | | +| `status` | `'initiate'` | A file load is about to start. | | `name` | `string` | The model id or directory path. | | `file` | `string` | The name of the file. | @@ -49,7 +49,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| -| `status` | `'download'` | | +| `status` | `'download'` | A file download has started. | | `name` | `string` | The model id or directory path. | | `file` | `string` | The name of the file. | @@ -57,7 +57,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| -| `status` | `'progress'` | | +| `status` | `'progress'` | A file download has reported byte progress. | | `name` | `string` | The model id or directory path. | | `file` | `string` | The name of the file. | | `progress` | `number` | A number between 0 and 100. | @@ -68,7 +68,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| -| `status` | `'done'` | | +| `status` | `'done'` | A file has finished loading. | | `name` | `string` | The model id or directory path. | | `file` | `string` | The name of the file. | @@ -76,7 +76,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| -| `status` | `'ready'` | | +| `status` | `'ready'` | The requested pipeline is ready to use. | | `task` | `string` | The loaded task. | | `model` | `string` | The loaded model. | @@ -84,7 +84,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| -| `status` | `'progress_total'` | | +| `status` | `'progress_total'` | Aggregate progress across all files being loaded. | | `name` | `string` | The model id or directory path. | | `progress` | `number` | A number between 0 and 100. | | `loaded` | `number` | The number of bytes loaded. | @@ -149,46 +149,46 @@ anywhere `generate()` is invoked directly. | `max_new_tokens`? | `number` | The maximum number of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | | `min_length`? | `number` | The minimum length of the sequence to be generated. Corresponds to the length of the input prompt + `min_new_tokens`. Its effect is overridden by `min_new_tokens`, if also set. _(default: `0`)_ | | `min_new_tokens`? | `number` | The minimum number of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | -| `early_stopping`? | `boolean\|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values:<br />- `true`, where the generation stops as soon as there are `num_beams` complete candidates;<br />- `false`, where an heuristic is applied and the generation stops when is it very unlikely to find better candidates;<br />- `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | +| `early_stopping`? | `boolean\|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values:<br />- `true`, where the generation stops as soon as there are `num_beams` complete candidates;<br />- `false`, where a heuristic is applied and the generation stops when it is very unlikely to find better candidates;<br />- `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | | `max_time`? | `number` | The maximum amount of time you allow the computation to run for in seconds. Generation will still finish the current pass after allocated time has been passed. _(default: `null`)_ | -| `do_sample`? | `boolean` | Whether or not to use sampling; use greedy decoding otherwise. _(default: `false`)_ | +| `do_sample`? | `boolean` | Whether to use sampling; use greedy decoding otherwise. _(default: `false`)_ | | `num_beams`? | `number` | Number of beams for beam search. 1 means no beam search. _(default: `1`)_ | | `num_beam_groups`? | `number` | Number of groups to divide `num_beams` into in order to ensure diversity among different groups of beams. See [this paper](https://huggingface.co/papers/1610.02424) for more details. _(default: `1`)_ | -| `penalty_alpha`? | `number` | The values balance the model confidence and the degeneration penalty in contrastive search decoding. _(default: `null`)_ | -| `use_cache`? | `boolean` | Whether or not the model should use the past last key/values attentions (if applicable to the model) to speed up decoding. _(default: `true`)_ | +| `penalty_alpha`? | `number` | The value balances model confidence and the degeneration penalty in contrastive search decoding. _(default: `null`)_ | +| `use_cache`? | `boolean` | Whether the model should use the past key/value attentions (if applicable to the model) to speed up decoding. _(default: `true`)_ | | `temperature`? | `number` | The value used to modulate the next token probabilities. _(default: `1.0`)_ | | `top_k`? | `number` | The number of highest probability vocabulary tokens to keep for top-k-filtering. _(default: `50`)_ | -| `top_p`? | `number` | If set to float < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or higher are kept for generation. _(default: `1.0`)_ | -| `typical_p`? | `number` | Local typicality measures how similar the conditional probability of predicting a target token next is to the expected conditional probability of predicting a random token next, given the partial text already generated. If set to float < 1, the smallest set of the most locally typical tokens with probabilities that add up to `typical_p` or higher are kept for generation. See [this paper](https://huggingface.co/papers/2202.00666) for more details. _(default: `1.0`)_ | -| `epsilon_cutoff`? | `number` | If set to float strictly between 0 and 1, only tokens with a conditional probability greater than `epsilon_cutoff` will be sampled. In the paper, suggested values range from 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | -| `eta_cutoff`? | `number` | Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to float strictly between 0 and 1, a token is only considered if it is greater than either `eta_cutoff` or `sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits)))`. The latter term is intuitively the expected next token probability, scaled by `sqrt(eta_cutoff)`. In the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | -| `diversity_penalty`? | `number` | This value is subtracted from a beam's score if it generates a token same as any beam from other group at a particular time. Note that `diversity_penalty` is only effective if `group beam search` is enabled. _(default: `0.0`)_ | -| `repetition_penalty`? | `number` | The parameter for repetition penalty. 1.0 means no penalty. See [this paper](https://huggingface.co/papers/1909.05858) for more details. _(default: `1.0`)_ | -| `encoder_repetition_penalty`? | `number` | The parameter for encoder_repetition_penalty. An exponential penalty on sequences that are not in the original input. 1.0 means no penalty. _(default: `1.0`)_ | +| `top_p`? | `number` | If set to a float below 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or higher are kept for generation. _(default: `1.0`)_ | +| `typical_p`? | `number` | Local typicality measures how similar the conditional probability of predicting a target token next is to the expected conditional probability of predicting a random token next, given the partial text already generated. If set to a float below 1, the smallest set of the most locally typical tokens with probabilities that add up to `typical_p` or higher are kept for generation. See [this paper](https://huggingface.co/papers/2202.00666) for more details. _(default: `1.0`)_ | +| `epsilon_cutoff`? | `number` | If set to a float strictly between 0 and 1, only tokens with a conditional probability greater than `epsilon_cutoff` will be sampled. In the paper, suggested values range from 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | +| `eta_cutoff`? | `number` | Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to a float strictly between 0 and 1, a token is only considered if it is greater than either `eta_cutoff` or `sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits)))`. The latter term is intuitively the expected next token probability, scaled by `sqrt(eta_cutoff)`. In the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | +| `diversity_penalty`? | `number` | This value is subtracted from a beam's score if it generates the same token as any beam from another group at a particular time. Note that `diversity_penalty` is only effective if `group beam search` is enabled. _(default: `0.0`)_ | +| `repetition_penalty`? | `number` | Penalty applied to repeated tokens. 1.0 means no penalty. See [this paper](https://huggingface.co/papers/1909.05858) for more details. _(default: `1.0`)_ | +| `encoder_repetition_penalty`? | `number` | Penalty applied to sequences that are not in the original input. 1.0 means no penalty. _(default: `1.0`)_ | | `length_penalty`? | `number` | Exponential penalty to the length that is used with beam-based generation. It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. _(default: `1.0`)_ | -| `no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size can only occur once. _(default: `0`)_ | -| `bad_words_ids`? | `number[][]` | List of token ids that are not allowed to be generated. In order to get the token ids of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | -| `force_words_ids`? | `number[][]\|number[][][]` | List of token ids that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | -| `renormalize_logits`? | `boolean` | Whether to renormalize the logits after applying all the logits processors or warpers (including the custom ones). It's highly recommended to set this flag to `true` as the search algorithms suppose the score logits are normalized but some logit processors or warpers break the normalization. _(default: `false`)_ | +| `no_repeat_ngram_size`? | `number` | If set to an integer greater than 0, all n-grams of that size can only occur once. _(default: `0`)_ | +| `bad_words_ids`? | `number[][]` | List of token IDs that are not allowed to be generated. In order to get the token IDs of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | +| `force_words_ids`? | `number[][]\|number[][][]` | List of token IDs that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | +| `renormalize_logits`? | `boolean` | Whether to renormalize the logits after applying all the logits processors or warpers (including the custom ones). It's highly recommended to set this flag to `true` because search algorithms assume the score logits are normalized, but some logit processors or warpers break the normalization. _(default: `false`)_ | | `constraints`? | `Object[]` | Custom constraints that can be added to the generation to ensure that the output will contain the use of certain tokens as defined by `Constraint` objects, in the most sensible way possible. _(default: `null`)_ | | `forced_bos_token_id`? | `number` | The id of the token to force as the first generated token after the `decoder_start_token_id`. Useful for multilingual models like mBART where the first generated token needs to be the target language token. _(default: `null`)_ | | `forced_eos_token_id`? | `number\|number[]` | The id of the token to force as the last generated token when `max_length` is reached. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | -| `remove_invalid_values`? | `boolean` | Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method to crash. Note that using `remove_invalid_values` can slow down generation. _(default: `false`)_ | -| `exponential_decay_length_penalty`? | `[number, number]` | This Tuple adds an exponentially increasing length penalty, after a certain amount of tokens have been generated. The tuple shall consist of: `(start_index, decay_factor)` where `start_index` indicates where penalty starts and `decay_factor` represents the factor of exponential decay. _(default: `null`)_ | +| `remove_invalid_values`? | `boolean` | Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method from crashing. Note that using `remove_invalid_values` can slow down generation. _(default: `false`)_ | +| `exponential_decay_length_penalty`? | `[number, number]` | This tuple adds an exponentially increasing length penalty after a certain number of tokens have been generated. The tuple consists of: `(start_index, decay_factor)` where `start_index` indicates where the penalty starts and `decay_factor` represents the factor of exponential decay. _(default: `null`)_ | | `suppress_tokens`? | `number[]` | A list of tokens that will be suppressed at generation. The `SuppressTokens` logit processor will set their log probs to `-inf` so that they are not sampled. _(default: `null`)_ | | `streamer`? | `TextStreamer` | A streamer that will be used to stream the generation. _(default: `null`)_ | | `begin_suppress_tokens`? | `number[]` | A list of tokens that will be suppressed at the beginning of the generation. The `SuppressBeginTokens` logit processor will set their log probs to `-inf` so that they are not sampled. _(default: `null`)_ | -| `forced_decoder_ids`? | `[number, number][]` | A list of pairs of integers which indicates a mapping from generation indices to token indices that will be forced before sampling. For example, `[[1, 123]]` means the second generated token will always be a token of index 123. _(default: `null`)_ | +| `forced_decoder_ids`? | `[number, number][]` | A list of pairs of integers that indicates a mapping from generation indices to token indices that will be forced before sampling. For example, `[[1, 123]]` means the second generated token will always be a token of index 123. _(default: `null`)_ | | `guidance_scale`? | `number` | The guidance scale for classifier free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages the model to generate samples that are more closely linked to the input prompt, usually at the expense of poorer quality. _(default: `null`)_ | | `num_return_sequences`? | `number` | The number of independently computed returned sequences for each element in the batch. _(default: `1`)_ | -| `output_attentions`? | `boolean` | Whether or not to return the attentions tensors of all attention layers. See `attentions` under returned tensors for more details. _(default: `false`)_ | -| `output_hidden_states`? | `boolean` | Whether or not to return the hidden states of all layers. See `hidden_states` under returned tensors for more details. _(default: `false`)_ | -| `output_scores`? | `boolean` | Whether or not to return the prediction scores. See `scores` under returned tensors for more details. _(default: `false`)_ | -| `return_dict_in_generate`? | `boolean` | Whether or not to return a `ModelOutput` instead of a plain tuple. _(default: `false`)_ | +| `output_attentions`? | `boolean` | Whether to return the attentions tensors of all attention layers. See `attentions` under returned tensors for more details. _(default: `false`)_ | +| `output_hidden_states`? | `boolean` | Whether to return the hidden states of all layers. See `hidden_states` under returned tensors for more details. _(default: `false`)_ | +| `output_scores`? | `boolean` | Whether to return the prediction scores. See `scores` under returned tensors for more details. _(default: `false`)_ | +| `return_dict_in_generate`? | `boolean` | Whether to return a `ModelOutput` instead of a plain tuple. _(default: `false`)_ | | `pad_token_id`? | `number` | The id of the *padding* token. _(default: `null`)_ | | `bos_token_id`? | `number` | The id of the *beginning-of-sequence* token. _(default: `null`)_ | | `eos_token_id`? | `number\|number[]` | The id of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | -| `encoder_no_repeat_ngram_size`? | `number` | If set to int > 0, all ngrams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. _(default: `0`)_ | +| `encoder_no_repeat_ngram_size`? | `number` | If set to an integer greater than 0, all n-grams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. _(default: `0`)_ | | `decoder_start_token_id`? | `number` | If an encoder-decoder model starts decoding with a different token than *bos*, the id of that token. _(default: `null`)_ | | `generation_kwargs`? | `Object` | Additional generation kwargs will be forwarded to the `generate` function of the model. Kwargs that are not present in `generate`'s signature will be used in the model forward pass. _(default: `{}`)_ | <!-- @generated:end id=fields:GenerationConfig --> diff --git a/packages/transformers/docs/source/tutorials/node.md b/packages/transformers/docs/source/tutorials/node.md index a187ad57d..f43247ef4 100644 --- a/packages/transformers/docs/source/tutorials/node.md +++ b/packages/transformers/docs/source/tutorials/node.md @@ -38,7 +38,7 @@ npm i @huggingface/transformers Next, create a new file called `app.js`, which will be the entry point for our application. Depending on whether you're using [ECMAScript modules](#ecmascript-modules-esm) or [CommonJS](#commonjs), you will need to do some things differently (see below). -We'll also create a helper class called `MyClassificationPipeline` control the loading of the pipeline. It uses the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create a single instance of the pipeline when `getInstance` is first called, and uses this pipeline for all subsequent calls: +We'll also create a helper class called `MyClassificationPipeline` to control the loading of the pipeline. It uses the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create a single instance of the pipeline when `getInstance` is first called, and uses this pipeline for all subsequent calls: ### ECMAScript modules (ESM) diff --git a/packages/transformers/docs/source/tutorials/react.md b/packages/transformers/docs/source/tutorials/react.md index dba6b0f45..b2d18f27c 100644 --- a/packages/transformers/docs/source/tutorials/react.md +++ b/packages/transformers/docs/source/tutorials/react.md @@ -55,7 +55,7 @@ For this application, we will use the [Xenova/nllb-200-distilled-600M](https://h We can achieve both of these goals by using a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) and some [React hooks](https://react.dev/reference/react). -1. Create a file called `worker.js` in the `src` directory. This script will do all the heavy-lifting for us, including loading and running of the translation pipeline. To ensure the model is only loaded once, we will create the `MyTranslationPipeline` class which use the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create a single instance of the pipeline when `getInstance` is first called, and use this pipeline for all subsequent calls: +1. Create a file called `worker.js` in the `src` directory. This script will do all the heavy-lifting for us, including loading and running the translation pipeline. To ensure the model is only loaded once, we will create the `MyTranslationPipeline` class, which uses the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create a single instance of the pipeline when `getInstance` is first called, and use this pipeline for all subsequent calls: ```javascript import { pipeline, TextStreamer } from "@huggingface/transformers"; diff --git a/packages/transformers/src/generation/configuration_utils.js b/packages/transformers/src/generation/configuration_utils.js index 2d68b45db..ff87a99fc 100644 --- a/packages/transformers/src/generation/configuration_utils.js +++ b/packages/transformers/src/generation/configuration_utils.js @@ -48,7 +48,7 @@ export class GenerationConfig { /** * Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values: * - `true`, where the generation stops as soon as there are `num_beams` complete candidates; - * - `false`, where an heuristic is applied and the generation stops when is it very unlikely to find better candidates; + * - `false`, where a heuristic is applied and the generation stops when it is very unlikely to find better candidates; * - `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). * @type {boolean|"never"} * @default false @@ -65,7 +65,7 @@ export class GenerationConfig { // Parameters that control the generation strategy used /** - * Whether or not to use sampling; use greedy decoding otherwise. + * Whether to use sampling; use greedy decoding otherwise. * @type {boolean} * @default false */ @@ -87,14 +87,14 @@ export class GenerationConfig { num_beam_groups = 1; /** - * The values balance the model confidence and the degeneration penalty in contrastive search decoding. + * The value balances model confidence and the degeneration penalty in contrastive search decoding. * @type {number} * @default null */ penalty_alpha = null; /** - * Whether or not the model should use the past last key/values attentions (if applicable to the model) to speed up decoding. + * Whether the model should use the past key/value attentions (if applicable to the model) to speed up decoding. * @type {boolean} * @default true */ @@ -116,7 +116,7 @@ export class GenerationConfig { top_k = 50; /** - * If set to float < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or higher are kept for generation. + * If set to a float below 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or higher are kept for generation. * @type {number} * @default 1.0 */ @@ -124,7 +124,7 @@ export class GenerationConfig { /** * Local typicality measures how similar the conditional probability of predicting a target token next is to the expected conditional probability of predicting a random token next, given the partial text already generated. - * If set to float < 1, the smallest set of the most locally typical tokens with probabilities that add up to `typical_p` or higher are kept for generation. + * If set to a float below 1, the smallest set of the most locally typical tokens with probabilities that add up to `typical_p` or higher are kept for generation. * See [this paper](https://huggingface.co/papers/2202.00666) for more details. * @type {number} * @default 1.0 @@ -132,7 +132,7 @@ export class GenerationConfig { typical_p = 1.0; /** - * If set to float strictly between 0 and 1, only tokens with a conditional probability greater than `epsilon_cutoff` will be sampled. + * If set to a float strictly between 0 and 1, only tokens with a conditional probability greater than `epsilon_cutoff` will be sampled. * In the paper, suggested values range from 3e-4 to 9e-4, depending on the size of the model. * See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. * @type {number} @@ -142,7 +142,7 @@ export class GenerationConfig { /** * Eta sampling is a hybrid of locally typical sampling and epsilon sampling. - * If set to float strictly between 0 and 1, a token is only considered if it is greater than either `eta_cutoff` or `sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits)))`. + * If set to a float strictly between 0 and 1, a token is only considered if it is greater than either `eta_cutoff` or `sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits)))`. * The latter term is intuitively the expected next token probability, scaled by `sqrt(eta_cutoff)`. In the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model. * See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. * @type {number} @@ -151,7 +151,7 @@ export class GenerationConfig { eta_cutoff = 0.0; /** - * This value is subtracted from a beam's score if it generates a token same as any beam from other group at a particular time. + * This value is subtracted from a beam's score if it generates the same token as any beam from another group at a particular time. * Note that `diversity_penalty` is only effective if `group beam search` is enabled. * @type {number} * @default 0.0 @@ -159,7 +159,7 @@ export class GenerationConfig { diversity_penalty = 0.0; /** - * The parameter for repetition penalty. 1.0 means no penalty. + * Penalty applied to repeated tokens. 1.0 means no penalty. * See [this paper](https://huggingface.co/papers/1909.05858) for more details. * @type {number} * @default 1.0 @@ -167,8 +167,7 @@ export class GenerationConfig { repetition_penalty = 1.0; /** - * The parameter for encoder_repetition_penalty. - * An exponential penalty on sequences that are not in the original input. + * Penalty applied to sequences that are not in the original input. * 1.0 means no penalty. * @type {number} * @default 1.0 @@ -185,15 +184,15 @@ export class GenerationConfig { length_penalty = 1.0; /** - * If set to int > 0, all ngrams of that size can only occur once. + * If set to an integer greater than 0, all n-grams of that size can only occur once. * @type {number} * @default 0 */ no_repeat_ngram_size = 0; /** - * List of token ids that are not allowed to be generated. - * In order to get the token ids of the words that should not appear in the generated text, use + * List of token IDs that are not allowed to be generated. + * In order to get the token IDs of the words that should not appear in the generated text, use * `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. * @type {number[][]} * @default null @@ -201,7 +200,7 @@ export class GenerationConfig { bad_words_ids = null; /** - * List of token ids that must be generated. + * List of token IDs that must be generated. * If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. * If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. * @type {number[][]|number[][][]} @@ -211,7 +210,7 @@ export class GenerationConfig { /** * Whether to renormalize the logits after applying all the logits processors or warpers (including the custom ones). - * It's highly recommended to set this flag to `true` as the search algorithms suppose the score logits are normalized but some logit processors or warpers break the normalization. + * It's highly recommended to set this flag to `true` because search algorithms assume the score logits are normalized, but some logit processors or warpers break the normalization. * @type {boolean} * @default false */ @@ -241,14 +240,14 @@ export class GenerationConfig { forced_eos_token_id = null; /** - * Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method to crash. Note that using `remove_invalid_values` can slow down generation. + * Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method from crashing. Note that using `remove_invalid_values` can slow down generation. * @type {boolean} */ remove_invalid_values = false; /** - * This Tuple adds an exponentially increasing length penalty, after a certain amount of tokens have been generated. - * The tuple shall consist of: `(start_index, decay_factor)` where `start_index` indicates where penalty starts and `decay_factor` represents the factor of exponential decay. + * This tuple adds an exponentially increasing length penalty after a certain number of tokens have been generated. + * The tuple consists of: `(start_index, decay_factor)` where `start_index` indicates where the penalty starts and `decay_factor` represents the factor of exponential decay. * @type {[number, number]} * @default null */ @@ -278,7 +277,7 @@ export class GenerationConfig { begin_suppress_tokens = null; /** - * A list of pairs of integers which indicates a mapping from generation indices to token indices that will be forced before sampling. + * A list of pairs of integers that indicates a mapping from generation indices to token indices that will be forced before sampling. * For example, `[[1, 123]]` means the second generated token will always be a token of index 123. * @type {[number, number][]} * @default null @@ -303,7 +302,7 @@ export class GenerationConfig { num_return_sequences = 1; /** - * Whether or not to return the attentions tensors of all attention layers. + * Whether to return the attentions tensors of all attention layers. * See `attentions` under returned tensors for more details. * @type {boolean} * @default false @@ -311,7 +310,7 @@ export class GenerationConfig { output_attentions = false; /** - * Whether or not to return the hidden states of all layers. + * Whether to return the hidden states of all layers. * See `hidden_states` under returned tensors for more details. * @type {boolean} * @default false @@ -319,7 +318,7 @@ export class GenerationConfig { output_hidden_states = false; /** - * Whether or not to return the prediction scores. + * Whether to return the prediction scores. * See `scores` under returned tensors for more details. * @type {boolean} * @default false @@ -327,7 +326,7 @@ export class GenerationConfig { output_scores = false; /** - * Whether or not to return a `ModelOutput` instead of a plain tuple. + * Whether to return a `ModelOutput` instead of a plain tuple. * @type {boolean} * @default false */ @@ -358,7 +357,7 @@ export class GenerationConfig { // Generation parameters exclusive to encoder-decoder models /** - * If set to int > 0, all ngrams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. + * If set to an integer greater than 0, all n-grams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. * @type {number} * @default 0 */ diff --git a/packages/transformers/src/generation/logits_process.js b/packages/transformers/src/generation/logits_process.js index a03611f69..0d717f9d7 100644 --- a/packages/transformers/src/generation/logits_process.js +++ b/packages/transformers/src/generation/logits_process.js @@ -22,7 +22,7 @@ export class LogitsProcessor extends Callable { * Apply the processor to the input logits. * * @abstract - * @param {bigint[][]} input_ids The input ids. + * @param {bigint[][]} input_ids The input IDs. * @param {Tensor} logits The logits to process. * @throws {Error} Throws an error if `_call` is not implemented in the subclass. */ @@ -39,7 +39,7 @@ export class LogitsWarper extends Callable { * Apply the processor to the input logits. * * @abstract - * @param {bigint[][]} input_ids The input ids. + * @param {bigint[][]} input_ids The input IDs. * @param {Tensor} logits The logits to process. * @throws {Error} Throws an error if `_call` is not implemented in the subclass. */ @@ -176,7 +176,7 @@ export class ForcedEOSTokenLogitsProcessor extends LogitsProcessor { /** * Create a ForcedEOSTokenLogitsProcessor. * @param {number} max_length The maximum length of the sequence to be generated. - * @param {number|number[]} eos_token_id The id(s) of the *end-of-sequence* token. + * @param {number|number[]} eos_token_id The ID or IDs of the *end-of-sequence* token. */ constructor(max_length, eos_token_id) { super(); @@ -187,7 +187,7 @@ export class ForcedEOSTokenLogitsProcessor extends LogitsProcessor { /** * Apply the processor to input_ids and logits. * - * @param {bigint[][]} input_ids The input ids. + * @param {bigint[][]} input_ids The input IDs. * @param {Tensor} logits The logits tensor. */ _call(input_ids, logits) { @@ -355,12 +355,12 @@ export class WhisperTimeStampLogitsProcessor extends LogitsProcessor { } /** - * A logits processor that disallows ngrams of a certain size to be repeated. + * A logits processor that disallows repeated n-grams of a certain size. */ export class NoRepeatNGramLogitsProcessor extends LogitsProcessor { /** * Create a NoRepeatNGramLogitsProcessor. - * @param {number} no_repeat_ngram_size The no-repeat-ngram size. All ngrams of this size can only occur once. + * @param {number} no_repeat_ngram_size The no-repeat n-gram size. All n-grams of this size can only occur once. */ constructor(no_repeat_ngram_size) { super(); @@ -368,8 +368,8 @@ export class NoRepeatNGramLogitsProcessor extends LogitsProcessor { } /** - * Generate n-grams from a sequence of token ids. - * @param {bigint[]} prevInputIds List of previous input ids + * Generate n-grams from a sequence of token IDs. + * @param {bigint[]} prevInputIds List of previous input IDs. * @returns {Map<string, number[]>} Map of generated n-grams */ getNgrams(prevInputIds) { @@ -398,9 +398,9 @@ export class NoRepeatNGramLogitsProcessor extends LogitsProcessor { } /** - * Generate n-grams from a sequence of token ids. + * Generate n-grams from a sequence of token IDs. * @param {Map<string, number[]>} bannedNgrams Map of banned n-grams - * @param {bigint[]} prevInputIds List of previous input ids + * @param {bigint[]} prevInputIds List of previous input IDs. * @returns {number[]} Map of generated n-grams */ getGeneratedNgrams(bannedNgrams, prevInputIds) { @@ -411,7 +411,7 @@ export class NoRepeatNGramLogitsProcessor extends LogitsProcessor { /** * Calculate banned n-gram tokens - * @param {bigint[]} prevInputIds List of previous input ids + * @param {bigint[]} prevInputIds List of previous input IDs. * @returns {number[]} Map of generated n-grams */ calcBannedNgramTokens(prevInputIds) { @@ -427,10 +427,10 @@ export class NoRepeatNGramLogitsProcessor extends LogitsProcessor { } /** - * Apply the no-repeat-ngram processor to the logits. + * Apply the no-repeat n-gram processor to the logits. * @param {bigint[][]} input_ids The input IDs. * @param {Tensor} logits The logits. - * @returns {Tensor} The logits with no-repeat-ngram processing. + * @returns {Tensor} The logits with no-repeat n-gram processing. */ _call(input_ids, logits) { for (let i = 0; i < input_ids.length; ++i) { @@ -458,7 +458,7 @@ export class NoRepeatNGramLogitsProcessor extends LogitsProcessor { export class RepetitionPenaltyLogitsProcessor extends LogitsProcessor { /** * Create a RepetitionPenaltyLogitsProcessor. - * @param {number} penalty The parameter for repetition penalty. + * @param {number} penalty Penalty applied to repeated tokens. * - 1.0 means no penalty. Above 1.0 penalizes previously generated tokens. * - Between 0.0 and 1.0 rewards previously generated tokens. */ @@ -497,7 +497,7 @@ export class MinLengthLogitsProcessor extends LogitsProcessor { /** * Create a MinLengthLogitsProcessor. * @param {number} min_length The minimum length below which the score of `eos_token_id` is set to negative infinity. - * @param {number|number[]} eos_token_id The ID/IDs of the end-of-sequence token. + * @param {number|number[]} eos_token_id The ID or IDs of the end-of-sequence token. */ constructor(min_length, eos_token_id) { super(); @@ -534,7 +534,7 @@ export class MinNewTokensLengthLogitsProcessor extends LogitsProcessor { * Create a MinNewTokensLengthLogitsProcessor. * @param {number} prompt_length_to_skip The input tokens length. * @param {number} min_new_tokens The minimum *new* tokens length below which the score of `eos_token_id` is set to negative infinity. - * @param {number|number[]} eos_token_id The ID/IDs of the end-of-sequence token. + * @param {number|number[]} eos_token_id The ID or IDs of the end-of-sequence token. */ constructor(prompt_length_to_skip, min_new_tokens, eos_token_id) { super(); @@ -570,8 +570,8 @@ export class MinNewTokensLengthLogitsProcessor extends LogitsProcessor { export class NoBadWordsLogitsProcessor extends LogitsProcessor { /** * Create a `NoBadWordsLogitsProcessor`. - * @param {number[][]} bad_words_ids List of list of token ids that are not allowed to be generated. - * @param {number|number[]} eos_token_id The id of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. + * @param {number[][]} bad_words_ids List of token ID sequences that are not allowed to be generated. + * @param {number|number[]} eos_token_id The ID of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. */ constructor(bad_words_ids, eos_token_id) { super(); diff --git a/packages/transformers/src/generation/stopping_criteria.js b/packages/transformers/src/generation/stopping_criteria.js index 5f7aa1d2a..4613b0dcd 100644 --- a/packages/transformers/src/generation/stopping_criteria.js +++ b/packages/transformers/src/generation/stopping_criteria.js @@ -84,8 +84,8 @@ export class StoppingCriteriaList extends Callable { } /** - * This class can be used to stop generation whenever the full generated number of tokens exceeds `max_length`. - * Keep in mind for decoder-only type of transformers, this will include the initial prompted tokens. + * Stops generation whenever the generated sequence length reaches `max_length`. + * For decoder-only models, this includes the initial prompt tokens. */ export class MaxLengthCriteria extends StoppingCriteria { /** @@ -107,7 +107,7 @@ export class MaxLengthCriteria extends StoppingCriteria { // TODO: add MaxTimeCriteria /** - * This class can be used to stop generation whenever the "end-of-sequence" token is generated. + * Stops generation whenever an "end-of-sequence" token is generated. * By default, it uses the `model.generation_config.eos_token_id`. */ export class EosTokenCriteria extends StoppingCriteria { @@ -140,7 +140,7 @@ export class EosTokenCriteria extends StoppingCriteria { } /** - * This class can be used to stop generation whenever the user interrupts the process. + * Stops generation whenever the user interrupts the process. */ export class InterruptableStoppingCriteria extends StoppingCriteria { constructor() { diff --git a/packages/transformers/src/utils/core.js b/packages/transformers/src/utils/core.js index e897d1423..4950818c8 100644 --- a/packages/transformers/src/utils/core.js +++ b/packages/transformers/src/utils/core.js @@ -12,21 +12,21 @@ import { Callable } from './generic.js'; /** * @typedef {Object} InitiateProgressInfo - * @property {'initiate'} status + * @property {'initiate'} status A file load is about to start. * @property {string} name The model id or directory path. * @property {string} file The name of the file. */ /** * @typedef {Object} DownloadProgressInfo - * @property {'download'} status + * @property {'download'} status A file download has started. * @property {string} name The model id or directory path. * @property {string} file The name of the file. */ /** * @typedef {Object} ProgressStatusInfo - * @property {'progress'} status + * @property {'progress'} status A file download has reported byte progress. * @property {string} name The model id or directory path. * @property {string} file The name of the file. * @property {number} progress A number between 0 and 100. @@ -48,7 +48,7 @@ import { Callable } from './generic.js'; /** * @typedef {Object} TotalProgressInfo - * @property {'progress_total'} status + * @property {'progress_total'} status Aggregate progress across all files being loaded. * @property {string} name The model id or directory path. * @property {number} progress A number between 0 and 100. * @property {number} loaded The number of bytes loaded. @@ -58,14 +58,14 @@ import { Callable } from './generic.js'; /** * @typedef {Object} DoneProgressInfo - * @property {'done'} status + * @property {'done'} status A file has finished loading. * @property {string} name The model id or directory path. * @property {string} file The name of the file. */ /** * @typedef {Object} ReadyProgressInfo - * @property {'ready'} status + * @property {'ready'} status The requested pipeline is ready to use. * @property {string} task The loaded task. * @property {string} model The loaded model. */ diff --git a/packages/transformers/src/utils/hub.js b/packages/transformers/src/utils/hub.js index 6cd7e4064..8256eee4e 100755 --- a/packages/transformers/src/utils/hub.js +++ b/packages/transformers/src/utils/hub.js @@ -30,7 +30,7 @@ export { MAX_EXTERNAL_DATA_CHUNKS } from './hub/constants.js'; * - The model is a model provided by the library (loaded with the *model id* string of a pretrained model). * - The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. * @property {string} [cache_dir=null] Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. - * @property {boolean} [local_files_only=false] Whether or not to only look at local files (e.g., not try downloading the model). + * @property {boolean} [local_files_only=false] Whether to only look at local files (e.g., not try downloading the model). * @property {string} [revision='main'] The specific model version to use. It can be a branch name, a tag name, or a commit id, * since we use a git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git. * NOTE: This setting is ignored for local requests. From f4f30342e96fc2ff91b6a8c1dee83b9ef06c5045 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 6 May 2026 17:58:27 -0400 Subject: [PATCH 059/103] improvements --- .../references/PIPELINE_OPTIONS.md | 14 ++--- .../transformers-js/references/TASKS.md | 52 +++++++++---------- packages/transformers/docs/source/index.md | 2 +- .../src/generation/configuration_utils.js | 20 +++---- .../src/generation/logits_process.js | 2 +- .../transformers/src/generation/parameters.js | 23 ++++---- .../src/generation/stopping_criteria.js | 2 +- .../pipelines/automatic-speech-recognition.js | 4 +- .../src/pipelines/depth-estimation.js | 2 +- .../src/pipelines/feature-extraction.js | 6 +-- .../src/pipelines/image-feature-extraction.js | 4 +- .../src/pipelines/image-segmentation.js | 2 +- .../src/pipelines/image-to-image.js | 4 +- .../src/pipelines/image-to-text.js | 6 +-- .../src/pipelines/summarization.js | 2 +- .../src/pipelines/text-classification.js | 6 +-- .../src/pipelines/text-generation.js | 2 +- .../src/pipelines/text2text-generation.js | 4 +- .../transformers/src/pipelines/translation.js | 6 +-- .../zero-shot-audio-classification.js | 2 +- .../src/pipelines/zero-shot-classification.js | 6 +-- .../zero-shot-image-classification.js | 4 +- .../pipelines/zero-shot-object-detection.js | 4 +- 23 files changed, 89 insertions(+), 90 deletions(-) diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index d1890b5eb..05394b7f7 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -165,7 +165,7 @@ anywhere `generate()` is invoked directly. | `diversity_penalty`? | `number` | This value is subtracted from a beam's score if it generates the same token as any beam from another group at a particular time. Note that `diversity_penalty` is only effective if `group beam search` is enabled. _(default: `0.0`)_ | | `repetition_penalty`? | `number` | Penalty applied to repeated tokens. 1.0 means no penalty. See [this paper](https://huggingface.co/papers/1909.05858) for more details. _(default: `1.0`)_ | | `encoder_repetition_penalty`? | `number` | Penalty applied to sequences that are not in the original input. 1.0 means no penalty. _(default: `1.0`)_ | -| `length_penalty`? | `number` | Exponential penalty to the length that is used with beam-based generation. It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. _(default: `1.0`)_ | +| `length_penalty`? | `number` | Exponential penalty applied to sequence length during beam-based generation. It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. _(default: `1.0`)_ | | `no_repeat_ngram_size`? | `number` | If set to an integer greater than 0, all n-grams of that size can only occur once. _(default: `0`)_ | | `bad_words_ids`? | `number[][]` | List of token IDs that are not allowed to be generated. In order to get the token IDs of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | | `force_words_ids`? | `number[][]\|number[][][]` | List of token IDs that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | @@ -175,9 +175,9 @@ anywhere `generate()` is invoked directly. | `forced_eos_token_id`? | `number\|number[]` | The id of the token to force as the last generated token when `max_length` is reached. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | | `remove_invalid_values`? | `boolean` | Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method from crashing. Note that using `remove_invalid_values` can slow down generation. _(default: `false`)_ | | `exponential_decay_length_penalty`? | `[number, number]` | This tuple adds an exponentially increasing length penalty after a certain number of tokens have been generated. The tuple consists of: `(start_index, decay_factor)` where `start_index` indicates where the penalty starts and `decay_factor` represents the factor of exponential decay. _(default: `null`)_ | -| `suppress_tokens`? | `number[]` | A list of tokens that will be suppressed at generation. The `SuppressTokens` logit processor will set their log probs to `-inf` so that they are not sampled. _(default: `null`)_ | -| `streamer`? | `TextStreamer` | A streamer that will be used to stream the generation. _(default: `null`)_ | -| `begin_suppress_tokens`? | `number[]` | A list of tokens that will be suppressed at the beginning of the generation. The `SuppressBeginTokens` logit processor will set their log probs to `-inf` so that they are not sampled. _(default: `null`)_ | +| `suppress_tokens`? | `number[]` | A list of tokens to suppress during generation. The `SuppressTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. _(default: `null`)_ | +| `streamer`? | `TextStreamer` | A streamer used to stream the generation. _(default: `null`)_ | +| `begin_suppress_tokens`? | `number[]` | A list of tokens to suppress at the beginning of the generation. The `SuppressBeginTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. _(default: `null`)_ | | `forced_decoder_ids`? | `[number, number][]` | A list of pairs of integers that indicates a mapping from generation indices to token indices that will be forced before sampling. For example, `[[1, 123]]` means the second generated token will always be a token of index 123. _(default: `null`)_ | | `guidance_scale`? | `number` | The guidance scale for classifier free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages the model to generate samples that are more closely linked to the input prompt, usually at the expense of poorer quality. _(default: `null`)_ | | `num_return_sequences`? | `number` | The number of independently computed returned sequences for each element in the batch. _(default: `1`)_ | @@ -187,10 +187,10 @@ anywhere `generate()` is invoked directly. | `return_dict_in_generate`? | `boolean` | Whether to return a `ModelOutput` instead of a plain tuple. _(default: `false`)_ | | `pad_token_id`? | `number` | The id of the *padding* token. _(default: `null`)_ | | `bos_token_id`? | `number` | The id of the *beginning-of-sequence* token. _(default: `null`)_ | -| `eos_token_id`? | `number\|number[]` | The id of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | +| `eos_token_id`? | `number\|number[]` | The ID of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | | `encoder_no_repeat_ngram_size`? | `number` | If set to an integer greater than 0, all n-grams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. _(default: `0`)_ | -| `decoder_start_token_id`? | `number` | If an encoder-decoder model starts decoding with a different token than *bos*, the id of that token. _(default: `null`)_ | -| `generation_kwargs`? | `Object` | Additional generation kwargs will be forwarded to the `generate` function of the model. Kwargs that are not present in `generate`'s signature will be used in the model forward pass. _(default: `{}`)_ | +| `decoder_start_token_id`? | `number` | If an encoder-decoder model starts decoding with a token other than *bos*, the ID of that token. _(default: `null`)_ | +| `generation_kwargs`? | `Object` | Additional generation kwargs forwarded to the model's `generate` function. Kwargs that are not present in `generate`'s signature are used in the model forward pass. _(default: `{}`)_ | <!-- @generated:end id=fields:GenerationConfig --> ### Streaming tokens as they're produced diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index 2c71a4ae5..2dec3aed0 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -56,7 +56,7 @@ const output = await classifier(audio, { top_k: 4 }); **Default model:** `Xenova/clap-htsat-unfused` -Zero shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you +Zero-shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you provide an audio and a set of `candidate_labels`. **Example:** Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. @@ -90,7 +90,7 @@ const output = await transcriber(url); // { text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country." } ``` -**Example:** Transcribe English w/ timestamps. +**Example:** Transcribe English with timestamps. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -106,7 +106,7 @@ const output = await transcriber(url, { return_timestamps: true }); // } ``` -**Example:** Transcribe English w/ word-level timestamps. +**Example:** Transcribe English with word-level timestamps. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -199,9 +199,9 @@ const output = await synthesizer('Bonjour'); **Default model:** `Xenova/vit-gpt2-image-captioning` -Image To Text pipeline using a `AutoModelForVision2Seq`. This pipeline predicts a caption for a given image. +Image-to-text pipeline using an `AutoModelForVision2Seq`. This pipeline predicts a caption for a given image. -**Example:** Generate a caption for an image w/ `Xenova/vit-gpt2-image-captioning`. +**Example:** Generate a caption for an image with `Xenova/vit-gpt2-image-captioning`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -211,7 +211,7 @@ const output = await captioner(url); // [{ generated_text: 'a cat laying on a couch with another cat' }] ``` -**Example:** Optical Character Recognition (OCR) w/ `Xenova/trocr-small-handwritten`. +**Example:** Optical Character Recognition (OCR) with `Xenova/trocr-small-handwritten`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -311,10 +311,10 @@ const output = await segmenter(url); **Default model:** `Xenova/clip-vit-base-patch32` -Zero shot image classification pipeline. This pipeline predicts the class of +Zero-shot image classification pipeline. This pipeline predicts the class of an image when you provide an image and a set of `candidate_labels`. -**Example:** Zero shot image classification w/ `Xenova/clip-vit-base-patch32`. +**Example:** Zero-shot image classification with `Xenova/clip-vit-base-patch32`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -362,7 +362,7 @@ const output = await detector(img, { threshold: 0.9 }); Zero-shot object detection pipeline. This pipeline predicts bounding boxes of objects when you provide an image and a set of `candidate_labels`. -**Example:** Zero-shot object detection w/ `Xenova/owlvit-base-patch32`. +**Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -394,7 +394,7 @@ const output = await detector(url, candidate_labels); // ] ``` -**Example:** Zero-shot object detection w/ `Xenova/owlvit-base-patch32` (returning top 4 matches and setting a threshold). +**Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32` (returning top 4 matches and setting a threshold). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -449,9 +449,9 @@ const output = await qa_pipeline(image, question); **Default model:** `Xenova/swin2SR-classical-sr-x2-64` -Image to Image pipeline using any `AutoModelForImageToImage`. This pipeline generates an image based on a previous image input. +Image-to-image pipeline using any `AutoModelForImageToImage`. This pipeline generates an image based on a previous image input. -**Example:** Super-resolution w/ `Xenova/swin2SR-classical-sr-x2-64` +**Example:** Super-resolution with `Xenova/swin2SR-classical-sr-x2-64` ```javascript import { pipeline } from '@huggingface/transformers'; @@ -472,7 +472,7 @@ const output = await upscaler(url); Depth estimation pipeline using any `AutoModelForDepthEstimation`. This pipeline predicts the depth of an image. -**Example:** Depth estimation w/ `onnx-community/depth-anything-v2-small` +**Example:** Depth estimation with `onnx-community/depth-anything-v2-small` ```javascript import { pipeline } from '@huggingface/transformers'; @@ -500,7 +500,7 @@ const output = await depth_estimator(image); **Default model:** `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX` Image feature extraction pipeline using no model head. This pipeline extracts the hidden -states from the base transformer, which can be used as features in downstream tasks. +states from the base transformer for use as features in downstream tasks. **Example:** Perform image feature extraction with `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX`. ```javascript @@ -541,7 +541,7 @@ const features = await image_feature_extractor(image); Text classification pipeline using any `ModelForSequenceClassification`. -**Example:** Sentiment-analysis w/ `Xenova/distilbert-base-uncased-finetuned-sst-2-english`. +**Example:** Sentiment analysis with `Xenova/distilbert-base-uncased-finetuned-sst-2-english`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -550,7 +550,7 @@ const output = await classifier('I love transformers!'); // [{ label: 'POSITIVE', score: 0.999788761138916 }] ``` -**Example:** Multilingual sentiment-analysis w/ `Xenova/bert-base-multilingual-uncased-sentiment` (and return top 5 classes). +**Example:** Multilingual sentiment analysis with `Xenova/bert-base-multilingual-uncased-sentiment` (and return top 5 classes). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -565,7 +565,7 @@ const output = await classifier('Le meilleur film de tous les temps.', { top_k: // ] ``` -**Example:** Toxic comment classification w/ `Xenova/toxic-bert` (and return all classes). +**Example:** Toxic comment classification with `Xenova/toxic-bert` (and return all classes). ```javascript const classifier = await pipeline('text-classification', 'Xenova/toxic-bert'); const output = await classifier('I hate you!', { top_k: null }); @@ -699,7 +699,7 @@ const output = await unmasker('The Milky Way is a [MASK] galaxy.', { top_k: 1 }) A pipeline for summarization tasks, inheriting from Text2TextGenerationPipeline. -**Example:** Summarization w/ `Xenova/distilbart-cnn-6-6`. +**Example:** Summarization with `Xenova/distilbart-cnn-6-6`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -724,7 +724,7 @@ const output = await summarizer(text, { Translates text from one language to another. -**Example:** Multilingual translation w/ `Xenova/nllb-200-distilled-600M`. +**Example:** Multilingual translation with `Xenova/nllb-200-distilled-600M`. See [here](https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200) for the full list of languages and their corresponding codes. @@ -740,7 +740,7 @@ const output = await translator('जीवन एक चॉकलेट बॉ // [{ translation_text: 'La vie est comme une boîte à chocolat.' }] ``` -**Example:** Multilingual translation w/ `Xenova/m2m100_418M`. +**Example:** Multilingual translation with `Xenova/m2m100_418M`. See [here](https://huggingface.co/facebook/m2m100_418M#languages-covered) for the full list of languages and their corresponding codes. @@ -756,7 +756,7 @@ const output = await translator('生活就像一盒巧克力。', { // [{ translation_text: 'Life is like a box of chocolate.' }] ``` -**Example:** Multilingual translation w/ `Xenova/mbart-large-50-many-to-many-mmt`. +**Example:** Multilingual translation with `Xenova/mbart-large-50-many-to-many-mmt`. See [here](https://huggingface.co/facebook/mbart-large-50-many-to-many-mmt#languages-covered) for the full list of languages and their corresponding codes. @@ -776,9 +776,9 @@ const output = await translator('संयुक्त राष्ट्र क **Default model:** `Xenova/flan-t5-small` -Text2TextGenerationPipeline class for generating text using a model that performs text-to-text generation tasks. +Text-to-text generation pipeline using a model that performs text-to-text generation tasks. -**Example:** Text-to-text generation w/ `Xenova/LaMini-Flan-T5-783M`. +**Example:** Text-to-text generation with `Xenova/LaMini-Flan-T5-783M`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -842,7 +842,7 @@ trained on NLI (natural language inference) tasks. Equivalent of `text-classific pipelines, but these models don't require a hardcoded number of potential classes, they can be chosen at runtime. It usually means it's slower but it is **much** more flexible. -**Example:** Zero shot classification with `Xenova/mobilebert-uncased-mnli`. +**Example:** Zero-shot classification with `Xenova/mobilebert-uncased-mnli`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -857,7 +857,7 @@ const output = await classifier(text, labels); // } ``` -**Example:** Zero shot classification with `Xenova/nli-deberta-v3-xsmall` (multi-label). +**Example:** Zero-shot classification with `Xenova/nli-deberta-v3-xsmall` (multi-label). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -880,7 +880,7 @@ const output = await classifier(text, labels, { multi_label: true }); **Aliases:** `embeddings` Feature extraction pipeline using no model head. This pipeline extracts the hidden -states from the base transformer, which can be used as features in downstream tasks. +states from the base transformer for use as features in downstream tasks. **Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` (without pooling or normalization). ```javascript diff --git a/packages/transformers/docs/source/index.md b/packages/transformers/docs/source/index.md index f8530ba88..50d99ce74 100644 --- a/packages/transformers/docs/source/index.md +++ b/packages/transformers/docs/source/index.md @@ -28,7 +28,7 @@ The documentation is organized into 5 sections: Here is the list of all tasks and architectures currently supported by Transformers.js. If you don't see your task/model listed here or it is not yet supported, feel free -to open up a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). +to open a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). To find compatible models on the Hub, select the "transformers.js" library tag in the filter menu (or visit [this link](https://huggingface.co/models?library=transformers.js)). You can refine your search by selecting the task you're interested in (e.g., [text-classification](https://huggingface.co/models?pipeline_tag=text-classification&library=transformers.js)). diff --git a/packages/transformers/src/generation/configuration_utils.js b/packages/transformers/src/generation/configuration_utils.js index ff87a99fc..a7428b5fb 100644 --- a/packages/transformers/src/generation/configuration_utils.js +++ b/packages/transformers/src/generation/configuration_utils.js @@ -175,7 +175,7 @@ export class GenerationConfig { encoder_repetition_penalty = 1.0; /** - * Exponential penalty to the length that is used with beam-based generation. + * Exponential penalty applied to sequence length during beam-based generation. * It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. * Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. * @type {number} @@ -254,23 +254,23 @@ export class GenerationConfig { exponential_decay_length_penalty = null; /** - * A list of tokens that will be suppressed at generation. - * The `SuppressTokens` logit processor will set their log probs to `-inf` so that they are not sampled. + * A list of tokens to suppress during generation. + * The `SuppressTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. * @type {number[]} * @default null */ suppress_tokens = null; /** - * A streamer that will be used to stream the generation. + * A streamer used to stream the generation. * @type {import('./streamers.js').TextStreamer} * @default null */ streamer = null; /** - * A list of tokens that will be suppressed at the beginning of the generation. - * The `SuppressBeginTokens` logit processor will set their log probs to `-inf` so that they are not sampled. + * A list of tokens to suppress at the beginning of the generation. + * The `SuppressBeginTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. * @type {number[]} * @default null */ @@ -348,7 +348,7 @@ export class GenerationConfig { bos_token_id = null; /** - * The id of the *end-of-sequence* token. + * The ID of the *end-of-sequence* token. * Optionally, use a list to set multiple *end-of-sequence* tokens. * @type {number|number[]} * @default null @@ -364,7 +364,7 @@ export class GenerationConfig { encoder_no_repeat_ngram_size = 0; /** - * If an encoder-decoder model starts decoding with a different token than *bos*, the id of that token. + * If an encoder-decoder model starts decoding with a token other than *bos*, the ID of that token. * @type {number} * @default null */ @@ -372,8 +372,8 @@ export class GenerationConfig { // Wild card /** - * Additional generation kwargs will be forwarded to the `generate` function of the model. - * Kwargs that are not present in `generate`'s signature will be used in the model forward pass. + * Additional generation kwargs forwarded to the model's `generate` function. + * Kwargs that are not present in `generate`'s signature are used in the model forward pass. * @type {Object} * @default {} */ diff --git a/packages/transformers/src/generation/logits_process.js b/packages/transformers/src/generation/logits_process.js index 0d717f9d7..e6af0a671 100644 --- a/packages/transformers/src/generation/logits_process.js +++ b/packages/transformers/src/generation/logits_process.js @@ -206,7 +206,7 @@ export class ForcedEOSTokenLogitsProcessor extends LogitsProcessor { /** * A LogitsProcessor that suppresses a list of tokens throughout generation. - * Sets their log probs to `-inf` so that they are not generated. + * Sets their log probabilities to `-inf` so that they are not generated. */ export class SuppressTokensLogitsProcessor extends LogitsProcessor { /** diff --git a/packages/transformers/src/generation/parameters.js b/packages/transformers/src/generation/parameters.js index bc0b734bc..d6ce27c5a 100644 --- a/packages/transformers/src/generation/parameters.js +++ b/packages/transformers/src/generation/parameters.js @@ -5,28 +5,27 @@ /** * @typedef {Object} GenerationFunctionParametersBase * @property {import('../utils/tensor.js').Tensor} [inputs=null] (`Tensor` of varying shape depending on the modality, *optional*): - * The sequence used as a prompt for the generation or as model inputs to the encoder. If `null` the + * The sequence used as a prompt for generation or as model inputs to the encoder. If `null`, the * method initializes it with `bos_token_id` and a batch size of 1. For decoder-only models `inputs` * should be in the format of `input_ids`. For encoder-decoder models *inputs* can represent any of * `input_ids`, `input_values`, `input_features`, or `pixel_values`. * @property {import('./configuration_utils.js').GenerationConfig} [generation_config=null] (`GenerationConfig`, *optional*): - * The generation configuration to be used as base parametrization for the generation call. - * `**kwargs` passed to generate matching the attributes of `generation_config` will override them. - * If `generation_config` is not provided, the default will be used, which has the following loading + * The generation configuration to use as the base parameters for the generation call. + * `**kwargs` passed to generate that match attributes of `generation_config` will override them. + * If `generation_config` is not provided, the default is used, with the following loading * priority: * - (1) from the `generation_config.json` model file, if it exists; - * - (2) from the model configuration. Please note that unspecified parameters will inherit [`GenerationConfig`]'s - * default values, whose documentation should be checked to parameterize generation. + * - (2) from the model configuration. Unspecified parameters inherit the default values documented by `GenerationConfig`. * @property {import('./logits_process.js').LogitsProcessorList} [logits_processor=null] (`LogitsProcessorList`, *optional*): * Custom logits processors that complement the default logits processors built from arguments and - * generation config. If a logit processor is passed that is already created with the arguments or a - * generation config an error is thrown. This feature is intended for advanced users. + * the generation config. If a logits processor duplicates one already created from the arguments or + * generation config, an error is thrown. This feature is intended for advanced users. * @property {import('./stopping_criteria.js').StoppingCriteria|import('./stopping_criteria.js').StoppingCriteria[]|import('./stopping_criteria.js').StoppingCriteriaList} [stopping_criteria=null] (`StoppingCriteriaList`, *optional*): - * Custom stopping criteria that complements the default stopping criteria built from arguments and a - * generation config. If a stopping criteria is passed that is already created with the arguments or a - * generation config an error is thrown. This feature is intended for advanced users. + * Custom stopping criteria that complement the default stopping criteria built from arguments and the + * generation config. If a stopping criterion duplicates one already created from the arguments or + * generation config, an error is thrown. This feature is intended for advanced users. * @property {import('./streamers.js').BaseStreamer} [streamer=null] (`BaseStreamer`, *optional*): - * Streamer object that will be used to stream the generated sequences. Generated tokens are passed + * Streamer object used to stream the generated sequences. Generated tokens are passed * through `streamer.put(token_ids)` and the streamer is responsible for any further processing. * @property {number[]|import('../utils/tensor.js').Tensor} [decoder_input_ids=null] (`number[]` or `Tensor`, *optional*): * If the model is an encoder-decoder model, this argument is used to pass the `decoder_input_ids`. diff --git a/packages/transformers/src/generation/stopping_criteria.js b/packages/transformers/src/generation/stopping_criteria.js index 4613b0dcd..56c1e2352 100644 --- a/packages/transformers/src/generation/stopping_criteria.js +++ b/packages/transformers/src/generation/stopping_criteria.js @@ -113,7 +113,7 @@ export class MaxLengthCriteria extends StoppingCriteria { export class EosTokenCriteria extends StoppingCriteria { /** * - * @param {number|number[]} eos_token_id The id of the *end-of-sequence* token. + * @param {number|number[]} eos_token_id The ID of the *end-of-sequence* token. * Optionally, use a list to set multiple *end-of-sequence* tokens. */ constructor(eos_token_id) { diff --git a/packages/transformers/src/pipelines/automatic-speech-recognition.js b/packages/transformers/src/pipelines/automatic-speech-recognition.js index acbb4a389..d28f69e8c 100644 --- a/packages/transformers/src/pipelines/automatic-speech-recognition.js +++ b/packages/transformers/src/pipelines/automatic-speech-recognition.js @@ -61,7 +61,7 @@ import { logger } from '../utils/logger.js'; * // { text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country." } * ``` * - * **Example:** Transcribe English w/ timestamps. + * **Example:** Transcribe English with timestamps. * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -77,7 +77,7 @@ import { logger } from '../utils/logger.js'; * // } * ``` * - * **Example:** Transcribe English w/ word-level timestamps. + * **Example:** Transcribe English with word-level timestamps. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/depth-estimation.js b/packages/transformers/src/pipelines/depth-estimation.js index 4dedc56a5..eb2abf2e3 100644 --- a/packages/transformers/src/pipelines/depth-estimation.js +++ b/packages/transformers/src/pipelines/depth-estimation.js @@ -33,7 +33,7 @@ import { interpolate_4d } from '../utils/tensor.js'; /** * Depth estimation pipeline using any `AutoModelForDepthEstimation`. This pipeline predicts the depth of an image. * - * **Example:** Depth estimation w/ `onnx-community/depth-anything-v2-small` + * **Example:** Depth estimation with `onnx-community/depth-anything-v2-small` * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/feature-extraction.js b/packages/transformers/src/pipelines/feature-extraction.js index ebc0149c7..f0c160fd3 100644 --- a/packages/transformers/src/pipelines/feature-extraction.js +++ b/packages/transformers/src/pipelines/feature-extraction.js @@ -14,8 +14,8 @@ import { Tensor, mean_pooling, quantize_embeddings } from '../utils/tensor.js'; /** * @typedef {Object} FeatureExtractionPipelineOptions Parameters specific to feature extraction pipelines. * @property {'none'|'mean'|'cls'|'first_token'|'eos'|'last_token'} [pooling="none"] The pooling method to use. - * @property {boolean} [normalize=false] Whether or not to normalize the embeddings in the last dimension. - * @property {boolean} [quantize=false] Whether or not to quantize the embeddings. + * @property {boolean} [normalize=false] Whether to normalize the embeddings in the last dimension. + * @property {boolean} [quantize=false] Whether to quantize the embeddings. * @property {'binary'|'ubinary'} [precision='binary'] The precision to use for quantization. * * @callback FeatureExtractionPipelineCallback Extract the features of the input(s). @@ -28,7 +28,7 @@ import { Tensor, mean_pooling, quantize_embeddings } from '../utils/tensor.js'; /** * Feature extraction pipeline using no model head. This pipeline extracts the hidden - * states from the base transformer, which can be used as features in downstream tasks. + * states from the base transformer for use as features in downstream tasks. * * **Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` (without pooling or normalization). * ```javascript diff --git a/packages/transformers/src/pipelines/image-feature-extraction.js b/packages/transformers/src/pipelines/image-feature-extraction.js index d362d6e01..ccc306d36 100644 --- a/packages/transformers/src/pipelines/image-feature-extraction.js +++ b/packages/transformers/src/pipelines/image-feature-extraction.js @@ -14,7 +14,7 @@ import { Tensor } from '../utils/tensor.js'; /** * @typedef {Object} ImageFeatureExtractionPipelineOptions Parameters specific to image feature extraction pipelines. - * @property {boolean} [pool=null] Whether or not to return the pooled output. If set to `false`, the model will return the raw hidden states. + * @property {boolean} [pool=null] Whether to return the pooled output. If set to `false`, the model will return the raw hidden states. * * @callback ImageFeatureExtractionPipelineCallback Extract the features of the input(s). * @param {ImagePipelineInputs} images One or several images (or one list of images) to get the features of. @@ -26,7 +26,7 @@ import { Tensor } from '../utils/tensor.js'; /** * Image feature extraction pipeline using no model head. This pipeline extracts the hidden - * states from the base transformer, which can be used as features in downstream tasks. + * states from the base transformer for use as features in downstream tasks. * * **Example:** Perform image feature extraction with `onnx-community/dinov3-vits16-pretrain-lvd1689m-ONNX`. * ```javascript diff --git a/packages/transformers/src/pipelines/image-segmentation.js b/packages/transformers/src/pipelines/image-segmentation.js index 91f2dde5a..bc61b1213 100644 --- a/packages/transformers/src/pipelines/image-segmentation.js +++ b/packages/transformers/src/pipelines/image-segmentation.js @@ -33,7 +33,7 @@ const SUBTASKS_MAPPING = { * @property {number} [overlap_mask_area_threshold=0.8] Mask overlap threshold to eliminate small, disconnected segments. * @property {null|string} [subtask=null] Segmentation task to be performed. One of [`panoptic`, `instance`, and `semantic`], * depending on model capabilities. If not set, the pipeline will attempt to resolve (in that order). - * @property {number[]} [label_ids_to_fuse=null] List of label ids to fuse. If not set, do not fuse any labels. + * @property {number[]} [label_ids_to_fuse=null] List of label IDs to fuse. If not set, do not fuse any labels. * @property {number[][]} [target_sizes=null] List of target sizes for the input images. If not set, use the original image sizes. * * @callback ImageSegmentationPipelineCallback Segment the input images. diff --git a/packages/transformers/src/pipelines/image-to-image.js b/packages/transformers/src/pipelines/image-to-image.js index 5da9818be..7ad800873 100644 --- a/packages/transformers/src/pipelines/image-to-image.js +++ b/packages/transformers/src/pipelines/image-to-image.js @@ -25,9 +25,9 @@ import { RawImage } from '../utils/image.js'; */ /** - * Image to Image pipeline using any `AutoModelForImageToImage`. This pipeline generates an image based on a previous image input. + * Image-to-image pipeline using any `AutoModelForImageToImage`. This pipeline generates an image based on a previous image input. * - * **Example:** Super-resolution w/ `Xenova/swin2SR-classical-sr-x2-64` + * **Example:** Super-resolution with `Xenova/swin2SR-classical-sr-x2-64` * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/image-to-text.js b/packages/transformers/src/pipelines/image-to-text.js index 7b0c677ae..b650b6fb1 100644 --- a/packages/transformers/src/pipelines/image-to-text.js +++ b/packages/transformers/src/pipelines/image-to-text.js @@ -30,9 +30,9 @@ import { Tensor } from '../utils/tensor.js'; */ /** - * Image To Text pipeline using a `AutoModelForVision2Seq`. This pipeline predicts a caption for a given image. + * Image-to-text pipeline using an `AutoModelForVision2Seq`. This pipeline predicts a caption for a given image. * - * **Example:** Generate a caption for an image w/ `Xenova/vit-gpt2-image-captioning`. + * **Example:** Generate a caption for an image with `Xenova/vit-gpt2-image-captioning`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -42,7 +42,7 @@ import { Tensor } from '../utils/tensor.js'; * // [{ generated_text: 'a cat laying on a couch with another cat' }] * ``` * - * **Example:** Optical Character Recognition (OCR) w/ `Xenova/trocr-small-handwritten`. + * **Example:** Optical Character Recognition (OCR) with `Xenova/trocr-small-handwritten`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/summarization.js b/packages/transformers/src/pipelines/summarization.js index 82ba829a3..0271cc7db 100644 --- a/packages/transformers/src/pipelines/summarization.js +++ b/packages/transformers/src/pipelines/summarization.js @@ -25,7 +25,7 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; /** * A pipeline for summarization tasks, inheriting from Text2TextGenerationPipeline. * - * **Example:** Summarization w/ `Xenova/distilbart-cnn-6-6`. + * **Example:** Summarization with `Xenova/distilbart-cnn-6-6`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/text-classification.js b/packages/transformers/src/pipelines/text-classification.js index fb85a139f..fc705fbcc 100644 --- a/packages/transformers/src/pipelines/text-classification.js +++ b/packages/transformers/src/pipelines/text-classification.js @@ -43,7 +43,7 @@ import { softmax } from '../utils/maths.js'; /** * Text classification pipeline using any `ModelForSequenceClassification`. * - * **Example:** Sentiment-analysis w/ `Xenova/distilbert-base-uncased-finetuned-sst-2-english`. + * **Example:** Sentiment analysis with `Xenova/distilbert-base-uncased-finetuned-sst-2-english`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -52,7 +52,7 @@ import { softmax } from '../utils/maths.js'; * // [{ label: 'POSITIVE', score: 0.999788761138916 }] * ``` * - * **Example:** Multilingual sentiment-analysis w/ `Xenova/bert-base-multilingual-uncased-sentiment` (and return top 5 classes). + * **Example:** Multilingual sentiment analysis with `Xenova/bert-base-multilingual-uncased-sentiment` (and return top 5 classes). * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -67,7 +67,7 @@ import { softmax } from '../utils/maths.js'; * // ] * ``` * - * **Example:** Toxic comment classification w/ `Xenova/toxic-bert` (and return all classes). + * **Example:** Toxic comment classification with `Xenova/toxic-bert` (and return all classes). * ```javascript * const classifier = await pipeline('text-classification', 'Xenova/toxic-bert'); * const output = await classifier('I hate you!', { top_k: null }); diff --git a/packages/transformers/src/pipelines/text-generation.js b/packages/transformers/src/pipelines/text-generation.js index 73d3e9fba..9a0f9b50f 100644 --- a/packages/transformers/src/pipelines/text-generation.js +++ b/packages/transformers/src/pipelines/text-generation.js @@ -30,7 +30,7 @@ function isChat(x) { * @typedef {TextGenerationSingle[]} TextGenerationOutput * * @typedef {Object} TextGenerationSpecificParams Parameters specific to text-generation pipelines. - * @property {boolean} [add_special_tokens] Whether or not to add special tokens when tokenizing the sequences. + * @property {boolean} [add_special_tokens] Whether to add special tokens when tokenizing the sequences. * @property {boolean} [return_full_text=true] If set to `false` only added text is returned, otherwise the full text is returned. * @property {Object[]|null} [tools=null] A list of tools to expose to chat templates that support tool use. * @property {Record<string, string>[]|null} [documents=null] A list of documents to expose to chat templates that support RAG. diff --git a/packages/transformers/src/pipelines/text2text-generation.js b/packages/transformers/src/pipelines/text2text-generation.js index ef2c822c9..5eeeef118 100644 --- a/packages/transformers/src/pipelines/text2text-generation.js +++ b/packages/transformers/src/pipelines/text2text-generation.js @@ -25,9 +25,9 @@ import { Tensor } from '../utils/tensor.js'; */ /** - * Text2TextGenerationPipeline class for generating text using a model that performs text-to-text generation tasks. + * Text-to-text generation pipeline using a model that performs text-to-text generation tasks. * - * **Example:** Text-to-text generation w/ `Xenova/LaMini-Flan-T5-783M`. + * **Example:** Text-to-text generation with `Xenova/LaMini-Flan-T5-783M`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/translation.js b/packages/transformers/src/pipelines/translation.js index 60eaf7c50..69245b546 100644 --- a/packages/transformers/src/pipelines/translation.js +++ b/packages/transformers/src/pipelines/translation.js @@ -25,7 +25,7 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; /** * Translates text from one language to another. * - * **Example:** Multilingual translation w/ `Xenova/nllb-200-distilled-600M`. + * **Example:** Multilingual translation with `Xenova/nllb-200-distilled-600M`. * * See [here](https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200) * for the full list of languages and their corresponding codes. @@ -41,7 +41,7 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; * // [{ translation_text: 'La vie est comme une boîte à chocolat.' }] * ``` * - * **Example:** Multilingual translation w/ `Xenova/m2m100_418M`. + * **Example:** Multilingual translation with `Xenova/m2m100_418M`. * * See [here](https://huggingface.co/facebook/m2m100_418M#languages-covered) * for the full list of languages and their corresponding codes. @@ -57,7 +57,7 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; * // [{ translation_text: 'Life is like a box of chocolate.' }] * ``` * - * **Example:** Multilingual translation w/ `Xenova/mbart-large-50-many-to-many-mmt`. + * **Example:** Multilingual translation with `Xenova/mbart-large-50-many-to-many-mmt`. * * See [here](https://huggingface.co/facebook/mbart-large-50-many-to-many-mmt#languages-covered) * for the full list of languages and their corresponding codes. diff --git a/packages/transformers/src/pipelines/zero-shot-audio-classification.js b/packages/transformers/src/pipelines/zero-shot-audio-classification.js index 8512dc3fd..07c957c9c 100644 --- a/packages/transformers/src/pipelines/zero-shot-audio-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-audio-classification.js @@ -37,7 +37,7 @@ import { softmax } from '../utils/maths.js'; */ /** - * Zero shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you + * Zero-shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you * provide an audio and a set of `candidate_labels`. * * **Example:** Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. diff --git a/packages/transformers/src/pipelines/zero-shot-classification.js b/packages/transformers/src/pipelines/zero-shot-classification.js index e4807d1e6..fa00bafc1 100644 --- a/packages/transformers/src/pipelines/zero-shot-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-classification.js @@ -21,7 +21,7 @@ import { logger } from '../utils/logger.js'; * @typedef {Object} ZeroShotClassificationPipelineOptions Parameters specific to zero-shot classification pipelines. * @property {string} [hypothesis_template="This example is {}."] The template used to turn each * candidate label into an NLI-style hypothesis. The candidate label will replace the {} placeholder. - * @property {boolean} [multi_label=false] Whether or not multiple candidate labels can be true. + * @property {boolean} [multi_label=false] Whether multiple candidate labels can be true. * If `false`, the scores are normalized such that the sum of the label likelihoods for each sequence * is 1. If `true`, the labels are considered independent and probabilities are normalized for each * candidate by doing a softmax of the entailment score vs. the contradiction score. @@ -44,7 +44,7 @@ import { logger } from '../utils/logger.js'; * pipelines, but these models don't require a hardcoded number of potential classes, they * can be chosen at runtime. It usually means it's slower but it is **much** more flexible. * - * **Example:** Zero shot classification with `Xenova/mobilebert-uncased-mnli`. + * **Example:** Zero-shot classification with `Xenova/mobilebert-uncased-mnli`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -59,7 +59,7 @@ import { logger } from '../utils/logger.js'; * // } * ``` * - * **Example:** Zero shot classification with `Xenova/nli-deberta-v3-xsmall` (multi-label). + * **Example:** Zero-shot classification with `Xenova/nli-deberta-v3-xsmall` (multi-label). * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/zero-shot-image-classification.js b/packages/transformers/src/pipelines/zero-shot-image-classification.js index a80309874..f432bc433 100644 --- a/packages/transformers/src/pipelines/zero-shot-image-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-image-classification.js @@ -38,10 +38,10 @@ import { softmax } from '../utils/maths.js'; */ /** - * Zero shot image classification pipeline. This pipeline predicts the class of + * Zero-shot image classification pipeline. This pipeline predicts the class of * an image when you provide an image and a set of `candidate_labels`. * - * **Example:** Zero shot image classification w/ `Xenova/clip-vit-base-patch32`. + * **Example:** Zero-shot image classification with `Xenova/clip-vit-base-patch32`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/zero-shot-object-detection.js b/packages/transformers/src/pipelines/zero-shot-object-detection.js index 6fdef0cba..badecd7eb 100644 --- a/packages/transformers/src/pipelines/zero-shot-object-detection.js +++ b/packages/transformers/src/pipelines/zero-shot-object-detection.js @@ -42,7 +42,7 @@ import { Pipeline, prepareImages, get_bounding_box } from './_base.js'; * Zero-shot object detection pipeline. This pipeline predicts bounding boxes of * objects when you provide an image and a set of `candidate_labels`. * - * **Example:** Zero-shot object detection w/ `Xenova/owlvit-base-patch32`. + * **Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -74,7 +74,7 @@ import { Pipeline, prepareImages, get_bounding_box } from './_base.js'; * // ] * ``` * - * **Example:** Zero-shot object detection w/ `Xenova/owlvit-base-patch32` (returning top 4 matches and setting a threshold). + * **Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32` (returning top 4 matches and setting a threshold). * ```javascript * import { pipeline } from '@huggingface/transformers'; * From af874d9e011007fe90d7666c200c82dd57b92c90 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 6 May 2026 18:18:33 -0400 Subject: [PATCH 060/103] improvements --- .ai/skills/transformers-js/references/TASKS.md | 12 ++++++------ .../transformers/docs/source/tutorials/vanilla-js.md | 2 +- .../src/pipelines/audio-classification.js | 4 ++-- packages/transformers/src/pipelines/fill-mask.js | 2 +- .../src/pipelines/image-classification.js | 4 ++-- .../transformers/src/pipelines/object-detection.js | 2 +- .../transformers/src/pipelines/question-answering.js | 2 +- .../src/pipelines/text-classification.js | 4 ++-- .../src/pipelines/token-classification.js | 2 +- .../src/pipelines/zero-shot-object-detection.js | 6 +++--- packages/transformers/src/utils/hub/utils.js | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index 2dec3aed0..58848140f 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -37,7 +37,7 @@ const output = await classifier(audio); // ] ``` -**Example:** Perform audio classification with `Xenova/ast-finetuned-audioset-10-10-0.4593` and return top 4 results. +**Example:** Perform audio classification with `Xenova/ast-finetuned-audioset-10-10-0.4593` and return the top 4 results. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -240,7 +240,7 @@ const output = await classifier(url); // ] ``` -**Example:** Classify an image and return top `n` classes. +**Example:** Classify an image and return the top `n` classes. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -394,7 +394,7 @@ const output = await detector(url, candidate_labels); // ] ``` -**Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32` (returning top 4 matches and setting a threshold). +**Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32` (returning the top 4 matches and setting a threshold). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -550,7 +550,7 @@ const output = await classifier('I love transformers!'); // [{ label: 'POSITIVE', score: 0.999788761138916 }] ``` -**Example:** Multilingual sentiment analysis with `Xenova/bert-base-multilingual-uncased-sentiment` (and return top 5 classes). +**Example:** Multilingual sentiment analysis with `Xenova/bert-base-multilingual-uncased-sentiment` (and return the top 5 classes). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -598,7 +598,7 @@ const output = await classifier('My name is Sarah and I live in London'); // ] ``` -**Example:** Perform named entity recognition with `Xenova/bert-base-NER` (and return all labels). +**Example:** Perform named entity recognition with `Xenova/bert-base-NER` (and return all entity labels). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -684,7 +684,7 @@ const output = await unmasker('The goal of life is [MASK].'); // ] ``` -**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return top result). +**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return the top result). ```javascript import { pipeline } from '@huggingface/transformers'; diff --git a/packages/transformers/docs/source/tutorials/vanilla-js.md b/packages/transformers/docs/source/tutorials/vanilla-js.md index 3d96acebf..c18dfe5cd 100644 --- a/packages/transformers/docs/source/tutorials/vanilla-js.md +++ b/packages/transformers/docs/source/tutorials/vanilla-js.md @@ -219,7 +219,7 @@ Once we’ve updated the `status` to "Analysing", we’re ready to perform _infe The second argument is an options object: - We set the `threshold` property to `0.5`. This means that we want the model to be at least 50% confident before claiming it has detected an object in the image. The lower the threshold, the more objects it'll detect (but may misidentify objects); the higher the threshold, the fewer objects it'll detect (but may miss objects in the scene). -- We also specify `percentage: true`, which means that we want the bounding box for the objects to be returned as percentages (instead of pixels). +- We also specify `percentage: true`, which means that bounding boxes are returned as percentages instead of pixels. If you now try to run the app and upload an image, you should see the following output logged to the console: diff --git a/packages/transformers/src/pipelines/audio-classification.js b/packages/transformers/src/pipelines/audio-classification.js index 95a1a3639..bc6fc3e50 100644 --- a/packages/transformers/src/pipelines/audio-classification.js +++ b/packages/transformers/src/pipelines/audio-classification.js @@ -20,7 +20,7 @@ import { softmax } from '../utils/maths.js'; * @typedef {AudioClassificationSingle[]} AudioClassificationOutput * * @typedef {Object} AudioClassificationPipelineOptions Parameters specific to audio classification pipelines. - * @property {number} [top_k=5] The number of top labels that will be returned by the pipeline. + * @property {number} [top_k=5] The number of top labels to return. * If the provided number is `null` or higher than the number of labels available in the model configuration, * it will default to the number of labels. * @@ -53,7 +53,7 @@ import { softmax } from '../utils/maths.js'; * // ] * ``` * - * **Example:** Perform audio classification with `Xenova/ast-finetuned-audioset-10-10-0.4593` and return top 4 results. + * **Example:** Perform audio classification with `Xenova/ast-finetuned-audioset-10-10-0.4593` and return the top 4 results. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/fill-mask.js b/packages/transformers/src/pipelines/fill-mask.js index b22278f43..0b2c58e16 100644 --- a/packages/transformers/src/pipelines/fill-mask.js +++ b/packages/transformers/src/pipelines/fill-mask.js @@ -68,7 +68,7 @@ import { softmax } from '../utils/maths.js'; * // ] * ``` * - * **Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return top result). + * **Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return the top result). * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/image-classification.js b/packages/transformers/src/pipelines/image-classification.js index bc1904fbf..2974add9b 100644 --- a/packages/transformers/src/pipelines/image-classification.js +++ b/packages/transformers/src/pipelines/image-classification.js @@ -20,7 +20,7 @@ import { softmax } from '../utils/maths.js'; * @typedef {ImageClassificationSingle[]} ImageClassificationOutput * * @typedef {Object} ImageClassificationPipelineOptions Parameters specific to image classification pipelines. - * @property {number} [top_k=1] The number of top labels that will be returned by the pipeline. + * @property {number} [top_k=1] The number of top labels to return. * * @typedef {ImagePipelineConstructorArgs & ImageClassificationPipelineCallback & Disposable} ImageClassificationPipelineType */ @@ -50,7 +50,7 @@ import { softmax } from '../utils/maths.js'; * // ] * ``` * - * **Example:** Classify an image and return top `n` classes. + * **Example:** Classify an image and return the top `n` classes. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/object-detection.js b/packages/transformers/src/pipelines/object-detection.js index 3c45c4f8b..990fd09bf 100644 --- a/packages/transformers/src/pipelines/object-detection.js +++ b/packages/transformers/src/pipelines/object-detection.js @@ -20,7 +20,7 @@ import { Pipeline, prepareImages, get_bounding_box } from './_base.js'; * * @typedef {Object} ObjectDetectionPipelineOptions Parameters specific to object detection pipelines. * @property {number} [threshold=0.9] The threshold used to filter boxes by score. - * @property {boolean} [percentage=false] Whether to return the boxes coordinates in percentage (true) or in pixels (false). + * @property {boolean} [percentage=false] Whether to return box coordinates as percentages (true) or pixels (false). * * @typedef {ImagePipelineConstructorArgs & ObjectDetectionPipelineCallback & Disposable} ObjectDetectionPipelineType */ diff --git a/packages/transformers/src/pipelines/question-answering.js b/packages/transformers/src/pipelines/question-answering.js index 254f15413..d58e0c0a6 100644 --- a/packages/transformers/src/pipelines/question-answering.js +++ b/packages/transformers/src/pipelines/question-answering.js @@ -20,7 +20,7 @@ import { softmax } from '../utils/maths.js'; * @property {string} answer The answer to the question. * * @typedef {Object} QuestionAnsweringPipelineOptions Parameters specific to question answering pipelines. - * @property {number} [top_k=1] The number of top answer predictions to be returned. + * @property {number} [top_k=1] The number of top answer predictions to return. * * @typedef {TextPipelineConstructorArgs & QuestionAnsweringPipelineCallback & Disposable} QuestionAnsweringPipelineType */ diff --git a/packages/transformers/src/pipelines/text-classification.js b/packages/transformers/src/pipelines/text-classification.js index fc705fbcc..7d91298ba 100644 --- a/packages/transformers/src/pipelines/text-classification.js +++ b/packages/transformers/src/pipelines/text-classification.js @@ -19,7 +19,7 @@ import { softmax } from '../utils/maths.js'; * @typedef {TextClassificationSingle[]} TextClassificationOutput * * @typedef {Object} TextClassificationPipelineOptions Parameters specific to text classification pipelines. - * @property {number|null} [top_k=1] The number of top predictions to be returned. If set to `null`, all predictions are returned. + * @property {number|null} [top_k=1] The number of top predictions to return. If set to `null`, all predictions are returned. */ /** @@ -52,7 +52,7 @@ import { softmax } from '../utils/maths.js'; * // [{ label: 'POSITIVE', score: 0.999788761138916 }] * ``` * - * **Example:** Multilingual sentiment analysis with `Xenova/bert-base-multilingual-uncased-sentiment` (and return top 5 classes). + * **Example:** Multilingual sentiment analysis with `Xenova/bert-base-multilingual-uncased-sentiment` (and return the top 5 classes). * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/token-classification.js b/packages/transformers/src/pipelines/token-classification.js index 6d3fffc4e..f9e3b7d8e 100644 --- a/packages/transformers/src/pipelines/token-classification.js +++ b/packages/transformers/src/pipelines/token-classification.js @@ -81,7 +81,7 @@ import { max, softmax } from '../utils/maths.js'; * // ] * ``` * - * **Example:** Perform named entity recognition with `Xenova/bert-base-NER` (and return all labels). + * **Example:** Perform named entity recognition with `Xenova/bert-base-NER` (and return all entity labels). * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/zero-shot-object-detection.js b/packages/transformers/src/pipelines/zero-shot-object-detection.js index badecd7eb..e3982ab33 100644 --- a/packages/transformers/src/pipelines/zero-shot-object-detection.js +++ b/packages/transformers/src/pipelines/zero-shot-object-detection.js @@ -21,10 +21,10 @@ import { Pipeline, prepareImages, get_bounding_box } from './_base.js'; * * @typedef {Object} ZeroShotObjectDetectionPipelineOptions Parameters specific to zero-shot object detection pipelines. * @property {number} [threshold=0.1] The probability necessary to make a prediction. - * @property {number} [top_k=null] The number of top predictions that will be returned by the pipeline. + * @property {number} [top_k=null] The number of top predictions to return. * If the provided number is `null` or higher than the number of predictions available, it will default * to the number of predictions. - * @property {boolean} [percentage=false] Whether to return the boxes coordinates in percentage (true) or in pixels (false). + * @property {boolean} [percentage=false] Whether to return box coordinates as percentages (true) or pixels (false). * * @typedef {TextImagePipelineConstructorArgs & ZeroShotObjectDetectionPipelineCallback & Disposable} ZeroShotObjectDetectionPipelineType */ @@ -74,7 +74,7 @@ import { Pipeline, prepareImages, get_bounding_box } from './_base.js'; * // ] * ``` * - * **Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32` (returning top 4 matches and setting a threshold). + * **Example:** Zero-shot object detection with `Xenova/owlvit-base-patch32` (returning the top 4 matches and setting a threshold). * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/utils/hub/utils.js b/packages/transformers/src/utils/hub/utils.js index e05ee27ee..ab58460ab 100644 --- a/packages/transformers/src/utils/hub/utils.js +++ b/packages/transformers/src/utils/hub/utils.js @@ -23,7 +23,7 @@ export function pathJoin(...parts) { /** * Determines whether the given string is a valid URL. - * @param {string|URL} string The string to test for validity as an URL. + * @param {string|URL} string The string to test for validity as a URL. * @param {string[]} [protocols=null] A list of valid protocols. If specified, the protocol must be in this list. * @param {string[]} [validHosts=null] A list of valid hostnames. If specified, the URL's hostname must be in this list. * @returns {boolean} True if the string is a valid URL, false otherwise. From a9f697ebfdf3929adb265b97bccf38a675b0878a Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 6 May 2026 18:42:59 -0400 Subject: [PATCH 061/103] improved wording --- .../references/PIPELINE_OPTIONS.md | 18 +++++++-------- .../src/generation/configuration_utils.js | 22 +++++++++---------- .../src/generation/logits_process.js | 8 +++---- .../src/pipelines/question-answering.js | 2 +- .../src/pipelines/text-to-audio.js | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 05394b7f7..6170cf702 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -150,14 +150,14 @@ anywhere `generate()` is invoked directly. | `min_length`? | `number` | The minimum length of the sequence to be generated. Corresponds to the length of the input prompt + `min_new_tokens`. Its effect is overridden by `min_new_tokens`, if also set. _(default: `0`)_ | | `min_new_tokens`? | `number` | The minimum number of tokens to generate, ignoring the number of tokens in the prompt. _(default: `null`)_ | | `early_stopping`? | `boolean\|"never"` | Controls the stopping condition for beam-based methods, like beam-search. It accepts the following values:<br />- `true`, where the generation stops as soon as there are `num_beams` complete candidates;<br />- `false`, where a heuristic is applied and the generation stops when it is very unlikely to find better candidates;<br />- `"never"`, where the beam search procedure only stops when there cannot be better candidates (canonical beam search algorithm). _(default: `false`)_ | -| `max_time`? | `number` | The maximum amount of time you allow the computation to run for in seconds. Generation will still finish the current pass after allocated time has been passed. _(default: `null`)_ | +| `max_time`? | `number` | The maximum time, in seconds, allowed for generation. Generation will still finish the current pass after the allocated time has passed. _(default: `null`)_ | | `do_sample`? | `boolean` | Whether to use sampling; use greedy decoding otherwise. _(default: `false`)_ | | `num_beams`? | `number` | Number of beams for beam search. 1 means no beam search. _(default: `1`)_ | -| `num_beam_groups`? | `number` | Number of groups to divide `num_beams` into in order to ensure diversity among different groups of beams. See [this paper](https://huggingface.co/papers/1610.02424) for more details. _(default: `1`)_ | +| `num_beam_groups`? | `number` | Number of groups to divide `num_beams` into to encourage diversity among different groups of beams. See [this paper](https://huggingface.co/papers/1610.02424) for more details. _(default: `1`)_ | | `penalty_alpha`? | `number` | The value balances model confidence and the degeneration penalty in contrastive search decoding. _(default: `null`)_ | | `use_cache`? | `boolean` | Whether the model should use the past key/value attentions (if applicable to the model) to speed up decoding. _(default: `true`)_ | | `temperature`? | `number` | The value used to modulate the next token probabilities. _(default: `1.0`)_ | -| `top_k`? | `number` | The number of highest probability vocabulary tokens to keep for top-k-filtering. _(default: `50`)_ | +| `top_k`? | `number` | The number of highest-probability vocabulary tokens to keep for top-k filtering. _(default: `50`)_ | | `top_p`? | `number` | If set to a float below 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or higher are kept for generation. _(default: `1.0`)_ | | `typical_p`? | `number` | Local typicality measures how similar the conditional probability of predicting a target token next is to the expected conditional probability of predicting a random token next, given the partial text already generated. If set to a float below 1, the smallest set of the most locally typical tokens with probabilities that add up to `typical_p` or higher are kept for generation. See [this paper](https://huggingface.co/papers/2202.00666) for more details. _(default: `1.0`)_ | | `epsilon_cutoff`? | `number` | If set to a float strictly between 0 and 1, only tokens with a conditional probability greater than `epsilon_cutoff` will be sampled. In the paper, suggested values range from 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language Model Desmoothing](https://huggingface.co/papers/2210.15191) for more details. _(default: `0.0`)_ | @@ -170,23 +170,23 @@ anywhere `generate()` is invoked directly. | `bad_words_ids`? | `number[][]` | List of token IDs that are not allowed to be generated. In order to get the token IDs of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | | `force_words_ids`? | `number[][]\|number[][][]` | List of token IDs that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | | `renormalize_logits`? | `boolean` | Whether to renormalize the logits after applying all the logits processors or warpers (including the custom ones). It's highly recommended to set this flag to `true` because search algorithms assume the score logits are normalized, but some logit processors or warpers break the normalization. _(default: `false`)_ | -| `constraints`? | `Object[]` | Custom constraints that can be added to the generation to ensure that the output will contain the use of certain tokens as defined by `Constraint` objects, in the most sensible way possible. _(default: `null`)_ | -| `forced_bos_token_id`? | `number` | The id of the token to force as the first generated token after the `decoder_start_token_id`. Useful for multilingual models like mBART where the first generated token needs to be the target language token. _(default: `null`)_ | -| `forced_eos_token_id`? | `number\|number[]` | The id of the token to force as the last generated token when `max_length` is reached. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | +| `constraints`? | `Object[]` | Custom constraints that guide generation to include certain tokens as defined by `Constraint` objects. _(default: `null`)_ | +| `forced_bos_token_id`? | `number` | The ID of the token to force as the first generated token after the `decoder_start_token_id`. Useful for multilingual models like mBART where the first generated token needs to be the target language token. _(default: `null`)_ | +| `forced_eos_token_id`? | `number\|number[]` | The ID of the token to force as the last generated token when `max_length` is reached. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | | `remove_invalid_values`? | `boolean` | Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method from crashing. Note that using `remove_invalid_values` can slow down generation. _(default: `false`)_ | | `exponential_decay_length_penalty`? | `[number, number]` | This tuple adds an exponentially increasing length penalty after a certain number of tokens have been generated. The tuple consists of: `(start_index, decay_factor)` where `start_index` indicates where the penalty starts and `decay_factor` represents the factor of exponential decay. _(default: `null`)_ | | `suppress_tokens`? | `number[]` | A list of tokens to suppress during generation. The `SuppressTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. _(default: `null`)_ | | `streamer`? | `TextStreamer` | A streamer used to stream the generation. _(default: `null`)_ | | `begin_suppress_tokens`? | `number[]` | A list of tokens to suppress at the beginning of the generation. The `SuppressBeginTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. _(default: `null`)_ | | `forced_decoder_ids`? | `[number, number][]` | A list of pairs of integers that indicates a mapping from generation indices to token indices that will be forced before sampling. For example, `[[1, 123]]` means the second generated token will always be a token of index 123. _(default: `null`)_ | -| `guidance_scale`? | `number` | The guidance scale for classifier free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages the model to generate samples that are more closely linked to the input prompt, usually at the expense of poorer quality. _(default: `null`)_ | +| `guidance_scale`? | `number` | The guidance scale for classifier-free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages the model to generate samples that are more closely tied to the input prompt, usually at the expense of poorer quality. _(default: `null`)_ | | `num_return_sequences`? | `number` | The number of independently computed returned sequences for each element in the batch. _(default: `1`)_ | | `output_attentions`? | `boolean` | Whether to return the attentions tensors of all attention layers. See `attentions` under returned tensors for more details. _(default: `false`)_ | | `output_hidden_states`? | `boolean` | Whether to return the hidden states of all layers. See `hidden_states` under returned tensors for more details. _(default: `false`)_ | | `output_scores`? | `boolean` | Whether to return the prediction scores. See `scores` under returned tensors for more details. _(default: `false`)_ | | `return_dict_in_generate`? | `boolean` | Whether to return a `ModelOutput` instead of a plain tuple. _(default: `false`)_ | -| `pad_token_id`? | `number` | The id of the *padding* token. _(default: `null`)_ | -| `bos_token_id`? | `number` | The id of the *beginning-of-sequence* token. _(default: `null`)_ | +| `pad_token_id`? | `number` | The ID of the *padding* token. _(default: `null`)_ | +| `bos_token_id`? | `number` | The ID of the *beginning-of-sequence* token. _(default: `null`)_ | | `eos_token_id`? | `number\|number[]` | The ID of the *end-of-sequence* token. Optionally, use a list to set multiple *end-of-sequence* tokens. _(default: `null`)_ | | `encoder_no_repeat_ngram_size`? | `number` | If set to an integer greater than 0, all n-grams of that size that occur in the `encoder_input_ids` cannot occur in the `decoder_input_ids`. _(default: `0`)_ | | `decoder_start_token_id`? | `number` | If an encoder-decoder model starts decoding with a token other than *bos*, the ID of that token. _(default: `null`)_ | diff --git a/packages/transformers/src/generation/configuration_utils.js b/packages/transformers/src/generation/configuration_utils.js index a7428b5fb..b0079f6ad 100644 --- a/packages/transformers/src/generation/configuration_utils.js +++ b/packages/transformers/src/generation/configuration_utils.js @@ -56,8 +56,8 @@ export class GenerationConfig { early_stopping = false; /** - * The maximum amount of time you allow the computation to run for in seconds. - * Generation will still finish the current pass after allocated time has been passed. + * The maximum time, in seconds, allowed for generation. + * Generation will still finish the current pass after the allocated time has passed. * @type {number} * @default null */ @@ -79,7 +79,7 @@ export class GenerationConfig { num_beams = 1; /** - * Number of groups to divide `num_beams` into in order to ensure diversity among different groups of beams. + * Number of groups to divide `num_beams` into to encourage diversity among different groups of beams. * See [this paper](https://huggingface.co/papers/1610.02424) for more details. * @type {number} * @default 1 @@ -109,7 +109,7 @@ export class GenerationConfig { temperature = 1.0; /** - * The number of highest probability vocabulary tokens to keep for top-k-filtering. + * The number of highest-probability vocabulary tokens to keep for top-k filtering. * @type {number} * @default 50 */ @@ -217,14 +217,14 @@ export class GenerationConfig { renormalize_logits = false; /** - * Custom constraints that can be added to the generation to ensure that the output will contain the use of certain tokens as defined by `Constraint` objects, in the most sensible way possible. + * Custom constraints that guide generation to include certain tokens as defined by `Constraint` objects. * @type {Object[]} * @default null */ constraints = null; /** - * The id of the token to force as the first generated token after the `decoder_start_token_id`. + * The ID of the token to force as the first generated token after the `decoder_start_token_id`. * Useful for multilingual models like mBART where the first generated token needs to be the target language token. * @type {number} * @default null @@ -232,7 +232,7 @@ export class GenerationConfig { forced_bos_token_id = null; /** - * The id of the token to force as the last generated token when `max_length` is reached. + * The ID of the token to force as the last generated token when `max_length` is reached. * Optionally, use a list to set multiple *end-of-sequence* tokens. * @type {number|number[]} * @default null @@ -285,8 +285,8 @@ export class GenerationConfig { forced_decoder_ids = null; /** - * The guidance scale for classifier free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. - * Higher guidance scale encourages the model to generate samples that are more closely linked to the input + * The guidance scale for classifier-free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. + * Higher guidance scale encourages the model to generate samples that are more closely tied to the input * prompt, usually at the expense of poorer quality. * @type {number} * @default null @@ -334,14 +334,14 @@ export class GenerationConfig { // Special tokens that can be used at generation time /** - * The id of the *padding* token. + * The ID of the *padding* token. * @type {number} * @default null */ pad_token_id = null; /** - * The id of the *beginning-of-sequence* token. + * The ID of the *beginning-of-sequence* token. * @type {number} * @default null */ diff --git a/packages/transformers/src/generation/logits_process.js b/packages/transformers/src/generation/logits_process.js index e6af0a671..330322b3e 100644 --- a/packages/transformers/src/generation/logits_process.js +++ b/packages/transformers/src/generation/logits_process.js @@ -617,7 +617,7 @@ export class NoBadWordsLogitsProcessor extends LogitsProcessor { } /** - * [`LogitsProcessor`] for classifier free guidance (CFG). The scores are split over the batch dimension, + * [`LogitsProcessor`] for classifier-free guidance (CFG). The scores are split over the batch dimension, * where the first half correspond to the conditional logits (predicted from the input prompt) and the second half * correspond to the unconditional logits (predicted from an empty or 'null' prompt). The processor computes a * weighted average across the conditional and unconditional logits, parameterised by the `guidance_scale`. @@ -627,15 +627,15 @@ export class NoBadWordsLogitsProcessor extends LogitsProcessor { export class ClassifierFreeGuidanceLogitsProcessor extends LogitsProcessor { /** * Create a `ClassifierFreeGuidanceLogitsProcessor`. - * @param {number} guidance_scale The guidance scale for classifier free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. - * Higher guidance scale encourages the model to generate samples that are more closely linked to the input + * @param {number} guidance_scale The guidance scale for classifier-free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. + * Higher guidance scale encourages the model to generate samples that are more closely tied to the input * prompt, usually at the expense of poorer quality. */ constructor(guidance_scale) { super(); if (guidance_scale <= 1) { throw new Error( - `Require guidance scale >1 to use the classifier free guidance processor, got guidance scale ${guidance_scale}.`, + `Require guidance scale >1 to use the classifier-free guidance processor, got guidance scale ${guidance_scale}.`, ); } this.guidance_scale = guidance_scale; diff --git a/packages/transformers/src/pipelines/question-answering.js b/packages/transformers/src/pipelines/question-answering.js index d58e0c0a6..dc04de320 100644 --- a/packages/transformers/src/pipelines/question-answering.js +++ b/packages/transformers/src/pipelines/question-answering.js @@ -14,7 +14,7 @@ import { softmax } from '../utils/maths.js'; /** * @typedef {Object} QuestionAnsweringOutput - * @property {number} score The probability associated to the answer. + * @property {number} score The probability associated with the answer. * @property {number} [start] The character start index of the answer (in the tokenized version of the input). * @property {number} [end] The character end index of the answer (in the tokenized version of the input). * @property {string} answer The answer to the question. diff --git a/packages/transformers/src/pipelines/text-to-audio.js b/packages/transformers/src/pipelines/text-to-audio.js index e36481a73..32a6b7309 100644 --- a/packages/transformers/src/pipelines/text-to-audio.js +++ b/packages/transformers/src/pipelines/text-to-audio.js @@ -142,7 +142,7 @@ export class TextToAudioPipeline } async _call(text_inputs, options) { - // If this.processor is not set, we are using a `AutoModelForTextToWaveform` model + // If this.processor is not set, we are using an `AutoModelForTextToWaveform` model if (this.processor) { return this._call_text_to_spectrogram(text_inputs, options); } else if (this.model.config.model_type === 'supertonic') { From 1d7318850f1950b113c3f598af6a55aad77373de Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 7 May 2026 13:28:19 -0400 Subject: [PATCH 062/103] improved wording --- .../references/CONFIGURATION.md | 8 ++-- .../references/PIPELINE_OPTIONS.md | 16 +++---- .../transformers-js/references/TASKS.md | 48 +++++++++---------- .../source/guides/node-audio-processing.md | 2 +- .../transformers/docs/source/guides/webgpu.md | 2 +- .../transformers/docs/source/pipelines.md | 14 +++--- .../docs/source/tutorials/next-ai-sdk.md | 6 +-- .../docs/source/tutorials/next.md | 2 +- .../docs/source/tutorials/node.md | 2 +- .../docs/source/tutorials/react.md | 16 +++---- .../docs/source/tutorials/vanilla-js.md | 14 +++--- packages/transformers/src/env.js | 9 ++-- packages/transformers/src/pipelines.js | 2 +- .../pipelines/automatic-speech-recognition.js | 2 +- .../src/pipelines/background-removal.js | 2 +- .../transformers/src/pipelines/fill-mask.js | 8 ++-- .../src/pipelines/image-segmentation.js | 2 +- .../src/pipelines/text-generation.js | 4 +- .../src/pipelines/text-to-audio.js | 2 +- .../transformers/src/pipelines/translation.js | 14 +++--- .../zero-shot-audio-classification.js | 8 ++-- .../src/pipelines/zero-shot-classification.js | 6 +-- .../transformers/src/tokenization_utils.js | 2 +- packages/transformers/src/utils/core.js | 10 ++-- packages/transformers/src/utils/hub.js | 9 ++-- packages/transformers/src/utils/random.js | 2 +- 26 files changed, 105 insertions(+), 107 deletions(-) diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index b90ba771d..f1b649cd4 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -32,7 +32,7 @@ env.cacheDir = '/path/to/cache/directory/'; | Option | Type | Description | |--------|------|-------------| | `version` | `string` | This version of Transformers.js. | -| `backends` | `object` | Expose environment variables of different backends, allowing users to set these variables if they want to. | +| `backends` | `object` | Exposes backend environment settings that users can override. | | `logLevel` | `number` | The logging level. Use LogLevel enum values. Defaults to LogLevel.WARNING. | | `allowRemoteModels` | `boolean` | Whether to allow loading of remote files, defaults to `true`. If set to `false`, it will have the same effect as setting `local_files_only=true` when loading pipelines, models, tokenizers, processors, etc. | | `remoteHost` | `string` | Host URL to load models from. Defaults to the Hugging Face Hub. | @@ -44,10 +44,10 @@ env.cacheDir = '/path/to/cache/directory/'; | `useFSCache` | `boolean` | Whether to use the file system to cache files. By default, it is `true` if available. | | `cacheDir` | `string\|null` | The directory to use for caching files with the file system. By default, it is `./.cache`. | | `useCustomCache` | `boolean` | Whether to use a custom cache system (defined by `customCache`), defaults to `false`. | -| `customCache` | `CacheInterface\|null` | The custom cache to use. Defaults to `null`. Note: this must be an object which implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. | +| `customCache` | `CacheInterface\|null` | The custom cache to use. Defaults to `null`. This must be an object that implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. | | `useWasmCache` | `boolean` | Whether to pre-load and cache WASM binaries and the WASM factory (.mjs) for ONNX Runtime. Defaults to `true` when cache is available. This can improve performance and enables offline usage by avoiding repeated downloads. | -| `cacheKey` | `string` | The cache key to use for storing models and WASM binaries. Defaults to 'transformers-cache'. | -| `experimental_useCrossOriginStorage` | `boolean` | Whether to use the Cross-Origin Storage API to cache model files across origins, allowing different sites to share the same cached model weights. Defaults to `false`. Requires the Cross-Origin Storage Chrome extension: https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih. The `experimental_` prefix indicates that the underlying browser API is not yet standardised and may change or be removed without a major version bump. For more information, see https://github.com/WICG/cross-origin-storage. | +| `cacheKey` | `string` | The cache key to use for storing models and WASM binaries. Defaults to `transformers-cache`. | +| `experimental_useCrossOriginStorage` | `boolean` | Whether to use the Cross-Origin Storage API to cache model files across origins, allowing different sites to share the same cached model weights. Defaults to `false`. Requires the Cross-Origin Storage Chrome extension: https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih. The `experimental_` prefix indicates that the underlying browser API is not yet standardized and may change or be removed without a major version bump. For more information, see https://github.com/WICG/cross-origin-storage. | | `fetch` | `(input: string \| URL, init?: any) => Promise<any>` | The fetch function to use. Defaults to `fetch`. | <!-- @generated:end id=typedef:TransformersEnvironment --> diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 6170cf702..c7afe2074 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -10,12 +10,12 @@ class in the [API reference](https://huggingface.co/docs/transformers.js/api/pip | Option | Type | Description | |--------|------|-------------| | `progress_callback`? | `ProgressCallback` | If specified, this function will be called during model construction, to provide the user with progress updates. _(default: `null`)_ | -| `config`? | `PretrainedConfig` | Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when:<br />- The model is a model provided by the library (loaded with the *model id* string of a pretrained model).<br />- The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | +| `config`? | `PretrainedConfig` | Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when:<br />- The model is provided by the library and loaded with the *model ID* string of a pretrained model.<br />- The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | | `cache_dir`? | `string` | Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. _(default: `null`)_ | | `local_files_only`? | `boolean` | Whether to only look at local files (e.g., not try downloading the model). _(default: `false`)_ | -| `revision`? | `string` | The specific model version to use. It can be a branch name, a tag name, or a commit id, since we use a git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git. NOTE: This setting is ignored for local requests. _(default: `'main'`)_ | +| `revision`? | `string` | The model revision to use. This can be a branch name, tag name, or commit ID. Because the Hub uses Git-based storage, `revision` can be any identifier accepted by Git. Ignored for local requests. _(default: `'main'`)_ | | `subfolder`? | `string` | In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can specify the folder name here. _(default: `'onnx'`)_ | -| `model_file_name`? | `string` | If specified, load the model with this name (excluding the dtype and .onnx suffixes). Currently only valid for encoder- or decoder-only models. _(default: `null`)_ | +| `model_file_name`? | `string` | If specified, load the model with this name (excluding the dtype and .onnx suffixes). This is currently valid only for encoder- or decoder-only models. _(default: `null`)_ | | `device`? | `DeviceType\|Record<string, DeviceType>` | The device to run the model on. If not specified, the device will be chosen from the environment settings. _(default: `null`)_ | | `dtype`? | `DataType\|Record<string, DataType>` | The data type to use for the model. If not specified, the data type will be chosen from the environment settings. _(default: `null`)_ | | `use_external_data_format`? | `ExternalData\|Record<string, ExternalData>` | Whether to load the model using the external data format (used for models >= 2GB in size). _(default: `false`)_ | @@ -42,7 +42,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| | `status` | `'initiate'` | A file load is about to start. | -| `name` | `string` | The model id or directory path. | +| `name` | `string` | The model ID or directory path. | | `file` | `string` | The name of the file. | **`DownloadProgressInfo`** @@ -50,7 +50,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| | `status` | `'download'` | A file download has started. | -| `name` | `string` | The model id or directory path. | +| `name` | `string` | The model ID or directory path. | | `file` | `string` | The name of the file. | **`ProgressStatusInfo`** @@ -58,7 +58,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| | `status` | `'progress'` | A file download has reported byte progress. | -| `name` | `string` | The model id or directory path. | +| `name` | `string` | The model ID or directory path. | | `file` | `string` | The name of the file. | | `progress` | `number` | A number between 0 and 100. | | `loaded` | `number` | The number of bytes loaded. | @@ -69,7 +69,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| | `status` | `'done'` | A file has finished loading. | -| `name` | `string` | The model id or directory path. | +| `name` | `string` | The model ID or directory path. | | `file` | `string` | The name of the file. | **`ReadyProgressInfo`** @@ -85,7 +85,7 @@ const pipe = await pipeline("sentiment-analysis", null, { | Option | Type | Description | |--------|------|-------------| | `status` | `'progress_total'` | Aggregate progress across all files being loaded. | -| `name` | `string` | The model id or directory path. | +| `name` | `string` | The model ID or directory path. | | `progress` | `number` | A number between 0 and 100. | | `loaded` | `number` | The number of bytes loaded. | | `total` | `number` | The total number of bytes to be loaded. | diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index 58848140f..f48d0d2cf 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -56,8 +56,8 @@ const output = await classifier(audio, { top_k: 4 }); **Default model:** `Xenova/clap-htsat-unfused` -Zero-shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you -provide an audio and a set of `candidate_labels`. +Zero-shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio clip from +audio and a set of `candidate_labels`. **Example:** Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. ```javascript @@ -65,11 +65,11 @@ import { pipeline } from '@huggingface/transformers'; const classifier = await pipeline('zero-shot-audio-classification', 'Xenova/clap-htsat-unfused'); const audio = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/dog_barking.wav'; -const candidate_labels = ['dog', 'vaccum cleaner']; +const candidate_labels = ['dog', 'vacuum cleaner']; const scores = await classifier(audio, candidate_labels); // [ // { score: 0.9993992447853088, label: 'dog' }, -// { score: 0.0006007603369653225, label: 'vaccum cleaner' } +// { score: 0.0006007603369653225, label: 'vacuum cleaner' } // ] ``` @@ -78,7 +78,7 @@ const scores = await classifier(audio, candidate_labels); **Default model:** `Xenova/whisper-tiny.en` **Aliases:** `asr` -Pipeline that aims at extracting spoken text contained within some audio. +Automatic speech recognition pipeline for transcribing spoken text from audio. **Example:** Transcribe English. ```javascript @@ -181,7 +181,7 @@ const output = await synthesizer('Hello there, how are you doing?', { speaker_em await output.save('output.wav'); // You can also use `output.toBlob()` to access the audio as a Blob ``` -**Example:** Multilingual speech generation with `Xenova/mms-tts-fra`. See [here](https://huggingface.co/models?pipeline_tag=text-to-speech&other=vits&sort=trending) for the full list of available languages (1107). +**Example:** Multilingual speech generation with `Xenova/mms-tts-fra`. See the [MMS-TTS model list](https://huggingface.co/models?pipeline_tag=text-to-speech&other=vits&sort=trending) for available languages. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -274,7 +274,7 @@ const output = await classifier(url, { top_k: 0 }); **Default model:** `Xenova/detr-resnet-50-panoptic` -Image segmentation pipeline using any `AutoModelForXXXSegmentation`. +Image segmentation pipeline using compatible segmentation models. This pipeline predicts masks of objects and their classes. **Example:** Perform image segmentation with `Xenova/detr-resnet-50-panoptic`. @@ -294,7 +294,7 @@ const output = await segmenter(url); **Default model:** `Xenova/modnet` -Background removal pipeline using certain `AutoModelForXXXSegmentation`. +Background removal pipeline using compatible image segmentation models. This pipeline removes the backgrounds of images. **Example:** Perform background removal with `Xenova/modnet`. @@ -652,9 +652,9 @@ const output = await answerer(question, context); **Default model:** `onnx-community/ettin-encoder-32m-ONNX` -Masked language modeling prediction pipeline using any `ModelWithLMHead`. +Masked language modeling prediction pipeline using compatible masked language models. -**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `onnx-community/ettin-encoder-32m-ONNX`. +**Example:** Perform masked language modeling (a.k.a. "fill-mask") with `onnx-community/ettin-encoder-32m-ONNX`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -669,7 +669,7 @@ const output = await unmasker('The capital of France is [MASK].'); // ] ``` -**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-uncased`. +**Example:** Perform masked language modeling (a.k.a. "fill-mask") with `Xenova/bert-base-cased`. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -684,7 +684,7 @@ const output = await unmasker('The goal of life is [MASK].'); // ] ``` -**Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return the top result). +**Example:** Perform masked language modeling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return the top result). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -726,8 +726,8 @@ Translates text from one language to another. **Example:** Multilingual translation with `Xenova/nllb-200-distilled-600M`. -See [here](https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200) -for the full list of languages and their corresponding codes. +See the [FLORES-200 language list](https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200) +for the available languages and their corresponding codes. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -742,8 +742,8 @@ const output = await translator('जीवन एक चॉकलेट बॉ **Example:** Multilingual translation with `Xenova/m2m100_418M`. -See [here](https://huggingface.co/facebook/m2m100_418M#languages-covered) -for the full list of languages and their corresponding codes. +See the [M2M100 language list](https://huggingface.co/facebook/m2m100_418M#languages-covered) +for the available languages and their corresponding codes. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -758,8 +758,8 @@ const output = await translator('生活就像一盒巧克力。', { **Example:** Multilingual translation with `Xenova/mbart-large-50-many-to-many-mmt`. -See [here](https://huggingface.co/facebook/mbart-large-50-many-to-many-mmt#languages-covered) -for the full list of languages and their corresponding codes. +See the [mBART-50 language list](https://huggingface.co/facebook/mbart-large-50-many-to-many-mmt#languages-covered) +for the available languages and their corresponding codes. ```javascript import { pipeline } from '@huggingface/transformers'; @@ -769,7 +769,7 @@ const output = await translator('संयुक्त राष्ट्र क src_lang: 'hi_IN', // Hindi tgt_lang: 'fr_XX', // French }); -// [{ translation_text: 'Le chef des Nations affirme qu 'il n 'y a military solution in Syria.' }] +// [{ translation_text: "Le chef de l'ONU affirme qu'il n'y a pas de solution militaire en Syrie." }] ``` ### `text2text-generation` @@ -793,9 +793,9 @@ const output = await generator('how can I become more healthy?', { **Default model:** `onnx-community/Qwen3-0.6B-ONNX` -Language generation pipeline using any `ModelWithLMHead` or `ModelForCausalLM`. +Language generation pipeline using compatible causal language models. This pipeline predicts the words that will follow a specified text prompt. -For the full list of generation parameters, see `GenerationConfig`. +For all generation parameters, see `GenerationConfig`. **Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). ```javascript @@ -838,9 +838,9 @@ console.log(output[0].generated_text.at(-1)?.content); **Default model:** `Xenova/distilbert-base-uncased-mnli` NLI-based zero-shot classification pipeline using a `ModelForSequenceClassification` -trained on NLI (natural language inference) tasks. Equivalent of `text-classification` -pipelines, but these models don't require a hardcoded number of potential classes, they -can be chosen at runtime. It usually means it's slower but it is **much** more flexible. +trained on NLI (natural language inference) tasks. This is similar to `text-classification` +pipelines, but these models do not require a hardcoded set of classes. Candidate classes can +be provided at runtime, making zero-shot classification slower but much more flexible. **Example:** Zero-shot classification with `Xenova/mobilebert-uncased-mnli`. ```javascript diff --git a/packages/transformers/docs/source/guides/node-audio-processing.md b/packages/transformers/docs/source/guides/node-audio-processing.md index a983b0b16..2de1f71b2 100644 --- a/packages/transformers/docs/source/guides/node-audio-processing.md +++ b/packages/transformers/docs/source/guides/node-audio-processing.md @@ -98,7 +98,7 @@ console.log(`Execution duration: ${(end - start) / 1000} seconds`); console.log(output); ``` -You can now run the application with `node index.js`. Note that when running the script for the first time, it may take a while to download and cache the model. Subsequent requests will use the cached model, and model loading will be much faster. +You can now run the application with `node index.js`. Note that when running the script for the first time, it may take a while to download and cache the model. Subsequent runs will use the cached model, so model loading will be much faster. You should see output similar to: diff --git a/packages/transformers/docs/source/guides/webgpu.md b/packages/transformers/docs/source/guides/webgpu.md index 964049268..db1d29589 100644 --- a/packages/transformers/docs/source/guides/webgpu.md +++ b/packages/transformers/docs/source/guides/webgpu.md @@ -1,6 +1,6 @@ # Running models on WebGPU -WebGPU is a new web standard for accelerated graphics and compute. The [API](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) enables web developers to use the underlying system's GPU to carry out high-performance computations directly in the browser. WebGPU is the successor to [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) and provides significantly better performance, because it allows for more direct interaction with modern GPUs. Lastly, it supports general-purpose GPU computations, which makes it just perfect for machine learning! +WebGPU is a web standard for accelerated graphics and compute. The [API](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) enables web developers to use the underlying system's GPU to carry out high-performance computations directly in the browser. WebGPU is the successor to [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) and provides significantly better performance, because it allows for more direct interaction with modern GPUs. It also supports general-purpose GPU computation, making it well suited for machine learning. > [!WARNING] > As of March 2026, global WebGPU support is around 85% (according to [caniuse.com](https://caniuse.com/webgpu)), meaning some users may not be able to use the API. diff --git a/packages/transformers/docs/source/pipelines.md b/packages/transformers/docs/source/pipelines.md index 227d030f3..766c90c51 100644 --- a/packages/transformers/docs/source/pipelines.md +++ b/packages/transformers/docs/source/pipelines.md @@ -4,7 +4,7 @@ Just like the [transformers Python library](https://github.com/huggingface/trans <Tip> -For the full list of available tasks/pipelines, check out [this table](#available-tasks). +For the full list of available tasks and pipelines, see [this table](#available-tasks). </Tip> @@ -22,7 +22,7 @@ When running for the first time, the `pipeline` will download and cache the defa <Tip> -By default, models will be downloaded from the [Hugging Face Hub](https://huggingface.co/models) and stored in [browser cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache), but there are ways to specify custom models and cache locations. For more information see [here](./custom_usage). +By default, models will be downloaded from the [Hugging Face Hub](https://huggingface.co/models) and stored in [browser cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache), but there are ways to specify custom models and cache locations. For more information, see the [custom usage guide](./custom_usage). </Tip> @@ -81,7 +81,7 @@ const result = await transcriber( We offer a variety of options to control how models are loaded from the Hugging Face Hub (or locally). By default, when running in-browser, a _quantized_ version of the model is used, which is smaller and faster, -but usually less accurate. To override this behaviour (i.e., use the unquantized model), you can use a custom +but usually less accurate. To override this behavior (i.e., use the unquantized model), you can use a custom `PretrainedOptions` object as the third parameter to the `pipeline` function: ```javascript @@ -91,7 +91,7 @@ const pipe = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", { }); ``` -Check out the section on [quantization](./guides/dtypes) to learn more. +See the section on [quantization](./guides/dtypes) to learn more. You can also specify which revision of the model to use, by passing a `revision` parameter. Since the Hugging Face Hub uses a git-based versioning system, you can use any valid git revision specifier (e.g., branch name or commit hash). @@ -106,7 +106,7 @@ const transcriber = await pipeline( ); ``` -For the full list of options, check out the [PretrainedOptions](./api/utils/hub#module_utils/hub.PretrainedOptions) documentation. +For the full list of options, see the [PretrainedOptions](./api/utils/hub#module_utils/hub.PretrainedOptions) documentation. ### Runtime parameters @@ -134,7 +134,7 @@ const result2 = await translator(result[0].translation_text, { // [ { translation_text: 'I like to walk my dog.' } ] ``` -When using models that support auto-regressive generation, you can specify generation parameters like the number of new tokens, sampling methods, temperature, repetition penalty, and much more. For a full list of available parameters, see the [GenerationConfig](./api/generation/configuration_utils#module_generation/configuration_utils.GenerationConfig) class. +When using models that support auto-regressive generation, you can specify generation parameters like the number of new tokens, sampling methods, temperature, and repetition penalty. For a full list of available parameters, see the [GenerationConfig](./api/generation/configuration_utils#module_generation/configuration_utils.GenerationConfig) class. For example, to generate a poem using `LaMini-Flan-T5-783M`, you can do: @@ -230,7 +230,7 @@ This approach ensures that each recursive call reduces the problem size by half </pre> </details> -This streaming feature allows you to process the output as it is generated, rather than waiting for the entire output to be generated before processing it. +Streaming lets you process output as it is generated, instead of waiting for the complete output before processing it. For more information on the available options for each pipeline, refer to the [API Reference](./api/pipelines). If you would like more control over the inference process, you can use the [`AutoModel`](./api/models), [`AutoTokenizer`](./api/tokenizers), or [`AutoProcessor`](./api/processors) classes instead. diff --git a/packages/transformers/docs/source/tutorials/next-ai-sdk.md b/packages/transformers/docs/source/tutorials/next-ai-sdk.md index 09f534a91..f09b62d75 100644 --- a/packages/transformers/docs/source/tutorials/next-ai-sdk.md +++ b/packages/transformers/docs/source/tutorials/next-ai-sdk.md @@ -30,7 +30,7 @@ npm install ai @ai-sdk/react @browser-ai/transformers-js @huggingface/transforme ## Step 2: Configure Next.js for browser inference -Transformers.js uses ONNX Runtime under the hood for both browser and server-side (Node.js) inference. In our case we only need the browser runtime so we can tell Next.js to exclude the Node.js-specific packages when bundling for the browser. Update `next.config.ts` +Transformers.js uses ONNX Runtime under the hood for both browser and server-side (Node.js) inference. In this case, we only need the browser runtime, so we can tell Next.js to exclude Node.js-specific packages when bundling for the browser. Update `next.config.ts`: ```typescript import type { NextConfig } from "next"; @@ -53,7 +53,7 @@ export default nextConfig; ## Step 3: Create the Web Worker -Running model inference on the main thread would block the UI. The `@browser-ai/transformers-js` package provides a ready-made worker handler that handles all the complexity for you. +Running model inference on the main thread would block the UI. The `@browser-ai/transformers-js` package provides a ready-made worker handler for model loading, inference, streaming, and main-thread communication. Create `src/app/worker.ts`: @@ -101,7 +101,7 @@ export const MODELS: ModelConfig[] = [ <Tip> -For tool calling, use reasoning models like Qwen3 which handle multi-step reasoning well, or fine-tuned model specifically for tool-calling capabilities. The `supportsWorker` flag controls whether the model is loaded in a Web Worker for better performance. +For tool calling, use reasoning models like Qwen3 which handle multi-step reasoning well, or a model fine-tuned specifically for tool calling. The `supportsWorker` flag controls whether the model is loaded in a Web Worker for better performance. </Tip> diff --git a/packages/transformers/docs/source/tutorials/next.md b/packages/transformers/docs/source/tutorials/next.md index 80d74c19a..807685e9b 100644 --- a/packages/transformers/docs/source/tutorials/next.md +++ b/packages/transformers/docs/source/tutorials/next.md @@ -216,7 +216,7 @@ Visit the URL shown in the terminal (e.g., [http://localhost:3000/](http://local ### (Optional) Step 4: Build and deploy -To build your application, simply run: +To build your application, run: ```bash npm run build diff --git a/packages/transformers/docs/source/tutorials/node.md b/packages/transformers/docs/source/tutorials/node.md index f43247ef4..759c0eb30 100644 --- a/packages/transformers/docs/source/tutorials/node.md +++ b/packages/transformers/docs/source/tutorials/node.md @@ -7,7 +7,7 @@ We'll also show you how to use the library in both CommonJS and ECMAScript modul - [ECMAScript modules (ESM)](#ecmascript-modules-esm) - The official standard format to package JavaScript code for reuse. It's the default module system in modern browsers, with modules imported using `import` and exported using `export`. - Fortunately, starting with version 13.2.0, Node.js has stable support of ES modules. + Fortunately, starting with version 13.2.0, Node.js has stable support for ES modules. - [CommonJS](#commonjs) - The default module system in Node.js. In this system, modules are imported using `require()` and exported using `module.exports`. diff --git a/packages/transformers/docs/source/tutorials/react.md b/packages/transformers/docs/source/tutorials/react.md index b2d18f27c..fb90ed076 100644 --- a/packages/transformers/docs/source/tutorials/react.md +++ b/packages/transformers/docs/source/tutorials/react.md @@ -14,9 +14,9 @@ Useful links: - [Node.js](https://nodejs.org/en/) version 18+ - [npm](https://www.npmjs.com/) version 9+ -## Step 1: Initialise the project +## Step 1: Initialize the project -For this tutorial, we will use [Vite](https://vitejs.dev/) to initialise our project. Vite is a build tool that allows us to quickly set up a React application with minimal configuration. Run the following command in your terminal: +For this tutorial, we will use [Vite](https://vitejs.dev/) to initialize our project. Vite is a build tool that quickly sets up a React application with minimal configuration. Run the following command in your terminal: ```bash npm create vite@latest react-translator -- --template react @@ -50,12 +50,12 @@ npm install @huggingface/transformers For this application, we will use the [Xenova/nllb-200-distilled-600M](https://huggingface.co/Xenova/nllb-200-distilled-600M) model, which can perform multilingual translation among 200 languages. Before we start, there are 2 things we need to take note of: -1. ML inference can be quite computationally intensive, so it's better to load and run the models in a separate thread from the main (UI) thread. -2. Since the model is quite large (>1 GB), we don't want to download it until the user clicks the "Translate" button. +1. ML inference can be computationally intensive, so it's better to load and run the models in a separate thread from the main (UI) thread. +2. Since the model is larger than 1 GB, we don't want to download it until the user clicks the "Translate" button. We can achieve both of these goals by using a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) and some [React hooks](https://react.dev/reference/react). -1. Create a file called `worker.js` in the `src` directory. This script will do all the heavy-lifting for us, including loading and running the translation pipeline. To ensure the model is only loaded once, we will create the `MyTranslationPipeline` class, which uses the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create a single instance of the pipeline when `getInstance` is first called, and use this pipeline for all subsequent calls: +1. Create a file called `worker.js` in the `src` directory. This script will load and run the translation pipeline. To ensure the model is only loaded once, we will create the `MyTranslationPipeline` class, which uses the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create one pipeline instance when `getInstance` is first called, and reuse it for all subsequent calls: ```javascript import { pipeline, TextStreamer } from "@huggingface/transformers"; @@ -83,7 +83,7 @@ We can achieve both of these goals by using a [Web Worker](https://developer.moz // Create a reference to the worker object. const worker = useRef(null); - // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted. + // We use the `useEffect` hook to set up the worker as soon as the `App` component is mounted. useEffect(() => { // Create the worker if it does not yet exist. worker.current ??= new Worker(new URL('./worker.js', import.meta.url), { @@ -122,7 +122,7 @@ We recommend starting the development server again with `npm run dev` First, let's define our components. Create a folder called `components` in the `src` directory, and create the following files: -1. `LanguageSelector.jsx`: This component will allow the user to select the input and output languages. Check out the full list of languages [here](https://github.com/huggingface/transformers.js-examples/tree/main/react-translator/src/components/LanguageSelector.jsx). +1. `LanguageSelector.jsx`: This component will allow the user to select the input and output languages. See the [language list](https://github.com/huggingface/transformers.js-examples/tree/main/react-translator/src/components/LanguageSelector.jsx) used by the example app. ```jsx const LANGUAGES = { @@ -514,7 +514,7 @@ You can now run the application with `npm run dev` and perform multilingual tran ## (Optional) Step 5: Build and deploy -To build your application, simply run `npm run build`. This will bundle your application and output the static files to the `dist` folder. +To build your application, run `npm run build`. This will bundle your application and output the static files to the `dist` folder. For this demo, we will deploy our application as a static [Hugging Face Space](https://huggingface.co/docs/hub/spaces), but you can deploy it anywhere you like! If you haven't already, you can create a free Hugging Face account [here](https://huggingface.co/join). diff --git a/packages/transformers/docs/source/tutorials/vanilla-js.md b/packages/transformers/docs/source/tutorials/vanilla-js.md index c18dfe5cd..8dcebcb26 100644 --- a/packages/transformers/docs/source/tutorials/vanilla-js.md +++ b/packages/transformers/docs/source/tutorials/vanilla-js.md @@ -2,7 +2,7 @@ In this tutorial, you’ll build a simple web application that detects objects in images using Transformers.js! To follow along, all you need is a code editor, a browser, and a simple server (e.g., VS Code Live Server). -Here's how it works: the user clicks “Upload image” and selects an image using an input dialog. After analysing the image with an object detection model, the predicted bounding boxes are overlaid on top of the image, like this: +Here's how it works: the user clicks “Upload image” and selects an image using an input dialog. After analyzing the image with an object detection model, the predicted bounding boxes are overlaid on top of the image, like this: ![Demo](https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/js-detection-interence-zebra.png) @@ -34,7 +34,7 @@ Before we start building with Transformers.js, we first need to lay the groundwo <summary>Click here to see a breakdown of this markup.</summary> -We’re adding an `<input>` element with `type="file"` that accepts images. This allows the user to select an image from their local file system using a popup dialog. The default styling for this element looks quite bad, so let's add some styling. The easiest way to achieve this is to wrap the `<input>` element in a `<label>`, hide the input, and then style the label as a button. +We’re adding an `<input>` element with `type="file"` that accepts images. This lets the user select an image from their local file system using a popup dialog. The browser's default file input is hard to style directly, so we'll wrap it in a `<label>`, hide the input, and style the label as a button. We’re also adding an empty `<div>` container for displaying the image, plus an empty `<p>` tag that we'll use to give status updates to the user while we download and run the model, since both of these operations take some time. @@ -150,7 +150,7 @@ const detector = await pipeline("object-detection", "Xenova/detr-resnet-50"); We’re passing two arguments into the `pipeline()` function: (1) task and (2) model. -1. The first tells Transformers.js what kind of task we want to perform. In our case, that is `object-detection`, but there are many other tasks that the library supports, including `text-generation`, `sentiment-analysis`, `summarization`, or `automatic-speech-recognition`. See [here](https://huggingface.co/docs/transformers.js/pipelines#tasks) for the full list. +1. The first tells Transformers.js what kind of task we want to perform. In our case, that is `object-detection`, but there are many other tasks that the library supports, including `text-generation`, `sentiment-analysis`, `summarization`, or `automatic-speech-recognition`. See the [task list](https://huggingface.co/docs/transformers.js/pipelines#tasks) for all supported pipelines. 2. The second argument specifies which model we would like to use to solve the given task. We will use [`Xenova/detr-resnet-50`](https://huggingface.co/Xenova/detr-resnet-50), as it is a relatively small (~40MB) but powerful model for detecting objects in an image. @@ -162,7 +162,7 @@ status.textContent = "Ready"; ## Step 4: Create the image uploader -The next step is to support uploading/selection of images. To achieve this, we will listen for "change" events from the `fileUpload` element. In the callback function, we use a `FileReader()` to read the contents of the image if one is selected (and nothing otherwise). +The next step is to support image uploads and selection. To achieve this, we will listen for "change" events from the `fileUpload` element. In the callback function, we use a `FileReader()` to read the contents of the image if one is selected (and nothing otherwise). ```js fileUpload.addEventListener("change", function (e) { @@ -197,7 +197,7 @@ We’re finally ready to start interacting with Transformers.js! Let’s uncomme ```js async function detect(img) { - status.textContent = "Analysing..."; + status.textContent = "Analyzing..."; const output = await detector(img.src, { threshold: 0.5, percentage: true, @@ -210,11 +210,11 @@ async function detect(img) { <Tip> -NOTE: The `detect` function needs to be asynchronous, since we’ll `await` the result of the model. +The `detect` function needs to be asynchronous, since we’ll `await` the result of the model. </Tip> -Once we’ve updated the `status` to "Analysing", we’re ready to perform _inference_, which simply means to run the model with some data. This is done via the `detector()` function that was returned from `pipeline()`. The first argument we’re passing is the image data (`img.src`). +Once we’ve updated the `status` to "Analyzing", we’re ready to perform _inference_, which means running the model on input data. This is done via the `detector()` function that was returned from `pipeline()`. The first argument we’re passing is the image data (`img.src`). The second argument is an options object: diff --git a/packages/transformers/src/env.js b/packages/transformers/src/env.js index 5de38d6e7..845e4478e 100644 --- a/packages/transformers/src/env.js +++ b/packages/transformers/src/env.js @@ -200,8 +200,7 @@ export const LogLevel = Object.freeze({ * Shape of the `env` object. Every field is mutable. * @typedef {Object} TransformersEnvironment * @property {string} version This version of Transformers.js. - * @property {{onnx: Partial<import('onnxruntime-common').Env> & { setLogLevel?: (logLevel: number) => void }}} backends Expose environment variables of different backends, - * allowing users to set these variables if they want to. + * @property {{onnx: Partial<import('onnxruntime-common').Env> & { setLogLevel?: (logLevel: number) => void }}} backends Exposes backend environment settings that users can override. * @property {number} logLevel The logging level. Use LogLevel enum values. Defaults to LogLevel.WARNING. * @property {boolean} allowRemoteModels Whether to allow loading of remote files, defaults to `true`. * If set to `false`, it will have the same effect as setting `local_files_only=true` when loading pipelines, models, tokenizers, processors, etc. @@ -215,15 +214,15 @@ export const LogLevel = Object.freeze({ * @property {boolean} useFSCache Whether to use the file system to cache files. By default, it is `true` if available. * @property {string|null} cacheDir The directory to use for caching files with the file system. By default, it is `./.cache`. * @property {boolean} useCustomCache Whether to use a custom cache system (defined by `customCache`), defaults to `false`. - * @property {import('./utils/cache.js').CacheInterface|null} customCache The custom cache to use. Defaults to `null`. Note: this must be an object which + * @property {import('./utils/cache.js').CacheInterface|null} customCache The custom cache to use. Defaults to `null`. This must be an object that * implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. * @property {boolean} useWasmCache Whether to pre-load and cache WASM binaries and the WASM factory (.mjs) for ONNX Runtime. * Defaults to `true` when cache is available. This can improve performance and enables offline usage by avoiding repeated downloads. - * @property {string} cacheKey The cache key to use for storing models and WASM binaries. Defaults to 'transformers-cache'. + * @property {string} cacheKey The cache key to use for storing models and WASM binaries. Defaults to `transformers-cache`. * @property {boolean} experimental_useCrossOriginStorage Whether to use the Cross-Origin Storage API to cache model files * across origins, allowing different sites to share the same cached model weights. Defaults to `false`. * Requires the Cross-Origin Storage Chrome extension: {@link https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih}. - * The `experimental_` prefix indicates that the underlying browser API is not yet standardised and may change or be + * The `experimental_` prefix indicates that the underlying browser API is not yet standardized and may change or be * removed without a major version bump. For more information, see {@link https://github.com/WICG/cross-origin-storage}. * @property {(input: string | URL, init?: any) => Promise<any>} fetch The fetch function to use. Defaults to `fetch`. */ diff --git a/packages/transformers/src/pipelines.js b/packages/transformers/src/pipelines.js index 98c10c692..ea560cc6b 100644 --- a/packages/transformers/src/pipelines.js +++ b/packages/transformers/src/pipelines.js @@ -1,5 +1,5 @@ /** - * @file Pipelines provide a high-level, easy to use, API for running machine learning models. + * @file Pipelines provide a high-level, easy-to-use API for running machine learning models. * * **Example:** Instantiate pipeline using the `pipeline` function. * ```javascript diff --git a/packages/transformers/src/pipelines/automatic-speech-recognition.js b/packages/transformers/src/pipelines/automatic-speech-recognition.js index d28f69e8c..f18c8680b 100644 --- a/packages/transformers/src/pipelines/automatic-speech-recognition.js +++ b/packages/transformers/src/pipelines/automatic-speech-recognition.js @@ -49,7 +49,7 @@ import { logger } from '../utils/logger.js'; */ /** - * Pipeline that aims at extracting spoken text contained within some audio. + * Automatic speech recognition pipeline for transcribing spoken text from audio. * * **Example:** Transcribe English. * ```javascript diff --git a/packages/transformers/src/pipelines/background-removal.js b/packages/transformers/src/pipelines/background-removal.js index 6e27b6c82..637fe3839 100644 --- a/packages/transformers/src/pipelines/background-removal.js +++ b/packages/transformers/src/pipelines/background-removal.js @@ -28,7 +28,7 @@ import { RawImage } from '../utils/image.js'; */ /** - * Background removal pipeline using certain `AutoModelForXXXSegmentation`. + * Background removal pipeline using compatible image segmentation models. * This pipeline removes the backgrounds of images. * * **Example:** Perform background removal with `Xenova/modnet`. diff --git a/packages/transformers/src/pipelines/fill-mask.js b/packages/transformers/src/pipelines/fill-mask.js index 0b2c58e16..1628153f7 100644 --- a/packages/transformers/src/pipelines/fill-mask.js +++ b/packages/transformers/src/pipelines/fill-mask.js @@ -36,9 +36,9 @@ import { softmax } from '../utils/maths.js'; */ /** - * Masked language modeling prediction pipeline using any `ModelWithLMHead`. + * Masked language modeling prediction pipeline using compatible masked language models. * - * **Example:** Perform masked language modelling (a.k.a. "fill-mask") with `onnx-community/ettin-encoder-32m-ONNX`. + * **Example:** Perform masked language modeling (a.k.a. "fill-mask") with `onnx-community/ettin-encoder-32m-ONNX`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -53,7 +53,7 @@ import { softmax } from '../utils/maths.js'; * // ] * ``` * - * **Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-uncased`. + * **Example:** Perform masked language modeling (a.k.a. "fill-mask") with `Xenova/bert-base-cased`. * ```javascript * import { pipeline } from '@huggingface/transformers'; * @@ -68,7 +68,7 @@ import { softmax } from '../utils/maths.js'; * // ] * ``` * - * **Example:** Perform masked language modelling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return the top result). + * **Example:** Perform masked language modeling (a.k.a. "fill-mask") with `Xenova/bert-base-cased` (and return the top result). * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/image-segmentation.js b/packages/transformers/src/pipelines/image-segmentation.js index bc61b1213..374fa9ddb 100644 --- a/packages/transformers/src/pipelines/image-segmentation.js +++ b/packages/transformers/src/pipelines/image-segmentation.js @@ -45,7 +45,7 @@ const SUBTASKS_MAPPING = { */ /** - * Image segmentation pipeline using any `AutoModelForXXXSegmentation`. + * Image segmentation pipeline using compatible segmentation models. * This pipeline predicts masks of objects and their classes. * * **Example:** Perform image segmentation with `Xenova/detr-resnet-50-panoptic`. diff --git a/packages/transformers/src/pipelines/text-generation.js b/packages/transformers/src/pipelines/text-generation.js index 9a0f9b50f..c668ee17b 100644 --- a/packages/transformers/src/pipelines/text-generation.js +++ b/packages/transformers/src/pipelines/text-generation.js @@ -52,9 +52,9 @@ function isChat(x) { */ /** - * Language generation pipeline using any `ModelWithLMHead` or `ModelForCausalLM`. + * Language generation pipeline using compatible causal language models. * This pipeline predicts the words that will follow a specified text prompt. - * For the full list of generation parameters, see `GenerationConfig`. + * For all generation parameters, see `GenerationConfig`. * * **Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). * ```javascript diff --git a/packages/transformers/src/pipelines/text-to-audio.js b/packages/transformers/src/pipelines/text-to-audio.js index 32a6b7309..8d4513683 100644 --- a/packages/transformers/src/pipelines/text-to-audio.js +++ b/packages/transformers/src/pipelines/text-to-audio.js @@ -63,7 +63,7 @@ import { env } from '../env.js'; * await output.save('output.wav'); // You can also use `output.toBlob()` to access the audio as a Blob * ``` * - * **Example:** Multilingual speech generation with `Xenova/mms-tts-fra`. See [here](https://huggingface.co/models?pipeline_tag=text-to-speech&other=vits&sort=trending) for the full list of available languages (1107). + * **Example:** Multilingual speech generation with `Xenova/mms-tts-fra`. See the [MMS-TTS model list](https://huggingface.co/models?pipeline_tag=text-to-speech&other=vits&sort=trending) for available languages. * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/translation.js b/packages/transformers/src/pipelines/translation.js index 69245b546..56700cd19 100644 --- a/packages/transformers/src/pipelines/translation.js +++ b/packages/transformers/src/pipelines/translation.js @@ -27,8 +27,8 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; * * **Example:** Multilingual translation with `Xenova/nllb-200-distilled-600M`. * - * See [here](https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200) - * for the full list of languages and their corresponding codes. + * See the [FLORES-200 language list](https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200) + * for the available languages and their corresponding codes. * * ```javascript * import { pipeline } from '@huggingface/transformers'; @@ -43,8 +43,8 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; * * **Example:** Multilingual translation with `Xenova/m2m100_418M`. * - * See [here](https://huggingface.co/facebook/m2m100_418M#languages-covered) - * for the full list of languages and their corresponding codes. + * See the [M2M100 language list](https://huggingface.co/facebook/m2m100_418M#languages-covered) + * for the available languages and their corresponding codes. * * ```javascript * import { pipeline } from '@huggingface/transformers'; @@ -59,8 +59,8 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; * * **Example:** Multilingual translation with `Xenova/mbart-large-50-many-to-many-mmt`. * - * See [here](https://huggingface.co/facebook/mbart-large-50-many-to-many-mmt#languages-covered) - * for the full list of languages and their corresponding codes. + * See the [mBART-50 language list](https://huggingface.co/facebook/mbart-large-50-many-to-many-mmt#languages-covered) + * for the available languages and their corresponding codes. * * ```javascript * import { pipeline } from '@huggingface/transformers'; @@ -70,7 +70,7 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; * src_lang: 'hi_IN', // Hindi * tgt_lang: 'fr_XX', // French * }); - * // [{ translation_text: 'Le chef des Nations affirme qu 'il n 'y a military solution in Syria.' }] + * // [{ translation_text: "Le chef de la mission de l 'ONU a déclaré qu 'il n' y a pas de solution militaire en Syria." }] * ``` */ export class TranslationPipeline diff --git a/packages/transformers/src/pipelines/zero-shot-audio-classification.js b/packages/transformers/src/pipelines/zero-shot-audio-classification.js index 07c957c9c..44cba0cf1 100644 --- a/packages/transformers/src/pipelines/zero-shot-audio-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-audio-classification.js @@ -37,8 +37,8 @@ import { softmax } from '../utils/maths.js'; */ /** - * Zero-shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio when you - * provide an audio and a set of `candidate_labels`. + * Zero-shot audio classification pipeline using `ClapModel`. This pipeline predicts the class of an audio clip from + * audio and a set of `candidate_labels`. * * **Example:** Perform zero-shot audio classification with `Xenova/clap-htsat-unfused`. * ```javascript @@ -46,11 +46,11 @@ import { softmax } from '../utils/maths.js'; * * const classifier = await pipeline('zero-shot-audio-classification', 'Xenova/clap-htsat-unfused'); * const audio = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/dog_barking.wav'; - * const candidate_labels = ['dog', 'vaccum cleaner']; + * const candidate_labels = ['dog', 'vacuum cleaner']; * const scores = await classifier(audio, candidate_labels); * // [ * // { score: 0.9993992447853088, label: 'dog' }, - * // { score: 0.0006007603369653225, label: 'vaccum cleaner' } + * // { score: 0.0006007603369653225, label: 'vacuum cleaner' } * // ] * ``` */ diff --git a/packages/transformers/src/pipelines/zero-shot-classification.js b/packages/transformers/src/pipelines/zero-shot-classification.js index fa00bafc1..931f0d440 100644 --- a/packages/transformers/src/pipelines/zero-shot-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-classification.js @@ -40,9 +40,9 @@ import { logger } from '../utils/logger.js'; /** * NLI-based zero-shot classification pipeline using a `ModelForSequenceClassification` - * trained on NLI (natural language inference) tasks. Equivalent of `text-classification` - * pipelines, but these models don't require a hardcoded number of potential classes, they - * can be chosen at runtime. It usually means it's slower but it is **much** more flexible. + * trained on NLI (natural language inference) tasks. This is similar to `text-classification` + * pipelines, but these models do not require a hardcoded set of classes. Candidate classes can + * be provided at runtime, making zero-shot classification slower but much more flexible. * * **Example:** Zero-shot classification with `Xenova/mobilebert-uncased-mnli`. * ```javascript diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index 8c5b11b5c..ca23d1367 100644 --- a/packages/transformers/src/tokenization_utils.js +++ b/packages/transformers/src/tokenization_utils.js @@ -608,7 +608,7 @@ export class PreTrainedTokenizer * @param {number[]|bigint[]|Tensor} token_ids List/Tensor of token IDs to decode. * @param {Object} [decode_args={}] * @param {boolean} [decode_args.skip_special_tokens=false] If true, special tokens are removed from the output string. - * @param {boolean} [decode_args.clean_up_tokenization_spaces=true] If true, spaces before punctuations and abbreviated forms are removed. + * @param {boolean} [decode_args.clean_up_tokenization_spaces=true] If true, spaces before punctuation and abbreviated forms are removed. * * @returns {string} The decoded string. * @throws {Error} If `token_ids` is not a non-empty array of integers. diff --git a/packages/transformers/src/utils/core.js b/packages/transformers/src/utils/core.js index 4950818c8..64ba5591a 100644 --- a/packages/transformers/src/utils/core.js +++ b/packages/transformers/src/utils/core.js @@ -13,21 +13,21 @@ import { Callable } from './generic.js'; /** * @typedef {Object} InitiateProgressInfo * @property {'initiate'} status A file load is about to start. - * @property {string} name The model id or directory path. + * @property {string} name The model ID or directory path. * @property {string} file The name of the file. */ /** * @typedef {Object} DownloadProgressInfo * @property {'download'} status A file download has started. - * @property {string} name The model id or directory path. + * @property {string} name The model ID or directory path. * @property {string} file The name of the file. */ /** * @typedef {Object} ProgressStatusInfo * @property {'progress'} status A file download has reported byte progress. - * @property {string} name The model id or directory path. + * @property {string} name The model ID or directory path. * @property {string} file The name of the file. * @property {number} progress A number between 0 and 100. * @property {number} loaded The number of bytes loaded. @@ -49,7 +49,7 @@ import { Callable } from './generic.js'; /** * @typedef {Object} TotalProgressInfo * @property {'progress_total'} status Aggregate progress across all files being loaded. - * @property {string} name The model id or directory path. + * @property {string} name The model ID or directory path. * @property {number} progress A number between 0 and 100. * @property {number} loaded The number of bytes loaded. * @property {number} total The total number of bytes to be loaded. @@ -59,7 +59,7 @@ import { Callable } from './generic.js'; /** * @typedef {Object} DoneProgressInfo * @property {'done'} status A file has finished loading. - * @property {string} name The model id or directory path. + * @property {string} name The model ID or directory path. * @property {string} file The name of the file. */ diff --git a/packages/transformers/src/utils/hub.js b/packages/transformers/src/utils/hub.js index 8256eee4e..3d5f48080 100755 --- a/packages/transformers/src/utils/hub.js +++ b/packages/transformers/src/utils/hub.js @@ -27,20 +27,19 @@ export { MAX_EXTERNAL_DATA_CHUNKS } from './hub/constants.js'; * @typedef {Object} PretrainedOptions Options for loading a pretrained model. * @property {import('./core.js').ProgressCallback} [progress_callback=null] If specified, this function will be called during model construction, to provide the user with progress updates. * @property {import('../configs.js').PretrainedConfig} [config=null] Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when: - * - The model is a model provided by the library (loaded with the *model id* string of a pretrained model). + * - The model is provided by the library and loaded with the *model ID* string of a pretrained model. * - The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. * @property {string} [cache_dir=null] Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. * @property {boolean} [local_files_only=false] Whether to only look at local files (e.g., not try downloading the model). - * @property {string} [revision='main'] The specific model version to use. It can be a branch name, a tag name, or a commit id, - * since we use a git-based system for storing models and other artifacts on huggingface.co, so `revision` can be any identifier allowed by git. - * NOTE: This setting is ignored for local requests. + * @property {string} [revision='main'] The model revision to use. This can be a branch name, tag name, or commit ID. + * Because the Hub uses Git-based storage, `revision` can be any identifier accepted by Git. Ignored for local requests. */ /** * @typedef {Object} ModelSpecificPretrainedOptions Options for loading a pretrained model. * @property {string} [subfolder='onnx'] In case the relevant files are located inside a subfolder of the model repo on huggingface.co, * you can specify the folder name here. - * @property {string} [model_file_name=null] If specified, load the model with this name (excluding the dtype and .onnx suffixes). Currently only valid for encoder- or decoder-only models. + * @property {string} [model_file_name=null] If specified, load the model with this name (excluding the dtype and .onnx suffixes). This is currently valid only for encoder- or decoder-only models. * @property {import("./devices.js").DeviceType|Record<string, import("./devices.js").DeviceType>} [device=null] The device to run the model on. If not specified, the device will be chosen from the environment settings. * @property {import("./dtypes.js").DataType|Record<string, import("./dtypes.js").DataType>} [dtype=null] The data type to use for the model. If not specified, the data type will be chosen from the environment settings. * @property {ExternalData|Record<string, ExternalData>} [use_external_data_format=false] Whether to load the model using the external data format (used for models >= 2GB in size). diff --git a/packages/transformers/src/utils/random.js b/packages/transformers/src/utils/random.js index fbeca3166..ed30d5ac1 100644 --- a/packages/transformers/src/utils/random.js +++ b/packages/transformers/src/utils/random.js @@ -59,7 +59,7 @@ export class Random { * * When called with a number, initializes the state deterministically from that value. * When called with no arguments (or `undefined`/`null`), seeds from OS entropy - * via `crypto.getRandomValues`, matching Python's `random.seed()` behaviour. + * via `crypto.getRandomValues`, matching Python's `random.seed()` behavior. * * @param {number} [n] The seed value. Omit to seed from OS entropy. */ From 28ef2e645e314ac97df937dd3cf22f9b0f5fe00a Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 7 May 2026 14:14:36 -0400 Subject: [PATCH 063/103] more improvements --- .../references/PIPELINE_OPTIONS.md | 24 ++++++++-------- .../transformers-js/references/TASKS.md | 28 +++++++++++-------- .../docs/source/integrations/vercel-ai-sdk.md | 2 +- .../docs/source/tutorials/next-ai-sdk.md | 15 ++++++---- packages/transformers/src/configs.js | 6 ++-- .../src/feature_extraction_utils.js | 4 +-- .../src/generation/configuration_utils.js | 14 +++++----- .../src/image_processors_utils.js | 2 +- .../transformers/src/models/modeling_utils.js | 6 ++-- .../src/models/speecht5/modeling_speecht5.js | 2 +- packages/transformers/src/pipelines.js | 2 +- .../pipelines/automatic-speech-recognition.js | 4 +-- .../src/pipelines/feature-extraction.js | 6 ++-- .../src/pipelines/image-classification.js | 2 +- .../src/pipelines/question-answering.js | 2 +- .../src/pipelines/summarization.js | 2 +- .../src/pipelines/text-classification.js | 4 ++- .../src/pipelines/text-generation.js | 2 +- .../src/pipelines/token-classification.js | 2 +- .../src/pipelines/zero-shot-classification.js | 4 +-- packages/transformers/src/processing_utils.js | 4 +-- .../transformers/src/tokenization_utils.js | 8 +++--- packages/transformers/src/utils/cache.js | 2 +- packages/transformers/src/utils/generic.js | 2 +- packages/transformers/src/utils/hub.js | 12 ++++---- 25 files changed, 86 insertions(+), 75 deletions(-) diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index c7afe2074..ae86e990f 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -9,17 +9,17 @@ class in the [API reference](https://huggingface.co/docs/transformers.js/api/pip <!-- @generated:start id=typedef:PretrainedModelOptions --> | Option | Type | Description | |--------|------|-------------| -| `progress_callback`? | `ProgressCallback` | If specified, this function will be called during model construction, to provide the user with progress updates. _(default: `null`)_ | -| `config`? | `PretrainedConfig` | Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when:<br />- The model is provided by the library and loaded with the *model ID* string of a pretrained model.<br />- The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | -| `cache_dir`? | `string` | Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. _(default: `null`)_ | +| `progress_callback`? | `ProgressCallback` | If specified, this function is called during model construction with progress updates. _(default: `null`)_ | +| `config`? | `PretrainedConfig` | Configuration to use for the model instead of an automatically loaded configuration. Configuration can be automatically loaded when:<br />- The model is provided by the library and loaded with the *model ID* string of a pretrained model.<br />- The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. _(default: `null`)_ | +| `cache_dir`? | `string` | Path to a directory where downloaded model files should be cached if the standard cache should not be used. _(default: `null`)_ | | `local_files_only`? | `boolean` | Whether to only look at local files (e.g., not try downloading the model). _(default: `false`)_ | | `revision`? | `string` | The model revision to use. This can be a branch name, tag name, or commit ID. Because the Hub uses Git-based storage, `revision` can be any identifier accepted by Git. Ignored for local requests. _(default: `'main'`)_ | | `subfolder`? | `string` | In case the relevant files are located inside a subfolder of the model repo on huggingface.co, you can specify the folder name here. _(default: `'onnx'`)_ | -| `model_file_name`? | `string` | If specified, load the model with this name (excluding the dtype and .onnx suffixes). This is currently valid only for encoder- or decoder-only models. _(default: `null`)_ | +| `model_file_name`? | `string` | Override the base ONNX model file name, excluding dtype and `.onnx` suffixes. This is most useful for single-session models. _(default: `null`)_ | | `device`? | `DeviceType\|Record<string, DeviceType>` | The device to run the model on. If not specified, the device will be chosen from the environment settings. _(default: `null`)_ | | `dtype`? | `DataType\|Record<string, DataType>` | The data type to use for the model. If not specified, the data type will be chosen from the environment settings. _(default: `null`)_ | -| `use_external_data_format`? | `ExternalData\|Record<string, ExternalData>` | Whether to load the model using the external data format (used for models >= 2GB in size). _(default: `false`)_ | -| `session_options`? | `InferenceSession.SessionOptions` | (Optional) User-specified session options passed to the runtime. If not provided, suitable defaults will be chosen. | +| `use_external_data_format`? | `ExternalData\|Record<string, ExternalData>` | Whether to load external data files. `null` uses the model configuration; `false` disables external data; `true` uses one chunk; a number selects the chunk count. _(default: `null`)_ | +| `session_options`? | `InferenceSession.SessionOptions` | User-specified ONNX Runtime session options. Suitable defaults are filled in when omitted. _(default: `{}`)_ | <!-- @generated:end id=typedef:PretrainedModelOptions --> ## Progress tracking @@ -154,8 +154,8 @@ anywhere `generate()` is invoked directly. | `do_sample`? | `boolean` | Whether to use sampling; use greedy decoding otherwise. _(default: `false`)_ | | `num_beams`? | `number` | Number of beams for beam search. 1 means no beam search. _(default: `1`)_ | | `num_beam_groups`? | `number` | Number of groups to divide `num_beams` into to encourage diversity among different groups of beams. See [this paper](https://huggingface.co/papers/1610.02424) for more details. _(default: `1`)_ | -| `penalty_alpha`? | `number` | The value balances model confidence and the degeneration penalty in contrastive search decoding. _(default: `null`)_ | -| `use_cache`? | `boolean` | Whether the model should use the past key/value attentions (if applicable to the model) to speed up decoding. _(default: `true`)_ | +| `penalty_alpha`? | `number` | Balance model confidence against the degeneration penalty during contrastive search decoding. _(default: `null`)_ | +| `use_cache`? | `boolean` | Whether the model should reuse past key/value states, when supported, to speed up decoding. _(default: `true`)_ | | `temperature`? | `number` | The value used to modulate the next token probabilities. _(default: `1.0`)_ | | `top_k`? | `number` | The number of highest-probability vocabulary tokens to keep for top-k filtering. _(default: `50`)_ | | `top_p`? | `number` | If set to a float below 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or higher are kept for generation. _(default: `1.0`)_ | @@ -168,7 +168,7 @@ anywhere `generate()` is invoked directly. | `length_penalty`? | `number` | Exponential penalty applied to sequence length during beam-based generation. It is applied as an exponent to the sequence length, which in turn is used to divide the score of the sequence. Since the score is the log likelihood of the sequence (i.e. negative), `length_penalty` > 0.0 promotes longer sequences, while `length_penalty` < 0.0 encourages shorter sequences. _(default: `1.0`)_ | | `no_repeat_ngram_size`? | `number` | If set to an integer greater than 0, all n-grams of that size can only occur once. _(default: `0`)_ | | `bad_words_ids`? | `number[][]` | List of token IDs that are not allowed to be generated. In order to get the token IDs of the words that should not appear in the generated text, use `tokenizer(bad_words, { add_prefix_space: true, add_special_tokens: false }).input_ids`. _(default: `null`)_ | -| `force_words_ids`? | `number[][]\|number[][][]` | List of token IDs that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. _(default: `null`)_ | +| `force_words_ids`? | `number[][]\|number[][][]` | List of token IDs that must be generated. If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite of `bad_words_ids`. If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), which allows different forms of each word. _(default: `null`)_ | | `renormalize_logits`? | `boolean` | Whether to renormalize the logits after applying all the logits processors or warpers (including the custom ones). It's highly recommended to set this flag to `true` because search algorithms assume the score logits are normalized, but some logit processors or warpers break the normalization. _(default: `false`)_ | | `constraints`? | `Object[]` | Custom constraints that guide generation to include certain tokens as defined by `Constraint` objects. _(default: `null`)_ | | `forced_bos_token_id`? | `number` | The ID of the token to force as the first generated token after the `decoder_start_token_id`. Useful for multilingual models like mBART where the first generated token needs to be the target language token. _(default: `null`)_ | @@ -176,12 +176,12 @@ anywhere `generate()` is invoked directly. | `remove_invalid_values`? | `boolean` | Whether to remove possible *nan* and *inf* outputs of the model to prevent the generation method from crashing. Note that using `remove_invalid_values` can slow down generation. _(default: `false`)_ | | `exponential_decay_length_penalty`? | `[number, number]` | This tuple adds an exponentially increasing length penalty after a certain number of tokens have been generated. The tuple consists of: `(start_index, decay_factor)` where `start_index` indicates where the penalty starts and `decay_factor` represents the factor of exponential decay. _(default: `null`)_ | | `suppress_tokens`? | `number[]` | A list of tokens to suppress during generation. The `SuppressTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. _(default: `null`)_ | -| `streamer`? | `TextStreamer` | A streamer used to stream the generation. _(default: `null`)_ | +| `streamer`? | `TextStreamer` | Streamer used to yield generated text incrementally. _(default: `null`)_ | | `begin_suppress_tokens`? | `number[]` | A list of tokens to suppress at the beginning of the generation. The `SuppressBeginTokens` logit processor sets their log probabilities to `-inf` so that they are not sampled. _(default: `null`)_ | -| `forced_decoder_ids`? | `[number, number][]` | A list of pairs of integers that indicates a mapping from generation indices to token indices that will be forced before sampling. For example, `[[1, 123]]` means the second generated token will always be a token of index 123. _(default: `null`)_ | +| `forced_decoder_ids`? | `[number, number][]` | A list of integer pairs that maps generation indices to token indices that will be forced before sampling. For example, `[[1, 123]]` means the second generated token will always be a token of index 123. _(default: `null`)_ | | `guidance_scale`? | `number` | The guidance scale for classifier-free guidance (CFG). CFG is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages the model to generate samples that are more closely tied to the input prompt, usually at the expense of poorer quality. _(default: `null`)_ | | `num_return_sequences`? | `number` | The number of independently computed returned sequences for each element in the batch. _(default: `1`)_ | -| `output_attentions`? | `boolean` | Whether to return the attentions tensors of all attention layers. See `attentions` under returned tensors for more details. _(default: `false`)_ | +| `output_attentions`? | `boolean` | Whether to return attention tensors from all attention layers. See `attentions` under returned tensors for more details. _(default: `false`)_ | | `output_hidden_states`? | `boolean` | Whether to return the hidden states of all layers. See `hidden_states` under returned tensors for more details. _(default: `false`)_ | | `output_scores`? | `boolean` | Whether to return the prediction scores. See `scores` under returned tensors for more details. _(default: `false`)_ | | `return_dict_in_generate`? | `boolean` | Whether to return a `ModelOutput` instead of a plain tuple. _(default: `false`)_ | diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index f48d0d2cf..00d5e24bd 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -98,9 +98,9 @@ const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisp const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; const output = await transcriber(url, { return_timestamps: true }); // { -// text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country." +// text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country.", // chunks: [ -// { timestamp: [0, 8], text: " And so my fellow Americans ask not what your country can do for you" } +// { timestamp: [0, 8], text: " And so my fellow Americans ask not what your country can do for you" }, // { timestamp: [8, 11], text: " ask what you can do for your country." } // ] // } @@ -539,7 +539,7 @@ const features = await image_feature_extractor(image); **Default model:** `Xenova/distilbert-base-uncased-finetuned-sst-2-english` **Aliases:** `sentiment-analysis` -Text classification pipeline using any `ModelForSequenceClassification`. +Text classification pipeline using `AutoModelForSequenceClassification`. **Example:** Sentiment analysis with `Xenova/distilbert-base-uncased-finetuned-sst-2-english`. ```javascript @@ -567,6 +567,8 @@ const output = await classifier('Le meilleur film de tous les temps.', { top_k: **Example:** Toxic comment classification with `Xenova/toxic-bert` (and return all classes). ```javascript +import { pipeline } from '@huggingface/transformers'; + const classifier = await pipeline('text-classification', 'Xenova/toxic-bert'); const output = await classifier('I hate you!', { top_k: null }); // [ @@ -584,7 +586,7 @@ const output = await classifier('I hate you!', { top_k: null }); **Default model:** `Xenova/bert-base-multilingual-cased-ner-hrl` **Aliases:** `ner` -Named Entity Recognition pipeline using any `ModelForTokenClassification`. +Named entity recognition pipeline using `AutoModelForTokenClassification`. **Example:** Perform named entity recognition with `Xenova/bert-base-NER`. ```javascript @@ -632,7 +634,7 @@ const output = await classifier('My name is Sarah and I live in London', { aggre **Default model:** `Xenova/distilbert-base-cased-distilled-squad` -Question Answering pipeline using any `ModelForQuestionAnswering`. +Question answering pipeline using `AutoModelForQuestionAnswering`. **Example:** Run question answering with `Xenova/distilbert-base-uncased-distilled-squad`. ```javascript @@ -697,7 +699,7 @@ const output = await unmasker('The Milky Way is a [MASK] galaxy.', { top_k: 1 }) **Default model:** `Xenova/distilbart-cnn-6-6` -A pipeline for summarization tasks, inheriting from Text2TextGenerationPipeline. +Summarization pipeline using sequence-to-sequence language models. **Example:** Summarization with `Xenova/distilbart-cnn-6-6`. ```javascript @@ -769,7 +771,7 @@ const output = await translator('संयुक्त राष्ट्र क src_lang: 'hi_IN', // Hindi tgt_lang: 'fr_XX', // French }); -// [{ translation_text: "Le chef de l'ONU affirme qu'il n'y a pas de solution militaire en Syrie." }] +// [{ translation_text: "Le chef de la mission de l 'ONU a déclaré qu 'il n' y a pas de solution militaire en Syria." }] ``` ### `text2text-generation` @@ -797,7 +799,7 @@ Language generation pipeline using compatible causal language models. This pipeline predicts the words that will follow a specified text prompt. For all generation parameters, see `GenerationConfig`. -**Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). +**Example:** Text generation with `onnx-community/SmolLM2-135M-ONNX` (default settings). ```javascript import { pipeline } from '@huggingface/transformers'; @@ -837,9 +839,9 @@ console.log(output[0].generated_text.at(-1)?.content); **Default model:** `Xenova/distilbert-base-uncased-mnli` -NLI-based zero-shot classification pipeline using a `ModelForSequenceClassification` +NLI-based zero-shot classification pipeline using `AutoModelForSequenceClassification` trained on NLI (natural language inference) tasks. This is similar to `text-classification` -pipelines, but these models do not require a hardcoded set of classes. Candidate classes can +pipelines, but these models do not require a hard-coded set of classes. Candidate classes can be provided at runtime, making zero-shot classification slower but much more flexible. **Example:** Zero-shot classification with `Xenova/mobilebert-uncased-mnli`. @@ -914,13 +916,15 @@ const output = await extractor('This is a simple test.', { pooling: 'mean', norm console.log(output.tolist()); ``` -**Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` models (with pooling and binary quantization). +**Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` with pooling and binary quantization. ```javascript +import { pipeline } from '@huggingface/transformers'; + const extractor = await pipeline('feature-extraction', 'onnx-community/all-MiniLM-L6-v2-ONNX'); const output = await extractor('This is a simple test.', { pooling: 'mean', quantize: true, precision: 'binary' }); // Tensor { // type: 'int8', -// data: Int8Array [49, 108, 25, ...], +// data: Int8Array [49, 108, 25, ...], // dims: [1, 48] // } diff --git a/packages/transformers/docs/source/integrations/vercel-ai-sdk.md b/packages/transformers/docs/source/integrations/vercel-ai-sdk.md index 02dfed955..1210b7df7 100644 --- a/packages/transformers/docs/source/integrations/vercel-ai-sdk.md +++ b/packages/transformers/docs/source/integrations/vercel-ai-sdk.md @@ -288,6 +288,6 @@ const { sendMessage, messages, stop } = useChat<TransformersUIMessage>({ ## Further reading -- [Building a Next.js AI Chatbot](../tutorials/next-ai-sdk) — a step-by-step tutorial building a full chatbot with tool calling +- [Building a Next.js AI Chatbot](../tutorials/next-ai-sdk): a step-by-step tutorial for building a full chatbot with tool calling - [`@browser-ai/transformers-js` documentation](https://www.browser-ai.dev/docs/ai-sdk-v6/transformers-js) - [Vercel AI SDK documentation](https://ai-sdk.dev/) diff --git a/packages/transformers/docs/source/tutorials/next-ai-sdk.md b/packages/transformers/docs/source/tutorials/next-ai-sdk.md index f09b62d75..1f161216d 100644 --- a/packages/transformers/docs/source/tutorials/next-ai-sdk.md +++ b/packages/transformers/docs/source/tutorials/next-ai-sdk.md @@ -1,8 +1,9 @@ # Building a Next.js AI Chatbot with Vercel AI SDK -In this tutorial, we'll build an in-browser AI chatbot using Next.js, Transformers.js, and the Vercel AI SDK v6. The chatbot runs entirely client-side with WebGPU acceleration — and supports tool calling with human approval. +In this tutorial, we'll build an in-browser AI chatbot using Next.js, Transformers.js, and the Vercel AI SDK v6. The chatbot runs entirely client-side with WebGPU acceleration and supports tool calling with human approval. Useful links: + - [Source code](https://github.com/huggingface/transformers.js-examples/tree/main/next-vercel-ai-sdk-v6-tool-calling) - [`@browser-ai/transformers-js` docs](https://www.browser-ai.dev/docs/ai-sdk-v6/transformers-js) - [Vercel AI SDK docs](https://ai-sdk.dev/) @@ -66,7 +67,7 @@ self.onmessage = (msg: MessageEvent) => { }; ``` -That's it — the handler takes care of model loading, inference, streaming, and communication with the main thread. +That's it: the handler takes care of model loading, inference, streaming, and communication with the main thread. ## Step 4: Define the model configuration @@ -278,6 +279,7 @@ export class TransformersChatTransport ``` Key parts of the transport: + - **Availability check**: Determines if the model needs downloading before inference. - **Progress streaming**: Sends download progress as custom data parts (`data-modelDownloadProgress`) that the UI can render as a progress bar. - **Tool support**: Passes the tools to `streamText()` so the model can call them. @@ -408,9 +410,10 @@ export default function ChatPage() { ``` The component renders message parts based on their `type`: -- `text` — standard text output from the model. -- `data-modelDownloadProgress` — custom data parts sent by the transport during model download. -- `tool-*` — tool call parts with states like `approval-requested`, `output-available`, etc. + +- `text`: standard text output from the model. +- `data-modelDownloadProgress`: custom data parts sent by the transport during model download. +- `tool-*`: tool call parts with states like `approval-requested`, `output-available`, etc. The `sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses` option tells `useChat` to automatically resume generation after the user responds to a tool approval request. @@ -431,7 +434,7 @@ Try prompts like: ## Next steps -- Add more models and a model selector — see the [full example source](https://github.com/huggingface/transformers.js-examples/tree/main/next-vercel-ai-sdk-v6-tool-calling) for a multi-model implementation with Zustand state management. +- Add more models and a model selector; see the [full example source](https://github.com/huggingface/transformers.js-examples/tree/main/next-vercel-ai-sdk-v6-tool-calling) for a multi-model implementation with Zustand state management. - Add a browser compatibility check with `doesBrowserSupportTransformersJS()` and fall back to a server-side route if WebGPU is unavailable. - Explore the [Vercel AI SDK agents documentation](https://ai-sdk.dev/docs/agents/overview) for more complex agent patterns. - See the [Vercel AI SDK guide](../integrations/vercel-ai-sdk) for a reference of all supported features (embeddings, vision, transcription, etc.). diff --git a/packages/transformers/src/configs.js b/packages/transformers/src/configs.js index 5d96c461c..8d73986f0 100644 --- a/packages/transformers/src/configs.js +++ b/packages/transformers/src/configs.js @@ -475,9 +475,9 @@ export class PretrainedConfig { } /** - * Loads a pre-trained config from the given `pretrained_model_name_or_path`. + * Loads a pretrained config from the given `pretrained_model_name_or_path`. * - * @param {string} pretrained_model_name_or_path The path to the pre-trained config. + * @param {string} pretrained_model_name_or_path The path to the pretrained config. * @param {PretrainedOptions} options Additional options for loading the config. * @throws {Error} Throws an error if the config.json is not found in the `pretrained_model_name_or_path`. * @@ -530,7 +530,7 @@ export class AutoConfig { * for more information. * @property {import('./utils/devices.js').DeviceType} [device] The default device to use for the model. * @property {import('./utils/dtypes.js').DataType|Record<string, import('./utils/dtypes.js').DataType>} [dtype] The default data type to use for the model. - * @property {import('./utils/hub.js').ExternalData|Record<string, import('./utils/hub.js').ExternalData>} [use_external_data_format=false] Whether to load the model using the external data format (used for models >= 2GB in size). + * @property {import('./utils/hub.js').ExternalData|Record<string, import('./utils/hub.js').ExternalData>} [use_external_data_format] Whether to load the model using the external data format (used for models >= 2GB in size). */ /** diff --git a/packages/transformers/src/feature_extraction_utils.js b/packages/transformers/src/feature_extraction_utils.js index ea256b080..b4255d88d 100644 --- a/packages/transformers/src/feature_extraction_utils.js +++ b/packages/transformers/src/feature_extraction_utils.js @@ -27,8 +27,8 @@ export class FeatureExtractor extends Callable { * the config object (either passed as an argument or loaded from `pretrained_model_name_or_path` if possible) * * @param {string} pretrained_model_name_or_path The name or path of the pretrained model. Can be either: - * - A string, the *model id* of a pretrained feature_extractor hosted inside a model repo on huggingface.co. - * Valid model ids can be located at the root-level, like `bert-base-uncased`, or namespaced under a + * - A string, the *model ID* of a pretrained feature extractor hosted inside a model repo on huggingface.co. + * Valid model IDs can be located at the root level, like `bert-base-uncased`, or namespaced under a * user or organization name, like `dbmdz/bert-base-german-cased`. * - A path to a *directory* containing feature_extractor files, e.g., `./my_model_directory/`. * @param {import('./utils/hub.js').PretrainedOptions} options Additional options for loading the feature_extractor. diff --git a/packages/transformers/src/generation/configuration_utils.js b/packages/transformers/src/generation/configuration_utils.js index b0079f6ad..56d265873 100644 --- a/packages/transformers/src/generation/configuration_utils.js +++ b/packages/transformers/src/generation/configuration_utils.js @@ -87,14 +87,14 @@ export class GenerationConfig { num_beam_groups = 1; /** - * The value balances model confidence and the degeneration penalty in contrastive search decoding. + * Balance model confidence against the degeneration penalty during contrastive search decoding. * @type {number} * @default null */ penalty_alpha = null; /** - * Whether the model should use the past key/value attentions (if applicable to the model) to speed up decoding. + * Whether the model should reuse past key/value states, when supported, to speed up decoding. * @type {boolean} * @default true */ @@ -201,8 +201,8 @@ export class GenerationConfig { /** * List of token IDs that must be generated. - * If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite to `bad_words_ids`. - * If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), where one can allow different forms of each word. + * If given a `number[][]`, this is treated as a simple list of words that must be included, the opposite of `bad_words_ids`. + * If given `number[][][]`, this triggers a [disjunctive constraint](https://github.com/huggingface/transformers/issues/14081), which allows different forms of each word. * @type {number[][]|number[][][]} * @default null */ @@ -262,7 +262,7 @@ export class GenerationConfig { suppress_tokens = null; /** - * A streamer used to stream the generation. + * Streamer used to yield generated text incrementally. * @type {import('./streamers.js').TextStreamer} * @default null */ @@ -277,7 +277,7 @@ export class GenerationConfig { begin_suppress_tokens = null; /** - * A list of pairs of integers that indicates a mapping from generation indices to token indices that will be forced before sampling. + * A list of integer pairs that maps generation indices to token indices that will be forced before sampling. * For example, `[[1, 123]]` means the second generated token will always be a token of index 123. * @type {[number, number][]} * @default null @@ -302,7 +302,7 @@ export class GenerationConfig { num_return_sequences = 1; /** - * Whether to return the attentions tensors of all attention layers. + * Whether to return attention tensors from all attention layers. * See `attentions` under returned tensors for more details. * @type {boolean} * @default false diff --git a/packages/transformers/src/image_processors_utils.js b/packages/transformers/src/image_processors_utils.js index 63c0caab6..24318a1ee 100644 --- a/packages/transformers/src/image_processors_utils.js +++ b/packages/transformers/src/image_processors_utils.js @@ -542,7 +542,7 @@ export function post_process_instance_segmentation(outputs, threshold = 0.5, tar /** * @typedef {Object} ImageProcessorConfig A configuration object used to create an image processor. - * @property {function} [progress_callback=null] If specified, this function will be called during model construction, to provide the user with progress updates. + * @property {function} [progress_callback=null] If specified, this function is called during model construction with progress updates. * @property {number[]} [image_mean] The mean values for image normalization. * @property {number[]} [image_std] The standard deviation values for image normalization. * @property {boolean} [do_rescale] Whether to rescale the image pixel values to the [0,1] range. diff --git a/packages/transformers/src/models/modeling_utils.js b/packages/transformers/src/models/modeling_utils.js index 42cbd851b..64da64385 100644 --- a/packages/transformers/src/models/modeling_utils.js +++ b/packages/transformers/src/models/modeling_utils.js @@ -207,7 +207,7 @@ export const MODEL_NAME_TO_CLASS_MAPPING = new Map(); export const MODEL_CLASS_TO_NAME_MAPPING = new Map(); /** - * A base class for pre-trained models that provides the model configuration and inference sessions. + * A base class for pretrained models that provides the model configuration and inference sessions. */ export class PreTrainedModel extends Callable { main_input_name = 'input_ids'; @@ -263,8 +263,8 @@ export class PreTrainedModel extends Callable { * (either passed as an argument or loaded from `pretrained_model_name_or_path` if possible) * * @param {string} pretrained_model_name_or_path The name or path of the pretrained model. Can be either: - * - A string, the *model id* of a pretrained model hosted inside a model repo on huggingface.co. - * Valid model ids can be located at the root-level, like `bert-base-uncased`, or namespaced under a + * - A string, the *model ID* of a pretrained model hosted inside a model repo on huggingface.co. + * Valid model IDs can be located at the root level, like `bert-base-uncased`, or namespaced under a * user or organization name, like `dbmdz/bert-base-german-cased`. * - A path to a *directory* containing model weights, e.g., `./my_model_directory/`. * @param {import('../utils/hub.js').PretrainedModelOptions} options Additional options for loading the model. diff --git a/packages/transformers/src/models/speecht5/modeling_speecht5.js b/packages/transformers/src/models/speecht5/modeling_speecht5.js index 970031eb1..3e7bf4683 100644 --- a/packages/transformers/src/models/speecht5/modeling_speecht5.js +++ b/packages/transformers/src/models/speecht5/modeling_speecht5.js @@ -76,7 +76,7 @@ export class SpeechT5ForTextToSpeech extends SpeechT5PreTrainedModel { * @param {number} [options.minlenratio=0.0] Used to calculate the minimum required length for the output sequence. * @param {number} [options.maxlenratio=20.0] Used to calculate the maximum allowed length for the output sequence. * @param {Object} [options.vocoder=null] The vocoder that converts the mel spectrogram into a speech waveform. If `null`, the output is the mel spectrogram. - * @param {boolean} [options.output_cross_attentions=false] Whether or not to return the attentions tensors of the decoder's cross-attention layers. + * @param {boolean} [options.output_cross_attentions=false] Whether to return attention tensors from the decoder's cross-attention layers. * @returns {Promise<SpeechOutput>} A promise which resolves to an object containing the spectrogram, waveform, and cross-attention tensors. */ async generate_speech( diff --git a/packages/transformers/src/pipelines.js b/packages/transformers/src/pipelines.js index ea560cc6b..95ef240b5 100644 --- a/packages/transformers/src/pipelines.js +++ b/packages/transformers/src/pipelines.js @@ -89,7 +89,7 @@ import { get_file_metadata } from './utils/model_registry/get_file_metadata.js'; * - `"zero-shot-audio-classification"`: will return a `ZeroShotAudioClassificationPipeline`. * - `"zero-shot-image-classification"`: will return a `ZeroShotImageClassificationPipeline`. * - `"zero-shot-object-detection"`: will return a `ZeroShotObjectDetectionPipeline`. - * @param {string} [model=null] The name of the pre-trained model to use. If not specified, the default model for the task will be used. + * @param {string} [model=null] The name of the pretrained model to use. If not specified, the default model for the task will be used. * @param {import('./utils/hub.js').PretrainedModelOptions} [options] Optional parameters for the pipeline. * @returns {Promise<AllTasks[T]>} A Pipeline object for the specified task. * @throws {Error} If an unsupported pipeline is requested. diff --git a/packages/transformers/src/pipelines/automatic-speech-recognition.js b/packages/transformers/src/pipelines/automatic-speech-recognition.js index f18c8680b..e21c78e01 100644 --- a/packages/transformers/src/pipelines/automatic-speech-recognition.js +++ b/packages/transformers/src/pipelines/automatic-speech-recognition.js @@ -69,9 +69,9 @@ import { logger } from '../utils/logger.js'; * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav'; * const output = await transcriber(url, { return_timestamps: true }); * // { - * // text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country." + * // text: " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country.", * // chunks: [ - * // { timestamp: [0, 8], text: " And so my fellow Americans ask not what your country can do for you" } + * // { timestamp: [0, 8], text: " And so my fellow Americans ask not what your country can do for you" }, * // { timestamp: [8, 11], text: " ask what you can do for your country." } * // ] * // } diff --git a/packages/transformers/src/pipelines/feature-extraction.js b/packages/transformers/src/pipelines/feature-extraction.js index f0c160fd3..08e112f42 100644 --- a/packages/transformers/src/pipelines/feature-extraction.js +++ b/packages/transformers/src/pipelines/feature-extraction.js @@ -62,13 +62,15 @@ import { Tensor, mean_pooling, quantize_embeddings } from '../utils/tensor.js'; * console.log(output.tolist()); * ``` * - * **Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` models (with pooling and binary quantization). + * **Example:** Run feature extraction using `onnx-community/all-MiniLM-L6-v2-ONNX` with pooling and binary quantization. * ```javascript + * import { pipeline } from '@huggingface/transformers'; + * * const extractor = await pipeline('feature-extraction', 'onnx-community/all-MiniLM-L6-v2-ONNX'); * const output = await extractor('This is a simple test.', { pooling: 'mean', quantize: true, precision: 'binary' }); * // Tensor { * // type: 'int8', - * // data: Int8Array [49, 108, 25, ...], + * // data: Int8Array [49, 108, 25, ...], * // dims: [1, 48] * // } * diff --git a/packages/transformers/src/pipelines/image-classification.js b/packages/transformers/src/pipelines/image-classification.js index 2974add9b..9a6d78322 100644 --- a/packages/transformers/src/pipelines/image-classification.js +++ b/packages/transformers/src/pipelines/image-classification.js @@ -20,7 +20,7 @@ import { softmax } from '../utils/maths.js'; * @typedef {ImageClassificationSingle[]} ImageClassificationOutput * * @typedef {Object} ImageClassificationPipelineOptions Parameters specific to image classification pipelines. - * @property {number} [top_k=1] The number of top labels to return. + * @property {number} [top_k=5] The number of top labels to return. Set to `0` to return all labels. * * @typedef {ImagePipelineConstructorArgs & ImageClassificationPipelineCallback & Disposable} ImageClassificationPipelineType */ diff --git a/packages/transformers/src/pipelines/question-answering.js b/packages/transformers/src/pipelines/question-answering.js index dc04de320..1036fd95c 100644 --- a/packages/transformers/src/pipelines/question-answering.js +++ b/packages/transformers/src/pipelines/question-answering.js @@ -40,7 +40,7 @@ import { softmax } from '../utils/maths.js'; */ /** - * Question Answering pipeline using any `ModelForQuestionAnswering`. + * Question answering pipeline using `AutoModelForQuestionAnswering`. * * **Example:** Run question answering with `Xenova/distilbert-base-uncased-distilled-squad`. * ```javascript diff --git a/packages/transformers/src/pipelines/summarization.js b/packages/transformers/src/pipelines/summarization.js index 0271cc7db..9ffc2aaaf 100644 --- a/packages/transformers/src/pipelines/summarization.js +++ b/packages/transformers/src/pipelines/summarization.js @@ -23,7 +23,7 @@ import { Text2TextGenerationPipeline } from './text2text-generation.js'; */ /** - * A pipeline for summarization tasks, inheriting from Text2TextGenerationPipeline. + * Summarization pipeline using sequence-to-sequence language models. * * **Example:** Summarization with `Xenova/distilbart-cnn-6-6`. * ```javascript diff --git a/packages/transformers/src/pipelines/text-classification.js b/packages/transformers/src/pipelines/text-classification.js index 7d91298ba..d2b30b638 100644 --- a/packages/transformers/src/pipelines/text-classification.js +++ b/packages/transformers/src/pipelines/text-classification.js @@ -41,7 +41,7 @@ import { softmax } from '../utils/maths.js'; */ /** - * Text classification pipeline using any `ModelForSequenceClassification`. + * Text classification pipeline using `AutoModelForSequenceClassification`. * * **Example:** Sentiment analysis with `Xenova/distilbert-base-uncased-finetuned-sst-2-english`. * ```javascript @@ -69,6 +69,8 @@ import { softmax } from '../utils/maths.js'; * * **Example:** Toxic comment classification with `Xenova/toxic-bert` (and return all classes). * ```javascript + * import { pipeline } from '@huggingface/transformers'; + * * const classifier = await pipeline('text-classification', 'Xenova/toxic-bert'); * const output = await classifier('I hate you!', { top_k: null }); * // [ diff --git a/packages/transformers/src/pipelines/text-generation.js b/packages/transformers/src/pipelines/text-generation.js index c668ee17b..be9b34763 100644 --- a/packages/transformers/src/pipelines/text-generation.js +++ b/packages/transformers/src/pipelines/text-generation.js @@ -56,7 +56,7 @@ function isChat(x) { * This pipeline predicts the words that will follow a specified text prompt. * For all generation parameters, see `GenerationConfig`. * - * **Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). + * **Example:** Text generation with `onnx-community/SmolLM2-135M-ONNX` (default settings). * ```javascript * import { pipeline } from '@huggingface/transformers'; * diff --git a/packages/transformers/src/pipelines/token-classification.js b/packages/transformers/src/pipelines/token-classification.js index f9e3b7d8e..294d3c6e2 100644 --- a/packages/transformers/src/pipelines/token-classification.js +++ b/packages/transformers/src/pipelines/token-classification.js @@ -67,7 +67,7 @@ import { max, softmax } from '../utils/maths.js'; */ /** - * Named Entity Recognition pipeline using any `ModelForTokenClassification`. + * Named entity recognition pipeline using `AutoModelForTokenClassification`. * * **Example:** Perform named entity recognition with `Xenova/bert-base-NER`. * ```javascript diff --git a/packages/transformers/src/pipelines/zero-shot-classification.js b/packages/transformers/src/pipelines/zero-shot-classification.js index 931f0d440..b306d88b8 100644 --- a/packages/transformers/src/pipelines/zero-shot-classification.js +++ b/packages/transformers/src/pipelines/zero-shot-classification.js @@ -39,9 +39,9 @@ import { logger } from '../utils/logger.js'; */ /** - * NLI-based zero-shot classification pipeline using a `ModelForSequenceClassification` + * NLI-based zero-shot classification pipeline using `AutoModelForSequenceClassification` * trained on NLI (natural language inference) tasks. This is similar to `text-classification` - * pipelines, but these models do not require a hardcoded set of classes. Candidate classes can + * pipelines, but these models do not require a hard-coded set of classes. Candidate classes can * be provided at runtime, making zero-shot classification slower but much more flexible. * * **Example:** Zero-shot classification with `Xenova/mobilebert-uncased-mnli`. diff --git a/packages/transformers/src/processing_utils.js b/packages/transformers/src/processing_utils.js index edb94d74e..4535f1d0d 100644 --- a/packages/transformers/src/processing_utils.js +++ b/packages/transformers/src/processing_utils.js @@ -142,8 +142,8 @@ export class Processor extends Callable { * property of the config object (either passed as an argument or loaded from `pretrained_model_name_or_path` if possible) * * @param {string} pretrained_model_name_or_path The name or path of the pretrained model. Can be either: - * - A string, the *model id* of a pretrained processor hosted inside a model repo on huggingface.co. - * Valid model ids can be located at the root-level, like `bert-base-uncased`, or namespaced under a + * - A string, the *model ID* of a pretrained processor hosted inside a model repo on huggingface.co. + * Valid model IDs can be located at the root level, like `bert-base-uncased`, or namespaced under a * user or organization name, like `dbmdz/bert-base-german-cased`. * - A path to a *directory* containing processor files, e.g., `./my_model_directory/`. * @param {PretrainedProcessorOptions} options Additional options for loading the processor. diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index ca23d1367..da3f7c0ba 100644 --- a/packages/transformers/src/tokenization_utils.js +++ b/packages/transformers/src/tokenization_utils.js @@ -1,7 +1,7 @@ /** * @file Tokenizers turn text into the integer ids a model understands, and * decode model output back into strings. Use `AutoTokenizer.from_pretrained()` - * to load the right implementation for a model id — the class is chosen from + * to load the right implementation for a model ID — the class is chosen from * the tokenizer's `tokenizer_config.json`. * * For chat-trained models, `tokenizer.apply_chat_template()` renders an @@ -311,9 +311,9 @@ export class PreTrainedTokenizer } /** - * Loads a pre-trained tokenizer from the given `pretrained_model_name_or_path`. + * Loads a pretrained tokenizer from the given `pretrained_model_name_or_path`. * - * @param {string} pretrained_model_name_or_path The path to the pre-trained tokenizer. + * @param {string} pretrained_model_name_or_path The path to the pretrained tokenizer. * @param {PretrainedTokenizerOptions} options Additional options for loading the tokenizer. * * @throws {Error} Throws an error if the tokenizer.json or tokenizer_config.json files are not found in the `pretrained_model_name_or_path`. @@ -702,7 +702,7 @@ export class PreTrainedTokenizer * ids. This method is intended for use with chat models, and will read the tokenizer's chat_template attribute to * determine the format and control tokens to use when converting. * - * See [here](https://huggingface.co/docs/transformers/chat_templating) for more information. + * See the [chat templating guide](https://huggingface.co/docs/transformers/chat_templating) for more information. * * **Example:** Applying a chat template to a conversation. * diff --git a/packages/transformers/src/utils/cache.js b/packages/transformers/src/utils/cache.js index ca8e27971..b4a7d77a4 100644 --- a/packages/transformers/src/utils/cache.js +++ b/packages/transformers/src/utils/cache.js @@ -17,7 +17,7 @@ import { CrossOriginStorage } from './cache/CrossOriginStorageCache.js'; * Retrieves an appropriate caching backend based on the environment configuration. * Attempts to use custom cache, browser cache, or file system cache in that order of priority. * @returns {Promise<CacheInterface | null>} - * @param file_cache_dir {string|null} Path to a directory in which a downloaded pretrained model configuration should be cached if using the file system cache. + * @param file_cache_dir {string|null} Path to a directory where downloaded model files should be cached when using the file system cache. */ export async function getCache(file_cache_dir = null) { // First, check if the a caching backend is available diff --git a/packages/transformers/src/utils/generic.js b/packages/transformers/src/utils/generic.js index 3e8fc290f..ac3896313 100644 --- a/packages/transformers/src/utils/generic.js +++ b/packages/transformers/src/utils/generic.js @@ -1,6 +1,6 @@ /** * A base class for creating callable objects. - * See [here](https://stackoverflow.com/q/76073890) for more information. + * See this [Stack Overflow discussion](https://stackoverflow.com/q/76073890) for more information. * * @type {new () => {(...args: any[]): any, _call(...args: any[]): any}} */ diff --git a/packages/transformers/src/utils/hub.js b/packages/transformers/src/utils/hub.js index 3d5f48080..248e8b05f 100755 --- a/packages/transformers/src/utils/hub.js +++ b/packages/transformers/src/utils/hub.js @@ -25,11 +25,11 @@ export { MAX_EXTERNAL_DATA_CHUNKS } from './hub/constants.js'; /** * @typedef {Object} PretrainedOptions Options for loading a pretrained model. - * @property {import('./core.js').ProgressCallback} [progress_callback=null] If specified, this function will be called during model construction, to provide the user with progress updates. - * @property {import('../configs.js').PretrainedConfig} [config=null] Configuration for the model to use instead of an automatically loaded configuration. Configuration can be automatically loaded when: + * @property {import('./core.js').ProgressCallback} [progress_callback=null] If specified, this function is called during model construction with progress updates. + * @property {import('../configs.js').PretrainedConfig} [config=null] Configuration to use for the model instead of an automatically loaded configuration. Configuration can be automatically loaded when: * - The model is provided by the library and loaded with the *model ID* string of a pretrained model. * - The model is loaded by supplying a local directory as `pretrained_model_name_or_path` and a configuration JSON file named *config.json* is found in the directory. - * @property {string} [cache_dir=null] Path to a directory in which a downloaded pretrained model configuration should be cached if the standard cache should not be used. + * @property {string} [cache_dir=null] Path to a directory where downloaded model files should be cached if the standard cache should not be used. * @property {boolean} [local_files_only=false] Whether to only look at local files (e.g., not try downloading the model). * @property {string} [revision='main'] The model revision to use. This can be a branch name, tag name, or commit ID. * Because the Hub uses Git-based storage, `revision` can be any identifier accepted by Git. Ignored for local requests. @@ -39,11 +39,11 @@ export { MAX_EXTERNAL_DATA_CHUNKS } from './hub/constants.js'; * @typedef {Object} ModelSpecificPretrainedOptions Options for loading a pretrained model. * @property {string} [subfolder='onnx'] In case the relevant files are located inside a subfolder of the model repo on huggingface.co, * you can specify the folder name here. - * @property {string} [model_file_name=null] If specified, load the model with this name (excluding the dtype and .onnx suffixes). This is currently valid only for encoder- or decoder-only models. + * @property {string} [model_file_name=null] Override the base ONNX model file name, excluding dtype and `.onnx` suffixes. This is most useful for single-session models. * @property {import("./devices.js").DeviceType|Record<string, import("./devices.js").DeviceType>} [device=null] The device to run the model on. If not specified, the device will be chosen from the environment settings. * @property {import("./dtypes.js").DataType|Record<string, import("./dtypes.js").DataType>} [dtype=null] The data type to use for the model. If not specified, the data type will be chosen from the environment settings. - * @property {ExternalData|Record<string, ExternalData>} [use_external_data_format=false] Whether to load the model using the external data format (used for models >= 2GB in size). - * @property {import('onnxruntime-common').InferenceSession.SessionOptions} [session_options] (Optional) User-specified session options passed to the runtime. If not provided, suitable defaults will be chosen. + * @property {ExternalData|Record<string, ExternalData>} [use_external_data_format=null] Whether to load external data files. `null` uses the model configuration; `false` disables external data; `true` uses one chunk; a number selects the chunk count. + * @property {import('onnxruntime-common').InferenceSession.SessionOptions} [session_options={}] User-specified ONNX Runtime session options. Suitable defaults are filled in when omitted. */ /** From f10f9df2056c2ef86b85385d755e138dd2c568ff Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 8 May 2026 16:37:30 -0400 Subject: [PATCH 064/103] improve docs --- .ai/AGENTS.md | 9 +- README.md | 2 +- .../transformers/docs/scripts/build_readme.js | 32 ++++--- .../transformers/docs/scripts/generate-all.js | 5 ++ packages/transformers/docs/scripts/lib/ir.mjs | 10 +-- .../transformers/docs/scripts/lib/parse.mjs | 6 -- .../docs/scripts/lib/render-api.mjs | 90 ++++++++++++++++++- .../docs/scripts/lib/render-skill.mjs | 4 - .../transformers/docs/scripts/validate.js | 11 +++ .../transformers/docs/source/_toctree.yml | 2 + packages/transformers/package.json | 3 +- packages/transformers/src/cache_utils.js | 6 +- .../src/models/auto/processing_auto.js | 2 +- 13 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 packages/transformers/docs/scripts/validate.js diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 6bf789c88..5316009de 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -48,9 +48,12 @@ which generates: `packages/transformers/src/**/*.js`. - Generated sections in `.ai/skills/transformers-js/SKILL.md`. - `.ai/skills/transformers-js/references/TASKS.md`. +- The repo-root `README.md` from `packages/transformers/docs/snippets/*.snippet`. -Do not edit generated API pages or generated skill reference files by hand. Update -the source JSDoc, docs snippets, or generator modules instead. +Do not edit generated API pages, the generated skill reference files, or the +root README by hand. Update the source JSDoc, docs snippets, or generator +modules instead. The generator also validates generated API pages against `docs/source/_toctree.yml` -and checks local Markdown links and anchors under `docs/source/`. +and checks local Markdown links and anchors under `docs/source/`. To run only +the validator (without regenerating), use `pnpm --filter @huggingface/transformers docs-validate`. diff --git a/README.md b/README.md index d93646298..1f7afa294 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ We recommend using [Optimum](https://github.com/huggingface/optimum-onnx) to con ## Supported tasks/models -Here is the list of all tasks and architectures currently supported by Transformers.js. If you don't see your task/model listed here or it is not yet supported, feel free to open up a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). +Here is the list of all tasks and architectures currently supported by Transformers.js. If you don't see your task/model listed here or it is not yet supported, feel free to open a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). To find compatible models on the Hub, select the "transformers.js" library tag in the filter menu (or visit [this link](https://huggingface.co/models?library=transformers.js)). You can refine your search by selecting the task you're interested in (e.g., [text-classification](https://huggingface.co/models?pipeline_tag=text-classification&library=transformers.js)). diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index 29c9ec63c..3d8493779 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -4,10 +4,7 @@ import url from "node:url"; import { DOCS_BASE_URL, buildApiSymbolLinks } from "./lib/api-links.mjs"; import { loadProject } from "./lib/load.mjs"; - -const scriptFile = url.fileURLToPath(import.meta.url); -const docsDir = path.dirname(path.dirname(scriptFile)); -const packageRoot = path.dirname(docsDir); +import { packageRoot } from "./lib/paths.mjs"; const FILES_TO_INCLUDE = { intro: "./docs/snippets/0_introduction.snippet", @@ -30,15 +27,26 @@ const CUSTOM_LINK_MAP = { "./guides/dtypes": `${DOCS_BASE_URL}/guides/dtypes`, }; -function main() { - const { out } = parseArgs(process.argv.slice(2)); - const { ir, publicNames } = loadProject(packageRoot); +// Default output: the repo root README. Resolved relative to packageRoot +// (`packages/transformers`) so the path works from any cwd. +const DEFAULT_README_OUT = "../../README.md"; + +export function buildReadme({ project, out = DEFAULT_README_OUT } = {}) { + const { ir, publicNames } = project ?? loadProject(packageRoot); const apiLinks = buildApiSymbolLinks(ir, publicNames); const snippets = Object.fromEntries(Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(path.join(packageRoot, file), "utf8")])); snippets.customUsage = demoteHeadings(snippets.customUsage); const readme = fixLinks(renderTemplate(snippets), apiLinks); - fs.writeFileSync(path.resolve(packageRoot, out), readme, "utf8"); + const target = path.resolve(packageRoot, out); + fs.writeFileSync(target, readme, "utf8"); + return target; +} + +function main() { + const { out } = parseArgs(process.argv.slice(2)); + const target = buildReadme({ out }); + console.log(`wrote ${path.relative(process.cwd(), target)}`); } function parseArgs(args) { @@ -48,7 +56,7 @@ function parseArgs(args) { } return { - out: outIndex === -1 ? "README.md" : args[outIndex + 1], + out: outIndex === -1 ? DEFAULT_README_OUT : args[outIndex + 1], }; } @@ -89,7 +97,7 @@ ${customUsage} ## Supported tasks/models -Here is the list of all tasks and architectures currently supported by Transformers.js. If you don't see your task/model listed here or it is not yet supported, feel free to open up a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). +Here is the list of all tasks and architectures currently supported by Transformers.js. If you don't see your task/model listed here or it is not yet supported, feel free to open a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). To find compatible models on the Hub, select the "transformers.js" library tag in the filter menu (or visit [this link](https://huggingface.co/models?library=transformers.js)). You can refine your search by selecting the task you're interested in (e.g., [text-classification](https://huggingface.co/models?pipeline_tag=text-classification&library=transformers.js)). @@ -119,4 +127,6 @@ function fixLinks(markdown, apiLinks) { }); } -main(); +if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { + main(); +} diff --git a/packages/transformers/docs/scripts/generate-all.js b/packages/transformers/docs/scripts/generate-all.js index 01f2b2ceb..473a324f3 100644 --- a/packages/transformers/docs/scripts/generate-all.js +++ b/packages/transformers/docs/scripts/generate-all.js @@ -1,5 +1,8 @@ #!/usr/bin/env node +import path from "node:path"; + +import { buildReadme } from "./build_readme.js"; import { generateApiDocs } from "./lib/generate-api.mjs"; import { generateSkillDocs } from "./lib/generate-skill.mjs"; import { loadProject } from "./lib/load.mjs"; @@ -10,6 +13,8 @@ const project = loadProject(packageRoot); generateApiDocs({ project }); generateSkillDocs({ project }); +const readmePath = buildReadme({ project }); +console.log(`wrote ${path.relative(process.cwd(), readmePath)}`); const validation = validateGeneratedDocs(); console.log(formatValidationResult(validation)); diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index 225b5a0d6..8669d30a6 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -158,7 +158,6 @@ function ingest(entities, mod) { name: cls.name, description, examples, - skillExamples: tagsOf(cls, "skillExample").map((t) => t.task), members: cls.members.filter((m) => !isPrivate(m)).map(buildMember), callable: null, }); @@ -258,7 +257,6 @@ function buildCallable(fn) { .map((t) => ({ name: t.name, type: t.type })) .filter((t) => t.name), examples, - skillExamples: tagsOf(fn, "skillExample").map((t) => t.task), deprecated: fn.tags.some((t) => t.tag === "deprecated"), }; } @@ -274,10 +272,14 @@ function attachClassCallables(modules) { const source = callback ?? (callMember && hasCallableShape(callMember) ? callMember : null); if (!source) continue; + // Promote the `_call` / `Callback` description onto the synthesized + // signature so the rendered `Foo(...)` line carries prose explaining + // what calling the instance does. Without this the page shows only the + // bare signature. cls.callable = { ...normalizeCallable(source), name: cls.name, - description: "", + description: source.description ?? "", deprecated: false, }; } @@ -294,7 +296,6 @@ function normalizeCallable(callable) { throws: cloned.throws ?? [], templates: cloned.templates ?? [], examples: cloned.examples ?? [], - skillExamples: cloned.skillExamples ?? [], }; } @@ -314,7 +315,6 @@ function parseCallableTypedef(td) { throws: [], templates: parsed.templates, examples: [], - skillExamples: [], deprecated: false, }; } diff --git a/packages/transformers/docs/scripts/lib/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs index c978665b4..0e1ea3942 100644 --- a/packages/transformers/docs/scripts/lib/parse.mjs +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -95,12 +95,6 @@ function parseTag(raw) { return { tag, description: rest.trim() }; case "example": return { tag, body: rest }; - case "skillExample": - return { tag, task: rest.trim() }; - case "skillCategory": - return { tag, slug: rest.trim() }; - case "doctest": - return { tag }; default: return { tag, type, description: rest.trim() }; } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index cf8497167..caaa4b3ae 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -8,10 +8,15 @@ import path from "node:path"; import { apiMemberAnchor, apiSymbolAnchor } from "./api-links.mjs"; import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; +// Pages with at least this many top-level items get an "On this page" TOC so +// the reader doesn't have to scroll an entire page to find a class. +const TOC_THRESHOLD = 6; + export function renderModule(mod, ir, opts = {}) { const publicNames = opts.publicNames ?? null; const ctx = { typedefIndex: ir.typedefIndex, + allModules: ir.modules, moduleName: mod.name, renderedNames: buildRenderedNameIndex(ir, publicNames), callableLinks: buildCallableLinkIndex(ir, publicNames), @@ -26,6 +31,8 @@ export function renderModule(mod, ir, opts = {}) { if (mod.description) out.push(mod.description.trim(), ""); for (const ex of mod.examples) out.push(...renderExample(ex)); + out.push(...renderTOC({ classes, functions, constants }, ctx)); + if (classes.length) { out.push("## Classes", ""); for (const cls of classes) out.push(...renderClass(cls, ctx)); @@ -55,6 +62,29 @@ export function renderModule(mod, ir, opts = {}) { ); } +// Render a one-line "On this page" TOC, but only when the page is large +// enough to make scrolling expensive. We list classes, functions, and +// constants — typedefs and callbacks are usually a much longer tail and +// would dominate the TOC; readers can find them via the section heading. +function renderTOC({ classes, functions, constants }, ctx) { + const total = classes.length + functions.length + constants.length; + if (total < TOC_THRESHOLD) return []; + + const groups = [ + ["Classes", classes, (c) => apiSymbolAnchor(ctx.moduleName, c.name)], + ["Functions", functions, (f) => apiSymbolAnchor(ctx.moduleName, f.name)], + ["Constants", constants, (c) => apiSymbolAnchor(ctx.moduleName, c.name)], + ]; + + const lines = ["## On this page", ""]; + for (const [title, items, anchorOf] of groups) { + if (!items.length) continue; + const links = items.map((it) => `[\`${it.name}\`](#${anchorOf(it)})`).join(" · "); + lines.push(`**${title}** — ${links}`, ""); + } + return lines; +} + function buildRenderedNameIndex(ir, publicNames) { const names = new Set(); const ctx = { typedefIndex: ir.typedefIndex, renderedNames: names }; @@ -252,11 +282,41 @@ function renderParamNode(p, ctx, indent) { const type = p.type ? ` (${renderType(p.type, ctx)})` : ""; const opt = p.optional ? " _optional_" : ""; const def = p.defaultValue != null ? ` — defaults to \`${p.defaultValue}\`` : ""; - const desc = p.description ? ` — ${indentContinuations(p.description.trim(), contPad)}` : ""; + // Fall back to the referenced typedef's first-sentence description when the + // author didn't write one inline. Without this, parameters typed as a named + // typedef render as a bare `- name (Type) optional` with no prose hint. + const fallback = p.description ? null : typedefSummary(p.type, ctx); + const descText = p.description?.trim() || fallback; + const desc = descText ? ` — ${indentContinuations(descText, contPad)}` : ""; const line = `${pad}- \`${simpleName(p.name)}\`${type}${opt}${def}${desc}`; return [line, ...p.children.flatMap((c) => renderParamNode(c, ctx, indent + 1))]; } +// Look up the named typedef for a parameter's type and return the first +// sentence of its description, if any. Skips obvious wrappers (unions, +// arrays, generics) to avoid misleading borrows. +function typedefSummary(rawType, ctx) { + if (!rawType || !ctx.typedefIndex) return null; + const pretty = prettifyTypeString(rawType); + if (!SIMPLE_NAME.test(pretty)) return null; + const moduleName = ctx.typedefIndex.get(pretty); + if (!moduleName) return null; + const mod = ctx.allModules?.find?.((m) => m.name === moduleName); + const def = mod?.typedefs.find((t) => t.name === pretty) ?? mod?.callbacks.find((c) => c.name === pretty); + if (!def?.description) return null; + return firstSentenceShort(def.description); +} + +function firstSentenceShort(text) { + const collapsed = text + .split(/\n\s*\n/, 1)[0] + .replace(/\s+/g, " ") + .trim(); + const m = collapsed.match(/^(.+?[.!?])(?=\s|$)/); + const sentence = (m ? m[1] : collapsed).trim(); + return sentence.endsWith(".") || sentence.endsWith("!") || sentence.endsWith("?") ? sentence : sentence + "."; +} + // Indent every line after the first so multi-line descriptions (including // bulleted continuations) render inside their parent list item. function indentContinuations(text, pad) { @@ -374,7 +434,13 @@ export function renderType(raw, ctx, opts = {}) { const unionParts = splitTopLevel(pretty, "|") .map((p) => p.trim()) .filter((p) => !/^_[A-Za-z]/.test(p)); - if (unionParts.length > 1) return unionParts.map((p) => renderType(p, ctx)).join(" | "); + if (unionParts.length > 1) { + // Common shorthand `T | T[]` (in either order) — collapse to a single link + // marked as "single or array" so the reader doesn't see two identical links. + const collapsed = collapseSingleOrArray(unionParts, ctx); + if (collapsed) return collapsed; + return unionParts.map((p) => renderType(p, ctx)).join(" | "); + } if (unionParts.length === 1 && unionParts[0] !== pretty.trim()) return renderType(unionParts[0], ctx); // Same for intersections (`A & B`): drop internal variants before joining. @@ -435,6 +501,26 @@ function renderArrayType(innerRaw, ctx) { return code ? `\`${code[1]}[]\`` : `${rendered}[]`; } +// `T | T[]` (in either order) collapses to one link with a `[]?` suffix — +// readers don't have to chase two identical-looking links to learn it accepts +// either a single value or an array. Returns null when the union doesn't +// match the shape. The single rendered link/text gets `[]?` appended: +// `Foo` → `` `Foo[]?` ``, `[`Foo`](url)` → `[`Foo[]?`](url)`. +function collapseSingleOrArray(parts, ctx) { + if (parts.length !== 2) return null; + const arrayPart = parts.find((p) => p.endsWith("[]")); + const singlePart = parts.find((p) => !p.endsWith("[]")); + if (!arrayPart || !singlePart) return null; + if (arrayPart.slice(0, -2).trim() !== singlePart.trim()) return null; + const inner = renderType(singlePart, ctx); + // Linked form `[`Name`](url)` — splice `[]?` into the label. + const linked = inner.match(/^\[`([^`]+)`\]\(([^)]+)\)$/); + if (linked) return `[\`${linked[1]}[]?\`](${linked[2]})`; + const code = inner.match(/^`([^`]+)`$/); + if (code) return `\`${code[1]}[]?\``; + return `${inner}[]?`; +} + function renderTupleType(innerRaw, ctx) { const parts = splitTopLevel(innerRaw, ","); if (parts.length === 1 && !parts[0].trim()) return "`[]`"; diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index bd10b9fa3..82a66ccb3 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -391,10 +391,6 @@ function finalize(lines) { ); } -function filterPublic(items, publicNames) { - return publicNames ? items.filter((it) => publicNames.has(it.name)) : items; -} - function aliasesFor(taskId, tasks) { return [...tasks.aliases.entries()].filter(([, t]) => t === taskId).map(([a]) => a); } diff --git a/packages/transformers/docs/scripts/validate.js b/packages/transformers/docs/scripts/validate.js new file mode 100644 index 000000000..60145292f --- /dev/null +++ b/packages/transformers/docs/scripts/validate.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +// Validate the generated docs without regenerating them. Useful in CI as a +// fast check that the committed `docs/source/api/` markdown is consistent +// with `docs/source/_toctree.yml` and that internal links resolve. + +import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mjs"; + +const validation = validateGeneratedDocs(); +console.log(formatValidationResult(validation)); +if (!validation.ok) process.exitCode = 1; diff --git a/packages/transformers/docs/source/_toctree.yml b/packages/transformers/docs/source/_toctree.yml index 13fde33fa..0a6349615 100644 --- a/packages/transformers/docs/source/_toctree.yml +++ b/packages/transformers/docs/source/_toctree.yml @@ -69,6 +69,8 @@ title: Stopping Criteria - local: api/generation/streamers title: Streamers + - local: api/generation/cache + title: Cache title: Generation isExpanded: false - sections: diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 4d5d7d011..ae79c609a 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -28,8 +28,9 @@ "dev": "node scripts/dev.mjs", "build": "node scripts/build.mjs && pnpm typegen", "test": "node --experimental-vm-modules --expose-gc node_modules/jest/bin/jest.js --verbose --logHeapUsage", - "readme": "node ./docs/scripts/build_readme.js --out ../../README.md", + "readme": "node ./docs/scripts/build_readme.js", "docs-generate": "node ./docs/scripts/generate-all.js", + "docs-validate": "node ./docs/scripts/validate.js", "docs-build": "doc-builder build transformers.js ./docs/source/ --not_python_module --build_dir ./docs/build/", "docs-preview": "doc-builder preview transformers.js ./docs/source/ --not_python_module" }, diff --git a/packages/transformers/src/cache_utils.js b/packages/transformers/src/cache_utils.js index bfe356ab3..f49dcbab3 100644 --- a/packages/transformers/src/cache_utils.js +++ b/packages/transformers/src/cache_utils.js @@ -1,5 +1,9 @@ /** - * @module models + * @file Cache classes used by `generate()` to preserve and reuse decoder + * past key/value tensors across calls. Pass a `DynamicCache` through + * generation options when you need to persist KV state between turns. + * + * @module generation/cache */ import { Tensor } from './utils/tensor.js'; diff --git a/packages/transformers/src/models/auto/processing_auto.js b/packages/transformers/src/models/auto/processing_auto.js index 60ded9141..ff24aca8b 100644 --- a/packages/transformers/src/models/auto/processing_auto.js +++ b/packages/transformers/src/models/auto/processing_auto.js @@ -16,7 +16,7 @@ import * as AllFeatureExtractors from '../feature_extractors.js'; /** * Loads a processor from a pretrained id. Unlike `AutoImageProcessor` and - * `AutoFeatureExtractor`, `AutoProcessor` returns a multi-modal [`Processor`](#processor) + * `AutoFeatureExtractor`, `AutoProcessor` returns a multi-modal {@link Processor} * that bundles together a tokenizer, image processor, and/or feature extractor * — use it when a single model needs more than one. * From d84a8c7a9863b3a3b28890c89e527a05820c36b5 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 8 May 2026 20:23:52 -0400 Subject: [PATCH 065/103] update skills/agents docs --- .ai/AGENTS.md | 23 +++++ .ai/skills/transformers-js/SKILL.md | 20 +++++ .../references/CONFIGURATION.md | 7 ++ .../references/PIPELINE_OPTIONS.md | 11 +++ .../transformers/docs/scripts/generate-all.js | 2 +- packages/transformers/docs/scripts/lib/ir.mjs | 52 +----------- .../docs/scripts/lib/render-api.mjs | 41 ++------- .../transformers/docs/scripts/lib/scan.mjs | 63 ++++++++++++++ .../docs/scripts/lib/validate.mjs | 83 +++++++++++++++---- packages/transformers/src/utils/tensor.js | 8 +- 10 files changed, 208 insertions(+), 102 deletions(-) create mode 100644 packages/transformers/docs/scripts/lib/scan.mjs diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 5316009de..7670f557e 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -57,3 +57,26 @@ modules instead. The generator also validates generated API pages against `docs/source/_toctree.yml` and checks local Markdown links and anchors under `docs/source/`. To run only the validator (without regenerating), use `pnpm --filter @huggingface/transformers docs-validate`. + +### Workflow after touching JSDoc + +1. Edit the JSDoc in `packages/transformers/src/**/*.js`. +2. Run `pnpm --filter @huggingface/transformers docs-generate` from the repo root. +3. Inspect the diff under `packages/transformers/docs/source/api/` — that's the + page a human or agent will read. If the JSDoc didn't render the way you + expected, tweak the source, not the generated markdown. +4. Watch for "doc-quality warnings" at the end of the generator's output — + these flag undocumented public exports and malformed `@param` tags. +5. Stage the source files; the generated `api/**/*.md` files are gitignored. + +### What is generated vs. hand-written + +| Path | Generated? | +|------|------------| +| `packages/transformers/docs/source/api/**/*.md` | yes — gitignored | +| `packages/transformers/docs/source/{guides,tutorials,integrations}/*.md` | hand-written | +| `packages/transformers/docs/source/_toctree.yml` | hand-written (must list every generated API page) | +| `.ai/skills/transformers-js/SKILL.md` | mixed — prose hand-written, blocks between `<!-- @generated:start ... -->` markers regenerated | +| `.ai/skills/transformers-js/references/TASKS.md` | fully generated, do not edit | +| `.ai/skills/transformers-js/references/{CONFIGURATION,PIPELINE_OPTIONS}.md` | mixed (same marker convention as SKILL.md) | +| `README.md` | generated from `packages/transformers/docs/snippets/*.snippet` | diff --git a/.ai/skills/transformers-js/SKILL.md b/.ai/skills/transformers-js/SKILL.md index 580fab8d5..66378f222 100644 --- a/.ai/skills/transformers-js/SKILL.md +++ b/.ai/skills/transformers-js/SKILL.md @@ -73,6 +73,26 @@ Browse models compatible with transformers.js on the Hub: Filter by task with the `pipeline_tag` parameter, e.g. <https://huggingface.co/models?library=transformers.js&pipeline_tag=text-generation>. +**Before recommending a model**, confirm it actually has ONNX weights — the +library cannot load a model without them. Two ways to check: + +1. Open the model page on the Hub and look for an `onnx/` directory in the + "Files and versions" tab. +2. Programmatically, with `ModelRegistry.get_available_dtypes(modelId)` — + returns the list of dtypes shipped, or `[]` if no ONNX files exist. + +```javascript +import { ModelRegistry } from "@huggingface/transformers"; + +const dtypes = await ModelRegistry.get_available_dtypes("Xenova/some-model"); +if (dtypes.length === 0) { + // No ONNX files — not usable with transformers.js. +} +``` + +Don't suggest a model without verifying this; the failure mode at runtime is a +download error that's harder to diagnose than a pre-flight check. + ### Quantization Most pipelines accept a `dtype` option. Smaller dtypes download and run faster diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index f1b649cd4..420c6f736 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -91,6 +91,13 @@ env.remoteHost = "https://cdn.example.com"; env.useBrowserCache = true; ``` +**Browser app, no CDN, just persist downloads via the Cache API:** + +```javascript +env.useBrowserCache = true; // default when Cache API is available +// First visit downloads from the Hub; subsequent visits hit the cache. +``` + ## Custom fetch (private / gated models, retries, etc.) Override `env.fetch` to inject auth headers, retry logic, or abort signals: diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index ae86e990f..9ffa71f40 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -142,6 +142,17 @@ Every field below can be passed as an option to a `text-generation`, `text2text-generation`, `translation`, or `summarization` pipeline call, or anywhere `generate()` is invoked directly. +**Pairs of fields with overlapping meanings:** + +- `max_new_tokens` overrides `max_length`. Pass one, not both — `max_new_tokens` + is usually what you want because it doesn't depend on the prompt length. +- `min_new_tokens` overrides `min_length` for the same reason. +- Sampling vs. greedy: setting `do_sample: true` enables `temperature`, + `top_k`, `top_p`, and `typical_p`. With `do_sample: false` (the default), + those values are silently ignored. +- Sampling vs. beams: `num_beams > 1` switches to beam search and `do_sample` + is ignored. Don't set both. + <!-- @generated:start id=fields:GenerationConfig --> | Option | Type | Description | |--------|------|-------------| diff --git a/packages/transformers/docs/scripts/generate-all.js b/packages/transformers/docs/scripts/generate-all.js index 473a324f3..addd9d8c4 100644 --- a/packages/transformers/docs/scripts/generate-all.js +++ b/packages/transformers/docs/scripts/generate-all.js @@ -16,6 +16,6 @@ generateSkillDocs({ project }); const readmePath = buildReadme({ project }); console.log(`wrote ${path.relative(process.cwd(), readmePath)}`); -const validation = validateGeneratedDocs(); +const validation = validateGeneratedDocs({ project }); console.log(formatValidationResult(validation)); if (!validation.ok) process.exitCode = 1; diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index 8669d30a6..afba97541 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -2,6 +2,8 @@ // one `@module <name>` declaration — its output is `docs/api/<name>.md`. import path from "node:path"; + +import { findTopLevel, splitTopLevel } from "./scan.mjs"; import { callableReferenceKey, parseUtilityType, UTILITY_TYPES } from "./type-refs.mjs"; export function buildIR(fileEntities) { @@ -400,56 +402,6 @@ function matchingBracket(text, start, open, close) { return -1; } -function findTopLevel(text, needle) { - let depth = 0; - let inStr = null; - for (let i = 0; i < text.length; i++) { - const ch = text[i]; - if (inStr) { - if (ch === inStr && text[i - 1] !== "\\") inStr = null; - } else if (ch === '"' || ch === "'" || ch === "`") { - inStr = ch; - } else if ("<({[".includes(ch)) { - depth++; - } else if (">)}]".includes(ch)) { - depth--; - } else if (depth === 0 && ch === needle) { - return i; - } - } - return -1; -} - -function splitTopLevel(text, sep) { - const out = []; - let depth = 0; - let inStr = null; - let buf = ""; - for (let i = 0; i < text.length; i++) { - const ch = text[i]; - if (inStr) { - if (ch === inStr && text[i - 1] !== "\\") inStr = null; - buf += ch; - } else if (ch === '"' || ch === "'" || ch === "`") { - inStr = ch; - buf += ch; - } else if ("<({[".includes(ch)) { - depth++; - buf += ch; - } else if (">)}]".includes(ch)) { - depth--; - buf += ch; - } else if (depth === 0 && ch === sep) { - out.push(buf); - buf = ""; - } else { - buf += ch; - } - } - if (buf) out.push(buf); - return out.length > 1 ? out : [text]; -} - function resolveCallableAliases(modules) { const callableIndex = buildCallableIndex(modules); diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index caaa4b3ae..26a4d4d62 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -6,6 +6,7 @@ import path from "node:path"; import { apiMemberAnchor, apiSymbolAnchor } from "./api-links.mjs"; +import { splitTopLevel } from "./scan.mjs"; import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; // Pages with at least this many top-level items get an "On this page" TOC so @@ -16,7 +17,7 @@ export function renderModule(mod, ir, opts = {}) { const publicNames = opts.publicNames ?? null; const ctx = { typedefIndex: ir.typedefIndex, - allModules: ir.modules, + moduleByName: new Map(ir.modules.map((m) => [m.name, m])), moduleName: mod.name, renderedNames: buildRenderedNameIndex(ir, publicNames), callableLinks: buildCallableLinkIndex(ir, publicNames), @@ -301,7 +302,7 @@ function typedefSummary(rawType, ctx) { if (!SIMPLE_NAME.test(pretty)) return null; const moduleName = ctx.typedefIndex.get(pretty); if (!moduleName) return null; - const mod = ctx.allModules?.find?.((m) => m.name === moduleName); + const mod = ctx.moduleByName?.get(moduleName); const def = mod?.typedefs.find((t) => t.name === pretty) ?? mod?.callbacks.find((c) => c.name === pretty); if (!def?.description) return null; return firstSentenceShort(def.description); @@ -577,38 +578,6 @@ function renderCallableReference(raw, ctx) { return linkCallable(ref.owner, ref.owner, ctx) ?? linkIfKnown(ref.owner, ctx) ?? `\`${ref.owner}\``; } -// Split `text` on `sep`, ignoring separators inside brackets, braces, parens, -// angle brackets, or string literals. -function splitTopLevel(text, sep) { - const out = []; - let depth = 0; - let inStr = null; - let buf = ""; - for (let i = 0; i < text.length; i++) { - const ch = text[i]; - if (inStr) { - if (ch === inStr && text[i - 1] !== "\\") inStr = null; - buf += ch; - } else if (ch === '"' || ch === "'" || ch === "`") { - inStr = ch; - buf += ch; - } else if ("<({[".includes(ch)) { - depth++; - buf += ch; - } else if (">)}]".includes(ch)) { - depth--; - buf += ch; - } else if (depth === 0 && ch === sep) { - out.push(buf); - buf = ""; - } else { - buf += ch; - } - } - if (buf) out.push(buf); - return out.length > 1 ? out : [text]; -} - // Strip noisy TS constructs from a type string without rewriting structure. // Conditional/mapped/infer types collapse to their outermost wrapper; simple // unions of names or long generic lists are preserved so the renderer can @@ -631,6 +600,10 @@ export function prettifyTypeString(raw) { // Built-in TS utility types are meaningless shorn of their arguments — the // reader is better served by `unknown` than by a naked `Parameters`. if (outer && TS_UTILITY_NAMES.has(outer[1])) return "unknown"; + // Generic wrappers like `Promise<...gnarly...>` need to keep the wrapper — + // a bare `Promise` is misleading. Replace the inner with `unknown` so the + // reader sees `Promise<unknown>` instead. + if (outer && /^[A-Za-z_$][\w$.]*</.test(s)) return `${outer[1]}<unknown>`; return outer ? outer[1] : "object"; } return s.replace(/\s+/g, " ").trim(); diff --git a/packages/transformers/docs/scripts/lib/scan.mjs b/packages/transformers/docs/scripts/lib/scan.mjs new file mode 100644 index 000000000..b24759de7 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/scan.mjs @@ -0,0 +1,63 @@ +// Bracket- and string-aware text scanners shared between IR-building and +// markdown rendering. Type strings can contain nested generics, tuples, +// function-type arrow lists, etc.; naive split/find on `,`/`|`/`&` would +// shred them. These helpers know to skip separators that appear inside +// `<>`, `()`, `{}`, `[]`, or quoted strings. + +const OPEN = "<({["; +const CLOSE = ">)}]"; + +// Return the index of the first occurrence of `needle` at top level (depth +// zero), or -1 if it doesn't appear outside brackets/strings. +export function findTopLevel(text, needle) { + let depth = 0; + let inStr = null; + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + } else if (OPEN.includes(ch)) { + depth++; + } else if (CLOSE.includes(ch)) { + depth--; + } else if (depth === 0 && ch === needle) { + return i; + } + } + return -1; +} + +// Split `text` on `sep`, ignoring separators inside brackets/strings. +// Returns `[text]` (the unsplit input) when no split occurred — callers +// can use that to detect "this was a single chunk all along". +export function splitTopLevel(text, sep) { + const out = []; + let depth = 0; + let inStr = null; + let buf = ""; + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + buf += ch; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + buf += ch; + } else if (OPEN.includes(ch)) { + depth++; + buf += ch; + } else if (CLOSE.includes(ch)) { + depth--; + buf += ch; + } else if (depth === 0 && ch === sep) { + out.push(buf); + buf = ""; + } else { + buf += ch; + } + } + if (buf) out.push(buf); + return out.length > 1 ? out : [text]; +} diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs index fbd15c17c..1218c0e93 100644 --- a/packages/transformers/docs/scripts/lib/validate.mjs +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -3,7 +3,7 @@ import path from "node:path"; import { apiOutputDir, toctreePath } from "./paths.mjs"; -export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toctreePath } = {}) { +export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toctreePath, project = null } = {}) { const generatedApiPages = listApiPages(outputDir); const linkedApiPages = readToctreeApiPages(tocPath); const sourceDir = path.dirname(tocPath); @@ -12,34 +12,85 @@ export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toct const unlisted = difference(generatedApiPages, linkedApiPages); const stale = difference(linkedApiPages, generatedApiPages); + // Source-quality warnings are advisory: they surface gaps in the JSDoc + // (undocumented exports, malformed `@param` lines) that the renderer can't + // fix on its own. They don't fail validation — broken links and a stale + // toctree do. + const docWarnings = project ? collectDocWarnings(project) : []; + return { ok: unlisted.length === 0 && stale.length === 0 && brokenLinks.length === 0, unlisted, stale, brokenLinks, + docWarnings, }; } export function formatValidationResult(result) { - if (result.ok) return "validated docs toctree"; - - const lines = ["docs validation failed"]; - if (result.unlisted.length) { - lines.push("", "Generated API pages missing from _toctree.yml:"); - for (const page of result.unlisted) lines.push(`- ${page}`); + const lines = []; + + if (result.ok) { + lines.push("validated docs toctree"); + } else { + lines.push("docs validation failed"); + if (result.unlisted.length) { + lines.push("", "Generated API pages missing from _toctree.yml:"); + for (const page of result.unlisted) lines.push(`- ${page}`); + } + if (result.stale.length) { + lines.push("", "API pages listed in _toctree.yml but not generated:"); + for (const page of result.stale) lines.push(`- ${page}`); + } + if (result.brokenLinks.length) { + lines.push("", "Broken local markdown links:"); + for (const link of result.brokenLinks) { + const anchor = link.anchor ? `#${link.anchor}` : ""; + lines.push(`- ${link.file}: ${link.target} -> ${link.resolved}${anchor} (${link.type})`); + } + } } - if (result.stale.length) { - lines.push("", "API pages listed in _toctree.yml but not generated:"); - for (const page of result.stale) lines.push(`- ${page}`); + + if (result.docWarnings?.length) { + lines.push("", `${result.docWarnings.length} doc-quality warning${result.docWarnings.length === 1 ? "" : "s"}:`); + for (const w of result.docWarnings) lines.push(`- ${w}`); } - if (result.brokenLinks.length) { - lines.push("", "Broken local markdown links:"); - for (const link of result.brokenLinks) { - const anchor = link.anchor ? `#${link.anchor}` : ""; - lines.push(`- ${link.file}: ${link.target} -> ${link.resolved}${anchor} (${link.type})`); + + return lines.join("\n"); +} + +// Walk the IR and report public exports whose JSDoc is empty (no description, +// no params, no example) and parameters that are missing a name. These don't +// fail the build but are worth surfacing during a normal docs-generate run. +function collectDocWarnings({ ir, publicNames }) { + const warnings = []; + const isPublic = (name) => !publicNames || publicNames.has(name); + + for (const mod of ir.modules) { + for (const cls of mod.classes) { + if (!isPublic(cls.name)) continue; + if (!cls.description && !cls.examples?.length) { + warnings.push(`${mod.name}: class \`${cls.name}\` has no description or example`); + } + for (const m of cls.members) { + if (m.kind !== "method") continue; + for (const p of m.params ?? []) { + if (!p.name) warnings.push(`${mod.name}: \`${cls.name}.${m.name}\` has a parameter with no name (check for malformed @param)`); + } + } + } + for (const fn of mod.functions) { + if (!isPublic(fn.name)) continue; + if (!fn.description && !fn.examples?.length) { + warnings.push(`${mod.name}: function \`${fn.name}\` has no description or example`); + } + for (const p of fn.params ?? []) { + if (!p.name) warnings.push(`${mod.name}: \`${fn.name}\` has a parameter with no name (check for malformed @param)`); + } } } - return lines.join("\n"); + + return warnings; } function listApiPages(outputDir) { diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 049a9a866..7d22f64c6 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -85,7 +85,13 @@ export class Tensor { ort_tensor; /** - * Create a new Tensor or copy an existing Tensor. + * Create a new Tensor. + * + * Two call shapes are supported: + * - `new Tensor(dataType, data, dims)` — build from raw data, e.g. + * `new Tensor('float32', new Float32Array([1, 2, 3]), [3])`. + * - `new Tensor(ortTensor)` — wrap an existing `onnxruntime` tensor. + * * @param {[DataType, DataArray, number[]]|[ONNXTensor]} args */ constructor(...args) { From d261841a1f8eb7e90f617acb79fbbe83433620d6 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 28 May 2026 16:07:37 -0400 Subject: [PATCH 066/103] make skills + api generation stricter --- .../transformers/docs/scripts/generate-all.js | 9 ++++-- .../docs/scripts/lib/generate-api.mjs | 12 ++------ .../docs/scripts/lib/generate-skill.mjs | 4 +-- .../docs/scripts/lib/render-api.mjs | 12 ++++++++ .../docs/scripts/lib/render-skill.mjs | 28 ++++++++++--------- 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/packages/transformers/docs/scripts/generate-all.js b/packages/transformers/docs/scripts/generate-all.js index addd9d8c4..080b09a6c 100644 --- a/packages/transformers/docs/scripts/generate-all.js +++ b/packages/transformers/docs/scripts/generate-all.js @@ -12,10 +12,15 @@ import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mj const project = loadProject(packageRoot); generateApiDocs({ project }); -generateSkillDocs({ project }); +const { errors: skillErrors } = generateSkillDocs({ project }); const readmePath = buildReadme({ project }); console.log(`wrote ${path.relative(process.cwd(), readmePath)}`); const validation = validateGeneratedDocs({ project }); console.log(formatValidationResult(validation)); -if (!validation.ok) process.exitCode = 1; +if (skillErrors?.length) { + console.log(""); + console.log(`skill generation failed with ${skillErrors.length} error${skillErrors.length === 1 ? "" : "s"}:`); + for (const err of skillErrors) console.log(`- ${err}`); +} +if (!validation.ok || skillErrors?.length) process.exitCode = 1; diff --git a/packages/transformers/docs/scripts/lib/generate-api.mjs b/packages/transformers/docs/scripts/lib/generate-api.mjs index c9fe56ef1..7ba0967cf 100644 --- a/packages/transformers/docs/scripts/lib/generate-api.mjs +++ b/packages/transformers/docs/scripts/lib/generate-api.mjs @@ -3,7 +3,7 @@ import path from "node:path"; import { apiOutputDir, packageRoot } from "./paths.mjs"; import { loadProject } from "./load.mjs"; -import { renderModule } from "./render-api.mjs"; +import { hasRenderableContent, renderModule } from "./render-api.mjs"; export function generateApiDocs({ project = loadProject(packageRoot), outputDir = apiOutputDir, log = console.log } = {}) { clearExistingMarkdown(outputDir); @@ -12,13 +12,13 @@ export function generateApiDocs({ project = loadProject(packageRoot), outputDir const skipped = []; for (const mod of project.ir.modules) { - const rendered = renderModule(mod, project.ir, { publicNames: project.publicNames }); - if (!hasPublicBody(rendered)) { + if (!hasRenderableContent(mod, project.publicNames)) { skipped.push(mod.name); log(`skipped ${mod.name}.md — no public content`); continue; } + const rendered = renderModule(mod, project.ir, { publicNames: project.publicNames }); const outputPath = path.resolve(outputDir, `${mod.name}.md`); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, rendered); @@ -29,12 +29,6 @@ export function generateApiDocs({ project = loadProject(packageRoot), outputDir return { written, skipped }; } -function hasPublicBody(markdown) { - if (/^## /m.test(markdown)) return true; - const body = markdown.replace(/^# [^\n]+\n/, ""); - return /\]\(/.test(body); -} - function clearExistingMarkdown(outputDir) { if (!fs.existsSync(outputDir)) return; for (const entry of fs.readdirSync(outputDir, { recursive: true })) { diff --git a/packages/transformers/docs/scripts/lib/generate-skill.mjs b/packages/transformers/docs/scripts/lib/generate-skill.mjs index 2da31b06d..07f0772e3 100644 --- a/packages/transformers/docs/scripts/lib/generate-skill.mjs +++ b/packages/transformers/docs/scripts/lib/generate-skill.mjs @@ -5,7 +5,7 @@ import { loadProject } from "./load.mjs"; import { renderSkill } from "./render-skill.mjs"; export function generateSkillDocs({ project = loadProject(packageRoot), skillDir = defaultSkillDir, log = console.log } = {}) { - renderSkill({ + const { errors } = renderSkill({ ir: project.ir, tasks: project.tasks, publicNames: project.publicNames, @@ -13,5 +13,5 @@ export function generateSkillDocs({ project = loadProject(packageRoot), skillDir }); log(`wrote skill to ${path.relative(repoRoot, skillDir) || skillDir}`); - return { skillDir }; + return { skillDir, errors }; } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 26a4d4d62..92593cd26 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -13,6 +13,18 @@ import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_U // the reader doesn't have to scroll an entire page to find a class. const TOC_THRESHOLD = 6; +// True when the module contributes any reader-visible content to its API page — +// at least one public class/function/constant, or a renderable typedef/callback. +// Used by `generateApiDocs` to skip writing pure-internal modules without +// having to inspect the rendered markdown. +export function hasRenderableContent(mod, publicNames = null) { + if (filterPublic(mod.classes, publicNames).length) return true; + if (filterPublic(mod.functions, publicNames).length) return true; + if (filterPublic(mod.constants, publicNames).length) return true; + if (mod.callbacks.length) return true; + return mod.typedefs.some((td) => !isInternalTypedef(td)); +} + export function renderModule(mod, ir, opts = {}) { const publicNames = opts.publicNames ?? null; const ctx = { diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index 82a66ccb3..8c286df61 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -14,7 +14,7 @@ const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/s const DOCS_SITE = "https://huggingface.co/docs/transformers.js/api"; export function renderSkill({ ir, tasks, publicNames, skillDir }) { - const ctx = { ir, tasks, publicNames: publicNames ?? null }; + const ctx = { ir, tasks, publicNames: publicNames ?? null, errors: [] }; // Rewrite fully-generated reference files. const refDir = path.join(skillDir, "references"); @@ -27,9 +27,11 @@ export function renderSkill({ ir, tasks, publicNames, skillDir }) { for (const file of walkMarkdown(skillDir)) { const original = fs.readFileSync(file, "utf8"); if (!original.includes("@generated:start")) continue; - const injected = injectMarkers(original, ctx); + const injected = injectMarkers(original, { ...ctx, file }); if (injected !== original) fs.writeFileSync(file, injected); } + + return { errors: ctx.errors }; } // Skill files live outside the docs site, so relative `./foo.md#bar` links — @@ -60,7 +62,7 @@ function injectMarkers(original, ctx) { return original.replace(MARKER_RE, (match, startTag, id, _body, endTag) => { const content = resolveMarker(id, ctx); if (content == null) { - console.warn(`skill: unknown marker id "${id}"`); + ctx.errors.push(`${ctx.file}: unknown marker id "${id}"`); return match; } return `${startTag}\n${absolutize(content).trimEnd()}\n${endTag}`; @@ -99,11 +101,11 @@ function resolveMarker(id, ctx) { function renderModuleExamples(moduleName, ctx) { const mod = ctx.ir.modules.find((m) => m.name === moduleName); if (!mod) { - console.warn(`skill: module "${moduleName}" not found`); + ctx.errors.push(`module "${moduleName}" not found (examples:${moduleName})`); return ""; } if (!mod.examples.length) { - console.warn(`skill: module "${moduleName}" has no example blocks`); + ctx.errors.push(`module "${moduleName}" has no example blocks (examples:${moduleName})`); return ""; } const lines = []; @@ -117,7 +119,7 @@ function renderModuleExamples(moduleName, ctx) { function renderFieldsTable(className, ctx) { const cls = findClass(ctx.ir, className); if (!cls) { - console.warn(`skill: class "${className}" not found`); + ctx.errors.push(`class "${className}" not found (fields:${className})`); return ""; } const fields = cls.members.filter((m) => m.kind === "field"); @@ -130,7 +132,7 @@ function renderFieldsTable(className, ctx) { defaultValue: f.defaultValue, description: f.description, })), - ) || warnEmpty(className) + ) || warnEmpty(className, ctx, `fields:${className}`) ); } @@ -168,7 +170,7 @@ function renderTaskRecipe(taskId, info, ctx) { function renderTypedefTable(name, ctx) { const td = findTypedef(ctx.ir, name); if (!td) { - console.warn(`skill: typedef "${name}" not found`); + ctx.errors.push(`typedef "${name}" not found (typedef:${name})`); return ""; } @@ -181,15 +183,15 @@ function renderTypedefTable(name, ctx) { if (!table) continue; parts.push(`**\`${variantName}\`**`, "", table, ""); } - return parts.length ? parts.join("\n").trimEnd() : warnEmpty(name); + return parts.length ? parts.join("\n").trimEnd() : warnEmpty(name, ctx, `typedef:${name}`); } const props = collectProperties(name, ctx.ir); - return propertiesTable(props) || warnEmpty(name); + return propertiesTable(props) || warnEmpty(name, ctx, `typedef:${name}`); } -function warnEmpty(name) { - console.warn(`skill: typedef "${name}" has no properties to render`); +function warnEmpty(name, ctx, marker) { + ctx.errors.push(`typedef "${name}" has no properties to render (${marker})`); return ""; } @@ -231,7 +233,7 @@ function splitUnion(type) { function renderClassSummary(name, ctx) { const cls = findClass(ctx.ir, name); if (!cls) { - console.warn(`skill: class "${name}" not found`); + ctx.errors.push(`class "${name}" not found (class:${name})`); return ""; } const lines = []; From 12afcbc28889c7a4717f2619d7adf04b7e20e8a3 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Fri, 29 May 2026 14:47:40 -0400 Subject: [PATCH 067/103] cleanup --- packages/transformers/docs/scripts/lib/ir.mjs | 2 +- .../transformers/docs/scripts/lib/load.mjs | 12 +++---- .../docs/scripts/lib/render-api.mjs | 17 ++-------- .../docs/scripts/lib/render-skill.mjs | 32 ++++--------------- .../transformers/docs/scripts/lib/text.mjs | 24 ++++++++++++++ .../docs/scripts/lib/validate.mjs | 12 +++---- 6 files changed, 43 insertions(+), 56 deletions(-) create mode 100644 packages/transformers/docs/scripts/lib/text.mjs diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index afba97541..194063841 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -494,7 +494,7 @@ function findCallable(ref, callableIndex) { } function clone(value) { - return JSON.parse(JSON.stringify(value)); + return structuredClone(value); } function tagsOf(entity, name) { diff --git a/packages/transformers/docs/scripts/lib/load.mjs b/packages/transformers/docs/scripts/lib/load.mjs index 6c49e88e2..72c0f234f 100644 --- a/packages/transformers/docs/scripts/lib/load.mjs +++ b/packages/transformers/docs/scripts/lib/load.mjs @@ -24,11 +24,9 @@ export function loadProject(root) { } function collectJsFiles(dir) { - const out = []; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) out.push(...collectJsFiles(full)); - else if (entry.name.endsWith(".js")) out.push(full); - } - return out; + return fs + .readdirSync(dir, { recursive: true }) + .filter((p) => p.endsWith(".js")) + .sort() + .map((p) => path.join(dir, p)); } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 92593cd26..ce512b5fa 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -7,6 +7,7 @@ import path from "node:path"; import { apiMemberAnchor, apiSymbolAnchor } from "./api-links.mjs"; import { splitTopLevel } from "./scan.mjs"; +import { firstSentence, stripDocArtifacts } from "./text.mjs"; import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; // Pages with at least this many top-level items get an "On this page" TOC so @@ -151,9 +152,7 @@ function renderExample(ex) { // reads as prose rather than raw JSDoc tags — `{@link ...}` is expanded later // by `expandInlineLinks`. function cleanDescription(text) { - return text - .trim() - .replace(/^\[`?([A-Za-z_$][\w$.]*)`?\]\s*/, "") + return stripDocArtifacts(text.trim()) .replace(/@see\s+(?=\{@link)/g, "") .replace(/@see\s+`?([A-Za-z_$][\w$.]*)`?/g, "`$1`"); } @@ -317,17 +316,7 @@ function typedefSummary(rawType, ctx) { const mod = ctx.moduleByName?.get(moduleName); const def = mod?.typedefs.find((t) => t.name === pretty) ?? mod?.callbacks.find((c) => c.name === pretty); if (!def?.description) return null; - return firstSentenceShort(def.description); -} - -function firstSentenceShort(text) { - const collapsed = text - .split(/\n\s*\n/, 1)[0] - .replace(/\s+/g, " ") - .trim(); - const m = collapsed.match(/^(.+?[.!?])(?=\s|$)/); - const sentence = (m ? m[1] : collapsed).trim(); - return sentence.endsWith(".") || sentence.endsWith("!") || sentence.endsWith("?") ? sentence : sentence + "."; + return firstSentence(def.description); } // Indent every line after the first so multi-line descriptions (including diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index 8c286df61..ae89967b5 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -10,6 +10,8 @@ import fs from "node:fs"; import path from "node:path"; +import { firstSentence } from "./text.mjs"; + const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-all.js -->"; const DOCS_SITE = "https://huggingface.co/docs/transformers.js/api"; @@ -45,13 +47,10 @@ function absolutize(markdown) { } function walkMarkdown(dir) { - const out = []; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) out.push(...walkMarkdown(full)); - else if (entry.name.endsWith(".md")) out.push(full); - } - return out; + return fs + .readdirSync(dir, { recursive: true }) + .filter((p) => p.endsWith(".md")) + .map((p) => path.join(dir, p)); } // ---------- marker injection ---------- @@ -407,22 +406,3 @@ function findClass(ir, name) { return null; } -// Extract the first complete sentence. A line break inside a paragraph is not -// a sentence boundary, so we re-flow a paragraph onto one line before cutting. -function firstSentence(text) { - if (!text) return "_(undocumented)_"; - const paragraph = text - .split(/\n\s*\n/, 1)[0] - .replace(/\s+/g, " ") - .trim(); - const sanitized = stripDocArtifacts(paragraph); - const match = sanitized.match(/^(.+?[.!?])(?=\s|$)/); - const sentence = (match ? match[1] : sanitized).trim(); - return sentence.endsWith(".") || sentence.endsWith("!") || sentence.endsWith("?") ? sentence : sentence + "."; -} - -// Descriptions copied from the Python library sometimes start with -// `[`TypeName`]` (reST cross-reference syntax). Drop the leading artifact. -function stripDocArtifacts(text) { - return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\]\s*/, ""); -} diff --git a/packages/transformers/docs/scripts/lib/text.mjs b/packages/transformers/docs/scripts/lib/text.mjs new file mode 100644 index 000000000..366217ef9 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/text.mjs @@ -0,0 +1,24 @@ +// Plain-text helpers shared between the API and skill renderers. + +// Extract the first complete sentence of a description. A line break inside a +// paragraph is not a sentence boundary, so re-flow the paragraph onto one line +// before cutting. Returns an empty string when given empty input — callers are +// expected to skip rendering rather than emit a placeholder. +export function firstSentence(text) { + if (!text) return ""; + const paragraph = stripDocArtifacts( + text + .split(/\n\s*\n/, 1)[0] + .replace(/\s+/g, " ") + .trim(), + ); + const match = paragraph.match(/^(.+?[.!?])(?=\s|$)/); + const sentence = (match ? match[1] : paragraph).trim(); + return /[.!?]$/.test(sentence) ? sentence : sentence + "."; +} + +// Descriptions copied from the Python library sometimes start with +// `[`TypeName`]` (reST cross-reference syntax). Drop the leading artifact. +export function stripDocArtifacts(text) { + return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\]\s*/, ""); +} diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs index 1218c0e93..40775682b 100644 --- a/packages/transformers/docs/scripts/lib/validate.mjs +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -101,14 +101,10 @@ function listApiPages(outputDir) { function listMarkdown(dir) { if (!fs.existsSync(dir)) return []; - - const out = []; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) out.push(...listMarkdown(full)); - else if (entry.name.endsWith(".md")) out.push(full); - } - return out; + return fs + .readdirSync(dir, { recursive: true }) + .filter((p) => p.endsWith(".md")) + .map((p) => path.join(dir, p)); } function toApiPage(outputDir, file) { From 264311ae76a52a71e09b33a62200593efa545622 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:18:53 -0400 Subject: [PATCH 068/103] Correct docs for default cache location --- .ai/skills/transformers-js/references/CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index 420c6f736..9b742af07 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -42,7 +42,7 @@ env.cacheDir = '/path/to/cache/directory/'; | `useFS` | `boolean` | Whether to use the file system to load files. By default, it is `true` if available. | | `useBrowserCache` | `boolean` | Whether to use Cache API to cache models. By default, it is `true` if available. | | `useFSCache` | `boolean` | Whether to use the file system to cache files. By default, it is `true` if available. | -| `cacheDir` | `string\|null` | The directory to use for caching files with the file system. By default, it is `./.cache`. | +| `cacheDir` | `string\|null` | The directory to use for caching files with the file system. By default, it is `.cache` relative to the library's installed location when a file system is available (e.g., Node.js), and `null` otherwise (e.g., browsers). | | `useCustomCache` | `boolean` | Whether to use a custom cache system (defined by `customCache`), defaults to `false`. | | `customCache` | `CacheInterface\|null` | The custom cache to use. Defaults to `null`. This must be an object that implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. | | `useWasmCache` | `boolean` | Whether to pre-load and cache WASM binaries and the WASM factory (.mjs) for ONNX Runtime. Defaults to `true` when cache is available. This can improve performance and enables offline usage by avoiding repeated downloads. | From 06e2719e6501a5145a3d6437c8c66b0b650fba94 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:33:42 -0400 Subject: [PATCH 069/103] ditto --- packages/transformers/src/env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transformers/src/env.js b/packages/transformers/src/env.js index 49f1627e8..6261ec479 100644 --- a/packages/transformers/src/env.js +++ b/packages/transformers/src/env.js @@ -213,7 +213,7 @@ export const LogLevel = Object.freeze({ * @property {boolean} useFS Whether to use the file system to load files. By default, it is `true` if available. * @property {boolean} useBrowserCache Whether to use Cache API to cache models. By default, it is `true` if available. * @property {boolean} useFSCache Whether to use the file system to cache files. By default, it is `true` if available. - * @property {string|null} cacheDir The directory to use for caching files with the file system. By default, it is `./.cache`. + * @property {string|null} cacheDir The directory to use for caching files with the file system. By default, it is `.cache` relative to the library's installed location when a file system is available (e.g., Node.js), and `null` otherwise (e.g., browsers). * @property {boolean} useCustomCache Whether to use a custom cache system (defined by `customCache`), defaults to `false`. * @property {import('./utils/cache.js').CacheInterface|null} customCache The custom cache to use. Defaults to `null`. This must be an object that * implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache. From 6b9bc0c1d99897d1a74c2d9a4c1e8805c1fc4462 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:34:30 -0400 Subject: [PATCH 070/103] use correct example format --- packages/transformers/src/generation/streamers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/transformers/src/generation/streamers.js b/packages/transformers/src/generation/streamers.js index 675f6580b..022c5634c 100644 --- a/packages/transformers/src/generation/streamers.js +++ b/packages/transformers/src/generation/streamers.js @@ -5,7 +5,8 @@ * the `streamer` argument of `generate()` to receive decoded text as tokens * are emitted — useful for chat UIs and incremental transcription. * - * @example + * **Example:** Stream generated text to stdout + * ```javascript * import { pipeline, TextStreamer } from '@huggingface/transformers'; * * const generator = await pipeline('text-generation', 'onnx-community/Qwen3-0.6B-ONNX'); @@ -14,6 +15,7 @@ * callback_function: (text) => process.stdout.write(text), * }); * await generator('Tell me a joke about JavaScript.', { max_new_tokens: 64, streamer }); + * ``` * * @module generation/streamers */ From b461c6e7ca0c8eec8eae3b087f20bb2d7799c283 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:34:42 -0400 Subject: [PATCH 071/103] Set default in jsdoc --- packages/transformers/src/configs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transformers/src/configs.js b/packages/transformers/src/configs.js index dc0e8a3ce..63bcff8bb 100644 --- a/packages/transformers/src/configs.js +++ b/packages/transformers/src/configs.js @@ -576,7 +576,7 @@ export class AutoConfig { * for more information. * @property {import('./utils/devices.js').DeviceType} [device] The default device to use for the model. * @property {import('./utils/dtypes.js').DataType|Record<string, import('./utils/dtypes.js').DataType>} [dtype] The default data type to use for the model. - * @property {import('./utils/hub.js').ExternalData|Record<string, import('./utils/hub.js').ExternalData>} [use_external_data_format] Whether to load the model using the external data format (used for models >= 2GB in size). + * @property {import('./utils/hub.js').ExternalData|Record<string, import('./utils/hub.js').ExternalData>} [use_external_data_format=null] Whether to load the model using the external data format (used for models >= 2GB in size). */ /** From b5e272c2ff3fb7d04af954a5309d582e482a9578 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:56:37 -0400 Subject: [PATCH 072/103] final updates --- .ai/AGENTS.md | 27 +++-- .ai/skills/transformers-js/SKILL.md | 39 +++++-- .../references/CONFIGURATION.md | 13 ++- .../references/PIPELINE_OPTIONS.md | 27 ++++- .../transformers-js/references/TASKS.md | 2 +- .github/workflows/tests.yml | 24 ++++ AGENTS.md | 1 + CLAUDE.md | 1 + CONTRIBUTING.md | 26 +++++ .../transformers/docs/scripts/build_readme.js | 22 +++- .../transformers/docs/scripts/generate-all.js | 41 +++++-- .../docs/scripts/lib/api-links.mjs | 16 +-- packages/transformers/docs/scripts/lib/fs.mjs | 16 +++ .../docs/scripts/lib/generate-api.mjs | 28 +++-- packages/transformers/docs/scripts/lib/ir.mjs | 39 +++---- .../transformers/docs/scripts/lib/load.mjs | 11 +- .../transformers/docs/scripts/lib/parse.mjs | 38 ++---- .../transformers/docs/scripts/lib/paths.mjs | 2 +- .../docs/scripts/lib/render-api.mjs | 106 ++++++++--------- .../docs/scripts/lib/render-skill.mjs | 110 +++++++----------- .../transformers/docs/scripts/lib/scan.mjs | 21 ++++ .../transformers/docs/scripts/lib/text.mjs | 60 +++++++++- .../docs/scripts/lib/validate.mjs | 62 +++++++--- .../transformers/docs/scripts/validate.js | 11 +- packages/transformers/package.json | 1 - packages/transformers/src/pipelines.js | 25 ++-- .../src/pipelines/image-classification.js | 4 +- packages/transformers/src/transformers.js | 1 + packages/transformers/src/utils/hub.js | 5 +- packages/transformers/src/utils/hub/utils.js | 41 ++++++- .../src/utils/model_registry/ModelRegistry.js | 9 +- .../model_registry/get_available_dtypes.js | 11 +- .../utils/model_registry/get_file_metadata.js | 95 ++++++++------- .../test_pipelines_image_classification.js | 14 +++ .../tests/utils/model_registry.test.js | 59 ++++++++++ 35 files changed, 675 insertions(+), 333 deletions(-) create mode 120000 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 packages/transformers/docs/scripts/lib/fs.mjs diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 7670f557e..39773a517 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -46,8 +46,9 @@ which generates: - `packages/transformers/docs/source/api/**/*.md` from JSDoc comments in `packages/transformers/src/**/*.js`. -- Generated sections in `.ai/skills/transformers-js/SKILL.md`. -- `.ai/skills/transformers-js/references/TASKS.md`. +- The marker-delimited generated sections in `.ai/skills/transformers-js/SKILL.md` + and `references/{CONFIGURATION,PIPELINE_OPTIONS}.md`. +- `.ai/skills/transformers-js/references/TASKS.md` (fully generated). - The repo-root `README.md` from `packages/transformers/docs/snippets/*.snippet`. Do not edit generated API pages, the generated skill reference files, or the @@ -71,12 +72,16 @@ the validator (without regenerating), use `pnpm --filter @huggingface/transforme ### What is generated vs. hand-written -| Path | Generated? | -|------|------------| -| `packages/transformers/docs/source/api/**/*.md` | yes — gitignored | -| `packages/transformers/docs/source/{guides,tutorials,integrations}/*.md` | hand-written | -| `packages/transformers/docs/source/_toctree.yml` | hand-written (must list every generated API page) | -| `.ai/skills/transformers-js/SKILL.md` | mixed — prose hand-written, blocks between `<!-- @generated:start ... -->` markers regenerated | -| `.ai/skills/transformers-js/references/TASKS.md` | fully generated, do not edit | -| `.ai/skills/transformers-js/references/{CONFIGURATION,PIPELINE_OPTIONS}.md` | mixed (same marker convention as SKILL.md) | -| `README.md` | generated from `packages/transformers/docs/snippets/*.snippet` | +| Path | Generated? | Tracked in git? | +|------|------------|-----------------| +| `packages/transformers/docs/source/api/**/*.md` | yes | no — gitignored | +| `packages/transformers/docs/source/{guides,tutorials,integrations}/*.md` | hand-written | yes | +| `packages/transformers/docs/source/_toctree.yml` | hand-written (must list every generated API page) | yes | +| `.ai/skills/transformers-js/SKILL.md` | mixed — prose hand-written, blocks between `<!-- @generated:start ... -->` markers regenerated | yes | +| `.ai/skills/transformers-js/references/TASKS.md` | fully generated, do not edit | yes | +| `.ai/skills/transformers-js/references/{CONFIGURATION,PIPELINE_OPTIONS}.md` | mixed (same marker convention as SKILL.md) | yes | +| `README.md` | generated from `packages/transformers/docs/snippets/*.snippet` | yes | + +Generated files that are tracked in git (the `.ai/` references and `README.md`) must be +regenerated and committed together with the source change that affects them — CI fails +the build if they drift from the source JSDoc. diff --git a/.ai/skills/transformers-js/SKILL.md b/.ai/skills/transformers-js/SKILL.md index 66378f222..c55607295 100644 --- a/.ai/skills/transformers-js/SKILL.md +++ b/.ai/skills/transformers-js/SKILL.md @@ -79,19 +79,34 @@ library cannot load a model without them. Two ways to check: 1. Open the model page on the Hub and look for an `onnx/` directory in the "Files and versions" tab. 2. Programmatically, with `ModelRegistry.get_available_dtypes(modelId)` — - returns the list of dtypes shipped, or `[]` if no ONNX files exist. + returns the list of dtypes shipped. An empty array means the model exists + but ships no ONNX files. It throws a `ModelFileNotFoundError` if the model + does not exist or is not accessible (private/gated — the Hub cannot tell + these apart), and a regular error on network failures, so an unreachable + model is never mistaken for one without ONNX files. ```javascript -import { ModelRegistry } from "@huggingface/transformers"; +import { ModelRegistry, ModelFileNotFoundError } from "@huggingface/transformers"; -const dtypes = await ModelRegistry.get_available_dtypes("Xenova/some-model"); -if (dtypes.length === 0) { - // No ONNX files — not usable with transformers.js. +// "Xenova/some-model" is a placeholder — substitute the ID you want to check. +try { + const dtypes = await ModelRegistry.get_available_dtypes("Xenova/some-model"); + if (dtypes.length === 0) { + // Model exists but has no ONNX files — not usable with transformers.js. + } +} catch (e) { + if (e instanceof ModelFileNotFoundError) { + // Model does not exist, or is private/gated without a token. + } else { + throw e; // network failure — retry rather than blacklisting the model + } } ``` Don't suggest a model without verifying this; the failure mode at runtime is a -download error that's harder to diagnose than a pre-flight check. +download error that's harder to diagnose than a pre-flight check. For a fuller +pre-flight pattern (cache checks, dtype fallback), see +[`references/CONFIGURATION.md`](references/CONFIGURATION.md#inspecting-models-before-loading). ### Quantization @@ -104,6 +119,7 @@ at the cost of some accuracy: | `fp16` | ~50% of fp32 | GPU / WebGPU inference | | `q8` | ~25% of fp32 | Good default for browsers | | `q4` | ~12% of fp32 | Tight memory budgets, large language models | +| `q4f16` | ~12% of fp32 | Like `q4` but with fp16 activations — pairs well with WebGPU LLMs | ```javascript const pipe = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX", { @@ -154,10 +170,13 @@ set of environment options, cache management, and private / gated models. ## Pipeline options Every pipeline accepts a `progress_callback` for download progress plus options -controlling device, dtype, and caching. Task-specific call options (e.g. -`top_k`, `max_new_tokens`, streaming, chat templates) live with each task in -[`references/TASKS.md`](references/TASKS.md). Common options and their types: -[`references/PIPELINE_OPTIONS.md`](references/PIPELINE_OPTIONS.md). +controlling device, dtype, and caching. The per-task recipes in +[`references/TASKS.md`](references/TASKS.md) show common call options (e.g. +`top_k`, `max_new_tokens`) in use; shared loading options, generation +parameters, streaming, and KV-cache reuse are documented in +[`references/PIPELINE_OPTIONS.md`](references/PIPELINE_OPTIONS.md). For the +exhaustive per-task option types, see the +[API reference](https://huggingface.co/docs/transformers.js/api/pipelines). ## Things to never do diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index 9b742af07..2509eed7e 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -98,9 +98,14 @@ env.useBrowserCache = true; // default when Cache API is available // First visit downloads from the Hub; subsequent visits hit the cache. ``` -## Custom fetch (private / gated models, retries, etc.) +## Private / gated models and custom fetch -Override `env.fetch` to inject auth headers, retry logic, or abort signals: +In Node.js, no configuration is needed for private or gated models: the library +automatically sends `Authorization: Bearer $HF_TOKEN` with requests to the +Hugging Face Hub, so setting that environment variable is enough. + +In browsers — or for retry logic and abort signals anywhere — override +`env.fetch`: ```javascript env.fetch = (url, init) => @@ -128,6 +133,10 @@ The cache must implement the Web Cache API's `match` and `put` methods. locally, which dtypes the model ships with, and can clear caches selectively. Useful for pre-flight UI and disk management. +Note that `get_available_dtypes` returns `[]` only when the model exists but +ships no ONNX files; it throws a `ModelFileNotFoundError` for missing or +inaccessible (private/gated) models, and a regular error on network failures. + ```javascript import { ModelRegistry } from "@huggingface/transformers"; diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 9ffa71f40..5fcee464f 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -133,8 +133,9 @@ const translator = await pipeline("translation", "Xenova/nllb-200-distilled-600M await translator("Hello", { src_lang: "eng_Latn", tgt_lang: "fra_Latn" }); ``` -Task-specific recipes and every call option for each pipeline are in -[`TASKS.md`](./TASKS.md). +The per-task recipes in [`TASKS.md`](./TASKS.md) show the most common call +options in use. For the exhaustive option types of a specific pipeline, see the +[API reference](https://huggingface.co/docs/transformers.js/api/pipelines). ## Generation parameters @@ -223,13 +224,27 @@ For whisper-style chunk / token / finalize callbacks, use `WhisperTextStreamer`. ### KV cache reuse across calls -Passing the cache from one call to the next skips re-encoding the prefix: +For multi-turn chat, pass the same `DynamicCache` to every call **and re-send the full +conversation each turn**. The pipeline uses the cache to skip re-encoding the tokens it +has already processed (the shared prefix). Do not pass only the new message: the model +needs the full sequence to compute token positions, so incremental-only input produces +wrong results. ```javascript import { pipeline, DynamicCache } from "@huggingface/transformers"; const generator = await pipeline("text-generation", "onnx-community/Qwen3-0.6B-ONNX"); -const cache = new DynamicCache(); -await generator("Hello,", { max_new_tokens: 20, past_key_values: cache }); -await generator(" how are you?", { max_new_tokens: 20, past_key_values: cache }); +const past_key_values = new DynamicCache(); +const messages = [{ role: "user", content: "What is the capital of France?" }]; + +// Turn 1 +const turn1 = await generator(messages, { max_new_tokens: 128, past_key_values }); +messages.push(turn1[0].generated_text.at(-1)); // append the assistant reply + +// Turn 2: send the full conversation again — only the new tokens are encoded +messages.push({ role: "user", content: "What about Germany?" }); +const turn2 = await generator(messages, { max_new_tokens: 128, past_key_values }); +console.log(turn2[0].generated_text.at(-1).content); + +await past_key_values.dispose(); // free the cache tensors when the conversation ends ``` diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index 00d5e24bd..e6e5f891a 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -260,7 +260,7 @@ import { pipeline } from '@huggingface/transformers'; const classifier = await pipeline('image-classification', 'Xenova/vit-base-patch16-224'); const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/tiger.jpg'; -const output = await classifier(url, { top_k: 0 }); +const output = await classifier(url, { top_k: null }); // [ // { label: 'tiger, Panthera tigris', score: 0.632695734500885 }, // { label: 'tiger cat', score: 0.3634825646877289 }, diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 73e330448..52c53c963 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,6 +18,30 @@ env: ONNXRUNTIME_NODE_INSTALL: skip jobs: + docs: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + - name: Use Node.js 22 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + node-version: 22 + cache: "pnpm" + - run: pnpm install --frozen-lockfile + - run: pnpm --filter @huggingface/transformers docs-generate + - name: Check generated docs are in sync + run: | + if ! git diff --exit-code; then + echo "" + echo "::error::Generated docs are out of sync with the source." \ + "Run 'pnpm --filter @huggingface/transformers docs-generate' and commit the result." + exit 1 + fi + build: if: github.event.pull_request.draft == false runs-on: diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 000000000..b28461c92 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +.ai/AGENTS.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..b28461c92 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +.ai/AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7a7d45d0..be2a3641a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -279,6 +279,9 @@ Follow the steps below to start contributing: - ☐ Make sure your code is [formatted properly with Prettier](#code-formatting) (`pnpm format:check`). - ☐ If adding a new feature, also add tests for it. - ☐ If your changes affect user-facing functionality, update the relevant documentation. +- ☐ If you changed JSDoc comments, README snippets, or task metadata, regenerate the docs + (`pnpm --filter @huggingface/transformers docs-generate`) and commit the result — CI + fails if generated files drift from the source. See [Documentation](#documentation). ### Tests We are using [Jest](https://jestjs.io/) to execute unit-tests. All tests can be found in `packages/transformers/tests` and have to end with `.test.js` @@ -299,6 +302,29 @@ cd packages/transformers pnpm test -- ./tests/models.test.js ``` +### Documentation + +Parts of the documentation are generated from the source code, and CI fails the build +if they drift. After changing JSDoc comments in `packages/transformers/src/`, a README +snippet in `packages/transformers/docs/snippets/`, or task metadata, regenerate with: + +```bash +pnpm --filter @huggingface/transformers docs-generate +``` + +and commit the regenerated files together with your change. In particular: + +- The repo-root `README.md` is **generated** from `packages/transformers/docs/snippets/*.snippet` — + don't edit it by hand; edit the snippet and regenerate. +- The AI skill files under `.ai/skills/transformers-js/` are partly generated: + `references/TASKS.md` entirely, the other files only between + `<!-- @generated:start -->` / `<!-- @generated:end -->` markers. +- The API reference pages (`packages/transformers/docs/source/api/`) are regenerated on + every run and are gitignored, so they never need committing. + +See [`.ai/AGENTS.md`](.ai/AGENTS.md) for the full map of what is generated versus +hand-written. + ### Style guide #### Code formatting diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index 3d8493779..f11059e5b 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -34,7 +34,15 @@ const DEFAULT_README_OUT = "../../README.md"; export function buildReadme({ project, out = DEFAULT_README_OUT } = {}) { const { ir, publicNames } = project ?? loadProject(packageRoot); const apiLinks = buildApiSymbolLinks(ir, publicNames); - const snippets = Object.fromEntries(Object.entries(FILES_TO_INCLUDE).map(([key, file]) => [key, fs.readFileSync(path.join(packageRoot, file), "utf8")])); + const snippets = Object.fromEntries( + Object.entries(FILES_TO_INCLUDE).map(([key, file]) => { + try { + return [key, fs.readFileSync(path.join(packageRoot, file), "utf8")]; + } catch (err) { + throw new Error(`could not read README snippet "${file}": ${err.message}`, { cause: err }); + } + }), + ); snippets.customUsage = demoteHeadings(snippets.customUsage); const readme = fixLinks(renderTemplate(snippets), apiLinks); @@ -108,7 +116,15 @@ ${models} } function demoteHeadings(markdown) { - return markdown.replace(/^#{1,5}(?=\s)/gm, "#$&"); + // `#` lines inside fenced code (e.g. shell comments) are not headings. + let inFence = false; + return markdown + .split("\n") + .map((line) => { + if (/^\s*```/.test(line)) inFence = !inFence; + return inFence ? line : line.replace(/^#{1,5}(?=\s)/, "#$&"); + }) + .join("\n"); } function fixLinks(markdown, apiLinks) { @@ -127,6 +143,6 @@ function fixLinks(markdown, apiLinks) { }); } -if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { +if (process.argv[1] && import.meta.url === url.pathToFileURL(process.argv[1]).href) { main(); } diff --git a/packages/transformers/docs/scripts/generate-all.js b/packages/transformers/docs/scripts/generate-all.js index 080b09a6c..40c165066 100644 --- a/packages/transformers/docs/scripts/generate-all.js +++ b/packages/transformers/docs/scripts/generate-all.js @@ -11,16 +11,35 @@ import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mj const project = loadProject(packageRoot); -generateApiDocs({ project }); -const { errors: skillErrors } = generateSkillDocs({ project }); -const readmePath = buildReadme({ project }); -console.log(`wrote ${path.relative(process.cwd(), readmePath)}`); - -const validation = validateGeneratedDocs({ project }); -console.log(formatValidationResult(validation)); -if (skillErrors?.length) { +// Run every phase even if an earlier one fails, so a single run surfaces all +// problems; collect errors per phase and fail at the end. +const errors = []; +const runPhase = (name, fn) => { + try { + return fn(); + } catch (err) { + errors.push(`${name}: ${err.message}`); + return null; + } +}; + +const apiResult = runPhase("api docs", () => generateApiDocs({ project })); +for (const err of apiResult?.errors ?? []) errors.push(`api docs: ${err}`); + +const skillResult = runPhase("skill", () => generateSkillDocs({ project })); +for (const err of skillResult?.errors ?? []) errors.push(`skill: ${err}`); + +runPhase("readme", () => { + const readmePath = buildReadme({ project }); + console.log(`wrote ${path.relative(process.cwd(), readmePath)}`); +}); + +const validation = runPhase("validation", () => validateGeneratedDocs({ project })); +if (validation) console.log(formatValidationResult(validation)); + +if (errors.length) { console.log(""); - console.log(`skill generation failed with ${skillErrors.length} error${skillErrors.length === 1 ? "" : "s"}:`); - for (const err of skillErrors) console.log(`- ${err}`); + console.log(`docs generation failed with ${errors.length} error${errors.length === 1 ? "" : "s"}:`); + for (const err of errors) console.log(`- ${err}`); } -if (!validation.ok || skillErrors?.length) process.exitCode = 1; +if (errors.length || !validation?.ok) process.exitCode = 1; diff --git a/packages/transformers/docs/scripts/lib/api-links.mjs b/packages/transformers/docs/scripts/lib/api-links.mjs index 06c3c3587..60c768fa7 100644 --- a/packages/transformers/docs/scripts/lib/api-links.mjs +++ b/packages/transformers/docs/scripts/lib/api-links.mjs @@ -1,29 +1,29 @@ export const DOCS_BASE_URL = "https://huggingface.co/docs/transformers.js"; -export function buildApiSymbolLinks(ir, publicNames, baseUrl = DOCS_BASE_URL) { +export function buildApiSymbolLinks(ir, publicNames) { const links = new Map(); const ambiguous = new Set(); for (const mod of ir.modules) { for (const cls of mod.classes) { if (!isPublic(cls, publicNames)) continue; - addLink(links, ambiguous, cls.name, apiUrl(mod.name, cls.name, baseUrl)); + addLink(links, ambiguous, cls.name, apiUrl(mod.name, cls.name)); } for (const fn of mod.functions) { if (!isPublic(fn, publicNames)) continue; - addLink(links, ambiguous, fn.name, apiUrl(mod.name, fn.name, baseUrl)); + addLink(links, ambiguous, fn.name, apiUrl(mod.name, fn.name)); } for (const constant of mod.constants) { if (!isPublic(constant, publicNames)) continue; - addLink(links, ambiguous, constant.name, apiUrl(mod.name, constant.name, baseUrl)); + addLink(links, ambiguous, constant.name, apiUrl(mod.name, constant.name)); } - for (const td of mod.typedefs) addLink(links, ambiguous, td.name, apiUrl(mod.name, td.name, baseUrl)); - for (const cb of mod.callbacks) addLink(links, ambiguous, cb.name, apiUrl(mod.name, cb.name, baseUrl)); + for (const td of mod.typedefs) addLink(links, ambiguous, td.name, apiUrl(mod.name, td.name)); + for (const cb of mod.callbacks) addLink(links, ambiguous, cb.name, apiUrl(mod.name, cb.name)); } return links; } -export function apiUrl(moduleName, symbolName, baseUrl = DOCS_BASE_URL) { - return `${baseUrl}/api/${moduleName}#${apiSymbolAnchor(moduleName, symbolName)}`; +function apiUrl(moduleName, symbolName) { + return `${DOCS_BASE_URL}/api/${moduleName}#${apiSymbolAnchor(moduleName, symbolName)}`; } export function apiSymbolAnchor(moduleName, symbolName) { diff --git a/packages/transformers/docs/scripts/lib/fs.mjs b/packages/transformers/docs/scripts/lib/fs.mjs new file mode 100644 index 000000000..e6f6fc029 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/fs.mjs @@ -0,0 +1,16 @@ +// Shared filesystem helpers for the docs generators. + +import fs from "node:fs"; +import path from "node:path"; + +// All files under `dir` (recursive) whose name ends with `ext`, sorted by +// their `/`-separated relative path so ordering is platform-independent. +export function listFiles(dir, ext) { + if (!fs.existsSync(dir)) return []; + return fs + .readdirSync(dir, { recursive: true }) + .filter((p) => p.endsWith(ext)) + .map((p) => p.replaceAll(path.sep, "/")) + .sort() + .map((p) => path.join(dir, p)); +} diff --git a/packages/transformers/docs/scripts/lib/generate-api.mjs b/packages/transformers/docs/scripts/lib/generate-api.mjs index 7ba0967cf..1a2ca89c2 100644 --- a/packages/transformers/docs/scripts/lib/generate-api.mjs +++ b/packages/transformers/docs/scripts/lib/generate-api.mjs @@ -1,15 +1,18 @@ import fs from "node:fs"; import path from "node:path"; +import { listFiles } from "./fs.mjs"; import { apiOutputDir, packageRoot } from "./paths.mjs"; import { loadProject } from "./load.mjs"; -import { hasRenderableContent, renderModule } from "./render-api.mjs"; +import { buildLinkIndexes, hasRenderableContent, renderModule } from "./render-api.mjs"; export function generateApiDocs({ project = loadProject(packageRoot), outputDir = apiOutputDir, log = console.log } = {}) { clearExistingMarkdown(outputDir); const written = []; const skipped = []; + const errors = []; + const linkIndexes = buildLinkIndexes(project.ir, project.publicNames); for (const mod of project.ir.modules) { if (!hasRenderableContent(mod, project.publicNames)) { @@ -18,20 +21,21 @@ export function generateApiDocs({ project = loadProject(packageRoot), outputDir continue; } - const rendered = renderModule(mod, project.ir, { publicNames: project.publicNames }); - const outputPath = path.resolve(outputDir, `${mod.name}.md`); - fs.mkdirSync(path.dirname(outputPath), { recursive: true }); - fs.writeFileSync(outputPath, rendered); - written.push(path.relative(outputDir, outputPath)); - log(`wrote ${mod.name}.md`); + try { + const rendered = renderModule(mod, project.ir, { publicNames: project.publicNames, linkIndexes }); + const outputPath = path.resolve(outputDir, `${mod.name}.md`); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, rendered); + written.push(path.relative(outputDir, outputPath)); + log(`wrote ${mod.name}.md`); + } catch (err) { + errors.push(`${mod.name}.md: ${err.message}`); + } } - return { written, skipped }; + return { written, skipped, errors }; } function clearExistingMarkdown(outputDir) { - if (!fs.existsSync(outputDir)) return; - for (const entry of fs.readdirSync(outputDir, { recursive: true })) { - if (entry.endsWith(".md")) fs.unlinkSync(path.join(outputDir, entry)); - } + for (const file of listFiles(outputDir, ".md")) fs.unlinkSync(file); } diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index 194063841..6197223a7 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -3,7 +3,8 @@ import path from "node:path"; -import { findTopLevel, splitTopLevel } from "./scan.mjs"; +import { findTopLevel, matchingBracket, splitTopLevel } from "./scan.mjs"; +import { parseImportedType, stripImportPrefixes } from "./text.mjs"; import { callableReferenceKey, parseUtilityType, UTILITY_TYPES } from "./type-refs.mjs"; export function buildIR(fileEntities) { @@ -55,7 +56,7 @@ function buildTypedefIndex(modules) { function isAliasTypedef(td) { if (!td.type) return false; - return td.type.replace(/import\(['"][^'"]+['"]\)\./g, "").trim() === td.name; + return stripImportPrefixes(td.type).trim() === td.name; } function resolveModule(entities) { @@ -140,9 +141,9 @@ function dedupeTypes(items) { } function parseImportedTypeRef(type, fromFile) { - const m = type?.trim().match(/^import\(['"]([^'"]+)['"]\)\.([A-Za-z_$][\w$]*)$/); - if (!m || !m[1].startsWith(".")) return null; - return { file: resolveJsPath(path.resolve(path.dirname(fromFile), m[1])), name: m[2] }; + const ref = parseImportedType(type); + if (!ref || !ref.specifier.startsWith(".")) return null; + return { file: resolveJsPath(path.resolve(path.dirname(fromFile), ref.specifier)), name: ref.name }; } function resolveJsPath(file) { @@ -160,6 +161,7 @@ function ingest(entities, mod) { name: cls.name, description, examples, + see: seeTagsOf(cls), members: cls.members.filter((m) => !isPrivate(m)).map(buildMember), callable: null, }); @@ -181,6 +183,7 @@ function ingest(entities, mod) { type: typeOf(v), description, examples, + see: seeTagsOf(v), }); } } @@ -251,6 +254,7 @@ function buildCallable(fn) { params: tagsOf(fn, "param").map(normalizeParam), returns: pickReturns(fn), aliasType: typeOf(fn), + see: seeTagsOf(fn), throws: tagsOf(fn, "throws").map((t) => ({ type: t.type, description: t.description })), // `@template {Constraint} Name` maps the generic name to its constraint; // used by the renderer to resolve generic parameter names to something @@ -383,25 +387,6 @@ function parseFunctionTypeParam(raw) { }; } -function matchingBracket(text, start, open, close) { - if (text[start] !== open) return -1; - let depth = 1; - let inStr = null; - for (let i = start + 1; i < text.length; i++) { - const ch = text[i]; - if (inStr) { - if (ch === inStr && text[i - 1] !== "\\") inStr = null; - } else if (ch === '"' || ch === "'" || ch === "`") { - inStr = ch; - } else if (ch === open) { - depth++; - } else if (ch === close && --depth === 0) { - return i; - } - } - return -1; -} - function resolveCallableAliases(modules) { const callableIndex = buildCallableIndex(modules); @@ -501,6 +486,12 @@ function tagsOf(entity, name) { return entity.tags.filter((t) => t.tag === name); } +function seeTagsOf(entity) { + return tagsOf(entity, "see") + .map((t) => t.description) + .filter(Boolean); +} + function typeOf(entity) { return entity.tags.find((t) => t.tag === "type")?.type ?? null; } diff --git a/packages/transformers/docs/scripts/lib/load.mjs b/packages/transformers/docs/scripts/lib/load.mjs index 72c0f234f..8cfb27d32 100644 --- a/packages/transformers/docs/scripts/lib/load.mjs +++ b/packages/transformers/docs/scripts/lib/load.mjs @@ -6,13 +6,14 @@ import fs from "node:fs"; import path from "node:path"; import { extractEntities } from "./structure.mjs"; +import { listFiles } from "./fs.mjs"; import { buildIR } from "./ir.mjs"; import { collectPublicExports } from "./exports.mjs"; import { extractTaskCatalog } from "./tasks.mjs"; export function loadProject(root) { const srcDir = path.join(root, "src"); - const fileEntities = collectJsFiles(srcDir).map((file) => ({ + const fileEntities = listFiles(srcDir, ".js").map((file) => ({ file, entities: extractEntities(fs.readFileSync(file, "utf8"), file), })); @@ -22,11 +23,3 @@ export function loadProject(root) { tasks: extractTaskCatalog(path.join(srcDir, "pipelines", "index.js")), }; } - -function collectJsFiles(dir) { - return fs - .readdirSync(dir, { recursive: true }) - .filter((p) => p.endsWith(".js")) - .sort() - .map((p) => path.join(dir, p)); -} diff --git a/packages/transformers/docs/scripts/lib/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs index 0e1ea3942..6fc0330fb 100644 --- a/packages/transformers/docs/scripts/lib/parse.mjs +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -2,6 +2,8 @@ // `{ description, tags }`. The TS-compiler pass in structure.mjs decides // which comments attach to which AST node; this module only parses content. +import { matchingBracket } from "./scan.mjs"; + const TAG_START = /^@[A-Za-z]/; const FENCE = /^```/; const IDENT_PAT = /[A-Za-z_$][\w$]*/.source; @@ -49,10 +51,11 @@ function parseTag(raw) { const tag = nameMatch[1]; let rest = raw.slice(nameMatch[0].length).replace(/^[ \t]+/, ""); - // `@default` is the only tag whose payload starts with a brace but is *not* - // a JSDoc type expression — skip the type extraction for it. + // `@default` and `@see` payloads may start with a brace that is *not* a + // JSDoc type expression (a literal value, or `{@link ...}`) — skip the type + // extraction for them. let type = null; - if (rest.startsWith("{") && tag !== "default") { + if (rest.startsWith("{") && tag !== "default" && tag !== "see") { const extracted = extractBalancedBraces(rest, 0); if (extracted) { type = extracted.content.trim(); @@ -87,14 +90,13 @@ function parseTag(raw) { case "default": return { tag, value: rest.trim() }; case "module": - return { tag, name: rest.trim() }; + // Only the first token is the module name; anything after is prose. + return { tag, name: rest.trim().split(/\s+/)[0] }; case "file": case "fileoverview": return { tag: "file", description: rest.trim() }; case "see": return { tag, description: rest.trim() }; - case "example": - return { tag, body: rest }; default: return { tag, type, description: rest.trim() }; } @@ -103,7 +105,7 @@ function parseTag(raw) { // Parse `[name=default] description` or `name description`. function parseNameAndRest(rest) { if (rest.startsWith("[")) { - const close = findMatchingBracket(rest, 0); + const close = matchingBracket(rest, 0, "[", "]"); if (close !== -1) { const inner = rest.slice(1, close); const eq = inner.indexOf("="); @@ -124,25 +126,9 @@ function parseNameAndRest(rest) { }; } -export function extractBalancedBraces(text, start) { - if (text[start] !== "{") return null; - let depth = 1; - let i = start + 1; - while (i < text.length && depth > 0) { - if (text[i] === "{") depth++; - else if (text[i] === "}") depth--; - i++; - } - return depth === 0 ? { content: text.slice(start + 1, i - 1), endIndex: i } : null; -} - -function findMatchingBracket(text, start) { - let depth = 0; - for (let i = start; i < text.length; i++) { - if (text[i] === "[") depth++; - else if (text[i] === "]" && --depth === 0) return i; - } - return -1; +function extractBalancedBraces(text, start) { + const close = matchingBracket(text, start, "{", "}"); + return close === -1 ? null : { content: text.slice(start + 1, close), endIndex: close + 1 }; } function trimDash(text) { diff --git a/packages/transformers/docs/scripts/lib/paths.mjs b/packages/transformers/docs/scripts/lib/paths.mjs index 0f9ce9b8c..e157ddc43 100644 --- a/packages/transformers/docs/scripts/lib/paths.mjs +++ b/packages/transformers/docs/scripts/lib/paths.mjs @@ -3,7 +3,7 @@ import url from "node:url"; const libDir = path.dirname(url.fileURLToPath(import.meta.url)); -export const docsDir = path.resolve(libDir, "..", ".."); +const docsDir = path.resolve(libDir, "..", ".."); export const packageRoot = path.dirname(docsDir); export const repoRoot = path.resolve(packageRoot, "..", ".."); diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index ce512b5fa..742feeab9 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -6,8 +6,8 @@ import path from "node:path"; import { apiMemberAnchor, apiSymbolAnchor } from "./api-links.mjs"; -import { splitTopLevel } from "./scan.mjs"; -import { firstSentence, stripDocArtifacts } from "./text.mjs"; +import { matchingBracket, splitTopLevel } from "./scan.mjs"; +import { exampleLines, firstSentence, stripDocArtifacts, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; // Pages with at least this many top-level items get an "On this page" TOC so @@ -26,14 +26,25 @@ export function hasRenderableContent(mod, publicNames = null) { return mod.typedefs.some((td) => !isInternalTypedef(td)); } +// Rendered-name and callable-link indexes depend only on `(ir, publicNames)`, +// so callers rendering many pages can build them once and pass them to +// `renderModule` via `opts.linkIndexes`. +export function buildLinkIndexes(ir, publicNames = null) { + return { + renderedNames: buildRenderedNameIndex(ir, publicNames), + callableLinks: buildCallableLinkIndex(ir, publicNames), + }; +} + export function renderModule(mod, ir, opts = {}) { const publicNames = opts.publicNames ?? null; + const { renderedNames, callableLinks } = opts.linkIndexes ?? buildLinkIndexes(ir, publicNames); const ctx = { typedefIndex: ir.typedefIndex, moduleByName: new Map(ir.modules.map((m) => [m.name, m])), moduleName: mod.name, - renderedNames: buildRenderedNameIndex(ir, publicNames), - callableLinks: buildCallableLinkIndex(ir, publicNames), + renderedNames, + callableLinks, }; const classes = filterPublic(mod.classes, publicNames); @@ -43,7 +54,7 @@ export function renderModule(mod, ir, opts = {}) { const out = []; out.push(`# ${mod.name}`, ""); if (mod.description) out.push(mod.description.trim(), ""); - for (const ex of mod.examples) out.push(...renderExample(ex)); + for (const ex of mod.examples) out.push(...exampleLines(ex)); out.push(...renderTOC({ classes, functions, constants }, ctx)); @@ -65,15 +76,13 @@ export function renderModule(mod, ir, opts = {}) { } if (mod.callbacks.length) { out.push("## Callbacks", ""); - for (const cb of mod.callbacks) out.push(...renderCallback(cb, ctx)); + for (const cb of mod.callbacks) out.push(...renderFunction(cb, ctx, 3, null, { nameOnlyHeading: true })); } - return ( - expandInlineLinks(out.join("\n"), ctx) - .replace(/[ \t]+$/gm, "") - .replace(/\n{3,}/g, "\n\n") - .trimEnd() + "\n" - ); + // Link expansion and blank-run collapsing must not touch example code, so + // they run with fenced blocks masked; trailing-whitespace cleanup is safe + // (and applied) everywhere. + return transformOutsideFences(out.join("\n").replace(/[ \t]+$/gm, ""), (text) => expandInlineLinks(text, ctx).replace(/\n{3,}/g, "\n\n")).trimEnd() + "\n"; } // Render a one-line "On this page" TOC, but only when the page is large @@ -138,13 +147,6 @@ function dedupeByName(items) { return items.filter((it) => (seen.has(it.name) ? false : seen.add(it.name))); } -function renderExample(ex) { - const lines = []; - if (ex.title) lines.push(`**Example:** ${ex.title}`); - lines.push("```" + ex.language, ex.code, "```", ""); - return lines; -} - // Descriptions carried over from the Python library sometimes start with a // reST-style `[`TypeName`]` cross-reference that JavaScript readers can't follow. // Strip the artifact from the start of the first paragraph; leave the rest alone. @@ -157,6 +159,13 @@ function cleanDescription(text) { .replace(/@see\s+`?([A-Za-z_$][\w$.]*)`?/g, "`$1`"); } +// Line-leading `@see` tags parse into an entity's `see` list; surface them so +// the reference survives into the rendered page. `{@link ...}` entries are +// expanded to markdown links by the page-level `expandInlineLinks` pass. +function seeAlso(entity) { + return entity.see?.length ? [`**See also:** ${entity.see.join(", ")}`, ""] : []; +} + // `{@link url}` / `{@link url Text}` / `{@link Symbol}` -> markdown. function expandInlineLinks(text, ctx) { return text @@ -172,7 +181,8 @@ function expandInlineLinks(text, ctx) { function renderClass(cls, ctx) { const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, cls.name)}"></a>`, "", `### ${cls.name}`, ""]; if (cls.description) lines.push(cleanDescription(cls.description), ""); - for (const ex of cls.examples) lines.push(...renderExample(ex)); + lines.push(...seeAlso(cls)); + for (const ex of cls.examples) lines.push(...exampleLines(ex)); if (cls.callable) { lines.push(...renderFunction({ ...cls.callable, displayName: cls.name, anchorName: `${cls.name}.call` }, ctx, 4)); } @@ -198,21 +208,28 @@ function renderField(f, ctx, parent) { const lines = [`#### \`${parent}.${f.name}\`${type}`, ""]; if (f.deprecated) lines.push("> **Deprecated**", ""); if (f.description) lines.push(cleanDescription(f.description), ""); - if (f.defaultValue != null && f.defaultValue !== "") lines.push(`**Default:** \`${f.defaultValue}\``, ""); + // Multiline initializers can't sit inside a single-backtick span — omit them. + if (f.defaultValue != null && f.defaultValue !== "" && !f.defaultValue.includes("\n")) { + lines.push(`**Default:** \`${f.defaultValue}\``, ""); + } return lines; } // `_`-prefixed methods are the library's convention for internal / subclass-only // hooks — they're not part of the user-facing API. Exception: `constructor` is // kept even though it has no leading underscore. -function shouldRenderMethod(m) { +export function shouldRenderMethod(m) { if (m.name.startsWith("_")) return false; return m.description || m.params?.length || m.returns?.description || m.returns?.type || m.examples?.length || m.throws?.length; } -function renderFunction(fn, ctx, depth, parent = null) { +// Also renders callback typedefs (`opts.nameOnlyHeading`) — they're types +// rather than invocable functions, so the heading is the bare name instead of +// a `name(params)` signature. +function renderFunction(fn, ctx, depth, parent = null, opts = {}) { const anchor = parent ? apiMemberAnchor(ctx.moduleName, parent, fn.anchorName ?? fn.name) : apiSymbolAnchor(ctx.moduleName, fn.anchorName ?? fn.name); - const lines = [`<a id="${anchor}"></a>`, "", `${"#".repeat(depth)} ${signature(fn, parent)}`, ""]; + const heading = opts.nameOnlyHeading ? fn.name : signature(fn, parent); + const lines = [`<a id="${anchor}"></a>`, "", `${"#".repeat(depth)} ${heading}`, ""]; // Resolve generic type parameters (`@template {Constraint} T`) inside this // function's parameter/return types. Without the constraint map, a `T` // would render as `any`. @@ -222,6 +239,7 @@ function renderFunction(fn, ctx, depth, parent = null) { if (fn.deprecated) lines.push("> **Deprecated**", ""); if (fn.description) lines.push(cleanDescription(fn.description), ""); + lines.push(...seeAlso(fn)); if (fn.params?.length) { lines.push("**Parameters**", "", ...renderParamList(fn.params, fnCtx), ""); } @@ -239,7 +257,7 @@ function renderFunction(fn, ctx, depth, parent = null) { } lines.push(""); } - for (const ex of fn.examples) lines.push(...renderExample(ex)); + for (const ex of fn.examples ?? []) lines.push(...exampleLines(ex)); return lines; } @@ -252,24 +270,6 @@ function signature(fn, parent) { return `\`${owner}${fn.displayName ?? fn.name}(${params})\``; } -function renderCallback(cb, ctx) { - const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, cb.name)}"></a>`, "", `### ${cb.name}`, ""]; - const templateMap = new Map(); - for (const t of cb.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); - const cbCtx = templateMap.size ? { ...ctx, templates: templateMap } : ctx; - - if (cb.description) lines.push(cleanDescription(cb.description), ""); - if (cb.params?.length) { - lines.push("**Parameters**", "", ...renderParamList(cb.params, cbCtx), ""); - } - if (cb.returns?.type || cb.returns?.description) { - const type = cb.returns.type ? renderType(cb.returns.type, cbCtx) : ""; - const desc = cb.returns.description ? ` — ${cb.returns.description}` : ""; - lines.push(`**Returns:** ${type}${desc}`, ""); - } - return lines; -} - // ---------- parameter lists ---------- // Nested params (`foo.bar`) fold under their parent as sub-bullets. @@ -345,7 +345,8 @@ function renderConstant(c, ctx) { const type = c.type ? ` : ${renderType(c.type, ctx)}` : ""; const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, c.name)}"></a>`, "", `### \`${c.name}\`${type}`, ""]; if (c.description) lines.push(cleanDescription(c.description), ""); - for (const ex of c.examples) lines.push(...renderExample(ex)); + lines.push(...seeAlso(c)); + for (const ex of c.examples) lines.push(...exampleLines(ex)); return lines; } @@ -424,7 +425,7 @@ const TUPLE = /^\[(.*)\]$/; // Turn a raw JSDoc type string into readable markdown. Prefers author-written // names over expanded TS structures; unknown gnarly types become `object`. -export function renderType(raw, ctx, opts = {}) { +function renderType(raw, ctx, opts = {}) { const pretty = prettifyTypeString(raw); if (opts.noLink) return `\`${pretty}\``; @@ -583,12 +584,12 @@ function renderCallableReference(raw, ctx) { // Conditional/mapped/infer types collapse to their outermost wrapper; simple // unions of names or long generic lists are preserved so the renderer can // split them into individual links. -export function prettifyTypeString(raw) { +function prettifyTypeString(raw) { if (!raw) return ""; let s = raw.trim(); s = stripLeading(s, "<", ">"); // <T extends X>(...) - s = s.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); + s = stripImportPrefixes(s); s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); s = s.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, "'$1'"); @@ -611,15 +612,8 @@ export function prettifyTypeString(raw) { } function stripLeading(s, open, close) { - if (s[0] !== open) return s; - let depth = 1; - let i = 1; - while (i < s.length && depth > 0) { - if (s[i] === open) depth++; - else if (s[i] === close) depth--; - i++; - } - return depth === 0 ? s.slice(i).trim() : s; + const end = matchingBracket(s, 0, open, close); + return end === -1 ? s : s.slice(end + 1).trim(); } // Conditional types (`A extends B ? X : Y`), mapped types (`{ [K in ...] }`), diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index ae89967b5..f54d922f3 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -10,7 +10,10 @@ import fs from "node:fs"; import path from "node:path"; -import { firstSentence } from "./text.mjs"; +import { listFiles } from "./fs.mjs"; +import { shouldRenderMethod } from "./render-api.mjs"; +import { splitTopLevel } from "./scan.mjs"; +import { exampleLines, firstSentence, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-all.js -->"; const DOCS_SITE = "https://huggingface.co/docs/transformers.js/api"; @@ -26,7 +29,7 @@ export function renderSkill({ ir, tasks, publicNames, skillDir }) { // Expand `<!-- @generated:start id=... -->` markers in every hand-written // markdown file under the skill directory. Prose outside markers is preserved. // Only the generated blocks get absolutized — hand-authored prose is left alone. - for (const file of walkMarkdown(skillDir)) { + for (const file of listFiles(skillDir, ".md")) { const original = fs.readFileSync(file, "utf8"); if (!original.includes("@generated:start")) continue; const injected = injectMarkers(original, { ...ctx, file }); @@ -46,13 +49,6 @@ function absolutize(markdown) { }); } -function walkMarkdown(dir) { - return fs - .readdirSync(dir, { recursive: true }) - .filter((p) => p.endsWith(".md")) - .map((p) => path.join(dir, p)); -} - // ---------- marker injection ---------- const MARKER_RE = /(<!-- @generated:start id=([^\s]+) -->)([\s\S]*?)(<!-- @generated:end id=\2 -->)/g; @@ -70,7 +66,6 @@ function injectMarkers(original, ctx) { // Dispatch a marker id to its generator. Supported ids: // task-list full list of supported pipeline tasks -// task:<id> recipe for a single task // typedef:<Name> properties table for a named typedef // class:<Name> class description + fields + methods summary // fields:<ClassName> properties table built from a class's fields @@ -80,10 +75,6 @@ function resolveMarker(id, ctx) { const [kind, arg] = splitOnce(id, ":"); switch (kind) { - case "task": { - const info = ctx.tasks.supportedTasks.get(arg); - return info ? renderTaskRecipe(arg, info, ctx) : null; - } case "typedef": return renderTypedefTable(arg, ctx); case "class": @@ -108,10 +99,7 @@ function renderModuleExamples(moduleName, ctx) { return ""; } const lines = []; - for (const ex of mod.examples) { - if (ex.title) lines.push(`**Example:** ${ex.title}`, ""); - lines.push("```" + ex.language, ex.code, "```", ""); - } + for (const ex of mod.examples) lines.push(...exampleLines(ex, { blankAfterTitle: true })); return lines.join("\n").trimEnd(); } @@ -131,7 +119,7 @@ function renderFieldsTable(className, ctx) { defaultValue: f.defaultValue, description: f.description, })), - ) || warnEmpty(className, ctx, `fields:${className}`) + ) || warnEmpty(`class "${className}" has no fields to render (fields:${className})`, ctx) ); } @@ -159,10 +147,7 @@ function renderTaskRecipe(taskId, info, ctx) { if (aliases.length) lines.push(`**Aliases:** ${aliases.map((a) => `\`${a}\``).join(", ")}`); lines.push(""); if (cls?.description) lines.push(cls.description.trim(), ""); - for (const ex of cls?.examples ?? []) { - if (ex.title) lines.push(`**Example:** ${ex.title}`); - lines.push("```" + ex.language, ex.code, "```", ""); - } + for (const ex of cls?.examples ?? []) lines.push(...exampleLines(ex)); return lines.join("\n").trimEnd(); } @@ -182,15 +167,15 @@ function renderTypedefTable(name, ctx) { if (!table) continue; parts.push(`**\`${variantName}\`**`, "", table, ""); } - return parts.length ? parts.join("\n").trimEnd() : warnEmpty(name, ctx, `typedef:${name}`); + return parts.length ? parts.join("\n").trimEnd() : warnEmpty(`typedef "${name}" has no properties to render (typedef:${name})`, ctx); } const props = collectProperties(name, ctx.ir); - return propertiesTable(props) || warnEmpty(name, ctx, `typedef:${name}`); + return propertiesTable(props) || warnEmpty(`typedef "${name}" has no properties to render (typedef:${name})`, ctx); } -function warnEmpty(name, ctx, marker) { - ctx.errors.push(`typedef "${name}" has no properties to render (${marker})`); +function warnEmpty(message, ctx) { + ctx.errors.push(message); return ""; } @@ -198,7 +183,7 @@ function propertiesTable(props) { if (!props.length) return ""; const lines = ["| Option | Type | Description |", "|--------|------|-------------|"]; for (const p of props) { - const type = p.type ? renderTypedefType(p.type, { table: true }) : ""; + const type = p.type ? renderTypedefType(p.type) : ""; const nameCell = p.optional ? `\`${p.name}\`?` : `\`${p.name}\``; const desc = prepareCell(p.description) + (p.defaultValue != null ? ` _(default: \`${p.defaultValue}\`)_` : ""); lines.push(`| ${nameCell} | ${type} | ${desc} |`); @@ -210,20 +195,7 @@ function propertiesTable(props) { // Returns [] if the type doesn't look like a union of simple names. function splitUnion(type) { if (!type) return []; - const parts = []; - let depth = 0; - let buf = ""; - for (const ch of type) { - if ("<({[".includes(ch)) depth++; - else if (">)}]".includes(ch)) depth--; - if (depth === 0 && ch === "|") { - parts.push(buf.trim()); - buf = ""; - } else { - buf += ch; - } - } - if (buf.trim()) parts.push(buf.trim()); + const parts = splitTopLevel(type, "|").map((p) => p.trim()); if (parts.length < 2) return []; if (!parts.every((p) => /^[A-Za-z_$][\w$.]*$/.test(p))) return []; return parts; @@ -238,7 +210,8 @@ function renderClassSummary(name, ctx) { const lines = []; if (cls.description) lines.push(cls.description.trim(), ""); const fields = cls.members.filter((m) => m.kind !== "method"); - const methods = cls.members.filter((m) => m.kind === "method" && m.description); + // Same visibility rule as the api renderer: `_`-prefixed methods are internal. + const methods = cls.members.filter((m) => m.kind === "method" && m.description && shouldRenderMethod(m)); if (fields.length) { lines.push("**Fields**", ""); for (const f of fields) { @@ -256,7 +229,7 @@ function renderClassSummary(name, ctx) { .filter((p) => p.name && !p.name.includes(".")) .map((p) => (p.optional ? `[${p.name}]` : p.name)) .join(", "); - const ret = m.returns?.type ? ` → \`${prettifyReturnType(m.returns.type)}\`` : ""; + const ret = m.returns?.type ? ` → \`${compactType(m.returns.type)}\`` : ""; lines.push(`- \`${m.name}(${params})\`${ret} — ${firstSentence(m.description)}`); } lines.push(""); @@ -264,30 +237,31 @@ function renderClassSummary(name, ctx) { return lines.join("\n").trimEnd(); } -function prettifyReturnType(raw) { - return raw - .replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1") - .replace(/\s+/g, " ") - .trim(); +// Compact display form of a type string: import prefixes collapsed to bare +// names, internal whitespace flattened. +function compactType(raw) { + return stripImportPrefixes(raw).replace(/\s+/g, " ").trim(); } // Flatten a typedef into its effective properties. If the typedef's type is // an intersection like `A & B`, collect properties from each referenced -// typedef in declaration order (de-duplicated by name). -function collectProperties(name, ir) { +// typedef in declaration order (de-duplicated by name). `visited` guards +// against self-referential intersection typedefs. +function collectProperties(name, ir, visited = new Set()) { + if (visited.has(name)) return []; + visited.add(name); const typedef = findTypedef(ir, name); if (!typedef) return []; if (typedef.properties?.length) return typedef.properties; - const parts = (typedef.type ?? "") - .split("&") + const parts = splitTopLevel(typedef.type ?? "", "&") .map((s) => s.trim()) .filter(Boolean); if (parts.length < 2) return []; const seen = new Map(); for (const part of parts) { - for (const p of collectProperties(part, ir)) { + for (const p of collectProperties(part, ir, visited)) { if (!seen.has(p.name)) seen.set(p.name, p); } } @@ -303,27 +277,28 @@ function findTypedef(ir, name) { return mod?.typedefs.find((t) => t.name === name) ?? null; } +// Escape table-breaking pipes exactly once — pipes that are already escaped +// are left alone, so this is safe to apply to text from any source. +function escapeCellPipes(text) { + return text.replace(/(?<!\\)\|/g, "\\|"); +} + // Compact display: inside a markdown table cell we want inline code spans. // Collapse `import('./x.js').Foo` to `Foo`, long inline object types to // `object`, and strip newlines. -function renderTypedefType(raw, { table = false } = {}) { - let compact = raw - .replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1") - .replace(/\s+/g, " ") - .trim(); +function renderTypedefType(raw) { + let compact = compactType(raw); if (compact.length > 60 && (compact.startsWith("{") || /[({=]/.test(compact))) { compact = "object"; } - const escaped = compact.replace(table ? /[`|]/g : /`/g, (ch) => `\\${ch}`); - return "`" + escaped + "`"; + return "`" + escapeCellPipes(compact.replace(/`/g, "\\`")) + "`"; } // Cells can't contain newlines or un-escaped pipes. `{@link url}` gets turned // into a plain URL so agents can still see it. function prepareCell(text) { - return (text || "") + return escapeCellPipes(text || "") .replace(/\{@link\s+([^}\s]+)(?:\s+[^}]+)?\}/g, "$1") - .replace(/\|/g, "\\|") .replace(/:\s*\n\s*-\s+/g, ":<br />- ") .replace(/\n\s*-\s+/g, "<br />- ") .replace(/\n+/g, " ") @@ -384,12 +359,8 @@ function groupTasksByModality(supportedTasks) { } function finalize(lines) { - return ( - lines - .join("\n") - .replace(/\n{3,}/g, "\n\n") - .trimEnd() + "\n" - ); + // Blank-run collapsing must not touch fenced example code. + return transformOutsideFences(lines.join("\n"), (text) => text.replace(/\n{3,}/g, "\n\n")).trimEnd() + "\n"; } function aliasesFor(taskId, tasks) { @@ -405,4 +376,3 @@ function findClass(ir, name) { } return null; } - diff --git a/packages/transformers/docs/scripts/lib/scan.mjs b/packages/transformers/docs/scripts/lib/scan.mjs index b24759de7..07c33b723 100644 --- a/packages/transformers/docs/scripts/lib/scan.mjs +++ b/packages/transformers/docs/scripts/lib/scan.mjs @@ -29,6 +29,27 @@ export function findTopLevel(text, needle) { return -1; } +// Given `text[start] === open`, return the index of the matching `close`, +// skipping nested pairs and quoted strings; -1 when absent or unbalanced. +export function matchingBracket(text, start, open, close) { + if (text[start] !== open) return -1; + let depth = 1; + let inStr = null; + for (let i = start + 1; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + } else if (ch === open) { + depth++; + } else if (ch === close && --depth === 0) { + return i; + } + } + return -1; +} + // Split `text` on `sep`, ignoring separators inside brackets/strings. // Returns `[text]` (the unsplit input) when no split occurred — callers // can use that to detect "this was a single chunk all along". diff --git a/packages/transformers/docs/scripts/lib/text.mjs b/packages/transformers/docs/scripts/lib/text.mjs index 366217ef9..3ed01e976 100644 --- a/packages/transformers/docs/scripts/lib/text.mjs +++ b/packages/transformers/docs/scripts/lib/text.mjs @@ -18,7 +18,63 @@ export function firstSentence(text) { } // Descriptions copied from the Python library sometimes start with -// `[`TypeName`]` (reST cross-reference syntax). Drop the leading artifact. +// `[`TypeName`]` (reST cross-reference syntax). Drop the leading artifact — +// but not a markdown link `[label](url)`, whose bracket is load-bearing. export function stripDocArtifacts(text) { - return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\]\s*/, ""); + return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\](?!\()\s*/, ""); +} + +// Run `transform` over the parts of `text` outside fenced code blocks. Each +// fence is swapped for a single-line placeholder while the transform runs, so +// page-level rewrites (link expansion, blank-run collapsing) can't mangle +// example code. An unterminated fence extends to the end of the text. +export function transformOutsideFences(text, transform) { + const FENCE = /^\s*```/; + const blocks = []; + const out = []; + let fence = null; + for (const line of text.split("\n")) { + if (fence) { + fence.push(line); + if (FENCE.test(line)) { + out.push(`\u0000${blocks.length}\u0000`); + blocks.push(fence.join("\n")); + fence = null; + } + } else if (FENCE.test(line)) { + fence = [line]; + } else { + out.push(line); + } + } + if (fence) { + out.push(`\u0000${blocks.length}\u0000`); + blocks.push(fence.join("\n")); + } + return transform(out.join("\n")).replace(/\u0000(\d+)\u0000/g, (_, i) => blocks[Number(i)]); +} + +// One `**Example:** <title>` block with its fenced code, as markdown lines. +// `blankAfterTitle` keeps the historical spacing of module-example markers. +export function exampleLines(ex, { blankAfterTitle = false } = {}) { + const lines = []; + if (ex.title) { + lines.push(`**Example:** ${ex.title}`); + if (blankAfterTitle) lines.push(""); + } + lines.push("```" + ex.language, ex.code, "```", ""); + return lines; +} + +// `import('./module.js').Name` prefixes appear throughout JSDoc type strings. +// Strip them down to the bare (possibly dotted) name for display. +export function stripImportPrefixes(type) { + return type.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); +} + +// Parse a type that is exactly one `import('./module.js').Name` expression +// into its module specifier and imported name; null for anything else. +export function parseImportedType(type) { + const m = type?.trim().match(/^import\(['"]([^'"]+)['"]\)\.([A-Za-z_$][\w$]*)$/); + return m ? { specifier: m[1], name: m[2] } : null; } diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs index 40775682b..c43072b67 100644 --- a/packages/transformers/docs/scripts/lib/validate.mjs +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { listFiles } from "./fs.mjs"; import { apiOutputDir, toctreePath } from "./paths.mjs"; export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toctreePath, project = null } = {}) { @@ -16,7 +17,7 @@ export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toct // (undocumented exports, malformed `@param` lines) that the renderer can't // fix on its own. They don't fail validation — broken links and a stale // toctree do. - const docWarnings = project ? collectDocWarnings(project) : []; + const docWarnings = [...(project ? collectDocWarnings(project) : []), ...collectAnchorWarnings(sourceDir)]; return { ok: unlisted.length === 0 && stale.length === 0 && brokenLinks.length === 0, @@ -88,25 +89,20 @@ function collectDocWarnings({ ir, publicNames }) { if (!p.name) warnings.push(`${mod.name}: \`${fn.name}\` has a parameter with no name (check for malformed @param)`); } } + for (const td of [...mod.typedefs, ...mod.callbacks]) { + if (!td.name) warnings.push(`${mod.name}: a @typedef/@callback has no name (check for malformed tag)`); + } } return warnings; } function listApiPages(outputDir) { - return listMarkdown(outputDir) + return listFiles(outputDir, ".md") .map((file) => toApiPage(outputDir, file)) .sort(); } -function listMarkdown(dir) { - if (!fs.existsSync(dir)) return []; - return fs - .readdirSync(dir, { recursive: true }) - .filter((p) => p.endsWith(".md")) - .map((p) => path.join(dir, p)); -} - function toApiPage(outputDir, file) { const relative = path.relative(outputDir, file).replaceAll(path.sep, "/").replace(/\.md$/, ""); return `api/${relative}`; @@ -128,17 +124,17 @@ function difference(a, b) { } function validateInternalLinks(sourceDir) { - const files = listMarkdown(sourceDir); + const files = listFiles(sourceDir, ".md"); const fileSet = new Set(files.map((file) => relativeMarkdownPath(sourceDir, file))); const anchorsByFile = new Map(files.map((file) => [relativeMarkdownPath(sourceDir, file), collectAnchors(file)])); const issues = []; for (const file of files) { const rel = relativeMarkdownPath(sourceDir, file); - const text = readMarkdownWithIncludes(file); + const text = maskFences(readMarkdownWithIncludes(file)); for (const match of text.matchAll(MARKDOWN_LINK_RE)) { - const target = match[1] ?? match[2]; + const target = match[1]; if (!isLocalLink(target)) continue; const [targetPath, rawAnchor = ""] = target.split("#"); @@ -163,7 +159,25 @@ function validateInternalLinks(sourceDir) { return issues; } -const MARKDOWN_LINK_RE = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; +// Labels may contain one nested level of square brackets (`[`Foo[]?`](...)`). +const MARKDOWN_LINK_RE = /!?\[(?:[^[\]]|\[[^\]]*\])*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; + +// Blank out fenced code blocks so the link and heading scanners can't match +// code — `x[i](y)` in a snippet is not a markdown link, and a `# comment` is +// not a heading. Line structure is preserved. +function maskFences(text) { + let inFence = false; + return text + .split("\n") + .map((line) => { + if (/^\s*```/.test(line)) { + inFence = !inFence; + return ""; + } + return inFence ? "" : line; + }) + .join("\n"); +} function isLocalLink(target) { return !!target && !/^[a-z][a-z0-9+.-]*:/i.test(target) && !target.startsWith("//"); @@ -180,8 +194,8 @@ function relativeMarkdownPath(sourceDir, file) { } function collectAnchors(file) { - const text = readMarkdownWithIncludes(file); - const anchors = new Set([""]); + const text = maskFences(readMarkdownWithIncludes(file)); + const anchors = new Set(); for (const match of text.matchAll(/<a\s+id=["']([^"']+)["']/g)) anchors.add(match[1]); for (const match of text.matchAll(/^#{1,6}\s+(.+)$/gm)) anchors.add(slugHeading(match[1])); @@ -203,6 +217,22 @@ function readMarkdownWithIncludes(file, seen = new Set()) { }); } +// Headings made entirely of HTML/entities/punctuation slugify to "" and can +// never be linked to. None exist today; this guards against regressions. +function collectAnchorWarnings(sourceDir) { + const warnings = []; + for (const file of listFiles(sourceDir, ".md")) { + const rel = relativeMarkdownPath(sourceDir, file); + const text = maskFences(readMarkdownWithIncludes(file)); + for (const match of text.matchAll(/^#{1,6}\s+(.+)$/gm)) { + if (!slugHeading(match[1])) { + warnings.push(`${rel}: heading "${match[1].trim()}" produces an empty anchor`); + } + } + } + return warnings; +} + function slugHeading(text) { return text .toLowerCase() diff --git a/packages/transformers/docs/scripts/validate.js b/packages/transformers/docs/scripts/validate.js index 60145292f..4b1e13dca 100644 --- a/packages/transformers/docs/scripts/validate.js +++ b/packages/transformers/docs/scripts/validate.js @@ -1,11 +1,18 @@ #!/usr/bin/env node // Validate the generated docs without regenerating them. Useful in CI as a -// fast check that the committed `docs/source/api/` markdown is consistent -// with `docs/source/_toctree.yml` and that internal links resolve. +// fast check that the locally generated `docs/source/api/` markdown (which is +// gitignored) is consistent with `docs/source/_toctree.yml` and that internal +// links resolve. +import { listFiles } from "./lib/fs.mjs"; +import { apiOutputDir } from "./lib/paths.mjs"; import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mjs"; +if (!listFiles(apiOutputDir, ".md").length) { + console.log(`no generated api markdown in ${apiOutputDir} — run \`pnpm docs-generate\` first`); +} + const validation = validateGeneratedDocs(); console.log(formatValidationResult(validation)); if (!validation.ok) process.exitCode = 1; diff --git a/packages/transformers/package.json b/packages/transformers/package.json index ae79c609a..7b2c4cbcc 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -28,7 +28,6 @@ "dev": "node scripts/dev.mjs", "build": "node scripts/build.mjs && pnpm typegen", "test": "node --experimental-vm-modules --expose-gc node_modules/jest/bin/jest.js --verbose --logHeapUsage", - "readme": "node ./docs/scripts/build_readme.js", "docs-generate": "node ./docs/scripts/generate-all.js", "docs-validate": "node ./docs/scripts/validate.js", "docs-build": "doc-builder build transformers.js ./docs/source/ --not_python_module --build_dir ./docs/build/", diff --git a/packages/transformers/src/pipelines.js b/packages/transformers/src/pipelines.js index 95ef240b5..a4900e817 100644 --- a/packages/transformers/src/pipelines.js +++ b/packages/transformers/src/pipelines.js @@ -139,16 +139,21 @@ export async function pipeline( /** @type {import('./utils/core.js').FilesLoadingMap} */ let files_loading = {}; if (progress_callback) { - /** @type {Array<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} */ - const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file))); - metadata.forEach((m, i) => { - if (m.exists) { - files_loading[expected_files[i]] = { - loaded: 0, - total: m.size ?? 0, - }; - } - }); + try { + /** @type {Array<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} */ + const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file))); + metadata.forEach((m, i) => { + if (m.exists) { + files_loading[expected_files[i]] = { + loaded: 0, + total: m.size ?? 0, + }; + } + }); + } catch (e) { + // If we fail to get metadata, we can still proceed without total progress. + logger.warn(`Unable to fetch model file metadata for total progress tracking: ${e}`); + } } const pretrainedOptions = { diff --git a/packages/transformers/src/pipelines/image-classification.js b/packages/transformers/src/pipelines/image-classification.js index 9a6d78322..b25bbd79b 100644 --- a/packages/transformers/src/pipelines/image-classification.js +++ b/packages/transformers/src/pipelines/image-classification.js @@ -20,7 +20,7 @@ import { softmax } from '../utils/maths.js'; * @typedef {ImageClassificationSingle[]} ImageClassificationOutput * * @typedef {Object} ImageClassificationPipelineOptions Parameters specific to image classification pipelines. - * @property {number} [top_k=5] The number of top labels to return. Set to `0` to return all labels. + * @property {number|null} [top_k=5] The number of top labels to return. Set to `null` to return all labels. * * @typedef {ImagePipelineConstructorArgs & ImageClassificationPipelineCallback & Disposable} ImageClassificationPipelineType */ @@ -70,7 +70,7 @@ import { softmax } from '../utils/maths.js'; * * const classifier = await pipeline('image-classification', 'Xenova/vit-base-patch16-224'); * const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/tiger.jpg'; - * const output = await classifier(url, { top_k: 0 }); + * const output = await classifier(url, { top_k: null }); * // [ * // { label: 'tiger, Panthera tigris', score: 0.632695734500885 }, * // { label: 'tiger cat', score: 0.3634825646877289 }, diff --git a/packages/transformers/src/transformers.js b/packages/transformers/src/transformers.js index 47099b1b3..e92689ee1 100644 --- a/packages/transformers/src/transformers.js +++ b/packages/transformers/src/transformers.js @@ -81,6 +81,7 @@ export { DynamicCache } from './cache_utils.js'; // Cache and file management export { ModelRegistry } from './utils/model_registry/ModelRegistry.js'; +export { ModelFileNotFoundError } from './utils/hub/utils.js'; // Expose common types used across the library for developers to access /** diff --git a/packages/transformers/src/utils/hub.js b/packages/transformers/src/utils/hub.js index 6319a4e93..7815e6247 100755 --- a/packages/transformers/src/utils/hub.js +++ b/packages/transformers/src/utils/hub.js @@ -14,6 +14,7 @@ import { pathJoin, isValidHfModelId, makePretrainedOptionsKey, + ModelFileNotFoundError, readResponse, } from './hub/utils.js'; import { getCache, tryCache } from './cache.js'; @@ -312,7 +313,7 @@ export async function loadResourceFile( if (options.local_files_only || !env.allowRemoteModels) { // User requested local files only, but the file is not found locally. if (fatal) { - throw Error( + throw new ModelFileNotFoundError( `\`local_files_only=true\` or \`env.allowRemoteModels=false\` and file was not found locally at "${localPath}".`, ); } else { @@ -324,7 +325,7 @@ export async function loadResourceFile( if (!validModelId) { // Before making any requests to the remote server, we check if the model ID is valid. // This prevents unnecessary network requests for invalid model IDs. - throw Error( + throw new ModelFileNotFoundError( `Local file missing at "${localPath}" and download aborted due to invalid model ID "${path_or_repo_id}".`, ); } diff --git a/packages/transformers/src/utils/hub/utils.js b/packages/transformers/src/utils/hub/utils.js index c1354ca9f..f33898dfa 100644 --- a/packages/transformers/src/utils/hub/utils.js +++ b/packages/transformers/src/utils/hub/utils.js @@ -79,13 +79,44 @@ export function makePretrainedOptionsKey(model_id, options = {}, ...parts) { ]); } +/** + * Error thrown when a model file cannot be found — because the repository does not + * exist, is gated/private without a valid token, the file is missing from the + * repository, or the file is unavailable locally with downloads disabled. + * + * Note that the Hugging Face Hub responds with 401 for non-existent repositories, + * so "model does not exist" and "model requires authentication" are indistinguishable. + * + * Transient failures (network errors, server errors) are NOT represented by this + * class; they surface as regular `Error`s (or `TypeError`s from `fetch`). + */ +export class ModelFileNotFoundError extends Error { + /** + * @param {string} message The error message. + * @param {Object} [options] Additional error information. + * @param {number|null} [options.status=null] The HTTP status code, when the failure came from a Hub response. + */ + constructor(message, { status = null } = {}) { + super(message); + this.name = 'ModelFileNotFoundError'; + this.status = status; + } +} + +/** + * HTTP statuses indicating the requested file does not exist or is not accessible, + * as opposed to transient request/server failures. + */ +const NOT_FOUND_STATUSES = new Set([401, 403, 404]); + /** * Helper method to handle fatal errors that occur while trying to load a file from the Hugging Face Hub. * @param {number} status The HTTP status code of the error. * @param {string} remoteURL The URL of the file that could not be loaded. * @param {boolean} fatal Whether to raise an error if the file could not be loaded. - * @returns {null} Returns `null` if `fatal = true`. - * @throws {Error} If `fatal = false`. + * @returns {null} Returns `null` if `fatal = false`. + * @throws {ModelFileNotFoundError|Error} If `fatal = true`: a `ModelFileNotFoundError` when the + * file is missing or inaccessible (401/403/404), otherwise a regular `Error`. */ export function handleError(status, remoteURL, fatal) { if (!fatal) { @@ -95,7 +126,11 @@ export function handleError(status, remoteURL, fatal) { } const message = ERROR_MAPPING[status] ?? `Error (${status}) occurred while trying to load file`; - throw Error(`${message}: "${remoteURL}".`); + const fullMessage = `${message}: "${remoteURL}".`; + if (NOT_FOUND_STATUSES.has(status)) { + throw new ModelFileNotFoundError(fullMessage, { status }); + } + throw Error(fullMessage); } /** diff --git a/packages/transformers/src/utils/model_registry/ModelRegistry.js b/packages/transformers/src/utils/model_registry/ModelRegistry.js index 48357d3c8..b78428564 100644 --- a/packages/transformers/src/utils/model_registry/ModelRegistry.js +++ b/packages/transformers/src/utils/model_registry/ModelRegistry.js @@ -222,6 +222,9 @@ export class ModelRegistry { * A dtype is considered available if all required model session files * exist for that dtype. * + * An empty array means the model exists and is accessible, but ships no + * complete set of ONNX files for any dtype. + * * @param {string} modelId - The model id (e.g., "onnx-community/all-MiniLM-L6-v2-ONNX") * @param {Object} [options] - Optional parameters * @param {import('../../configs.js').PretrainedConfig} [options.config=null] - Pre-loaded config @@ -229,7 +232,11 @@ export class ModelRegistry { * @param {string} [options.revision='main'] - Model revision * @param {string} [options.cache_dir=null] - Custom cache directory * @param {boolean} [options.local_files_only=false] - Only check local files - * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']) + * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']). + * Empty if the model has no ONNX files. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If the model does not exist or is + * not accessible (e.g., gated or private without a token), so a missing or inaccessible model + * is never misreported as having no ONNX files. Network-level failures also throw. * * **Example:** * ```javascript diff --git a/packages/transformers/src/utils/model_registry/get_available_dtypes.js b/packages/transformers/src/utils/model_registry/get_available_dtypes.js index fad61da76..e838c7a49 100644 --- a/packages/transformers/src/utils/model_registry/get_available_dtypes.js +++ b/packages/transformers/src/utils/model_registry/get_available_dtypes.js @@ -22,6 +22,9 @@ const CONCRETE_DTYPES = Object.keys(DEFAULT_DTYPE_SUFFIX_MAPPING); * exist for that dtype. For example, a Seq2Seq model needs both an encoder * and decoder file — the dtype is only listed if both are present. * + * An empty array means the model exists and is accessible, but ships no + * complete set of ONNX files for any dtype. + * * @param {string} modelId The model id (e.g., "onnx-community/all-MiniLM-L6-v2-ONNX") * @param {Object} [options] Optional parameters * @param {PretrainedConfig} [options.config=null] Pre-loaded model config (optional, will be fetched if not provided) @@ -29,7 +32,13 @@ const CONCRETE_DTYPES = Object.keys(DEFAULT_DTYPE_SUFFIX_MAPPING); * @param {string} [options.revision='main'] Model revision * @param {string} [options.cache_dir=null] Custom cache directory * @param {boolean} [options.local_files_only=false] Only check local files - * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']) + * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']). + * Empty if the model has no ONNX files. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If the model does not exist or is not + * accessible (e.g., gated or private without a token). Note that the Hugging Face Hub responds with + * 401 for non-existent repositories, so these cases are indistinguishable. + * @throws {Error} On network-level or server failures (e.g., offline), so an unreachable + * model is never misreported as having no ONNX files. */ export async function get_available_dtypes( modelId, diff --git a/packages/transformers/src/utils/model_registry/get_file_metadata.js b/packages/transformers/src/utils/model_registry/get_file_metadata.js index 2ee8a509c..e3c2608b2 100644 --- a/packages/transformers/src/utils/model_registry/get_file_metadata.js +++ b/packages/transformers/src/utils/model_registry/get_file_metadata.js @@ -5,8 +5,7 @@ import { env } from '../../env.js'; import { getCache } from '../cache.js'; import { buildResourcePaths, checkCachedResource, getFetchHeaders, getFile } from '../hub.js'; -import { isValidUrl, makePretrainedOptionsKey } from '../hub/utils.js'; -import { logger } from '../logger.js'; +import { handleError, isValidUrl, makePretrainedOptionsKey } from '../hub/utils.js'; import { memoizePromise } from '../memoize_promise.js'; /** @@ -49,6 +48,11 @@ async function fetch_file_head(urlOrPath) { * @param {string} filename The name of the file to check. * @param {PretrainedOptions} [options] An object containing optional parameters. * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} A Promise that resolves to file metadata. + * `exists: false` means the file was determinately not found — never that the check itself failed. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If access to the file was denied (401/403), + * e.g. a gated or private repository without a valid token. + * @throws {Error} On network-level failures (e.g., offline) or server errors, so an + * unreachable file is never misreported as missing. */ export function get_file_metadata(path_or_repo_id, filename, options = {}) { const key = makePretrainedOptionsKey(path_or_repo_id, options, filename); @@ -103,52 +107,57 @@ async function _get_file_metadata(path_or_repo_id, filename, options) { // Check remote if allowed - use Range request for efficiency if (env.allowRemoteModels && !options.local_files_only && validModelId) { - try { - // Make a Range request to get metadata without downloading full content - const rangeResponse = await fetch_file_head(remoteURL); - - if (rangeResponse && rangeResponse.status >= 200 && rangeResponse.status < 300) { - let size; - const contentType = rangeResponse.headers.get('content-type'); - - // If server supports Range requests, we get a 206 Partial Content response - // with a content-range header showing the total uncompressed file size - if (rangeResponse.status === 206) { - const contentRange = rangeResponse.headers.get('content-range'); - if (contentRange) { - // Format: "bytes 0-0/12345" where 12345 is the total uncompressed size - const match = contentRange.match(/bytes \d+-\d+\/(\d+)/); - if (match) { - size = parseInt(match[1], 10); - } - } - } else if (rangeResponse.status === 200) { - // Server doesn't support Range requests and returned the full file (200 OK) - // Cancel the response body immediately to avoid downloading the entire file - try { - await rangeResponse.body?.cancel(); - } catch (cancelError) { - // Ignore cancellation errors - body might already be consumed or not cancellable + // Make a Range request to get metadata without downloading full content. + // Transport-level failures (offline, DNS, timeout) propagate to the caller: + // they mean "could not check", not "file is missing". + const rangeResponse = await fetch_file_head(remoteURL); + + if (rangeResponse && rangeResponse.status >= 200 && rangeResponse.status < 300) { + let size; + const contentType = rangeResponse.headers.get('content-type'); + + // If server supports Range requests, we get a 206 Partial Content response + // with a content-range header showing the total uncompressed file size + if (rangeResponse.status === 206) { + const contentRange = rangeResponse.headers.get('content-range'); + if (contentRange) { + // Format: "bytes 0-0/12345" where 12345 is the total uncompressed size + const match = contentRange.match(/bytes \d+-\d+\/(\d+)/); + if (match) { + size = parseInt(match[1], 10); } } - - // Fallback to content-length if content-range is not available (200 OK response) - // This can happen if server doesn't support Range requests - if (size === undefined) { - const contentLength = rangeResponse.headers.get('content-length'); - size = contentLength ? parseInt(contentLength, 10) : undefined; + } else if (rangeResponse.status === 200) { + // Server doesn't support Range requests and returned the full file (200 OK) + // Cancel the response body immediately to avoid downloading the entire file + try { + await rangeResponse.body?.cancel(); + } catch (cancelError) { + // Ignore cancellation errors - body might already be consumed or not cancellable } + } - return { - exists: true, - size, - contentType: contentType || undefined, - fromCache: false, - }; + // Fallback to content-length if content-range is not available (200 OK response) + // This can happen if server doesn't support Range requests + if (size === undefined) { + const contentLength = rangeResponse.headers.get('content-length'); + size = contentLength ? parseInt(contentLength, 10) : undefined; } - } catch (e) { - // Range request failed most likely because of a network error, timeout, etc. - logger.warn(`Unable to fetch file metadata for "${remoteURL}": ${e}`); + + return { + exists: true, + size, + contentType: contentType || undefined, + fromCache: false, + }; + } + + // 404 means the file determinately does not exist. 416 (Range Not Satisfiable) + // is what a zero-length file yields for a `bytes=0-0` request; we preserve the + // long-standing behavior of treating it as absent. Any other failure status + // (401/403 access denied, 429, 5xx, ...) is an error, not "missing". + if (rangeResponse && rangeResponse.status !== 404 && rangeResponse.status !== 416) { + handleError(rangeResponse.status, remoteURL, true); } } diff --git a/packages/transformers/tests/pipelines/test_pipelines_image_classification.js b/packages/transformers/tests/pipelines/test_pipelines_image_classification.js index 2940000b1..957c3e8e1 100644 --- a/packages/transformers/tests/pipelines/test_pipelines_image_classification.js +++ b/packages/transformers/tests/pipelines/test_pipelines_image_classification.js @@ -42,6 +42,20 @@ export default () => { }, MAX_TEST_EXECUTION_TIME, ); + it( + "return all labels (top_k=null)", + async () => { + const output = await pipe(images[0], { top_k: null }); + // The test model has exactly two labels, so returning "all" means both. + expect(output).toHaveLength(2); + const target = [ + { label: "LABEL_1", score: 0.5020533800125122 }, + { label: "LABEL_0", score: 0.4979466497898102 }, + ]; + expect(output).toBeCloseToNested(target, 5); + }, + MAX_TEST_EXECUTION_TIME, + ); }); describe("batch_size>1", () => { diff --git a/packages/transformers/tests/utils/model_registry.test.js b/packages/transformers/tests/utils/model_registry.test.js index cb9e6408a..7101dc114 100644 --- a/packages/transformers/tests/utils/model_registry.test.js +++ b/packages/transformers/tests/utils/model_registry.test.js @@ -6,11 +6,19 @@ jest.unstable_mockModule("../../src/utils/model_registry/get_file_metadata.js", get_file_metadata: mockGetFileMetadata, })); +// Mock get_config so config-loading failures can be simulated +const mockGetConfig = jest.fn(); +jest.unstable_mockModule("../../src/utils/model_registry/get_model_files.js", () => ({ + get_config: mockGetConfig, + get_model_files: jest.fn(), +})); + // Import registry to populate MODEL_TYPE_MAPPING (side-effect import) await import("../../src/models/registry.js"); // Dynamic import after mock setup (required for ESM) const { get_available_dtypes } = await import("../../src/utils/model_registry/get_available_dtypes.js"); +const { ModelFileNotFoundError } = await import("../../src/utils/hub/utils.js"); // A minimal config that mimics a BERT-like encoder-only model const ENCODER_ONLY_CONFIG = { @@ -53,6 +61,9 @@ function setupExistingFiles(...existingFiles) { describe("get_available_dtypes", () => { beforeEach(() => { mockGetFileMetadata.mockReset(); + mockGetConfig.mockReset(); + // Default: behave like the real get_config when a pre-loaded config is supplied + mockGetConfig.mockImplementation((_modelId, { config } = {}) => Promise.resolve(config)); }); it("should detect fp32 and q4 for an encoder-only model", async () => { @@ -180,4 +191,52 @@ describe("get_available_dtypes", () => { expect(validDtypes).toContain(dtype); } }); + + describe("missing or inaccessible models", () => { + it("should throw when the model does not exist (Hub responds 401)", async () => { + mockGetConfig.mockRejectedValue(new ModelFileNotFoundError('Unauthorized access to file: "https://huggingface.co/test/missing/resolve/main/config.json".', { status: 401 })); + + await expect(get_available_dtypes("test/missing")).rejects.toThrow(ModelFileNotFoundError); + // No dtype probing should happen for a missing model + expect(mockGetFileMetadata).not.toHaveBeenCalled(); + }); + + it("should throw when config.json is missing from the repo (404)", async () => { + mockGetConfig.mockRejectedValue(new ModelFileNotFoundError('Could not locate file: "https://huggingface.co/test/missing/resolve/main/config.json".', { status: 404 })); + + await expect(get_available_dtypes("test/missing")).rejects.toThrow(ModelFileNotFoundError); + }); + + it("should throw when the model is not available locally with downloads disabled", async () => { + mockGetConfig.mockRejectedValue(new ModelFileNotFoundError('`local_files_only=true` or `env.allowRemoteModels=false` and file was not found locally at "/models/test/missing/config.json".')); + + await expect(get_available_dtypes("test/missing", { local_files_only: true })).rejects.toThrow(ModelFileNotFoundError); + }); + + it("should rethrow network-level failures from the config fetch", async () => { + mockGetConfig.mockRejectedValue(new TypeError("fetch failed")); + + await expect(get_available_dtypes("test/model")).rejects.toThrow("fetch failed"); + }); + + it("should rethrow server errors from the config fetch", async () => { + mockGetConfig.mockRejectedValue(new Error('Internal server error: "https://huggingface.co/test/model/resolve/main/config.json".')); + + await expect(get_available_dtypes("test/model")).rejects.toThrow("Internal server error"); + }); + + it("should rethrow network-level failures from dtype file probes", async () => { + // Config loads fine, but probing the ONNX files fails at the network level: + // the caller must see the error rather than an empty dtype list. + mockGetFileMetadata.mockRejectedValue(new TypeError("fetch failed")); + + await expect(get_available_dtypes("test/model", { config: ENCODER_ONLY_CONFIG })).rejects.toThrow("fetch failed"); + }); + + it("should rethrow access errors from dtype file probes", async () => { + mockGetFileMetadata.mockRejectedValue(new ModelFileNotFoundError('Forbidden access to file: "https://huggingface.co/test/model/resolve/main/onnx/model.onnx".', { status: 403 })); + + await expect(get_available_dtypes("test/model", { config: ENCODER_ONLY_CONFIG })).rejects.toThrow(ModelFileNotFoundError); + }); + }); }); From 24d660ddf05396589a5779ebe38b82899f19ad8b Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:23:39 -0400 Subject: [PATCH 073/103] fable 5.1 cleanup --- .ai/AGENTS.md | 4 +- .../references/CONFIGURATION.md | 8 +++- .../references/PIPELINE_OPTIONS.md | 13 +++--- .../transformers/docs/scripts/build_readme.js | 43 +++---------------- .../transformers/docs/scripts/generate-all.js | 10 +++-- .../transformers/docs/scripts/lib/exports.mjs | 8 ++-- .../docs/scripts/lib/generate-api.mjs | 23 ++++------ .../docs/scripts/lib/generate-skill.mjs | 17 -------- packages/transformers/docs/scripts/lib/ir.mjs | 27 ++++++------ .../transformers/docs/scripts/lib/js-ast.mjs | 6 +++ .../transformers/docs/scripts/lib/parse.mjs | 2 +- .../docs/scripts/lib/render-api.mjs | 18 +++----- .../docs/scripts/lib/render-skill.mjs | 10 +---- .../transformers/docs/scripts/lib/scan.mjs | 11 +++-- .../transformers/docs/scripts/lib/tasks.mjs | 6 +-- .../transformers/docs/scripts/lib/text.mjs | 29 ++++++++++++- .../docs/scripts/lib/type-refs.mjs | 10 ----- .../docs/scripts/lib/validate.mjs | 21 +++------ 18 files changed, 113 insertions(+), 153 deletions(-) delete mode 100644 packages/transformers/docs/scripts/lib/generate-skill.mjs diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 39773a517..6062ac834 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -5,7 +5,7 @@ repository. Agentic users must read and follow it before proposing changes. ## Available skills -- [`transformers-js`](skills/transformers-js/SKILL.md) — how to use the library +- [`transformers-js`](/.ai/skills/transformers-js/SKILL.md) — how to use the library itself. Load this skill when working on code that calls `@huggingface/transformers`. ## Contributing @@ -41,7 +41,7 @@ or generated skill content change: pnpm --filter @huggingface/transformers docs-generate ``` -`docs-generate` runs [`docs/scripts/generate-all.js`](../packages/transformers/docs/scripts/generate-all.js), +`docs-generate` runs [`docs/scripts/generate-all.js`](/packages/transformers/docs/scripts/generate-all.js), which generates: - `packages/transformers/docs/source/api/**/*.md` from JSDoc comments in diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index 2509eed7e..ee566f8cc 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -108,10 +108,14 @@ In browsers — or for retry logic and abort signals anywhere — override `env.fetch`: ```javascript +// Obtain the token from your own backend at runtime; never hard-code one in +// shipped client code. +const token = await getHuggingFaceToken(); + env.fetch = (url, init) => fetch(url, { ...init, - headers: { ...init?.headers, Authorization: `Bearer ${process.env.HF_TOKEN}` }, + headers: { ...init?.headers, Authorization: `Bearer ${token}` }, }); ``` @@ -138,7 +142,7 @@ ships no ONNX files; it throws a `ModelFileNotFoundError` for missing or inaccessible (private/gated) models, and a regular error on network failures. ```javascript -import { ModelRegistry } from "@huggingface/transformers"; +import { ModelRegistry, pipeline } from "@huggingface/transformers"; const task = "feature-extraction"; const modelId = "onnx-community/all-MiniLM-L6-v2-ONNX"; diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 5fcee464f..ce8e51baa 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -95,7 +95,7 @@ const pipe = await pipeline("sentiment-analysis", null, { ## Device selection ```javascript -// Default — CPU via WASM (most compatible) +// Default — CPU (onnxruntime-node in Node.js, WASM in browsers) await pipeline("sentiment-analysis", null); // GPU via WebGPU (Chrome 113+, fastest for big models) @@ -148,11 +148,12 @@ anywhere `generate()` is invoked directly. - `max_new_tokens` overrides `max_length`. Pass one, not both — `max_new_tokens` is usually what you want because it doesn't depend on the prompt length. - `min_new_tokens` overrides `min_length` for the same reason. -- Sampling vs. greedy: setting `do_sample: true` enables `temperature`, - `top_k`, `top_p`, and `typical_p`. With `do_sample: false` (the default), - those values are silently ignored. -- Sampling vs. beams: `num_beams > 1` switches to beam search and `do_sample` - is ignored. Don't set both. +- Sampling vs. greedy: setting `do_sample: true` enables `temperature` and + `top_k`. With `do_sample: false` (the default), those values are silently + ignored. +- Sampling vs. beams: `do_sample: true` takes precedence — beams are ignored + while sampling. For beam search, set `num_beams > 1` and leave `do_sample` + off. Don't set both. <!-- @generated:start id=fields:GenerationConfig --> | Option | Type | Description | diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index f11059e5b..9f614a327 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -1,10 +1,9 @@ import fs from "node:fs"; import path from "node:path"; -import url from "node:url"; import { DOCS_BASE_URL, buildApiSymbolLinks } from "./lib/api-links.mjs"; -import { loadProject } from "./lib/load.mjs"; import { packageRoot } from "./lib/paths.mjs"; +import { mapLines } from "./lib/text.mjs"; const FILES_TO_INCLUDE = { intro: "./docs/snippets/0_introduction.snippet", @@ -27,12 +26,12 @@ const CUSTOM_LINK_MAP = { "./guides/dtypes": `${DOCS_BASE_URL}/guides/dtypes`, }; -// Default output: the repo root README. Resolved relative to packageRoot +// Output: the repo root README. Resolved relative to packageRoot // (`packages/transformers`) so the path works from any cwd. -const DEFAULT_README_OUT = "../../README.md"; +const README_OUT = "../../README.md"; -export function buildReadme({ project, out = DEFAULT_README_OUT } = {}) { - const { ir, publicNames } = project ?? loadProject(packageRoot); +export function buildReadme({ project }) { + const { ir, publicNames } = project; const apiLinks = buildApiSymbolLinks(ir, publicNames); const snippets = Object.fromEntries( Object.entries(FILES_TO_INCLUDE).map(([key, file]) => { @@ -46,28 +45,11 @@ export function buildReadme({ project, out = DEFAULT_README_OUT } = {}) { snippets.customUsage = demoteHeadings(snippets.customUsage); const readme = fixLinks(renderTemplate(snippets), apiLinks); - const target = path.resolve(packageRoot, out); + const target = path.resolve(packageRoot, README_OUT); fs.writeFileSync(target, readme, "utf8"); return target; } -function main() { - const { out } = parseArgs(process.argv.slice(2)); - const target = buildReadme({ out }); - console.log(`wrote ${path.relative(process.cwd(), target)}`); -} - -function parseArgs(args) { - const outIndex = args.indexOf("--out"); - if (outIndex !== -1 && !args[outIndex + 1]) { - throw new Error("Expected a path after --out."); - } - - return { - out: outIndex === -1 ? DEFAULT_README_OUT : args[outIndex + 1], - }; -} - function renderTemplate({ intro, installation, quickTour, customUsage, tasks, models }) { return ` @@ -117,14 +99,7 @@ ${models} function demoteHeadings(markdown) { // `#` lines inside fenced code (e.g. shell comments) are not headings. - let inFence = false; - return markdown - .split("\n") - .map((line) => { - if (/^\s*```/.test(line)) inFence = !inFence; - return inFence ? line : line.replace(/^#{1,5}(?=\s)/, "#$&"); - }) - .join("\n"); + return mapLines(markdown, (line, fenced) => (fenced ? line : line.replace(/^#{1,5}(?=\s)/, "#$&"))); } function fixLinks(markdown, apiLinks) { @@ -142,7 +117,3 @@ function fixLinks(markdown, apiLinks) { return `(${link})`; }); } - -if (process.argv[1] && import.meta.url === url.pathToFileURL(process.argv[1]).href) { - main(); -} diff --git a/packages/transformers/docs/scripts/generate-all.js b/packages/transformers/docs/scripts/generate-all.js index 40c165066..2908b22e4 100644 --- a/packages/transformers/docs/scripts/generate-all.js +++ b/packages/transformers/docs/scripts/generate-all.js @@ -4,9 +4,9 @@ import path from "node:path"; import { buildReadme } from "./build_readme.js"; import { generateApiDocs } from "./lib/generate-api.mjs"; -import { generateSkillDocs } from "./lib/generate-skill.mjs"; import { loadProject } from "./lib/load.mjs"; -import { packageRoot } from "./lib/paths.mjs"; +import { packageRoot, repoRoot, skillDir } from "./lib/paths.mjs"; +import { renderSkill } from "./lib/render-skill.mjs"; import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mjs"; const project = loadProject(packageRoot); @@ -26,7 +26,11 @@ const runPhase = (name, fn) => { const apiResult = runPhase("api docs", () => generateApiDocs({ project })); for (const err of apiResult?.errors ?? []) errors.push(`api docs: ${err}`); -const skillResult = runPhase("skill", () => generateSkillDocs({ project })); +const skillResult = runPhase("skill", () => { + const result = renderSkill({ ir: project.ir, tasks: project.tasks, publicNames: project.publicNames, skillDir }); + console.log(`wrote skill to ${path.relative(repoRoot, skillDir) || skillDir}`); + return result; +}); for (const err of skillResult?.errors ?? []) errors.push(`skill: ${err}`); runPhase("readme", () => { diff --git a/packages/transformers/docs/scripts/lib/exports.mjs b/packages/transformers/docs/scripts/lib/exports.mjs index c0492514a..caac985d2 100644 --- a/packages/transformers/docs/scripts/lib/exports.mjs +++ b/packages/transformers/docs/scripts/lib/exports.mjs @@ -12,7 +12,7 @@ import fs from "node:fs"; import path from "node:path"; import ts from "typescript"; -import { stripQuotes, unwrapObjectFreeze } from "./js-ast.mjs"; +import { parseJsFile, stripQuotes, unwrapObjectFreeze } from "./js-ast.mjs"; export function collectPublicExports(entryFile) { const names = new Set(); @@ -24,8 +24,7 @@ function walk(file, visited, names) { if (visited.has(file) || !fs.existsSync(file)) return; visited.add(file); - const source = fs.readFileSync(file, "utf8"); - const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const sf = parseJsFile(file); const dir = path.dirname(file); ts.forEachChild(sf, (node) => visitTopLevel(node, sf, dir, visited, names)); @@ -71,8 +70,7 @@ function visitTopLevel(node, sf, dir, visited, names) { // `Object.freeze({ ... })`, pull its shorthand-property names into `names`. function addNamespaceMembersFromFile(file, wanted, names) { if (!fs.existsSync(file)) return; - const source = fs.readFileSync(file, "utf8"); - const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const sf = parseJsFile(file); const wantedSet = new Set(wanted); ts.forEachChild(sf, (node) => { if (!ts.isVariableStatement(node) || !hasExportModifier(node)) return; diff --git a/packages/transformers/docs/scripts/lib/generate-api.mjs b/packages/transformers/docs/scripts/lib/generate-api.mjs index 1a2ca89c2..de99e7f76 100644 --- a/packages/transformers/docs/scripts/lib/generate-api.mjs +++ b/packages/transformers/docs/scripts/lib/generate-api.mjs @@ -2,40 +2,35 @@ import fs from "node:fs"; import path from "node:path"; import { listFiles } from "./fs.mjs"; -import { apiOutputDir, packageRoot } from "./paths.mjs"; -import { loadProject } from "./load.mjs"; +import { apiOutputDir } from "./paths.mjs"; import { buildLinkIndexes, hasRenderableContent, renderModule } from "./render-api.mjs"; -export function generateApiDocs({ project = loadProject(packageRoot), outputDir = apiOutputDir, log = console.log } = {}) { - clearExistingMarkdown(outputDir); +export function generateApiDocs({ project }) { + clearExistingMarkdown(); - const written = []; - const skipped = []; const errors = []; const linkIndexes = buildLinkIndexes(project.ir, project.publicNames); for (const mod of project.ir.modules) { if (!hasRenderableContent(mod, project.publicNames)) { - skipped.push(mod.name); - log(`skipped ${mod.name}.md — no public content`); + console.log(`skipped ${mod.name}.md — no public content`); continue; } try { const rendered = renderModule(mod, project.ir, { publicNames: project.publicNames, linkIndexes }); - const outputPath = path.resolve(outputDir, `${mod.name}.md`); + const outputPath = path.resolve(apiOutputDir, `${mod.name}.md`); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, rendered); - written.push(path.relative(outputDir, outputPath)); - log(`wrote ${mod.name}.md`); + console.log(`wrote ${mod.name}.md`); } catch (err) { errors.push(`${mod.name}.md: ${err.message}`); } } - return { written, skipped, errors }; + return { errors }; } -function clearExistingMarkdown(outputDir) { - for (const file of listFiles(outputDir, ".md")) fs.unlinkSync(file); +function clearExistingMarkdown() { + for (const file of listFiles(apiOutputDir, ".md")) fs.unlinkSync(file); } diff --git a/packages/transformers/docs/scripts/lib/generate-skill.mjs b/packages/transformers/docs/scripts/lib/generate-skill.mjs deleted file mode 100644 index 07f0772e3..000000000 --- a/packages/transformers/docs/scripts/lib/generate-skill.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import path from "node:path"; - -import { packageRoot, repoRoot, skillDir as defaultSkillDir } from "./paths.mjs"; -import { loadProject } from "./load.mjs"; -import { renderSkill } from "./render-skill.mjs"; - -export function generateSkillDocs({ project = loadProject(packageRoot), skillDir = defaultSkillDir, log = console.log } = {}) { - const { errors } = renderSkill({ - ir: project.ir, - tasks: project.tasks, - publicNames: project.publicNames, - skillDir, - }); - - log(`wrote skill to ${path.relative(repoRoot, skillDir) || skillDir}`); - return { skillDir, errors }; -} diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index 6197223a7..acb1c83c9 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -5,7 +5,7 @@ import path from "node:path"; import { findTopLevel, matchingBracket, splitTopLevel } from "./scan.mjs"; import { parseImportedType, stripImportPrefixes } from "./text.mjs"; -import { callableReferenceKey, parseUtilityType, UTILITY_TYPES } from "./type-refs.mjs"; +import { parseCallableReference, parseUtilityType, UTILITY_TYPES } from "./type-refs.mjs"; export function buildIR(fileEntities) { const modules = new Map(); @@ -126,7 +126,7 @@ function mergeByName(target, items) { const seen = new Set(target.map((item) => item.name)); for (const item of items) { if (!item.name || seen.has(item.name)) continue; - target.push(clone(item)); + target.push(structuredClone(item)); seen.add(item.name); } } @@ -293,7 +293,7 @@ function attachClassCallables(modules) { } function normalizeCallable(callable) { - const cloned = clone(callable); + const cloned = structuredClone(callable); return { ...cloned, params: cloned.params ?? [], @@ -431,11 +431,11 @@ function* allCallables(modules) { function inheritCallable(callable, target) { if (!callable.description) callable.description = target.description; - if (!callable.params?.length && target.params?.length) callable.params = clone(target.params); - if (!callable.returns && target.returns) callable.returns = clone(target.returns); - if (!callable.throws?.length && target.throws?.length) callable.throws = clone(target.throws); - if (!callable.templates?.length && target.templates?.length) callable.templates = clone(target.templates); - if (!callable.examples?.length && target.examples?.length) callable.examples = clone(target.examples); + if (!callable.params?.length && target.params?.length) callable.params = structuredClone(target.params); + if (!callable.returns && target.returns) callable.returns = structuredClone(target.returns); + if (!callable.throws?.length && target.throws?.length) callable.throws = structuredClone(target.throws); + if (!callable.templates?.length && target.templates?.length) callable.templates = structuredClone(target.templates); + if (!callable.examples?.length && target.examples?.length) callable.examples = structuredClone(target.examples); } function resolveUtilityType(type, callableIndex) { @@ -462,7 +462,7 @@ function resolveUtilityParams(params, callableIndex) { const utility = parseUtilityType(param.type); const target = utility && findCallable(utility.target, callableIndex); if (utility?.kind === UTILITY_TYPES.PARAMETERS && utility.index == null && target?.params?.length) { - resolved.push(...clone(target.params)); + resolved.push(...structuredClone(target.params)); continue; } resolved.push({ @@ -474,12 +474,9 @@ function resolveUtilityParams(params, callableIndex) { } function findCallable(ref, callableIndex) { - const key = callableReferenceKey(ref); - return key ? callableIndex.get(key) : null; -} - -function clone(value) { - return structuredClone(value); + const parsed = parseCallableReference(ref); + if (!parsed) return null; + return callableIndex.get(parsed.method ? `${parsed.owner}.${parsed.method}` : parsed.owner); } function tagsOf(entity, name) { diff --git a/packages/transformers/docs/scripts/lib/js-ast.mjs b/packages/transformers/docs/scripts/lib/js-ast.mjs index b219b04de..ca99b297e 100644 --- a/packages/transformers/docs/scripts/lib/js-ast.mjs +++ b/packages/transformers/docs/scripts/lib/js-ast.mjs @@ -1,5 +1,11 @@ +import fs from "node:fs"; import ts from "typescript"; +// Parse a JS source file into a TypeScript AST (with parent pointers). +export function parseJsFile(file) { + return ts.createSourceFile(file, fs.readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); +} + export function stripQuotes(text) { return text.replace(/^['"`]|['"`]$/g, ""); } diff --git a/packages/transformers/docs/scripts/lib/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs index 6fc0330fb..ac79ce3ea 100644 --- a/packages/transformers/docs/scripts/lib/parse.mjs +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -46,8 +46,8 @@ function splitIntoSegments(lines) { } function parseTag(raw) { + // Always matches: `splitIntoSegments` only opens a segment on `@[A-Za-z]`. const nameMatch = raw.match(/^@([A-Za-z]+)/); - if (!nameMatch) return { tag: "unknown", raw }; const tag = nameMatch[1]; let rest = raw.slice(nameMatch[0].length).replace(/^[ \t]+/, ""); diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 742feeab9..08d51a70b 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -7,8 +7,8 @@ import path from "node:path"; import { apiMemberAnchor, apiSymbolAnchor } from "./api-links.mjs"; import { matchingBracket, splitTopLevel } from "./scan.mjs"; -import { exampleLines, firstSentence, stripDocArtifacts, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; -import { isRenderableUtilityType, parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; +import { exampleLines, firstSentence, paramSignature, stripDocArtifacts, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; +import { parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; // Pages with at least this many top-level items get an "On this page" TOC so // the reader doesn't have to scroll an entire page to find a class. @@ -27,7 +27,7 @@ export function hasRenderableContent(mod, publicNames = null) { } // Rendered-name and callable-link indexes depend only on `(ir, publicNames)`, -// so callers rendering many pages can build them once and pass them to +// so callers rendering many pages build them once and pass them to // `renderModule` via `opts.linkIndexes`. export function buildLinkIndexes(ir, publicNames = null) { return { @@ -36,9 +36,9 @@ export function buildLinkIndexes(ir, publicNames = null) { }; } -export function renderModule(mod, ir, opts = {}) { +export function renderModule(mod, ir, opts) { const publicNames = opts.publicNames ?? null; - const { renderedNames, callableLinks } = opts.linkIndexes ?? buildLinkIndexes(ir, publicNames); + const { renderedNames, callableLinks } = opts.linkIndexes; const ctx = { typedefIndex: ir.typedefIndex, moduleByName: new Map(ir.modules.map((m) => [m.name, m])), @@ -262,12 +262,8 @@ function renderFunction(fn, ctx, depth, parent = null, opts = {}) { } function signature(fn, parent) { - const params = (fn.params || []) - .filter((p) => p.name && !p.name.includes(".")) - .map((p) => (p.optional ? `[${p.name}]` : p.name)) - .join(", "); const owner = parent ? `${parent}.` : ""; - return `\`${owner}${fn.displayName ?? fn.name}(${params})\``; + return `\`${owner}${fn.displayName ?? fn.name}(${paramSignature(fn.params)})\``; } // ---------- parameter lists ---------- @@ -593,7 +589,7 @@ function prettifyTypeString(raw) { s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); s = s.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, "'$1'"); - if (isRenderableUtilityType(s)) { + if (parseUtilityType(s)) { return s.replace(/\s+/g, " ").trim(); } diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index f54d922f3..0b3222c96 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -13,7 +13,7 @@ import path from "node:path"; import { listFiles } from "./fs.mjs"; import { shouldRenderMethod } from "./render-api.mjs"; import { splitTopLevel } from "./scan.mjs"; -import { exampleLines, firstSentence, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; +import { exampleLines, firstSentence, paramSignature, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; const GENERATED_BANNER = "<!-- DO NOT EDIT: generated from src/**/*.js by docs/scripts/generate-all.js -->"; const DOCS_SITE = "https://huggingface.co/docs/transformers.js/api"; @@ -223,14 +223,8 @@ function renderClassSummary(name, ctx) { if (methods.length) { lines.push("**Methods**", ""); for (const m of methods) { - // Only show top-level params in the summary signature — `options.foo` - // rows are nested options, covered by the linked typedef. - const params = (m.params ?? []) - .filter((p) => p.name && !p.name.includes(".")) - .map((p) => (p.optional ? `[${p.name}]` : p.name)) - .join(", "); const ret = m.returns?.type ? ` → \`${compactType(m.returns.type)}\`` : ""; - lines.push(`- \`${m.name}(${params})\`${ret} — ${firstSentence(m.description)}`); + lines.push(`- \`${m.name}(${paramSignature(m.params)})\`${ret} — ${firstSentence(m.description)}`); } lines.push(""); } diff --git a/packages/transformers/docs/scripts/lib/scan.mjs b/packages/transformers/docs/scripts/lib/scan.mjs index 07c33b723..96a3c6889 100644 --- a/packages/transformers/docs/scripts/lib/scan.mjs +++ b/packages/transformers/docs/scripts/lib/scan.mjs @@ -7,6 +7,11 @@ const OPEN = "<({["; const CLOSE = ">)}]"; +// The `>` of a function-type arrow (`=>`) is not a closing angle bracket. +function isArrowTail(text, i) { + return text[i] === ">" && text[i - 1] === "="; +} + // Return the index of the first occurrence of `needle` at top level (depth // zero), or -1 if it doesn't appear outside brackets/strings. export function findTopLevel(text, needle) { @@ -20,7 +25,7 @@ export function findTopLevel(text, needle) { inStr = ch; } else if (OPEN.includes(ch)) { depth++; - } else if (CLOSE.includes(ch)) { + } else if (CLOSE.includes(ch) && !isArrowTail(text, i)) { depth--; } else if (depth === 0 && ch === needle) { return i; @@ -43,7 +48,7 @@ export function matchingBracket(text, start, open, close) { inStr = ch; } else if (ch === open) { depth++; - } else if (ch === close && --depth === 0) { + } else if (ch === close && !isArrowTail(text, i) && --depth === 0) { return i; } } @@ -69,7 +74,7 @@ export function splitTopLevel(text, sep) { } else if (OPEN.includes(ch)) { depth++; buf += ch; - } else if (CLOSE.includes(ch)) { + } else if (CLOSE.includes(ch) && !isArrowTail(text, i)) { depth--; buf += ch; } else if (depth === 0 && ch === sep) { diff --git a/packages/transformers/docs/scripts/lib/tasks.mjs b/packages/transformers/docs/scripts/lib/tasks.mjs index c465179be..1e8a44dd3 100644 --- a/packages/transformers/docs/scripts/lib/tasks.mjs +++ b/packages/transformers/docs/scripts/lib/tasks.mjs @@ -3,13 +3,11 @@ // supported pipeline task, with its canonical pipeline class and default model. import ts from "typescript"; -import fs from "node:fs"; -import { stripQuotes, unwrapObjectFreeze } from "./js-ast.mjs"; +import { parseJsFile, stripQuotes, unwrapObjectFreeze } from "./js-ast.mjs"; export function extractTaskCatalog(indexFile) { - const source = fs.readFileSync(indexFile, "utf8"); - const sf = ts.createSourceFile(indexFile, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const sf = parseJsFile(indexFile); const supportedTasks = new Map(); // task-id -> { pipelineClass, defaultModel, type } const aliases = new Map(); // alias -> task-id diff --git a/packages/transformers/docs/scripts/lib/text.mjs b/packages/transformers/docs/scripts/lib/text.mjs index 3ed01e976..04b96f1d8 100644 --- a/packages/transformers/docs/scripts/lib/text.mjs +++ b/packages/transformers/docs/scripts/lib/text.mjs @@ -24,12 +24,29 @@ export function stripDocArtifacts(text) { return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\](?!\()\s*/, ""); } +const FENCE = /^\s*```/; + +// Map every line of `text` through `fn(line, fenced)`, where `fenced` is true +// for lines inside a fenced code block, including the fence delimiters +// themselves. Line structure is preserved; an unterminated fence extends to +// the end of the text. +export function mapLines(text, fn) { + let inFence = false; + return text + .split("\n") + .map((line) => { + const isFence = FENCE.test(line); + if (isFence) inFence = !inFence; + return fn(line, isFence || inFence); + }) + .join("\n"); +} + // Run `transform` over the parts of `text` outside fenced code blocks. Each // fence is swapped for a single-line placeholder while the transform runs, so // page-level rewrites (link expansion, blank-run collapsing) can't mangle // example code. An unterminated fence extends to the end of the text. export function transformOutsideFences(text, transform) { - const FENCE = /^\s*```/; const blocks = []; const out = []; let fence = null; @@ -54,6 +71,16 @@ export function transformOutsideFences(text, transform) { return transform(out.join("\n")).replace(/\u0000(\d+)\u0000/g, (_, i) => blocks[Number(i)]); } +// Comma-separated parameter names for a signature. Only top-level params are +// shown — `options.foo` rows are nested options, covered by the linked typedef. +// Optional params render as `[name]`. +export function paramSignature(params) { + return (params ?? []) + .filter((p) => p.name && !p.name.includes(".")) + .map((p) => (p.optional ? `[${p.name}]` : p.name)) + .join(", "); +} + // One `**Example:** <title>` block with its fenced code, as markdown lines. // `blankAfterTitle` keeps the historical spacing of module-example markers. export function exampleLines(ex, { blankAfterTitle = false } = {}) { diff --git a/packages/transformers/docs/scripts/lib/type-refs.mjs b/packages/transformers/docs/scripts/lib/type-refs.mjs index 46d8390f2..39f26dee2 100644 --- a/packages/transformers/docs/scripts/lib/type-refs.mjs +++ b/packages/transformers/docs/scripts/lib/type-refs.mjs @@ -44,10 +44,6 @@ export function parseUtilityType(raw) { : null; } -export function isRenderableUtilityType(raw) { - return parseUtilityType(raw) !== null; -} - export function parseCallableReference(raw) { if (!raw) return null; const text = raw.trim(); @@ -57,9 +53,3 @@ export function parseCallableReference(raw) { } return null; } - -export function callableReferenceKey(raw) { - const ref = parseCallableReference(raw); - if (!ref) return null; - return ref.method ? `${ref.owner}.${ref.method}` : ref.owner; -} diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs index c43072b67..606f3306f 100644 --- a/packages/transformers/docs/scripts/lib/validate.mjs +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -3,11 +3,12 @@ import path from "node:path"; import { listFiles } from "./fs.mjs"; import { apiOutputDir, toctreePath } from "./paths.mjs"; +import { mapLines } from "./text.mjs"; -export function validateGeneratedDocs({ outputDir = apiOutputDir, tocPath = toctreePath, project = null } = {}) { - const generatedApiPages = listApiPages(outputDir); - const linkedApiPages = readToctreeApiPages(tocPath); - const sourceDir = path.dirname(tocPath); +export function validateGeneratedDocs({ project = null } = {}) { + const generatedApiPages = listApiPages(apiOutputDir); + const linkedApiPages = readToctreeApiPages(toctreePath); + const sourceDir = path.dirname(toctreePath); const brokenLinks = validateInternalLinks(sourceDir); const unlisted = difference(generatedApiPages, linkedApiPages); @@ -166,17 +167,7 @@ const MARKDOWN_LINK_RE = /!?\[(?:[^[\]]|\[[^\]]*\])*\]\(([^)\s]+)(?:\s+"[^"]*")? // code — `x[i](y)` in a snippet is not a markdown link, and a `# comment` is // not a heading. Line structure is preserved. function maskFences(text) { - let inFence = false; - return text - .split("\n") - .map((line) => { - if (/^\s*```/.test(line)) { - inFence = !inFence; - return ""; - } - return inFence ? "" : line; - }) - .join("\n"); + return mapLines(text, (line, fenced) => (fenced ? "" : line)); } function isLocalLink(target) { From 461021f6f4d9abe8b63aecaf1751a89149dd2654 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:27:08 -0400 Subject: [PATCH 074/103] Update AGENTS.md --- .ai/AGENTS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 6062ac834..39773a517 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -5,7 +5,7 @@ repository. Agentic users must read and follow it before proposing changes. ## Available skills -- [`transformers-js`](/.ai/skills/transformers-js/SKILL.md) — how to use the library +- [`transformers-js`](skills/transformers-js/SKILL.md) — how to use the library itself. Load this skill when working on code that calls `@huggingface/transformers`. ## Contributing @@ -41,7 +41,7 @@ or generated skill content change: pnpm --filter @huggingface/transformers docs-generate ``` -`docs-generate` runs [`docs/scripts/generate-all.js`](/packages/transformers/docs/scripts/generate-all.js), +`docs-generate` runs [`docs/scripts/generate-all.js`](../packages/transformers/docs/scripts/generate-all.js), which generates: - `packages/transformers/docs/source/api/**/*.md` from JSDoc comments in From ad6ae41f91beed3f7f671ade1e4029cd54812885 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sun, 6 Sep 2026 23:39:05 -0400 Subject: [PATCH 075/103] improve built docs --- .../references/CONFIGURATION.md | 2 +- .../references/PIPELINE_OPTIONS.md | 6 +- packages/transformers/docs/scripts/lib/ir.mjs | 41 +++- .../transformers/docs/scripts/lib/parse.mjs | 6 +- .../docs/scripts/lib/render-api.mjs | 211 +++++++++++++----- .../transformers/docs/scripts/lib/scan.mjs | 45 +++- .../transformers/docs/scripts/lib/text.mjs | 12 +- packages/transformers/src/env.js | 2 +- .../src/generation/stopping_criteria.js | 19 +- packages/transformers/src/pipelines.js | 7 +- packages/transformers/src/processing_utils.js | 3 + packages/transformers/src/utils/image.js | 6 +- .../src/utils/model_registry/ModelRegistry.js | 8 +- packages/transformers/src/utils/tensor.js | 58 +++++ 14 files changed, 347 insertions(+), 79 deletions(-) diff --git a/.ai/skills/transformers-js/references/CONFIGURATION.md b/.ai/skills/transformers-js/references/CONFIGURATION.md index ee566f8cc..9baa9acb7 100644 --- a/.ai/skills/transformers-js/references/CONFIGURATION.md +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -38,7 +38,7 @@ env.cacheDir = '/path/to/cache/directory/'; | `remoteHost` | `string` | Host URL to load models from. Defaults to the Hugging Face Hub. | | `remotePathTemplate` | `string` | Path template to fill in and append to `remoteHost` when loading models. | | `allowLocalModels` | `boolean` | Whether to allow loading of local files, defaults to `false` if running in-browser, and `true` otherwise. If set to `false`, it will skip the local file check and try to load the model from the remote host. | -| `localModelPath` | `string` | Path to load local models from. Defaults to `/models/`. | +| `localModelPath` | `string` | Path to load local models from. By default, it is `/models/` relative to the library's installed location when a file system is available (e.g., Node.js), and the `/models/` URL path otherwise (e.g., browsers). | | `useFS` | `boolean` | Whether to use the file system to load files. By default, it is `true` if available. | | `useBrowserCache` | `boolean` | Whether to use Cache API to cache models. By default, it is `true` if available. | | `useFSCache` | `boolean` | Whether to use the file system to cache files. By default, it is `true` if available. | diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index ce8e51baa..3cc3a5a53 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -127,7 +127,7 @@ const classifier = await pipeline("text-classification"); await classifier("great movie", { top_k: 3 }); const generator = await pipeline("text-generation"); -await generator("Once upon a time", { max_new_tokens: 100, temperature: 0.7 }); +await generator("Once upon a time", { max_new_tokens: 100, do_sample: true, temperature: 0.7 }); const translator = await pipeline("translation", "Xenova/nllb-200-distilled-600M"); await translator("Hello", { src_lang: "eng_Latn", tgt_lang: "fra_Latn" }); @@ -148,8 +148,8 @@ anywhere `generate()` is invoked directly. - `max_new_tokens` overrides `max_length`. Pass one, not both — `max_new_tokens` is usually what you want because it doesn't depend on the prompt length. - `min_new_tokens` overrides `min_length` for the same reason. -- Sampling vs. greedy: setting `do_sample: true` enables `temperature` and - `top_k`. With `do_sample: false` (the default), those values are silently +- Sampling vs. greedy: `temperature` and `top_k` only take effect with + `do_sample: true`. Under greedy decoding (the default) they are silently ignored. - Sampling vs. beams: `do_sample: true` takes precedence — beams are ignored while sampling. For beam search, set `num_beams > 1` and leave `do_sample` diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index acb1c83c9..60c508854 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -131,12 +131,22 @@ function mergeByName(target, items) { } } +// Keep one entry per name, at the position of its first occurrence. A +// re-export alias (`@typedef {import('./x.js').Foo} Foo`) says nothing the +// real declaration doesn't, so when both are present the real one wins — +// otherwise the name renders as a self-reference and every link to it dangles. function dedupeTypes(items) { - const seen = new Set(); - return items.filter((item) => { - if (!item.name || seen.has(item.name)) return false; - seen.add(item.name); - return true; + const best = new Map(); + for (const item of items) { + if (!item.name) continue; + const existing = best.get(item.name); + if (!existing || (isAliasTypedef(existing) && !isAliasTypedef(item))) best.set(item.name, item); + } + const emitted = new Set(); + return items.flatMap((item) => { + if (!item.name || emitted.has(item.name)) return []; + emitted.add(item.name); + return [best.get(item.name)]; }); } @@ -240,7 +250,10 @@ function buildMember(m) { kind: m.kind, name: m.name, description: m.description, - type: typeOf(m), + // An accessor's type is usually written as `@type`, but a getter may + // instead document it as `@returns {T}` — same information, and without + // this fallback the member renders (or is skipped) with no type at all. + type: typeOf(m) ?? (m.kind === "getter" ? (pickReturns(m)?.type ?? null) : null), defaultValue: m.tags.find((t) => t.tag === "default")?.value ?? m.initializer ?? null, deprecated: m.tags.some((t) => t.tag === "deprecated"), }; @@ -275,7 +288,11 @@ function attachClassCallables(modules) { const callMember = cls.members.find((m) => m.kind === "method" && m.name === "_call"); const callbackName = `${cls.name}Callback`; const callback = callbacks.get(callbackName); - const source = callback ?? (callMember && hasCallableShape(callMember) ? callMember : null); + const callSource = callMember && hasCallableShape(callMember) ? callMember : null; + // The `XCallback` typedef carries the precise generic signature but no + // prose; a documented `_call` carries both. Prefer whichever explains + // what calling the instance actually does. + const source = (callSource?.description ? callSource : null) ?? callback ?? callSource; if (!source) continue; // Promote the `_call` / `Callback` description onto the synthesized @@ -364,10 +381,18 @@ function parseFunctionType(raw) { }; } +// `<Q extends string | string[], const O extends Options = {}>` — a parameter +// may carry a `const` modifier and a default. Neither says anything a reader of +// the rendered signature needs, but both used to make the whole entry +// unparseable, which left `O` rendering as a bare `O`. function parseTemplateList(raw) { return splitTopLevel(raw, ",") .map((part) => { - const m = part.trim().match(/^([A-Za-z_$][\w$]*)(?:\s+extends\s+(.+))?$/); + let text = part.trim().replace(/^const\s+/, ""); + const eq = findTopLevel(text, "="); + // `=` starts a default; `=>` is a function-type arrow inside a constraint. + if (eq !== -1 && text[eq + 1] !== ">") text = text.slice(0, eq).trim(); + const m = text.match(/^([A-Za-z_$][\w$]*)(?:\s+extends\s+(.+))?$/); return m ? { name: m[1], type: m[2]?.trim() ?? null } : null; }) .filter(Boolean); diff --git a/packages/transformers/docs/scripts/lib/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs index ac79ce3ea..7b4e6234f 100644 --- a/packages/transformers/docs/scripts/lib/parse.mjs +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -81,8 +81,10 @@ function parseTag(raw) { return { tag, name: rest.trim().split(/\s+/)[0] || null }; case "template": { // `@template {Constraint} Name description` — just the first identifier - // after any optional constraint is the template-parameter name. - const m = rest.match(/^([A-Za-z_$][\w$]*)/); + // after any optional constraint is the template-parameter name. The name + // may also be written `[Name=Default]`; the default is documentation + // noise, but dropping the whole tag would leave `Name` unresolvable. + const m = rest.match(/^\[?([A-Za-z_$][\w$]*)/); return { tag, type, name: m?.[1] ?? null }; } case "type": diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 08d51a70b..9ebf6f55e 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -6,8 +6,8 @@ import path from "node:path"; import { apiMemberAnchor, apiSymbolAnchor } from "./api-links.mjs"; -import { matchingBracket, splitTopLevel } from "./scan.mjs"; -import { exampleLines, firstSentence, paramSignature, stripDocArtifacts, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; +import { matchingBracket, splitConditional, splitTopLevel } from "./scan.mjs"; +import { DOC_REFERENCE, exampleLines, firstSentence, paramSignature, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; import { parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; // Pages with at least this many top-level items get an "On this page" TOC so @@ -43,6 +43,9 @@ export function renderModule(mod, ir, opts) { typedefIndex: ir.typedefIndex, moduleByName: new Map(ir.modules.map((m) => [m.name, m])), moduleName: mod.name, + // Unfiltered: `renderTypedef` needs the module's `_`-prefixed classes, + // which are deliberately absent from the public export surface. + moduleClasses: mod.classes, renderedNames, callableLinks, }; @@ -147,14 +150,15 @@ function dedupeByName(items) { return items.filter((it) => (seen.has(it.name) ? false : seen.add(it.name))); } -// Descriptions carried over from the Python library sometimes start with a -// reST-style `[`TypeName`]` cross-reference that JavaScript readers can't follow. -// Strip the artifact from the start of the first paragraph; leave the rest alone. -// Also normalize `@see Symbol` / `@see {@link Symbol}` to inline markup so it -// reads as prose rather than raw JSDoc tags — `{@link ...}` is expanded later -// by `expandInlineLinks`. +// reST-style ``[`TypeName`]`` cross-references carried over from the Python +// library are left in place here — `expandInlineLinks` turns them into real +// links (or plain code spans) once the page-level symbol index is available. +// Normalize `@see Symbol` / `@see {@link Symbol}` to inline markup so it reads +// as prose rather than raw JSDoc tags — `{@link ...}` is expanded later by +// `expandInlineLinks` too. function cleanDescription(text) { - return stripDocArtifacts(text.trim()) + return text + .trim() .replace(/@see\s+(?=\{@link)/g, "") .replace(/@see\s+`?([A-Za-z_$][\w$.]*)`?/g, "`$1`"); } @@ -173,7 +177,11 @@ function expandInlineLinks(text, ctx) { const displayed = (label ?? target).trim(); return /^https?:\/\//.test(target) ? `[${displayed}](${target})` : (linkReference(target, displayed, ctx) ?? `\`${displayed}\``); }) - .replace(/\[`([A-Za-z_$][\w$]*)`\](?!\()/g, (match, name) => linkIfKnown(name, ctx) ?? match); + .replace(DOC_REFERENCE, (_, ref) => { + // `pipeline()` reads as a call but resolves as the symbol `pipeline`. + const symbol = ref.replace(/\(\)$/, ""); + return linkReference(symbol, ref, ctx) ?? `\`${ref}\``; + }); } // ---------- classes, functions, callbacks ---------- @@ -378,9 +386,37 @@ function renderTypedef(td, ctx) { if (td.properties?.length) { lines.push("**Properties**", "", ...renderParamList(td.properties, ctx), ""); } + lines.push(...renderBackingClassMembers(td, ctx)); return lines; } +// `@typedef {Record<string, Tensor> & _DynamicCache} DynamicCache` is the +// library's way of saying "a plain object that also has these methods": the +// members live on a `_`-prefixed class that is never documented on its own, +// and only the typedef name is exported. Find that class so its members can be +// rendered under the typedef. +function backingClass(td, ctx) { + if (!td.type || !ctx.moduleClasses?.length) return null; + const parts = splitTopLevel(prettifyTypeString(td.type), "&").map((p) => p.trim()); + if (parts.length < 2) return null; + for (const part of parts) { + if (!/^_[A-Za-z]/.test(part)) continue; + const cls = ctx.moduleClasses.find((c) => c.name === part); + if (cls) return cls; + } + return null; +} + +// Render the backing class's constructor and public methods under the typedef, +// anchored to the typedef's name (`module_x.DynamicCache.update`) so links +// point at the name readers actually use. The `_Type:_` line above already +// shows the remaining, non-private half of the intersection. +function renderBackingClassMembers(td, ctx) { + const cls = backingClass(td, ctx); + if (!cls) return []; + return cls.members.flatMap((m) => (m.kind === "method" && shouldRenderMethod(m) ? renderFunction(m, ctx, 4, td.name) : [])); +} + function shouldRenderTypedef(td, ctx) { if (isInternalTypedef(td)) return false; const { typeIsShowable } = typedefRenderInfo(td, ctx); @@ -395,18 +431,23 @@ function typedefRenderInfo(td, ctx) { // — showing `_Type:_ `object`` adds noise without helping the reader. const isOpaque = ["`object`", "`Object`", "`unknown`", "`any`"].includes(displayed); // Unions and intersections are worth showing even when long — every variant - // is a named type the reader can click through to. Opaque inline object - // literals (`{...}`) stay hidden. + // is a named type the reader can click through to. An inline object literal + // has no clickable parts, so it earns its place only while it still fits on + // a line. Typedefs with a **Properties** list show that instead. const isUnionOrIntersection = / \| | & /.test(displayed); + const isObjectLiteral = displayed.startsWith("`{"); const fitsInline = displayed.length < 120; + // `_`-prefixed names are internal by convention and never get a page of + // their own, so a type built out of one points the reader nowhere. + const referencesInternal = /`_[A-Za-z]/.test(displayed); const typeIsShowable = displayed && !td.properties?.length && - (isUnionOrIntersection || fitsInline) && - !displayed.startsWith("`{") && + (fitsInline || (isUnionOrIntersection && !isObjectLiteral)) && !isSelfReference && !isGenericPassthrough && - !isOpaque; + !isOpaque && + !referencesInternal; return { displayed, typeIsShowable }; } @@ -434,10 +475,9 @@ function renderType(raw, ctx, opts = {}) { .map((p) => p.trim()) .filter((p) => !/^_[A-Za-z]/.test(p)); if (unionParts.length > 1) { - // Common shorthand `T | T[]` (in either order) — collapse to a single link - // marked as "single or array" so the reader doesn't see two identical links. - const collapsed = collapseSingleOrArray(unionParts, ctx); - if (collapsed) return collapsed; + // Every variant is rendered (and linked) on its own. `T | T[]` deliberately + // stays as two parts — a collapsed `T[]?` reads as "optional array", which + // is not what the union means. return unionParts.map((p) => renderType(p, ctx)).join(" | "); } if (unionParts.length === 1 && unionParts[0] !== pretty.trim()) return renderType(unionParts[0], ctx); @@ -500,31 +540,14 @@ function renderArrayType(innerRaw, ctx) { return code ? `\`${code[1]}[]\`` : `${rendered}[]`; } -// `T | T[]` (in either order) collapses to one link with a `[]?` suffix — -// readers don't have to chase two identical-looking links to learn it accepts -// either a single value or an array. Returns null when the union doesn't -// match the shape. The single rendered link/text gets `[]?` appended: -// `Foo` → `` `Foo[]?` ``, `[`Foo`](url)` → `[`Foo[]?`](url)`. -function collapseSingleOrArray(parts, ctx) { - if (parts.length !== 2) return null; - const arrayPart = parts.find((p) => p.endsWith("[]")); - const singlePart = parts.find((p) => !p.endsWith("[]")); - if (!arrayPart || !singlePart) return null; - if (arrayPart.slice(0, -2).trim() !== singlePart.trim()) return null; - const inner = renderType(singlePart, ctx); - // Linked form `[`Name`](url)` — splice `[]?` into the label. - const linked = inner.match(/^\[`([^`]+)`\]\(([^)]+)\)$/); - if (linked) return `[\`${linked[1]}[]?\`](${linked[2]})`; - const code = inner.match(/^`([^`]+)`$/); - if (code) return `\`${code[1]}[]?\``; - return `${inner}[]?`; -} - function renderTupleType(innerRaw, ctx) { const parts = splitTopLevel(innerRaw, ","); if (parts.length === 1 && !parts[0].trim()) return "`[]`"; - const rendered = parts.map((p) => renderType(p.trim(), ctx)).join(", "); - return `[${rendered}]`; + const rendered = parts.map((p) => renderType(p.trim(), ctx)); + // A one-element tuple of a plain name would render as ``[`Name`]``, which is + // indistinguishable from a reST cross-reference — keep it inside the span. + const single = rendered.length === 1 && rendered[0].match(/^`([^`]+)`$/); + return single ? `\`[${single[1]}]\`` : `[${rendered.join(", ")}]`; } function linkIfKnown(name, ctx) { @@ -576,6 +599,75 @@ function renderCallableReference(raw, ctx) { return linkCallable(ref.owner, ref.owner, ctx) ?? linkIfKnown(ref.owner, ctx) ?? `\`${ref.owner}\``; } +// A conditional type (`Check extends X ? A : B`) resolves to one branch or the +// other, so the union of its branches is an honest — and readable — stand-in. +// Rewrite recursively: a branch may itself be a conditional, and a conditional +// may sit inside a wrapper's generic arguments (`Promise<T extends X ? A[] : A>` +// -> `Promise<A[] | A>`). Branches keep source order (true branch first) and +// identical parts are deduped. Callers must skip strings containing `infer` or +// a mapped type — those stay gnarly. +function rewriteConditionals(raw) { + const s = unwrapParens(raw.trim()); + if (!s.includes("extends")) return s; + + const cond = splitConditional(s); + if (cond) { + const parts = []; + for (const branch of [cond.whenTrue, cond.whenFalse]) { + for (const part of splitTopLevel(rewriteConditionals(branch), "|")) { + const trimmed = part.trim(); + // A `never` branch contributes nothing to the set of possible values. + if (!trimmed || trimmed === "never" || parts.includes(trimmed)) continue; + parts.push(trimmed); + } + } + return parts.length ? parts.join(" | ") : "never"; + } + + for (const sep of ["|", "&"]) { + const parts = splitTopLevel(s, sep); + if (parts.length > 1) return parts.map((p) => rewriteConditionals(p.trim())).join(` ${sep} `); + } + + if (s.endsWith("[]")) { + const inner = rewriteConditionals(s.slice(0, -2)); + // A rewritten union has to be parenthesized before `[]` binds to it. + return splitTopLevel(inner, "|").length > 1 ? `(${inner})[]` : `${inner}[]`; + } + + if (s.startsWith("(") && matchingBracket(s, 0, "(", ")") === s.length - 1) { + return `(${rewriteConditionals(s.slice(1, -1))})`; + } + + const generic = s.match(NAMED_GENERIC); + if (generic && matchingBracket(s, s.indexOf("<"), "<", ">") === s.length - 1) { + const args = splitTopLevel(generic[2], ",") + .map((a) => rewriteConditionals(a.trim())) + .join(", "); + return `${generic[1]}<${args}>`; + } + + return s; +} + +// Drop parentheses that wrap an entire type — source authors add them to group +// nested conditionals, and keeping them would leave `(A | B) | C` in the output. +function unwrapParens(s) { + let out = s; + while (out.startsWith("(") && matchingBracket(out, 0, "(", ")") === out.length - 1) { + out = out.slice(1, -1).trim(); + } + return out; +} + +// Mapped types (`{[K in Keys]: V}`) and `infer` have no readable expansion — +// they stay gnarly rather than being rewritten. +const MAPPED_TYPE = /\[\s*\w+\s+in\s/; + +function isRewritableConditional(s) { + return !/\binfer\b/.test(s) && !MAPPED_TYPE.test(s); +} + // Strip noisy TS constructs from a type string without rewriting structure. // Conditional/mapped/infer types collapse to their outermost wrapper; simple // unions of names or long generic lists are preserved so the renderer can @@ -584,15 +676,21 @@ function prettifyTypeString(raw) { if (!raw) return ""; let s = raw.trim(); + // Types written across several JSDoc lines arrive with embedded newlines; + // the scanners below all reason about single-line text. + s = s.replace(/\s+/g, " ").trim(); s = stripLeading(s, "<", ">"); // <T extends X>(...) s = stripImportPrefixes(s); s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); s = s.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, "'$1'"); if (parseUtilityType(s)) { - return s.replace(/\s+/g, " ").trim(); + return s; } + if (isRewritableConditional(s)) s = rewriteConditionals(s); + s = spaceObjectLiterals(s); + if (isGnarly(s)) { const outer = s.match(/^([A-Za-z_$][\w$.]*)(?:<|\s|$)/); // Built-in TS utility types are meaningless shorn of their arguments — the @@ -607,25 +705,34 @@ function prettifyTypeString(raw) { return s.replace(/\s+/g, " ").trim(); } +// `{a: X}` -> `{ a: X }`. Inline object literals are rendered verbatim, and +// they read much better with breathing room inside the braces. Empty `{}` and +// braces that already have a space are left alone. +function spaceObjectLiterals(s) { + return s.replace(/\{(?![\s}])/g, "{ ").replace(/(?<![\s{])\}/g, " }"); +} + function stripLeading(s, open, close) { const end = matchingBracket(s, 0, open, close); return end === -1 ? s : s.slice(end + 1).trim(); } -// Conditional types (`A extends B ? X : Y`), mapped types (`{ [K in ...] }`), -// and `infer` are unreadable inline. So are indexed accesses into typeof -// expressions (`Parameters<X['foo']>[0]`) — readable names these are not. +// Beyond this, a type string stops being something a reader can take in at a +// glance in a table cell or bullet — it collapses to its outermost wrapper. +const MAX_INLINE_TYPE_LENGTH = 160; + +// What survives `rewriteConditionals` is renderable verbatim — including +// callable types (`(a: X) => Y`) and inline object literals (`{a: X, b?: Y}`), +// whose `?` markers are perfectly readable. Genuinely unreadable shapes are +// mapped types (`{[K in ...]: V}`), `infer`, an `extends` we couldn't rewrite, +// indexed accesses into typeof expressions (`Parameters<X['foo']>[0]`), and +// anything too long to scan. function isGnarly(s) { if (/\b(?:infer|extends)\b/.test(s)) return true; + if (MAPPED_TYPE.test(s)) return true; // Any bracketed indexing like `X['foo']` or `X[0]` inside the type string — // including inside `Parameters<...>` / `ReturnType<...>` — makes it too // noisy to render verbatim. if (/\[['"0-9]/.test(s)) return true; - let angle = 0; - for (let i = 0; i < s.length; i++) { - if (s[i] === "<") angle++; - else if (s[i] === ">") angle = Math.max(0, angle - 1); - else if (angle === 0 && (s[i] === "?" || (s[i] === "[" && s[i + 1] === "K"))) return true; - } - return false; + return s.length > MAX_INLINE_TYPE_LENGTH; } diff --git a/packages/transformers/docs/scripts/lib/scan.mjs b/packages/transformers/docs/scripts/lib/scan.mjs index 96a3c6889..866b1905c 100644 --- a/packages/transformers/docs/scripts/lib/scan.mjs +++ b/packages/transformers/docs/scripts/lib/scan.mjs @@ -13,8 +13,10 @@ function isArrowTail(text, i) { } // Return the index of the first occurrence of `needle` at top level (depth -// zero), or -1 if it doesn't appear outside brackets/strings. -export function findTopLevel(text, needle) { +// zero), or -1 if it doesn't appear outside brackets/strings. `needle` may be +// a single character or a longer substring (e.g. `" extends "`), and search +// starts at `from`. +export function findTopLevel(text, needle, from = 0) { let depth = 0; let inStr = null; for (let i = 0; i < text.length; i++) { @@ -27,13 +29,50 @@ export function findTopLevel(text, needle) { depth++; } else if (CLOSE.includes(ch) && !isArrowTail(text, i)) { depth--; - } else if (depth === 0 && ch === needle) { + } else if (depth === 0 && i >= from && text.startsWith(needle, i)) { return i; } } return -1; } +// Split a top-level conditional type `Check extends Extends ? A : B` into its +// two branches, or return null when `text` isn't one. The `?`/`:` pair is +// matched like a ternary so a conditional nested in the true branch +// (`X extends Y ? (A extends B ? C : D) : E`, or the same without parens) +// doesn't steal the outer `:`. +export function splitConditional(text) { + const EXTENDS = " extends "; + const ext = findTopLevel(text, EXTENDS); + if (ext === -1) return null; + const question = findTopLevel(text, "?", ext + EXTENDS.length); + if (question === -1) return null; + + let depth = 0; + let inStr = null; + let pending = 0; + for (let i = question + 1; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + } else if (OPEN.includes(ch)) { + depth++; + } else if (CLOSE.includes(ch) && !isArrowTail(text, i)) { + depth--; + } else if (depth === 0 && ch === "?") { + pending++; + } else if (depth === 0 && ch === ":") { + if (pending === 0) { + return { whenTrue: text.slice(question + 1, i).trim(), whenFalse: text.slice(i + 1).trim() }; + } + pending--; + } + } + return null; +} + // Given `text[start] === open`, return the index of the matching `close`, // skipping nested pairs and quoted strings; -1 when absent or unbalanced. export function matchingBracket(text, start, open, close) { diff --git a/packages/transformers/docs/scripts/lib/text.mjs b/packages/transformers/docs/scripts/lib/text.mjs index 04b96f1d8..0adecc586 100644 --- a/packages/transformers/docs/scripts/lib/text.mjs +++ b/packages/transformers/docs/scripts/lib/text.mjs @@ -17,11 +17,15 @@ export function firstSentence(text) { return /[.!?]$/.test(sentence) ? sentence : sentence + "."; } -// Descriptions copied from the Python library sometimes start with -// `[`TypeName`]` (reST cross-reference syntax). Drop the leading artifact — -// but not a markdown link `[label](url)`, whose bracket is load-bearing. +// Descriptions copied from the Python library use reST cross-references — +// ``[`TypeName`]``, sometimes with a `~` prefix or a trailing `()`. Reduce them +// to plain inline code so the sentence still reads. Renderers that can resolve +// symbols to anchors (the API pages) link them instead; this is the plain-text +// fallback. A markdown link `[label](url)` keeps its load-bearing bracket. +export const DOC_REFERENCE = /\[`~?([A-Za-z_$][\w$.]*(?:\(\))?)`\](?!\()/g; + export function stripDocArtifacts(text) { - return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\](?!\()\s*/, ""); + return text.replace(DOC_REFERENCE, "`$1`"); } const FENCE = /^\s*```/; diff --git a/packages/transformers/src/env.js b/packages/transformers/src/env.js index 6bd3ea6fb..253ca2b3f 100644 --- a/packages/transformers/src/env.js +++ b/packages/transformers/src/env.js @@ -206,7 +206,7 @@ export const LogLevel = Object.freeze({ * @property {string} remotePathTemplate Path template to fill in and append to `remoteHost` when loading models. * @property {boolean} allowLocalModels Whether to allow loading of local files, defaults to `false` if running in-browser, and `true` otherwise. * If set to `false`, it will skip the local file check and try to load the model from the remote host. - * @property {string} localModelPath Path to load local models from. Defaults to `/models/`. + * @property {string} localModelPath Path to load local models from. By default, it is `/models/` relative to the library's installed location when a file system is available (e.g., Node.js), and the `/models/` URL path otherwise (e.g., browsers). * @property {boolean} useFS Whether to use the file system to load files. By default, it is `true` if available. * @property {boolean} useBrowserCache Whether to use Cache API to cache models. By default, it is `true` if available. * @property {boolean} useFSCache Whether to use the file system to cache files. By default, it is `true` if available. diff --git a/packages/transformers/src/generation/stopping_criteria.js b/packages/transformers/src/generation/stopping_criteria.js index 56c1e2352..86dabf727 100644 --- a/packages/transformers/src/generation/stopping_criteria.js +++ b/packages/transformers/src/generation/stopping_criteria.js @@ -22,7 +22,7 @@ export class StoppingCriteria extends Callable { * * @param {number[][]} input_ids (`number[][]` of shape `(batch_size, sequence_length)`): * Indices of input sequence tokens in the vocabulary. - * @param {number[][]} scores scores (`number[][]` of shape `(batch_size, config.vocab_size)`): + * @param {number[][]} scores (`number[][]` of shape `(batch_size, config.vocab_size)`): * Prediction scores of a language modeling head. These can be scores for each vocabulary token before SoftMax * or scores for each vocabulary token after SoftMax. * @returns {boolean[]} A list of booleans indicating whether each sequence should be stopped. @@ -143,19 +143,36 @@ export class EosTokenCriteria extends StoppingCriteria { * Stops generation whenever the user interrupts the process. */ export class InterruptableStoppingCriteria extends StoppingCriteria { + /** + * Constructs a new instance of `InterruptableStoppingCriteria`. + */ constructor() { super(); this.interrupted = false; } + /** + * Interrupts the generation process, causing every sequence in the batch to stop + * on the next call to this criterion. + */ interrupt() { this.interrupted = true; } + /** + * Clears a previous interruption, allowing generation to continue. + */ reset() { this.interrupted = false; } + /** + * @param {number[][]} input_ids (`number[][]` of shape `(batch_size, sequence_length)`): + * Indices of input sequence tokens in the vocabulary. + * @param {number[][]} scores (`number[][]` of shape `(batch_size, config.vocab_size)`): + * Prediction scores of a language modeling head. + * @returns {boolean[]} A list of booleans indicating whether each sequence should be stopped. + */ _call(input_ids, scores) { return new Array(input_ids.length).fill(this.interrupted); } diff --git a/packages/transformers/src/pipelines.js b/packages/transformers/src/pipelines.js index a4900e817..263f4e4fe 100644 --- a/packages/transformers/src/pipelines.js +++ b/packages/transformers/src/pipelines.js @@ -67,14 +67,16 @@ import { get_file_metadata } from './utils/model_registry/get_file_metadata.js'; * @template {PipelineType} T The type of pipeline to return. * @param {T} task The task defining which pipeline will be returned. Currently accepted tasks are: * - `"audio-classification"`: will return a `AudioClassificationPipeline`. - * - `"automatic-speech-recognition"`: will return a `AutomaticSpeechRecognitionPipeline`. + * - `"automatic-speech-recognition"` (alias "asr" available): will return a `AutomaticSpeechRecognitionPipeline`. * - `"background-removal"`: will return a `BackgroundRemovalPipeline`. * - `"depth-estimation"`: will return a `DepthEstimationPipeline`. * - `"document-question-answering"`: will return a `DocumentQuestionAnsweringPipeline`. - * - `"feature-extraction"`: will return a `FeatureExtractionPipeline`. + * - `"feature-extraction"` (alias "embeddings" available): will return a `FeatureExtractionPipeline`. * - `"fill-mask"`: will return a `FillMaskPipeline`. * - `"image-classification"`: will return a `ImageClassificationPipeline`. + * - `"image-feature-extraction"`: will return a `ImageFeatureExtractionPipeline`. * - `"image-segmentation"`: will return a `ImageSegmentationPipeline`. + * - `"image-to-image"`: will return a `ImageToImagePipeline`. * - `"image-to-text"`: will return a `ImageToTextPipeline`. * - `"object-detection"`: will return a `ObjectDetectionPipeline`. * - `"question-answering"`: will return a `QuestionAnsweringPipeline`. @@ -82,6 +84,7 @@ import { get_file_metadata } from './utils/model_registry/get_file_metadata.js'; * - `"text2text-generation"`: will return a `Text2TextGenerationPipeline`. * - `"text-classification"` (alias "sentiment-analysis" available): will return a `TextClassificationPipeline`. * - `"text-generation"`: will return a `TextGenerationPipeline`. + * - `"text-to-audio"` (alias "text-to-speech" available): will return a `TextToAudioPipeline`. * - `"token-classification"` (alias "ner" available): will return a `TokenClassificationPipeline`. * - `"translation"`: will return a `TranslationPipeline`. * - `"translation_xx_to_yy"`: will return a `TranslationPipeline`. diff --git a/packages/transformers/src/processing_utils.js b/packages/transformers/src/processing_utils.js index 4535f1d0d..c0cf785c4 100644 --- a/packages/transformers/src/processing_utils.js +++ b/packages/transformers/src/processing_utils.js @@ -59,6 +59,7 @@ export class Processor extends Callable { } /** + * The image processor used by this processor, if any. * @returns {import('./image_processors_utils.js').ImageProcessor|undefined} The image processor of the processor, if it exists. */ get image_processor() { @@ -66,6 +67,7 @@ export class Processor extends Callable { } /** + * The tokenizer used by this processor, if any. * @returns {PreTrainedTokenizer|undefined} The tokenizer of the processor, if it exists. */ get tokenizer() { @@ -73,6 +75,7 @@ export class Processor extends Callable { } /** + * The feature extractor used by this processor, if any. * @returns {import('./feature_extraction_utils.js').FeatureExtractor|undefined} The feature extractor of the processor, if it exists. */ get feature_extractor() { diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index 82318df15..7515c2c52 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -195,7 +195,11 @@ export class RawImage { /** * Helper method to create a new Image from a tensor - * @param {Tensor} tensor + * @param {Tensor} tensor The 3D tensor containing the image data. Must be of type `uint8`. + * @param {'CHW'|'HWC'} [channel_format='CHW'] The dimension ordering of the tensor. + * @returns {RawImage} The image created from the tensor. + * @throws {Error} If the tensor does not have 3 dimensions, or the channel format, + * tensor type, or number of channels is unsupported. */ static fromTensor(tensor, channel_format = 'CHW') { if (tensor.dims.length !== 3) { diff --git a/packages/transformers/src/utils/model_registry/ModelRegistry.js b/packages/transformers/src/utils/model_registry/ModelRegistry.js index b78428564..9b28402e9 100644 --- a/packages/transformers/src/utils/model_registry/ModelRegistry.js +++ b/packages/transformers/src/utils/model_registry/ModelRegistry.js @@ -352,7 +352,13 @@ export class ModelRegistry { * @param {string} path_or_repo_id - Model id or path * @param {string} filename - The file name * @param {import('../hub.js').PretrainedOptions} [options] - Optional parameters - * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} File metadata + * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} File metadata. + * `exists: false` means the file was determinately not found — never that the check itself failed. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If access to the file was denied (401/403), + * e.g. a private or gated repository without a valid token, or a repository which does not exist + * (the Hub returns 401 for nonexistent repos). + * @throws {Error} On network-level failures (e.g., offline) or server errors, so an + * unreachable file is never misreported as missing. * * **Example:** * ```javascript diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index b6d536b87..5c5f1bc71 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -128,6 +128,10 @@ export class Tensor { }); } + /** + * Releases the resources held by the underlying ONNX Runtime tensor (e.g., GPU buffers). + * The tensor should not be used after this method is called. + */ dispose() { this.ort_tensor.dispose(); // this.ort_tensor = undefined; @@ -613,6 +617,8 @@ export class Tensor { /** * In-place version of @see {@link Tensor.squeeze} + * @param {number|number[]|null} [dim=null] If given, the input will be squeezed only in the specified dimensions. + * @returns {Tensor} `this`, with the specified dimensions of size 1 removed. */ squeeze_(dim = null) { this.dims = calc_squeeze_dims(this.dims, dim); @@ -642,6 +648,9 @@ export class Tensor { /** * In-place version of @see {@link Tensor.flatten} + * @param {number} [start_dim=0] the first dim to flatten + * @param {number} [end_dim=-1] the last dim to flatten + * @returns {Tensor} `this`, flattened along the specified dimensions. */ flatten_(start_dim = 0, end_dim = -1) { // TODO validate inputs @@ -696,6 +705,10 @@ export class Tensor { return new Tensor(this.type, this_data, dims); // NOTE: uses same underlying storage } + /** + * In-place version of @see {@link Tensor.neg} + * @returns {Tensor} `this`, with every element negated. + */ neg_() { const this_data = this.data; for (let i = 0; i < this_data.length; ++i) { @@ -703,6 +716,11 @@ export class Tensor { } return this; } + + /** + * Returns a new tensor with the negative of the elements of this tensor. + * @returns {Tensor} the output tensor. + */ neg() { return this.clone().neg_(); } @@ -776,10 +794,22 @@ export class Tensor { return this.clone().round_(); } + /** + * Returns the mean value of each row of this tensor in the given dimension `dim`. + * @param {number|null} [dim=null] the dimension to reduce. If `null`, the mean of all elements is computed. + * @param {boolean} [keepdim=false] whether the output tensor has `dim` retained or not. + * @returns {Tensor} A new tensor with means taken along the specified dimension. + */ mean(dim = null, keepdim = false) { return mean(this, dim, keepdim); } + /** + * Returns the minimum value of each row of this tensor in the given dimension `dim`. + * @param {number|null} [dim=null] the dimension to reduce. If `null`, the minimum of all elements is computed. + * @param {boolean} [keepdim=false] whether the output tensor has `dim` retained or not. + * @returns {Tensor} A new tensor with minimum values taken along the specified dimension. + */ min(dim = null, keepdim = false) { if (dim === null) { // None to reduce over all dimensions. @@ -796,6 +826,12 @@ export class Tensor { return new Tensor(type, result, resultDims); } + /** + * Returns the maximum value of each row of this tensor in the given dimension `dim`. + * @param {number|null} [dim=null] the dimension to reduce. If `null`, the maximum of all elements is computed. + * @param {boolean} [keepdim=false] whether the output tensor has `dim` retained or not. + * @returns {Tensor} A new tensor with maximum values taken along the specified dimension. + */ max(dim = null, keepdim = false) { if (dim === null) { // None to reduce over all dimensions. @@ -812,6 +848,13 @@ export class Tensor { return new Tensor(type, result, resultDims); } + /** + * Returns the index of the minimum value of all elements in this tensor. + * @param {number|null} [dim=null] the dimension to reduce. Only `null` (reduce over all elements) is currently supported. + * @param {boolean} [keepdim=false] whether the output tensor has `dim` retained or not. + * @returns {Tensor} An `int64` scalar tensor containing the index of the minimum value. + * @throws {Error} If `dim` is not `null`. + */ argmin(dim = null, keepdim = false) { if (dim !== null) { throw new Error('`dim !== null` not yet implemented.'); @@ -819,6 +862,14 @@ export class Tensor { const index = min(this.data)[1]; return new Tensor('int64', [BigInt(index)], []); } + + /** + * Returns the index of the maximum value of all elements in this tensor. + * @param {number|null} [dim=null] the dimension to reduce. Only `null` (reduce over all elements) is currently supported. + * @param {boolean} [keepdim=false] whether the output tensor has `dim` retained or not. + * @returns {Tensor} An `int64` scalar tensor containing the index of the maximum value. + * @throws {Error} If `dim` is not `null`. + */ argmax(dim = null, keepdim = false) { if (dim !== null) { throw new Error('`dim !== null` not yet implemented.'); @@ -1560,6 +1611,13 @@ export function full(size, fill_value) { return fullHelper(size, fill_value, dtype, typedArrayCls); } +/** + * Creates a tensor with the same size as `tensor`, filled with `fill_value`. + * The tensor's dtype is inferred from `fill_value`. + * @param {Tensor} tensor The size of input will determine size of the output tensor. + * @param {number|bigint|boolean} fill_value The value to fill the output tensor with. + * @returns {Tensor} The filled tensor. + */ export function full_like(tensor, fill_value) { return full(tensor.dims, fill_value); } From 4118dbee775118a6c716d5498ad49c1e6cb6cd16 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Sun, 6 Sep 2026 23:55:44 -0400 Subject: [PATCH 076/103] improve docs --- .../transformers-js/references/TASKS.md | 5 ++ packages/transformers/docs/scripts/lib/ir.mjs | 21 ++++-- .../docs/scripts/lib/render-api.mjs | 69 +++++++++++++++---- .../docs/scripts/lib/render-skill.mjs | 8 ++- 4 files changed, 81 insertions(+), 22 deletions(-) diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index e6e5f891a..d143ec032 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -76,6 +76,7 @@ const scores = await classifier(audio, candidate_labels); ### `automatic-speech-recognition` **Default model:** `Xenova/whisper-tiny.en` + **Aliases:** `asr` Automatic speech recognition pipeline for transcribing spoken text from audio. @@ -160,6 +161,7 @@ const output = await transcriber(url, { chunk_length_s: 30, stride_length_s: 5 } ### `text-to-audio` **Default model:** `onnx-community/Supertonic-TTS-ONNX` + **Aliases:** `text-to-speech` Text-to-audio generation pipeline using any `AutoModelForTextToWaveform` or `AutoModelForTextToSpectrogram`. @@ -537,6 +539,7 @@ const features = await image_feature_extractor(image); ### `text-classification` **Default model:** `Xenova/distilbert-base-uncased-finetuned-sst-2-english` + **Aliases:** `sentiment-analysis` Text classification pipeline using `AutoModelForSequenceClassification`. @@ -584,6 +587,7 @@ const output = await classifier('I hate you!', { top_k: null }); ### `token-classification` **Default model:** `Xenova/bert-base-multilingual-cased-ner-hrl` + **Aliases:** `ner` Named entity recognition pipeline using `AutoModelForTokenClassification`. @@ -879,6 +883,7 @@ const output = await classifier(text, labels, { multi_label: true }); ### `feature-extraction` **Default model:** `onnx-community/all-MiniLM-L6-v2-ONNX` + **Aliases:** `embeddings` Feature extraction pipeline using no model head. This pipeline extracts the hidden diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index 60c508854..1a12e29be 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -203,6 +203,11 @@ function ingest(entities, mod) { function collectTypedefsFromBlock(block, mod) { let current = null; let first = true; + // `@template {Constraint} [T=default]` tags sit on the block, not on the + // individual `@typedef`. Carrying them onto every type declared by the block + // lets the renderer resolve `T` in the type expression and in `@property` + // types instead of falling back to `any`. + const blockTemplates = templatesOf(block); const flush = () => { if (!current) return; if (current.kind === "typedef") mod.typedefs.push(current); @@ -218,12 +223,13 @@ function collectTypedefsFromBlock(block, mod) { description: tag.description || (first ? block.description : ""), }); current = callable - ? { ...callable, kind: "callback" } + ? { ...callable, kind: "callback", templates: callable.templates?.length ? callable.templates : blockTemplates } : { kind: "typedef", name: tag.name, type: tag.type, description: tag.description || (first ? block.description : ""), + templates: blockTemplates, properties: [], }; first = false; @@ -235,6 +241,7 @@ function collectTypedefsFromBlock(block, mod) { description: block.description, params: tagsOf(block, "param").map(normalizeParam), returns: pickReturns(block), + templates: blockTemplates, }; first = false; } else if ((tag.tag === "property" || tag.tag === "prop") && current?.kind === "typedef") { @@ -272,9 +279,7 @@ function buildCallable(fn) { // `@template {Constraint} Name` maps the generic name to its constraint; // used by the renderer to resolve generic parameter names to something // readable instead of a bare `any`. - templates: tagsOf(fn, "template") - .map((t) => ({ name: t.name, type: t.type })) - .filter((t) => t.name), + templates: templatesOf(fn), examples, deprecated: fn.tags.some((t) => t.tag === "deprecated"), }; @@ -504,6 +509,14 @@ function findCallable(ref, callableIndex) { return callableIndex.get(parsed.method ? `${parsed.owner}.${parsed.method}` : parsed.owner); } +// `@template {Constraint} Name` -> `{ name, type }`, the shape the renderer +// uses to resolve generic parameter names to their constraints. +function templatesOf(entity) { + return tagsOf(entity, "template") + .map((t) => ({ name: t.name, type: t.type })) + .filter((t) => t.name); +} + function tagsOf(entity, name) { return entity.tags.filter((t) => t.tag === name); } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 9ebf6f55e..7db2b461d 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -241,9 +241,7 @@ function renderFunction(fn, ctx, depth, parent = null, opts = {}) { // Resolve generic type parameters (`@template {Constraint} T`) inside this // function's parameter/return types. Without the constraint map, a `T` // would render as `any`. - const templateMap = new Map(); - for (const t of fn.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); - const fnCtx = templateMap.size ? { ...ctx, templates: templateMap } : ctx; + const fnCtx = withTemplates(ctx, fn); if (fn.deprecated) lines.push("> **Deprecated**", ""); if (fn.description) lines.push(cleanDescription(fn.description), ""); @@ -373,9 +371,18 @@ function isGenericParamName(name) { return /^T[A-Z][A-Za-z]*$/.test(name) || /^[TKV]$/.test(name); } +// Resolve `@template {Constraint} T` names inside an entity's types. Without +// the constraint map, a `T` renders as `any`. +function withTemplates(ctx, entity) { + const templateMap = new Map(); + for (const t of entity?.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); + return templateMap.size ? { ...ctx, templates: templateMap } : ctx; +} + function renderTypedef(td, ctx) { if (!shouldRenderTypedef(td, ctx)) return []; + const tdCtx = withTemplates(ctx, td); const { displayed, typeIsShowable } = typedefRenderInfo(td, ctx); const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, td.name)}"></a>`, "", `### ${td.name}`, ""]; @@ -384,7 +391,7 @@ function renderTypedef(td, ctx) { lines.push(`_Type:_ ${displayed}`, ""); } if (td.properties?.length) { - lines.push("**Properties**", "", ...renderParamList(td.properties, ctx), ""); + lines.push("**Properties**", "", ...renderParamList(td.properties, tdCtx), ""); } lines.push(...renderBackingClassMembers(td, ctx)); return lines; @@ -424,7 +431,7 @@ function shouldRenderTypedef(td, ctx) { } function typedefRenderInfo(td, ctx) { - const displayed = td.type ? renderType(td.type, { ...ctx, selfName: td.name }) : ""; + const displayed = td.type ? renderType(td.type, { ...withTemplates(ctx, td), selfName: td.name }) : ""; const isSelfReference = displayed === `\`${td.name}\``; const isGenericPassthrough = /^`[A-Z][A-Za-z]?`$/.test(displayed); // Collapsed fallbacks (`object`, `unknown`, `any`) carry no real information @@ -463,7 +470,7 @@ const TUPLE = /^\[(.*)\]$/; // Turn a raw JSDoc type string into readable markdown. Prefers author-written // names over expanded TS structures; unknown gnarly types become `object`. function renderType(raw, ctx, opts = {}) { - const pretty = prettifyTypeString(raw); + const pretty = prettifyTypeString(raw, ctx?.selfName); if (opts.noLink) return `\`${pretty}\``; const utility = parseUtilityType(pretty); @@ -540,14 +547,20 @@ function renderArrayType(innerRaw, ctx) { return code ? `\`${code[1]}[]\`` : `${rendered}[]`; } +// A tuple is one code span with plain member names — ``[`number`, `number`]`` +// is indistinguishable from a broken markdown link, and a link nested inside a +// bracketed list reads worse than the plain name it replaces. function renderTupleType(innerRaw, ctx) { const parts = splitTopLevel(innerRaw, ","); if (parts.length === 1 && !parts[0].trim()) return "`[]`"; - const rendered = parts.map((p) => renderType(p.trim(), ctx)); - // A one-element tuple of a plain name would render as ``[`Name`]``, which is - // indistinguishable from a reST cross-reference — keep it inside the span. - const single = rendered.length === 1 && rendered[0].match(/^`([^`]+)`$/); - return single ? `\`[${single[1]}]\`` : `[${rendered.join(", ")}]`; + const rendered = parts.map((p) => stripMarkup(renderType(p.trim(), ctx))); + return `\`[${rendered.join(", ")}]\``; +} + +// Reduce rendered markdown (links, code spans) back to the bare type text, so +// it can be placed inside an enclosing code span. +function stripMarkup(rendered) { + return rendered.replace(/\[`([^`]+)`\]\([^)]*\)/g, "$1").replace(/`/g, ""); } function linkIfKnown(name, ctx) { @@ -664,15 +677,41 @@ function unwrapParens(s) { // they stay gnarly rather than being rewritten. const MAPPED_TYPE = /\[\s*\w+\s+in\s/; -function isRewritableConditional(s) { - return !/\binfer\b/.test(s) && !MAPPED_TYPE.test(s); +// Indexed access anywhere in the type (`Acc['length']`, `DIM[0]`): the +// condition depends on a value the union of branches can't express. +const INDEXED_ACCESS = /\[['"0-9]/; + +// Dropping the condition is only honest when the branches stand on their own. +// A depth-recursive type (`Acc['length'] extends Depth ? T : NestArray<T[], +// Depth, [...Acc, never]>`) encodes its termination in the condition itself: +// unioning the branches yields a type that is both wrong and unreadable. Bail +// on the constructs that mark such a type — a spread accumulator, an indexed +// access, or a reference to the typedef being defined — and leave it gnarly, so +// the caller collapses it exactly as it did before this rewrite existed. +// (A whole-branch `never` is fine: `rewriteConditionals` drops it, which is +// what a `never` fallback means. See the post-rewrite guard below for a `never` +// that survives.) +function isRewritableConditional(s, selfName) { + if (/\binfer\b/.test(s) || MAPPED_TYPE.test(s)) return false; + if (s.includes("...") || INDEXED_ACCESS.test(s)) return false; + if (selfName && new RegExp(`\\b${selfName}\\b`).test(s)) return false; + return true; +} + +// A `never` that is still in the rewritten string was never a branch of its +// own — it sits inside a type argument, where the union of branches can't +// explain it. Keep the original so it collapses instead. +function rewriteConditionalsSafely(s, selfName) { + if (!isRewritableConditional(s, selfName)) return s; + const rewritten = rewriteConditionals(s); + return /\bnever\b/.test(rewritten) ? s : rewritten; } // Strip noisy TS constructs from a type string without rewriting structure. // Conditional/mapped/infer types collapse to their outermost wrapper; simple // unions of names or long generic lists are preserved so the renderer can // split them into individual links. -function prettifyTypeString(raw) { +function prettifyTypeString(raw, selfName) { if (!raw) return ""; let s = raw.trim(); @@ -688,7 +727,7 @@ function prettifyTypeString(raw) { return s; } - if (isRewritableConditional(s)) s = rewriteConditionals(s); + s = rewriteConditionalsSafely(s, selfName); s = spaceObjectLiterals(s); if (isGnarly(s)) { diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index 0b3222c96..ff4224cd7 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -143,9 +143,11 @@ function renderTaskList({ tasks }) { function renderTaskRecipe(taskId, info, ctx) { const cls = findClass(ctx.ir, info.pipelineClass); const aliases = aliasesFor(taskId, ctx.tasks); - const lines = [`**Default model:** \`${info.defaultModel}\``]; - if (aliases.length) lines.push(`**Aliases:** ${aliases.map((a) => `\`${a}\``).join(", ")}`); - lines.push(""); + // Separate paragraphs: consecutive lines would collapse into one, running + // the aliases onto the end of the default-model line. Every other bold label + // in a task section (`**Example:**`) stands as its own paragraph too. + const lines = [`**Default model:** \`${info.defaultModel}\``, ""]; + if (aliases.length) lines.push(`**Aliases:** ${aliases.map((a) => `\`${a}\``).join(", ")}`, ""); if (cls?.description) lines.push(cls.description.trim(), ""); for (const ex of cls?.examples ?? []) lines.push(...exampleLines(ex)); return lines.join("\n").trimEnd(); From 6f42f3ab1b0f17919f2e9b28926dd0507fcd03e3 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:05:59 -0400 Subject: [PATCH 077/103] final cleanup --- .ai/skills/transformers-js/references/PIPELINE_OPTIONS.md | 5 ++--- .ai/skills/transformers-js/references/TASKS.md | 1 + packages/transformers/src/pipelines/image-classification.js | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 3cc3a5a53..2ae0e25d8 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -151,9 +151,8 @@ anywhere `generate()` is invoked directly. - Sampling vs. greedy: `temperature` and `top_k` only take effect with `do_sample: true`. Under greedy decoding (the default) they are silently ignored. -- Sampling vs. beams: `do_sample: true` takes precedence — beams are ignored - while sampling. For beam search, set `num_beams > 1` and leave `do_sample` - off. Don't set both. +- Beam search is not yet implemented: `num_beams > 1` is accepted but + generation still keeps a single sequence, so leave it at the default. <!-- @generated:start id=fields:GenerationConfig --> | Option | Type | Description | diff --git a/.ai/skills/transformers-js/references/TASKS.md b/.ai/skills/transformers-js/references/TASKS.md index d143ec032..5ce17a39e 100644 --- a/.ai/skills/transformers-js/references/TASKS.md +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -239,6 +239,7 @@ const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve const output = await classifier(url); // [ // { label: 'tiger, Panthera tigris', score: 0.632695734500885 }, +// ... (top 5 labels by default) // ] ``` diff --git a/packages/transformers/src/pipelines/image-classification.js b/packages/transformers/src/pipelines/image-classification.js index b25bbd79b..c9592fd1b 100644 --- a/packages/transformers/src/pipelines/image-classification.js +++ b/packages/transformers/src/pipelines/image-classification.js @@ -47,6 +47,7 @@ import { softmax } from '../utils/maths.js'; * const output = await classifier(url); * // [ * // { label: 'tiger, Panthera tigris', score: 0.632695734500885 }, + * // ... (top 5 labels by default) * // ] * ``` * From 03043ef70c7eee7d3d72c015f9c31a10da845373 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:16:45 -0400 Subject: [PATCH 078/103] cleanup comments --- CONTRIBUTING.md | 4 +- .../transformers/docs/scripts/build_readme.js | 9 +- .../transformers/docs/scripts/generate-all.js | 3 +- .../transformers/docs/scripts/lib/exports.mjs | 9 +- packages/transformers/docs/scripts/lib/fs.mjs | 3 +- packages/transformers/docs/scripts/lib/ir.mjs | 61 ++--- .../transformers/docs/scripts/lib/load.mjs | 5 +- .../transformers/docs/scripts/lib/parse.mjs | 12 +- .../docs/scripts/lib/render-api.mjs | 212 +++++++----------- .../docs/scripts/lib/render-skill.mjs | 61 ++--- .../transformers/docs/scripts/lib/scan.mjs | 27 +-- .../docs/scripts/lib/structure.mjs | 13 +- .../transformers/docs/scripts/lib/tasks.mjs | 5 +- .../transformers/docs/scripts/lib/text.mjs | 41 ++-- .../docs/scripts/lib/validate.mjs | 19 +- .../transformers/docs/scripts/validate.js | 6 +- .../src/generation/stopping_criteria.js | 11 +- packages/transformers/src/pipelines.js | 2 +- packages/transformers/src/utils/hub/utils.js | 18 +- .../src/utils/model_registry/ModelRegistry.js | 21 +- .../model_registry/get_available_dtypes.js | 13 +- .../utils/model_registry/get_file_metadata.js | 31 +-- packages/transformers/src/utils/tensor.js | 18 +- .../tests/utils/model_registry.test.js | 3 +- 24 files changed, 223 insertions(+), 384 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be2a3641a..9890d96e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -280,8 +280,8 @@ Follow the steps below to start contributing: - ☐ If adding a new feature, also add tests for it. - ☐ If your changes affect user-facing functionality, update the relevant documentation. - ☐ If you changed JSDoc comments, README snippets, or task metadata, regenerate the docs - (`pnpm --filter @huggingface/transformers docs-generate`) and commit the result — CI - fails if generated files drift from the source. See [Documentation](#documentation). + (`pnpm --filter @huggingface/transformers docs-generate`) and commit the result. + See [Documentation](#documentation). ### Tests We are using [Jest](https://jestjs.io/) to execute unit-tests. All tests can be found in `packages/transformers/tests` and have to end with `.test.js` diff --git a/packages/transformers/docs/scripts/build_readme.js b/packages/transformers/docs/scripts/build_readme.js index 9f614a327..2dfae4e23 100644 --- a/packages/transformers/docs/scripts/build_readme.js +++ b/packages/transformers/docs/scripts/build_readme.js @@ -16,8 +16,7 @@ const FILES_TO_INCLUDE = { const PIPELINE_API_LINK_PREFIX = `${DOCS_BASE_URL}/api/pipelines#module_pipelines.`; -// Links that should point somewhere other than the direct docs URL. Most are -// README-local anchors or guide/API pages referenced by snippets. +// Links that point somewhere other than the direct docs URL: README-local anchors, guide/API pages. const CUSTOM_LINK_MAP = { "./custom_usage#convert-your-models-to-onnx": "#convert-your-models-to-onnx", "/custom_usage#convert-your-models-to-onnx": "#convert-your-models-to-onnx", @@ -26,8 +25,7 @@ const CUSTOM_LINK_MAP = { "./guides/dtypes": `${DOCS_BASE_URL}/guides/dtypes`, }; -// Output: the repo root README. Resolved relative to packageRoot -// (`packages/transformers`) so the path works from any cwd. +// Repo-root README, resolved relative to packageRoot so the path works from any cwd. const README_OUT = "../../README.md"; export function buildReadme({ project }) { @@ -103,8 +101,7 @@ function demoteHeadings(markdown) { } function fixLinks(markdown, apiLinks) { - // This is not a complete Markdown parser, just the narrow link rewrite - // needed by the README snippets. + // Not a Markdown parser, just the narrow link rewrite the README snippets need. return markdown.replace(/(?<=\])\((.+?)\)/gm, (_, rawLink) => { let link = rawLink; if (link in CUSTOM_LINK_MAP) { diff --git a/packages/transformers/docs/scripts/generate-all.js b/packages/transformers/docs/scripts/generate-all.js index 2908b22e4..4fcc1c9ea 100644 --- a/packages/transformers/docs/scripts/generate-all.js +++ b/packages/transformers/docs/scripts/generate-all.js @@ -11,8 +11,7 @@ import { formatValidationResult, validateGeneratedDocs } from "./lib/validate.mj const project = loadProject(packageRoot); -// Run every phase even if an earlier one fails, so a single run surfaces all -// problems; collect errors per phase and fail at the end. +// Run every phase even if an earlier one fails, so one run surfaces every problem. const errors = []; const runPhase = (name, fn) => { try { diff --git a/packages/transformers/docs/scripts/lib/exports.mjs b/packages/transformers/docs/scripts/lib/exports.mjs index caac985d2..ada2225bf 100644 --- a/packages/transformers/docs/scripts/lib/exports.mjs +++ b/packages/transformers/docs/scripts/lib/exports.mjs @@ -66,8 +66,8 @@ function visitTopLevel(node, sf, dir, visited, names) { } } -// Look up the named exports in `file` and, for each one whose initializer is -// `Object.freeze({ ... })`, pull its shorthand-property names into `names`. +// For each named export in `file` whose initializer is `Object.freeze({ ... })`, pull its +// shorthand-property names into `names`. function addNamespaceMembersFromFile(file, wanted, names) { if (!fs.existsSync(file)) return; const sf = parseJsFile(file); @@ -82,9 +82,8 @@ function addNamespaceMembersFromFile(file, wanted, names) { }); } -// If the initializer is `Object.freeze({ ... })`, also treat each shorthand -// property name inside the object literal as publicly reachable via the -// exported namespace (e.g. `random.Random`). +// Shorthand properties of an `Object.freeze({ ... })` initializer are reachable through the +// exported namespace (e.g. `random.Random`), so they count as public. function addFrozenNamespaceMembers(init, names) { const literal = unwrapObjectFreeze(init); if (!literal) return; diff --git a/packages/transformers/docs/scripts/lib/fs.mjs b/packages/transformers/docs/scripts/lib/fs.mjs index e6f6fc029..325b0b256 100644 --- a/packages/transformers/docs/scripts/lib/fs.mjs +++ b/packages/transformers/docs/scripts/lib/fs.mjs @@ -3,8 +3,7 @@ import fs from "node:fs"; import path from "node:path"; -// All files under `dir` (recursive) whose name ends with `ext`, sorted by -// their `/`-separated relative path so ordering is platform-independent. +// Recursive; sorted by `/`-separated relative path so ordering is platform-independent. export function listFiles(dir, ext) { if (!fs.existsSync(dir)) return []; return fs diff --git a/packages/transformers/docs/scripts/lib/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs index 1a12e29be..d503cdf67 100644 --- a/packages/transformers/docs/scripts/lib/ir.mjs +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -32,9 +32,8 @@ export function buildIR(fileEntities) { return { modules: moduleList, typedefIndex: buildTypedefIndex(modules) }; } -// Classes win (authoritative for a name). Typedefs fill gaps. Re-export -// aliases like `@typedef {import('x').Foo} Foo` register last so they can't -// shadow the canonical definition. +// Classes are authoritative for a name; typedefs fill gaps. Re-export aliases +// (`@typedef {import('x').Foo} Foo`) register last so they can't shadow the canonical definition. function buildTypedefIndex(modules) { const index = new Map(); for (const mod of modules.values()) { @@ -131,9 +130,8 @@ function mergeByName(target, items) { } } -// Keep one entry per name, at the position of its first occurrence. A -// re-export alias (`@typedef {import('./x.js').Foo} Foo`) says nothing the -// real declaration doesn't, so when both are present the real one wins — +// One entry per name, at its first occurrence. When both a re-export alias +// (`@typedef {import('./x.js').Foo} Foo`) and the real declaration exist, the real one wins: // otherwise the name renders as a self-reference and every link to it dangles. function dedupeTypes(items) { const best = new Map(); @@ -181,8 +179,8 @@ function ingest(entities, mod) { } for (const v of entities.variables) { if (isPrivate(v)) continue; - // A `const` annotated with `@param` / `@returns` is a function alias — - // hoist it into the functions section so it renders with a proper signature. + // A `const` annotated with `@param` / `@returns` is a function alias; hoist it into the + // functions section so it renders with a signature. if (tagsOf(v, "param").length || tagsOf(v, "returns").length) { mod.functions.push(buildCallable(v)); continue; @@ -198,15 +196,13 @@ function ingest(entities, mod) { } } -// A single @typedef/@callback block may declare multiple types; @property tags -// that follow each typedef attach to it, up to the next typedef/callback. +// One `@typedef`/`@callback` block may declare several types; `@property` tags attach to the +// preceding typedef, up to the next one. function collectTypedefsFromBlock(block, mod) { let current = null; let first = true; - // `@template {Constraint} [T=default]` tags sit on the block, not on the - // individual `@typedef`. Carrying them onto every type declared by the block - // lets the renderer resolve `T` in the type expression and in `@property` - // types instead of falling back to `any`. + // `@template` tags sit on the block, not on the individual `@typedef`. Carry them onto every type + // the block declares so the renderer can resolve `T` instead of falling back to `any`. const blockTemplates = templatesOf(block); const flush = () => { if (!current) return; @@ -257,9 +253,8 @@ function buildMember(m) { kind: m.kind, name: m.name, description: m.description, - // An accessor's type is usually written as `@type`, but a getter may - // instead document it as `@returns {T}` — same information, and without - // this fallback the member renders (or is skipped) with no type at all. + // A getter may document its type as `@returns {T}` rather than `@type`; without the fallback + // the member has no type at all. type: typeOf(m) ?? (m.kind === "getter" ? (pickReturns(m)?.type ?? null) : null), defaultValue: m.tags.find((t) => t.tag === "default")?.value ?? m.initializer ?? null, deprecated: m.tags.some((t) => t.tag === "deprecated"), @@ -276,9 +271,8 @@ function buildCallable(fn) { aliasType: typeOf(fn), see: seeTagsOf(fn), throws: tagsOf(fn, "throws").map((t) => ({ type: t.type, description: t.description })), - // `@template {Constraint} Name` maps the generic name to its constraint; - // used by the renderer to resolve generic parameter names to something - // readable instead of a bare `any`. + // `@template {Constraint} Name` maps a generic name to its constraint, which the renderer shows + // instead of a bare `any`. templates: templatesOf(fn), examples, deprecated: fn.tags.some((t) => t.tag === "deprecated"), @@ -294,16 +288,13 @@ function attachClassCallables(modules) { const callbackName = `${cls.name}Callback`; const callback = callbacks.get(callbackName); const callSource = callMember && hasCallableShape(callMember) ? callMember : null; - // The `XCallback` typedef carries the precise generic signature but no - // prose; a documented `_call` carries both. Prefer whichever explains - // what calling the instance actually does. + // The `XCallback` typedef has the precise generic signature but no prose; a documented + // `_call` has both. Prefer whichever explains what calling the instance does. const source = (callSource?.description ? callSource : null) ?? callback ?? callSource; if (!source) continue; - // Promote the `_call` / `Callback` description onto the synthesized - // signature so the rendered `Foo(...)` line carries prose explaining - // what calling the instance does. Without this the page shows only the - // bare signature. + // Promote the source description onto the synthesized signature; without it the page shows a + // bare `Foo(...)` line. cls.callable = { ...normalizeCallable(source), name: cls.name, @@ -386,10 +377,9 @@ function parseFunctionType(raw) { }; } -// `<Q extends string | string[], const O extends Options = {}>` — a parameter -// may carry a `const` modifier and a default. Neither says anything a reader of -// the rendered signature needs, but both used to make the whole entry -// unparseable, which left `O` rendering as a bare `O`. +// A template parameter may carry a `const` modifier and a default +// (`<Q extends string | string[], const O extends Options = {}>`). Both are dropped, but the entry +// still has to parse, or its name renders unresolved. function parseTemplateList(raw) { return splitTopLevel(raw, ",") .map((part) => { @@ -509,8 +499,7 @@ function findCallable(ref, callableIndex) { return callableIndex.get(parsed.method ? `${parsed.owner}.${parsed.method}` : parsed.owner); } -// `@template {Constraint} Name` -> `{ name, type }`, the shape the renderer -// uses to resolve generic parameter names to their constraints. +// `@template {Constraint} Name` -> `{ name, type }`, the shape the renderer resolves against. function templatesOf(entity) { return tagsOf(entity, "template") .map((t) => ({ name: t.name, type: t.type })) @@ -550,10 +539,8 @@ function cleanParamDescription(description) { return description.replace(/^\([^)]*\boptional\b[^)]*\):\s*/i, "").trim(); } -// Canonical example format: `**Example:** <title>\n```lang\n<code>\n```` inside -// any JSDoc description body. Extracted into a structured `examples` array so -// renderers can emit them consistently; the source lines are stripped from -// the description so they're not rendered twice. +// Canonical example format inside any JSDoc description body. Matches are moved into a structured +// `examples` array and stripped from the description so they aren't rendered twice. const INLINE_EXAMPLE = /\*\*Example:\*\*\s*([^\n]*)\n+```(\w+)?\n([\s\S]*?)\n```/g; function gatherExamples(description) { diff --git a/packages/transformers/docs/scripts/lib/load.mjs b/packages/transformers/docs/scripts/lib/load.mjs index 8cfb27d32..75692f14c 100644 --- a/packages/transformers/docs/scripts/lib/load.mjs +++ b/packages/transformers/docs/scripts/lib/load.mjs @@ -1,6 +1,5 @@ -// Shared loader: reads every `.js` file under `src/`, extracts TS-backed -// entities, builds the IR, and returns it along with the public-export set -// and the task catalog. Both the API-markdown and skill renderers consume it. +// Reads every `.js` file under `src/` and returns the IR, the public-export set and the task +// catalog. Both the API-markdown and skill renderers consume it. import fs from "node:fs"; import path from "node:path"; diff --git a/packages/transformers/docs/scripts/lib/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs index 7b4e6234f..fed2c990c 100644 --- a/packages/transformers/docs/scripts/lib/parse.mjs +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -51,9 +51,8 @@ function parseTag(raw) { const tag = nameMatch[1]; let rest = raw.slice(nameMatch[0].length).replace(/^[ \t]+/, ""); - // `@default` and `@see` payloads may start with a brace that is *not* a - // JSDoc type expression (a literal value, or `{@link ...}`) — skip the type - // extraction for them. + // `@default` and `@see` payloads may start with a brace that is not a type expression (a literal + // value, or `{@link ...}`). let type = null; if (rest.startsWith("{") && tag !== "default" && tag !== "see") { const extracted = extractBalancedBraces(rest, 0); @@ -80,10 +79,9 @@ function parseTag(raw) { case "callback": return { tag, name: rest.trim().split(/\s+/)[0] || null }; case "template": { - // `@template {Constraint} Name description` — just the first identifier - // after any optional constraint is the template-parameter name. The name - // may also be written `[Name=Default]`; the default is documentation - // noise, but dropping the whole tag would leave `Name` unresolvable. + // In `@template {Constraint} Name description` the name is the first identifier after the + // optional constraint, and may be written `[Name=Default]`. The default is dropped, the tag + // is not: without it `Name` is unresolvable. const m = rest.match(/^\[?([A-Za-z_$][\w$]*)/); return { tag, type, name: m?.[1] ?? null }; } diff --git a/packages/transformers/docs/scripts/lib/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs index 7db2b461d..a6276b43e 100644 --- a/packages/transformers/docs/scripts/lib/render-api.mjs +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -10,14 +10,11 @@ import { matchingBracket, splitConditional, splitTopLevel } from "./scan.mjs"; import { DOC_REFERENCE, exampleLines, firstSentence, paramSignature, stripImportPrefixes, transformOutsideFences } from "./text.mjs"; import { parseCallableReference, parseUtilityType, TS_UTILITY_NAMES } from "./type-refs.mjs"; -// Pages with at least this many top-level items get an "On this page" TOC so -// the reader doesn't have to scroll an entire page to find a class. +// Pages with at least this many top-level items get an "On this page" TOC. const TOC_THRESHOLD = 6; -// True when the module contributes any reader-visible content to its API page — -// at least one public class/function/constant, or a renderable typedef/callback. -// Used by `generateApiDocs` to skip writing pure-internal modules without -// having to inspect the rendered markdown. +// True when the module has reader-visible content: a public class/function/constant, or a renderable +// typedef/callback. Lets `generateApiDocs` skip internal-only modules without rendering them. export function hasRenderableContent(mod, publicNames = null) { if (filterPublic(mod.classes, publicNames).length) return true; if (filterPublic(mod.functions, publicNames).length) return true; @@ -26,9 +23,8 @@ export function hasRenderableContent(mod, publicNames = null) { return mod.typedefs.some((td) => !isInternalTypedef(td)); } -// Rendered-name and callable-link indexes depend only on `(ir, publicNames)`, -// so callers rendering many pages build them once and pass them to -// `renderModule` via `opts.linkIndexes`. +// The indexes depend only on `(ir, publicNames)`: build once and pass to `renderModule` via +// `opts.linkIndexes` when rendering many pages. export function buildLinkIndexes(ir, publicNames = null) { return { renderedNames: buildRenderedNameIndex(ir, publicNames), @@ -43,8 +39,8 @@ export function renderModule(mod, ir, opts) { typedefIndex: ir.typedefIndex, moduleByName: new Map(ir.modules.map((m) => [m.name, m])), moduleName: mod.name, - // Unfiltered: `renderTypedef` needs the module's `_`-prefixed classes, - // which are deliberately absent from the public export surface. + // Unfiltered: `renderTypedef` needs the module's `_`-prefixed classes, which the public + // export surface omits. moduleClasses: mod.classes, renderedNames, callableLinks, @@ -82,16 +78,13 @@ export function renderModule(mod, ir, opts) { for (const cb of mod.callbacks) out.push(...renderFunction(cb, ctx, 3, null, { nameOnlyHeading: true })); } - // Link expansion and blank-run collapsing must not touch example code, so - // they run with fenced blocks masked; trailing-whitespace cleanup is safe - // (and applied) everywhere. + // Link expansion and blank-run collapsing must not touch example code, so fenced blocks are + // masked; trailing-whitespace cleanup is safe everywhere. return transformOutsideFences(out.join("\n").replace(/[ \t]+$/gm, ""), (text) => expandInlineLinks(text, ctx).replace(/\n{3,}/g, "\n\n")).trimEnd() + "\n"; } -// Render a one-line "On this page" TOC, but only when the page is large -// enough to make scrolling expensive. We list classes, functions, and -// constants — typedefs and callbacks are usually a much longer tail and -// would dominate the TOC; readers can find them via the section heading. +// Typedefs and callbacks are a long tail that would dominate the TOC, so only classes, functions +// and constants are listed; readers reach the rest via the section heading. function renderTOC({ classes, functions, constants }, ctx) { const total = classes.length + functions.length + constants.length; if (total < TOC_THRESHOLD) return []; @@ -150,12 +143,8 @@ function dedupeByName(items) { return items.filter((it) => (seen.has(it.name) ? false : seen.add(it.name))); } -// reST-style ``[`TypeName`]`` cross-references carried over from the Python -// library are left in place here — `expandInlineLinks` turns them into real -// links (or plain code spans) once the page-level symbol index is available. -// Normalize `@see Symbol` / `@see {@link Symbol}` to inline markup so it reads -// as prose rather than raw JSDoc tags — `{@link ...}` is expanded later by -// `expandInlineLinks` too. +// reST-style ``[`TypeName`]`` references are left alone; `expandInlineLinks` resolves them once the +// page-level symbol index exists. `@see Symbol` becomes inline markup so it reads as prose. function cleanDescription(text) { return text .trim() @@ -163,9 +152,8 @@ function cleanDescription(text) { .replace(/@see\s+`?([A-Za-z_$][\w$.]*)`?/g, "`$1`"); } -// Line-leading `@see` tags parse into an entity's `see` list; surface them so -// the reference survives into the rendered page. `{@link ...}` entries are -// expanded to markdown links by the page-level `expandInlineLinks` pass. +// Line-leading `@see` tags land in `entity.see`; any `{@link ...}` inside them is expanded later by +// `expandInlineLinks`. function seeAlso(entity) { return entity.see?.length ? [`**See also:** ${entity.see.join(", ")}`, ""] : []; } @@ -208,8 +196,8 @@ function renderClass(cls, ctx) { function renderField(f, ctx, parent) { if (f.name.startsWith("_")) return []; - // Skip undocumented, untyped field entries — pure placeholders with no - // reader value. Deprecation flags keep a field visible as a warning. + // Undocumented, untyped placeholders carry nothing; a deprecation flag still keeps a field + // visible as a warning. if (!f.type && !f.description && !f.deprecated && f.defaultValue == null) return []; const type = f.type ? ` : ${renderType(f.type, ctx)}` : ""; @@ -223,24 +211,20 @@ function renderField(f, ctx, parent) { return lines; } -// `_`-prefixed methods are the library's convention for internal / subclass-only -// hooks — they're not part of the user-facing API. Exception: `constructor` is -// kept even though it has no leading underscore. +// `_`-prefixed methods are internal / subclass-only hooks by convention, so they stay out of the +// user-facing API. export function shouldRenderMethod(m) { if (m.name.startsWith("_")) return false; return m.description || m.params?.length || m.returns?.description || m.returns?.type || m.examples?.length || m.throws?.length; } -// Also renders callback typedefs (`opts.nameOnlyHeading`) — they're types -// rather than invocable functions, so the heading is the bare name instead of -// a `name(params)` signature. +// `opts.nameOnlyHeading` renders callback typedefs: they are types rather than invocable +// functions, so the heading is the bare name and not a `name(params)` signature. function renderFunction(fn, ctx, depth, parent = null, opts = {}) { const anchor = parent ? apiMemberAnchor(ctx.moduleName, parent, fn.anchorName ?? fn.name) : apiSymbolAnchor(ctx.moduleName, fn.anchorName ?? fn.name); const heading = opts.nameOnlyHeading ? fn.name : signature(fn, parent); const lines = [`<a id="${anchor}"></a>`, "", `${"#".repeat(depth)} ${heading}`, ""]; - // Resolve generic type parameters (`@template {Constraint} T`) inside this - // function's parameter/return types. Without the constraint map, a `T` - // would render as `any`. + // Without the constraint map, a `T` in the parameter/return types would render as `any`. const fnCtx = withTemplates(ctx, fn); if (fn.deprecated) lines.push("> **Deprecated**", ""); @@ -296,9 +280,8 @@ function renderParamNode(p, ctx, indent) { const type = p.type ? ` (${renderType(p.type, ctx)})` : ""; const opt = p.optional ? " _optional_" : ""; const def = p.defaultValue != null ? ` — defaults to \`${p.defaultValue}\`` : ""; - // Fall back to the referenced typedef's first-sentence description when the - // author didn't write one inline. Without this, parameters typed as a named - // typedef render as a bare `- name (Type) optional` with no prose hint. + // Parameters typed as a named typedef and given no inline description borrow that typedef's + // first sentence, rather than rendering as a bare `- name (Type) optional`. const fallback = p.description ? null : typedefSummary(p.type, ctx); const descText = p.description?.trim() || fallback; const desc = descText ? ` — ${indentContinuations(descText, contPad)}` : ""; @@ -306,9 +289,8 @@ function renderParamNode(p, ctx, indent) { return [line, ...p.children.flatMap((c) => renderParamNode(c, ctx, indent + 1))]; } -// Look up the named typedef for a parameter's type and return the first -// sentence of its description, if any. Skips obvious wrappers (unions, -// arrays, generics) to avoid misleading borrows. +// First sentence of the named typedef's description. Skips wrappers (unions, arrays, generics) to +// avoid misleading borrows. function typedefSummary(rawType, ctx) { if (!rawType || !ctx.typedefIndex) return null; const pretty = prettifyTypeString(rawType); @@ -321,8 +303,7 @@ function typedefSummary(rawType, ctx) { return firstSentence(def.description); } -// Indent every line after the first so multi-line descriptions (including -// bulleted continuations) render inside their parent list item. +// Indent continuation lines so multi-line descriptions render inside their parent list item. function indentContinuations(text, pad) { const lines = text.split("\n"); if (lines.length === 1) return text; @@ -352,9 +333,8 @@ function renderConstant(c, ctx) { return lines; } -// Skip typedefs that exist purely to thread generic parameters through the -// type system (`@typedef {T} Name`, `_`-prefixed names) or that are opaque -// placeholders with no useful content to show a reader. +// Skip typedefs that only thread generic parameters through the type system (`@typedef {T} Name`, +// `_`-prefixed names) or that are opaque placeholders with nothing to show. function isInternalTypedef(td) { if (td.name.startsWith("_")) return true; const type = (td.type ?? "").trim(); @@ -365,14 +345,13 @@ function isInternalTypedef(td) { return false; } -// Generic type parameter names (T, K, V, TItem, TReturnTensor, ...) — show as -// `any` in rendered types so the reader doesn't chase an undefined symbol. +// Generic type parameter names (T, K, V, TItem, ...). Rendered as `any` so the reader doesn't +// chase an undefined symbol. function isGenericParamName(name) { return /^T[A-Z][A-Za-z]*$/.test(name) || /^[TKV]$/.test(name); } -// Resolve `@template {Constraint} T` names inside an entity's types. Without -// the constraint map, a `T` renders as `any`. +// Constraint map for `@template {Constraint} T`; an unconstrained `T` renders as `any`. function withTemplates(ctx, entity) { const templateMap = new Map(); for (const t of entity?.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); @@ -397,11 +376,9 @@ function renderTypedef(td, ctx) { return lines; } -// `@typedef {Record<string, Tensor> & _DynamicCache} DynamicCache` is the -// library's way of saying "a plain object that also has these methods": the -// members live on a `_`-prefixed class that is never documented on its own, -// and only the typedef name is exported. Find that class so its members can be -// rendered under the typedef. +// `@typedef {Record<string, Tensor> & _DynamicCache} DynamicCache` means "a plain object that also +// has these methods": the members live on a `_`-prefixed class that is never documented on its own. +// Find that class so its members render under the typedef. function backingClass(td, ctx) { if (!td.type || !ctx.moduleClasses?.length) return null; const parts = splitTopLevel(prettifyTypeString(td.type), "&").map((p) => p.trim()); @@ -414,10 +391,8 @@ function backingClass(td, ctx) { return null; } -// Render the backing class's constructor and public methods under the typedef, -// anchored to the typedef's name (`module_x.DynamicCache.update`) so links -// point at the name readers actually use. The `_Type:_` line above already -// shows the remaining, non-private half of the intersection. +// Anchored to the typedef's name (`module_x.DynamicCache.update`) so links point at the name +// readers use. The `_Type:_` line above already shows the non-private half of the intersection. function renderBackingClassMembers(td, ctx) { const cls = backingClass(td, ctx); if (!cls) return []; @@ -434,18 +409,15 @@ function typedefRenderInfo(td, ctx) { const displayed = td.type ? renderType(td.type, { ...withTemplates(ctx, td), selfName: td.name }) : ""; const isSelfReference = displayed === `\`${td.name}\``; const isGenericPassthrough = /^`[A-Z][A-Za-z]?`$/.test(displayed); - // Collapsed fallbacks (`object`, `unknown`, `any`) carry no real information - // — showing `_Type:_ `object`` adds noise without helping the reader. + // Collapsed fallbacks carry no information: `_Type:_ `object`` is noise. const isOpaque = ["`object`", "`Object`", "`unknown`", "`any`"].includes(displayed); - // Unions and intersections are worth showing even when long — every variant - // is a named type the reader can click through to. An inline object literal - // has no clickable parts, so it earns its place only while it still fits on - // a line. Typedefs with a **Properties** list show that instead. + // Unions and intersections are shown even when long: every variant is a clickable named type. An + // object literal has no clickable parts, so it must still fit on a line. Typedefs with a + // **Properties** list show that instead. const isUnionOrIntersection = / \| | & /.test(displayed); const isObjectLiteral = displayed.startsWith("`{"); const fitsInline = displayed.length < 120; - // `_`-prefixed names are internal by convention and never get a page of - // their own, so a type built out of one points the reader nowhere. + // `_`-prefixed names never get a page of their own, so a type built from one points nowhere. const referencesInternal = /`_[A-Za-z]/.test(displayed); const typeIsShowable = displayed && @@ -467,8 +439,8 @@ const SIMPLE_NAME = new RegExp(`^[A-Za-z_$][\\w$.]*$`); const INDEXED_NAME = /^([A-Za-z_$][\w$.]*)\[[^\]]+\]$/; const TUPLE = /^\[(.*)\]$/; -// Turn a raw JSDoc type string into readable markdown. Prefers author-written -// names over expanded TS structures; unknown gnarly types become `object`. +// Turn a raw JSDoc type string into readable markdown. Prefers author-written names over expanded +// TS structures; gnarly types become `object`. function renderType(raw, ctx, opts = {}) { const pretty = prettifyTypeString(raw, ctx?.selfName); if (opts.noLink) return `\`${pretty}\``; @@ -476,20 +448,19 @@ function renderType(raw, ctx, opts = {}) { const utility = parseUtilityType(pretty); if (utility) return renderUtilityType(utility, ctx); - // Unions split first. `_`-prefixed variants (internal types from intersections) - // are filtered out so they don't leak into the public docs. + // Unions split first. `_`-prefixed variants (internal types from intersections) are dropped so + // they don't leak into the public docs. const unionParts = splitTopLevel(pretty, "|") .map((p) => p.trim()) .filter((p) => !/^_[A-Za-z]/.test(p)); if (unionParts.length > 1) { - // Every variant is rendered (and linked) on its own. `T | T[]` deliberately - // stays as two parts — a collapsed `T[]?` reads as "optional array", which - // is not what the union means. + // `T | T[]` stays two parts: a collapsed `T[]?` reads as "optional array", which the union + // does not mean. return unionParts.map((p) => renderType(p, ctx)).join(" | "); } if (unionParts.length === 1 && unionParts[0] !== pretty.trim()) return renderType(unionParts[0], ctx); - // Same for intersections (`A & B`): drop internal variants before joining. + // Same for intersections (`A & B`). const intersectParts = splitTopLevel(pretty, "&") .map((p) => p.trim()) .filter((p) => !/^_[A-Za-z]/.test(p)); @@ -547,9 +518,8 @@ function renderArrayType(innerRaw, ctx) { return code ? `\`${code[1]}[]\`` : `${rendered}[]`; } -// A tuple is one code span with plain member names — ``[`number`, `number`]`` -// is indistinguishable from a broken markdown link, and a link nested inside a -// bracketed list reads worse than the plain name it replaces. +// One code span with plain member names: ``[`number`, `number`]`` is indistinguishable from a +// broken markdown link. function renderTupleType(innerRaw, ctx) { const parts = splitTopLevel(innerRaw, ","); if (parts.length === 1 && !parts[0].trim()) return "`[]`"; @@ -557,8 +527,7 @@ function renderTupleType(innerRaw, ctx) { return `\`[${rendered.join(", ")}]\``; } -// Reduce rendered markdown (links, code spans) back to the bare type text, so -// it can be placed inside an enclosing code span. +// Reduce rendered markdown back to bare type text so it can sit inside an enclosing code span. function stripMarkup(rendered) { return rendered.replace(/\[`([^`]+)`\]\([^)]*\)/g, "$1").replace(/`/g, ""); } @@ -612,13 +581,9 @@ function renderCallableReference(raw, ctx) { return linkCallable(ref.owner, ref.owner, ctx) ?? linkIfKnown(ref.owner, ctx) ?? `\`${ref.owner}\``; } -// A conditional type (`Check extends X ? A : B`) resolves to one branch or the -// other, so the union of its branches is an honest — and readable — stand-in. -// Rewrite recursively: a branch may itself be a conditional, and a conditional -// may sit inside a wrapper's generic arguments (`Promise<T extends X ? A[] : A>` -// -> `Promise<A[] | A>`). Branches keep source order (true branch first) and -// identical parts are deduped. Callers must skip strings containing `infer` or -// a mapped type — those stay gnarly. +// Rewrite `Check extends X ? A : B` to `A | B`, recursively and inside generic args +// (`Promise<T extends X ? A[] : A>` -> `Promise<A[] | A>`). Source branch order is kept; duplicates +// are removed. Callers skip `infer` and mapped types. function rewriteConditionals(raw) { const s = unwrapParens(raw.trim()); if (!s.includes("extends")) return s; @@ -663,8 +628,7 @@ function rewriteConditionals(raw) { return s; } -// Drop parentheses that wrap an entire type — source authors add them to group -// nested conditionals, and keeping them would leave `(A | B) | C` in the output. +// Drop parentheses wrapping an entire type; keeping them would leave `(A | B) | C` in the output. function unwrapParens(s) { let out = s; while (out.startsWith("(") && matchingBracket(out, 0, "(", ")") === out.length - 1) { @@ -673,24 +637,16 @@ function unwrapParens(s) { return out; } -// Mapped types (`{[K in Keys]: V}`) and `infer` have no readable expansion — -// they stay gnarly rather than being rewritten. +// Mapped types (`{[K in Keys]: V}`) have no readable expansion, so they stay gnarly. const MAPPED_TYPE = /\[\s*\w+\s+in\s/; -// Indexed access anywhere in the type (`Acc['length']`, `DIM[0]`): the -// condition depends on a value the union of branches can't express. +// Indexed access (`Acc['length']`, `DIM[0]`): the condition depends on a value the union of +// branches can't express. const INDEXED_ACCESS = /\[['"0-9]/; -// Dropping the condition is only honest when the branches stand on their own. -// A depth-recursive type (`Acc['length'] extends Depth ? T : NestArray<T[], -// Depth, [...Acc, never]>`) encodes its termination in the condition itself: -// unioning the branches yields a type that is both wrong and unreadable. Bail -// on the constructs that mark such a type — a spread accumulator, an indexed -// access, or a reference to the typedef being defined — and leave it gnarly, so -// the caller collapses it exactly as it did before this rewrite existed. -// (A whole-branch `never` is fine: `rewriteConditionals` drops it, which is -// what a `never` fallback means. See the post-rewrite guard below for a `never` -// that survives.) +// Bail on conditionals whose condition carries meaning the branches lack: a spread accumulator, an +// indexed access, or a self-reference (e.g. depth-recursive `NestArray`). A whole-branch `never` is +// fine; `rewriteConditionals` drops it. function isRewritableConditional(s, selfName) { if (/\binfer\b/.test(s) || MAPPED_TYPE.test(s)) return false; if (s.includes("...") || INDEXED_ACCESS.test(s)) return false; @@ -698,25 +654,22 @@ function isRewritableConditional(s, selfName) { return true; } -// A `never` that is still in the rewritten string was never a branch of its -// own — it sits inside a type argument, where the union of branches can't -// explain it. Keep the original so it collapses instead. +// A `never` left in the rewritten string was never a branch of its own: it sits inside a type +// argument, where the branch union can't explain it. Keep the original so it collapses. function rewriteConditionalsSafely(s, selfName) { if (!isRewritableConditional(s, selfName)) return s; const rewritten = rewriteConditionals(s); return /\bnever\b/.test(rewritten) ? s : rewritten; } -// Strip noisy TS constructs from a type string without rewriting structure. -// Conditional/mapped/infer types collapse to their outermost wrapper; simple -// unions of names or long generic lists are preserved so the renderer can -// split them into individual links. +// Strip noisy TS constructs without rewriting structure. Conditional/mapped/infer types collapse to +// their outermost wrapper; unions of names and long generic lists survive so the renderer can link +// each part. function prettifyTypeString(raw, selfName) { if (!raw) return ""; let s = raw.trim(); - // Types written across several JSDoc lines arrive with embedded newlines; - // the scanners below all reason about single-line text. + // Multi-line JSDoc types arrive with newlines; the scanners below assume single-line text. s = s.replace(/\s+/g, " ").trim(); s = stripLeading(s, "<", ">"); // <T extends X>(...) s = stripImportPrefixes(s); @@ -732,21 +685,16 @@ function prettifyTypeString(raw, selfName) { if (isGnarly(s)) { const outer = s.match(/^([A-Za-z_$][\w$.]*)(?:<|\s|$)/); - // Built-in TS utility types are meaningless shorn of their arguments — the - // reader is better served by `unknown` than by a naked `Parameters`. + // A built-in utility type is meaningless shorn of its arguments (a naked `Parameters`). if (outer && TS_UTILITY_NAMES.has(outer[1])) return "unknown"; - // Generic wrappers like `Promise<...gnarly...>` need to keep the wrapper — - // a bare `Promise` is misleading. Replace the inner with `unknown` so the - // reader sees `Promise<unknown>` instead. + // Keep the wrapper: a bare `Promise` is misleading, so emit `Promise<unknown>`. if (outer && /^[A-Za-z_$][\w$.]*</.test(s)) return `${outer[1]}<unknown>`; return outer ? outer[1] : "object"; } return s.replace(/\s+/g, " ").trim(); } -// `{a: X}` -> `{ a: X }`. Inline object literals are rendered verbatim, and -// they read much better with breathing room inside the braces. Empty `{}` and -// braces that already have a space are left alone. +// `{a: X}` -> `{ a: X }`. Empty `{}` and already-spaced braces are left alone. function spaceObjectLiterals(s) { return s.replace(/\{(?![\s}])/g, "{ ").replace(/(?<![\s{])\}/g, " }"); } @@ -756,22 +704,18 @@ function stripLeading(s, open, close) { return end === -1 ? s : s.slice(end + 1).trim(); } -// Beyond this, a type string stops being something a reader can take in at a -// glance in a table cell or bullet — it collapses to its outermost wrapper. +// Beyond this a type stops being scannable in a bullet or table cell, and collapses to its +// outermost wrapper. const MAX_INLINE_TYPE_LENGTH = 160; -// What survives `rewriteConditionals` is renderable verbatim — including -// callable types (`(a: X) => Y`) and inline object literals (`{a: X, b?: Y}`), -// whose `?` markers are perfectly readable. Genuinely unreadable shapes are -// mapped types (`{[K in ...]: V}`), `infer`, an `extends` we couldn't rewrite, -// indexed accesses into typeof expressions (`Parameters<X['foo']>[0]`), and -// anything too long to scan. +// Unreadable inline: mapped types, `infer`, an `extends` that could not be rewritten, indexed +// access into typeof, or anything over MAX_INLINE_TYPE_LENGTH. Callables and object literals are +// fine. function isGnarly(s) { if (/\b(?:infer|extends)\b/.test(s)) return true; if (MAPPED_TYPE.test(s)) return true; - // Any bracketed indexing like `X['foo']` or `X[0]` inside the type string — - // including inside `Parameters<...>` / `ReturnType<...>` — makes it too - // noisy to render verbatim. + // Any bracketed indexing (`X['foo']`, `X[0]`), including inside `Parameters<...>`, is too noisy + // to render verbatim. if (/\[['"0-9]/.test(s)) return true; return s.length > MAX_INLINE_TYPE_LENGTH; } diff --git a/packages/transformers/docs/scripts/lib/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs index ff4224cd7..e044619d2 100644 --- a/packages/transformers/docs/scripts/lib/render-skill.mjs +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -1,11 +1,7 @@ // Render the skill files in `.ai/skills/transformers-js/`. Two kinds of output: -// -// 1. Generated fragments injected into hand-written `SKILL.md` between -// `<!-- @generated:start id=X -->` and `<!-- @generated:end id=X -->` -// sentinels. Prose outside the markers is preserved across runs. -// -// 2. Fully generated files under `references/`. These carry a banner at the -// top and are rewritten in full on every run. +// 1. Fragments injected into hand-written `SKILL.md` between `<!-- @generated:start id=X -->` and +// `<!-- @generated:end id=X -->` sentinels; prose outside the markers survives every run. +// 2. Fully generated files under `references/`, banner-marked and rewritten in full each run. import fs from "node:fs"; import path from "node:path"; @@ -26,9 +22,8 @@ export function renderSkill({ ir, tasks, publicNames, skillDir }) { fs.mkdirSync(refDir, { recursive: true }); fs.writeFileSync(path.join(refDir, "TASKS.md"), absolutize(renderTasks(ctx))); - // Expand `<!-- @generated:start id=... -->` markers in every hand-written - // markdown file under the skill directory. Prose outside markers is preserved. - // Only the generated blocks get absolutized — hand-authored prose is left alone. + // Expand `<!-- @generated:start id=... -->` markers in every hand-written markdown file under + // the skill directory. Only generated blocks are absolutized; prose is left alone. for (const file of listFiles(skillDir, ".md")) { const original = fs.readFileSync(file, "utf8"); if (!original.includes("@generated:start")) continue; @@ -39,10 +34,8 @@ export function renderSkill({ ir, tasks, publicNames, skillDir }) { return { errors: ctx.errors }; } -// Skill files live outside the docs site, so relative `./foo.md#bar` links — -// inherited from JSDoc descriptions and from our typedef cross-reference -// renderer — have to be rewritten to the public docs URL. `.md` becomes the -// extensionless path the site uses. +// Skill files live outside the docs site, so relative `./foo.md#bar` links have to be rewritten to +// the public docs URL, where the path is extensionless. function absolutize(markdown) { return markdown.replace(/\]\(\.\/([^)\s]+?)\.md(#[^)\s]*)?\)/g, (_, page, anchor = "") => { return `](${DOCS_SITE}/${page}${anchor})`; @@ -143,9 +136,8 @@ function renderTaskList({ tasks }) { function renderTaskRecipe(taskId, info, ctx) { const cls = findClass(ctx.ir, info.pipelineClass); const aliases = aliasesFor(taskId, ctx.tasks); - // Separate paragraphs: consecutive lines would collapse into one, running - // the aliases onto the end of the default-model line. Every other bold label - // in a task section (`**Example:**`) stands as its own paragraph too. + // Separate paragraphs: consecutive lines would collapse into one, running the aliases onto the + // end of the default-model line. const lines = [`**Default model:** \`${info.defaultModel}\``, ""]; if (aliases.length) lines.push(`**Aliases:** ${aliases.map((a) => `\`${a}\``).join(", ")}`, ""); if (cls?.description) lines.push(cls.description.trim(), ""); @@ -193,8 +185,7 @@ function propertiesTable(props) { return lines.join("\n"); } -// Split a type string on top-level `|`, returning just the referenced names. -// Returns [] if the type doesn't look like a union of simple names. +// Names of a top-level `|` union; [] when the type isn't a union of simple names. function splitUnion(type) { if (!type) return []; const parts = splitTopLevel(type, "|").map((p) => p.trim()); @@ -233,16 +224,13 @@ function renderClassSummary(name, ctx) { return lines.join("\n").trimEnd(); } -// Compact display form of a type string: import prefixes collapsed to bare -// names, internal whitespace flattened. +// Import prefixes collapsed to bare names, internal whitespace flattened. function compactType(raw) { return stripImportPrefixes(raw).replace(/\s+/g, " ").trim(); } -// Flatten a typedef into its effective properties. If the typedef's type is -// an intersection like `A & B`, collect properties from each referenced -// typedef in declaration order (de-duplicated by name). `visited` guards -// against self-referential intersection typedefs. +// Flatten a typedef into its effective properties. An intersection (`A & B`) collects from each +// referenced typedef in declaration order, de-duplicated by name; `visited` guards self-reference. function collectProperties(name, ir, visited = new Set()) { if (visited.has(name)) return []; visited.add(name); @@ -264,8 +252,7 @@ function collectProperties(name, ir, visited = new Set()) { return [...seen.values()]; } -// Use the IR's cross-reference index (which already prefers canonical -// definitions over re-export aliases) to find the authoritative typedef. +// The IR's cross-reference index already prefers canonical definitions over re-export aliases. function findTypedef(ir, name) { const modName = ir.typedefIndex.get(name); if (!modName) return null; @@ -273,15 +260,13 @@ function findTypedef(ir, name) { return mod?.typedefs.find((t) => t.name === name) ?? null; } -// Escape table-breaking pipes exactly once — pipes that are already escaped -// are left alone, so this is safe to apply to text from any source. +// Escape table-breaking pipes exactly once; already-escaped pipes are left alone. function escapeCellPipes(text) { return text.replace(/(?<!\\)\|/g, "\\|"); } -// Compact display: inside a markdown table cell we want inline code spans. -// Collapse `import('./x.js').Foo` to `Foo`, long inline object types to -// `object`, and strip newlines. +// Table cells want inline code spans: `import('./x.js').Foo` becomes `Foo`, long inline object +// types become `object`, newlines go. function renderTypedefType(raw) { let compact = compactType(raw); if (compact.length > 60 && (compact.startsWith("{") || /[({=]/.test(compact))) { @@ -290,8 +275,8 @@ function renderTypedefType(raw) { return "`" + escapeCellPipes(compact.replace(/`/g, "\\`")) + "`"; } -// Cells can't contain newlines or un-escaped pipes. `{@link url}` gets turned -// into a plain URL so agents can still see it. +// Cells can't contain newlines or un-escaped pipes. `{@link url}` becomes a plain URL so agents +// can still see it. function prepareCell(text) { return escapeCellPipes(text || "") .replace(/\{@link\s+([^}\s]+)(?:\s+[^}]+)?\}/g, "$1") @@ -301,8 +286,7 @@ function prepareCell(text) { .trim(); } -// Per-task recipe page. Grouped by modality so readers can find the task they -// want quickly, with a table of contents up front. +// Per-task recipe page, grouped by modality with a table of contents up front. function renderTasks(ctx) { const lines = [ GENERATED_BANNER, @@ -333,9 +317,8 @@ function taskAnchor(id) { return id.toLowerCase().replace(/[^a-z0-9]+/g, "-"); } -// Keyword-based grouping keeps the TOC stable without hard-coding per-task -// metadata. A task falls into the first modality whose keyword matches its id. -// Order matters: `text-to-audio` must hit "Audio" before "Text". +// A task falls into the first modality whose keyword matches its id, so order matters: +// `text-to-audio` must hit "Audio" before "Text". const MODALITY_RULES = [ ["Audio", /audio|speech|asr|tts/], ["Vision", /image|vision|depth|object|segment|background|document/], diff --git a/packages/transformers/docs/scripts/lib/scan.mjs b/packages/transformers/docs/scripts/lib/scan.mjs index 866b1905c..05a5e5842 100644 --- a/packages/transformers/docs/scripts/lib/scan.mjs +++ b/packages/transformers/docs/scripts/lib/scan.mjs @@ -1,8 +1,6 @@ -// Bracket- and string-aware text scanners shared between IR-building and -// markdown rendering. Type strings can contain nested generics, tuples, -// function-type arrow lists, etc.; naive split/find on `,`/`|`/`&` would -// shred them. These helpers know to skip separators that appear inside -// `<>`, `()`, `{}`, `[]`, or quoted strings. +// Bracket- and string-aware scanners shared by IR-building and rendering. A naive split/find on +// `,`/`|`/`&` would shred nested generics, tuples and arrow lists, so these helpers skip separators +// inside `<>`, `()`, `{}`, `[]` and quoted strings. const OPEN = "<({["; const CLOSE = ">)}]"; @@ -12,10 +10,8 @@ function isArrowTail(text, i) { return text[i] === ">" && text[i - 1] === "="; } -// Return the index of the first occurrence of `needle` at top level (depth -// zero), or -1 if it doesn't appear outside brackets/strings. `needle` may be -// a single character or a longer substring (e.g. `" extends "`), and search -// starts at `from`. +// Index of the first top-level (depth-zero) `needle`, or -1. `needle` may be a longer substring +// (e.g. `" extends "`); the search starts at `from`. export function findTopLevel(text, needle, from = 0) { let depth = 0; let inStr = null; @@ -36,11 +32,9 @@ export function findTopLevel(text, needle, from = 0) { return -1; } -// Split a top-level conditional type `Check extends Extends ? A : B` into its -// two branches, or return null when `text` isn't one. The `?`/`:` pair is -// matched like a ternary so a conditional nested in the true branch -// (`X extends Y ? (A extends B ? C : D) : E`, or the same without parens) -// doesn't steal the outer `:`. +// Split `Check extends Extends ? A : B` into its branches, or null when `text` isn't one. The +// `?`/`:` pair is matched like a ternary so a conditional nested in the true branch +// (`X extends Y ? A extends B ? C : D : E`) can't steal the outer `:`. export function splitConditional(text) { const EXTENDS = " extends "; const ext = findTopLevel(text, EXTENDS); @@ -94,9 +88,8 @@ export function matchingBracket(text, start, open, close) { return -1; } -// Split `text` on `sep`, ignoring separators inside brackets/strings. -// Returns `[text]` (the unsplit input) when no split occurred — callers -// can use that to detect "this was a single chunk all along". +// Split `text` on `sep`, ignoring separators inside brackets/strings. Returns `[text]` when no +// split occurred, so callers can detect a single chunk. export function splitTopLevel(text, sep) { const out = []; let depth = 0; diff --git a/packages/transformers/docs/scripts/lib/structure.mjs b/packages/transformers/docs/scripts/lib/structure.mjs index 615043cea..14157bac4 100644 --- a/packages/transformers/docs/scripts/lib/structure.mjs +++ b/packages/transformers/docs/scripts/lib/structure.mjs @@ -1,6 +1,5 @@ -// Walk a JS file with the TypeScript compiler and produce the structured -// entities (module header, classes with members, free functions, top-level -// constants, typedefs) that the IR layer groups into per-module docs. +// Walk a JS file with the TypeScript compiler and produce the entities (module header, classes, +// functions, constants, typedefs) that the IR layer groups into per-module docs. import ts from "typescript"; import { parseJsDoc } from "./parse.mjs"; @@ -11,9 +10,8 @@ export function extractEntities(source, file) { const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); const entities = { module: null, classes: [], functions: [], variables: [], typedefs: [] }; - // File-level blocks (`@module` / `@file` / standalone `@typedef` / `@callback`) - // can't be discovered via node-attached JSDoc — TS attaches them to the next - // statement. Scan the raw source and classify by tag. + // TS attaches file-level blocks (`@module`, `@file`, standalone `@typedef`/`@callback`) to the + // next statement, so they can't be found via node-attached JSDoc. Scan the source and classify. for (const m of source.matchAll(/\/\*\*[\s\S]*?\*\//g)) { if (isInsideLineComment(source, m.index)) continue; const parsed = parseJsDoc(m[0]); @@ -81,8 +79,7 @@ function buildMemberEntity(node, sf) { return null; } -// `ts.getJSDocCommentsAndTags` may return multiple blocks; the nearest/latest -// one is the canonical JSDoc for the node. +// `ts.getJSDocCommentsAndTags` may return several blocks; the last one is the node's canonical JSDoc. function parseNodeDoc(node) { const docs = ts.getJSDocCommentsAndTags(node).filter((d) => ts.isJSDoc(d)); if (!docs.length) return null; diff --git a/packages/transformers/docs/scripts/lib/tasks.mjs b/packages/transformers/docs/scripts/lib/tasks.mjs index 1e8a44dd3..32db4491c 100644 --- a/packages/transformers/docs/scripts/lib/tasks.mjs +++ b/packages/transformers/docs/scripts/lib/tasks.mjs @@ -1,6 +1,5 @@ -// Extract the task catalog (`SUPPORTED_TASKS` + `TASK_ALIASES`) from the -// library source. The skill renderer uses this to emit one task recipe per -// supported pipeline task, with its canonical pipeline class and default model. +// Extract `SUPPORTED_TASKS` + `TASK_ALIASES` from the library source. The skill renderer turns each +// entry into a task recipe with its pipeline class and default model. import ts from "typescript"; diff --git a/packages/transformers/docs/scripts/lib/text.mjs b/packages/transformers/docs/scripts/lib/text.mjs index 0adecc586..be4f8cc87 100644 --- a/packages/transformers/docs/scripts/lib/text.mjs +++ b/packages/transformers/docs/scripts/lib/text.mjs @@ -1,9 +1,7 @@ // Plain-text helpers shared between the API and skill renderers. -// Extract the first complete sentence of a description. A line break inside a -// paragraph is not a sentence boundary, so re-flow the paragraph onto one line -// before cutting. Returns an empty string when given empty input — callers are -// expected to skip rendering rather than emit a placeholder. +// First complete sentence of a description. A line break inside a paragraph is not a sentence +// boundary, so the paragraph is re-flowed onto one line before cutting. Empty input returns "". export function firstSentence(text) { if (!text) return ""; const paragraph = stripDocArtifacts( @@ -17,11 +15,9 @@ export function firstSentence(text) { return /[.!?]$/.test(sentence) ? sentence : sentence + "."; } -// Descriptions copied from the Python library use reST cross-references — -// ``[`TypeName`]``, sometimes with a `~` prefix or a trailing `()`. Reduce them -// to plain inline code so the sentence still reads. Renderers that can resolve -// symbols to anchors (the API pages) link them instead; this is the plain-text -// fallback. A markdown link `[label](url)` keeps its load-bearing bracket. +// reST cross-references inherited from the Python library: ``[`TypeName`]``, sometimes `~`-prefixed +// or `()`-suffixed. Renderers that can resolve anchors link them; the rest reduce them to inline +// code. A markdown link `[label](url)` keeps its load-bearing bracket. export const DOC_REFERENCE = /\[`~?([A-Za-z_$][\w$.]*(?:\(\))?)`\](?!\()/g; export function stripDocArtifacts(text) { @@ -30,10 +26,8 @@ export function stripDocArtifacts(text) { const FENCE = /^\s*```/; -// Map every line of `text` through `fn(line, fenced)`, where `fenced` is true -// for lines inside a fenced code block, including the fence delimiters -// themselves. Line structure is preserved; an unterminated fence extends to -// the end of the text. +// `fenced` is true for lines inside a fenced code block, including the delimiters. Line structure is +// preserved; an unterminated fence extends to the end of the text. export function mapLines(text, fn) { let inFence = false; return text @@ -46,10 +40,8 @@ export function mapLines(text, fn) { .join("\n"); } -// Run `transform` over the parts of `text` outside fenced code blocks. Each -// fence is swapped for a single-line placeholder while the transform runs, so -// page-level rewrites (link expansion, blank-run collapsing) can't mangle -// example code. An unterminated fence extends to the end of the text. +// Each fence is swapped for a single-line placeholder while `transform` runs, so page-level +// rewrites can't mangle example code. An unterminated fence extends to the end of the text. export function transformOutsideFences(text, transform) { const blocks = []; const out = []; @@ -75,9 +67,8 @@ export function transformOutsideFences(text, transform) { return transform(out.join("\n")).replace(/\u0000(\d+)\u0000/g, (_, i) => blocks[Number(i)]); } -// Comma-separated parameter names for a signature. Only top-level params are -// shown — `options.foo` rows are nested options, covered by the linked typedef. -// Optional params render as `[name]`. +// Comma-separated parameter names for a signature. Only top-level params: `options.foo` rows are +// nested options covered by the linked typedef. Optional params render as `[name]`. export function paramSignature(params) { return (params ?? []) .filter((p) => p.name && !p.name.includes(".")) @@ -85,8 +76,8 @@ export function paramSignature(params) { .join(", "); } -// One `**Example:** <title>` block with its fenced code, as markdown lines. -// `blankAfterTitle` keeps the historical spacing of module-example markers. +// One `**Example:** <title>` block with its fenced code, as markdown lines. `blankAfterTitle` adds +// the blank line module-example markers use. export function exampleLines(ex, { blankAfterTitle = false } = {}) { const lines = []; if (ex.title) { @@ -97,14 +88,12 @@ export function exampleLines(ex, { blankAfterTitle = false } = {}) { return lines; } -// `import('./module.js').Name` prefixes appear throughout JSDoc type strings. -// Strip them down to the bare (possibly dotted) name for display. +// `import('./module.js').Name` -> `Name`, for display. export function stripImportPrefixes(type) { return type.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); } -// Parse a type that is exactly one `import('./module.js').Name` expression -// into its module specifier and imported name; null for anything else. +// Split a type that is exactly one `import('./module.js').Name` into specifier and name; else null. export function parseImportedType(type) { const m = type?.trim().match(/^import\(['"]([^'"]+)['"]\)\.([A-Za-z_$][\w$]*)$/); return m ? { specifier: m[1], name: m[2] } : null; diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs index 606f3306f..20e87d082 100644 --- a/packages/transformers/docs/scripts/lib/validate.mjs +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -14,10 +14,8 @@ export function validateGeneratedDocs({ project = null } = {}) { const unlisted = difference(generatedApiPages, linkedApiPages); const stale = difference(linkedApiPages, generatedApiPages); - // Source-quality warnings are advisory: they surface gaps in the JSDoc - // (undocumented exports, malformed `@param` lines) that the renderer can't - // fix on its own. They don't fail validation — broken links and a stale - // toctree do. + // Source-quality warnings are advisory: they surface JSDoc gaps the renderer can't fix. Only + // broken links and a stale toctree fail validation. const docWarnings = [...(project ? collectDocWarnings(project) : []), ...collectAnchorWarnings(sourceDir)]; return { @@ -61,9 +59,8 @@ export function formatValidationResult(result) { return lines.join("\n"); } -// Walk the IR and report public exports whose JSDoc is empty (no description, -// no params, no example) and parameters that are missing a name. These don't -// fail the build but are worth surfacing during a normal docs-generate run. +// Public exports with empty JSDoc (no description, params or example) and parameters missing a +// name. Advisory only. function collectDocWarnings({ ir, publicNames }) { const warnings = []; const isPublic = (name) => !publicNames || publicNames.has(name); @@ -163,9 +160,8 @@ function validateInternalLinks(sourceDir) { // Labels may contain one nested level of square brackets (`[`Foo[]?`](...)`). const MARKDOWN_LINK_RE = /!?\[(?:[^[\]]|\[[^\]]*\])*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; -// Blank out fenced code blocks so the link and heading scanners can't match -// code — `x[i](y)` in a snippet is not a markdown link, and a `# comment` is -// not a heading. Line structure is preserved. +// Blank out fenced code so the link and heading scanners can't match it: `x[i](y)` in a snippet is +// not a markdown link, and a `# comment` is not a heading. Line structure is preserved. function maskFences(text) { return mapLines(text, (line, fenced) => (fenced ? "" : line)); } @@ -208,8 +204,7 @@ function readMarkdownWithIncludes(file, seen = new Set()) { }); } -// Headings made entirely of HTML/entities/punctuation slugify to "" and can -// never be linked to. None exist today; this guards against regressions. +// Headings made entirely of HTML/entities/punctuation slugify to "" and can never be linked to. function collectAnchorWarnings(sourceDir) { const warnings = []; for (const file of listFiles(sourceDir, ".md")) { diff --git a/packages/transformers/docs/scripts/validate.js b/packages/transformers/docs/scripts/validate.js index 4b1e13dca..2f9fb2b34 100644 --- a/packages/transformers/docs/scripts/validate.js +++ b/packages/transformers/docs/scripts/validate.js @@ -1,9 +1,7 @@ #!/usr/bin/env node -// Validate the generated docs without regenerating them. Useful in CI as a -// fast check that the locally generated `docs/source/api/` markdown (which is -// gitignored) is consistent with `docs/source/_toctree.yml` and that internal -// links resolve. +// Validate the generated docs without regenerating them: a CI check that the gitignored +// `docs/source/api/` markdown matches `docs/source/_toctree.yml` and that internal links resolve. import { listFiles } from "./lib/fs.mjs"; import { apiOutputDir } from "./lib/paths.mjs"; diff --git a/packages/transformers/src/generation/stopping_criteria.js b/packages/transformers/src/generation/stopping_criteria.js index 86dabf727..55b2b7728 100644 --- a/packages/transformers/src/generation/stopping_criteria.js +++ b/packages/transformers/src/generation/stopping_criteria.js @@ -1,10 +1,9 @@ /** * @file Stopping criteria for controlling when generation halts. * - * Each criterion returns an array of booleans — one per sequence in the batch — - * signalling which sequences should stop emitting tokens. Combine multiple - * criteria with `StoppingCriteriaList`, and pass that to `generate()` via - * the `stopping_criteria` argument. + * Each criterion returns one boolean per sequence in the batch, indicating which sequences + * should stop. Combine criteria with `StoppingCriteriaList` and pass it to `generate()` + * as the `stopping_criteria` argument. * * @module generation/stopping_criteria */ @@ -33,7 +32,6 @@ export class StoppingCriteria extends Callable { } /** * A list of `StoppingCriteria` that stops generation when any one of them returns `true`. - * Pass an instance via `stopping_criteria` to any generation call to combine multiple stop conditions. */ export class StoppingCriteriaList extends Callable { /** @@ -152,8 +150,7 @@ export class InterruptableStoppingCriteria extends StoppingCriteria { } /** - * Interrupts the generation process, causing every sequence in the batch to stop - * on the next call to this criterion. + * Interrupts generation, stopping every sequence on the next call. */ interrupt() { this.interrupted = true; diff --git a/packages/transformers/src/pipelines.js b/packages/transformers/src/pipelines.js index 263f4e4fe..d537f12b6 100644 --- a/packages/transformers/src/pipelines.js +++ b/packages/transformers/src/pipelines.js @@ -154,7 +154,7 @@ export async function pipeline( } }); } catch (e) { - // If we fail to get metadata, we can still proceed without total progress. + // Progress totals are best-effort. logger.warn(`Unable to fetch model file metadata for total progress tracking: ${e}`); } } diff --git a/packages/transformers/src/utils/hub/utils.js b/packages/transformers/src/utils/hub/utils.js index 13caf08fa..8bff76864 100644 --- a/packages/transformers/src/utils/hub/utils.js +++ b/packages/transformers/src/utils/hub/utils.js @@ -80,15 +80,11 @@ export function makePretrainedOptionsKey(model_id, options = {}, ...parts) { } /** - * Error thrown when a model file cannot be found — because the repository does not - * exist, is gated/private without a valid token, the file is missing from the - * repository, or the file is unavailable locally with downloads disabled. + * Error thrown when a model file is missing or inaccessible: the repository does not exist, + * it is gated or private, the file is absent, or downloads are disabled and it is not cached. * - * Note that the Hugging Face Hub responds with 401 for non-existent repositories, - * so "model does not exist" and "model requires authentication" are indistinguishable. - * - * Transient failures (network errors, server errors) are NOT represented by this - * class; they surface as regular `Error`s (or `TypeError`s from `fetch`). + * The Hub returns 401 for nonexistent repositories, so a missing repository and one requiring + * authentication are indistinguishable. Network and server failures throw a regular `Error`. */ export class ModelFileNotFoundError extends Error { /** @@ -104,8 +100,7 @@ export class ModelFileNotFoundError extends Error { } /** - * HTTP statuses indicating the requested file does not exist or is not accessible, - * as opposed to transient request/server failures. + * HTTP statuses indicating the file is missing or inaccessible, rather than a transient failure. */ const NOT_FOUND_STATUSES = new Set([401, 403, 404]); @@ -115,8 +110,7 @@ const NOT_FOUND_STATUSES = new Set([401, 403, 404]); * @param {string} remoteURL The URL of the file that could not be loaded. * @param {boolean} fatal Whether to raise an error if the file could not be loaded. * @returns {null} Returns `null` if `fatal = false`. - * @throws {ModelFileNotFoundError|Error} If `fatal = true`: a `ModelFileNotFoundError` when the - * file is missing or inaccessible (401/403/404), otherwise a regular `Error`. + * @throws {ModelFileNotFoundError|Error} If `fatal = true`. A `ModelFileNotFoundError` for 401/403/404, otherwise a regular `Error`. */ export function handleError(status, remoteURL, fatal) { if (!fatal) { diff --git a/packages/transformers/src/utils/model_registry/ModelRegistry.js b/packages/transformers/src/utils/model_registry/ModelRegistry.js index 9b28402e9..f72f30212 100644 --- a/packages/transformers/src/utils/model_registry/ModelRegistry.js +++ b/packages/transformers/src/utils/model_registry/ModelRegistry.js @@ -222,8 +222,7 @@ export class ModelRegistry { * A dtype is considered available if all required model session files * exist for that dtype. * - * An empty array means the model exists and is accessible, but ships no - * complete set of ONNX files for any dtype. + * An empty array means the model is accessible but has no complete set of ONNX files for any dtype. * * @param {string} modelId - The model id (e.g., "onnx-community/all-MiniLM-L6-v2-ONNX") * @param {Object} [options] - Optional parameters @@ -232,11 +231,9 @@ export class ModelRegistry { * @param {string} [options.revision='main'] - Model revision * @param {string} [options.cache_dir=null] - Custom cache directory * @param {boolean} [options.local_files_only=false] - Only check local files - * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']). - * Empty if the model has no ONNX files. - * @throws {import('../hub/utils.js').ModelFileNotFoundError} If the model does not exist or is - * not accessible (e.g., gated or private without a token), so a missing or inaccessible model - * is never misreported as having no ONNX files. Network-level failures also throw. + * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']). Empty if the model has no ONNX files. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If the model is missing or inaccessible. The Hub returns 401 for nonexistent repositories. + * @throws {Error} On network or server failures. * * **Example:** * ```javascript @@ -352,13 +349,9 @@ export class ModelRegistry { * @param {string} path_or_repo_id - Model id or path * @param {string} filename - The file name * @param {import('../hub.js').PretrainedOptions} [options] - Optional parameters - * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} File metadata. - * `exists: false` means the file was determinately not found — never that the check itself failed. - * @throws {import('../hub/utils.js').ModelFileNotFoundError} If access to the file was denied (401/403), - * e.g. a private or gated repository without a valid token, or a repository which does not exist - * (the Hub returns 401 for nonexistent repos). - * @throws {Error} On network-level failures (e.g., offline) or server errors, so an - * unreachable file is never misreported as missing. + * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} File metadata. `exists: false` means the file was not found. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If the file is missing or inaccessible. The Hub returns 401 for nonexistent repositories. + * @throws {Error} On network or server failures. * * **Example:** * ```javascript diff --git a/packages/transformers/src/utils/model_registry/get_available_dtypes.js b/packages/transformers/src/utils/model_registry/get_available_dtypes.js index e838c7a49..d0eb18569 100644 --- a/packages/transformers/src/utils/model_registry/get_available_dtypes.js +++ b/packages/transformers/src/utils/model_registry/get_available_dtypes.js @@ -22,8 +22,7 @@ const CONCRETE_DTYPES = Object.keys(DEFAULT_DTYPE_SUFFIX_MAPPING); * exist for that dtype. For example, a Seq2Seq model needs both an encoder * and decoder file — the dtype is only listed if both are present. * - * An empty array means the model exists and is accessible, but ships no - * complete set of ONNX files for any dtype. + * An empty array means the model is accessible but has no complete set of ONNX files for any dtype. * * @param {string} modelId The model id (e.g., "onnx-community/all-MiniLM-L6-v2-ONNX") * @param {Object} [options] Optional parameters @@ -32,13 +31,9 @@ const CONCRETE_DTYPES = Object.keys(DEFAULT_DTYPE_SUFFIX_MAPPING); * @param {string} [options.revision='main'] Model revision * @param {string} [options.cache_dir=null] Custom cache directory * @param {boolean} [options.local_files_only=false] Only check local files - * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']). - * Empty if the model has no ONNX files. - * @throws {import('../hub/utils.js').ModelFileNotFoundError} If the model does not exist or is not - * accessible (e.g., gated or private without a token). Note that the Hugging Face Hub responds with - * 401 for non-existent repositories, so these cases are indistinguishable. - * @throws {Error} On network-level or server failures (e.g., offline), so an unreachable - * model is never misreported as having no ONNX files. + * @returns {Promise<string[]>} Array of available dtype strings (e.g., ['fp32', 'fp16', 'q4', 'q8']). Empty if the model has no ONNX files. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If the model is missing or inaccessible. The Hub returns 401 for nonexistent repositories. + * @throws {Error} On network or server failures. */ export async function get_available_dtypes( modelId, diff --git a/packages/transformers/src/utils/model_registry/get_file_metadata.js b/packages/transformers/src/utils/model_registry/get_file_metadata.js index e3c2608b2..54a59d002 100644 --- a/packages/transformers/src/utils/model_registry/get_file_metadata.js +++ b/packages/transformers/src/utils/model_registry/get_file_metadata.js @@ -47,12 +47,9 @@ async function fetch_file_head(urlOrPath) { * - a path to a *directory* potentially containing the file. * @param {string} filename The name of the file to check. * @param {PretrainedOptions} [options] An object containing optional parameters. - * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} A Promise that resolves to file metadata. - * `exists: false` means the file was determinately not found — never that the check itself failed. - * @throws {import('../hub/utils.js').ModelFileNotFoundError} If access to the file was denied (401/403), - * e.g. a gated or private repository without a valid token. - * @throws {Error} On network-level failures (e.g., offline) or server errors, so an - * unreachable file is never misreported as missing. + * @returns {Promise<{exists: boolean, size?: number, contentType?: string, fromCache?: boolean}>} A Promise that resolves to file metadata. `exists: false` means the file was not found. + * @throws {import('../hub/utils.js').ModelFileNotFoundError} If access to the file was denied (401/403), e.g. a gated or private repository without a valid token. + * @throws {Error} On network or server failures. */ export function get_file_metadata(path_or_repo_id, filename, options = {}) { const key = makePretrainedOptionsKey(path_or_repo_id, options, filename); @@ -107,17 +104,15 @@ async function _get_file_metadata(path_or_repo_id, filename, options) { // Check remote if allowed - use Range request for efficiency if (env.allowRemoteModels && !options.local_files_only && validModelId) { - // Make a Range request to get metadata without downloading full content. - // Transport-level failures (offline, DNS, timeout) propagate to the caller: - // they mean "could not check", not "file is missing". + // A Range request reads the metadata without downloading the file. + // Transport failures propagate: they mean "could not check", not "missing". const rangeResponse = await fetch_file_head(remoteURL); if (rangeResponse && rangeResponse.status >= 200 && rangeResponse.status < 300) { let size; const contentType = rangeResponse.headers.get('content-type'); - // If server supports Range requests, we get a 206 Partial Content response - // with a content-range header showing the total uncompressed file size + // A 206 response carries a content-range header with the total uncompressed size. if (rangeResponse.status === 206) { const contentRange = rangeResponse.headers.get('content-range'); if (contentRange) { @@ -128,17 +123,15 @@ async function _get_file_metadata(path_or_repo_id, filename, options) { } } } else if (rangeResponse.status === 200) { - // Server doesn't support Range requests and returned the full file (200 OK) - // Cancel the response body immediately to avoid downloading the entire file + // Server ignored the Range request and is sending the full file, so cancel the body. try { await rangeResponse.body?.cancel(); } catch (cancelError) { - // Ignore cancellation errors - body might already be consumed or not cancellable + // Body may already be consumed, or not cancellable. } } - // Fallback to content-length if content-range is not available (200 OK response) - // This can happen if server doesn't support Range requests + // Fall back to content-length when there is no content-range. if (size === undefined) { const contentLength = rangeResponse.headers.get('content-length'); size = contentLength ? parseInt(contentLength, 10) : undefined; @@ -152,10 +145,8 @@ async function _get_file_metadata(path_or_repo_id, filename, options) { }; } - // 404 means the file determinately does not exist. 416 (Range Not Satisfiable) - // is what a zero-length file yields for a `bytes=0-0` request; we preserve the - // long-standing behavior of treating it as absent. Any other failure status - // (401/403 access denied, 429, 5xx, ...) is an error, not "missing". + // 404 means the file does not exist, and 416 is what a zero-length file returns for `bytes=0-0`. + // Any other failure status (401/403, 429, 5xx, ...) is an error, not "missing". if (rangeResponse && rangeResponse.status !== 404 && rangeResponse.status !== 416) { handleError(rangeResponse.status, remoteURL, true); } diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 5c5f1bc71..e680a7aac 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -1,10 +1,8 @@ /** * @file Tensors and tensor operations. * - * `Tensor` is the typed n-dimensional array used throughout the library for - * model inputs and outputs. The functions in this module create, transform, - * and combine tensors — shape manipulation, slicing, reductions, math ops, - * and the `.tolist()` / `.item()` escape hatches back to plain JavaScript. + * `Tensor` is the typed n-dimensional array used throughout the library for model inputs + * and outputs. This module also provides functions to create, transform, and combine tensors. * * @module utils/tensor */ @@ -85,12 +83,9 @@ export class Tensor { ort_tensor; /** - * Create a new Tensor. - * - * Two call shapes are supported: - * - `new Tensor(dataType, data, dims)` — build from raw data, e.g. - * `new Tensor('float32', new Float32Array([1, 2, 3]), [3])`. - * - `new Tensor(ortTensor)` — wrap an existing `onnxruntime` tensor. + * Create a new Tensor, either from raw data or by wrapping an `onnxruntime` tensor: + * - `new Tensor(dataType, data, dims)`, e.g. `new Tensor('float32', new Float32Array([1, 2, 3]), [3])`. + * - `new Tensor(ortTensor)`. * * @param {[DataType, DataArray, number[]]|[ONNXTensor]} args */ @@ -129,8 +124,7 @@ export class Tensor { } /** - * Releases the resources held by the underlying ONNX Runtime tensor (e.g., GPU buffers). - * The tensor should not be used after this method is called. + * Releases the underlying ONNX Runtime tensor (e.g., GPU buffers). Do not use the tensor afterwards. */ dispose() { this.ort_tensor.dispose(); diff --git a/packages/transformers/tests/utils/model_registry.test.js b/packages/transformers/tests/utils/model_registry.test.js index 7101dc114..db96bb674 100644 --- a/packages/transformers/tests/utils/model_registry.test.js +++ b/packages/transformers/tests/utils/model_registry.test.js @@ -226,8 +226,7 @@ describe("get_available_dtypes", () => { }); it("should rethrow network-level failures from dtype file probes", async () => { - // Config loads fine, but probing the ONNX files fails at the network level: - // the caller must see the error rather than an empty dtype list. + // A failed probe must surface, rather than produce an empty dtype list. mockGetFileMetadata.mockRejectedValue(new TypeError("fetch failed")); await expect(get_available_dtypes("test/model", { config: ENCODER_ONLY_CONFIG })).rejects.toThrow("fetch failed"); From 2e465fd0a61c52a0e2f4461dfcb6a1538c36f73f Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:25:36 -0400 Subject: [PATCH 079/103] Update PIPELINE_OPTIONS.md --- .../references/PIPELINE_OPTIONS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md index 2ae0e25d8..11e7a937d 100644 --- a/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md +++ b/.ai/skills/transformers-js/references/PIPELINE_OPTIONS.md @@ -24,6 +24,21 @@ class in the [API reference](https://huggingface.co/docs/transformers.js/api/pip ## Progress tracking +The `progress_total` event reports aggregate progress across every file the model needs, +which is what a loading bar usually wants: + +```javascript +const pipe = await pipeline("sentiment-analysis", null, { + progress_callback: (info) => { + if (info.status === "progress_total") { + console.log(`Loading model: ${info.progress.toFixed(1)}%`); + } + }, +}); +``` + +For per-file detail, listen for `progress` events instead: + ```javascript const pipe = await pipeline("sentiment-analysis", null, { progress_callback: (info) => { From d360506f2d45617b282037c11fdd594f73f2d22d Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:52:42 -0400 Subject: [PATCH 080/103] bump HF packages --- packages/transformers/package.json | 4 ++-- pnpm-lock.yaml | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 7b2c4cbcc..4751f742c 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -55,8 +55,8 @@ }, "homepage": "https://github.com/huggingface/transformers.js#readme", "dependencies": { - "@huggingface/jinja": "^0.5.8", - "@huggingface/tokenizers": "^0.1.3", + "@huggingface/jinja": "^0.5.10", + "@huggingface/tokenizers": "^0.2.0", "onnxruntime-node": "1.24.3", "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c", "sharp": "^0.34.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6493e82fc..6bfb44725 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,11 +18,11 @@ importers: packages/transformers: dependencies: '@huggingface/jinja': - specifier: ^0.5.8 - version: 0.5.8 + specifier: ^0.5.10 + version: 0.5.10 '@huggingface/tokenizers': - specifier: ^0.1.3 - version: 0.1.3 + specifier: ^0.2.0 + version: 0.2.0 onnxruntime-node: specifier: 1.24.3 version: 1.24.3 @@ -408,12 +408,12 @@ packages: cpu: [x64] os: [win32] - '@huggingface/jinja@0.5.8': - resolution: {integrity: sha512-ZdElB7DPS7QQS8ZnFc5RPPtkg+eN11z8AmIZWAyes6pSbwXqiFB/POVevvm01begdSX1ho9Gxln/F6qlQMsuaA==} + '@huggingface/jinja@0.5.10': + resolution: {integrity: sha512-SgS1D1bglQ94ceD4ZCL6eayUDy9uV1xuyk61OjgSxU5GJh7upqZCKII0JoTYMc6wWsg3JUgaPRX0ElQzXPk3Cw==} engines: {node: '>=18'} - '@huggingface/tokenizers@0.1.3': - resolution: {integrity: sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==} + '@huggingface/tokenizers@0.2.0': + resolution: {integrity: sha512-LidMHe1FpcSYH4vcSjXooda34pC0M8m1gmDOv7SQi6Iv+ib1zX7J3sHBCC9/3FPCkn2k1b55FneVqMRxrr99pg==} '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} @@ -2075,9 +2075,9 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@huggingface/jinja@0.5.8': {} + '@huggingface/jinja@0.5.10': {} - '@huggingface/tokenizers@0.1.3': {} + '@huggingface/tokenizers@0.2.0': {} '@img/colour@1.0.0': {} From a559a45852755a70a107f1a42affd98dd8d0a94d Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:01:05 -0400 Subject: [PATCH 081/103] bump sharp --- packages/transformers/package.json | 2 +- pnpm-lock.yaml | 305 ++++++++++++++++------------- 2 files changed, 174 insertions(+), 133 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 4751f742c..5c4eb27ab 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -59,7 +59,7 @@ "@huggingface/tokenizers": "^0.2.0", "onnxruntime-node": "1.24.3", "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c", - "sharp": "^0.34.5" + "sharp": "^0.35.4" }, "devDependencies": { "@types/jest": "^30.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6bfb44725..75d496aa7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: 1.26.0-dev.20260416-b7804b056c version: 1.26.0-dev.20260416-b7804b056c sharp: - specifier: ^0.34.5 - version: 0.34.5 + specifier: ^0.35.4 + version: 0.35.4(@types/node@24.10.9) devDependencies: '@types/jest': specifier: ^30.0.0 @@ -246,6 +246,9 @@ packages: '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} @@ -415,140 +418,149 @@ packages: '@huggingface/tokenizers@0.2.0': resolution: {integrity: sha512-LidMHe1FpcSYH4vcSjXooda34pC0M8m1gmDOv7SQi6Iv+ib1zX7J3sHBCC9/3FPCkn2k1b55FneVqMRxrr99pg==} - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-arm64@0.35.4': + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-x64@0.35.4': + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-freebsd-wasm32@0.35.4': + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.3': + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.3.3': + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.3.3': + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.3.3': + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + '@img/sharp-libvips-linux-ppc64@1.3.3': + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + '@img/sharp-libvips-linux-riscv64@1.3.3': + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} cpu: [riscv64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.3.3': + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.3.3': + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm64@0.35.4': + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm@0.35.4': + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} + engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-ppc64@0.35.4': + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} + engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-riscv64@0.35.4': + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} + engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-s390x@0.35.4': + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} + engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-x64@0.35.4': + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-arm64@0.35.4': + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-x64@0.35.4': + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-wasm32@0.35.4': + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.4': + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} + engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-arm64@0.35.4': + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-ia32@0.35.4': + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} + engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-x64@0.35.4': + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -1613,13 +1625,23 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.4: + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -1987,6 +2009,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 @@ -2079,100 +2106,110 @@ snapshots: '@huggingface/tokenizers@0.2.0': {} - '@img/colour@1.0.0': {} + '@img/colour@1.1.0': {} - '@img/sharp-darwin-arm64@0.34.5': + '@img/sharp-darwin-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.3.3 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-freebsd-wasm32@0.35.4': + dependencies: + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-arm64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm64@1.3.3': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-arm@1.3.3': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-ppc64@1.3.3': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linux-riscv64@1.3.3': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linux-s390x@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@img/sharp-libvips-linux-x64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': optional: true - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + optional: true + + '@img/sharp-linux-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.3.3 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.3.3 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-ppc64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.3.3 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-riscv64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.3.3 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linux-s390x@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.3.3 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linux-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.3.3 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + '@img/sharp-linuxmusl-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 optional: true - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.35.4': dependencies: - '@emnapi/runtime': 1.8.1 + '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-webcontainers-wasm32@0.35.4': + dependencies: + '@img/sharp-wasm32': 0.35.4 + optional: true + + '@img/sharp-win32-arm64@0.35.4': optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-ia32@0.35.4': optional: true - '@img/sharp-win32-x64@0.34.5': + '@img/sharp-win32-x64@0.35.4': optional: true '@isaacs/cliui@8.0.2': @@ -3478,40 +3515,44 @@ snapshots: semver@7.7.3: {} + semver@7.8.5: {} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 - sharp@0.34.5: + sharp@0.35.4(@types/node@24.10.9): dependencies: - '@img/colour': 1.0.0 + '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.7.3 + semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 + '@types/node': 24.10.9 shebang-command@2.0.0: dependencies: From 8363959deefa724c5faae60fa9b35d1763ec4280 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:26:11 -0400 Subject: [PATCH 082/103] bump onnxruntime-web and onnxruntime-node --- packages/transformers/package.json | 4 +- pnpm-lock.yaml | 128 +++++++++-------------------- 2 files changed, 43 insertions(+), 89 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 5c4eb27ab..0d28a1b37 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -57,8 +57,8 @@ "dependencies": { "@huggingface/jinja": "^0.5.10", "@huggingface/tokenizers": "^0.2.0", - "onnxruntime-node": "1.24.3", - "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c", + "onnxruntime-node": "1.29.0", + "onnxruntime-web": "1.30.0-dev.20260904-d47fd8824", "sharp": "^0.35.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75d496aa7..018d90ccb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: specifier: ^0.2.0 version: 0.2.0 onnxruntime-node: - specifier: 1.24.3 - version: 1.24.3 + specifier: 1.29.0 + version: 1.29.0 onnxruntime-web: - specifier: 1.26.0-dev.20260416-b7804b056c - version: 1.26.0-dev.20260416-b7804b056c + specifier: 1.30.0-dev.20260904-d47fd8824 + version: 1.30.0-dev.20260904-d47fd8824 sharp: specifier: ^0.35.4 version: 0.35.4(@types/node@24.10.9) @@ -864,9 +864,9 @@ packages: '@webgpu/types@0.1.69': resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==} - adm-zip@0.5.16: - resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} - engines: {node: '>=12.0'} + adm-zip@0.6.0: + resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + engines: {node: '>=14.0'} ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} @@ -931,10 +931,6 @@ packages: resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==} hasBin: true - boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1051,9 +1047,6 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1081,9 +1074,6 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -1171,8 +1161,8 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - global-agent@3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + global-agent@4.1.3: + resolution: {integrity: sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==} engines: {node: '>=10.0'} globalthis@1.0.4: @@ -1407,9 +1397,6 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1442,8 +1429,8 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - matcher@3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + matcher@4.0.0: + resolution: {integrity: sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==} engines: {node: '>=10'} merge-stream@2.0.0: @@ -1504,18 +1491,18 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - onnxruntime-common@1.24.0-dev.20251116-b39e144322: - resolution: {integrity: sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==} + onnxruntime-common@1.29.0: + resolution: {integrity: sha512-/F63/e2VJoaVXGGNu6S5QH7jivBThGO95OzAVXXQ8hTta/b1QxI8udHa6cI3+3mAb5WWIIaMMwfZw01oivjJ1g==} - onnxruntime-common@1.24.3: - resolution: {integrity: sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==} + onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: + resolution: {integrity: sha512-rgjnYrMP7e7CNGZOIygFaeF5qZCoRtBBeMFGjRNCXNyUpxXMGZIn9EE/LnCjCdCix6MYbyNR3JvMTabCutI9hg==} - onnxruntime-node@1.24.3: - resolution: {integrity: sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==} + onnxruntime-node@1.29.0: + resolution: {integrity: sha512-WjiVVB72riILz8HbYvxvmjKyE/WmkYoSfKY++axo5jAR609HQg8MwiG/HhShpTcJfmmAdzxxmB+MMST3A+SiPA==} os: [win32, darwin, linux] - onnxruntime-web@1.26.0-dev.20260416-b7804b056c: - resolution: {integrity: sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==} + onnxruntime-web@1.30.0-dev.20260904-d47fd8824: + resolution: {integrity: sha512-vqD58oCa9rtgB+AaSpJPECiQTxX0KDqAqP+4cPvoxOskz7yvvB3oINO/BG23CraRgkhO71Khgjwg0K8E2d5v4A==} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -1609,13 +1596,6 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - roarr@2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} - - semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1630,8 +1610,8 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + serialize-error@8.1.0: + resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} engines: {node: '>=10'} sharp@0.35.4: @@ -1672,9 +1652,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -1741,8 +1718,8 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} type-fest@0.21.3: @@ -2590,7 +2567,7 @@ snapshots: '@webgpu/types@0.1.69': {} - adm-zip@0.5.16: {} + adm-zip@0.6.0: {} ansi-escapes@4.3.2: dependencies: @@ -2673,8 +2650,6 @@ snapshots: baseline-browser-mapping@2.9.18: {} - boolean@3.2.0: {} - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2771,8 +2746,6 @@ snapshots: detect-newline@3.1.0: {} - detect-node@2.1.0: {} - eastasianwidth@0.2.0: {} electron-to-chromium@1.5.279: {} @@ -2791,8 +2764,6 @@ snapshots: es-errors@1.3.0: {} - es6-error@4.1.1: {} - esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -2906,14 +2877,12 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - global-agent@3.0.0: + global-agent@4.1.3: dependencies: - boolean: 3.2.0 - es6-error: 4.1.1 - matcher: 3.0.0 - roarr: 2.15.4 - semver: 7.7.3 - serialize-error: 7.0.1 + globalthis: 1.0.4 + matcher: 4.0.0 + semver: 7.8.5 + serialize-error: 8.1.0 globalthis@1.0.4: dependencies: @@ -3321,8 +3290,6 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - json-stringify-safe@5.0.1: {} - json5@2.2.3: {} leven@3.1.0: {} @@ -3349,7 +3316,7 @@ snapshots: dependencies: tmpl: 1.0.5 - matcher@3.0.0: + matcher@4.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -3398,22 +3365,22 @@ snapshots: dependencies: mimic-fn: 2.1.0 - onnxruntime-common@1.24.0-dev.20251116-b39e144322: {} + onnxruntime-common@1.29.0: {} - onnxruntime-common@1.24.3: {} + onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: {} - onnxruntime-node@1.24.3: + onnxruntime-node@1.29.0: dependencies: - adm-zip: 0.5.16 - global-agent: 3.0.0 - onnxruntime-common: 1.24.3 + adm-zip: 0.6.0 + global-agent: 4.1.3 + onnxruntime-common: 1.29.0 - onnxruntime-web@1.26.0-dev.20260416-b7804b056c: + onnxruntime-web@1.30.0-dev.20260904-d47fd8824: dependencies: flatbuffers: 25.9.23 guid-typescript: 1.0.9 long: 5.3.2 - onnxruntime-common: 1.24.0-dev.20251116-b39e144322 + onnxruntime-common: 1.30.0-dev.20260813-72e1c9c9b8 platform: 1.3.6 protobufjs: 7.5.4 @@ -3500,26 +3467,15 @@ snapshots: resolve-from@5.0.0: {} - roarr@2.15.4: - dependencies: - boolean: 3.2.0 - detect-node: 2.1.0 - globalthis: 1.0.4 - json-stringify-safe: 5.0.1 - semver-compare: 1.0.0 - sprintf-js: 1.1.3 - - semver-compare@1.0.0: {} - semver@6.3.1: {} semver@7.7.3: {} semver@7.8.5: {} - serialize-error@7.0.1: + serialize-error@8.1.0: dependencies: - type-fest: 0.13.1 + type-fest: 0.20.2 sharp@0.35.4(@types/node@24.10.9): dependencies: @@ -3575,8 +3531,6 @@ snapshots: sprintf-js@1.0.3: {} - sprintf-js@1.1.3: {} - stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -3641,7 +3595,7 @@ snapshots: type-detect@4.0.8: {} - type-fest@0.13.1: {} + type-fest@0.20.2: {} type-fest@0.21.3: {} From 8f74e578a716aea22447ec4c4d609cb41c985a57 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:40:33 -0400 Subject: [PATCH 083/103] bump protobufjs version in pnpm-lock.yaml --- pnpm-lock.yaml | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 018d90ccb..ea07db4b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -691,29 +691,26 @@ packages: '@protobufjs/base64@1.1.2': resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} '@sinclair/typebox@0.34.48': resolution: {integrity: sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==} @@ -1574,8 +1571,8 @@ packages: resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - protobufjs@7.5.4: - resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} engines: {node: '>=12.0.0'} pure-rand@7.0.1: @@ -2422,24 +2419,21 @@ snapshots: '@protobufjs/base64@1.1.2': {} - '@protobufjs/codegen@2.0.4': {} + '@protobufjs/codegen@2.0.5': {} - '@protobufjs/eventemitter@1.1.0': {} + '@protobufjs/eventemitter@1.1.1': {} - '@protobufjs/fetch@1.1.0': + '@protobufjs/fetch@1.1.1': dependencies: '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.0': {} - '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.0': {} + '@protobufjs/utf8@1.1.2': {} '@sinclair/typebox@0.34.48': {} @@ -3382,7 +3376,7 @@ snapshots: long: 5.3.2 onnxruntime-common: 1.30.0-dev.20260813-72e1c9c9b8 platform: 1.3.6 - protobufjs: 7.5.4 + protobufjs: 7.6.6 p-limit@2.3.0: dependencies: @@ -3440,18 +3434,17 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - protobufjs@7.5.4: + protobufjs@7.6.6: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 + '@protobufjs/utf8': 1.1.2 '@types/node': 24.10.9 long: 5.3.2 From 95a9d2419da76c589a53f9183baa3bb8064bacd4 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 02:20:18 -0400 Subject: [PATCH 084/103] fix type errors --- .../transformers/src/models/vits/tokenization_vits.js | 1 + packages/transformers/src/tokenization_utils.js | 11 +++++++---- packages/transformers/src/utils/image.js | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/transformers/src/models/vits/tokenization_vits.js b/packages/transformers/src/models/vits/tokenization_vits.js index 9f34a37c4..f4ce46879 100644 --- a/packages/transformers/src/models/vits/tokenization_vits.js +++ b/packages/transformers/src/models/vits/tokenization_vits.js @@ -17,6 +17,7 @@ export class VitsTokenizer extends PreTrainedTokenizer { super(tokenizerJSON, tokenizerConfig); // Custom decoder function + // @ts-expect-error ts(2322) this._tokenizer.decoder = new VitsDecoder({ type: 'VitsDecoder' }); } } diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index da3f7c0ba..3ec94d86b 100644 --- a/packages/transformers/src/tokenization_utils.js +++ b/packages/transformers/src/tokenization_utils.js @@ -841,10 +841,13 @@ export function _build_translation_inputs(self, raw_inputs, tokenizer_options, g // In the same way as the Python library, we override the post-processor // to force the source language to be first: - for (const item of self._tokenizer.post_processor.config.single) { - if ('SpecialToken' in item && self.languageRegex.test(item.SpecialToken.id)) { - item.SpecialToken.id = self.lang_to_token(src_lang_token); - break; + const post_processor_config = self._tokenizer.post_processor?.config; + if ('single' in post_processor_config) { + for (const item of post_processor_config.single) { + if ('SpecialToken' in item && self.languageRegex.test(item.SpecialToken.id)) { + item.SpecialToken.id = self.lang_to_token(src_lang_token); + break; + } } } // TODO: Do the same for pair? diff --git a/packages/transformers/src/utils/image.js b/packages/transformers/src/utils/image.js index 7515c2c52..650bcfa7b 100644 --- a/packages/transformers/src/utils/image.js +++ b/packages/transformers/src/utils/image.js @@ -34,7 +34,7 @@ if (apis.IS_WEB_ENV) { } else if (sharp) { // Running in Node.js, electron, or other non-browser environment - loadImageFunction = async (/**@type {sharp.Sharp}*/ img) => { + loadImageFunction = async (/**@type {import('sharp').Sharp}*/ img) => { const metadata = await img.metadata(); const rawChannels = metadata.channels; From 1fc6c568f23c7f25e1fe60fdd3e0f423d44066ee Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 02:27:01 -0400 Subject: [PATCH 085/103] run unit tests on node > 18 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 52c53c963..7eee94030 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,7 +48,7 @@ jobs: group: aws-general-8-plus strategy: matrix: - node-version: [18, 20, 22] + node-version: [20, 22, 24] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From c6bb2267abfeb24687c6c987a5953e6cb5ef61b0 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 02:42:12 -0400 Subject: [PATCH 086/103] skip musicgen --- .../tests/models/musicgen/test_modeling_musicgen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transformers/tests/models/musicgen/test_modeling_musicgen.js b/packages/transformers/tests/models/musicgen/test_modeling_musicgen.js index e68f83b6b..69292f52a 100644 --- a/packages/transformers/tests/models/musicgen/test_modeling_musicgen.js +++ b/packages/transformers/tests/models/musicgen/test_modeling_musicgen.js @@ -3,7 +3,7 @@ import { T5Tokenizer, MusicgenForConditionalGeneration, full } from "../../../sr import { MAX_MODEL_LOAD_TIME, MAX_TEST_EXECUTION_TIME, MAX_MODEL_DISPOSE_TIME, DEFAULT_MODEL_OPTIONS } from "../../init.js"; export default () => { - describe("MusicgenForConditionalGeneration", () => { + describe.skip("MusicgenForConditionalGeneration", () => { const model_id = "hf-internal-testing/tiny-random-MusicgenForConditionalGeneration"; // Example adapted from https://huggingface.co/docs/transformers/model_doc/musicgen#text-conditional-generation From 902cdb94ca453c07736d8cd02f8c8d8534f228bf Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 11:38:21 -0400 Subject: [PATCH 087/103] Fix rfft --- packages/transformers/src/ops/registry.js | 9 +++-- packages/transformers/src/utils/tensor.js | 33 ++++++++++++++++++- .../transformers/tests/utils/tensor.test.js | 30 +++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/packages/transformers/src/ops/registry.js b/packages/transformers/src/ops/registry.js index 42508267d..4541f88b1 100644 --- a/packages/transformers/src/ops/registry.js +++ b/packages/transformers/src/ops/registry.js @@ -129,11 +129,10 @@ export class TensorOpRegistry { if (!this._rfft) { this._rfft = wrap( [ - 8, 9, 18, 0, 58, 97, 10, 33, 10, 1, 120, 10, 0, 10, 1, 97, 18, 1, 121, 34, 3, 68, 70, 84, 42, 15, - 10, 8, 111, 110, 101, 115, 105, 100, 101, 100, 24, 1, 160, 1, 2, 18, 1, 100, 90, 21, 10, 1, 120, 18, - 16, 10, 14, 8, 1, 18, 10, 10, 3, 18, 1, 115, 10, 3, 18, 1, 99, 90, 11, 10, 1, 97, 18, 6, 10, 4, 8, - 7, 18, 0, 98, 21, 10, 1, 121, 18, 16, 10, 14, 8, 1, 18, 10, 10, 3, 18, 1, 115, 10, 3, 18, 1, 99, 66, - 2, 16, 20, + 8, 9, 58, 93, 10, 33, 10, 1, 120, 10, 0, 10, 1, 97, 18, 1, 121, 34, 3, 68, 70, 84, 42, 15, 10, 8, + 111, 110, 101, 115, 105, 100, 101, 100, 24, 1, 160, 1, 2, 18, 1, 100, 90, 19, 10, 1, 120, 18, 14, + 10, 12, 8, 1, 18, 8, 10, 0, 10, 0, 10, 2, 8, 1, 90, 11, 10, 1, 97, 18, 6, 10, 4, 8, 7, 18, 0, 98, + 19, 10, 1, 121, 18, 14, 10, 12, 8, 1, 18, 8, 10, 0, 10, 0, 10, 2, 8, 2, 66, 2, 16, 20, ], this.session_options, 'y', diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index e680a7aac..dc6f67b42 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -337,6 +337,35 @@ export class Tensor { return this; } + /** + * Return a new Tensor with the element-wise remainder of division by a constant. + * Follows Python's modulo semantics, so the result has the same sign as the divisor + * (e.g. `-1 mod 2 = 1`). Equivalent to `torch.remainder`. + * @param {number|bigint} val The divisor. + * @returns {Tensor} The new tensor. + */ + remainder(val) { + return this.clone().remainder_(val); + } + + /** + * In-place version of @see {@link Tensor.remainder} + * @param {number|bigint} val The divisor. + * @returns {Tensor} Returns `this`. + */ + remainder_(val) { + const this_data = this.data; + // `%` cannot mix bigint and number operands, so cast the divisor to match the element type. + // (Typed as `any` since TypeScript cannot apply `%` to `any` and `number | bigint`.) + const is_bigint = this_data instanceof BigInt64Array || this_data instanceof BigUint64Array; + const divisor = /** @type {any} */ (is_bigint ? BigInt(val) : Number(val)); + for (let i = 0; i < this_data.length; ++i) { + // `%` truncates towards zero; shifting by the divisor gives the result the divisor's sign. + this_data[i] = ((this_data[i] % divisor) + divisor) % divisor; + } + return this; + } + /** * Creates a deep copy of the current Tensor. * @returns {Tensor} A new Tensor with the same type, data, and dimensions as the original. @@ -1149,7 +1178,9 @@ export async function matmul(a, b) { */ export async function rfft(x, a) { const op = await TensorOpRegistry.rfft; - return await op({ x, a }); + // ONNX DFT expects a trailing real/imaginary component dimension, so add one to the + // real-valued input and resolve (possibly negative) axes against the original dimensions. + return await op({ x: x.unsqueeze(-1), a: a.remainder(x.dims.length) }); } /** diff --git a/packages/transformers/tests/utils/tensor.test.js b/packages/transformers/tests/utils/tensor.test.js index 278aa8771..886b22f65 100644 --- a/packages/transformers/tests/utils/tensor.test.js +++ b/packages/transformers/tests/utils/tensor.test.js @@ -423,6 +423,36 @@ describe("Tensor operations", () => { expect(result).toBeCloseToNested(target); }); }); + + describe("remainder", () => { + it("should follow the sign of the divisor (python-style modulo)", () => { + const t1 = new Tensor("float32", [-3, -1, 0, 1, 3, 4.5], [6]); + const target = new Tensor("float32", [1, 1, 0, 1, 1, 0.5], [6]); + + const result = t1.remainder(2); + expect(result).toBeCloseToNested(target); + }); + it("should support a negative divisor", () => { + const t1 = new Tensor("float32", [3, -3], [2]); + const target = new Tensor("float32", [-1, -1], [2]); + + const result = t1.remainder(-2); + expect(result).toBeCloseToNested(target); + }); + it("should support int64 (bigint) tensors", () => { + const t1 = new Tensor("int64", [-3n, -1n, 0n, 1n, 3n], [5]); + const target = new Tensor("int64", [1n, 1n, 0n, 1n, 1n], [5]); + + const result = t1.remainder(2); + expect(result).toEqual(target); + }); + it("should operate in place with remainder_", () => { + const t1 = new Tensor("int64", [-1n], []); + const result = t1.remainder_(2); + expect(result).toBe(t1); + expect(t1.item()).toBe(1n); + }); + }); describe("gt", () => { it("should perform element-wise greater than comparison with a scalar", () => { const t1 = new Tensor("float32", [1, 5, 3, 7], [4]); From 357d0e3169a1061771b156bd01a38798c7495019 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 7 Sep 2026 11:48:22 -0400 Subject: [PATCH 088/103] update targets for latest onnxruntime versions --- packages/transformers/tests/models/arcee/test_modeling_arcee.js | 2 +- packages/transformers/tests/models/lfm2/test_modeling_lfm2.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/transformers/tests/models/arcee/test_modeling_arcee.js b/packages/transformers/tests/models/arcee/test_modeling_arcee.js index a852aeb49..18b7138f6 100644 --- a/packages/transformers/tests/models/arcee/test_modeling_arcee.js +++ b/packages/transformers/tests/models/arcee/test_modeling_arcee.js @@ -37,7 +37,7 @@ export default () => { max_length: 10, }); expect(outputs.tolist()).toEqual([ - [2n, 1n, 22172n, 5706n, 3803n, 11619n, 28763n, 4015n, 18904n, 7959n], + [2n, 1n, 22172n, 17562n, 349n, 7959n, 20464n, 25378n, 30238n, 22990n], [1n, 22172n, 3186n, 1316n, 11038n, 22918n, 9469n, 25671n, 22918n, 2687n], ]); }, diff --git a/packages/transformers/tests/models/lfm2/test_modeling_lfm2.js b/packages/transformers/tests/models/lfm2/test_modeling_lfm2.js index c41dabf4a..a78459301 100644 --- a/packages/transformers/tests/models/lfm2/test_modeling_lfm2.js +++ b/packages/transformers/tests/models/lfm2/test_modeling_lfm2.js @@ -37,7 +37,7 @@ export default () => { max_length: 10, }); expect(outputs.tolist()).toEqual([ - [0n, 1n, 52572n, 60239n, 57205n, 6790n, 58292n, 30935n, 5959n, 6902n], + [0n, 1n, 52572n, 60239n, 44014n, 6623n, 27679n, 59585n, 30290n, 6790n], [1n, 52572n, 2031n, 59572n, 43345n, 42427n, 31142n, 41100n, 5321n, 5816n], ]); }, From db4f28d1ecffa113d087adf827e272e527254ae7 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:14:12 -0400 Subject: [PATCH 089/103] bump onnxruntime versions --- packages/transformers/package.json | 4 ++-- pnpm-lock.yaml | 27 +++++++++++---------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 0d28a1b37..903c8878c 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -57,8 +57,8 @@ "dependencies": { "@huggingface/jinja": "^0.5.10", "@huggingface/tokenizers": "^0.2.0", - "onnxruntime-node": "1.29.0", - "onnxruntime-web": "1.30.0-dev.20260904-d47fd8824", + "onnxruntime-node": "1.30.0-dev.20260909-b905e946b", + "onnxruntime-web": "1.30.0-dev.20260909-b905e946b", "sharp": "^0.35.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea07db4b7..12fa7f770 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: specifier: ^0.2.0 version: 0.2.0 onnxruntime-node: - specifier: 1.29.0 - version: 1.29.0 + specifier: 1.30.0-dev.20260909-b905e946b + version: 1.30.0-dev.20260909-b905e946b onnxruntime-web: - specifier: 1.30.0-dev.20260904-d47fd8824 - version: 1.30.0-dev.20260904-d47fd8824 + specifier: 1.30.0-dev.20260909-b905e946b + version: 1.30.0-dev.20260909-b905e946b sharp: specifier: ^0.35.4 version: 0.35.4(@types/node@24.10.9) @@ -1488,18 +1488,15 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - onnxruntime-common@1.29.0: - resolution: {integrity: sha512-/F63/e2VJoaVXGGNu6S5QH7jivBThGO95OzAVXXQ8hTta/b1QxI8udHa6cI3+3mAb5WWIIaMMwfZw01oivjJ1g==} - onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: resolution: {integrity: sha512-rgjnYrMP7e7CNGZOIygFaeF5qZCoRtBBeMFGjRNCXNyUpxXMGZIn9EE/LnCjCdCix6MYbyNR3JvMTabCutI9hg==} - onnxruntime-node@1.29.0: - resolution: {integrity: sha512-WjiVVB72riILz8HbYvxvmjKyE/WmkYoSfKY++axo5jAR609HQg8MwiG/HhShpTcJfmmAdzxxmB+MMST3A+SiPA==} + onnxruntime-node@1.30.0-dev.20260909-b905e946b: + resolution: {integrity: sha512-tkKNQ702ELoWHrKfkXstolWu28ckU3IZLQ2/YGZjdyOUz8a28G5wFkZ2yWfnGwHhfBqgM3uLAvtT/NYEQMZvRg==} os: [win32, darwin, linux] - onnxruntime-web@1.30.0-dev.20260904-d47fd8824: - resolution: {integrity: sha512-vqD58oCa9rtgB+AaSpJPECiQTxX0KDqAqP+4cPvoxOskz7yvvB3oINO/BG23CraRgkhO71Khgjwg0K8E2d5v4A==} + onnxruntime-web@1.30.0-dev.20260909-b905e946b: + resolution: {integrity: sha512-v9irpM/V/D+rVpIkIOOgR8wORiVJ50mPnwvG3VsxJ3MTTrJxIrIqxkqz6dqP9mFp0Sb1bd1h1fGxHLoFjD97wg==} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -3359,17 +3356,15 @@ snapshots: dependencies: mimic-fn: 2.1.0 - onnxruntime-common@1.29.0: {} - onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: {} - onnxruntime-node@1.29.0: + onnxruntime-node@1.30.0-dev.20260909-b905e946b: dependencies: adm-zip: 0.6.0 global-agent: 4.1.3 - onnxruntime-common: 1.29.0 + onnxruntime-common: 1.30.0-dev.20260813-72e1c9c9b8 - onnxruntime-web@1.30.0-dev.20260904-d47fd8824: + onnxruntime-web@1.30.0-dev.20260909-b905e946b: dependencies: flatbuffers: 25.9.23 guid-typescript: 1.0.9 From b8a27ac72a184e1132c1ac05d38ea61b43df7db9 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 01:19:33 -0400 Subject: [PATCH 090/103] bump --- packages/transformers/package.json | 4 ++-- pnpm-lock.yaml | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 903c8878c..771d5d401 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -57,8 +57,8 @@ "dependencies": { "@huggingface/jinja": "^0.5.10", "@huggingface/tokenizers": "^0.2.0", - "onnxruntime-node": "1.30.0-dev.20260909-b905e946b", - "onnxruntime-web": "1.30.0-dev.20260909-b905e946b", + "onnxruntime-node": "1.30.0-dev.20260909-e76036b8e", + "onnxruntime-web": "1.30.0-dev.20260909-e76036b8e", "sharp": "^0.35.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12fa7f770..32926d22f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: specifier: ^0.2.0 version: 0.2.0 onnxruntime-node: - specifier: 1.30.0-dev.20260909-b905e946b - version: 1.30.0-dev.20260909-b905e946b + specifier: 1.30.0-dev.20260909-e76036b8e + version: 1.30.0-dev.20260909-e76036b8e onnxruntime-web: - specifier: 1.30.0-dev.20260909-b905e946b - version: 1.30.0-dev.20260909-b905e946b + specifier: 1.30.0-dev.20260909-e76036b8e + version: 1.30.0-dev.20260909-e76036b8e sharp: specifier: ^0.35.4 version: 0.35.4(@types/node@24.10.9) @@ -1491,12 +1491,12 @@ packages: onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: resolution: {integrity: sha512-rgjnYrMP7e7CNGZOIygFaeF5qZCoRtBBeMFGjRNCXNyUpxXMGZIn9EE/LnCjCdCix6MYbyNR3JvMTabCutI9hg==} - onnxruntime-node@1.30.0-dev.20260909-b905e946b: - resolution: {integrity: sha512-tkKNQ702ELoWHrKfkXstolWu28ckU3IZLQ2/YGZjdyOUz8a28G5wFkZ2yWfnGwHhfBqgM3uLAvtT/NYEQMZvRg==} + onnxruntime-node@1.30.0-dev.20260909-e76036b8e: + resolution: {integrity: sha512-bjhOz3+7qCGftm9YyO+izfCcrLlE8Fg6wo9gWA3iheyZ5sVHfDB6ue7Qg6yE6d1i9LjWCdVynkmgzPLZv13MAg==} os: [win32, darwin, linux] - onnxruntime-web@1.30.0-dev.20260909-b905e946b: - resolution: {integrity: sha512-v9irpM/V/D+rVpIkIOOgR8wORiVJ50mPnwvG3VsxJ3MTTrJxIrIqxkqz6dqP9mFp0Sb1bd1h1fGxHLoFjD97wg==} + onnxruntime-web@1.30.0-dev.20260909-e76036b8e: + resolution: {integrity: sha512-EHgt418NLs/iV30oFgZnYpbzdTZ169k93c3e8zUSG4aeQcUHiYvTIqInBppY7LI2J4peFf6IJQAS2XHwELE+5A==} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -3358,13 +3358,13 @@ snapshots: onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: {} - onnxruntime-node@1.30.0-dev.20260909-b905e946b: + onnxruntime-node@1.30.0-dev.20260909-e76036b8e: dependencies: adm-zip: 0.6.0 global-agent: 4.1.3 onnxruntime-common: 1.30.0-dev.20260813-72e1c9c9b8 - onnxruntime-web@1.30.0-dev.20260909-b905e946b: + onnxruntime-web@1.30.0-dev.20260909-e76036b8e: dependencies: flatbuffers: 25.9.23 guid-typescript: 1.0.9 From de9fa5e55d8aade1beb5acc12eff79d3e86c6e97 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <admin@xenova.com> Date: Thu, 10 Sep 2026 01:21:11 -0400 Subject: [PATCH 091/103] Fix post-processor config check for null safety Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/transformers/src/tokenization_utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index 3ec94d86b..30fc58ea6 100644 --- a/packages/transformers/src/tokenization_utils.js +++ b/packages/transformers/src/tokenization_utils.js @@ -842,7 +842,7 @@ export function _build_translation_inputs(self, raw_inputs, tokenizer_options, g // In the same way as the Python library, we override the post-processor // to force the source language to be first: const post_processor_config = self._tokenizer.post_processor?.config; - if ('single' in post_processor_config) { + if (post_processor_config && 'single' in post_processor_config) { for (const item of post_processor_config.single) { if ('SpecialToken' in item && self.languageRegex.test(item.SpecialToken.id)) { item.SpecialToken.id = self.lang_to_token(src_lang_token); From 4fe211508a75c98c14b269250cfe1d748175ea4e Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 01:26:47 -0400 Subject: [PATCH 092/103] bump recommended node.js versions --- .ai/skills/transformers-js/SKILL.md | 2 +- CONTRIBUTING.md | 2 +- .../transformers/docs/source/guides/node-audio-processing.md | 4 ++-- packages/transformers/docs/source/tutorials/next-ai-sdk.md | 4 ++-- packages/transformers/docs/source/tutorials/next.md | 4 ++-- packages/transformers/docs/source/tutorials/node.md | 4 ++-- packages/transformers/docs/source/tutorials/react.md | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.ai/skills/transformers-js/SKILL.md b/.ai/skills/transformers-js/SKILL.md index c55607295..c555715c9 100644 --- a/.ai/skills/transformers-js/SKILL.md +++ b/.ai/skills/transformers-js/SKILL.md @@ -5,7 +5,7 @@ license: Apache-2.0 metadata: author: huggingface repository: https://github.com/huggingface/transformers.js -compatibility: Node.js 18+ (or equivalent Bun / Deno), or a modern browser with ES modules. WebGPU requires runtime and hardware support; WASM is the fallback. Model downloads from the Hugging Face Hub require network access unless you ship models locally. +compatibility: Node.js 20+ (or equivalent Bun / Deno), or a modern browser with ES modules. WebGPU requires runtime and hardware support; WASM is the fallback. Model downloads from the Hugging Face Hub require network access unless you ship models locally. --- # transformers.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9890d96e6..0bc89d175 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -226,7 +226,7 @@ Git](https://git-scm.com/book/en/v2) is a very good reference. You'll need the following tools installed to contribute to 🤗 Transformers.js: -- **[Node.js v18](https://nodejs.org/)** or above +- **[Node.js v20](https://nodejs.org/)** or above - **[pnpm](https://pnpm.io/)** - Fast, disk space efficient package manager To install pnpm: diff --git a/packages/transformers/docs/source/guides/node-audio-processing.md b/packages/transformers/docs/source/guides/node-audio-processing.md index 2de1f71b2..b975cc040 100644 --- a/packages/transformers/docs/source/guides/node-audio-processing.md +++ b/packages/transformers/docs/source/guides/node-audio-processing.md @@ -17,8 +17,8 @@ This tutorial will be written as an ES module, but you can easily adapt it to us ## Prerequisites -- [Node.js](https://nodejs.org/en/) version 18+ -- [npm](https://www.npmjs.com/) version 9+ +- [Node.js](https://nodejs.org/en/) version 20+ +- [npm](https://www.npmjs.com/) version 10+ ## Getting started diff --git a/packages/transformers/docs/source/tutorials/next-ai-sdk.md b/packages/transformers/docs/source/tutorials/next-ai-sdk.md index 1f161216d..e95bc979e 100644 --- a/packages/transformers/docs/source/tutorials/next-ai-sdk.md +++ b/packages/transformers/docs/source/tutorials/next-ai-sdk.md @@ -10,8 +10,8 @@ Useful links: ## Prerequisites -- [Node.js](https://nodejs.org/en/) version 18+ -- [npm](https://www.npmjs.com/) version 9+ +- [Node.js](https://nodejs.org/en/) version 20+ +- [npm](https://www.npmjs.com/) version 10+ - A browser with WebGPU support (Chrome 113+, Edge 113+, or Firefox/Safari with flags enabled) ## Step 1: Create the project diff --git a/packages/transformers/docs/source/tutorials/next.md b/packages/transformers/docs/source/tutorials/next.md index 807685e9b..9e530f1eb 100644 --- a/packages/transformers/docs/source/tutorials/next.md +++ b/packages/transformers/docs/source/tutorials/next.md @@ -13,8 +13,8 @@ Useful links: ## Prerequisites -- [Node.js](https://nodejs.org/en/) version 18+ -- [npm](https://www.npmjs.com/) version 9+ +- [Node.js](https://nodejs.org/en/) version 20+ +- [npm](https://www.npmjs.com/) version 10+ ## Client-side inference diff --git a/packages/transformers/docs/source/tutorials/node.md b/packages/transformers/docs/source/tutorials/node.md index 759c0eb30..03aa7de87 100644 --- a/packages/transformers/docs/source/tutorials/node.md +++ b/packages/transformers/docs/source/tutorials/node.md @@ -24,8 +24,8 @@ Although you can always use the [Python library](https://github.com/huggingface/ ## Prerequisites -- [Node.js](https://nodejs.org/en/) version 18+ -- [npm](https://www.npmjs.com/) version 9+ +- [Node.js](https://nodejs.org/en/) version 20+ +- [npm](https://www.npmjs.com/) version 10+ ## Getting started diff --git a/packages/transformers/docs/source/tutorials/react.md b/packages/transformers/docs/source/tutorials/react.md index fb90ed076..f6370130b 100644 --- a/packages/transformers/docs/source/tutorials/react.md +++ b/packages/transformers/docs/source/tutorials/react.md @@ -11,8 +11,8 @@ Useful links: ## Prerequisites -- [Node.js](https://nodejs.org/en/) version 18+ -- [npm](https://www.npmjs.com/) version 9+ +- [Node.js](https://nodejs.org/en/) version 20+ +- [npm](https://www.npmjs.com/) version 10+ ## Step 1: Initialize the project From b56617940f87acc9293e6d153aed01729b284c5c Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:00:37 -0400 Subject: [PATCH 093/103] suggestions --- packages/transformers/src/utils/tensor.js | 6 +++++- packages/transformers/tests/utils/tensor.test.js | 15 +++++++++++++++ .../transformers/tests/utils/tensor_ops.test.js | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index dc6f67b42..1c237d3ef 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -359,6 +359,9 @@ export class Tensor { // (Typed as `any` since TypeScript cannot apply `%` to `any` and `number | bigint`.) const is_bigint = this_data instanceof BigInt64Array || this_data instanceof BigUint64Array; const divisor = /** @type {any} */ (is_bigint ? BigInt(val) : Number(val)); + if ((divisor === 0 || divisor === 0n) && this.type.includes('int')) { + throw new RangeError('Division by zero'); + } for (let i = 0; i < this_data.length; ++i) { // `%` truncates towards zero; shifting by the divisor gives the result the divisor's sign. this_data[i] = ((this_data[i] % divisor) + divisor) % divisor; @@ -1177,10 +1180,11 @@ export async function matmul(a, b) { * @returns {Promise<Tensor>} the output tensor. */ export async function rfft(x, a) { + const axis = safeIndex(Number(a.item()), x.dims.length); const op = await TensorOpRegistry.rfft; // ONNX DFT expects a trailing real/imaginary component dimension, so add one to the // real-valued input and resolve (possibly negative) axes against the original dimensions. - return await op({ x: x.unsqueeze(-1), a: a.remainder(x.dims.length) }); + return await op({ x: x.unsqueeze(-1), a: new Tensor('int64', [BigInt(axis)], []) }); } /** diff --git a/packages/transformers/tests/utils/tensor.test.js b/packages/transformers/tests/utils/tensor.test.js index 886b22f65..a5c725184 100644 --- a/packages/transformers/tests/utils/tensor.test.js +++ b/packages/transformers/tests/utils/tensor.test.js @@ -425,6 +425,21 @@ describe("Tensor operations", () => { }); describe("remainder", () => { + it.each(["int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64"])("should reject zero divisors for %s without mutating the input", (type) => { + const values = type.endsWith("64") ? [1n, 2n] : [1, 2]; + const tensor = new Tensor(type, values, [2]); + for (const divisor of [0, -0, 0n]) { + expect(() => tensor.remainder(divisor)).toThrow("Division by zero"); + expect(() => tensor.remainder_(divisor)).toThrow("Division by zero"); + expect(Array.from(tensor.data)).toEqual(values); + } + }); + it.each(["float32", "float64"])("should return NaN for %s with a zero divisor", (type) => { + const tensor = new Tensor(type, [1, 0, -1], [3]); + expect(Array.from(tensor.remainder(0).data)).toEqual([NaN, NaN, NaN]); + expect(Array.from(tensor.data)).toEqual([1, 0, -1]); + expect(Array.from(tensor.remainder_(0).data)).toEqual([NaN, NaN, NaN]); + }); it("should follow the sign of the divisor (python-style modulo)", () => { const t1 = new Tensor("float32", [-3, -1, 0, 1, 3, 4.5], [6]); const target = new Tensor("float32", [1, 1, 0, 1, 1, 0.5], [6]); diff --git a/packages/transformers/tests/utils/tensor_ops.test.js b/packages/transformers/tests/utils/tensor_ops.test.js index a0ad1f076..bcde946bd 100644 --- a/packages/transformers/tests/utils/tensor_ops.test.js +++ b/packages/transformers/tests/utils/tensor_ops.test.js @@ -251,6 +251,20 @@ describe("Tensor operations", () => { }); describe("rfft", () => { + it.each([2, -3])("should reject out-of-range axis %s", async (axis) => { + const input = new Tensor("float32", [0, 1, 2, 3, 4, 5], [2, 3]); + const dim = new Tensor("int64", [BigInt(axis)], []); + await expect(rfft(input, dim)).rejects.toThrow("out of bounds"); + }); + it.each([[0, -2], [1, -1]])("should produce the same result for axes %s and %s", async (positive, negative) => { + const input = new Tensor("float32", [0, 1, 2, 3, 4, 5], [2, 3]); + const dim = new Tensor("int64", [BigInt(negative)], []); + const expected = await rfft(input, new Tensor("int64", [BigInt(positive)], [])); + const actual = await rfft(input, dim); + expect(actual.dims).toEqual(expected.dims); + expectToBeCloseToArray(expected.data, actual.data); + expect(dim.item()).toBe(BigInt(negative)); + }); it("non-power of 2", async () => { const rows = 2; const cols = 3; From 36ec66b4fbfc98fa89cc78876ae327c2ae552c6d Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:58:13 -0400 Subject: [PATCH 094/103] nice --- packages/transformers/scripts/build/constants.mjs | 2 +- packages/transformers/src/backends/onnx-node.js | 9 +++++++++ packages/transformers/src/backends/onnx.js | 3 +-- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 packages/transformers/src/backends/onnx-node.js diff --git a/packages/transformers/scripts/build/constants.mjs b/packages/transformers/scripts/build/constants.mjs index 765203f5e..c00b6ccb7 100644 --- a/packages/transformers/scripts/build/constants.mjs +++ b/packages/transformers/scripts/build/constants.mjs @@ -10,7 +10,7 @@ export const NODE_EXTERNAL_MODULES = [ // node:* modules are handled by externalNodeBuiltinsPlugin ]; -export const WEB_IGNORE_MODULES = ["onnxruntime-node", "sharp", "fs", "path", "url", "stream", "stream/promises"]; +export const WEB_IGNORE_MODULES = ["./onnx-node.js", "sharp", "fs", "path", "url", "stream", "stream/promises"]; export const WEB_EXTERNAL_MODULES = ["onnxruntime-common", "onnxruntime-web"]; const __dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/packages/transformers/src/backends/onnx-node.js b/packages/transformers/src/backends/onnx-node.js new file mode 100644 index 000000000..e70f67435 --- /dev/null +++ b/packages/transformers/src/backends/onnx-node.js @@ -0,0 +1,9 @@ +import { createRequire } from 'node:module'; + +// Resolve from this file in both ESM and CommonJS builds. +const requireFromHere = createRequire(typeof __filename === 'string' ? __filename : import.meta.url); + +// Disable POSIX telemetry before loading the native binding. +process.env.ORT_DISABLE_TELEMETRY = '1'; + +export default requireFromHere('onnxruntime-node'); diff --git a/packages/transformers/src/backends/onnx.js b/packages/transformers/src/backends/onnx.js index 0385c66dd..7dbfe44ba 100644 --- a/packages/transformers/src/backends/onnx.js +++ b/packages/transformers/src/backends/onnx.js @@ -15,8 +15,7 @@ import { env, apis, LogLevel } from '../env.js'; // NOTE: Import order matters here. We need to import `onnxruntime-node` before `onnxruntime-web`. -// In either case, we select the default export if it exists, otherwise we use the named export. -import * as ONNX_NODE from 'onnxruntime-node'; +import ONNX_NODE from './onnx-node.js'; import * as ONNX_WEB from 'onnxruntime-web/webgpu'; import { loadWasmBinary, loadWasmFactory } from './utils/cacheWasm.js'; import { isBlobURL, toAbsoluteURL } from '../utils/hub/utils.js'; From 0b76836b8f27fdf2168d2ffa04617bcf6f180f1e Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:01:32 -0400 Subject: [PATCH 095/103] bump --- packages/transformers/package.json | 4 ++-- pnpm-lock.yaml | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 771d5d401..acc85008b 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -57,8 +57,8 @@ "dependencies": { "@huggingface/jinja": "^0.5.10", "@huggingface/tokenizers": "^0.2.0", - "onnxruntime-node": "1.30.0-dev.20260909-e76036b8e", - "onnxruntime-web": "1.30.0-dev.20260909-e76036b8e", + "onnxruntime-node": "1.30.0-dev.20260910-f2c39fe2f", + "onnxruntime-web": "1.30.0-dev.20260910-f2c39fe2f", "sharp": "^0.35.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32926d22f..77ed57d80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: specifier: ^0.2.0 version: 0.2.0 onnxruntime-node: - specifier: 1.30.0-dev.20260909-e76036b8e - version: 1.30.0-dev.20260909-e76036b8e + specifier: 1.30.0-dev.20260910-f2c39fe2f + version: 1.30.0-dev.20260910-f2c39fe2f onnxruntime-web: - specifier: 1.30.0-dev.20260909-e76036b8e - version: 1.30.0-dev.20260909-e76036b8e + specifier: 1.30.0-dev.20260910-f2c39fe2f + version: 1.30.0-dev.20260910-f2c39fe2f sharp: specifier: ^0.35.4 version: 0.35.4(@types/node@24.10.9) @@ -1491,12 +1491,12 @@ packages: onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: resolution: {integrity: sha512-rgjnYrMP7e7CNGZOIygFaeF5qZCoRtBBeMFGjRNCXNyUpxXMGZIn9EE/LnCjCdCix6MYbyNR3JvMTabCutI9hg==} - onnxruntime-node@1.30.0-dev.20260909-e76036b8e: - resolution: {integrity: sha512-bjhOz3+7qCGftm9YyO+izfCcrLlE8Fg6wo9gWA3iheyZ5sVHfDB6ue7Qg6yE6d1i9LjWCdVynkmgzPLZv13MAg==} + onnxruntime-node@1.30.0-dev.20260910-f2c39fe2f: + resolution: {integrity: sha512-G92M8RAfXH8LDlo+E+bG9l1tH//NkpfDsRJFGLCovY5k0IyV0Z4JMjtA5Ixex511aOveeA2gpkRVajyU0nyJ8g==} os: [win32, darwin, linux] - onnxruntime-web@1.30.0-dev.20260909-e76036b8e: - resolution: {integrity: sha512-EHgt418NLs/iV30oFgZnYpbzdTZ169k93c3e8zUSG4aeQcUHiYvTIqInBppY7LI2J4peFf6IJQAS2XHwELE+5A==} + onnxruntime-web@1.30.0-dev.20260910-f2c39fe2f: + resolution: {integrity: sha512-TDbT5UUl51I5G7H/F16X1kMbMxFdfYblRzNoqd8Qi0nYd50HjYqvKYCIBls9bEuKyw8GfKfAdsE0Q6f5tANyXQ==} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -3358,13 +3358,13 @@ snapshots: onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: {} - onnxruntime-node@1.30.0-dev.20260909-e76036b8e: + onnxruntime-node@1.30.0-dev.20260910-f2c39fe2f: dependencies: adm-zip: 0.6.0 global-agent: 4.1.3 onnxruntime-common: 1.30.0-dev.20260813-72e1c9c9b8 - onnxruntime-web@1.30.0-dev.20260909-e76036b8e: + onnxruntime-web@1.30.0-dev.20260910-f2c39fe2f: dependencies: flatbuffers: 25.9.23 guid-typescript: 1.0.9 From f7547f6a547574b70206a384b259e4cbb50ac74c Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:17:36 -0400 Subject: [PATCH 096/103] fix --- packages/transformers/src/utils/tensor.js | 6 ++++-- packages/transformers/tests/utils/tensor.test.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 1c237d3ef..3cf69212b 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -363,8 +363,10 @@ export class Tensor { throw new RangeError('Division by zero'); } for (let i = 0; i < this_data.length; ++i) { - // `%` truncates towards zero; shifting by the divisor gives the result the divisor's sign. - this_data[i] = ((this_data[i] % divisor) + divisor) % divisor; + const remainder = this_data[i] % divisor; + // Only shift opposite signs: adding a large divisor can round away a valid remainder. + const needs_shift = (remainder < 0 && divisor > 0) || (remainder > 0 && divisor < 0); + this_data[i] = needs_shift ? remainder + divisor : remainder; } return this; } diff --git a/packages/transformers/tests/utils/tensor.test.js b/packages/transformers/tests/utils/tensor.test.js index a5c725184..d113d3a85 100644 --- a/packages/transformers/tests/utils/tensor.test.js +++ b/packages/transformers/tests/utils/tensor.test.js @@ -440,6 +440,14 @@ describe("Tensor operations", () => { expect(Array.from(tensor.data)).toEqual([1, 0, -1]); expect(Array.from(tensor.remainder_(0).data)).toEqual([NaN, NaN, NaN]); }); + it.each(["float32", "float64"])("should preserve small %s remainders with large same-sign divisors", (type) => { + for (const [divisor, values] of [[1e20, [1, 3]], [-1e20, [-1, -3]]]) { + const tensor = new Tensor(type, values, [2]); + expect(Array.from(tensor.remainder(divisor).data)).toEqual(values); + expect(Array.from(tensor.data)).toEqual(values); + expect(Array.from(tensor.remainder_(divisor).data)).toEqual(values); + } + }); it("should follow the sign of the divisor (python-style modulo)", () => { const t1 = new Tensor("float32", [-3, -1, 0, 1, 3, 4.5], [6]); const target = new Tensor("float32", [1, 1, 0, 1, 1, 0.5], [6]); @@ -461,6 +469,12 @@ describe("Tensor operations", () => { const result = t1.remainder(2); expect(result).toEqual(target); }); + it("should support negative bigint divisors and leave exact multiples at zero", () => { + const tensor = new Tensor("int64", [-4n, -3n, 0n, 3n, 4n], [5]); + const expected = [0n, -1n, 0n, -1n, 0n]; + expect(Array.from(tensor.remainder(-2n).data)).toEqual(expected); + expect(Array.from(tensor.remainder_(-2n).data)).toEqual(expected); + }); it("should operate in place with remainder_", () => { const t1 = new Tensor("int64", [-1n], []); const result = t1.remainder_(2); From 8c837ca7f8ccd4b823271a515391bbf192a25cc8 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:20:27 -0400 Subject: [PATCH 097/103] fix --- packages/transformers/src/backends/onnx-node.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/transformers/src/backends/onnx-node.js b/packages/transformers/src/backends/onnx-node.js index e70f67435..b259e96a8 100644 --- a/packages/transformers/src/backends/onnx-node.js +++ b/packages/transformers/src/backends/onnx-node.js @@ -1,7 +1,8 @@ import { createRequire } from 'node:module'; -// Resolve from this file in both ESM and CommonJS builds. -const requireFromHere = createRequire(typeof __filename === 'string' ? __filename : import.meta.url); +// Prefer this module's URL: `node -e` can expose the caller's __filename to ESM. +// esbuild replaces import.meta with an empty object in CommonJS, where __filename is local. +const requireFromHere = createRequire(import.meta.url ?? __filename); // Disable POSIX telemetry before loading the native binding. process.env.ORT_DISABLE_TELEMETRY = '1'; From cb7fa316afd22303343a44a8d74e80ed45f93afe Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:50:29 -0400 Subject: [PATCH 098/103] fixes --- packages/transformers/src/utils/tensor.js | 2 +- packages/transformers/tests/utils/tensor.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 3cf69212b..2e5cd90d6 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -359,7 +359,7 @@ export class Tensor { // (Typed as `any` since TypeScript cannot apply `%` to `any` and `number | bigint`.) const is_bigint = this_data instanceof BigInt64Array || this_data instanceof BigUint64Array; const divisor = /** @type {any} */ (is_bigint ? BigInt(val) : Number(val)); - if ((divisor === 0 || divisor === 0n) && this.type.includes('int')) { + if ((divisor === 0 || divisor === 0n) && (this.type.includes('int') || this.type === 'bool')) { throw new RangeError('Division by zero'); } for (let i = 0; i < this_data.length; ++i) { diff --git a/packages/transformers/tests/utils/tensor.test.js b/packages/transformers/tests/utils/tensor.test.js index d113d3a85..5e41ca095 100644 --- a/packages/transformers/tests/utils/tensor.test.js +++ b/packages/transformers/tests/utils/tensor.test.js @@ -425,8 +425,8 @@ describe("Tensor operations", () => { }); describe("remainder", () => { - it.each(["int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64"])("should reject zero divisors for %s without mutating the input", (type) => { - const values = type.endsWith("64") ? [1n, 2n] : [1, 2]; + it.each(["int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", "bool"])("should reject zero divisors for %s without mutating the input", (type) => { + const values = type.endsWith("64") ? [1n, 0n] : [1, 0]; const tensor = new Tensor(type, values, [2]); for (const divisor of [0, -0, 0n]) { expect(() => tensor.remainder(divisor)).toThrow("Division by zero"); From 21ee768e08924a328e6f2498f9e1e3ec737dd701 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 14 Sep 2026 01:47:44 -0400 Subject: [PATCH 099/103] bump ort --- packages/transformers/package.json | 4 ++-- pnpm-lock.yaml | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index acc85008b..e1f7ecaf7 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -57,8 +57,8 @@ "dependencies": { "@huggingface/jinja": "^0.5.10", "@huggingface/tokenizers": "^0.2.0", - "onnxruntime-node": "1.30.0-dev.20260910-f2c39fe2f", - "onnxruntime-web": "1.30.0-dev.20260910-f2c39fe2f", + "onnxruntime-node": "1.31.0-dev.20260911-2a43ec07e", + "onnxruntime-web": "1.31.0-dev.20260911-2a43ec07e", "sharp": "^0.35.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77ed57d80..1383d542a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: specifier: ^0.2.0 version: 0.2.0 onnxruntime-node: - specifier: 1.30.0-dev.20260910-f2c39fe2f - version: 1.30.0-dev.20260910-f2c39fe2f + specifier: 1.31.0-dev.20260911-2a43ec07e + version: 1.31.0-dev.20260911-2a43ec07e onnxruntime-web: - specifier: 1.30.0-dev.20260910-f2c39fe2f - version: 1.30.0-dev.20260910-f2c39fe2f + specifier: 1.31.0-dev.20260911-2a43ec07e + version: 1.31.0-dev.20260911-2a43ec07e sharp: specifier: ^0.35.4 version: 0.35.4(@types/node@24.10.9) @@ -1488,15 +1488,15 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: - resolution: {integrity: sha512-rgjnYrMP7e7CNGZOIygFaeF5qZCoRtBBeMFGjRNCXNyUpxXMGZIn9EE/LnCjCdCix6MYbyNR3JvMTabCutI9hg==} + onnxruntime-common@1.31.0-dev.20260911-2a43ec07e: + resolution: {integrity: sha512-gBuF6U32YErKIAt+yD7DeGBpjRxhJ1uJwako6ygjokSZULDU6Pd+KWComX8Tumk1hYVHRxjPj7mdMnD0ryAPOw==} - onnxruntime-node@1.30.0-dev.20260910-f2c39fe2f: - resolution: {integrity: sha512-G92M8RAfXH8LDlo+E+bG9l1tH//NkpfDsRJFGLCovY5k0IyV0Z4JMjtA5Ixex511aOveeA2gpkRVajyU0nyJ8g==} + onnxruntime-node@1.31.0-dev.20260911-2a43ec07e: + resolution: {integrity: sha512-VKO86uyrpms41+/rG7gMmQmosX4UE+Uy/1eBtjKrhK3GAuERHLRAL6Xr9Jcbd1Z/ZX9Dv7M9AFn15BnWhdPXZg==} os: [win32, darwin, linux] - onnxruntime-web@1.30.0-dev.20260910-f2c39fe2f: - resolution: {integrity: sha512-TDbT5UUl51I5G7H/F16X1kMbMxFdfYblRzNoqd8Qi0nYd50HjYqvKYCIBls9bEuKyw8GfKfAdsE0Q6f5tANyXQ==} + onnxruntime-web@1.31.0-dev.20260911-2a43ec07e: + resolution: {integrity: sha512-TGnajWjoVz3s6K2XPvq02cObANU9rLgYSjOKxa+y1X681kOlIRJi4SvPXd2WLNUEAtVzi6YXhRrUR9E7iHsqGA==} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -3356,20 +3356,20 @@ snapshots: dependencies: mimic-fn: 2.1.0 - onnxruntime-common@1.30.0-dev.20260813-72e1c9c9b8: {} + onnxruntime-common@1.31.0-dev.20260911-2a43ec07e: {} - onnxruntime-node@1.30.0-dev.20260910-f2c39fe2f: + onnxruntime-node@1.31.0-dev.20260911-2a43ec07e: dependencies: adm-zip: 0.6.0 global-agent: 4.1.3 - onnxruntime-common: 1.30.0-dev.20260813-72e1c9c9b8 + onnxruntime-common: 1.31.0-dev.20260911-2a43ec07e - onnxruntime-web@1.30.0-dev.20260910-f2c39fe2f: + onnxruntime-web@1.31.0-dev.20260911-2a43ec07e: dependencies: flatbuffers: 25.9.23 guid-typescript: 1.0.9 long: 5.3.2 - onnxruntime-common: 1.30.0-dev.20260813-72e1c9c9b8 + onnxruntime-common: 1.31.0-dev.20260911-2a43ec07e platform: 1.3.6 protobufjs: 7.6.6 From 1044fde27a6916af8192ef648a89f1bcd8c19254 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 14 Sep 2026 01:54:11 -0400 Subject: [PATCH 100/103] Update pnpm-lock.yaml --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1383d542a..854db0720 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -861,8 +861,8 @@ packages: '@webgpu/types@0.1.69': resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==} - adm-zip@0.6.0: - resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + adm-zip@0.6.1: + resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==} engines: {node: '>=14.0'} ansi-escapes@4.3.2: @@ -2558,7 +2558,7 @@ snapshots: '@webgpu/types@0.1.69': {} - adm-zip@0.6.0: {} + adm-zip@0.6.1: {} ansi-escapes@4.3.2: dependencies: @@ -3360,7 +3360,7 @@ snapshots: onnxruntime-node@1.31.0-dev.20260911-2a43ec07e: dependencies: - adm-zip: 0.6.0 + adm-zip: 0.6.1 global-agent: 4.1.3 onnxruntime-common: 1.31.0-dev.20260911-2a43ec07e From 381caef7e5b5bc26e19f0feee752e2602b798e53 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:21:22 -0400 Subject: [PATCH 101/103] use onnxruntime-node 1.30.0 pin to stable, which includes all built binaries (like CUDA linux x64) --- packages/transformers/package.json | 2 +- pnpm-lock.yaml | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index e1f7ecaf7..f8287f00b 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -57,7 +57,7 @@ "dependencies": { "@huggingface/jinja": "^0.5.10", "@huggingface/tokenizers": "^0.2.0", - "onnxruntime-node": "1.31.0-dev.20260911-2a43ec07e", + "onnxruntime-node": "1.30.0", "onnxruntime-web": "1.31.0-dev.20260911-2a43ec07e", "sharp": "^0.35.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 854db0720..054c3ca36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^0.2.0 version: 0.2.0 onnxruntime-node: - specifier: 1.31.0-dev.20260911-2a43ec07e - version: 1.31.0-dev.20260911-2a43ec07e + specifier: 1.30.0 + version: 1.30.0 onnxruntime-web: specifier: 1.31.0-dev.20260911-2a43ec07e version: 1.31.0-dev.20260911-2a43ec07e @@ -1488,11 +1488,14 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onnxruntime-common@1.30.0: + resolution: {integrity: sha512-7fdVWjAID1dVhH/G8qK3APARunV4VkBFoCQAP7qp4Wkab0mrorvmc+sqiT+mKXOzDqdjN5j+/Z9nb4gzNPWcyA==} + onnxruntime-common@1.31.0-dev.20260911-2a43ec07e: resolution: {integrity: sha512-gBuF6U32YErKIAt+yD7DeGBpjRxhJ1uJwako6ygjokSZULDU6Pd+KWComX8Tumk1hYVHRxjPj7mdMnD0ryAPOw==} - onnxruntime-node@1.31.0-dev.20260911-2a43ec07e: - resolution: {integrity: sha512-VKO86uyrpms41+/rG7gMmQmosX4UE+Uy/1eBtjKrhK3GAuERHLRAL6Xr9Jcbd1Z/ZX9Dv7M9AFn15BnWhdPXZg==} + onnxruntime-node@1.30.0: + resolution: {integrity: sha512-twhs1C2C/BFkz1yc5OY0KIU2GUq6DURO7hD4bx5Q2Qy3nAMJwRXW8xU3NVczE29VA9lolLOYepoD8fjTGOfIqw==} os: [win32, darwin, linux] onnxruntime-web@1.31.0-dev.20260911-2a43ec07e: @@ -3356,13 +3359,15 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onnxruntime-common@1.30.0: {} + onnxruntime-common@1.31.0-dev.20260911-2a43ec07e: {} - onnxruntime-node@1.31.0-dev.20260911-2a43ec07e: + onnxruntime-node@1.30.0: dependencies: adm-zip: 0.6.1 global-agent: 4.1.3 - onnxruntime-common: 1.31.0-dev.20260911-2a43ec07e + onnxruntime-common: 1.30.0 onnxruntime-web@1.31.0-dev.20260911-2a43ec07e: dependencies: From 500805e5ded69d06d1382b3be14dce3e6e3bdbd2 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:26:24 -0400 Subject: [PATCH 102/103] bump onnxruntime-web to latest dev version --- packages/transformers/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/transformers/package.json b/packages/transformers/package.json index f8287f00b..a204694d1 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -58,7 +58,7 @@ "@huggingface/jinja": "^0.5.10", "@huggingface/tokenizers": "^0.2.0", "onnxruntime-node": "1.30.0", - "onnxruntime-web": "1.31.0-dev.20260911-2a43ec07e", + "onnxruntime-web": "1.31.0-dev.20260914-8d85527a0", "sharp": "^0.35.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 054c3ca36..d2628cbd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ importers: specifier: 1.30.0 version: 1.30.0 onnxruntime-web: - specifier: 1.31.0-dev.20260911-2a43ec07e - version: 1.31.0-dev.20260911-2a43ec07e + specifier: 1.31.0-dev.20260914-8d85527a0 + version: 1.31.0-dev.20260914-8d85527a0 sharp: specifier: ^0.35.4 version: 0.35.4(@types/node@24.10.9) @@ -1498,8 +1498,8 @@ packages: resolution: {integrity: sha512-twhs1C2C/BFkz1yc5OY0KIU2GUq6DURO7hD4bx5Q2Qy3nAMJwRXW8xU3NVczE29VA9lolLOYepoD8fjTGOfIqw==} os: [win32, darwin, linux] - onnxruntime-web@1.31.0-dev.20260911-2a43ec07e: - resolution: {integrity: sha512-TGnajWjoVz3s6K2XPvq02cObANU9rLgYSjOKxa+y1X681kOlIRJi4SvPXd2WLNUEAtVzi6YXhRrUR9E7iHsqGA==} + onnxruntime-web@1.31.0-dev.20260914-8d85527a0: + resolution: {integrity: sha512-Iy7rtadoBgxS/LLvDr3QW38DB1PNXRnr0GJMcL0TAt7c9qjgVQl83UlGCVyeAnK2InpmW8Uc3PL8XIuqtDeF6g==} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -3369,7 +3369,7 @@ snapshots: global-agent: 4.1.3 onnxruntime-common: 1.30.0 - onnxruntime-web@1.31.0-dev.20260911-2a43ec07e: + onnxruntime-web@1.31.0-dev.20260914-8d85527a0: dependencies: flatbuffers: 25.9.23 guid-typescript: 1.0.9 From 44892accfdc711bd5db282d38ee2b931a17ba3a0 Mon Sep 17 00:00:00 2001 From: Joshua Lochner <26504141+xenova@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:59:31 -0400 Subject: [PATCH 103/103] throw error for invalid divisor + type combination --- packages/transformers/src/utils/tensor.js | 9 +++++++-- .../transformers/tests/utils/tensor.test.js | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 2e5cd90d6..b1bf8c107 100644 --- a/packages/transformers/src/utils/tensor.js +++ b/packages/transformers/src/utils/tensor.js @@ -339,8 +339,9 @@ export class Tensor { /** * Return a new Tensor with the element-wise remainder of division by a constant. - * Follows Python's modulo semantics, so the result has the same sign as the divisor - * (e.g. `-1 mod 2 = 1`). Equivalent to `torch.remainder`. + * Uses Python-style modulo signs (e.g. `-1 mod 2 = 1`) while preserving the tensor's dtype. + * Negative divisors are unsupported for unsigned and boolean tensors. + * This operation does not implement PyTorch's dtype promotion or scalar casting rules. * @param {number|bigint} val The divisor. * @returns {Tensor} The new tensor. */ @@ -362,6 +363,10 @@ export class Tensor { if ((divisor === 0 || divisor === 0n) && (this.type.includes('int') || this.type === 'bool')) { throw new RangeError('Division by zero'); } + // These dtypes cannot store a negative remainder without wrapping. + if (divisor < 0 && (this.type.startsWith('uint') || this.type === 'bool')) { + throw new RangeError('Negative divisors are not supported for unsigned or boolean tensors'); + } for (let i = 0; i < this_data.length; ++i) { const remainder = this_data[i] % divisor; // Only shift opposite signs: adding a large divisor can round away a valid remainder. diff --git a/packages/transformers/tests/utils/tensor.test.js b/packages/transformers/tests/utils/tensor.test.js index 5e41ca095..21aba266a 100644 --- a/packages/transformers/tests/utils/tensor.test.js +++ b/packages/transformers/tests/utils/tensor.test.js @@ -425,6 +425,26 @@ describe("Tensor operations", () => { }); describe("remainder", () => { + it.each(["uint8", "uint16", "uint32", "uint64", "bool"])("should reject negative divisors for %s without mutating the input", (type) => { + const values = type === "uint64" ? [1n, 0n] : [1, 0]; + const tensor = new Tensor(type, values, [2]); + for (const divisor of [-1, -2, -1n, -2n]) { + expect(() => tensor.remainder(divisor)).toThrow("Negative divisors are not supported"); + expect(Array.from(tensor.data)).toEqual(values); + expect(() => tensor.remainder_(divisor)).toThrow("Negative divisors are not supported"); + expect(Array.from(tensor.data)).toEqual(values); + } + }); + it.each(["uint8", "uint16", "uint32", "uint64", "bool"])("should still support positive divisors for %s", (type) => { + const values = type === "uint64" ? [1n, 0n] : [1, 0]; + for (const divisor of [2, 2n]) { + const tensor = new Tensor(type, values, [2]); + expect(Array.from(tensor.remainder(divisor).data)).toEqual(values); + expect(Array.from(tensor.data)).toEqual(values); + expect(tensor.remainder_(divisor)).toBe(tensor); + expect(Array.from(tensor.data)).toEqual(values); + } + }); it.each(["int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", "bool"])("should reject zero divisors for %s without mutating the input", (type) => { const values = type.endsWith("64") ? [1n, 0n] : [1, 0]; const tensor = new Tensor(type, values, [2]);