Skip to main content

paratext logo  paratext

A modular, project-based pipeline that produces metadata from digitised library & archive collections with a multimodal model. Includes a human-in-the-loop review tool.

One command does the whole loop: run a model over a directory of images or PDFs, write resumable JSONL with provenance, package it for review, and export the approved results. Each project is a self-contained module — its own prompt, schema, and input handling — so the same code path runs a 50-item pilot and a 250,000-item sweep. Only --limit differs.

What you'll need

  • A directory of images or PDFs. Images are read from a flat directory, one item per file; PDFs recursively.
  • An OpenAI-compatible endpoint serving a model that accepts images. Local hosting (llama.cpp, vLLM, LM Studio, Lemonade etc) or a hosted provider.
  • Python 3.11 or newer, and uv. (The optional card detector is capped at 3.13 until torch ships 3.14 wheels.)

Install

uv tool install paratext-cli

The distribution is paratext-cli; the command and the import package are both paratext. Installing plain paratext gets you an unrelated PyPI project of the same name, which provides no paratext command.

Upgrade with uv tool upgrade paratext-cli. Your projects live in their own package, so upgrading the framework leaves them untouched.

To run against the source instead, clone it and uv tool install ./paratext.

Quickstart

# 1. Scaffold a project: asks input type, fields and prompt, writes the config,
#    registers the entry point, runs `uv sync`. Ready to run.
#    Works in an empty directory (it offers to create the project for you) or
#    inside an existing one, where it nests into your package.
paratext new my-cards

# 2. Check what it will actually do before spending a model run on it.
paratext inspect -p my-cards

# 3. Extract, package, and review.
paratext run -p my-cards --limit 50
paratext review

run writes the extraction JSONL and a review/my-cards-r1/ dataset; review opens a local web UI over everything under review/. Datasets are re-read per request, so a fresh run appears on reload without a restart.

inspect prints the fields the model is asked for, the prompt, the preprocessing applied, and whether schema, prompt and view still agree. It describes what is installed — so if it disagrees with the files you're editing, the package needs reinstalling. That mismatch is the most common cause of "my change did nothing".

Writing a project

paratext new scaffolds three files:

my_cards/
    prompt.md     # the prompt (prose, for the model)
    schema.py     # the Pydantic output schema (your metadata fields)
    __init__.py   # wires them together

__init__.py stays small because input handling comes from a source adapter:

from paratext.projects import Project, load_prompt
from paratext.sources import image_source   # or pdf_source

from .schema import Record

PROJECT = Project(
    name="my-cards",
    schema_version="v1",
    prompt=load_prompt(__file__),
    schema=Record,
    source=image_source(),
)

Register it so it's discovered at runtime:

[project.entry-points."paratext.projects"]
my-cards = "my_cards:PROJECT"

That's the whole contract. The review view defaults to showing every schema field; override it only when you want to curate the display. Optional hooks (curate, build_record, ground_truth) handle drop rules and ground truth.

Your fields end up named in three places — schema, prompt, and view — with no automatic link between them. Keep them in step by calling audit_project(PROJECT) from a test; paratext new generates one. Put behaviour in prompt.md, and keep the schema's Field(description=...) short and structural — those descriptions are sent to the model too, and shouldn't restate the prompt in a second voice.

Review and rounds

Extraction quality lives almost entirely in the prompt, so the workflow is a loop: run → review → edit the prompt → run again. A round captures one prompt version, keyed on the prompt's hash:

  • Edit prompt.md and re-run → a new round (-r2, -r3, …). The UI shows the two most recent rounds side by side and highlights what changed.
  • Re-run the same prompt (a resume, or a bigger --limit) → the current round is updated in place, keeping the annotations you've already made.

Reviewers give a verdict and a free-text note. The Build eval set tab goes further: it surfaces the rows the model got wrong and lets you edit the fields into the correct answer, stored separately as gold labels. Accuracy still reflects the model — correcting a row never changes its verdict — but those corrected rows ship as gold alongside the approved ones when you export.

Everything is saved to a SQLite annotations.db you can query directly.

Configure

A paratext.toml in the working directory holds your defaults, and paratext config creates and opens it. Keys are kebab-case, matching the CLI flag that sets them:

base-url = "http://localhost:8000/v1"
model    = "Qwen3-VL-30B"

[project.my-cards]
source = "/data/my-cards/images"
output = "output/my-cards.jsonl"

Once a project has a section, paratext run -p my-cards needs nothing else. CLI flags override environment variables, which override the file.

Full reference, including hosted endpoints and auth: docs/configuration.md.

Commands

Command What it does
paratext run -p <project> Extract and package in one step (the common path)
paratext extract -p <project> Run the model, write JSONL only
paratext package <jsonl> Re-package an existing JSONL (no model calls)
paratext review [dir] Launch the review UI (default: ./review)
paratext export -p <project> Export a reviewed round (--format hf/marc/dc)
paratext inspect [-p <project>] Show what an installed project does
paratext new [name] Scaffold a new project package
paratext config [--show] Open paratext.toml; --show prints resolved defaults
paratext sample Symlink a random image subset out of a nested tree
paratext carbon Show current grid carbon/renewables
paratext guide Print the agent guide

Run paratext <command> -h for that command's flags.

Going further

  • Export — Hugging Face datasets, MARCXML, Dublin Core, and what makes up the gold set.
  • Configuration — full key reference, hosted endpoints, environment variables.
  • Scanned cards — optional verso filtering, card cropping and show-through suppression for index-card collections.
  • Green scheduling — wait for a clean electricity grid before running a batch.
  • Publishing — cutting a release to PyPI via Trusted Publishing.
  • AGENTS.md — the guide for AI coding agents, including how to extend paratext for your own collection.

When something looks wrong

  • A run finished but preprocessing didn't happen. run prints a ! notice for anything that degraded rather than failed — most often a card crop falling back to a uniform crop because no detector was available.
  • An edit to schema.py or prompt.md had no effect. paratext inspect reports the installed project. If it disagrees with your editor, reinstall (uv sync). An editable install avoids this entirely.
  • A field renamed in one place but not another. paratext inspect runs the same audit as audit_project. Call it from your tests too.

Development

uv sync --extra dev            # add --extra detector for the card detector
uv run paratext               # run the CLI against local source
uv run pytest -q               # tests
uv run ruff check              # lint

License

Apache-2.0 — see LICENSE and NOTICE. Copyright 2026 National Library of Scotland. The card-detector model weights are distributed separately on the Hugging Face Hub under their own license.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paratext_cli-0.1.0.tar.gz (227.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paratext_cli-0.1.0-py3-none-any.whl (127.7 kB view details)

Uploaded Python 3

File details

Details for the file paratext_cli-0.1.0.tar.gz.

File metadata

  • Download URL: paratext_cli-0.1.0.tar.gz
  • Upload date:
  • Size: 227.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","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}

File hashes

Hashes for paratext_cli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6b7590ddff2cee29896709fea91da649c7f32daaf25b1f0549001d102e465d7d
MD5 48eba06cc375011774b4537dbe807595
BLAKE2b-256 2f69d9cec55d5974142069c08fcf97dccc6f147c6eef011908682a60c117b1a0

See more details on using hashes here.

File details

Details for the file paratext_cli-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: paratext_cli-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 127.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","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}

File hashes

Hashes for paratext_cli-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3ee3de98bdd49514a2db8b538bf0e6bafdaca4719f36cba364bbf9870eda6ad5
MD5 d1cc640fdd004cd4cc459818a0a2ebdd
BLAKE2b-256 b932ed44c85d3bc3d2b921e734a66868feb9d15d37588daf6bb4488be23f05f6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page