CrewAI environment variables setup is the first thing that breaks when you move from a notebook to a real deployment. The framework reads from a handful of standard variables, but the documentation scatters them across provider-specific pages. This tutorial collects every variable you actually need, shows where each one takes effect, and gives you a reproducible .env pattern that survives CI/CD and multi-provider routing.
Prerequisites
- Python 3.10 or newer
pip install crewai python-dotenv- Access to at least one LLM provider (OpenAI, Anthropic, or an OpenAI-compatible gateway)
- A terminal and a text editor
Verify your baseline:
python -c "import crewai; print(crewai.__version__)"
Expected output (version will differ):
0.80.0
The minimum viable .env
Create a project directory and add a .env file with only the variables your chosen provider requires. CrewAI uses python-dotenv automatically when you import crewai, so no extra loading code is necessary.
mkdir crewai-env-demo && cd crewai-env-demo
cat > .env << 'EOF'
# OpenAI
OPENAI_API_KEY=sk-...
# Anthropic
ANTHROPIC_API_KEY=sk-ant-...
# Optional: force a specific model for all agents
# OPENAI_MODEL_NAME=gpt-4o-mini
EOF
Test that the variables load:
# test_env.py
import os
from dotenv import load_dotenv
load_dotenv()
print("OPENAI_API_KEY:", "set" if os.getenv("OPENAI_API_KEY") else "missing")
print("ANTHROPIC_API_KEY:", "set" if os.getenv("ANTHROPIC_API_KEY") else "missing")
print("OPENAI_MODEL_NAME:", os.getenv("OPENAI_MODEL_NAME", "not set (defaults to gpt-4o)"))
python test_env.py
Expected output:
OPENAI_API_KEY: set
ANTHROPIC_API_KEY: set
OPENAI_MODEL_NAME: not set (defaults to gpt-4o)
Provider-specific variables CrewAI respects
CrewAI delegates to LiteLLM under the hood, so every variable LiteLLM documents works here. The ones you will actually use in production:
| Variable | Purpose | Required for |
|---|---|---|
OPENAI_API_KEY |
OpenAI authentication | OpenAI, Azure OpenAI, any OpenAI-compatible endpoint |
OPENAI_API_BASE |
Override base URL | Proxies, gateways, local models (vLLM, Ollama) |
OPENAI_ORGANIZATION |
Org ID for usage attribution | OpenAI multi-org accounts |
ANTHROPIC_API_KEY |
Anthropic authentication | Claude models |
ANTHROPIC_API_BASE |
Override base URL | Anthropic-compatible proxies |
GROQ_API_KEY |
Groq authentication | Groq-hosted models |
COHERE_API_KEY |
Cohere authentication | Command models |
GEMINI_API_KEY |
Google AI Studio authentication | Gemini models |
VERTEXAI_PROJECT |
GCP project ID | Vertex AI |
VERTEXAI_LOCATION |
GCP region | Vertex AI |
AZURE_API_KEY |
Azure OpenAI key | Azure OpenAI |
AZURE_API_BASE |
Azure endpoint | Azure OpenAI |
AZURE_API_VERSION |
API version string | Azure OpenAI |
Model selection variables
These apply globally unless overridden per-agent:
# .env additions
OPENAI_MODEL_NAME=gpt-4o-mini # default for OpenAI-compatible calls
ANTHROPIC_MODEL_NAME=claude-3-5-sonnet-20241022 # default for Anthropic
You can also set LITELLM_MODEL_NAME as a universal fallback, but explicit provider variables are clearer.
Per-agent model overrides
Environment variables set defaults. For agent-level control, pass the model string directly in the Agent constructor. The format follows LiteLLM’s provider/model convention.
# agents.py
from crewai import Agent
researcher = Agent(
role="Researcher",
goal="Find the latest data on token pricing",
backstory="You read API changelogs for fun.",
llm="openai/gpt-4o-mini", # explicit override
verbose=True,
)
writer = Agent(
role="Writer",
goal="Summarize findings in a table",
backstory="You love markdown tables.",
llm="anthropic/claude-3-5-sonnet-20241022",
verbose=True,
)
Run a quick smoke test:
# smoke.py
from crewai import Crew, Task
from agents import researcher, writer
crew = Crew(
agents=[researcher, writer],
tasks=[
Task(description="List three factors that affect LLM token pricing", agent=researcher),
Task(description="Format the answer as a markdown table", agent=writer),
],
verbose=True,
)
result = crew.kickoff()
print(result.raw)
python smoke.py
Expected output (truncated):
## Task 1: List three factors that affect LLM token pricing
**Agent:** Researcher
**Output:** 1. Model provider and tier (e.g., GPT-4o vs GPT-4o-mini)
2. Input vs output token ratios
3. Context window size and caching policies
## Task 2: Format the answer as a markdown table
**Agent:** Writer
**Output:** | Factor | Description |
|--------|-------------|
| Model provider and tier | Different models have different per-token costs... |
Routing through an OpenAI-compatible gateway
If you run multiple providers behind a single endpoint — for fallback, cost routing, or cache control — point OPENAI_API_BASE at that gateway and keep OPENAI_API_KEY as your gateway credential. CrewAI will send all openai/* model calls there.
# .env for gateway routing
OPENAI_API_BASE=https://api.n4n.ai/v1
OPENAI_API_KEY=n4n-...
OPENAI_MODEL_NAME=gpt-4o-mini
The gateway honors client routing directives (e.g., model=anthropic/claude-3-5-sonnet) and forwards provider cache-control hints, so you get cross-provider fallback without changing agent code.
# gateway_test.py
from crewai import Agent, Crew, Task
agent = Agent(
role="Analyst",
goal="Compare latency across providers",
backstory="You measure things.",
llm="anthropic/claude-3-5-sonnet-20241022", # routed through gateway
verbose=True,
)
crew = Crew(
agents=[agent],
tasks=[Task(description="Return the string 'routed'", agent=agent)],
verbose=True,
)
print(crew.kickoff().raw)
python gateway_test.py
Expected output:
## Task 1: Return the string 'routed'
**Agent:** Analyst
**Output:** routed
The request hits the gateway, which forwards to Anthropic and returns the response — all via the OpenAI-compatible path.
Azure OpenAI configuration
Azure requires three variables plus a deployment-name-as-model pattern.
# .env for Azure
AZURE_API_KEY=your-azure-key
AZURE_API_BASE=https://your-resource.openai.azure.com
AZURE_API_VERSION=2024-06-01
# Model name must match your deployment name exactly
OPENAI_MODEL_NAME=gpt-4o-deployment
# azure_test.py
from crewai import Agent, Crew, Task
agent = Agent(
role="Azure Tester",
goal="Confirm Azure routing works",
backstory="You validate cloud configs.",
llm="azure/gpt-4o-deployment", # prefix with azure/
verbose=True,
)
crew = Crew(agents=[agent], tasks=[Task(description="Say hello from Azure", agent=agent)], verbose=True)
print(crew.kickoff().raw)
Local models via Ollama or vLLM
Point OPENAI_API_BASE at your local server and use a dummy key.
# .env for local
OPENAI_API_BASE=http://localhost:11434/v1
OPENAI_API_KEY=ollama
OPENAI_MODEL_NAME=llama3.1:8b
# local_test.py
from crewai import Agent, Crew, Task
agent = Agent(
role="Local Tester",
goal="Run on llama3.1",
backstory="You run offline.",
llm="openai/llama3.1:8b",
verbose=True,
)
crew = Crew(agents=[agent], tasks=[Task(description="Reply with 'local ok'", agent=agent)], verbose=True)
print(crew.kickoff().raw)
Structuring .env for multiple environments
Don’t commit secrets. Use a .env.example in version control and load environment-specific files at runtime.
project/
├── .env.example # committed, no secrets
├── .env.development # gitignored
├── .env.staging # gitignored
├── .env.production # gitignored
└── config/
└── settings.py # loads the right file
# config/settings.py
import os
from pathlib import Path
from dotenv import load_dotenv
ENV = os.getenv("APP_ENV", "development")
env_path = Path(__file__).parents[1] / f".env.{ENV}"
if env_path.exists():
load_dotenv(env_path, override=True)
else:
load_dotenv(override=True) # falls back to .env
# Validate required vars
REQUIRED = ["OPENAI_API_KEY"]
missing = [v for v in REQUIRED if not os.getenv(v)]
if missing:
raise RuntimeError(f"Missing required environment variables: {missing}")
# Export typed config for the rest of the app
class Settings:
openai_api_key: str = os.getenv("OPENAI_API_KEY", "")
openai_api_base: str = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
openai_model: str = os.getenv("OPENAI_MODEL_NAME", "gpt-4o-mini")
anthropic_api_key: str = os.getenv("ANTHROPIC_API_KEY", "")
anthropic_model: str = os.getenv("ANTHROPIC_MODEL_NAME", "claude-3-5-sonnet-20241022")
settings = Settings()
Usage in your crew definition:
# crew_definition.py
from crewai import Agent
from config.settings import settings
researcher = Agent(
role="Researcher",
goal="Fetch data",
backstory="...",
llm=f"openai/{settings.openai_model}",
# or override per agent:
# llm="anthropic/claude-3-5-sonnet-20241022",
)
Debugging variable resolution
When an agent picks the wrong model, trace the resolution order:
- Explicit
llm=argument on theAgent OPENAI_MODEL_NAME/ANTHROPIC_MODEL_NAME/LITELLM_MODEL_NAME- LiteLLM defaults (
gpt-4ofor OpenAI,claude-3-5-sonnet-20241022for Anthropic)
Add temporary logging to see what LiteLLM receives:
# debug_litellm.py
import litellm
litellm.set_verbose=True # prints request/response to stderr
from crewai import Agent, Crew, Task
agent = Agent(role="Debug", goal="Show model", backstory="...", llm="openai/gpt-4o-mini")
crew = Crew(agents=[agent], tasks=[Task(description="Hi", agent=agent)], verbose=True)
crew.kickoff()
Run it and watch stderr for the resolved model string and base URL.
Common pitfalls
Variable not loaded — Ensure python-dotenv is installed and you import crewai (which calls load_dotenv) after the .env file exists. In scripts, call load_dotenv() explicitly before any CrewAI import.
Wrong model used — Check for trailing whitespace in .env values. OPENAI_MODEL_NAME=gpt-4o-mini (with space) fails silently.
Azure deployment name mismatch — The model string must be azure/<deployment-name>, not the base model name.
Gateway returns 401 — Verify the gateway expects the key in Authorization: Bearer header. Some proxies use X-API-Key; set OPENAI_API_KEY to the raw token and the gateway handles translation.
Rate limits bubble up as exceptions — CrewAI retries with exponential backoff via LiteLLM. Configure LITELLM_MAX_RETRIES and LITELLM_RETRY_DELAY if defaults don’t match your SLA.
# .env additions for retry tuning
LITELLM_MAX_RETRIES=3
LITELLM_RETRY_DELAY=2
Validation checklist before deploy
-
.env.examplecontains every variable the app reads, with placeholder values - No secrets in version control (run
git secrets --scanortrufflehog) - Each environment has its own
.env.<env>file loaded bysettings.py -
APP_ENVis set in the deployment target (container, VM, serverless) - Smoke test runs against each configured provider in CI
- Observability captures
modelandprovidertags on every request
Next steps
You now have a reproducible crewai environment variables setup that works across local, staging, and production. From here:
- Add per-agent
llmoverrides for cost/quality tradeoffs - Implement a fallback chain in your gateway so
openai/gpt-4o→anthropic/claude-3-5-sonnet→groq/llama3-70bdegrades gracefully - Meter per-token usage by parsing LiteLLM’s response
usagefield and emitting custom metrics
The pattern scales: one .env convention, one settings loader, and explicit per-agent model strings keep the configuration visible and testable.