Two libraries, one clean handoff

MarkItDown's job is narrow: turn a file into a Markdown string. It has no idea what happens to that string next. LlamaIndex's Document is just as narrow from the other direction: it wraps a string (plus optional metadata) so an index can chunk, embed, and retrieve it. The integration between them is nothing more than passing one library's output into the other's constructor:

result = md.convert(path)
document = Document(text=result.markdown, metadata={"source": path.name})

This lesson assumes basic familiarity with the LlamaIndex course, Lessons 1-4 (Settings, Document, VectorStoreIndex, query engines), it's a soft prerequisite the same way this course notes the LiteParse course's existence, see that course if any of Settings.llm, VectorStoreIndex.from_documents, or .as_query_engine() are unfamiliar.

What each library is actually responsible for

StepLibraryWhat happens
File to MarkdownMarkItDown.convert(path).markdown string
Markdown to DocumentNeither, just PythonWrap the string, attach metadata={"source": ...}
Documents to searchable indexLlamaIndexVectorStoreIndex.from_documents(documents), embeds and stores each
Question to answerLlamaIndex.as_query_engine().query(question), retrieve + synthesize

By the time VectorStoreIndex.from_documents() runs, it has no idea the text came from a .docx, a .pptx, or a plain .txt file, MarkItDown's involvement already ended.

Why office_notice.png is left out again

Same reasoning as Lesson 5: this lesson's MarkItDown() instance has no LLM client attached, so converting the image fixture here would add almost no useful text to the index. Lesson 12's capstone brings the image back in with captioning wired up, so it can actually be found by a query.

The code, piece by piece

Settings.llm = GoogleGenAI(model="gemini-3.5-flash-lite", api_key=API_KEY)
Settings.embed_model = GoogleGenAIEmbedding(model_name="models/gemini-embedding-001", api_key=API_KEY)

Exactly the models and configuration confirmed working in the LlamaIndex course, set once, globally, before any indexing happens.

for filename in SOURCE_FILES:
result = md.convert(FIXTURES_DIR / filename)
documents.append(Document(text=result.markdown, metadata={"source": filename}))

The handoff itself: convert, wrap, collect. metadata={"source": filename} means every retrieved chunk can be traced back to its original file, visible in response.source_nodes later.

index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query(question)

From here it's pure LlamaIndex, unrelated to MarkItDown at all.

Checkpoint

  • The integration is just a handoff: Document(text=result.markdown, ...), neither library needs to know about the other beyond that.
  • MarkItDown's job ends at a string: chunking, embedding, and retrieval are entirely LlamaIndex's responsibility.
  • metadata={"source": ...}: carries the original filename through indexing so retrieved results stay traceable.
  • Soft prerequisite: this lesson assumes the LlamaIndex course's Lessons 1-4 (Settings, Document, VectorStoreIndex, query engines) are already familiar.

If anything here still feels unclear, ask before moving to Lesson 11.