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:
- A Python virtual environment, so package versions do not clash with other projects.
- Packages for core abstractions plus your chosen model provider.
- Secrets (API keys) loaded from environment variables, never written in code.
- 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
.envholds secrets and settings. It must be listed in.gitignore.requirements.txtrecords exact versions so another person (or your server) can recreate the environment.config.pyis 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
- You pass a dictionary
{"topic": "LangChain"}. The prompt template fills{topic}and produces a list of messages (one system message, one human message). - 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
AIMessageobject. StrOutputParsertakes 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
.envlocally and in your platform’s secret manager in production. - Provide a
.env.examplefile 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
- Verify your setup. Create the project folder and virtual environment, install the packages, and run
check_setup.py. Addlanggraphto the list and install it. Hint: usepip install langgraph, then add its name to thepackageslist. - Change the prompt. In the first chain, add a second input variable
{audience}(for example “beginners”) to the human message and pass it ininvoke. What error do you get if you forget to pass it? - Add a .env.example. Write a
.env.examplefile and a.gitignorethat excludes.envand.venv. Then extendconfig.pyso that a missingOPENAI_MODELgives a clear error message instead of aKeyError.
Summary
- LangChain is split into
langchain-core,langchain, partner packages,langchain-communityandlanggraph; 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
.envfile. - Centralise model creation in
config.pyso 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_coreand partner packages, not old tutorial paths.
