Semantic Kernel has become the de facto orchestration layer for .NET and Python developers building LLM applications. But the documentation assumes you’re calling Azure OpenAI or OpenAI directly. When you route through a gateway like n4n.ai — one OpenAI-compatible endpoint addressing 240+ models with automatic fallback when a provider is rate-limited or degraded — the configuration surface changes. This tutorial walks through the exact setup: installing packages, wiring API keys, configuring the kernel, and verifying the connection with chat completion and streaming.
Prerequisites
- .NET 8 SDK or Python 3.10+
- An n4n.ai account with an API key (grab it from the dashboard)
- A code editor and terminal
The examples below use the n4n.ai base URL https://api.n4n.ai/v1. If you’re self-hosting or using a different gateway, swap the base URL accordingly.
Install the packages
.NET
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
Python
pip install semantic-kernel openai
The OpenAI connector is what Semantic Kernel uses to talk to any OpenAI-compatible endpoint — including n4n.ai.
Configure the kernel
The key insight: you don’t need a special “n4n.ai connector.” You configure the standard OpenAI connector with the n4n.ai base URL and your API key. Semantic Kernel’s HttpClient pipeline handles the rest.
.NET — programmatic configuration
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
var builder = Kernel.CreateBuilder();
// n4n.ai OpenAI-compatible endpoint
var apiKey = Environment.GetEnvironmentVariable("N4N_API_KEY")
?? throw new InvalidOperationException("Set N4N_API_KEY env var");
var baseUrl = "https://api.n4n.ai/v1";
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o-mini", // any model n4n.ai serves
apiKey: apiKey,
httpClient: new HttpClient { BaseAddress = new Uri(baseUrl) },
serviceId: "n4n-chat" // optional, useful for multiple services
);
var kernel = builder.Build();
.NET — configuration via appsettings.json
{
"SemanticKernel": {
"ChatCompletion": {
"N4N": {
"ModelId": "gpt-4o-mini",
"ApiKey": "${N4N_API_KEY}",
"Endpoint": "https://api.n4n.ai/v1",
"ServiceId": "n4n-chat"
}
}
}
}
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("N4N"); // matches the config section name
var kernel = builder.Build();
Python — programmatic configuration
import os
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
api_key = os.environ["N4N_API_KEY"]
base_url = "https://api.n4n.ai/v1"
kernel = Kernel()
kernel.add_service(
OpenAIChatCompletion(
ai_model_id="gpt-4o-mini",
api_key=api_key,
base_url=base_url,
service_id="n4n-chat"
)
)
Python — environment-driven (recommended for containers)
export N4N_API_KEY="sk-..."
export N4N_BASE_URL="https://api.n4n.ai/v1"
export N4N_MODEL="gpt-4o-mini"
import os
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
kernel = Kernel()
kernel.add_service(
OpenAIChatCompletion(
ai_model_id=os.environ["N4N_MODEL"],
api_key=os.environ["N4N_API_KEY"],
base_url=os.environ["N4N_BASE_URL"],
service_id="n4n-chat"
)
)
Verify the connection — chat completion
Run a minimal request to confirm the kernel talks to n4n.ai and returns a completion.
.NET
using Microsoft.SemanticKernel;
var result = await kernel.InvokePromptAsync("Reply with exactly: pong");
Console.WriteLine(result); // Expected: pong
Expected output:
pong
Python
result = await kernel.invoke_prompt("Reply with exactly: pong")
print(result) # Expected: pong
Expected output:
pong
If you see pong, the kernel, connector, API key, and base URL are all wired correctly.
Streaming responses
Streaming is where you feel the latency difference. Semantic Kernel exposes InvokePromptStreamingAsync (.NET) and invoke_prompt_stream (Python).
.NET streaming
using Microsoft.SemanticKernel;
await foreach (var chunk in kernel.InvokePromptStreamingAsync("Count from 1 to 5, one per line")) {
Console.Write(chunk);
}
Console.WriteLine();
Expected output (streamed):
1
2
3
4
5
Python streaming
async for chunk in kernel.invoke_prompt_stream("Count from 1 to 5, one per line"):
print(chunk, end="", flush=True)
print()
Expected output (streamed):
1
2
3
4
5
Using multiple models with service IDs
One kernel can hold multiple chat completion services. This is useful when you route different tasks to different models — say, a cheap model for classification and a reasoning model for code generation.
.NET
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o-mini",
apiKey: apiKey,
httpClient: new HttpClient { BaseAddress = new Uri(baseUrl) },
serviceId: "fast"
);
builder.AddOpenAIChatCompletion(
modelId: "o1-preview",
apiKey: apiKey,
httpClient: new HttpClient { BaseAddress = new Uri(baseUrl) },
serviceId: "reasoning"
);
var kernel = builder.Build();
// Explicitly select the service
var fastResult = await kernel.InvokePromptAsync(
"Classify: 'refund request' -> category",
new() { { "service_id", "fast" } }
);
var reasoningResult = await kernel.InvokePromptAsync(
"Write a recursive Fibonacci in Rust with memoization",
new() { { "service_id", "reasoning" } }
);
Python
kernel.add_service(OpenAIChatCompletion(
ai_model_id="gpt-4o-mini",
api_key=api_key,
base_url=base_url,
service_id="fast"
))
kernel.add_service(OpenAIChatCompletion(
ai_model_id="o1-preview",
api_key=api_key,
base_url=base_url,
service_id="reasoning"
))
fast_result = await kernel.invoke_prompt(
"Classify: 'refund request' -> category",
service_id="fast"
)
reasoning_result = await kernel.invoke_prompt(
"Write a recursive Fibonacci in Rust with memoization",
service_id="reasoning"
)
Handling provider fallback and rate limits
n4n.ai returns standard OpenAI-compatible error codes. When a provider is rate-limited or degraded, the gateway automatically fails over to another provider serving the same model. Your code sees a successful response — no retry logic required.
However, if all providers for a model are exhausted, you’ll get a 429 Too Many Requests with a Retry-After header. Semantic Kernel’s OpenAI connector respects this header and retries automatically (configurable via OpenAIClientOptions).
.NET — custom retry policy
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.Net.Http;
var httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
// Semantic Kernel uses the OpenAI .NET SDK under the hood;
// you can pass a configured OpenAIClientOptions for retry behavior
var options = new OpenAIClientOptions
{
RetryPolicy = new ExponentialBackoffRetryPolicy(
maxRetries: 3,
baseDelay: TimeSpan.FromSeconds(2)
)
};
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o-mini",
apiKey: apiKey,
httpClient: httpClient,
serviceId: "n4n-chat",
clientOptions: options
);
Python — retry configuration
from openai import OpenAI
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
# The connector accepts an OpenAI client instance
client = OpenAI(
api_key=api_key,
base_url=base_url,
max_retries=3,
timeout=60.0
)
kernel.add_service(OpenAIChatCompletion(
ai_model_id="gpt-4o-mini",
async_client=client, # or client for sync
service_id="n4n-chat"
))
Per-token usage metering
n4n.ai returns usage fields in the standard OpenAI response format (usage.prompt_tokens, usage.completion_tokens, usage.total_tokens). Semantic Kernel surfaces this via the FunctionResult metadata.
.NET
var result = await kernel.InvokePromptAsync("Summarize this in 10 words: ...");
var usage = result.Metadata?["usage"] as Microsoft.SemanticKernel.ChatCompletion.ChatCompletionUsage;
if (usage != null) {
Console.WriteLine($"Prompt: {usage.PromptTokens}, Completion: {usage.CompletionTokens}, Total: {usage.TotalTokens}");
}
Python
result = await kernel.invoke_prompt("Summarize this in 10 words: ...")
usage = result.metadata.get("usage")
if usage:
print(f"Prompt: {usage.prompt_tokens}, Completion: {usage.completion_tokens}, Total: {usage.total_tokens}")
Expected output:
Prompt: 42, Completion: 18, Total: 60
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
401 Unauthorized |
API key missing or invalid | Verify N4N_API_KEY env var; check dashboard for key rotation |
404 Not Found |
Wrong base URL or model ID | Ensure base URL ends with /v1; model ID must exist in n4n.ai catalog |
HttpRequestException / ConnectionError |
Network / DNS / proxy | Test curl -H "Authorization: Bearer $N4N_API_KEY" https://api.n4n.ai/v1/models |
| Streaming hangs | Middleware buffering response | Disable response buffering in reverse proxies (nginx: proxy_buffering off;) |
| Model not found | Using a model alias n4n.ai doesn’t recognize | List available models via GET /v1/models or the dashboard |
Quick test: list available models
Before coding, verify your key works and see what models are routable.
curl -s -H "Authorization: Bearer $N4N_API_KEY" \
https://api.n4n.ai/v1/models | jq '.data[].id' | head -20
Expected output (sample):
"gpt-4o-mini"
"gpt-4o"
"o1-preview"
"o1-mini"
"claude-3-5-sonnet-20241022"
"gemini-1.5-pro"
"llama-3.1-70b-instruct"
...
Use any of these IDs as modelId / ai_model_id in the kernel configuration.
What’s next
You now have a Semantic Kernel instance wired to n4n.ai with:
- Correct base URL and API key configuration
- Verified chat completion and streaming
- Multi-model routing via service IDs
- Automatic provider fallback (handled by the gateway)
- Usage metering for cost tracking
From here, plug in planners, vector stores, or agent frameworks — the kernel doesn’t care which provider serves the tokens. The gateway handles model diversity, fallbacks, and per-token metering so your orchestration code stays clean.