PDF to Markdown in Python: a RAG-ready pipeline
Convert a PDF to Markdown with page-numbered chunks, embed them and run semantic search, in about 40 lines of Python.
Large language models read Markdown far better than raw PDF text. In this guide you convert a PDF to clean Markdown with the AnyToMarkdown API, get RAG-ready chunks with page numbers, and embed them for semantic search.
What you need
- Python 3.9+ and
pip install requests openai numpy - A RapidAPI key: subscribe to the free plan on AnyToMarkdown
- An
OPENAI_API_KEYfor the embedding step
1. Convert the PDF and get chunks
import requests
HOST = "anytomarkdown-pdf-to-markdown-and-document-parser-api.p.rapidapi.com"
headers = {"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY", "X-RapidAPI-Host": HOST}
with open("handbook.pdf", "rb") as f:
r = requests.post(
f"https://{HOST}/v1/convert",
headers=headers,
files={"file": f},
params={"includeChunks": "true", "includeContent": "false", "chunkSize": 400, "chunkOverlap": 40},
timeout=180,
)
r.raise_for_status()
chunks = r.json()["chunks"]
print(len(chunks), "chunks")
includeContent=false skips the full Markdown because you only need the chunks. Each chunk looks like this:
{ "index": 1, "content": "...", "tokens": 392, "headings": ["Employee Handbook", "2. Benefits"], "pageStart": 2, "pageEnd": 2 }
Chunks follow the document's sections: they never start in the middle of a table, and each one carries its heading path, so a chunk about vacation days still says it belongs to Benefits.
2. Embed the chunks
from openai import OpenAI
import numpy as np
client = OpenAI()
texts = [c["content"] for c in chunks]
response = client.embeddings.create(model="text-embedding-3-small", input=texts)
vectors = np.array([item.embedding for item in response.data])
Keep headings and pageStart next to each vector. They let your answers cite a section and page.
3. Search
def search(question, top=3):
q = np.array(client.embeddings.create(model="text-embedding-3-small", input=question).data[0].embedding)
scores = vectors @ q / (np.linalg.norm(vectors, axis=1) * np.linalg.norm(q))
for i in scores.argsort()[::-1][:top]:
c = chunks[i]
print(f"{scores[i]:.2f} {' > '.join(c['headings'])} (page {c['pageStart']})")
print(c["content"][:200], "\n")
search("How many vacation days do employees get?")
Tips
- Scanned PDF? Leave
ocronauto, or force it withocr=force. - Only need some pages? Add
pages=1-10. - The free plan accepts 10 MB and 50 pages per request; higher plans raise the limits.
- Want to see the Markdown first? Try the free PDF to Markdown converter.
Try it without code: the free online converter uses the same engine.Open the converter