Skip to main content

Robust, resumable LLM dataset annotation

CI codecov PyPI version Python versions License

llm-annotator is a Python 3.12+ library that runs an LLM over a dataset and writes the answers back as columns. A run is resumable: every finished sample is appended to a JSONL progress file, so a crashed, preempted or timed-out job continues where it stopped.

Four providers share one interface:

  • vLLM in-process (VLLMOfflineClient, config name vllm_offline)
  • vLLM server (VLLMOnlineClient, config name vllm_online)
  • OpenAI (OpenAIClient, config name openai)
  • Anthropic (ClaudeClient, config name claude)

Install

uv add llm-annotator

or

pip install llm-annotator

A provider needs its extra:

uv add "llm-annotator[vllm]"       # vllm_offline
uv add "llm-annotator[openai]"     # openai and vllm_online
uv add "llm-annotator[anthropic]"  # claude

The online vLLM client speaks the OpenAI protocol, so it takes the openai extra rather than the much heavier vllm one. Authentication variables and per-provider notes are in docs/provider-info.md, which also has the two FlashInfer wheels to install next to the vllm extra wherever you serve models, so that vLLM does not JIT-compile its kernels at start-up.

Quickstart

Describe the run in one YAML (or JSON) file and start it. No Python needed.

output_dir: outputs/imdb-sentiment

dataset:
  name: stanfordnlp/imdb
  split: test
  max_num_samples: 20

client:
  provider: vllm_offline
  model: HuggingFaceTB/SmolLM2-135M-Instruct

steps:
  - name: sentiment
    prompt: "Classify the sentiment: {text}"
llm-annotate my-pipeline.yaml

The result is a dataset with a sentiment_response column next to the original text, written to outputs/imdb-sentiment/final/.

A config can hold several steps that run in order, each annotating what the previous one produced, which is what a generate-then-judge workflow needs. examples/pipeline-qa/ is a complete two-step example and docs/pipeline.md is the key reference.

The same run from Python:

from llm_annotator import Annotator, VLLMOfflineClient

client = VLLMOfflineClient(model="HuggingFaceTB/SmolLM2-135M-Instruct")

with Annotator(client=client, verbose=True) as anno:
    ds = anno.annotate_dataset(
        output_dir="outputs/imdb-sentiment",
        prompt_template="Classify the sentiment: {text}",
        dataset_name="stanfordnlp/imdb",
        dataset_split="test",
        max_num_samples=20,
    )

Or build a dataset from scratch instead of annotating one:

from llm_annotator import Annotator, OpenAIClient

client = OpenAIClient(model="gpt-4o-mini")

with Annotator(client=client) as anno:
    ds = anno.generate_dataset(
        output_dir="outputs/generated-qa",
        prompts="Write a short geography quiz question with answer.",
        max_num_samples=200,
    )

What it does

  • No-code config runs: prompts, schemas, model, dataset and any number of chained annotation steps in one JSON or YAML file, started with llm-annotate my-pipeline.yaml.
  • Staged pipeline: prepare_data applies the templates and sorts, and run_annotation does the inference, so a crashed GPU job restarts without repeating the preparation.
  • Resumable runs: results are streamed to JSONL checkpoints per sample. Raising dataset.max_num_samples and re-running annotates only the new rows, see docs/growing-a-run.md.
  • Multi-server vLLM: VLLMQueueAnnotator runs one workload over a pool of vLLM servers, one per GPU of a multi-node allocation. See examples/vllm-server-pool/ for the config-driven and the Python form.
  • SLURM out of the box: slurm/submit_pipeline.sh turns a config into one job chain per step, with everything cluster-specific in one cluster file. See slurm/README.md.
  • Annotation of an existing dataset and generation from scratch.
  • Structured output through a JSON schema, with the schema's properties as columns.
  • A thinking model's reasoning trace in its own column, on either vLLM provider and on Claude.
  • Retry and validation hooks, and per-sample error columns instead of a dead run.
  • Hugging Face Hub backup of the prepared data and the progress files while the run continues, and of the final dataset when it ends.

Two-step staged workflow

For a large dataset or a cluster job, split the work: prepare_data loads the dataset, applies the prompt template, optionally sorts by length and caches the result; run_annotation only does inference against that prepared data.

from llm_annotator import Annotator, VLLMOfflineClient

client = VLLMOfflineClient(model="Qwen/Qwen3-8B", max_model_len=4096)

HUB_ID = "my-org/imdb-sentiment"  # backups and the final dataset
PROMPT = "Classify the sentiment: {text}"

with Annotator(client=client, verbose=True) as anno:
    prepared_dataset, local_path, hub_id = anno.prepare_data(
        output_dir="outputs/imdb-sentiment",
        prompt_template=PROMPT,
        dataset_name="stanfordnlp/imdb",
        dataset_split="test",
        max_num_samples=100,
        sort_by_length=True,
        hub_id=HUB_ID,
    )

    ds = anno.run_annotation(
        output_dir="outputs/imdb-sentiment",
        prompt_template=PROMPT,
        prepared_dataset=prepared_dataset,
        hub_id=HUB_ID,
        upload_every_n_samples=500,
    )

If inference fails, run the second call again with the same output_dir and hub_id: the prepared data comes back from the Hub and the samples already in the local progress files are skipped.

One hub_id drives all three Hub destinations. The prepared data and the JSONL progress files go to temporary branches of that repository, the final dataset is pushed to its main branch, and both temporary branches are deleted once the run finishes.

On a machine with no local progress files (a purged scratch directory, or a run that moves to another cluster), restore the progress backup first:

llm-annotate-restore --hub-id my-org/imdb-sentiment --output-dir outputs/imdb-sentiment

run_annotation refuses to start when the repository has a progress backup while the local progress directory is empty, so a forgotten restore cannot replace the backup with a run that starts from zero.

The settings that produced the prepared data are recorded next to it, so a later call with an edited prompt template is refused instead of reused. What may change between two runs, and what that costs, is in docs/growing-a-run.md.

Run it on SLURM

The same config runs on a cluster without a scheduler-specific rewrite. Fill in one small cluster file (partitions, accounting, cores per GPU) and submit:

cp slurm/cluster.env.example slurm/cluster.env
./slurm/submit_pipeline.sh --dry-run my-pipeline.yaml   # inspect the jobs
./slurm/submit_pipeline.sh my-pipeline.yaml

Each step becomes its own job chain: a step served by vLLM gets a GPU server array plus a client, a step on a hosted API gets a CPU-only job, and GPUs are released as soon as the step that needed them is done. Details in slurm/README.md.

Documentation

The full documentation is at bramvanroy.github.io/llm-annotator.

examples/ holds runnable examples, and case-studies/ holds two complete research projects built on the library.

Contributing

Development setup, the make targets, the test markers and the docs layout are in CONTRIBUTING.md.

uv sync --dev
make style
make quality
make typecheck
make test

Metadata

Release files for llm-annotator 0.17.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for llm-annotator 0.17.0
File Size Uploaded
llm_annotator-0.17.0.tar.gz 716.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for llm-annotator 0.17.0
File Interpreter ABI Platform
llm_annotator-0.17.0-py3-none-any.whl Python 3 none any Details

Total release size: 839.1 kB

Release files / llm_annotator-0.17.0.tar.gz

Download URL llm_annotator-0.17.0.tar.gz
Size 716.3 kB
Tags Source
SHA-256 checksum
How to use checksums
5f5652ec38b151913e854533adb50b87619641b5c1fef5f7bbc6c660ffa81784
BLAKE2b-256 checksum
How to use checksums
b00b48b2655f837c6058796ffcbcc46b49bbfd468a323d5b407efe9ee176deb5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / llm_annotator-0.17.0-py3-none-any.whl

Download URL llm_annotator-0.17.0-py3-none-any.whl
Size 122.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
990fc3e2b309dc0ea95c8b3c3bc856d5f52758c0caff7e29a4823eecdd04fe0b
BLAKE2b-256 checksum
How to use checksums
32daba2644125ac74d5a60da9338dde54e7179743117774a8791d6c26ff73f71
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.18.0

2 release files

This release

0.17.0 This release

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.13.2

2 release files

0.12.1

2 release files

0.12.0

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.2

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.4.0

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page