diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md new file mode 100644 index 000000000..6bf789c88 --- /dev/null +++ b/.ai/AGENTS.md @@ -0,0 +1,56 @@ +# 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-generate +``` + +## Documentation generation + +Run the full documentation generator after any JSDoc, docs snippet, task metadata, +or generated skill content change: + +```bash +pnpm --filter @huggingface/transformers docs-generate +``` + +`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`. +- 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. + +The generator also validates generated API pages against `docs/source/_toctree.yml` +and checks local Markdown links and anchors under `docs/source/`. diff --git a/.ai/skills/transformers-js/SKILL.md b/.ai/skills/transformers-js/SKILL.md new file mode 100644 index 000000000..580fab8d5 --- /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 + + +- [`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` + + +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: + + +Filter by task with the `pipeline_tag` parameter, e.g. +. + +### 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: +- API reference: +- Examples repo: + +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..2734aff3c --- /dev/null +++ b/.ai/skills/transformers-js/references/CONFIGURATION.md @@ -0,0 +1,166 @@ +# 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 + + +**Example:** Load models from your own server and disable remote downloads. + +```javascript +import { env } from '@huggingface/transformers'; +env.allowRemoteModels = false; +env.localModelPath = '/path/to/local/models/'; +``` + +**Example:** Point the filesystem cache at a custom directory (Node.js). + +```javascript +import { env } from '@huggingface/transformers'; +env.cacheDir = '/path/to/cache/directory/'; +``` + + +## All options + + +| 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` | The fetch function to use. Defaults to `fetch`. | + + +## 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 }); +``` + + +Static class for cache and file management operations. + +**Methods** + +- `get_files(modelId, [options])` → `Promise` — Get all files (model, tokenizer, processor) needed for a model. +- `get_pipeline_files(task, modelId, [options])` → `Promise` — Get all files needed for a specific pipeline task. +- `get_model_files(modelId, [options])` → `Promise` — Get model files needed for a specific model. +- `get_tokenizer_files(modelId)` → `Promise` — Get tokenizer files needed for a specific model. +- `get_processor_files(modelId)` → `Promise` — Get processor files needed for a specific model. +- `get_available_dtypes(modelId, [options])` → `Promise` — 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` — 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` — Checks if all files for a given model are already cached, with per-file detail. +- `is_pipeline_cached(task, modelId, [options])` → `Promise` — 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` — 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` — Clears all cached files for a given model. +- `clear_pipeline_cache(task, modelId, [options])` → `Promise` — Clears all cached files for a specific pipeline task. + + +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..4cf72812b --- /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 + + +| 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` | The device to run the model on. If not specified, the device will be chosen from the environment settings. _(default: `null`)_ | +| `dtype`? | `DataType\|Record` | 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` | 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. | + + +## 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 + + +**`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. | + + +## 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. + + +| 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: `{}`)_ | + + +### 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..2c71a4ae5 --- /dev/null +++ b/.ai/skills/transformers-js/references/TASKS.md @@ -0,0 +1,929 @@ + + +# 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`. + +**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()); +``` 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/.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 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 19e8eca4b..57e4cb0cc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@


- + transformers.js javascript library logo @@ -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/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/build_readme.js b/packages/transformers/docs/scripts/build_readme.js new file mode 100644 index 000000000..29c9ec63c --- /dev/null +++ b/packages/transformers/docs/scripts/build_readme.js @@ -0,0 +1,122 @@ +import fs from "node:fs"; +import path from "node:path"; +import url from "node:url"; + +import { DOCS_BASE_URL, buildApiSymbolLinks } from "./lib/api-links.mjs"; +import { loadProject } from "./lib/load.mjs"; + +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", + 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", +}; + +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", + "/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 { 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")])); + + snippets.customUsage = demoteHeadings(snippets.customUsage); + 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 }) { + return ` + +

+
+ + + + transformers.js javascript library logo + +
+

+ +

+ NPM + NPM Downloads + jsDelivr Hits + License + Documentation +

+ +${intro} + +## Installation + +${installation} + +## Quick tour + +${quickTour} + +## Custom usage + +${customUsage} + +## Supported tasks/models + +Here is the list of all tasks and architectures currently supported by Transformers.js. If you don't see your task/model listed here or it is not yet supported, feel free to open up a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). + +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} +`; +} + +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. + 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}`; + } + return `(${link})`; + }); +} + +main(); diff --git a/packages/transformers/docs/scripts/build_readme.py b/packages/transformers/docs/scripts/build_readme.py deleted file mode 100644 index 033e79bb5..000000000 --- a/packages/transformers/docs/scripts/build_readme.py +++ /dev/null @@ -1,108 +0,0 @@ - -import re -README_TEMPLATE = """ - -

-
- - - - transformers.js javascript library logo - -
-

- -

- NPM - NPM Downloads - jsDelivr Hits - License - Documentation -

- -{intro} - -## Installation - -{installation} - -## Quick tour - -{quick_tour} - -## Custom usage - -{custom_usage} - -## Supported tasks/models - -Here is the list of all tasks and architectures currently supported by Transformers.js. If you don't see your task/model listed here or it is not yet supported, feel free to open up a feature request [here](https://github.com/huggingface/transformers.js/issues/new/choose). - -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' - -# 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', -} - - -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() 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.js b/packages/transformers/docs/scripts/generate.js deleted file mode 100644 index f651032a1..000000000 --- a/packages/transformers/docs/scripts/generate.js +++ /dev/null @@ -1,79 +0,0 @@ -// Based on [this tutorial](https://github.com/jsdoc2md/jsdoc-to-markdown/wiki/How-to-create-one-output-file-per-class). - -import fs from "node:fs"; -import path from "node:path"; -import url from "node:url"; - -import jsdoc2md from "jsdoc-to-markdown"; - -const docs = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); -const root = path.dirname(docs); - -// jsdoc config file -const conf = path.join(docs, "jsdoc-conf.json"); - -// input and output paths -const inputFile = path.join(root, "/src/**/*.js"); -const outputDir = path.join(root, "/docs/source/api/"); - -// get template data -const templateData = await jsdoc2md.getTemplateData({ - files: inputFile, - configure: conf, - "no-cache": true, -}); - -// reduce templateData to an array of module names -const moduleNames = templateData.reduce((moduleNames, identifier) => { - if (identifier.kind === "module") { - moduleNames.push(identifier.name); - } - return moduleNames; -}, []); - -// 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)); - } - } -} - -// 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); -} 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/exports.mjs b/packages/transformers/docs/scripts/lib/exports.mjs new file mode 100644 index 000000000..c0492514a --- /dev/null +++ b/packages/transformers/docs/scripts/lib/exports.mjs @@ -0,0 +1,100 @@ +// 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"; +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); + return names; +} + +function walk(file, visited, names) { + if (visited.has(file) || !fs.existsSync(file)) return; + visited.add(file); + + const source = fs.readFileSync(file, "utf8"); + const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const dir = path.dirname(file); + + 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; + } + + 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 hasExportModifier(node) { + return !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword); +} 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/ir.mjs b/packages/transformers/docs/scripts/lib/ir.mjs new file mode 100644 index 000000000..225b5a0d6 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/ir.mjs @@ -0,0 +1,592 @@ +// Group per-file entities (from structure.mjs) into per-module IR. A module is +// one `@module ` declaration — its output is `docs/api/.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 { 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) }; +} + +// 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"); + const raw = fileTag?.description || entities.module.description || ""; + const { description, examples } = gatherExamples(raw); + return { name: moduleTag.name, description, examples }; +} + +function newModule(name) { + return { + name, + description: "", + examples: [], + classes: [], + functions: [], + 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); + } + for (const cls of entities.classes) { + if (isPrivate(cls)) continue; + const { description, examples } = gatherExamples(cls.description); + mod.classes.push({ + name: cls.name, + description, + 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) { + if (!isPrivate(fn)) mod.functions.push(buildCallable(fn)); + } + 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, + type: typeOf(v), + description, + examples, + }); + } +} + +// 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(); + const callable = parseCallableTypedef({ + name: tag.name, + type: tag.type, + description: tag.description || (first ? block.description : ""), + }); + 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(); + 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) { + const { description, examples } = gatherExamples(fn.description); + return { + name: fn.name, + 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 + // 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"), + }; +} + +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); + + 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)) { + callable.params = resolveUtilityParams(callable.params ?? [], callableIndex); + 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) { + 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); + } + } + } + return index; +} + +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; + } + } + } +} + +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 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; +} + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +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: cleanParamDescription(tag.description || ""), + }; +} + +function cleanParamDescription(description) { + return description.replace(/^\([^)]*\boptional\b[^)]*\):\s*/i, "").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) { + return entity.tags?.some((t) => t.tag === "private" || t.tag === "internal"); +} 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/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/parse.mjs b/packages/transformers/docs/scripts/lib/parse.mjs new file mode 100644 index 000000000..c978665b4 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/parse.mjs @@ -0,0 +1,156 @@ +// 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]+/, ""); + + // `@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("{") && tag !== "default") { + 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": { + // `@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": + 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/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/render-api.mjs b/packages/transformers/docs/scripts/lib/render-api.mjs new file mode 100644 index 000000000..cf8497167 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/render-api.mjs @@ -0,0 +1,581 @@ +// 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 + +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 = {}) { + const publicNames = opts.publicNames ?? null; + const ctx = { + typedefIndex: ir.typedefIndex, + moduleName: mod.name, + renderedNames: buildRenderedNameIndex(ir, publicNames), + callableLinks: buildCallableLinkIndex(ir, publicNames), + }; + + 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"), ctx) + .replace(/[ \t]+$/gm, "") + .replace(/\n{3,}/g, "\n\n") + .trimEnd() + "\n" + ); +} + +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 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: 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: apiMemberAnchor(mod.name, cls.name, m.name) }); + } + } + } + } + return links; +} + +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; +} + +// 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*/, "") + .replace(/@see\s+(?=\{@link)/g, "") + .replace(/@see\s+`?([A-Za-z_$][\w$.]*)`?/g, "`$1`"); +} + +// `{@link url}` / `{@link url Text}` / `{@link Symbol}` -> markdown. +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 ---------- + +function renderClass(cls, ctx) { + const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, cls.name)}"></a>`, "", `### ${cls.name}`, ""]; + if (cls.description) lines.push(cleanDescription(cls.description), ""); + for (const ex of cls.examples) lines.push(...renderExample(ex)); + 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") { + 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) { + 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 []; + + 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(cleanDescription(f.description), ""); + if (f.defaultValue != null && f.defaultValue !== "") lines.push(`**Default:** \`${f.defaultValue}\``, ""); + return lines; +} + +// `_`-prefixed methods are the library's convention for internal / subclass-only +// hooks — they're not part of the user-facing API. Exception: `constructor` is +// kept even though it has no leading underscore. +function shouldRenderMethod(m) { + 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 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`. + 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, fnCtx), ""); + } + if (fn.returns?.type || fn.returns?.description) { + 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**", ""); + 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.displayName ?? fn.name}(${params})\``; +} + +function renderCallback(cb, ctx) { + const lines = [`<a id="${apiSymbolAnchor(ctx.moduleName, cb.name)}"></a>`, "", `### ${cb.name}`, ""]; + const templateMap = new Map(); + for (const t of cb.templates ?? []) if (t.name && t.type) templateMap.set(t.name, t.type); + const cbCtx = templateMap.size ? { ...ctx, templates: templateMap } : ctx; + + if (cb.description) lines.push(cleanDescription(cb.description), ""); + if (cb.params?.length) { + lines.push("**Parameters**", "", ...renderParamList(cb.params, cbCtx), ""); + } + if (cb.returns?.type || cb.returns?.description) { + const type = cb.returns.type ? renderType(cb.returns.type, cbCtx) : ""; + const desc = cb.returns.description ? ` — ${cb.returns.description}` : ""; + lines.push(`**Returns:** ${type}${desc}`, ""); + } + return lines; +} + +// ---------- parameter lists ---------- + +// Nested params (`foo.bar`) fold under their parent as sub-bullets. +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 = [`<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; +} + +// 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(); + // `@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) { + if (!shouldRenderTypedef(td, ctx)) return []; + + const { displayed, typeIsShowable } = typedefRenderInfo(td, ctx); + + 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}`, ""); + } + 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); + // Collapsed fallbacks (`object`, `unknown`, `any`) carry no real information + // — showing `_Type:_ `object`` adds noise without helping the reader. + const isOpaque = ["`object`", "`Object`", "`unknown`", "`any`"].includes(displayed); + // Unions and intersections are worth showing even when long — every variant + // is a named type the reader can click through to. Opaque inline object + // literals (`{...}`) stay hidden. + const isUnionOrIntersection = / \| | & /.test(displayed); + const fitsInline = displayed.length < 120; + const typeIsShowable = + displayed && + !td.properties?.length && + (isUnionOrIntersection || fitsInline) && + !displayed.startsWith("`{") && + !isSelfReference && + !isGenericPassthrough && + !isOpaque; + + return { displayed, typeIsShowable }; +} + +// ---------- 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$.]*)\[[^\]]+\]$/; +const TUPLE = /^\[(.*)\]$/; + +// Turn a raw JSDoc type string into readable markdown. Prefers author-written +// names over expanded TS structures; unknown gnarly types become `object`. +export function renderType(raw, ctx, opts = {}) { + 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, "|") + .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 (pretty.endsWith("[]")) { + 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 }); + if (isGenericParamName(pretty)) return "`any`"; + 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}>`; + } + + 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 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); +} + +function linkKnownName(name, label, ctx) { + const moduleName = ctx.typedefIndex?.get(name); + if (!moduleName || name === ctx.selfName || !ctx.renderedNames?.has(name)) return null; + const anchor = apiSymbolAnchor(moduleName, name); + 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 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) { + 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 linkCallable(ref.owner, ref.owner, ctx) ?? linkIfKnown(ref.owner, ctx) ?? `\`${ref.owner}\``; +} + +// Split `text` on `sep`, ignoring separators inside brackets, braces, parens, +// angle brackets, or string literals. +function splitTopLevel(text, sep) { + const out = []; + let depth = 0; + let inStr = null; + let buf = ""; + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + if (inStr) { + if (ch === inStr && text[i - 1] !== "\\") inStr = null; + buf += ch; + } else if (ch === '"' || ch === "'" || ch === "`") { + inStr = ch; + buf += ch; + } else if ("<({[".includes(ch)) { + depth++; + buf += ch; + } else if (">)}]".includes(ch)) { + depth--; + buf += ch; + } else if (depth === 0 && ch === sep) { + out.push(buf); + buf = ""; + } else { + buf += ch; + } + } + if (buf) out.push(buf); + return out.length > 1 ? out : [text]; +} + +// Strip noisy TS constructs from a type string without rewriting structure. +// Conditional/mapped/infer types collapse to their outermost wrapper; simple +// unions of names or long generic lists are preserved so the renderer can +// split them into individual links. +export function prettifyTypeString(raw) { + if (!raw) return ""; + let s = raw.trim(); + + s = stripLeading(s, "<", ">"); // <T extends X>(...) + s = s.replace(/import\(['"][^'"]+['"]\)\.([A-Za-z_$][\w$.]*)/g, "$1"); + s = s.replace(/import\(['"][^'"]+['"]\)/g, "any"); + s = s.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, "'$1'"); + + 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 + // 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(); +} + +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. 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++; + 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/render-skill.mjs b/packages/transformers/docs/scripts/lib/render-skill.mjs new file mode 100644 index 000000000..5830842bc --- /dev/null +++ b/packages/transformers/docs/scripts/lib/render-skill.mjs @@ -0,0 +1,428 @@ +// 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-all.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 }; + + // Rewrite fully-generated reference files. + const refDir = path.join(skillDir, "references"); + fs.mkdirSync(refDir, { recursive: true }); + fs.writeFileSync(path.join(refDir, "TASKS.md"), absolutize(renderTasks(ctx))); + + // Expand `<!-- @generated:start id=... -->` markers in every hand-written + // markdown file under the skill directory. Prose outside markers is preserved. + // Only the generated blocks get absolutized — hand-authored prose is left alone. + 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); + } +} + +// 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 })) { + 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${absolutize(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}\`](references/TASKS.md#${taskAnchor(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, { 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} |`); + } + 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 ? ` — ${firstSentence(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} — ${firstSentence(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, { table = false } = {}) { + 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"; + } + const escaped = compact.replace(table ? /[`|]/g : /`/g, (ch) => `\\${ch}`); + return "`" + escaped + "`"; +} + +// 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(); +} + +// 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, + "", + "# 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", + "", + ]; + 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 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); + } + for (const [name, ids] of [...groups]) if (!ids.length) groups.delete(name); + return groups; +} + +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; +} + +// Extract the first complete sentence. A line break inside a paragraph is not +// a sentence boundary, so we re-flow a paragraph onto one line before cutting. +function firstSentence(text) { + if (!text) return "_(undocumented)_"; + const paragraph = text + .split(/\n\s*\n/, 1)[0] + .replace(/\s+/g, " ") + .trim(); + const sanitized = stripDocArtifacts(paragraph); + const match = sanitized.match(/^(.+?[.!?])(?=\s|$)/); + const sentence = (match ? match[1] : sanitized).trim(); + return sentence.endsWith(".") || sentence.endsWith("!") || sentence.endsWith("?") ? sentence : sentence + "."; +} + +// Descriptions copied from the Python library sometimes start with +// `[`TypeName`]` (reST cross-reference syntax). Drop the leading artifact. +function stripDocArtifacts(text) { + return text.replace(/^\[`?[A-Za-z_$][\w$.]*`?\]\s*/, ""); +} diff --git a/packages/transformers/docs/scripts/lib/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/docs/scripts/lib/tasks.mjs b/packages/transformers/docs/scripts/lib/tasks.mjs new file mode 100644 index 000000000..c465179be --- /dev/null +++ b/packages/transformers/docs/scripts/lib/tasks.mjs @@ -0,0 +1,59 @@ +// 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"; + +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); + + 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 }; +} + +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))); + } +} 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..46d8390f2 --- /dev/null +++ b/packages/transformers/docs/scripts/lib/type-refs.mjs @@ -0,0 +1,65 @@ +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; +} diff --git a/packages/transformers/docs/scripts/lib/validate.mjs b/packages/transformers/docs/scripts/lib/validate.mjs new file mode 100644 index 000000000..fbd15c17c --- /dev/null +++ b/packages/transformers/docs/scripts/lib/validate.mjs @@ -0,0 +1,169 @@ +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 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 && brokenLinks.length === 0, + unlisted, + stale, + brokenLinks, + }; +} + +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}`); + } + 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"); +} + +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)); +} + +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/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/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/_toctree.yml b/packages/transformers/docs/source/_toctree.yml index 76d779249..13fde33fa 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 @@ -84,12 +82,12 @@ title: Image - local: api/utils/audio title: Audio + - local: api/utils/video + title: Video - local: api/utils/tensor title: Tensor - local: api/utils/maths title: Maths - - local: api/utils/logger - title: Logger - local: api/utils/random title: Random title: Utilities diff --git a/packages/transformers/docs/source/guides/dtypes.md b/packages/transformers/docs/source/guides/dtypes.md index b3e24727c..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%;"> @@ -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"; @@ -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,20 +56,26 @@ 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 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..964049268 100644 --- a/packages/transformers/docs/source/guides/webgpu.md +++ b/packages/transformers/docs/source/guides/webgpu.md @@ -2,20 +2,20 @@ 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. +> [!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: > > - 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/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/pipelines.md b/packages/transformers/docs/source/pipelines.md index 024f51c21..227d030f3 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( @@ -110,14 +106,12 @@ 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. -### 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( @@ -140,12 +134,10 @@ 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: -<!-- TODO: Replace 'Xenova/LaMini-Flan-T5-783M' with 'MBZUAI/LaMini-Flan-T5-783M' --> - ```javascript // Create a pipeline for text2text-generation const poet = await pipeline( 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 diff --git a/packages/transformers/docs/source/tutorials/vanilla-js.md b/packages/transformers/docs/source/tutorials/vanilla-js.md index 049d085a3..3d96acebf 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> @@ -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> diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 8ea694b11..4d5d7d011 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -28,8 +28,8 @@ "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", - "docs-api": "node ./docs/scripts/generate.js", + "readme": "node ./docs/scripts/build_readme.js --out ../../README.md", + "docs-generate": "node ./docs/scripts/generate-all.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" }, @@ -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", @@ -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/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: diff --git a/packages/transformers/src/cache_utils.js b/packages/transformers/src/cache_utils.js index 4fb16992c..bfe356ab3 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'; /** @@ -74,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 516a3df71..5d96c461c 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,10 +505,14 @@ 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`. + * + * ```javascript + * import { AutoConfig } from '@huggingface/transformers'; * - * @example * 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..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,22 @@ 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({ /** All messages including debug output (value: 10) */ @@ -201,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, @@ -233,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/feature_extraction_utils.js b/packages/transformers/src/feature_extraction_utils.js index eed106497..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); @@ -47,7 +51,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/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 647a30806..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 */ @@ -557,6 +564,9 @@ export class MinNewTokensLengthLogitsProcessor extends LogitsProcessor { } } +/** + * LogitsProcessor that enforces that specified sequences will never be selected. + */ export class NoBadWordsLogitsProcessor extends LogitsProcessor { /** * Create a `NoBadWordsLogitsProcessor`. diff --git a/packages/transformers/src/generation/stopping_criteria.js b/packages/transformers/src/generation/stopping_criteria.js index 6451f6cd1..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 */ @@ -25,6 +32,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 { /** diff --git a/packages/transformers/src/generation/streamers.js b/packages/transformers/src/generation/streamers.js index 813dd959b..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 */ @@ -15,6 +31,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 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/feature_extraction_auto.js b/packages/transformers/src/models/auto/feature_extraction_auto.js index 476596257..2ff8d7d5c 100644 --- a/packages/transformers/src/models/auto/feature_extraction_auto.js +++ b/packages/transformers/src/models/auto/feature_extraction_auto.js @@ -1,8 +1,25 @@ +/** + * @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'; import * as AllFeatureExtractors from '../feature_extractors.js'; +/** + * 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. + * + * ```javascript + * import { AutoFeatureExtractor, load_audio } from '@huggingface/transformers'; + * + * const extractor = await AutoFeatureExtractor.from_pretrained('onnx-community/whisper-tiny.en'); + * const audio = await load_audio('https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav', 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 820dfacc5..07d451aa0 100644 --- a/packages/transformers/src/models/auto/image_processing_auto.js +++ b/packages/transformers/src/models/auto/image_processing_auto.js @@ -1,9 +1,25 @@ +/** + * @module processors + */ + import { getModelJSON } from '../../utils/hub.js'; import { ImageProcessor } from '../../image_processors_utils.js'; import * as AllImageProcessors from '../image_processors.js'; import { GITHUB_ISSUE_URL, IMAGE_PROCESSOR_NAME } from '../../utils/constants.js'; import { logger } from '../../utils/logger.js'; +/** + * Loads an image processor from a pretrained id. The concrete class is + * selected from the `image_processor_type` in `preprocessor_config.json`. + * + * ```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} */ static async from_pretrained(pretrained_model_name_or_path, options = {}) { diff --git a/packages/transformers/src/models/auto/modeling_auto.js b/packages/transformers/src/models/auto/modeling_auto.js index 030c51acd..c890b0864 100644 --- a/packages/transformers/src/models/auto/modeling_auto.js +++ b/packages/transformers/src/models/auto/modeling_auto.js @@ -139,13 +139,19 @@ 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<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; @@ -153,10 +159,13 @@ 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]; @@ -164,10 +173,13 @@ 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]; @@ -175,10 +187,13 @@ 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]; @@ -186,10 +201,13 @@ 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 - * const model = await AutoModelForSpeechSeq2Seq.from_pretrained('openai/whisper-tiny.en'); + * **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]; @@ -197,10 +215,13 @@ 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 - * const model = await AutoModelForTextToSpectrogram.from_pretrained('microsoft/speecht5_tts'); + * **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]; @@ -208,10 +229,13 @@ 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 - * const model = await AutoModelForTextToSpectrogram.from_pretrained('facebook/mms-tts-eng'); + * **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]; @@ -219,10 +243,13 @@ 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]; @@ -230,10 +257,13 @@ 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]; @@ -241,10 +271,13 @@ 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]; @@ -252,10 +285,13 @@ 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]; @@ -263,10 +299,13 @@ 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]; @@ -274,10 +313,13 @@ 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]; @@ -285,10 +327,13 @@ 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 - * const model = await AutoModelForSemanticSegmentation.from_pretrained('nvidia/segformer-b3-finetuned-cityscapes-1024-1024'); + * **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]; @@ -296,10 +341,13 @@ 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 - * const model = await AutoModelForUniversalSegmentation.from_pretrained('hf-internal-testing/tiny-random-MaskFormerForInstanceSegmentation'); + * **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]; @@ -307,78 +355,226 @@ 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]; } +/** + * Helper class which is used to instantiate pretrained zero-shot object detection models with the `from_pretrained` function. + * + * **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]; } /** * 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 - * const model = await AutoModelForMaskGeneration.from_pretrained('Xenova/sam-vit-base'); + * **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]; } +/** + * Helper class which is used to instantiate pretrained connectionist temporal classification (CTC) models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained audio classification models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained speaker embedding models (X-Vector) with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained audio frame (token) classification models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained document question answering models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained image matting models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained image-to-image models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained depth estimation models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained surface-normal estimation models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained pose estimation models with the `from_pretrained` function. + * + * **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]; } +/** + * Helper class which is used to instantiate pretrained image feature extraction models with the `from_pretrained` function. + * + * **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]; } +/** + * 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. + * + * **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]; } +/** + * 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. + * + * **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/models/auto/processing_auto.js b/packages/transformers/src/models/auto/processing_auto.js index c0f444e97..60ded9141 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'; @@ -11,33 +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 - * let processor = await AutoProcessor.from_pretrained('openai/whisper-tiny.en'); + * import { AutoProcessor } from '@huggingface/transformers'; + * 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 - * 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_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 ] - * // ] - * // } + * import { AutoProcessor, load_image } from '@huggingface/transformers'; + * + * 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/models/auto/tokenization_auto.js b/packages/transformers/src/models/auto/tokenization_auto.js index 62f3c613e..80670a722 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'; @@ -36,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/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/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/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/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/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/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]?.(); 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/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/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..73d3e9fba 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'; @@ -50,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`. * * **Example:** Text generation with `HuggingFaceTB/SmolLM2-135M` (default settings). * ```javascript diff --git a/packages/transformers/src/pipelines/text-to-audio.js b/packages/transformers/src/pipelines/text-to-audio.js index 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..8512dc3fd 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'; @@ -36,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'; * 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'; /** diff --git a/packages/transformers/src/processing_utils.js b/packages/transformers/src/processing_utils.js index b28be3e6a..edb94d74e 100644 --- a/packages/transformers/src/processing_utils.js +++ b/packages/transformers/src/processing_utils.js @@ -1,12 +1,20 @@ /** - * @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, 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 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 { * // data: Float32Array(240000) [0.4752984642982483, 0.5597258806228638, 0.56434166431427, ...], @@ -29,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']; @@ -37,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(); @@ -71,6 +80,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 +97,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 +109,7 @@ export class Processor extends Callable { } /** + * Decode a single tokenized sequence via the underlying tokenizer. * @param {Parameters<PreTrainedTokenizer['decode']>} args * @returns {ReturnType<PreTrainedTokenizer['decode']>} */ @@ -136,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([ diff --git a/packages/transformers/src/tokenization_utils.js b/packages/transformers/src/tokenization_utils.js index 29de6f186..8c5b11b5c 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. @@ -208,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. @@ -228,6 +244,9 @@ function getSpecialTokens(tokenizer) { * @typedef {TTokenize extends false ? string : TReturnDict extends false ? BatchEncodingItem<string, TReturnTensor> : BatchEncoding<BatchEncodingItem<string, TReturnTensor>>} ApplyChatTemplateReturn */ +/** + * `PreTrainedTokenizer` is the base class for all tokenizers in Transformers.js. + */ export class PreTrainedTokenizer extends /** @type {new (tokenizerJSON: Object, tokenizerConfig: Object) => PreTrainedTokenizerCallback} */ ( Callable @@ -710,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( diff --git a/packages/transformers/src/transformers.js b/packages/transformers/src/transformers.js index ef01569ef..47099b1b3 100644 --- a/packages/transformers/src/transformers.js +++ b/packages/transformers/src/transformers.js @@ -1,13 +1,36 @@ /** - * @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 Public API of `@huggingface/transformers`. Everything re-exported from + * this file is considered stable — other imports are internal and may change. * - * 1. [Environment variables](./env) - * 2. [Pipelines](./pipelines) - * 3. [Models](./models) - * 4. [Tokenizers](./tokenizers) - * 5. [Processors](./processors) - * 6. [Configs](./configs) + * **Start here** + * - [`pipeline()`](./pipelines.md#module_pipelines.pipeline) — the one-call entry point for every task. + * - [Environment](./env.md) — `env` fields and `LogLevel` enum. + * + * **Model loading** + * - [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) — 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. + * + * **Data types and I/O** + * - [Tensors](./utils/tensor.md) — `Tensor`, shape ops, math, I/O. + * - [Images](./utils/image.md) — `RawImage`, `load_image()`. + * - [Audio](./utils/audio.md) — `RawAudio`, `load_audio()`. + * - [Video](./utils/video.md) — `RawVideo`, `RawVideoFrame`, `load_video()` _(experimental)_. + * + * **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 */ @@ -45,6 +68,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'; diff --git a/packages/transformers/src/utils/audio.js b/packages/transformers/src/utils/audio.js index c2af80753..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; @@ -824,6 +825,18 @@ function writeString(view, offset, string) { } } +/** + * An audio buffer paired with its sampling rate. + * + * **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 { /** * Create a new `RawAudio` object. 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 d7d8c7fcd..82318df15 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 */ @@ -71,6 +72,16 @@ const CONTENT_TYPE_MAP = new Map([ ['gif', 'image/gif'], ]); +/** + * Represents an image stored as a raw pixel buffer. + * + * **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 { /** * Create a new `RawImage` object. @@ -192,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 { @@ -813,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); 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/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/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<string[]>} 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<string[]>} 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<string[]>} 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<string[]>} 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<string[]>} 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<string[]>} 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<string, import('../devices.js').DeviceType>} [options.device=null] - Override device * @returns {Promise<boolean>} 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<string, import('../devices.js').DeviceType>} [options.device=null] - Override device * @returns {Promise<import('./is_cached.js').CacheCheckResult>} 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<string, import('../devices.js').DeviceType>} [options.device=null] - Override device * @returns {Promise<boolean>} 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<string, import('../devices.js').DeviceType>} [options.device=null] - Override device * @returns {Promise<import('./is_cached.js').CacheCheckResult>} 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<import('./clear_cache.js').CacheClearResult>} 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<string, import('../devices.js').DeviceType>} [options.device] - Override device * @returns {Promise<import('./clear_cache.js').CacheClearResult>} 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); diff --git a/packages/transformers/src/utils/random.js b/packages/transformers/src/utils/random.js index 76319beae..fbeca3166 100644 --- a/packages/transformers/src/utils/random.js +++ b/packages/transformers/src/utils/random.js @@ -36,10 +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 - * const rng1 = new Random(42); - * const rng2 = new Random(42); + * **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) { @@ -105,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; @@ -210,8 +215,22 @@ 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:** + * ```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, seed: _default.seed.bind(_default), diff --git a/packages/transformers/src/utils/tensor.js b/packages/transformers/src/utils/tensor.js index 335cd25eb..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 */ @@ -22,6 +24,17 @@ import { random } from './random.js'; * @typedef {import('./maths.js').AnyTypedArray | any[]} DataArray */ +/** + * A typed multi-dimensional array. + * + * **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 { /** * Dimensions of the tensor. @@ -136,6 +149,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 +164,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 @@ -944,13 +943,6 @@ export class Tensor { /** * This creates a nested array of a given type and depth (see examples). - * - * @example - * NestArray<string, 1>; // string[] - * @example - * NestArray<number, 2>; // number[][] - * @example - * NestArray<string, 3>; // string[][][] etc. * @template T * @template {number} Depth * @template {never[]} [Acc=[]] @@ -960,11 +952,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 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.'); } 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` diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bea34792e..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 @@ -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 @@ -390,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': @@ -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'} @@ -2331,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': {} @@ -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: {}