n4nAI

Migrating from the OpenAI Python SDK to n4n in 10 minutes

Step-by-step tutorial to migrate OpenAI Python SDK to n4n: repoint the base URL, adjust model IDs, and preserve streaming, tools, and usage metering.

n4n Team3 min read589 words

Audio narration

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

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.
  • openai Python package v1.40+ already installed or upgradable.
  • An existing script that imports from openai import OpenAI and 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-4o without openai/ 404s.
  • Trailing slash in base_url: causes //v1 and 404s on some proxies.
  • Provider-specific params: some models reject logprobs or seed; the gateway returns the provider error unchanged.
  • Env var confusion: mixing OPENAI_API_KEY and N4N_API_KEY leads to auth errors.

Pre-prod checklist

  • base_url set, 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.

Tagsopenai-sdkmigrationpythongateway

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 migrating from openai sdk to a unified gateway posts →