LangChain and LlamaIndex PDF loaders
langchain-superpdf and llama-index-readers-superpdf: local PDFs as Markdown per page, split by section, with page numbers for citations in RAG.
superPDF AI turns PDFs into clean Markdown for retrieval-augmented generation (RAG): one document per page, with the page number in the metadata so answers can cite it. The loaders run on your computer; only the text you send to an embeddings model or LLM leaves it.
Why it suits RAG
- Headings are real Markdown headings (
#,##), so chunks can follow the document’s sections instead of a character count. - Tables stay tables, in Markdown or, with
tables="html", HTML that keeps merged header cells. - Charts become data: bar, line and pie charts drawn in the PDF are written as tables of their values.
- Scanned pages are read with OCR in 15 languages, so they don’t come back empty.
- Page numbers travel with every document and chunk, for citations.
Install
pip install langchain-superpdf # LangChain
pip install llama-index-readers-superpdf # LlamaIndexEach package installs superpdf with it. Node.js comes with that package; the first load installs the superPDF reader once (see Install ahead of time).
LangChain
Load a PDF
from langchain_superpdf import SuperPDFLoader
docs = SuperPDFLoader("report.pdf").load() # one Document per page
docs[0].page_content # "# Quarterly report\n\nRevenue grew in every region…"
docs[0].metadata
# {"source": "report.pdf", "total_pages": 4, "creationdate": "",
# "creator": "…", "producer": "…", "page": 0, "page_label": "1"}The metadata follows LangChain’s other PDF loaders: page counts from 0 and page_label from 1. SuperPDFLoader is a BaseLoader, so load(), lazy_load() and aload() all work.
| Option | Effect |
|---|---|
mode="single" | One Document for the whole PDF, pages joined with pages_delimiter (default "\n\f") |
pages="1-3" | Only these pages |
tables="html" | Tables as HTML, keeping merged header cells |
ocr=False | Skip OCR on scanned pages (faster; scanned pages come back empty) |
timeout=1200 | Seconds before giving up on one PDF (default 600) |
Load a folder
from pathlib import Path
docs = SuperPDFLoader(sorted(Path("papers").glob("**/*.pdf"))).load()For PDFs that arrive as bytes, such as uploads, parse a blob with SuperPDFParser:
from langchain_core.documents.base import Blob
from langchain_superpdf import SuperPDFParser
blob = Blob.from_data(pdf_bytes, path="upload.pdf")
docs = list(SuperPDFParser().lazy_parse(blob))Split by section
Split each page on its headings, and copy the page metadata onto every chunk so it can still be cited:
from langchain_text_splitters import MarkdownHeaderTextSplitter
splitter = MarkdownHeaderTextSplitter([("#", "title"), ("##", "section")])
chunks = []
for doc in docs:
for chunk in splitter.split_text(doc.page_content):
chunk.metadata.update(doc.metadata) # source and page
chunks.append(chunk)Search and cite
from langchain_core.vectorstores import InMemoryVectorStore
# embeddings: any LangChain embeddings model
store = InMemoryVectorStore.from_documents(chunks, embeddings)
for hit in store.similarity_search("revenue by region", k=3):
print(f"{hit.metadata['source']}, page {hit.metadata['page_label']}")Agents (LangGraph)
For agents, use superPDF AI’s MCP server: its 30 PDF tools load as LangChain tools through langchain-mcp-adapters and run in any LangGraph agent. The tools can only use the folders you pass with --allow-dir.
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient(
{
"superpdf": {
"command": "npx",
"args": ["-y", "@superpdf/cli", "mcp", "--allow-dir", "/Users/you/PDFs"],
"transport": "stdio",
}
}
)
tools = await client.get_tools()
agent = create_agent("anthropic:claude-sonnet-5-5", tools)LlamaIndex
Load a PDF
from llama_index.readers.superpdf import SuperPDFReader
docs = SuperPDFReader(tables="html").load_data("report.pdf")
docs[0].metadata # {"page_label": "1", "file_name": "report.pdf"}The metadata follows LlamaIndex’s own PDFReader. SuperPDFReader takes the same options as the LangChain loader, plus return_full_document=True for one Document per PDF.
Load a folder
from llama_index.core import SimpleDirectoryReader
reader = SimpleDirectoryReader("papers", file_extractor={".pdf": SuperPDFReader()})
docs = reader.load_data() # page_label, file_name and the usual file metadataNodes by section, then an index
from llama_index.core import VectorStoreIndex
from llama_index.core.node_parser import MarkdownNodeParser
nodes = MarkdownNodeParser().get_nodes_from_documents(docs)
nodes[2].metadata["header_path"] # "/Quarterly report/"
index = VectorStoreIndex(nodes) # uses Settings.embed_model
for hit in index.as_retriever(similarity_top_k=3).retrieve("revenue by region"):
print(hit.node.metadata["page_label"], hit.node.metadata["header_path"])Already using pip install superpdf?
The superpdf[langchain] and superpdf[llama-index] extras still work: from superpdf.langchain import SuperPDFLoader and from superpdf.llama_index import SuperPDFReader. Their metadata is simpler (source or file_path, and page counted from 1). New projects should use the two packages above, whose metadata matches each framework’s other PDF loaders.
What stays on your computer
superPDF AI reads every PDF on your computer; nothing is uploaded by the loaders. The text then goes wherever your pipeline sends it: to the embeddings model and LLM you configure. With local models, nothing leaves your computer at all.
Troubleshooting
| Problem | Fix |
|---|---|
ModuleNotFoundError: langchain_superpdf | pip install langchain-superpdf (or llama-index-readers-superpdf) into the Python you run; python -m pip install … makes sure. |
| The first load is slow | The first call installs the superPDF reader (about 220 MB) once. Run python -c "import superpdf; superpdf.install_cli()" ahead of time, for example in a Docker build. |
| Scanned PDFs are slow | OCR takes a few seconds a page. Pass ocr=False if you don’t need scanned pages, or pages= to read fewer. |
| Tables lose their merged headers | Pass tables="html". |
| An empty page | The page has no text layer and OCR was off or found no text. Check it with superpdf.read_text(path). |
Source: integrations on GitHub. Packages: langchain-superpdf and llama-index-readers-superpdf on PyPI.