On this page

You’re building your first serious AI feature in .NET. You've figured out your prompts, you know which model you want to call, but now you're staring at a blank file. How do you actually structure the code that wires everything together?

This is the orchestration question, and it leads you to a crossroads: do you adopt a framework like Semantic Kernel, or do you just write a plain old C# class?

Think of it like choosing between Entity Framework and Dapper. One gives you a powerful, "batteries-included" toolkit that handles a lot for you. The other gives you raw, explicit control and a direct line to the metal. There's no single right answer, but choosing the wrong path for your project can lead to a world of frustration.

Let's break down the real-world trade-offs.

🔗The Trade-offs That Actually Matter

This isn't just about code style. It's about how you build, test, and debug your application.

  • Extensibility and Testing: With Semantic Kernel, you build functionality by creating "plugins" and let the "planner" figure out how to chain them together. It feels magical when it works. But testing it means you're now in the business of mocking the Kernel, its plugins, and its planners. With your own code, you're just writing a class with methods. Testing is as simple as mocking an interface—a pattern your team already uses everywhere else. You own the orchestration, so you can test it directly.

  • Function Calling & Tools: LLM providers have powerful, native "tool calling" features. Semantic Kernel provides a common abstraction over these. This is great for getting started, but what happens when you need a specific feature from a specific provider, like OpenAI's tool_choice options? In SK, you might be waiting for the framework to add support. In your own code, you just add the parameter to your direct API call. You're never fighting the abstraction.

  • Tracing and Debugging: When something goes wrong, how do you debug it? With SK, you're stepping through its internal middleware, event handlers, and complex object model to figure out what happened. With a custom orchestrator, you're just stepping through your code. You can wrap every single step—rendering a prompt, calling the model, parsing the result—in your own OpenTelemetry spans with exactly the custom tags and metrics you want.

  • Governance: Things like managing prompt versions, adding guardrails, and enforcing security are critical. Both approaches can solve this. The question is where that logic lives. In SK, you might manage prompt templates as files. In custom code, they might just be strings or classes. Either way, you're responsible for the versioning and safety.

🔗When Semantic Kernel Is Your Best Friend

  • You're Prototyping at Lightning Speed: You need to build a demo, fast. Your agent needs to search some documents (using a "memory" plugin) and then call a company API (using a "tool" plugin). With SK, you can stitch this together in an afternoon. The high-level abstractions are designed for this kind of rapid composition.

  • Your Team Wants a Standardized Toolkit: If you have multiple teams building AI features, adopting SK provides a common language and structure. It encourages convention over configuration, which can be a huge win for consistency across a large organization.

🔗When Custom Code Is King

  • You Need Absolute, Granular Control: What if one tool call needs a 500ms timeout, but another can take 5 seconds? What if one specific tool needs a custom retry policy with an exponential backoff? In your own code, this is a few lines using a library like Polly. In a framework, you're now wrestling with its specific configuration model to achieve the same result.

  • Deep Observability Is Non-Negotiable: Your service has a strict SLA, and you need to know exactly where every millisecond is spent. With a custom orchestrator, you control the code, so you can add precise tracing and metrics to every single step of the process.

  • You're Building a Lean Machine: For a tiny microservice or a serverless function, every dependency matters. A custom orchestrator might only be a few hundred lines of your own code. Semantic Kernel, while powerful, brings in a significant dependency graph. If deployment size and cold start time are critical, less is more.

🔗A Quick Look at the Code

The philosophy behind each approach becomes clear when you see the code.

// Semantic Kernel: "Magic" pipeline
// It feels declarative. The Kernel handles finding the prompt,
// injecting variables, and executing the whole thing.
var kernel = Kernel.CreateBuilder().Build();
// Assumes you've configured prompt templates and plugins elsewhere
var result = await kernel.InvokePromptAsync(
    "Turn this into a title: {{input}}",
    new() { ["input"] = someText });
// Custom Orchestrator: Explicit execution
// Every step is clear and written by you. You control tracing,
// error handling, and the client call directly.
using var span = tracer.StartActiveSpan("orchestrate-title-generation");

var prompt = _promptTemplate.Render(new { Input = someText });
span.SetAttribute("app.prompt.text", prompt);

var response = await _openAiClient.GetChatCompletionsAsync(
    "gpt-4o",
    new ChatCompletionsOptions { Messages = { new(ChatRole.User, prompt) } },
    cancellationToken);

metrics.Record("app.llm.tokens.total", response.Value.Usage.TotalTokens);
return response.Value.Choices[0].Message.Content;

🔗The Bottom Line: Start Simple, Evolve as Needed

Don't treat this as a one-time, irreversible decision. Follow a path of natural evolution.

Start with Semantic Kernel for your first prototype. It will get you moving quickly and help you understand the patterns of building LLM-powered features.

But be prepared to outgrow it. As your application matures, you'll find parts of it that become performance-critical or require strict error handling and control. When that happens, don't be afraid to pull that specific hot path out of SK and rewrite it as a small, focused, custom orchestrator.

It's not a failure to move away from a framework; it's a sign of maturity. It means you've learned exactly what you need, and now you're building it without compromise.