Retrieval-Augmented Generation (RAG) with Python

Lesson 9 of 9

Final Project: Build Your Own Mini RAG Pipeline

Every piece from this course now exists on its own: collecting documents, chunking, embedding, storing vectors, embedding a query, retrieving, augmenting, and generating an answer. This final project assembles all of it into one complete, working RAG pipeline.

Requirements

Your finished rag_pipeline.py should satisfy every one of these:

  1. Collect at least 3 source "documents" as plain strings with a bit of metadata (raw_documents).
  2. Chunk every document with chunk_text(), tuning chunk_size/overlap for your content.
  3. Embed every chunk (toy_embed(), or a real embedding model, see stretch goals) and store it in a VectorStore along with its source metadata.
  4. Accept a natural-language query and embed it with the exact same embedding function used for the chunks.
  5. Retrieve the top-K most similar chunks with retrieve()/cosine_similarity().
  6. Build an augmented prompt with build_prompt() that includes the retrieved context and instructs the model to answer only from it.
  7. Feed the prompt to an LLM (generate_answer(), a real OpenAI or OpenRouter call) and print the grounded answer.

Bringing every earlier lesson's code together in order (collect → chunk → embed → store → query → retrieve → augment → generate) is the whole pipeline:

def run_pipeline(query, raw_documents, top_k=2):
    # 1-4: build the knowledge base
    store = VectorStore()
    for doc in raw_documents:
        for chunk in chunk_text(doc['text']):
            store.add(chunk, toy_embed(chunk), metadata={'source': doc['source']})

    # 5: embed the query
    query_vector = toy_embed(query)

    # 6: retrieve
    retrieved = retrieve(store, query_vector, top_k=top_k)

    # 7: augment
    prompt = build_prompt(query, retrieved)

    # 8: generate (needs a real API key, see the LLM Response lesson)
    return generate_answer(prompt)

print(run_pipeline('How do I reset my password?', raw_documents))

Stretch goals

  • Swap toy_embed() for a real embedding model (OpenAI's text-embedding-3-small, or a local model via the sentence-transformers package) for genuinely semantic, not just word-overlap, retrieval.
  • Swap the in-memory VectorStore for a real vector database like FAISS (runs locally, no account needed) or Pinecone.
  • Add source citations to the final answer, e.g. "According to faq.md: ...", using each retrieved chunk's metadata.
  • Try a smarter chunking strategy, split on paragraphs or sentences instead of a fixed word count, and compare retrieval quality.
  • Add a simple cache so an identical query doesn't re-embed or re-call the LLM twice.

Submit a link to your finished project (a repo or gist) below, an instructor will review it before you can mark this lesson complete.

This lesson ends in a project. Build it on your own machine, there's nowhere to submit it here, but the brief above is everything you need.