n4nAI

CrewAI setup tutorial: environment variables explained

Configure CrewAI environment variables for API keys, model routing, and provider fallbacks with a complete working example you can run today.

n4n Team3 min read731 words

Audio narration

Coming soon — every post will get a voice note here.

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:

  1. Explicit llm= argument on the Agent
  2. OPENAI_MODEL_NAME / ANTHROPIC_MODEL_NAME / LITELLM_MODEL_NAME
  3. LiteLLM defaults (gpt-4o for OpenAI, claude-3-5-sonnet-20241022 for 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.example contains every variable the app reads, with placeholder values
  • No secrets in version control (run git secrets --scan or trufflehog)
  • Each environment has its own .env.<env> file loaded by settings.py
  • APP_ENV is set in the deployment target (container, VM, serverless)
  • Smoke test runs against each configured provider in CI
  • Observability captures model and provider tags 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 llm overrides for cost/quality tradeoffs
  • Implement a fallback chain in your gateway so openai/gpt-4oanthropic/claude-3-5-sonnetgroq/llama3-70b degrades gracefully
  • Meter per-token usage by parsing LiteLLM’s response usage field 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.

Tagscrewain4n-aisetupconfiguration

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All crewai getting started with n4n.ai posts →