Most Python services call OpenAI through the official SDK and a hard-coded model string. To migrate OpenAI Python SDK to n4n, you change two lines of configuration and rethink model identifiers—the client code barely moves. This tutorial walks through a live cutover with runnable snippets and expected output at each checkpoint.
Prerequisites
- Python 3.10 or newer.
openaiPython package v1.40+ already installed or upgradable.- An existing script that imports
from openai import OpenAIand makes chat completion calls. - An API key from the gateway, exported as
N4N_API_KEY.
If your current code reads OPENAI_API_KEY from the environment, leave it; we will swap the variable name and base URL only.
echo "N4N_API_KEY=$N4N_API_KEY" | sed 's/=.*/=<redacted>/'
# confirm Python version
python --version
Expected: Python 3.11.4 (or similar 3.10+).
Step 1: Install and confirm SDK version
The OpenAI SDK v1.x uses a single base_url parameter for any OpenAI-compatible server. Ensure you are on a recent release.
pip install -U "openai>=1.40"
python -c "import openai; print('openai', openai.__version__)"
Expected output:
openai 1.45.0
If you see a 0.x version, your imports will differ; upgrade before continuing.
Step 2: Repoint the client
Here is a minimal original client:
from openai import OpenAI
client = OpenAI(api_key="sk-...") # OpenAI direct
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}]
)
To migrate OpenAI Python SDK to n4n, instantiate the same class with a different base_url and key. n4n.ai exposes a single OpenAI-compatible endpoint covering 240+ models, so one host replaces multiple provider URLs.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["N4N_API_KEY"],
base_url="https://api.n4n.ai/v1" # trailing slash omitted
)
That is the only structural change. The chat.completions.create method signature is identical.
Step 3: Adjust model identifiers
OpenAI’s SDK accepts bare names like gpt-4o. The gateway routes by provider namespace. A model ID is {provider}/{model}.
List what is available:
models = client.models.list()
for m in models.data[:5]:
print(m.id)
Expected output (truncated):
openai/gpt-4o
anthropic/claude-3.5-sonnet
meta-llama/llama-3.1-70b-instruct
google/gemini-1.5-pro
mistral/mistral-large
Update your call sites:
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Hello"}]
)
print(resp.choices[0].message.content)
Expected:
Hello! How can I help you today?
If you skip the namespace you will get a 404 with model not found. A quick sed across your repo can prefix existing strings, but verify each model exists on the gateway.
Step 4: Run your first request and inspect usage
Token accounting is unchanged. The usage object returns prompt, completion, and total tokens.
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Count to three."}]
)
print(resp.usage)
Expected:
CompletionUsage(completion_tokens=8, prompt_tokens=12, total_tokens=20)
Per-token metering arrives in the same shape as OpenAI, so downstream logging or cost tracking code needs no edits.
Step 5: Streaming and async require zero changes
Streaming works by passing stream=True. The chunk iterator is identical.
stream = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": "Stream a haiku."}],
stream=True
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
Expected partial output:
Snow falls softly—
silent night, warm cup of tea,
winter breathes quiet.
For async, AsyncOpenAI with the same base_url works:
from openai import AsyncOpenAI
aclient = AsyncOpenAI(api_key=os.environ["N4N_API_KEY"], base_url="https://api.n4n.ai/v1")
Step 6: Preserve tool calls and JSON mode
If your application uses function calling, pass tools exactly as before. The gateway forwards the schema.
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
}
}]
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Weather in Berlin?"}],
tools=tools
)
print(resp.choices[0].message.tool_calls)
Expected (if model decides to call):
[ChatCompletionMessageToolCall(id='call_abc', function=Function(name='get_weather', arguments='{"city":"Berlin"}'), type='function')]
JSON mode is also passed through:
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role":"user","content":"Return JSON"}],
response_format={"type":"json_object"}
)
Step 7: Use gateway routing directives
When you migrate OpenAI Python SDK to n4n, you gain fallback without writing retry logic. The gateway honors client routing directives sent as extra headers.
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Hi"}],
extra_headers={"x-n4n-fallback": "anthropic/claude-3.5-sonnet"}
)
If the primary provider is rate-limited or degraded, the gateway serves the fallback model and the response shape stays identical. Provider cache-control hints can also be forwarded via extra_headers such as {"x-provider-cache": "true"}.
Step 8: A drop-in wrapper to minimize diffs
If you have many call sites, hide the configuration in a factory:
def make_gateway_client(default_model="openai/gpt-4o"):
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["N4N_API_KEY"],
base_url="https://api.n4n.ai/v1"
)
return client, default_model
client, MODEL = make_gateway_client()
resp = client.chat.completions.create(model=MODEL, messages=[{"role":"user","content":"Ping"}])
This keeps the rest of your codebase free of gateway specifics.
Common pitfalls
- Bare model names:
gpt-4owithoutopenai/404s. - Trailing slash in base_url: causes
//v1and 404s on some proxies. - Provider-specific params: some models reject
logprobsorseed; the gateway returns the provider error unchanged. - Env var confusion: mixing
OPENAI_API_KEYandN4N_API_KEYleads to auth errors.
Pre-prod checklist
-
base_urlset, key from env. - All model IDs namespaced (
provider/model). - Streaming and async paths tested.
- Tool schemas and JSON mode validated.
- Fallback headers added where resilience matters.
- Usage logging asserts on
resp.usage.total_tokens.
The migration is configuration, not rewrite. Your existing OpenAI SDK calls become multi-provider with a two-line client change and disciplined model strings.