n4nAI

Debugging CrewAI n4n.ai connection errors

Step-by-step guide to diagnose and fix CrewAI connection errors when using n4n.ai as the LLM gateway, with runnable verification scripts.

n4n Team5 min read1,140 words

Audio narration

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

CrewAI n4n.ai connection error troubleshooting starts with understanding that most failures aren’t mysterious — they’re configuration mismatches between what CrewAI expects and what the gateway actually receives. The OpenAI-compatible endpoint at n4n.ai accepts standard Chat Completions requests, but CrewAI’s default client initialization, environment variable handling, and model naming conventions can each introduce subtle mismatches. This guide walks through the most common failure modes in the order you should check them, with verification steps at each stage so you know exactly where the break occurs.

Step 1: Verify the raw endpoint works before adding CrewAI

Before importing CrewAI, confirm the gateway responds to a minimal Chat Completions request. This isolates network, authentication, and model-availability issues from framework code.

curl -s https://api.n4n.ai/v1/chat/completions \
  -H "Authorization: Bearer $N4N_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{"role": "user", "content": "ping"}],
    "max_tokens": 5
  }' | jq .

Success looks like a JSON response with choices[0].message.content. If you get a 401, the API key is invalid or missing. A 404 on the model means the model slug is wrong — list available models at https://api.n4n.ai/v1/models. A 429 or 503 means the provider is rate-limited or degraded; the gateway will automatically fall back to another provider for that model, but the first request may still return an error if all upstreams are saturated.

Verification: The jq output shows a non-empty content field. Save the raw response; you’ll compare it against what CrewAI logs later.

Step 2: Check environment variable precedence

CrewAI reads OPENAI_API_KEY, OPENAI_API_BASE, and OPENAI_MODEL_NAME from the environment, but it also accepts explicit constructor arguments. The precedence order is: constructor args > environment variables > defaults. A common mistake is setting OPENAI_API_BASE to https://api.n4n.ai (missing /v1) while passing api_key explicitly — the base URL gets ignored because the constructor argument wins for the key but not for the base.

# Wrong: base URL missing /v1, and env var may be ignored
import os
os.environ["OPENAI_API_BASE"] = "https://api.n4n.ai"  # missing /v1
os.environ["OPENAI_API_KEY"] = "sk-..."  # may not be used if you pass api_key=

from crewai import Agent, LLM

llm = LLM(
    model="openai/gpt-4o-mini",
    api_key="sk-...",  # explicit arg wins, but base_url still defaults to OpenAI
)
# Correct: pass everything explicitly or rely entirely on env vars
from crewai import LLM

llm = LLM(
    model="openai/gpt-4o-mini",
    base_url="https://api.n4n.ai/v1",  # include /v1
    api_key="sk-...",
)

Verification: Add a debug print before creating the agent:

print(f"base_url: {llm.base_url}")
print(f"model: {llm.model}")

Confirm base_url ends with /v1 and model matches a slug from /v1/models.

Step 3: Use the correct model slug format

CrewAI’s LLM class expects a model identifier that the underlying LiteLLM library can route. For n4n.ai, the slug is openai/<model-id> where <model-id> is the provider-specific identifier (e.g., gpt-4o-mini, anthropic/claude-3-5-sonnet-20241022, meta-llama/llama-3.1-70b-instruct). The openai/ prefix tells LiteLLM to use the OpenAI-compatible client, which is what n4n.ai exposes.

Common mistakes:

  • Using just gpt-4o-mini — LiteLLM tries the real OpenAI API
  • Using n4n/gpt-4o-mini — no such provider prefix exists
  • Using the display name from the dashboard instead of the API slug
# Correct slugs for n4n.ai
MODEL_SLUGS = {
    "gpt-4o-mini": "openai/gpt-4o-mini",
    "claude-3.5-sonnet": "openai/anthropic/claude-3-5-sonnet-20241022",
    "llama-3.1-70b": "openai/meta-llama/llama-3.1-70b-instruct",
}

llm = LLM(
    model=MODEL_SLUGS["gpt-4o-mini"],
    base_url="https://api.n4n.ai/v1",
    api_key="sk-...",
)

Verification: Enable LiteLLM debug logging to see the exact request being sent:

import litellm
litellm.set_verbose=True  # logs request/response to stderr

Run a minimal completion:

response = llm.call("Say 'ok' if you receive this.")
print(response)

The logs will show POST https://api.n4n.ai/v1/chat/completions with the correct model field. If you see api.openai.com instead, your base_url isn’t being respected — go back to Step 2.

Step 4: Handle CrewAI’s agent and task initialization order

CrewAI constructs the LLM instance when you create an Agent, not when you create the LLM object. If you reuse an LLM instance across agents but mutate its attributes after the first agent is created, the change may not propagate. Create a fresh LLM per agent, or configure all attributes before the first Agent instantiation.

# Anti-pattern: mutating after first agent
llm = LLM(model="openai/gpt-4o-mini", base_url="https://api.n4n.ai/v1", api_key="sk-...")
agent1 = Agent(role="Researcher", llm=llm, ...)
llm.model = "openai/anthropic/claude-3-5-sonnet-20241022"  # too late for agent1
agent2 = Agent(role="Writer", llm=llm, ...)  # may still use old model
# Correct: configure fully before any Agent, or create separate LLM instances
def make_llm(model_slug: str):
    return LLM(
        model=model_slug,
        base_url="https://api.n4n.ai/v1",
        api_key="sk-...",
    )

researcher = Agent(role="Researcher", llm=make_llm("openai/gpt-4o-mini"), ...)
writer = Agent(role="Writer", llm=make_llm("openai/anthropic/claude-3-5-sonnet-20241022"), ...)

Verification: Add a callback to log which model each agent actually uses:

from crewai import Agent
from crewai.llms.base_llm import BaseLLM

class DebugLLM(BaseLLM):
    def __init__(self, wrapped: BaseLLM, label: str):
        self.wrapped = wrapped
        self.label = label
    
    def call(self, messages, **kwargs):
        print(f"[{self.label}] Calling model: {self.wrapped.model}")
        return self.wrapped.call(messages, **kwargs)

# Wrap each agent's LLM
researcher.llm = DebugLLM(researcher.llm, "researcher")
writer.llm = DebugLLM(writer.llm, "writer")

Run a one-task crew and confirm the printed model matches your intent.

Step 5: Inspect request/response headers for routing hints

n4n.ai forwards provider cache-control hints and routing metadata in response headers. When a request hits a rate limit or provider degradation, the gateway may route to a fallback provider — this appears as a different x-provider header value. CrewAI doesn’t surface these headers by default, but you can intercept them with a custom HTTP client.

import httpx
from crewai import LLM

class LoggingClient(httpx.Client):
    def send(self, request, **kwargs):
        response = super().send(request, **kwargs)
        print(f"Status: {response.status_code}")
        print(f"Provider: {response.headers.get('x-provider')}")
        print(f"Cache: {response.headers.get('x-cache')}")
        print(f"Fallback: {response.headers.get('x-fallback')}")
        return response

custom_client = LoggingClient()
llm = LLM(
    model="openai/gpt-4o-mini",
    base_url="https://api.n4n.ai/v1",
    api_key="sk-...",
    http_client=custom_client,  # CrewAI passes this to LiteLLM
)

Verification: Run a request that you know triggers a fallback (e.g., exceed a low rate limit on a test key). You should see x-fallback: true and a different x-provider than the primary. If you never see fallback headers under load, the gateway may be returning 429/503 directly — check your account’s fallback configuration.

Step 6: Debug streaming and tool-calling failures

CrewAI enables streaming by default for agents. n4n.ai supports SSE streaming, but some upstream providers don’t, and the gateway will buffer the response. If your crew hangs on the first agent response, it’s often a streaming timeout. Disable streaming to isolate:

llm = LLM(
    model="openai/gpt-4o-mini",
    base_url="https://api.n4n.ai/v1",
    api_key="sk-...",
    # CrewAI passes these to LiteLLM
    stream=False,
    timeout=60,  # seconds
)

Tool calling (function calling) requires the model to support it. Not all models on n4n.ai expose function-calling capability even if the upstream does. Test tool calling in isolation:

from crewai import Agent, Task, Crew
from pydantic import BaseModel

class WeatherTool(BaseModel):
    location: str

def get_weather(location: str) -> str:
    return f"Sunny in {location}"

researcher = Agent(
    role="Weather Reporter",
    llm=llm,
    tools=[get_weather],
)

task = Task(
    description="Get the weather in Tokyo",
    agent=researcher,
    expected_output="Weather report",
)

crew = Crew(agents=[researcher], tasks=[task], verbose=True)
result = crew.kickoff()
print(result)

If the agent responds with text instead of invoking the tool, the model either doesn’t support tools or the tool schema wasn’t transmitted correctly. Check the LiteLLM debug logs (Step 3) for tools in the request payload.

Verification: With litellm.set_verbose=True, confirm the request includes a tools array with your function schema. The response should have tool_calls in choices[0].message. If it’s missing, try a known tool-capable model like openai/gpt-4o-mini or openai/anthropic/claude-3-5-sonnet-20241022.

Step 7: Validate per-token usage metering headers

n4n.ai returns usage metadata in both the response body (usage) and headers (x-usage-prompt-tokens, x-usage-completion-tokens, x-usage-total-tokens). CrewAI’s LLM.call() returns only the text content, discarding the usage object. If you need token counts for cost tracking, wrap the call:

from crewai import LLM
from typing import Any

class MeteredLLM(LLM):
    def call(self, messages: list[dict], **kwargs) -> str:
        # Access the underlying LiteLLM completion call
        import litellm
        
        response = litellm.completion(
            model=self.model,
            messages=messages,
            api_base=self.base_url,
            api_key=self.api_key,
            **kwargs,
        )
        
        usage = response.usage
        print(f"Tokens — prompt: {usage.prompt_tokens}, completion: {usage.completion_tokens}, total: {usage.total_tokens}")
        
        # Also check headers if available
        if hasattr(response, '_hidden_params'):
            headers = response._hidden_params.get('response_headers', {})
            print(f"Header usage: {headers.get('x-usage-total-tokens')}")
        
        return response.choices[0].message.content

llm = MeteredLLM(
    model="openai/gpt-4o-mini",
    base_url="https://api.n4n.ai/v1",
    api_key="sk-...",
)

Verification: Run a known token count (e.g., “Count to ten” ≈ 10 completion tokens). Compare the printed totals against the header values — they should match. A discrepancy suggests the gateway’s metering differs from the provider’s, which matters for billing reconciliation.

Step 8: Test with a minimal CrewAI script end-to-end

Combine the verified pieces into a single runnable script. This is your “known good” baseline — if this works, any subsequent failure is in your business logic, not the connection.

#!/usr/bin/env python3
"""
Minimal CrewAI + n4n.ai verification script.
Run: python verify_crewai_n4n.py
"""
import os
import litellm
from crewai import Agent, Task, Crew, LLM

# 1. Enable debug logging
litellm.set_verbose = True

# 2. Configuration — replace with your values
API_KEY = os.getenv("N4N_API_KEY") or "sk-YOUR_KEY_HERE"
BASE_URL = "https://api.n4n.ai/v1"
MODEL = "openai/gpt-4o-mini"

# 3. Create LLM with explicit config
llm = LLM(
    model=MODEL,
    base_url=BASE_URL,
    api_key=API_KEY,
    stream=False,
    timeout=60,
)

# 4. Simple agent and task
researcher = Agent(
    role="Verifier",
    goal="Confirm the connection works",
    backstory="You verify LLM connectivity.",
    llm=llm,
    verbose=True,
)

task = Task(
    description="Respond with exactly: 'CONNECTION_OK'",
    agent=researcher,
    expected_output="CONNECTION_OK",
)

# 5. Run
crew = Crew(agents=[researcher], tasks=[task], verbose=True)
result = crew.kickoff()

# 6. Verify
output = str(result).strip()
print(f"\n=== RESULT ===")
print(output)
assert output == "CONNECTION_OK", f"Expected 'CONNECTION_OK', got: {output}"
print("✓ Verification passed")

Verification: Run the script. You should see:

  1. LiteLLM request logs showing POST https://api.n4n.ai/v1/chat/completions
  2. The agent’s thought process (verbose=True)
  3. Final output CONNECTION_OK
  4. The assertion passes

If any step fails, re-run the individual verification from Steps 1-7 to isolate.

Step 9: Common error codes and their fixes

Error Cause Fix
401 Unauthorized Invalid or missing API key Regenerate key at n4n.ai dashboard; ensure no trailing whitespace
404 Not Found on /v1/models Wrong base URL Must be https://api.n4n.ai/v1 exactly
400 Bad Request: model not found Wrong model slug Use openai/<provider-model-id> from /v1/models
429 Too Many Requests Rate limit exceeded Implement exponential backoff; gateway falls back but first request may fail
503 Service Unavailable All upstreams degraded Retry with backoff; check status page
Timeout Request exceeded timeout Increase timeout in LLM(); disable streaming
AttributeError: 'NoneType' object has no attribute 'choices' Empty response Usually a 5xx that LiteLLM didn’t raise; enable debug logs

Step 10: Automate health checks in CI/CD

Add a lightweight health check to your deployment pipeline that runs the verification script (Step 8) against a staging API key. This catches configuration drift — someone changes the base URL, rotates the key, or switches the default model — before it reaches production.

# .github/workflows/crewai-healthcheck.yml
name: CrewAI n4n.ai Health Check
on:
  schedule:
    - cron: '0 */6 * * *'  # every 6 hours
  workflow_dispatch:

jobs:
  healthcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install crewai litellm httpx pyyaml
      - env:
          N4N_API_KEY: ${{ secrets.N4N_STAGING_KEY }}
        run: python verify_crewai_n4n.py

Verification: The workflow passes (green check) when the connection is healthy. On failure, the logs show exactly which step failed — use the error code table in Step 9 to triage.


You now have a repeatable process: verify the raw endpoint, lock down environment precedence, use correct model slugs, initialize agents in the right order, inspect headers for routing behavior, debug streaming and tools, capture usage metrics, and codify it all in an automated health check. The next time a CrewAI n4n.ai connection error appears in your logs, you’ll know exactly which step to start from.

Tagscrewain4n-aitroubleshootingsetup

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 →