Skip to content
Back to skills

Tokenization And Stopwords

ASecurity

"Tokenize bm25s documents and queries with consistent vocabularies,

  • 247 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 8, 2026
businesspythonapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned September 8, 2026

npx -y skills add VectorSpaceLab/AREX-Skill --skill tokenization-and-stopwords --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tokenization And Stopwords?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tokenization And Stopwords
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/vectorspacelab-tokenization-and-stopwords/badge)](https://www.skillsdirectory.com/skills/vectorspacelab-tokenization-and-stopwords)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tokenization-and-stopwords
description: "Tokenize bm25s documents and queries with consistent vocabularies,
  stopwords, stemming, streaming, and tokenizer persistence."
disable-model-invocation: true
metadata:
  disco-role: operating
license: MIT
---
# Tokenization and stopwords

Use this route when the task is to turn text into bm25s token IDs or token strings, keep a corpus/query vocabulary aligned, configure stopwords or stemming, stream a large input, or persist tokenizer state. Keep BM25 indexing, retrieval, index-file persistence, and CLI search in their sibling routes.

## Choose the API first

- Use the module function `bm25s.tokenize` for a self-contained batch. It accepts one string or an iterable of strings and returns a `Tokenized` object by default.
- Use `bm25s.tokenization.Tokenizer` when corpus and query calls must share mutable vocabulary state, when a callable splitter is needed, or when streaming and vocab/stopword persistence are required.
- Do not confuse `return_ids` (module function) with `return_as` (class method). Details and signatures are in [references/api-reference.md](references/api-reference.md).

## Functional batch recipe

```python
import bm25s

corpus_tokens = bm25s.tokenize(
    ["A cat likes to purr", "A dog likes to play"],
    stopwords="en",
    show_progress=False,
)
assert hasattr(corpus_tokens, "ids") and hasattr(corpus_tokens, "vocab")
query_strings = bm25s.tokenize(
    "cat purr", stopwords="en", return_ids=False, show_progress=False
)
```

`return_ids=True` returns `Tokenized(ids=..., vocab=...)`; `return_ids=False` returns list-of-list strings. The module function builds a fresh vocabulary for each call, so IDs from separate calls are not a shared query vocabulary.

## Stateful corpus/query recipe

```python
from bm25s.tokenization import Tokenizer

tok = Tokenizer(
    lower=True,
    splitter=r"(?u)\\b\\w\\w+\\b",  # regex or callable
    stopwords="en",
    stemmer=None,
)
corpus_ids = tok.tokenize(
    ["A cat likes to purr", "A dog likes to play"],
    update_vocab=True,
    return_as="ids",
    show_progress=False,
)
query_ids = tok.tokenize(
    ["cat purr", "unknown term"],
    update_vocab=False,
    return_as="ids",
    show_progress=False,
)
query_text = tok.decode(query_ids)
```

Build the corpus vocabulary first. For ordinary retrieval queries, use `update_vocab=False` so unseen words are omitted rather than changing the indexed vocabulary. Use `update_vocab="never"` when even a known stem must not create a new surface-word mapping; this distinction matters with a stemmer.

## Operational rules

1. Text is lowercased first when `lower=True`, then split, stopword-filtered, and optionally stemmed. Custom stopword entries should normally already be lowercase.
2. IDs are assigned in first-seen order. With no stemmer, `get_vocab_dict()` is `word -> id`; with a stemmer it is `stem -> stem_id`.
3. The class defaults to English stopwords and `allow_empty=True`; its empty-token sentinel is deliberately documented in the API reference because empty behavior differs from the module function.
4. `Tokenizer.tokenize(..., update_vocab="if_empty")` updates only when `word_to_id` is empty. It is the class default. `streaming_tokenize` does not support this mode; choose `True`, `False`, or `"never"` there.
5. `return_as="stream"` is lazy and cannot be decoded until consumed. `length=` is needed when a non-streaming call receives a one-shot generator.
6. A callable class splitter receives one lowercased string and must return an iterable of strings. A class stemmer is called one word at a time. The module function instead calls a callable stemmer once with a list of unique tokens.
7. Persistence saves only tokenizer vocab dictionaries or stopwords. It does not save `lower`, splitter, stemmer, or other construction settings; recreate compatible settings before loading.

See [references/stemming-and-stopwords.md](references/stemming-and-stopwords.md) for language aliases and stemmer contracts, and [references/streaming-and-parallelism.md](references/streaming-and-parallelism.md) for lazy and multiprocessing patterns.

## Empty, unknown, and invalid inputs

- Keep `allow_empty` explicit in production recipes. At this revision, the module and class implement opposite-looking empty defaults: module `bm25s.tokenize(..., allow_empty=True)` leaves an empty document as `[]`, while `allow_empty=False` inserts `""`; the class inserts `""` for an empty document when `allow_empty=True` and has an existing/created sentinel, while `False` yields `[]`.
- With a stateful tokenizer and `update_vocab=False`, an unknown non-stemmed word is omitted. If no known IDs remain, the result is usually `[]`; an existing empty sentinel can make an empty class result `[empty_id]` when `allow_empty=True`.
- Unknown stopword names raise `ValueError`; pass `None`/`False` for no stopwords or pass a list.
- A non-callable class splitter/stemmer raises `ValueError` during construction. The module stemmer must expose `stemWords` or be callable on a token list; see the exact contracts in the references.

## Persist and reload safely

```python
from pathlib import Path

tok.save_vocab("tokenizer-state")
tok.save_stopwords("tokenizer-state")
# Recreate the same lower/splitter/stemmer configuration, then:
restored.load_vocab("tokenizer-state")
restored.load_stopwords("tokenizer-state")
```

Use the defaults `vocab.tokenizer.json` and `stopwords.tokenizer.json`, or explicit unique names. Never pass the BM25 index's `vocab.index.json` as `vocab_name`: `save_vocab` has no collision guard and can overwrite it. Run the bounded local check at [scripts/tokenizer_persistence_smoke.py](scripts/tokenizer_persistence_smoke.py) to verify a compatible state round trip without downloading a dataset.

## Handoff checklist

Before handing token IDs to a retrieval workflow, record the tokenizer settings, confirm corpus and query use the same `get_vocab_dict()`, and choose `allow_empty` deliberately. Prefer `Tokenized`/`to_tokenized_tuple` when the vocabulary must travel with IDs. Use [references/troubleshooting.md](references/troubleshooting.md) for failures involving optional PyStemmer, malformed callable outputs, one-shot streams, or persistence files.

Do not download BEIR/NQ data merely to validate tokenization. Use a tiny local fixture and `show_progress=False`; benchmark-scale multiprocessing is an opt-in adaptation described in the streaming reference.

Files in this skill

  • SKILL.md6.3 KB
  • references/api-reference.md7.2 KB
  • references/stemming-and-stopwords.md5.1 KB
  • references/streaming-and-parallelism.md5.3 KB
  • references/troubleshooting.md5.9 KB
  • scripts/tokenizer_persistence_smoke.py2.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.