A RAG ingestion pipeline on clean Markdown

Convert mixed real-world documents to Markdown first, then chunk by headings — the simplest reliable RAG ingestion pattern, shown in Python.

Last updated

RAG quality is set at ingestion: garbage chunks in, garbage answers out. Converting every source document to Markdown FIRST gives you one clean, heading-structured format to chunk — regardless of whether the source was a PDF, a Word file or scraped HTML.

Pipeline sketch

import pathlib

import httpx

TYPES = {
    ".pdf": "application/pdf",
    ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    ".html": "text/html",
    ".txt": "text/plain",
}

def to_markdown(path: pathlib.Path) -> str:
    resp = httpx.post(
        "https://api.mdkit.online/v1/convert",
        files={"file": (path.name, path.read_bytes(), TYPES[path.suffix])},
        headers={"X-API-Key": "YOUR_KEY"},
        timeout=120,
    )
    resp.raise_for_status()
    return resp.json()["markdown"]

for doc in pathlib.Path("corpus").iterdir():
    markdown = to_markdown(doc)
    chunks = markdown.split("\n## ")  # chunk on second-level headings
    # …embed + index the chunks…

Scaling up

For a large corpus, switch the loop to the async lane: submit every document up front with a webhook_url, and index each one as its callback arrives — no polling loop, and files up to 50 MB.

Try it

Try it free.

FAQ

What is the best document format for RAG ingestion?
Markdown. Converting every source document to Markdown first gives you one heading-structured format to chunk, whatever the original was — which means one chunking strategy instead of one per file type.
Should I chunk by token count or by heading?
Chunk by heading first and only split oversized sections by tokens. Heading boundaries are semantic boundaries; a fixed token window will happily cut a table in half.
Why does ingestion quality matter more than the model?
Because retrieval can only return what you stored. A chunk that lost its table structure at ingestion is wrong in every answer that cites it, no matter which model reads it.