paritex
Reconstruct papers as LaTeX with an AI backend, re-render with tectonic, and measure how faithful the reconstruction is.
Companion to hallubib: hallubib asks are your references real?, paritex asks is this LaTeX really your paper?
A paper's source is not recoverable from its rendering, so what the backend produces is a guess — paritex makes the guess earn its keep. Every round must survive three checks: tectonic must compile it, a structural bibliography gate rejects any round whose refs.bib is missing, empty, inlined as thebibliography, or short of the keys the text cites, and word-level parity against the original scores what survived. Failures feed back to the backend; the surviving evidence lands in report.json. Whether the references name real papers is hallubib's question, asked of the refs.bib this contract guarantees exists.
Usage
paritex fetch # download the demo paper set into papers/
paritex reconstruct papers/bitcoin.pdf --rounds 3 # init + AI reconstruct + render + gate + eval
reconstruct on a PDF creates a reconstruction project directory (bitcoin/) and runs the loop: AI backend writes main.tex (+ refs.bib), tectonic renders it to main.pdf, the bibliography gate checks structure, and the result is compared word-level against the original. Render failures, gate violations, and text divergences are fed back to the backend for up to --rounds passes, stopping early at --target parity; a render or gate failure on the final round raises instead of passing quietly.
A project directory is self-contained:
bitcoin/
original.pdf # the source paper
assets/ # raster images extracted from it, for \includegraphics
main.tex # the reconstruction (backend-written)
refs.bib # its bibliography — always a real file, enforced by the gate
main.pdf # tectonic render
report.json # parity ratio, page counts, all divergences
The file names are the produced-files contract: consumers import ORIGINAL, MAIN_TEX, REFS_BIB, REBUILT, ASSETS, REPORT from paritex instead of restating them, and paritex treats files it did not create as opaque, so a consumer can layer its own files into the same directory.
The steps are also available separately:
paritex init papers/attention.pdf # just create the project dir + extract assets
paritex render attention # tectonic main.tex
paritex eval attention # compare main.pdf to original.pdf
paritex parity original.pdf other.pdf # bare two-PDF comparison
The report
report.json is a serialization contract — version-stamped, deterministically ordered, meant to be committed and rendered by consumers:
{
"paritex_version": "0.1.0",
"parity": {
"ratio": 0.992,
"divergences": [
{"kind": "missing", "original": "...", "rebuilt": "...", "page": 3}
]
},
"pages_original": 15,
"pages_rebuilt": 15
}
kind is missing / added / changed, page is where the divergence sits in the original — enough context to show which parts of the paper survived reconstruction verbatim.
Backends
The AI backend is a configured command; three are built in (paritex backends lists them):
claude-code(default) — runs the Claude Code CLI as an agent inside the project dir; it readsoriginal.pdfitself, writesmain.tex/refs.bib, and may run tectonic to self-check. Auth: the box's Claude Code login. The Anthropic auth env vars (ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN) are scrubbed from the child environment, so a key exported in your shell can never silently take precedence over the login and bill your API account.claude-api— the same agent command, but billed to the API: it refuses to start unlessANTHROPIC_API_KEYis set, and passes it through. Spending credits is an explicit choice of backend, never a side effect of the environment.claude-gen— plain one-shot generation on the box login: the prompt (with the extracted paper text) goes in, LaTeX comes out on stdout; the bibliography arrives as afilecontents*block that paritex materializes asrefs.bib.
All three come from one factory, claude_backend(name, auth="login"|"api", allowed_tools=..., mode=...), which consumers use to build their own flavors (e.g. with extra allowed tools). Auth is expressed through three generic spec keys any backend can use: env (a table of variables set for the child), drop_env (variables scrubbed from it), and require_env (variables that must be present or the run fails before spawning).
Any command can be a backend via paritex.toml (looked up in the working directory, then ~/.config/paritex.toml, or passed with --config):
default = "codex"
[backends.codex]
mode = "agent" # the command writes main.tex itself, cwd = project dir
argv = ["codex", "exec", "--full-auto", "{prompt}"]
timeout = 3600
[backends.llm-cli]
mode = "generate" # stdout becomes main.tex (code fences stripped)
argv = ["llm", "-m", "gpt-5"] # no {prompt} in argv -> prompt is piped to stdin
prompt_file = "my-prompt.txt" # override the default prompt template
require_env = ["OPENAI_API_KEY"] # fail loudly before spawning if unset
drop_env = [] # scrub inherited vars from the child
env = { LLM_TEMPERATURE = "0" } # set vars for the child
Per-backend prompt, feedback, compile_feedback, and bib_feedback templates (inline or *_file) override the defaults in paritex/prompts.py; placeholders like {pdf}, {main_tex}, {refs_bib}, {assets}, {text}, {ratio}, {feedback}, {violations}, {log} are substituted literally, so LaTeX braces are safe.
Consumers with their own config carry the same spec shape: parse_backend(name, spec, base) builds a Backend from a mapping with these keys, so one AI-invocation schema serves every tool without a paritex.toml in sight.
Tables, figures, and bibliographies are covered by the default prompts: tables must be rebuilt as tabular content, extracted assets/ images are offered for \includegraphics (with TikZ as the fallback for vector diagrams), and references go through refs.bib — which the gate then enforces and the word-level parity check verifies.
Demo papers
paritex fetch downloads a small set of famous, shortish, layout-diverse papers to exercise the loop: attention (Transformers; single-column, tables), gan, word2vec, resnet (two-column CVPR), gw150914 (LIGO; two-column REVTeX), bitcoin (whitepaper with diagrams).
Python API
from paritex import init_project, load_backends, reconstruct
backends, default = load_backends()
project = init_project(Path("papers/gan.pdf"))
report = reconstruct(project, backends[default], rounds=3, target=0.98, on_event=print)
print(report.parity.ratio, report.pages_original, report.pages_rebuilt)
on_event receives a Progress per step — backend start, render ok/fail, gate ok/fail, parity ratio and divergence count per round — so a consumer can stream run progress to a terminal or UI instead of wrapping the loop in threads.
Release files for paritex 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| paritex-0.1.3.tar.gz | 25.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| paritex-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.7 kB
Release files / paritex-0.1.3.tar.gz
| Download URL | paritex-0.1.3.tar.gz |
|---|---|
| Size | 25.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9c27b3d19a442525c87b328488362e0c0a3cfafce8a45b4063663fd39e7bdb62
|
|
BLAKE2b-256 checksum How to use checksums |
1a408d042e94bb5f9bf14a34b83b6a72b1a5d57d888dc81d9b4ddc8aefc50933
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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 / paritex-0.1.3-py3-none-any.whl
| Download URL | paritex-0.1.3-py3-none-any.whl |
|---|---|
| Size | 22.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7b86acf189cf6a36bdf937bf225b60573e420a88616991d40fb419ec5882b853
|
|
BLAKE2b-256 checksum How to use checksums |
d8ff957a2438857c75192b9fd767a8b12c322504c1e73023bbf946fc4d4c2b72
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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}
|