Running CrewAI locally with n4n.ai and Claude Sonnet 4.5 gives you a reproducible multi-agent setup without managing multiple provider accounts or API keys. This tutorial walks through the complete flow: environment setup, gateway configuration, agent and task definition, execution, and verification. You’ll end up with a working crew that routes through a single OpenAI-compatible endpoint while n4n.ai handles provider fallback and usage metering behind the scenes.
Step 1: Prerequisites and environment setup
Start with a clean Python environment. CrewAI requires Python 3.10 or higher. Use venv or conda to isolate dependencies.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
Verify the Python version:
python --version
# Python 3.10.12 (or newer)
You’ll also need an n4n.ai account with an API key. Create one at the dashboard if you haven’t already. Keep the key handy — you’ll set it as an environment variable in the next step.
Step 2: Install CrewAI and dependencies
Install CrewAI with the tools extra for built-in utilities, plus the OpenAI client library (n4n.ai exposes an OpenAI-compatible interface).
pip install "crewai[tools]" openai python-dotenv
Confirm the install:
python -c "import crewai; print(crewai.__version__)"
# 0.80.0 (or newer)
Create a .env file in your project root to store credentials. Never commit this file.
cat > .env << 'EOF'
N4N_API_KEY=sk-your-n4n-key-here
N4N_BASE_URL=https://api.n4n.ai/v1
EOF
Step 3: Configure n4n.ai credentials and endpoint
CrewAI’s LLM abstraction accepts any OpenAI-compatible client. Point it at the n4n.ai base URL and pass your API key. This is where the crewai local n4n.ai claude sonnet 4.5 integration happens — the gateway handles model routing, so you reference the model by its n4n.ai identifier.
Create config.py to centralize the LLM factory:
# config.py
import os
from dotenv import load_dotenv
from crewai import LLM
load_dotenv()
def get_llm(model: str = "anthropic/claude-sonnet-4.5") -> LLM:
"""
Return a CrewAI LLM instance configured for n4n.ai.
The model string follows the n4n.ai catalog format: provider/model-name.
"""
return LLM(
model=model,
base_url=os.getenv("N4N_BASE_URL"),
api_key=os.getenv("N4N_API_KEY"),
temperature=0.3,
max_tokens=4096,
)
Test the configuration in a REPL:
python -c "
from config import get_llm
llm = get_llm()
resp = llm.call('Reply with only the word: pong')
print(resp)
"
# Expected: pong (or similar brief response)
If you see a response, the gateway connection works. If you get a 401, double-check the API key. A 404 on the model name means the identifier doesn’t match the n4n.ai catalog — list available models at https://api.n4n.ai/v1/models with your key.
Step 4: Define agents and tasks for a sample crew
Build a minimal two-agent crew: a researcher who gathers facts and a writer who summarizes them. This demonstrates task delegation and context passing between agents.
Create crew.py:
# crew.py
from crewai import Agent, Task, Crew, Process
from config import get_llm
llm = get_llm()
researcher = Agent(
role="Tech Researcher",
goal="Find concrete, up-to-date details about a given technical topic",
backstory=(
"You specialize in extracting specific facts — version numbers, "
"release dates, API changes — from documentation and changelogs. "
"You never hallucinate; you cite sources or say you don't know."
),
llm=llm,
verbose=True,
allow_delegation=False,
)
writer = Agent(
role="Technical Writer",
goal="Produce a concise, accurate summary for engineers",
backstory=(
"You turn raw research into a 150-word brief with clear headings. "
"You preserve technical precision and avoid marketing fluff."
),
llm=llm,
verbose=True,
allow_delegation=False,
)
research_task = Task(
description=(
"Research the key differences between CrewAI 0.70 and 0.80. "
"Focus on: agent memory changes, task output handling, and any "
"breaking API changes. List at least 3 specific changes with "
"version references."
),
expected_output="Bulleted list of 3+ specific changes with version context",
agent=researcher,
)
write_task = Task(
description=(
"Using the researcher's output, write a 150-word engineering brief "
"titled 'CrewAI 0.70 → 0.80 Migration Notes'. Include a one-line "
"summary, then three sections: Memory, Task Output, Breaking Changes."
),
expected_output="Formatted brief with title, summary, and three sections",
agent=writer,
context=[research_task], # passes researcher output to writer
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
process=Process.sequential,
verbose=True,
)
Step 5: Run the crew and verify output
Execute the crew from a script entry point. Create main.py:
# main.py
from crew import crew
if __name__ == "__main__":
result = crew.kickoff()
print("\n=== FINAL OUTPUT ===\n")
print(result.raw)
Run it:
python main.py
You should see verbose logs for each agent’s reasoning, tool use (if any), and final output. The run completes when the writer task finishes. Expected wall time: 15–40 seconds depending on gateway latency.
Verification checklist:
- Both agents log
Thought:/Action:/Observation:cycles in the terminal - Researcher output contains at least three version-specific bullets
- Writer output matches the requested structure (title, summary, three sections)
- No
LLM call failedorRate limiterrors appear — n4n.ai retries across providers automatically - Total token usage prints at the end (CrewAI logs this when
verbose=True)
Sample successful tail of output:
=== FINAL OUTPUT ===
# CrewAI 0.70 → 0.80 Migration Notes
**Summary:** CrewAI 0.80 introduces persistent agent memory, structured task outputs, and breaking changes to the `Crew` constructor signature.
## Memory
- 0.70: Stateless agents; memory required external vector stores.
- 0.80: Built-in `memory=True` flag enables short-term and long-term memory per agent.
## Task Output
- 0.70: Tasks returned raw strings; parsing was manual.
- 0.80: `TaskOutput` objects with `raw`, `json_dict`, and `pydantic` attributes.
## Breaking Changes
- `Crew(agents, tasks)` now requires `process=` keyword argument.
- `Agent(llm=...)` no longer accepts raw OpenAI client; must use `crewai.LLM` wrapper.
- `kickoff()` returns `CrewOutput` instead of raw string.
If the output is truncated or missing sections, increase max_tokens in config.py or refine the task expected_output to be more explicit.
Step 6: Observability and debugging tips
Token usage and cost tracking
n4n.ai returns standard OpenAI usage fields in each response. CrewAI aggregates these per run. To capture them programmatically:
# debug_usage.py
from crew import crew
from crewai.utilities.token_counter import TokenProcess
result = crew.kickoff()
print(f"Total tokens: {result.token_usage.total_tokens}")
print(f"Prompt tokens: {result.token_usage.prompt_tokens}")
print(f"Completion tokens: {result.token_usage.completion_tokens}")
Inspecting raw gateway responses
Enable HTTP debug logging to see the exact request/response between CrewAI and n4n.ai:
export OPENAI_LOG=debug
python main.py 2>&1 | head -100
This shows the model identifier sent (anthropic/claude-sonnet-4.5), the gateway’s provider routing decision, and any x-n4n-provider or x-n4n-fallback headers indicating automatic failover.
Handling rate limits and degradation
n4n.ai honors provider Retry-After headers and retries across healthy providers. If you hit a hard limit, the exception surfaces as openai.RateLimitError. Wrap kickoff() with tenacity for application-level retries:
# resilient_main.py
from tenacity import retry, stop_after_attempt, wait_exponential_jitter
from crew import crew
@retry(
wait=wait_exponential_jitter(initial=2, max=30),
stop=stop_after_attempt(3),
)
def run_with_retry():
return crew.kickoff()
if __name__ == "__main__":
result = run_with_retry()
print(result.raw)
Switching models for cost/latency tradeoffs
The get_llm() factory accepts any n4n.ai catalog model. For faster iteration, swap to a smaller model during development:
# In config.py or at call site
llm = get_llm("openai/gpt-4o-mini") # cheaper, faster for dev loops
# llm = get_llm("anthropic/claude-sonnet-4.5") # production quality
No code changes elsewhere — agents and tasks remain identical.
Step 7: Persisting crew state for reproducibility
CrewAI 0.80+ supports memory=True on agents and output_file on tasks. Enable both to persist intermediate state across runs:
# crew_with_memory.py
from crewai import Agent, Task, Crew, Process
from config import get_llm
llm = get_llm()
researcher = Agent(
role="Tech Researcher",
goal="...",
backstory="...",
llm=llm,
verbose=True,
memory=True, # enables persistent memory
)
research_task = Task(
description="...",
expected_output="...",
agent=researcher,
output_file="research_output.md", # writes result to file
)
On subsequent runs, the researcher recalls prior findings. The output_file gives you a version-controlled artifact for CI/CD gates or documentation pipelines.
Troubleshooting common issues
| Symptom | Likely Cause | Fix |
|---|---|---|
AuthenticationError |
Invalid or missing N4N_API_KEY |
Verify key in .env; ensure no trailing whitespace |
NotFoundError: model not found |
Model identifier mismatch | Query GET /v1/models at n4n.ai base URL; use exact id field |
TimeoutError on kickoff() |
Gateway latency spike | Increase timeout in LLM() constructor; enable verbose=True to see which call hangs |
| Output cuts off mid-sentence | max_tokens too low |
Raise max_tokens in get_llm(); Sonnet 4.5 supports 8192 output tokens |
| Agents repeat work | memory=True but no persistent store |
CrewAI uses in-memory storage by default; configure memory_config with a vector DB for cross-process persistence |
Next steps
You now have a working crewai local n4n.ai claude sonnet 4.5 pipeline. From here:
- Add tools (web search, file I/O, code execution) via
crewai_toolsor custom@toolfunctions - Parallelize independent tasks with
Process.hierarchicaland a manager agent - Wire n4n.ai usage webhooks into your cost dashboard for per-crew accounting
- Containerize the project with a
Dockerfilethat bakes the.envat runtime via secrets
The gateway abstraction means you can swap models, add fallback policies, or enforce org-wide rate limits without touching agent code. That’s the leverage.