This semantic kernel agent framework tutorial walks through building an autonomous agent that plans and executes tasks using native plugins. Semantic Kernel gives you a lightweight orchestration layer over LLMs; the Agent Framework adds conversation state and a reasoning loop. We’ll stand up a working agent that calls a custom function, interprets the result, and answers a follow-up question without hand-written control flow.
Step 1: Scaffold the project and install dependencies
Start with a clean .NET 8 console app. The Agent Framework lives in a preview package, so you must opt into prerelease and pin a version—the API shifts between minors.
dotnet new console -n SkAgentDemo
cd SkAgentDemo
dotnet add package Microsoft.SemanticKernel --version 1.24.0
dotnet add package Microsoft.SemanticKernel.Agents --version 1.24.0-preview
Enable nullable reference types in SkAgentDemo.csproj to catch missing plugin descriptions at compile time. If you’re on Python, the same concepts apply via semantic-kernel[agents], but C# gives stricter binding between the model’s tool schema and your method signatures, which reduces silent call failures.
Step 2: Configure the kernel with an LLM endpoint
The agent needs a Kernel with a registered chat completion service. Semantic Kernel speaks the OpenAI protocol, so any compliant base URL works. For production traffic I route through n4n.ai’s OpenAI-compatible endpoint: it fronts 240+ models, fails over automatically when a provider is rate-limited or degraded, and forwards provider cache-control hints if you set them on the client. That removes a whole category of 429 handling from your code.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o-mini",
apiKey: Environment.GetEnvironmentVariable("N4N_API_KEY")!,
httpClient: new HttpClient
{
BaseAddress = new Uri("https://api.n4n.ai/v1")
});
var kernel = builder.Build();
Swap modelId for whatever backend you want. The rest of this semantic kernel agent framework tutorial stays identical regardless of which OpenAI-compatible provider sits behind the kernel.
Step 3: Define a native plugin the agent can call
Agents reason best when they have typed tools with explicit contracts. A plugin is a plain class; methods tagged [KernelFunction] become callable tools. The description text is not decoration—the model reads it to decide invocation.
using Microsoft.SemanticKernel;
public class WeatherPlugin
{
[KernelFunction("get_weather")]
[Description("Returns a short current-weather summary for a given city.")]
public string GetWeather(
[Description("City name, e.g. 'London'")] string city)
{
// Stub: replace with HTTP call to a real meterological service.
return $"Clear skies, 18°C in {city}";
}
}
Keep the method side-effect free and fast. Real plugins should be async and return Task<string>, with timeouts and retries inside. If the function throws, the agent sees a generic error and may hallucinate a recovery—don’t let transport noise reach the model.
Step 4: Instantiate the ChatCompletionAgent
ChatCompletionAgent wraps the kernel, holds system instructions, and drives the function-calling loop when given a ChatHistory. Register the plugin so the model can discover it during tool selection.
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
var agent = new ChatCompletionAgent(
kernel,
instructions: "You are a concise assistant. When asked about weather, call get_weather, then answer the user's actual question using the returned data.")
{
Name = "TripAssistant"
};
agent.Plugins.AddFromType<WeatherPlugin>();
The instructions field is your system prompt. Be imperative: tell it to call the function rather than infer. Vague instructions produce confident fabrications. ChatCompletionAgent differs from OpenAIAssistantAgent in that it runs entirely client-side—no assistant ID or server-stored thread required.
Step 5: Drive a multi-step reasoning loop
A single InvokeAsync round-trips the model, executes any requested functions, and feeds results back for a final answer. Build a ChatHistory, add the user turn, and stream the response.
var chat = new ChatHistory();
chat.AddUserMessage("What's the weather in London? Do I need a jacket?");
await foreach (var message in agent.InvokeAsync(chat))
{
if (message.Role == AuthorRole.Assistant)
Console.WriteLine($"Agent: {message.Content}");
}
After the loop, chat contains the tool call and the tool result as intermediate messages. Inspect them if the answer looks wrong:
foreach (var m in chat)
Console.WriteLine($"{m.Role}: {m.Content}");
Expected trace: user → assistant (with FunctionCall content) → tool → assistant (final text). If the function never appears, the model ignored the schema; tighten instructions or move to a stronger model.
Step 6: Layer in a stepwise planner for auditable execution
For tasks with ordering constraints, add FunctionCallingStepwisePlanner. It produces an explicit plan, runs each step, and returns the final answer plus the step list. This complements the agent and fits the Semantic Kernel planners cluster.
using Microsoft.SemanticKernel.Planning;
var planner = new FunctionCallingStepwisePlanner(kernel);
var result = await planner.ExecuteAsync(
"Get weather for Paris and state if it is warmer than London.",
new KernelArguments());
Console.WriteLine(result.FinalAnswer);
foreach (var step in result.Steps)
Console.WriteLine($"Executed: {step}");
The planner reuses the same plugin registry. You get an auditable trace, which matters when a downstream system needs to know exactly which tools fired. It has no conversation memory—each ExecuteAsync is stateless.
Step 7: Verify the build works
Run dotnet run and confirm:
- The console prints an assistant message referencing the stub weather (proves the plugin was called).
- No
KernelFunctionbinding exceptions or 401s from the endpoint. - If you routed through n4n.ai, the standard OpenAI
usageobject carries per-token metering, so logresponse.Metadata["Usage"]or check your dashboard to confirm tokens flowed.
If the agent skips the function, enable Kernel info logging to see the raw tool-call payload. Fix the prompt before blaming the model.
Production considerations
Bound the ChatHistory. Unbounded history silently truncates earlier tool results when the context window fills, and the agent loses the ground truth it just retrieved. Snapshot history to external storage for long sessions.
Make plugins async. The inference thread should not block on I/O. Set explicit Temperature and MaxTokens on the chat completion service; default sampling makes agent behavior hard to reproduce in tests.
Start with one agent and one well-tested plugin. AgentGroupChat (preview) enables multi-agent handoff, but the failure modes multiply—get single-agent observability working first. This semantic kernel agent framework tutorial gave you the minimum viable path; extend it only after the loop is observable and the tools are honest.