Skip to main content

Book 1 · The AI Engineer · 5 min read

RAG Project Structure: .env, .gitignore, and Intentional Design

Structure is the decision about where things belong, made once, so you never have to make it again. A pushed API key, a version conflict, a missing test — these are not accidents. They are the cost of decisions deferred.

RAG Project Structure: .env, .gitignore, and Intentional Design

Before the first line of code. Before the first embedding call. Before any logic is written.

The structure.

A senior developer named Drona had spent three years maintaining a codebase nobody else could navigate — not because it was complicated, but because it had grown without intention. Every feature added where there was space. Every config file placed where it was convenient. He inherited it and spent three months simply understanding where things were before he could change anything.

He made a rule:

"Structure is not organisation. Structure is the decision about where things belong, made once, so you never have to make it again."

The decision made once at the start costs nothing. The decision deferred costs three months of navigation, and a 2 AM debugging session where the file you need is somewhere in a directory that grew without a plan.

The Valid Failure: Configuration Errors That Look Like Logic Errors

A RAG system that returns wrong results because of a dependency mismatch produces failures that look identical to retrieval failures — wrong answers, missed products, confused context. Debugging the Grounding Layer when the environment is the actual problem means fixing the wrong thing. Every hour spent on the retrieval code is wasted when the real issue is a missing API key, a version conflict, or a dependency that silently installed the wrong version.

This failure is invisible precisely because it is environmental. The code looks correct. The logic looks correct. The system outputs wrong answers because the foundation was never verified.

The fix is not sophisticated. It is early.

The Structure, Decided Once

Seven lines, before any code runs:

shopbot/
├── data/           ← Product catalog. Evidence. The source of truth.
├── src/            ← All Python logic
├── evaluation/     ← Test cases and evaluation scripts
├── .env            ← API keys. Never committed. Never.
├── .gitignore
└── requirements.txt

Each folder has one job. data/ holds the catalog — not code, evidence. It earns its own directory because what goes in it is not logic, it is the raw material the Grounding Layer retrieves from. src/ holds all the logic that makes the system work. evaluation/ holds the test cases and scripts that measure whether the Grounding Layer is actually working — built from day one, not added after something goes wrong.

.gitignore Before the First Commit

The .gitignore file is created before any code, before any commit, before anything that could run.

.env
venv/
__pycache__/
chroma_db/
*.pyc
.DS_Store

.env is first in the list. This is not alphabetical — it is priority.

Arjun pushed an API key to a public repository once. The resulting thirty minutes — finding the key, rotating it, checking whether it had been used, explaining what had happened — were among the worst thirty minutes of a professional life that has had some genuinely bad days.

Once was enough.

chroma_db/ is in .gitignore for a different reason. The vector database folder contains gigabytes of binary index data that is entirely regenerable by running the ingestion script. It does not belong in version control. When the chunking strategy changes and the index is rebuilt, the old binary files are not part of the history — the product catalog in data/ and the chunker in src/ are.

Before running a single line of code, the first verification is:

git init
git status

The .env file must not appear in the list of tracked files. If it does, the .gitignore is not working and must be fixed before anything else. Not after the first commit. Before it.

.env: The Boundary Between Code and Secrets

OPENAI_API_KEY=sk-your-key-here

One file. One line. The API key never appears in any Python file — never hardcoded, never in a comment, never in a test script.

The code accesses it through python-dotenv:

from dotenv import load_dotenv
load_dotenv()  # reads .env into environment variables

A separate API key for development and production is not paranoia — it is the boundary that lets a development key be rotated without affecting anything live. Development keys get pasted into Slack messages. They get left in notebooks. They get committed to repositories despite .gitignore being present, because someone created a file outside the project root. A dedicated development key, when it leaks, is rotated in one action. A shared key is not.

requirements.txt: The Reproducible Environment

pip install langchain langchain-openai chromadb fastapi uvicorn python-dotenv
pip freeze > requirements.txt

pip freeze records exactly which version of every package is installed. Anyone who clones the repository and runs pip install -r requirements.txt gets an identical environment. No version drift. No "it works on my machine" conversations. No debugging sessions that turn out to be dependency mismatches three levels inside a dependency tree.

The virtual environment isolates the project from global Python packages — other projects' dependencies cannot break in, and ShopBot's dependencies cannot interfere with other projects. When the same code is deployed to a server, requirements.txt recreates the exact environment that was tested.

Verify Before Building

The setup is verified before any application logic is written:

# test_setup.py
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings

load_dotenv()
embeddings = OpenAIEmbeddings()
result = embeddings.embed_query("Does this kurta come in size S?")

print(f"Embedding dimensions: {len(result)}")

Output: Embedding dimensions: 1536

1,536 numbers. Not elegant — proof. The API key is found. The package is installed and the version matches. The network can reach OpenAI. The foundation is solid. Everything built on top of this result will inherit a verified base.

A misconfigured environment that appears correct is worse than an obviously broken one. The next nine chapters build on this output. Starting that build on an unverified environment means every failure that follows could be the environment, could be the logic, and takes twice as long to diagnose.

Structure Is Policy

The directory layout, the .gitignore, the .env pattern, the requirements.txt — none of these are sophisticated. All of them are decisions made once so they do not have to be made again.

The Pramana Framework extends to the environment as well as the retrieval. A system that retrieves valid knowledge must itself be built on a verified foundation. The .env file is the boundary between the system and its secrets. The .gitignore is the enforcement of that boundary. The requirements.txt is the guarantee that the verified environment can be recreated. The test script is the evidence that all three are working.

Drona had a second rule:

"The student who skips the fundamentals borrows time at high interest, paid later, when it hurts most."

The interest is paid at 2 AM, something broken, the file you need in a directory that grew without a plan.


This article covers concepts from Book 1, Chapter 2 of the RAG Mastery Series. The series builds ShopBot — a production RAG system — across four books, from first embeddings to cloud-native deployment.

This is the concept. The book is the system.

Book 1: The AI Engineer

Articles give you understanding. The book gives you a working system — full production code, RAGAS evaluation scores, and the patterns that hold up at 11 PM when something breaks.