LlamaParse — document parsing
A PDF can look perfect to a human and still poison RAG. LlamaParse uses layout-aware parsing and OCR to turn tables, columns, and scans into usable markdown.
What you'll learn
- Why a PDF's visual layout is missing from ordinary text extraction
- How LlamaParse uses layout-aware parsing, OCR, and markdown output
- How a broken table becomes a wrong RAG answer, with real numbers
- How to validate parsing and diagnose failures before tuning retrieval
Before you start
At 3:07 a.m., a customer asks your support agent, “What is the Enterprise refund window?”
The answer is plainly printed in your pricing PDF: 30 days. Your embeddings are good. The retriever returns the right page. The language model confidently answers “no refund,” because the parser separated the table’s labels from its values before retrieval ever started.
This is the unglamorous part of RAG: a document can look perfectly readable in a browser and be nearly useless as machine input. LlamaParse is a document parser designed to preserve the structure that ordinary PDF extraction loses.
A PDF is not a text document
A PDF is closer to a set of painting instructions than to a Word document. It stores glyphs, images, fonts, and coordinates: put this character at this position, draw that line there, place this image on top. It may contain no explicit statement that words form a heading or that a number belongs in a table’s third column.
A text extractor reconstructs a text sequence from those instructions. It can work well on a simple, born-digital page, meaning one containing text characters rather than a scan. On a complicated page, it must guess:
- A table is two-dimensional, but extracted text is usually one-dimensional.
- Two columns need an invented reading order.
- A visually separate footer may be mixed into the article.
- A scanned page contains pixels, not characters, so it needs OCR, or optical character recognition.
Those guesses change the facts available to the model.
The pricing-table example
Suppose the visible table on page 4 is:
| Plan | Price | Refund |
|---|---|---|
| Enterprise | $499/month | 30 days |
| Starter | $99/month | None |
The values get their meaning from position. “$499/month” means something different beside Enterprise than beside Starter.
A naive extractor might produce:
Plan Price Refund Enterprise Starter 99 499 30 days None
The exact order varies by PDF and extractor. That variability is the problem: the output still contains the right words, but the associations are gone. A downstream system can no longer reliably tell whether Enterprise costs 99 or 499 dollars, or whether its refund window is 30 days or none.
Chunking can make the damage worse. If it splits the sequence between “Enterprise” and “499,” one chunk contains the plan and another contains the price. Retrieval may find the page but fail to return both pieces together. Generation then fills the gap with a plausible number.
RAG often fails this way—not with an obviously broken sentence, but with a fluent answer assembled from relationships that ingestion quietly destroyed.
Where the damage enters the RAG pipeline
The pipeline is often described as “load, split, embed, retrieve, generate.” The load step determines what information later stages are allowed to see.
The stages form a chain:
- The page contains visual relationships.
- Parsing turns the page into text and structure.
- Chunking chooses what travels together.
- Embeddings represent those pieces for search.
- Retrieval selects context for the prompt.
- The model writes from that context.
If step 2 separates “Enterprise” and “30 days,” a better embedding model cannot restore the missing edge. An embedding represents the text it receives, and a reranker can choose only among relationships present in retrieved chunks. Parsing quality is therefore a ceiling on retrieval quality.
What LlamaParse does
LlamaParse is a managed parser in the Llama ecosystem. You submit a document and receive content that can be loaded into LlamaIndex. For text-based RAG, you usually request markdown, which supports headings, lists, links, and table syntax.
Depending on the document and parsing mode, it can combine native PDF extraction, page rendering, OCR, and vision-language-model assistance. A vision-language model, or VLM, interprets images and language together. The useful mental model is visual: identify headings, columns, table boundaries, captions, and reading order before emitting text. The exact processing path can change, so do not assume every page uses the same route.
For the pricing example, the desired output is:
## Plans
| Plan | Price | Refund |
| --- | --- | --- |
| Enterprise | $499/month | 30 days |
| Starter | $99/month | None |
This is still text, not a relational database, and it cannot infer missing information. But it makes relationships explicit enough for a chunker and language model to use.
LlamaParse is the loader, not the embedding model, vector database, retriever, or answer generator. You still need to split, index, retrieve, and evaluate.
A minimal LlamaIndex pipeline
The Python package is available through llama_cloud_services. Make the
embedding and language-model providers explicit: LLAMA_CLOUD_API_KEY alone
does not configure the rest of this example.
python -m pip install \
"llama-cloud-services>=0.6,<0.7" \
"llama-index-core>=0.12,<0.13" \
"llama-index-llms-openai>=0.3,<0.4" \
"llama-index-embeddings-openai>=0.3,<0.4"
Set both secrets in your environment or deployment secret manager:
export LLAMA_CLOUD_API_KEY="your-llama-cloud-key"
export OPENAI_API_KEY="your-openai-key"
import os
from llama_cloud_services import LlamaParse
from llama_index.core import Settings, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.llm = OpenAI(model="gpt-4o-mini")
parser = LlamaParse(
api_key=os.environ["LLAMA_CLOUD_API_KEY"],
result_type="markdown",
)
documents = parser.load_data("pricing.pdf")
# Inspect parsed text before indexing it.
for document in documents:
print(document.get_content()[:800])
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=3)
response = query_engine.query("What is the Enterprise refund window?")
print(response)
The inspection loop belongs before indexing. If the output has interleaved columns or a mangled table, stop there. Indexing bad text only makes it harder to diagnose.
Validate before tuning retrieval
Treat parsing as an observable ingestion step. Start with representative files: a clean text PDF, a multi-column report, a table with merged cells, a scan, and a form. Save each original file beside its markdown result, with document and page metadata when available. This supports review, citations, and re-indexing without reparsing unchanged files.
Inspect hard structures rather than only the first paragraph:
- Do headings remain headings?
- Does each table row keep its values together?
- Are columns in the intended order?
- Are headers and footers being repeated into chunks?
- Do minus signs, decimals, currency symbols, and superscripts survive?
Only then chunk. Markdown provides useful boundaries but does not guarantee them. Keep a table’s title and header row with its data; for a large table, repeat the headers in each group of rows or store the table as a dedicated retrievable unit. For the pricing table, a useful unit contains the heading, header row, and both plan rows.
Build a small evaluation set of 10–30 questions with known answers. Include a table row, a footnote, a scanned page, and a value from a second column. For the running example, the expected answer is Enterprise, $499 per month, with a 30-day refund window. Check both the retrieved chunks and the final answer.
Debug in order: parsed markdown, indexed chunks, retrieval, then generation.
If the markdown is wrong, fix parsing or the source. If it is right but the
row is not retrieved, investigate chunking, embeddings, query construction,
metadata filters, top_k, or reranking. Changing the language model cannot
repair an association that disappeared during ingestion.
When LlamaParse is the wrong first choice
LlamaParse is not automatically better for every file.
A simple, born-digital, single-column PDF may be handled adequately by a local parser. A managed layout-aware service adds network latency, processing cost, an external data path, and an operational dependency. For a private corpus, confirm where documents are sent, how they are retained, and which contractual controls apply.
Use the heavier parser when layout carries meaning: financial tables, scanned contracts, scientific papers, forms, brochures, slide exports, or multi-column reports. Use a local path when the source is clean, the corpus is huge, latency matters, and tests show no meaningful parsing loss. Routing documents by type is often better than forcing every file through one parser.
There is a quality trade-off too. A vision-language model may infer a table boundary but misread a tiny character or merged cell. Markdown is a convenient interchange format, not proof that the interpretation is correct. For invoices, compliance records, or payment data, preserve the original and validate critical fields.
Failure modes you can see in production
A plausible value comes from the wrong row
Inspect the parsed markdown for the source page. Flattened rows or interleaved columns indicate a parsing or layout problem. If the markdown is correct but the retrieved chunk lacks the table heading, fix chunking and repeat headers. Temperature cannot restore a missing association.
OCR introduces small, expensive errors
Scans may turn O into zero, lose minus signs, or drop a digit from an account
number. Compare parsed text with the exact source page, improve the scan when
possible, and validate fields with domain rules. High-stakes values should
require citations and human confirmation.
Columns, sidebars, or tables are retrieved incorrectly
A sidebar can appear inside the main article when reading order is wrong. A correct table can still be split so that its rows lack headers. Inspect the markdown and the exact indexed chunks; separate sidebars when they cannot be represented reliably in one passage, and keep table headings with rows.
Ingestion silently fails
Password protection, damaged PDFs, rate limits, and transient network failures can produce an empty or partial result. Open the source independently, record the file and page that failed, retry transient errors with backoff, and cache successful results by document version. Never index an empty result as a successful parse.
In one breath
A PDF stores positioned drawing instructions, not dependable semantic structure. Ordinary extraction must guess reading order, so tables flatten, columns interleave, and scans need OCR. LlamaParse uses a layout-aware, often vision-assisted process to turn difficult pages into markdown that keeps headings, rows, and relationships visible. That helps LlamaIndex create better chunks and retrieve the right evidence.
But parsing is not retrieval, and markdown is not ground truth. Inspect parsed output, keep source metadata, chunk tables with their headers, cache results, and test real questions before tuning embeddings or prompts. If a relationship is lost during parsing, the rest of the RAG pipeline cannot reliably invent it back.
Quick check
Quick check
Next
For the larger indexing and retrieval picture, continue with LlamaIndex: indexes, query engines, and retrievers. For an agent framework with a different handoff and guardrail model, see the OpenAI Agents SDK.
Practice this in an interview
All questionsNodes are the retrievable pieces of source content, carrying text, metadata, and relationships. A query engine retrieves relevant nodes and synthesizes an answer; wrapping it in QueryEngineTool lets an agent choose that RAG pipeline as a tool.
Evaluation splits into retrieval quality (did we fetch the right chunks?) and generation quality (did the model use them correctly?). Key metrics are context precision/recall for retrieval and faithfulness plus answer relevance for generation. Frameworks like RAGAS automate LLM-as-judge scoring; human annotation anchors the ground truth.
RAG retrieves relevant external documents at query time and gives them to an LLM as context before generation. A basic pipeline ingests and chunks documents, embeds the chunks into a searchable store, retrieves the best matches for a question, and asks the LLM to answer from that evidence.
RAG couples a retrieval step — fetching relevant documents from an external store — with a generative model so the LLM can answer questions about knowledge it was never trained on. It solves the stale-knowledge and hallucination problems without retraining. The pattern is preferred when the knowledge base changes frequently or contains proprietary data.