How to Build a RAG Pipeline in Python

A RAG pipeline has four stages: chunk your documents, embed them, retrieve the most relevant chunks by cosine similarity, and stuff them into an LLM prompt as context. Each stage is simple - wiring them together correctly is the skill.

AI Engineerairagretrieval

The four-stage pipeline

Retrieval-Augmented Generation (RAG) grounds an LLM in a specific corpus so it answers from your data rather than from training memory. Every RAG system - from a basic prototype to a production knowledge tool - follows the same four stages:

  1. Chunk - split documents into retrievable pieces (~200 chars each)
  2. Index - embed all chunks into vectors (TF-IDF or dense embeddings)
  3. Retrieve - vectorize the question, rank chunks by cosine similarity, return top-k
  4. Generate - pass the retrieved chunks as a system message and call the LLM

Build it in Python

The simplest production-valid implementation uses TfidfVectorizer from scikit-learn. TF-IDF is offline, deterministic, and requires no external embedding API - a solid first choice for small-to-medium corpora.

import numpy as np
import requests
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

LLM_URL = "http://llm-service:8080/v1/chat/completions"

_vectorizer = None
_matrix = None
_chunks = []

def chunk(text):
    """Split a document into ~paragraph-sized chunks."""
    return [p.strip() for p in text.split("\n\n") if p.strip()] or [text.strip()]

def index(corpus):
    """Fit TF-IDF on all chunks and store the matrix."""
    global _vectorizer, _matrix, _chunks
    _chunks = [c for doc in corpus for c in chunk(doc)]
    _vectorizer = TfidfVectorizer().fit(_chunks)
    _matrix = _vectorizer.transform(_chunks)

def retrieve(question, k=2):
    """Return the top-k chunks most similar to the question."""
    qv = _vectorizer.transform([question])
    sims = cosine_similarity(qv, _matrix)[0]
    idxs = np.argsort(sims)[::-1][:k]
    return [_chunks[i] for i in idxs]

def answer(question):
    """Retrieve context and call the LLM."""
    context = "\n\n".join(retrieve(question, k=2))
    payload = {
        "model": "my-model",
        "messages": [
            {"role": "system", "content": f"Answer using ONLY the context below.\n\nContext:\n{context}"},
            {"role": "user", "content": question},
        ],
        "temperature": 0,
        "max_tokens": 80,
    }
    r = requests.post(LLM_URL, json=payload, timeout=60)
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"]

Call index(corpus) once at startup, then answer(question) for every query.

Why each stage matters

Upgrading the pipeline

Once this shape is working, each stage upgrades independently:

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

What is a RAG pipeline?

Retrieval-Augmented Generation (RAG) is a pattern where you retrieve relevant chunks from a corpus - by embedding the question and ranking by similarity - and pass those chunks as context to an LLM. The model answers from the retrieved text rather than training memory, which reduces hallucination and keeps answers grounded in your data.

How do I retrieve the most relevant chunks for a question?

Embed all chunks at index time (TF-IDF or dense vectors), embed the question at query time with the same vectorizer, compute cosine similarity between the question vector and the chunk matrix, then take the top-k chunks by score. In scikit-learn this is cosine_similarity(qv, matrix)[0] followed by argsort.

How do I pass retrieved context to an LLM?

Join the top-k chunks into a single string and send them as a system message: {"role": "system", "content": "Answer using ONLY the context below.\n\nContext:\n" + context}. Putting context in the system message keeps it separate from the user question and makes it easier to instruct the model to stay grounded.

Keep learning

Restore a Broken LLM API IntegrationAI/ML projectHarden an LLM Pipeline Against API FailuresAI/ML projectParse JSON From an LLM (Strip Markdown Fences)AI/ML projectAI/ML roadmapStep by step to hiredAI/ML interview questionsSTAR answersAll AI/ML projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →