Architecture
Pipeline overview
bibr processes scientific papers through six logical phases. The shared
stage plan lives in bibr/pipeline/plans.py; local and served pipelines
use the same parsing, extraction, validation, and export stages.
File / bytes --> Validate
├── DOCX / JATS / HTML / ePub --> Native parse ─────────┐
└── PDF --> Render --> Layout --> Native text / OCR ──┤
v
Structure / sentence segmentation
v
Extract --> Identity validation
v
Core checkpoint --> Enrich --> Export
Two runtime options are available:
bibr chew/ Python library (bibr/local/pipeline.py) --LocalPipelinemanages model lifetimes on one machine. OCR uses the selected Paddle-first runtime, an explicit HTTP backend, or a cloud vision provider.bibr serve(bibr/serve/app.py) -- LitServe HTTP API with disk-backed multipart ingress and one spawned inference worker hosting the pipeline GPU models (layout, segmenter).
Pipeline shutdown releases owned models, servers, and clients. Cancellation during local server startup or sentence segmentation waits for the underlying worker to finish before disposing its result or releasing the model lock. Cancellation can therefore take as long as the blocking operation; server startup still relies on the selected backend's own timeout.
Pipeline stages
1. Validate (bibr/input/validate.py)
Checks MIME type, file corruption, and encryption. Refuses unsupported formats (.exe, .zip, .tex, legacy .doc). Supports .pdf/.docx/.xml/.html/.htm/.epub — .xml as JATS.
2. Input (bibr/ocr/, bibr/input/docx_native.py)
- DOCX and JATS XML files are parsed natively (python-docx, and a JATS
<article>parser respectively) to extract headings and text structure, skipping OCR entirely - HTML (
.html/.htm) and ePub (.epub) files are also parsed natively —HtmlHandlingStage(bibr/pipeline/stages/html.py), backed bybibr/input/html_native.pyandbibr/input/epub_native.py— skipping OCR the same way as DOCX/JATS - PP-DocLayoutV3 detects PDF regions (headings, body text, tables, formulas, figures, and more); regions then flow through native-text inspection and recognition
OCR_BACKEND=paddleselects one Paddle-first OCR runtime transactionally at startup; explicitpaddle-*,glm-*, and cloud backends remain available throughOcrOptions
PDF limits. PIPELINE_MAX_PAGES limits the processed page count
(default 200). Render-pixel and dimension limits reject oversized pages before
rasterization. Size memory for the processed pages and concurrent files.
Native text bypass and recognition. bibr/ocr/pdf_inspection.py inspects
embedded PDF text, metadata, outline headings, and reference-line geometry
under one PDFium walk. With OCR_NATIVE_TEXT_ENABLED=true (the default),
qualifying text regions are filled from the PDF text layer and bypass OCR.
Acceptance uses character-count and printable-text checks
(OCR_NATIVE_TEXT_MIN_CHARS=20, OCR_NATIVE_TEXT_MIN_PRINTABLE_RATIO=0.85)
plus guards for corrupt text. Remaining regions use the selected OCR runtime.
Disabling the bypass leaves metadata, outline, and geometry inspection available.
3. Structure (bibr/structure/pdf_parser.py)
PDFParser converts OCR regions into PaperContents:
- Maps region labels to content handlers via
LABEL_TREATMENTdispatch dict - Runs sentence segmentation through the pipeline's wtpsplit-lite segmenter (local or served)
- Reconstructs heading hierarchy, incorporates PDF outline hints, and handles multi-study scopes
- Extracts tables, figures, footnotes, URL links, and inline figure/table references
- Assigns captions and preserves ordered physical parts when figures or tables span multiple regions/pages
4. Extract (bibr/extract/extractor.py, bibr/paper.py)
Post-parse pipeline runs after structure parsing:
- Section classification -- three-tier cascade maps headers to canonical IMRaD categories: alias lookup table, then a trained classifier model, then LLM fallback (
section_classifier.py; see Classifiers) - Study hierarchy -- regex markers such as Study 1 and Experiment A establish separate section scopes before classification (
section_tree.py) - Metadata extraction -- selected front-matter rows ground title, authors, abstract, DOI, and publication fields. JATS and HTML/ePub can supply preparsed metadata, avoiding the core metadata LLM call
- Paper classification -- the default SPECTER2 multitask model predicts paper type and OECD domains from title/abstract; confidence gates and LLM fallback are described in Classifiers
- Reference extraction -- segmentation (default
geom, a local geometry model, cascading through region anchors -> LLM -> CRF when geometry is absent or unconfident) locates each reference; parsing (defaultner, a local ModernBERT-CRF model, withllmfor opt-in batched LLM parsing) extracts structured fields - Citation linking -- 3-tier hybrid approach: numeric bracket/superscript citations, author-year citations, then LLM fallback (
citation_linker.py) - Equation extraction -- regex + LLM fallback for statistical reporting decomposition (
equation_extractor.py) - IMRaD enforcement -- deduplication of Abstract and References by classification-source trust; repeated Methods/Results/Discussion remain valid
- Source ownership -- abstract spans, author grounding, and integrity-statement evidence are resolved against the selected article block. Ambiguous front matter can yield validation issues instead of metadata taken from another article in the file
Identity validation and a core checkpoint run before enrichment. Extraction
and enrichment have separate completion evidence, so a failed or delayed
external lookup need not force OCR and extraction to run again. The
integrity-statement resolver defaults to PIPELINE_INTEGRITY_STATEMENT_MODE=shadow:
it records typed evidence while preserving the compatibility scalar fields.
5. Enrich (bibr/enrich/references.py)
Opt-in Crossref (and optional bibr-resolver) enrichment of extracted references:
- DOI lookup for direct matches
- Bibliographic search as fallback (fuzzy title matching)
- Matches stay in
bib_matchandmetadata_match; explicitfill/replaceconsolidation can merge accepted reference fields intobib - Off by default.
CROSSREF_ENRICH=trueenables it for a deployment; per run,bibr chew --crossref/--no-crossref,chew(crossref=True|False)and the serve API'scrossrefform field override the setting either way (extraction.settings.crossref_enrichin the output records the effective value) - A served request can enable both reference parsing and Crossref even when
REF_PARSE_STRATEGY=offis the deployment default. Enrichment is skipped when the effective reference parsing strategy remains off. - When enabled, the up-front network work (resolver health probe and title searches, Crossref bulk DOI lookup) starts as soon as the references are parsed and overlaps the rest of the extract stage (citation linking, structured integrity), so it no longer adds serial wall time after extraction; the core checkpoint still sees unenriched references
6. Export (bibr/export/)
JSON (bibr/export/json_export.py):
- JSON-serializable dict matching the bibr v11.0 paper schema
- Top-level keys include:
paper_id,schema_version,source,metadata,author,text,section,url,bib,xref,figure,table,eq,bib_match,metadata_match,funding,affiliation,qualification_provenance,extraction,validation. All telemetry (engines, timings, LLM usage, enrichment, warnings, diagnostics receipts, opt-in regions) lives underextraction. Figure/table rows retain legacy primary fields and add ordered physicalpartswith provenance. - Schema version:
11.0 metadatais scalar-only (no nested objects or lists of objects) so R consumers canas.data.frame(metadata). Pipeline telemetry lives underextraction; the input file's identity undersource.- All positional IDs are 1-based;
section_id=0is the Root sentinel (excluded from export) - Enrichment matches are in a separate top-level
bib_matcharray (flat, keyed bybib_id+service) - Optional
extraction.regionsdebug payload (per-region bbox/font/content) is opt-in viainclude_regions=TrueonPaper.export_to_json()/export_paper_to_json()/ theinclude_regionsform field onPOST /papers/extract/ the--regionsCLI flag. It also preservesraw_ocr_contentwhen Paddle normalization changed a table or formula response, so diagnostics can compare the original model output with canonical content.
Within major version 11, the schema is additive-only: new fields may appear in
any 11.x release and readers must ignore keys they don't recognize. Dispatch
on the presence of a root schema_version key, never on parsing its value —
pre-v11 payloads have no such key at all. See CHANGELOG.md for the full v11
break and forward-versioning policy.
OCR selection and evidence
PDF OCR starts with PP-DocLayoutV3 regions. OCR_BACKEND=paddle then selects a
concrete runtime transactionally at startup: Linux x86_64 tries
paddle-vllm (PaddleOCR-VL-1.6) when an NVIDIA GPU with at least 8 GB VRAM
is detected, then glm-llama; without that GPU it uses glm-llama directly.
Apple Silicon tries
paddle-rapid-mlx, paddle-mlx-vlm, glm-rapid-mlx, then glm-llama.
The selected backend/model/profile becomes the OCR runtime identity used in
the OCR-cache key and export extraction.ocr, so cache entries and
provenance cannot be confused across recognizers or normalizers. There is no
silent per-request GLM fallback after a concrete runtime has passed startup.
OCR cache entries retain page-attempt and page-failure counts with extraction
warnings. Cache hits apply the current OCR_MIN_SUCCESS_RATE, so tightening
the threshold also rejects cached OCR that falls below it. Cache format 9
invalidates older entries that lack this completion evidence.
Paddle table output uses OTSL markers (such as <fcel>, <lcel>, <nl>, and
<ecel>) that bibr decodes into canonical HTML. Paddle formula output has one
outer Markdown/LaTeX fence or balanced display delimiter removed; the LaTeX
body is otherwise preserved. The normalized value feeds parsing, while raw
Paddle output remains available in raw_ocr_content through extraction.regions.
Key data structures
PaperContents (bibr/paper_contents.py)
Holds parsed text content built by the native input parsers or PDFParser:
sections-- list ofPaperSection(header, section_type, classification_score)sentences-- list ofPaperSentence(text, section_id, paragraph_id, page_number)tables-- list ofPaperTable(caption, HTML markup, cells, physical parts)links-- list ofPaperURLLinkfigures-- list ofPaperFigure(caption, optional base64 image, physical parts)xrefs-- list ofPaperXref(inline citations, table/figure refs)equations-- list ofPaperEquation(lhs, comp, rhs)text_df,links_df,sentences_df,equations_df-- cached DataFrame properties
Paper (bibr/paper.py)
Top-level dataclass wrapping PaperContents + PaperMetadata:
metadata--PaperMetadata(title, authors, DOI, keywords, references, matches, paper type, OECD domain)contents--PaperContentsexport_to_json()-- serialize to dict
PaperMetadata (bibr/models.py)
Pydantic model for the paper's own fields and reference list:
doi,title,keywords-- core identifierspaper_type,paper_type_confidence-- paper type and confidence from the local classifier or LLM fallbackoecd_l1,oecd_l2,oecd_confidence-- OECD Frascati domain taxonomyauthors-- list ofPaperAuthor(given, family, affiliation, email, orcid, role)references-- list ofPaperReference(BibTeX fields + nestedmatchdict withExternalMatchper service)
CanonicalSection (bibr/paper_contents.py)
IMRaD+ section classification enum:
| Value | Description |
|---|---|
title |
Paper title (root/level-0 heading) |
abstract |
Abstract / Summary |
intro |
Introduction / Background |
method |
Methods / Materials |
results |
Results / Findings |
discussion |
Discussion / Conclusion |
references |
References / Bibliography |
acknowledgment |
Acknowledgments |
funding |
Funding information |
keywords |
Keywords |
endnote |
Supplementary material, future work, outlook |
appendix |
Appendix / Supporting Information |
open_data |
Data availability / code availability |
author_contributions |
Author Contributions / CRediT statement |
coi |
Conflict of Interest / Competing Interests |
ethics |
Ethics statement / IRB approval / Informed consent |
footnote |
Footnotes |
table |
Table caption/label region |
figure |
Figure caption/label region |
unknown |
Unclassified (fallback) |
External services
| Service | Purpose | Required |
|---|---|---|
OCR runtime (paddle-* default; glm-* explicit/fallback) |
PDF recognition after layout detection | bibr chew selects a local runtime; bibr serve proxies to an explicit external OCR service |
| LLM API (Google/OpenAI/Anthropic/Groq/Ollama or managed local server) | Metadata extraction and fallback tasks | Standard extraction path; no_llm=True disables these tasks |
| Crossref API | Reference enrichment (DOI lookup + search) | Optional |
| Redis | Response caching (bibr serve mode) |
Optional |
Deployment architecture
bibr (single-machine)
Pipeline orchestrator (bibr/local/pipeline.py). Loads models sequentially to fit within limited GPU memory. Components:
| Module | Purpose |
|---|---|
bibr/local/cli/ |
CLI entry point (bibr chew, bibr batch and the other subcommands) |
bibr/local/pipeline.py |
LocalPipeline orchestrator with memory management |
bibr/local/layout.py |
PP-DocLayoutV3 layout detector |
bibr/local/segmenter.py |
wtpsplit-lite sentence segmenter |
bibr/local/ocr.py, bibr/local/vllm_ocr.py, bibr/local/mlx_vlm_ocr.py, bibr/local/rapid_mlx.py, bibr/local/ocr_cloud.py |
OCR backends behind bibr/ocr/registry.py: managed paddle-vllm / glm-llama / paddle-*-mlx / glm-rapid-mlx servers, paddle-http / glm-http external servers, and the cloud vision LLMs |
bibr/pipeline/ |
The stage pipeline (stages/), run context, resources and caches shared by the local and LitServe entry points |
Memory management modes control GPU VRAM usage: aggressive (load/unload per phase), balanced (keep layout + segmenter resident; OCR also stays resident across chunks unless a local LLM server needs the VRAM), keep_all (everything loaded).
Auto-selection uses aggressive with ≤8 GB system RAM or ≤8 GB CUDA VRAM;
otherwise it uses balanced. With a cloud LLM and non-aggressive memory
mode, PIPELINE_STREAM_BACKHALF=true allows completed files' parsing,
extraction, enrichment, and export to overlap subsequent OCR work. Managed
local LLMs retain the stage barrier for the OCR-to-LLM memory handoff.
bibr serve (LitServe)
LitServe separates the HTTP API process from its spawned inference worker. Public multipart bodies never cross that process boundary. The request flow is:
POST /papers/extract
-> API admission + explicit multipart bounds
(one file, eight unique options at most 64 bytes each)
-> one multipart spool (at most 1 MiB in memory)
-> owner-only disk file (50 MiB file limit; 51 MiB body envelope)
-> leased UUID/size/SHA-256/options descriptor
-> LitServe multiprocessing queue
-> worker verifies one read, deletes file, and runs the pipeline
/_bibr/inference is the private LitServe descriptor route and returns 404
when called over HTTP. It exists only so the API process can dispatch the
opaque descriptor through LitServe's in-process route adapter. A descriptor
contains no file bytes and no filesystem path.
| Component | Module | Purpose |
|---|---|---|
build_server() |
bibr/serve/app.py |
Builds the litserve.LitServer, public multipart route, private descriptor dispatch, and owned upload lifecycle |
UploadStore / InferenceDispatchTracker |
bibr/serve/ingress.py |
Persists bounded uploads, queues opaque descriptors, and cleans files on consumption, cancellation, failure, or shutdown |
BibrPipelineAPI |
bibr/serve/deployments/pipeline.py |
Main processing orchestrator |
LayoutDetector |
bibr/serve/deployments/layout.py |
PP-DocLayoutV3 layout detection (GPU/CPU) |
SentenceSegmenter |
bibr/serve/deployments/segmenter.py |
wtpsplit-lite sentence segmentation (GPU/CPU) |
Exactly one inference worker is pinned in build_server(), with no setting to
change it: another worker means another model copy (~1.5 GB RSS), CUDA context,
and GpuBatcher, so batches shrink as workers rise. Async I/O and the GPU
batchers provide concurrency within that one worker, and the heavy CPU stages
already use every core from one process — see
Production deployment.
Failed inference workers
fail-stop by default (PIPELINE_RESTART_WORKERS=false). LitServe 0.2.17 worker
replacement cannot reliably notify the API waiter owned by a dead worker, so
true remains an unsupported opt-in until the locked real-process death-path
gate proves completion notification.
Async jobs persist through the same upload store and submit the same descriptor
to LitServe. The queue remains in the HTTP API process;
JOBS_STORE=memory keeps results there too, while JOBS_STORE=redis shares
status and results between replicas. The server pins exactly one API process
per instance, including with jobs disabled.
Upload leases keep queued and dispatched descriptors out of stale sweeping.
LitServe is pinned below 0.3 because this boundary relies on 0.2.x
manager/worker helpers; the spawned success and death-path compatibility tests
must be migrated before that version constraint is relaxed.
Configuration
Settings are managed by GlobalSettings (bibr/config.py) using pydantic-settings.
Values are read from environment variables or a .env file. Each pipeline
owns a settings snapshot; per-run choices such as page range and reference
strategies travel through RunConfig, without changing process-global defaults.
Key settings:
| Variable | Description | Default |
|---|---|---|
LLM_PROVIDER |
LLM backend | google |
LLM_MODEL |
LLM model name | gemini-3.5-flash-lite |
LLM_REASONING_EFFORT |
Default reasoning effort for OpenAI models | minimal |
LLM_REASONING_EFFORT_AUTHORS |
Reasoning effort override for author extraction | low |
LLM_REASONING_EFFORT_CITATIONS |
Reasoning effort override for citation resolution | low |
OCR_BACKEND |
OCR runtime selector/backend | paddle |
OCR_BASE_URL |
Base URL for an external HTTP OCR server | http://localhost:8080 |
CROSSREF_ENRICH |
Enable Crossref/resolver reference enrichment (per-run override: --crossref/--no-crossref, chew(crossref=...), serve crossref field) |
false |
EQUATION_EXTRACTION |
Enable equation extraction | true |
FIGURE_IMAGES |
Include base64-encoded figure images in output | false |
REF_SEG_STRATEGY |
Reference segmentation strategy (geom, region, llm, or crf) |
geom |
REF_PARSE_STRATEGY |
Reference parsing strategy (ner, llm, llm-chunked, or off) |
ner |
REF_TRAINING_DATA_DIR |
Save raw bib text + LLM extracts (parser data); LLM segmentation spans in segmentation/ subdir (segmenter data) |
disabled |
ENVIRONMENT |
Runtime mode (development/production) |
development |
For the complete reference, run bibr config example --full or see the
Settings reference.