RAG stands for Retrieval Augmented Generation. Instead of asking an LLM to answer from its training data alone, you feed it relevant documents at query time so it can ground its response in your actual data.
The typical flow:
Running everything locally means:
# Core dependencies
ollama # LLM + embeddings
qdrant-client # vector store
langchain # document loading + chunking
PyPDF2 # PDF parsing
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
loader = PyPDFLoader("your_doc.pdf")
pages = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_documents(pages)
import ollama
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance, PointStruct
client = QdrantClient(":memory:")
client.create_collection(
collection_name="docs",
vectors_config=VectorParams(size=768, distance=Distance.COSINE)
)
points = []
for i, chunk in enumerate(chunks):
response = ollama.embeddings(model="nomic-embed-text", prompt=chunk.page_content)
points.append(PointStruct(id=i, vector=response["embedding"], payload={"text": chunk.page_content}))
client.upsert(collection_name="docs", points=points)
def query(question: str) -> str:
q_vec = ollama.embeddings(model="nomic-embed-text", prompt=question)["embedding"]
results = client.search(collection_name="docs", query_vector=q_vec, limit=3)
context = "\n\n".join([r.payload["text"] for r in results])
response = ollama.chat(
model="llama3.1:8b",
messages=[{
"role": "user",
"content": f"Answer based on the context below.\n\nContext:\n{context}\n\nQuestion: {question}"
}]
)
return response["message"]["content"]
print(query("What are the main findings?"))
The system works surprisingly well for technical documents. Retrieval quality depends heavily on chunk size — I found 500 tokens with 50 overlap to be a solid baseline.
The whole pipeline runs in under 2 seconds on my machine with an RTX 3060.
Next step: add a proper Streamlit UI and persistent storage so I can query different documents without re-embedding every time.