The crewai n4n.ai function calling integration is straightforward because n4n.ai exposes an OpenAI-compatible chat endpoint, so CrewAI’s LLM client can target it without custom adapters. This post walks through standing up a multi-agent crew that calls a custom tool via function calling against that gateway, using any of the 240+ available models with automatic provider fallback.
Step 1: Install dependencies
You need CrewAI (which bundles LangChain dependencies) and a way to load env vars. I use python-dotenv and requests for the example tool.
pip install crewai python-dotenv requests
Pin versions if you’re in production: CrewAI moves fast. At time of writing, crewai==0.30.0 works with the LLM class shown below. If you’re on an older version, you may need to wrap ChatOpenAI from LangChain instead—but the base URL override works identically.
Step 2: Configure environment and model selection
Create a .env file. The gateway uses a single API key for all models behind the OpenAI-compatible interface.
# .env
N4N_API_KEY=sk-your-key-here
N4N_MODEL=gpt-4o-mini
Load it early in your script:
from dotenv import load_dotenv
load_dotenv()
Model names follow the gateway’s catalog. Because the crewai n4n.ai function calling integration relies on the model string being passed straight through to the chat completions endpoint, pick a model that supports function calling. Most OpenAI and Anthropic models do; smaller distilled models may not emit valid tool calls consistently. Keep the key out of source control—.env should be gitignored, and in CI you inject it as a secret.
Step 3: Define a function-calling tool
CrewAI tools are just Python functions with type hints and a docstring. The docstring becomes the tool description; the signature becomes the JSON schema. Be precise—vague descriptions produce missed calls.
import requests
from crewai.tools import tool
@tool("Get current weather")
def get_weather(city: str) -> str:
"""Fetch the current air temperature in Celsius for a given city.
Args:
city: Name of the city, e.g., "Tokyo" or "San Francisco".
Returns:
A human-readable string with the temperature.
"""
geo = requests.get(
f"https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1",
timeout=10,
).json()
if not geo.get("results"):
return f"Unknown city: {city}"
lat = geo["results"][0]["latitude"]
lon = geo["results"][0]["longitude"]
weather = requests.get(
f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t=temperature_2m",
timeout=10,
).json()
temp = weather["current"]["temperature_2m"]
return f"{temp}°C in {city}"
Schema requirements
The function-calling loop expects city to be a string. If you need integers or enums, annotate them explicitly. CrewAI converts the signature via LangChain’s tool parser, so Optional and default values are respected but keep the docstring in sync. Test the tool standalone before wiring it into an agent:
print(get_weather.run("Tokyo"))
A clean standalone run avoids wasting tokens debugging the agent loop. If the tool raises, wrap the HTTP calls in try/except and return a descriptive error string—CrewAI will pass that back to the model, which can then retry or report failure.
Step 4: Point CrewAI’s LLM at the gateway
CrewAI’s LLM wrapper accepts base_url, mirroring the OpenAI client. This is the core of the crewai n4n.ai function calling integration.
from crewai import LLM
import os
llm = LLM(
model=os.getenv("N4N_MODEL", "gpt-4o-mini"),
base_url="https://api.n4n.ai/v1",
api_key=os.getenv("N4N_API_KEY"),
temperature=0.1,
timeout=60,
max_retries=2,
)
The endpoint is OpenAI-compatible, so function calling payloads are forwarded as-is. The gateway honors client routing directives and forwards provider cache-control hints, meaning you can append headers or use model suffixes to pin a provider if you need determinism. For most crews, the default round-robin with automatic fallback is fine. Set temperature low for tool-heavy workflows; high randomness degrades argument extraction.
Step 5: Build the agent and task
An agent without a task is dead weight. Define a researcher that must use the tool, and a task that forces a tool call.
from crewai import Agent, Task, Crew
researcher = Agent(
role="Weather Researcher",
goal="Answer weather questions accurately using the provided tool",
backstory="You never guess temperatures; you always call the weather tool.",
tools=[get_weather],
llm=llm,
verbose=True,
allow_delegation=False,
)
task = Task(
description="What is the current temperature in Tokyo? Use the tool to find out.",
expected_output="A sentence stating the current temperature in Tokyo in Celsius.",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[task], verbose=True)
Why verbosity matters
Set verbose=True on both agent and crew during development. You’ll see the raw function-call JSON, the tool result, and the final synthesis. In production, drop it to False and ship logs to your observability stack. If you scale to multiple agents, verbosity is the only way to trace which agent called which tool when a result looks wrong.
Step 6: Run and verify success
Execute the script:
python main.py
Verification checklist:
- Tool invocation: The agent’s logs show a
get_weathercall with{"city": "Tokyo"}. - Tool result: The log prints
{"return": "23.4°C in Tokyo"}(or similar live data). - Final answer:
crew.kickoff()returns a string containing the temperature. - Token metering: If you have dashboard access to the gateway, confirm a completed chat completion with tool-call tokens accounted for.
If the chosen provider is degraded, the gateway’s fallback returns a valid response from another provider, so the crew still prints a result. That’s the main operational benefit of routing through an OpenAI-compatible proxy instead of hardcoding api.openai.com.
Step 7: Production hardening
Before shipping, add a retry wrapper around the tool and a guard on the city argument:
@tool("Get current weather")
def get_weather(city: str) -> str:
if not city or len(city) > 50:
return "Invalid city name."
try:
# ... existing requests code ...
except requests.RequestException as e:
return f"Tool error: {e}"
Run crews inside a worker queue rather than a synchronous script when tool calls hit external APIs with rate limits. CrewAI supports async execution via crew.kickoff_async() if your event loop is already set up. Also consider caching tool results: weather data for the same city within a minute rarely changes, and the gateway’s cache-control forwarding means repeated identical calls can be served without double-billing if you pass the right headers at the LLM client level.
Common failure modes
- Model lacks tool support: If you pick a model that doesn’t emit
tool_calls, CrewAI will either hallucinate or error. Check the model card. - Timeout on tool HTTP: The example uses
timeout=10. Wrap tools intry/exceptand return an error string; never let a bare exception kill the crew. - Schema drift: Changing the function signature without updating the docstring confuses the LLM. Regenerate the tool definition by restarting the process.
- Wrong base_url: A missing
/v1or trailing slash breaks the OpenAI client. Test withcurlagainst/v1/modelsfirst.
Closing notes
The pattern above is the minimal viable crewai n4n.ai function calling integration. From here, add more agents, parallel tasks, and guardrails on tool outputs. Because the LLM layer is swappable via base_url, you can A/B models without touching agent code—just change N4N_MODEL.