When you migrate azure openai sdk to gateway, you trade Azure’s deployment-scoped endpoints and API-version pinning for a single OpenAI-compatible surface. That move decouples your code from Azure’s resource topology and lets you route the same call to multiple providers behind one interface. The migration is mostly mechanical, but a few Azure-only constructs need explicit mapping before you flip traffic.
Step 1: Inventory your Azure OpenAI client instances
Locate every place you construct an Azure OpenAI client. Teams on older openai versions (pre-1.0) used api_type="azure" with a base URL hack; modern code uses the AzureOpenAI class. Both patterns hide the same problem: the model field is a deployment alias, not a public model ID.
Legacy pattern (still in many codebases):
import openai
openai.api_type = "azure"
openai.api_base = "https://contoso.openai.azure.com/"
openai.api_version = "2024-02-15-preview"
openai.api_key = os.environ["AZURE_OPENAI_KEY"]
resp = openai.ChatCompletion.create(
engine="prod-gpt35-turbo", # note: engine, not model
messages=[{"role": "user", "content": "Hello"}],
)
Current SDK pattern:
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint="https://contoso.openai.azure.com",
api_key=os.environ["AZURE_OPENAI_KEY"],
api_version="2024-02-15-preview",
)
resp = client.chat.completions.create(
model="prod-gpt35-turbo", # deployment name
messages=[{"role": "user", "content": "Hello"}],
)
In Node.js the modern pattern looks like this:
import { AzureOpenAI } from "openai";
const client = new AzureOpenAI({
azureEndpoint: "https://contoso.openai.azure.com",
apiKey: process.env.AZURE_OPENAI_KEY,
apiVersion: "2024-02-15-preview",
});
Record three things for each instance: the api_version, the azure_endpoint region, and every model/engine string passed to create. Those strings are Azure deployment aliases, not the underlying OpenAI model families.
Step 2: Map Azure deployments to canonical model IDs
A gateway speaks OpenAI model names directly. Build a lookup table from your deployment names to public model IDs. Include date-stamped variants if you used them.
{
"prod-gpt35-turbo": "gpt-3.5-turbo",
"prod-gpt35-turbo-0613": "gpt-3.5-turbo-0613",
"prod-gpt4-32k": "gpt-4-32k",
"embed-ada-002": "text-embedding-ada-002",
"vision-preview": "gpt-4-vision-preview"
}
If you maintained separate deployments for staging and prod that point to the same model, collapse them. The gateway handles environment separation via API keys or routing headers, not deployment names. Keep the table in a single config module so you can grep for leftover aliases later.
Step 3: Replace the client constructor
Swap AzureOpenAI for the standard OpenAI client and point base_url at your gateway. When you migrate azure openai sdk to gateway, this is the only mandatory code change for basic calls.
from openai import OpenAI
client = OpenAI(
base_url="https://api.n4n.ai/v1", # a single OpenAI-compatible endpoint
api_key=os.environ["GATEWAY_KEY"],
)
response = client.chat.completions.create(
model="gpt-3.5-turbo", # canonical model ID
messages=[{"role": "user", "content": "Hello"}],
)
A gateway such as n4n.ai exposes one OpenAI-compatible endpoint that addresses 240+ models, so the same client can later call models outside Azure without further refactoring.
In TypeScript:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.n4n.ai/v1",
apiKey: process.env.GATEWAY_KEY,
});
const response = await client.chat.completions.create({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: "Hello" }],
});
Step 4: Strip Azure-only parameters and adapt call shapes
Azure requires api_version and azure_endpoint; the gateway ignores them. Remove those kwargs. Also delete any deployment_id or engine usage. The model field now carries the real model.
Azure embeddings code before:
client.embeddings.create(model="embed-ada-002", input="text")
After migration:
client.embeddings.create(model="text-embedding-ada-002", input="text")
Vision and function calling pass through unchanged because the request schema matches OpenAI’s. If you used response_format={"type": "json_object"} for JSON mode, keep it—the gateway forwards it. One nuance: Azure’s seed parameter works identically, but verify the gateway returns the same system_fingerprint semantics if you depend on them for reproducibility.
Step 5: Migrate auth and custom headers
Azure OpenAI authenticates with an api-key header. The gateway expects Authorization: Bearer <key>, which the SDK sets automatically when you pass api_key. If you attached distributed-tracing headers (traceparent) or custom metadata, keep them via extra_headers:
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Trace this"}],
extra_headers={"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"},
)
Do not hardcode the key. Read it from environment or a secret manager, same as before.
Step 6: Handle Azure content filters and streaming
Azure injects content_filter_results into response objects. Most gateways do not emulate that structure. If your code branches on response.choices[0].finish_reason == "content_filter", replace it with a generic safety check or a gateway-provided moderation call.
Streaming works identically—the SDK yields chunk objects. Verify your async iteration still compiles:
stream = client.chat.completions.create(model="gpt-4", messages=[...], stream=True)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
If you used Azure’s stream_options={"include_usage": True}, the gateway supports the same field; confirm usage appears in the final chunk.
Step 7: Set routing and cache directives
When you migrate azure openai sdk to gateway, you gain provider-agnostic routing. Gateways that honor client routing directives let you pin a call to a specific backend or enable fallback. Forward provider cache-control hints to preserve Azure’s prompt caching:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "system", "content": "You are terse."}, {"role": "user", "content": "Go."}],
extra_headers={
"x-route-to": "azure-eastus",
"cache-control": "max-age=3600",
},
)
n4n.ai forwards provider cache-control hints, so Azure’s semantic caching behavior survives the migration if you keep the header. If a provider is rate-limited or degraded, the gateway’s automatic fallback retries another backend without code changes—but only if you haven’t pinned an unreachable route.
Step 8: Write a parity test
Before flipping traffic, run a side-by-side test. Capture the same prompt against both clients and compare non-stochastic fields:
def parity_check(prompt: str):
az = azure_client.chat.completions.create(model="prod-gpt35-turbo", messages=[{"role":"user","content":prompt}])
gw = gateway_client.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role":"user","content":prompt}])
assert az.choices[0].message.content is not None
assert gw.choices[0].message.content is not None
assert gw.usage.total_tokens > 0
print("token usage:", gw.usage.prompt_tokens, gw.usage.completion_tokens)
Extend the test to embeddings and vision if you use them. For embeddings, compare vector dimensionality, not values. For function calling, send a tool schema and assert the gateway returns tool_calls with parseable arguments. Run this matrix for each mapped model.
Step 9: Verify success in production
Success means the gateway returns valid OpenAI-shaped responses with per-token usage metering and no Azure deployment errors. Check your logs for:
resp.modelequals the canonical ID you sent.resp.usageis populated (gateways meter per token).- Latency within your SLA; if a provider is degraded, the gateway’s automatic fallback should retry another without code changes.
A quick curl sanity check post-deploy:
curl https://api.n4n.ai/v1/chat/completions \
-H "Authorization: Bearer $GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-3.5-turbo","messages":[{"role":"user","content":"ping"}]}'
Expect a JSON object with choices and usage. If that parses and tokens are billed, the migration is complete.
Step 10: Delete Azure-specific branches
Once stable, remove api_version conditionals, AzureOpenAI imports, and deployment maps from your codebase. Keep the lookup table only if you still mirror some traffic to Azure for audit. Your dependency list shrinks to a single openai package version, and configuration reduces to one base_url and one key.
Common pitfalls
- Deployment name leaks: A forgotten
"prod-gpt4"string returns 404 from the gateway. Grep formodel=andengine=across the repo. - API version pinning: Some teams read
api_versionfrom config and append it to the URL. The gateway uses/v1exclusively; drop the suffix. - Regional endpoints: Azure URLs contain region; gateways are region-less at the edge. Update base URL constants in one place.
- Content filter logic: Removing
content_filter_resultswithout a replacement can silently disable safety guards. Add a moderation step if required.
Following these steps to migrate azure openai sdk to gateway yields a thinner client, unified logging, and the freedom to shift providers without touching business logic.