Convert to markdown with chapter headings preserved

LangChain expects documents, not raw bytes. The cleanest way to integrate Docling is to wrap it in a BaseLoader. The loader parses the PDF, exports to markdown, and yields a single LCDocument with metadata attached. Why markdown? Because heading syntax gives the splitter natural boundaries.

booktutor.py
python
from typing import Iterator
from langchain_core.documents import Document as LCDocument
from langchain_core.document_loaders import BaseLoader

class DoclingBookLoader(BaseLoader):
    def __init__(self, file_path: str) -> None:
        self.file_path = file_path
        # converter setup from the previous lesson
        self.converter = build_converter()

    def lazy_load(self) -> Iterator[LCDocument]:
        docling_doc = self.converter.convert(self.file_path).document
        text = docling_doc.export_to_markdown()
        metadata = {
            'source': self.file_path,
            'format': 'book',
        }
        yield LCDocument(page_content=text, metadata=metadata)

lazy_load yields documents instead of returning a list. That keeps memory low for very long books. The single yield here is fine because we want the splitter to see the whole book as one continuous markdown stream.

Why markdown headings matter

The splitter uses blank lines and newlines as natural chunk boundaries. Headings give you that structure for free.

Good instinct, but the splitter already handles that. Passing one document lets RecursiveCharacterTextSplitter use markdown structure (blank lines, newlines) to cut on natural boundaries, while still producing chunks of controlled size. Splitting into one document per chapter too early locks you out of fine-grained chunking inside long chapters.

Quiz: Quiz

Loading practice…