Parsing documents before you chunk them

The step chunking assumes already happened - turning a scanned page, a multi-column PDF, or a table into text worth splitting.

Chunking, next, will tell you to split on headings and paragraphs, and will warn in passing that "PDFs love to emit column-shuffled text." This lesson is what happens before that warning matters: getting text out of a document in the first place, in a shape your chunker can trust. Skip this step and every later lesson in this course is tuning a pipeline that was fed garbage on day one.

What a naive parser throws away

Open any PDF library, call its default text extraction function, and it will hand you a string without complaint. That string is not the document. It is whatever order the glyphs happened to be drawn on the page, and a PDF does not store reading order - it stores positions. Working out that the left column comes before the right one, that a footnote is not part of the main paragraph, or that a page header is not a new sentence, is an inference the format never makes for you.

On a single column of plain prose, the inference is trivial and every extractor gets it right. On anything else, it starts failing in specific, predictable ways.

What the document hasWhat a naive extractor producesWhy
Two or more columnsText interleaved mid-sentence between columnsExtraction follows drawing order, not reading order
A tableA flat run of numbers with no row or column boundariesCell positions are geometry, not text structure
A repeated header or footerThe same fragment injected into the middle of paragraphs, once per pageThe extractor cannot tell a page furniture element from body text
A scanned pageNothing, or a handful of stray charactersThere is no text layer at all - only pixels
A slide or an infographicFragments in an order that matches nothingLayout carries the meaning; text alone does not

None of these are edge cases if your corpus is real-world documents. Contracts have footers. Financial reports have tables on every page. Support tickets get exported with screenshots. If your source documents are clean single-column text you can skip most of this lesson - but check that assumption before you assume it.

A chunk can only be as good as the text it was cut from. If the parser already lost the table, no chunk size and no embedding model fixes it.

Matching the tool to the document

There is no single parser that is always right, because the documents are not always the same shape. Think of it as four tiers, and pick the cheapest one that actually works on what you have.

Plain text extraction is the baseline every PDF library gives you for free. It is fine, genuinely fine, for clean single-column text with no tables worth keeping - a lot of internal wikis and plain reports fall here. Do not reach for anything fancier until you have looked at the output and found it wanting.

Layout-aware parsing is the step up. Libraries like Unstructured and LlamaParse, and PyMuPDF used carefully, detect the actual blocks on a page - a title, a paragraph, a list item, a table - and reconstruct reading order across columns instead of following draw order. They typically hand back structured elements rather than one long string, which is exactly what you want feeding into the heading-aware chunker in Chunking, next.

OCR solves a different problem: there is no text at all, only an image of text. A scanned invoice, a photographed whiteboard, a fax that got exported as a PDF of pixels - none of these have anything for a text extractor to extract. OCR reads the pixels and produces text, at which point you are back to needing a layout-aware pass on top of it, because OCR alone still does not know a table from a paragraph.

Vision-model page reading skips the pipeline and sends the page image itself to a model that can see layout and text together. It is the most expensive tier - a real model call per page - and the most tolerant of genuinely messy layouts: a form with fields scattered across the page, a slide with a chart and three text boxes, a table with merged cells that would confuse a rule-based parser.

Handles scanned pagesPreserves table structureReading order on columnsCostBest for
Plain text extractionNoNoPoorFree, instantClean single-column text
Layout-aware parserNo (needs OCR first)Yes, usuallyGoodA parsing library callStructured PDFs, reports, contracts
OCRYesNo, on its ownPoor, on its ownA per-page OCR callScans and photographs
Vision-model page readingYesYesYesA generation call per pageIrregular layouts, forms, mixed content

Most real pipelines end up using more than one tier: OCR to get text off a scan, then a layout-aware pass on the result, and a vision model reserved for the pages that still come out wrong. Measure which pages fail before deciding you need the expensive tier everywhere.

Tables, and knowing when parsing is the problem

Tables deserve their own paragraph because flattening one is the single most damaging thing a naive parser does. A paragraph that loses its formatting is still mostly readable. A table that loses its grid produces a run of numbers that looks exactly as confident as real data and means nothing - a chunk saying "14 30 90 250 500" with no header row is worse than no chunk at all, because it will retrieve, get pasted into a prompt, and get summarised as if it were true.

The fix is not clever: convert every table to markdown or JSON before it reaches the chunker, so each value stays attached to its row and column label. A layout-aware parser or a vision model can both produce this directly; a plain text extractor cannot, which is exactly why tables are where the cheap tier fails first.

import base64
import anthropic

client = anthropic.Anthropic()

with open("scanned-invoice.pdf", "rb") as f:
    pdf_data = base64.standard_b64encode(f.read()).decode()

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "document",
                "source": {
                    "type": "base64",
                    "media_type": "application/pdf",
                    "data": pdf_data,
                },
            },
            {
                "type": "text",
                "text": (
                    "Extract every table on this page as a markdown table. "
                    "Keep each column header attached to its values - do not "
                    "flatten rows into plain text."
                ),
            },
        ],
    }],
)

print(message.content[0].text)

This is the vision-model tier from section 2, made concrete: one page in, one markdown table out, headers intact. It costs a generation call per page, which is real money at volume - reserve it for the pages a cheaper parser gets wrong, not as your default for every document.

Keep this diagnostic habit for the rest of the course: before you touch chunk size, the embedding model, or the reranker, read fifty of the actual chunks your parser produced. If a number is in the wrong place, a table has no headers, or a paragraph starts mid-sentence with words from a different column, that is a parsing bug. No amount of retrieval tuning downstream will ever see the version of the document that was correct - it only ever sees what the parser handed over. Fix it at the source, and the rest of this course starts working on text worth searching.

WHAT YOU TAKE AWAY

  1. Read the raw extracted text before blaming chunking or the embedding model.
  2. Match the tool to the document: plain extraction, layout-aware parsing, OCR or a vision model.
  3. Convert every table to markdown or JSON before it reaches the chunker.
  4. Keep the page number and block type on every piece of extracted text.
  5. Treat garbled input as a parsing bug, not a retrieval bug - no downstream tuning fixes it.

RECALL NO SCROLLING BACK

00 / 05 answered

  1. QUESTION 01

    A two-column PDF page extracts as one interleaved paragraph, half of one column then half of the other. Why?

  2. QUESTION 02

    A pricing table gets flattened into one line of extracted text. What is actually lost?

  3. QUESTION 03

    When should you reach for OCR rather than a layout-aware parser?

  4. QUESTION 04

    Retrieval on your corpus looks bad. What should you check before touching chunk size or the embedding model?

  5. QUESTION 05

    What is the honest cost of sending a page image to a vision-capable model instead of a dedicated parsing library?