Source: https://velofy.co/terbium/troubleshooting/

[terbium](https://velofy.co/terbium/) / Guides

# Troubleshooting and limits

Fixes for common terbium problems: the old PyPI version, silent OCR, AI calls that never happen, wrong document types, and the known limits of each lane.

Each section names a symptom, its cause in the 0.10.0 source, and what to do. Everything here was checked against the code; the behaviours marked as observed were reproduced with terbium 0.10.0, Python 3.12 and PyMuPDF 2.0 on 25 October 2026.

## First principles

Most problems come from one of four places, and it helps to know which before reading the rest:

*   **The version.** The Python Package Index (PyPI) and GitHub serve different releases, and several flags exist only in the newer one.
*   **The input has no words.** Every lane reads words. A scan or an image file has none until Tesseract supplies them, and without Tesseract some paths stay silent.
*   **The artificial intelligence (AI) lane did not run.** It needs a key, the provider’s software development kit (SDK), and a hard table or an incomplete row to work on. Inside `parse`, model errors are swallowed so a parse never crashes, which also hides why nothing happened.
*   **A heuristic guessed wrong.** Classification, section headings and stock keeping unit (SKU) search are rules, not understanding. Each can be overridden or checked.

## Installation and version

### pip installs 0.9.7 and flags from these docs fail

PyPI still serves 0.9.7 (checked on 25 October 2026). In 0.9.7, `--type`, `--html` and `--open` do not exist, so the command stops with an `unrecognized arguments` error. Check with `terbium --version`, then install 0.10.0 from the GitHub release:

```
pip install "https://github.com/velofy/terbium/releases/download/v0.10.0/terbium_parse-0.10.0-py3-none-any.whl"
```

### `pip install terbium` installed something else

The PyPI project named `terbium` is unrelated. Remove it and install `terbium-parse`:

```
pip uninstall terbium
pip install terbium-parse
```

### `pip install -e .` fails with the macOS system Python

The pip 21.2 that ships with the macOS system Python 3.9 cannot do an editable install from `pyproject.toml`. Run `python -m pip install --upgrade pip` first.

### A FutureWarning about `fitz` on every run

With PyMuPDF 2.0, which pip installs by default today, every run prints:

```
FutureWarning: The `fitz` API is deprecated and will be removed in future.
Use `import pymupdf` instead.
```

terbium 0.10.0 imports PyMuPDF under its old name, `fitz`. The warning is harmless today. Silence it with the standard Python warning filter:

```
PYTHONWARNINGS=ignore::FutureWarning terbium catalogue.pdf
```

The warning says a future PyMuPDF release will remove the `fitz` name, and terbium 0.10.0 depends on it.

## Scanned input and OCR

### An image file gives zero records and no message

Observed. Without Tesseract, the local optical character recognition (OCR) program, an image file (`png`, `jpg`, `webp`, `tiff` and the rest) becomes a page with no words. `parse` returns no records and prints nothing, because no table was scored and the image-only check covers Portable Document Format (PDF) pages only.

Check whether terbium can see Tesseract:

```
python -c "from terbium.layout import ocr; print(ocr.available())"
```

If it prints `False`, install Tesseract (`brew install tesseract` on macOS, `apt install tesseract-ocr` on Debian and Ubuntu). If Tesseract is installed and it still prints `False`, set `TESSDATA_PREFIX` to your `tessdata` folder. terbium sets it only when the variable is unset or wrong, and only to one of these folders that exists:

*   `/opt/homebrew/share/tessdata`
*   `/usr/local/share/tessdata`
*   `/usr/share/tessdata`
*   `/usr/share/tesseract-ocr/4.00/tessdata`
*   `/usr/share/tesseract-ocr/5/tessdata`
*   `/opt/local/share/tessdata`

For image files, the adapter runs Tesseract whenever it is available, so `ocr=False` does not turn OCR off for them. It does for PDF pages.

### An image of a table or a product grid gives no records, even with Tesseract

Table reconstruction and label grids run only on PDF pages. For an image file, the recognized words feed classification and the transaction and resume lanes, which read words directly, so a receipt photo works but a photo of a price list does not. OCR on a PDF page feeds the full geometry engine, so wrapping the image in a one-page PDF lets `parse` rebuild its tables when Tesseract is installed:

```
from PIL import Image

import terbium

Image.open("pricelist.png").convert("RGB").save("pricelist.pdf")
doc = terbium.parse("pricelist.pdf")
```

### A scanned PDF returns no records

A PDF page with images and no text layer has no words to reconstruct. Without an AI key, `parse` says so:

```
terbium: 2 page(s) are image-only with no text layer (e.g. 1, 2). Read them with the vision lane:
pass ai=terbium.AI(...) with a key.
```

Install Tesseract to read the pages locally, or pass an `AI` for the vision lane. With a key set, this message is not added.

### The catalog message says OCR “already ran”, but Tesseract is not installed

Observed. `ocr=True` does not install or enable Tesseract; without it, nothing is read. `build_catalog` then builds its message as if OCR had run, so the message points at the vision lane instead of telling you to install Tesseract. Leave `ocr="auto"` (the default) to get the accurate message.

## The AI lane

### I set a key, but nothing changed

Work down this list:

1.  **Is there anything to fix?** `parse` calls a model only for tables under the threshold, and `build_catalog` only for rows missing a name, SKU or materials. The transaction fill is skipped when there is a summary record and every summary record already has a `total`. A clean document makes no calls, and `doc.used_ai` stays `False`.
2.  **Is the SDK installed?** Each provider needs its extra, for example `pip install "terbium-parse[anthropic]"`. Inside `parse`, a missing SDK raises an error that is caught, so the hard tables stay as they were with no message.
3.  **Is the provider name right?** `AI(provider=...)` accepts `anthropic`, `openai`, `kimi`, `grok` or `gemini`. Any other value, or a pinned provider whose key is missing, means no model is called, and no error is raised.
4.  **Is the key really set?** An empty environment variable counts as unset.
5.  **Did you pass `True`?** `parse(ai=True)` builds `terbium.AI()` for you. `build_catalog(ai=True)` skips the AI fill for photo rows, and `enrich_products(records, True)` raises `AttributeError`. Pass `terbium.AI()` to both.

To see the real error, make one small call through the same provider terbium would pick. This costs one short model call:

```
import terbium
from terbium.harness.providers import provider_name, text_provider

ai = terbium.AI()
print(ai.available, provider_name(ai))   # False, None: no key was found
print(text_provider(ai).complete("Reply with OK.", "Be brief.", "haiku"))
```

The last line raises the underlying error, such as `RuntimeError: The Anthropic lane needs the 'anthropic' package: pip install terbium-parse[anthropic]`, an authentication error or an unknown model.

### With the AI lane on, no escalation message appears

The message is printed only when no AI is configured. With a key, tables that are still hard after the AI step keep a message in `doc.escalation`. Read it there.

### `read_vision` raises instead of returning `None`

`read_vision` returns `None` when no provider is available, the page cannot be rendered, or the reply is not JavaScript Object Notation (JSON). Errors from the model call itself, such as a missing SDK, are raised. Wrap the call in `try` when you run it over many pages.

## Classification and lanes

### The document type is wrong

Print the scores:

```
doc = terbium.parse("file.pdf", announce=False)
print(doc.doc_type, doc.class_scores)
```

The classifier counts keywords and patterns, so prose that mentions “experience” or “invoice” can tip it. Override with `doc_type=` in Python or `--type` on the command line.

### Resume lines are missing

Observed. A line that contains a section word anywhere (`experience`, `education`, `skills`, `projects`, `certifications` and their synonyms, such as `employment`, `expertise` or `portfolio`) is read as a section heading and does not appear as content. For example, “Led a team with deep Python experience” disappears and starts a new `experience` section. There is no option to change this in 0.10.0.

Also, when the resume’s geometry produces two or more table records, those table records are returned instead of the section records.

### A phone number lost its opening bracket

Observed. The phone pattern starts matching at the first digit, so `(555) 123-4567` comes out as `555) 123-4567`.

### A receipt has extra line items and two total records

Observed. When a receipt has no line item table, terbium builds line items from the page layout, and any line that ends in a number counts. `Receipt No: R-8891` became a line item with description `Receipt No: R` and amount `8891`, and the `Total:` line produced two `total` records. These records score 0.65 and 0.7, so at the default threshold they are always in `doc.ambiguous_records`. Keep line items whose `amount` carries a currency symbol, or review them. Details: [What to check in transaction output](https://velofy.co/terbium/documents/#what-to-check-in-transaction-output).

### The invoice summary record has a stray field

Observed. The summary record collects every `Label: value` pair and stacked label it finds on the page. A table header line followed by a row can be read as a label above its value, so a summary can carry, for example, a `description` holding the first line item. Line items also copy the header fields, including the invoice `total`. Select the fields you need by `record_type`.

## Catalogues

### Two products share one SKU

Observed. The SKU search covers the text below, beside and above each photo, then the whole page. A labelled `SKU:` line anywhere in that zone wins. So a product with no printed code can receive a neighbour’s SKU. Check for repeats before you import:

```
from collections import Counter

counts = Counter(r["sku"] for r in rows if r["sku"])
repeated = [r for r in rows if r["sku"] and counts[r["sku"]] > 1]
```

### `--no-images` returns no products for a photo catalogue

Observed. Without an image folder, `build_catalog` cannot anchor rows on photos and falls back to table rows. A lookbook has no product table, so the result can be empty. Use `--images DIR` instead.

### `catalog_escalation(rows)` reports unreadable pages on a healthy catalogue

Observed. `build_catalog` removes the nearby text it kept for each row before returning, and `terbium.catalog.catalog_escalation` treats a row with an image and no nearby text as unreadable. Called on the returned rows, it reports every photo page. Rely on the message `build_catalog` prints itself, with `announce=True`.

### Pictures in a PowerPoint deck are missing

Observed. Only pictures placed directly on a slide become rows. Pictures inside a group shape, and pictures inserted into a picture placeholder, are skipped. Ungroup them, or paste them as plain pictures, and save the deck again.

### Extra options to `build_catalog` have no effect on a PowerPoint deck

Keyword arguments such as `only_photos` or `min_side` go to `export_images`, which handles PDF files. The PowerPoint (PPTX) path ignores them.

## Spreadsheets and CSV

### Formula cells come out empty

Observed. terbium reads the values that Excel stored the last time it calculated the sheet, not the formulas. A file written by a script, such as one made with openpyxl, may have no stored values, so its formula cells are empty and drop out of the records. Open and save the file in Excel or LibreOffice first.

### Rows past a certain point are missing

The adapters stop reading at 5,000 rows and 200 columns per Excel (XLSX) sheet, and at 20,000 rows per comma-separated values (CSV) file. Nothing is printed when this happens. Split larger files.

## Known limits

*   Table reconstruction from geometry runs only on PDF pages. Tables drawn with loose text boxes on a slide, or text in an image file, are not rebuilt into tables.
*   A table without a header row loses its first row: that row becomes the column headers.
*   Confidence measures a table’s shape, not the truth of its values. See [How it works](https://velofy.co/terbium/how-it-works/#trade-offs).
*   The HyperText Markup Language (HTML) output loads the Inter font from Google Fonts when it is opened, and falls back to the system font offline.
*   From the 0.10.0 changelog: image input to `o3-mini` (the `opus` tier on OpenAI) is unverified, model identifiers (IDs) are not verified against the provider application programming interfaces (APIs), and the Gemini lane uses `google-generativeai`, which Google has superseded with `google-genai`.
*   The repository has no continuous integration (CI). Tests run only when someone runs them locally.
