Module 1 · RAG Foundations

Setting Up a LangChain Project

Lesson 2 of 23 10 min read Updated

A LangChain project is an ordinary Python project that uses a few specialised packages to talk to language models, embedding models and vector stores. Getting the setup right at the start saves hours later. In this lesson you will create an isolated environment, install the right packages, store secrets safely, and run a first chain that works even without an API key.

Overview: how a LangChain project is organised

LangChain is not one big package any more. It is split into several packages so you install only what you need. Knowing this split is the key to avoiding most beginner errors.

Package What it contains
langchain-core The base building blocks: prompts, messages, output parsers, the Runnable interface, the | operator (LCEL), and fake models for testing.
langchain Higher-level helpers and chains built on top of core.
langchain-openai (and other partner packages) Integrations for one provider, such as chat models and embeddings. Other providers have their own packages.
langchain-community A large collection of community-maintained integrations: document loaders, vector stores and more.
langgraph The library for building agent workflows as graphs. We use it later in the course.
python-dotenv Not part of LangChain. It reads a .env file into environment variables.

Why split the packages? Provider SDKs change often. If everything lived in one package, every provider update would force a new release of everything. With partner packages, you install langchain-openai only when you use OpenAI, and the core stays small and stable.

A typical RAG (Retrieval-Augmented Generation) project needs four things working together:

  1. A Python virtual environment, so package versions do not clash with other projects.
  2. Packages for core abstractions plus your chosen model provider.
  3. Secrets (API keys) loaded from environment variables, never written in code.
  4. A configuration module that creates models in one place, so the rest of your code does not care which provider is used.

What is a virtual environment?

A virtual environment (venv) is a private folder that holds its own copy of Python packages. Project A can use one version of a library while project B uses another. LangChain moves fast, so isolation matters a lot.

What are environment variables?

An environment variable is a named value kept by your operating system for a running program. Libraries such as langchain-openai automatically look for OPENAI_API_KEY. Keeping keys here means they never appear in your source code or in Git.

Key concepts and the project layout

A simple layout that scales well for the rest of this course:

rag-project/
  .venv/              # virtual environment (not committed)
  .env                # secrets (not committed)
  .gitignore
  requirements.txt    # pinned dependencies
  check_setup.py      # verifies the install
  config.py           # creates models in one place
  hello_chain.py      # first chain
  • .env holds secrets and settings. It must be listed in .gitignore.
  • requirements.txt records exact versions so another person (or your server) can recreate the environment.
  • config.py is the single place that decides which model to use. Everything else imports from it.

Examples

Example 1: Create the environment and install packages

Run these commands in a terminal. They need Python 3.9 or newer; check the current minimum on the LangChain documentation, because it can change.

mkdir rag-project
cd rag-project
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install langchain langchain-core langchain-openai langchain-community python-dotenv
pip freeze > requirements.txt

On Windows, activate with .venv\Scripts\activate instead of the source line. After activation your prompt usually shows (.venv). The last command saves the exact installed versions into requirements.txt. Later, pip install -r requirements.txt rebuilds the same environment.

Example 2: Store secrets in a .env file

Create a file named .env in the project root. Use your own values; the ones below are placeholders.

OPENAI_API_KEY=your-key-here
OPENAI_MODEL=your-chat-model-name

Model names change often, so we do not hard-code one. Check your provider’s documentation for the current name and put it in OPENAI_MODEL. Also add .env and .venv to .gitignore so they are never committed.

Example 3: Verify the installation

This script prints the installed version of each package and says whether a key is present. It never prints the key itself.

import os
from importlib.metadata import PackageNotFoundError, version

from dotenv import load_dotenv

load_dotenv()

packages = [
    "langchain",
    "langchain-core",
    "langchain-openai",
    "langchain-community",
    "python-dotenv",
]

for name in packages:
    try:
        print(f"{name}: {version(name)}")
    except PackageNotFoundError:
        print(f"{name}: NOT INSTALLED")

has_key = bool(os.environ.get("OPENAI_API_KEY"))
print("OPENAI_API_KEY set:", has_key)

Output (illustrative; your version numbers will differ):

langchain: 0.x.y
langchain-core: 0.x.y
langchain-openai: 0.x.y
langchain-community: 0.x.y
python-dotenv: 1.x.y
OPENAI_API_KEY set: False

load_dotenv() reads .env and puts its lines into os.environ. importlib.metadata.version asks Python which version of an installed distribution is present. Printing only True or False for the key is a safe habit: logs should never contain secrets.

Example 4: A configuration module with an offline fallback

Not everyone has an API key, and tests should not call a paid service. LangChain ships fake models in langchain_core for exactly this. The module below returns a real model when a key exists, and a fake one otherwise.

import os

from dotenv import load_dotenv
from langchain_core.embeddings import DeterministicFakeEmbedding
from langchain_core.language_models.fake_chat_models import FakeListChatModel

load_dotenv()


def get_chat_model():
    """Return a real chat model if a key is configured, else a fake one."""
    if os.environ.get("OPENAI_API_KEY"):
        from langchain_openai import ChatOpenAI

        return ChatOpenAI(model=os.environ["OPENAI_MODEL"], temperature=0)
    return FakeListChatModel(responses=["(offline) This is a fake answer."])


def get_embeddings():
    """Return fake embeddings. Swap in a real provider later."""
    return DeterministicFakeEmbedding(size=8)


if __name__ == "__main__":
    emb = get_embeddings()
    print(len(emb.embed_query("hello")))
    print(len(emb.embed_documents(["a", "b"])))

Output:

8
2

DeterministicFakeEmbedding turns any text into a vector of the given size. The same text always gives the same vector, but the numbers have no real meaning. So it is good for testing your pipeline’s plumbing, but not for judging search quality. The import of langchain_openai sits inside the if, so the module works even if that package is missing.

Example 5: Your first chain

Now we build a chain with LCEL (LangChain Expression Language). LCEL joins components with the | operator, so the output of one step becomes the input of the next.

from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

model = FakeListChatModel(responses=["LangChain connects models, prompts and data."])

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a concise tutor."),
        ("human", "Explain {topic} in one sentence."),
    ]
)

chain = prompt | model | StrOutputParser()

print(chain.invoke({"topic": "LangChain"}))

Output:

LangChain connects models, prompts and data.

With a real model the sentence would differ and vary between runs, so real output is illustrative only. The fake model returns the next string from its list each time, which makes the result exact and repeatable.

Under the hood: what happens in chain.invoke

  1. You pass a dictionary {"topic": "LangChain"}. The prompt template fills {topic} and produces a list of messages (one system message, one human message).
  2. The model receives those messages. A real chat model sends them to the provider over HTTPS using your API key; the fake model just picks its next canned response. Either way it returns an AIMessage object.
  3. StrOutputParser takes that message and returns only its text as a plain Python string.

Each piece is a Runnable: it has the same methods (invoke, batch, stream). The | operator just builds a new Runnable that runs the pieces in order. This shared interface is why you can replace the fake model with a real one without changing the rest of the chain.

Common Mistakes

1. Hard-coding the API key

from langchain_openai import ChatOpenAI

# WRONG: the key is now in your source code and in Git history
model = ChatOpenAI(model="some-model", api_key="sk-real-looking-key")

Anyone who sees your repository can use your key and run up your bill. Fix: put the key in .env, add .env to .gitignore, and let the library read OPENAI_API_KEY from the environment. If a key leaks, revoke it at once in the provider dashboard.

2. Using old import paths

# WRONG: old style from early LangChain versions
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate

Many tutorials online still use these imports. They are deprecated or removed in current releases and raise errors or warnings. Fix: import from the partner package and from core, as in this lesson: from langchain_openai import ChatOpenAI and from langchain_core.prompts import ChatPromptTemplate.

3. Installing into the wrong Python

If you forget to activate the venv, pip install goes to your system Python, and your script then fails with ModuleNotFoundError. Check with which python (or where python on Windows); the path should point inside .venv. A safe habit is to run python -m pip install ... so pip and Python always match.

4. Calling load_dotenv() too late

If you create a model before load_dotenv() runs, the key is not yet in the environment. Call it once at the top of your entry module, or in config.py and import models from there.

Best Practices

  • Create one virtual environment per project and never commit it.
  • Pin dependencies in requirements.txt, and upgrade deliberately, reading release notes. LangChain changes quickly.
  • Install only the partner packages you use.
  • Keep secrets in .env locally and in your platform’s secret manager in production.
  • Provide a .env.example file with placeholder values so teammates know which variables are needed.
  • Create models in one module (config.py) so changing provider is a one-file change.
  • Use fake models and fake embeddings in unit tests: they are free, fast and deterministic.
  • Never print or log keys; log only whether they are set.
  • Check prices, quotas and current model names on your provider’s website, because they change often.

Practice Exercises

  1. Verify your setup. Create the project folder and virtual environment, install the packages, and run check_setup.py. Add langgraph to the list and install it. Hint: use pip install langgraph, then add its name to the packages list.
  2. Change the prompt. In the first chain, add a second input variable {audience} (for example “beginners”) to the human message and pass it in invoke. What error do you get if you forget to pass it?
  3. Add a .env.example. Write a .env.example file and a .gitignore that excludes .env and .venv. Then extend config.py so that a missing OPENAI_MODEL gives a clear error message instead of a KeyError.

Summary

  • LangChain is split into langchain-core, langchain, partner packages, langchain-community and langgraph; install only what you need.
  • Always work inside a virtual environment and pin versions in requirements.txt.
  • Keep API keys in environment variables loaded from a git-ignored .env file.
  • Centralise model creation in config.py so providers can be swapped easily.
  • Fake chat models and fake embeddings let you learn and test without an API key.
  • LCEL chains join Runnables with |: prompt, then model, then output parser.
  • Use current imports from langchain_core and partner packages, not old tutorial paths.

Progress is saved in this browser.