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 01/54] 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 02/54] 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 03/54] 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 04/54] 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 05/54] 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 06/54] 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 07/54] 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 08/54] 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 09/54] 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 10/54] 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 11/54] 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 12/54] 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 13/54] 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 14/54] 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 15/54] 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 16/54] 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 17/54] 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 18/54] 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 19/54] 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 20/54] 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 21/54] 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 22/54] 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 23/54] 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 24/54] 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 25/54] 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 26/54] 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 27/54] 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 28/54] 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 29/54] 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 30/54] 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 31/54] 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 32/54] 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 33/54] 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 34/54] 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 35/54] 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 36/54] 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 37/54] 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 38/54] 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 39/54] 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 40/54] 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 41/54] 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 42/54] 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 43/54] 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 44/54] 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 45/54] 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 46/54] 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 47/54] 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 48/54] 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 49/54] 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 50/54] 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 51/54] 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 52/54] 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 53/54] 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 54/54] 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();