Everything this course covered, in one script

This capstone introduces no new docling or LlamaIndex API. It combines pieces already built across the whole course:

  • Lesson 15: one reused DocumentConverter, driven over a whole folder.
  • Lesson 9: OCR, needed for scanned_invoice.pdf's missing text layer.
  • Lesson 8: table structure recognition, needed for quarterly_report.pdf's table.
  • Lesson 11: HybridChunker, structure-aware chunking instead of a naive character split.
  • Lesson 16: the docling-chunk-to-LlamaIndex-Document handoff, and the VectorStoreIndex to query engine loop.

The difference from Lesson 16 is scope: every file in sample_data/, five documents across four formats (a native-text PDF with a table, an image-only scanned PDF, a DOCX, a PPTX), one converter, one chunker, one index.

Proving the index actually spans every format

Four questions, each answerable from a different source file, confirm the index isn't secretly only working off the easiest file to convert:

  • North region hardware revenue growth: answer lives in quarterly_report.pdf, via TableFormer.
  • Main risk in the warehouse rollout: answer lives in project_plan.docx.
  • Mitigation for the conveyor motor risk: answer lives in team_update.pptx.
  • Total due on invoice 4471: answer lives in scanned_invoice.pdf, via OCR, no text layer to begin with.

If the retrieved source for each answer matches the format in that list, the whole pipeline, four formats in (five files, since research_note.pdf is converted too but not directly queried here), one Gemini-backed index, worked end to end.

The code, piece by piece

source_files = sorted(p for p in SAMPLE_DATA.iterdir() if p.is_file())
for source_path in source_files:
result = converter.convert(source_path)
for chunk in chunker.chunk(dl_doc=result.document):
documents.append(
Document(text=chunker.contextualize(chunk=chunk), metadata={"source": source_path.name})
)

No format-specific branching, no skip list, the same default DocumentConverter and HybridChunker handle a table-bearing PDF, a scanned PDF, a DOCX, and a PPTX identically. response.source_nodes on the query result carries back each retrieved chunk's metadata, so printing top_sources per answer is a direct check that retrieval actually pulled from the right file, not a lucky guess.

Setup

Needs a GOOGLE_API_KEY in .env at the project root, and makes real API calls (embeddings for indexing, generation for each answer), keep that in mind against a constrained quota.

Where to go from here

This capstone stops at a script that answers four hardcoded questions. A real next step would wrap build_index() in a small FastAPI service (the same shape as the pgvector course's capstone), swap the in-memory VectorStoreIndex for a persistent store so the index survives a restart, and add new documents incrementally instead of rebuilding from scratch on every run. Nothing about the docling side of this pipeline would need to change, docling's job already ends at producing well-structured, contextualized chunks, everything past that point is ordinary RAG infrastructure work covered in the pgvector and LangGraph courses.

Congratulations on completing the course.