When building LLM features in C#, the trade-off between Semantic Kernel vs raw HTTP dotnet integration is really a question of orchestration versus control. Semantic Kernel gives you a Microsoft-supported abstraction over prompts, plugins, and planners, while raw HTTP means you own the request pipeline to an OpenAI-compatible endpoint. Below we break down both approaches across the metrics that matter in production: capabilities, cost, latency, ergonomics, ecosystem, and hard limits.
Capabilities
Semantic Kernel (SK) is more than an HTTP client. It provides a Kernel object that composes AI services, native function exports, and semantic functions defined in YAML or code. You get built-in support for function calling, auto function invocation, and experimental planners that chain prompts. For example, registering a local C# method as a plugin and letting the model invoke it:
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_KEY"));
var kernel = builder.Build();
kernel.ImportPluginFromType<WeatherPlugin>();
var result = await kernel.InvokePromptAsync("What's the weather in Berlin?");
Raw HTTP gives you none of that scaffolding. You serialize a JSON body and parse the response. But you can talk to any model behind any OpenAI-compatible route, including local llama.cpp servers or a multi-provider gateway. A minimal streaming call with curl:
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"What'\''s the weather in Berlin?"}],"stream":true}'
The request shape is transparent:
{
"model": "gpt-4o",
"messages": [{ "role": "user", "content": "What's the weather in Berlin?" }],
"stream": true
}
In C#, you would wrap this in HttpClient.PostAsync and read the SSE stream. The capability gap is real: SK ships retrievers, memory stores, and telemetry hooks. Raw HTTP forces you to build those, but never prevents you from doing so.
Price / cost model
Neither option changes the fundamental token economics. You pay the model provider or gateway per input and output token. Semantic Kernel is Apache-2.0 licensed; there is no commercial fee. The hidden cost is dependency weight and the engineering time to track its frequent API shifts.
Raw HTTP has zero SDK cost, but you will spend hours on retry policies, error mapping, and response parsing. If you route through a gateway such as n4n.ai, you get per-token usage metering and automatic fallback when a provider is rate-limited, without adding a line of retry code in your dotnet service. That can reduce effective cost by avoiding stalled requests, but the token price itself is unchanged.
Latency / throughput
SK adds a thin layer of object mapping and middleware. In measured loops, the overhead is typically sub-millisecond per call plus serialization cost for function schemas. Raw HTTP avoids that, but only if you hand-roll efficient HttpClient singleton usage and System.Text.Json pooling.
Both support streaming, which is the dominant latency win for chat UIs. SK exposes StreamingKernelContent via InvokeStreamingAsync; raw HTTP reads Server-Sent Events line by line. Throughput at scale depends more on the model provider than the client. If you batch with raw HTTP and use Parallel.ForEachAsync over a shared HttpClient with HTTP/2, you can hit higher requests/sec than SK’s default sequential invocation, but you lose SK’s built-in concurrency throttling and cancellation propagation.
Ergonomics
This is where Semantic Kernel vs raw HTTP dotnet diverges most for day-to-day work. SK integrates with Microsoft.Extensions.DependencyInjection, offers strongly typed function exports, and gives you a testable Kernel mock. A plugin is just a class with [KernelFunction] attributes:
public class WeatherPlugin
{
[KernelFunction]
public string GetWeather(string city) => $"Sunny in {city}";
}
New engineers can read a semantic function YAML and understand the prompt without seeing transport code.
Raw HTTP is explicit. You see the exact JSON, the headers, the status codes. That clarity is valuable when debugging provider-specific quirks, but you will write a ChatClient wrapper, a PromptBuilder, and a RetryHandler before you reach parity. For a team already fluent in IHttpClientFactory and Polly, that may be fine. For a team that wants to ship a RAG pipeline in a week, SK removes dozens of decisions.
Ecosystem
SK is backed by Microsoft, with NuGet packages, regular releases, and a growing set of connectors for Azure AI, Hugging Face, and OpenAI. The plugin ecosystem includes vector store integrations and the “Agent” preview. Community samples are numerous but often version-locked.
Raw HTTP sits on the universal HTTP ecosystem: HttpClient, Polly, Refit, Flurl. You can adopt any OpenTelemetry instrumentation, any load balancer, any service mesh. There is no lock-in to a SDK’s supported model list. If a new provider appears with an OpenAI-compatible surface, your raw client works unchanged; SK requires a new connector or a custom AIService implementation.
Limits
SK’s abstractions leak when you need fine-grained control: custom request headers, provider-specific extensions like response format schemas, or non-standard streaming events. Its rapid version cadence (1.0 to 1.2 in months) can break builds. Function calling is powerful but constrains you to models that support the schema SK emits.
Raw HTTP has no ceiling but no guardrails. You must handle 429/5xx, token overflow, and JSON drift yourself. Missing a stream: false flag or misreading SSE delimiters will silently corrupt output. There is no built-in telemetry, so you wire HttpClient diagnostics listeners manually.
Head-to-head summary
| Dimension | Semantic Kernel | Raw HTTP |
|---|---|---|
| Capabilities | Plugins, planners, memory, telemetry | Any endpoint, full request control |
| Cost model | Free SDK, same token cost | Free, but build/maintain cost |
| Latency | +~1ms mapping overhead, streaming | Minimal, depends on your code |
| Ergonomics | DI, typed functions, YAML prompts | Explicit, verbose, transparent |
| Ecosystem | Microsoft-backed, version-sensitive | Universal HTTP, no lock-in |
| Limits | Abstraction leaks, fast breaking changes | No guardrails, full DIY burden |
Which to choose
Prototype or line-of-business app with standard OpenAI models: Use Semantic Kernel. The Kernel abstraction, plugin system, and DI integration let you ship a functioning copilot in days. If you later need a second provider, swap the AddOpenAIChatCompletion for another service registration.
High-throughput proxy or multi-provider routing: Use raw HTTP. When you are building a gateway, a load tester, or a cost optimizer that must forward arbitrary headers and honor cache-control hints, the SDK gets in the way. A lean HttpClient wrapper with Polly retries beats fighting SK’s pipeline.
Team with existing HTTP infrastructure: If you already run IHttpClientFactory, OpenTelemetry, and Refit across services, raw HTTP keeps LLM calls consistent with the rest of your stack. Add a small LlmClient interface and move on.
Complex agentic workflows with function calling: Semantic Kernel’s auto-invocation and planner reduce the code you write to coordinate tools. Just pin the package version and budget for upgrades.
Edge or size-constrained environments: Raw HTTP with a compiled System.Text.Json source generator avoids pulling the SK dependency graph, which matters in AWS Lambda cold starts or mobile.
The middle ground is common: raw HTTP for the transport layer behind a thin internal client, and SK for the orchestration layer in features that need it. That hybrid keeps the semantic kernel vs raw http dotnet decision from being all-or-nothing.