Agent skill
dotnet-semantic-kernel
Build AI-enabled .NET applications with Semantic Kernel using services, plugins, prompts, and function-calling patterns that remain testable and maintainable.
Install this agent skill to your Project
npx add-skill https://github.com/managedcode/dotnet-skills/tree/main/catalog/Frameworks/Semantic-Kernel/skills/dotnet-semantic-kernel
SKILL.md
Semantic Kernel for .NET
Trigger On
- adding AI-driven prompts, plugins, or orchestration to a .NET app
- reviewing kernel construction, service registration, or plugin usage
- building function-calling patterns with LLMs
- migrating older Semantic Kernel code to current APIs
Documentation
- Semantic Kernel Overview
- Plugins and Functions
- Agent Functions
- GitHub Repository
- Microsoft Agent Framework
References
- patterns.md - Plugin patterns, function calling patterns, multi-agent patterns, prompt templates, and RAG patterns
- anti-patterns.md - Common Semantic Kernel mistakes and how to avoid them
Core Concepts
| Concept | Description |
|---|---|
| Kernel | Central orchestrator for AI services and plugins |
| Plugin | Collection of functions exposed to the LLM |
| Function | Native C# method or prompt template |
| Chat Completion | LLM service for generating responses |
| Memory | Vector storage for semantic search |
Workflow
- Build the Kernel with required services
- Create Plugins with well-described functions
- Configure Function Calling for automatic tool use
- Handle Responses and manage conversation state
- Test and Observe AI behavior with logging
Kernel Setup
Basic Configuration
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4",
endpoint: config["AzureOpenAI:Endpoint"]!,
apiKey: config["AzureOpenAI:ApiKey"]!);
// Or OpenAI
builder.AddOpenAIChatCompletion(
modelId: "gpt-4",
apiKey: config["OpenAI:ApiKey"]!);
var kernel = builder.Build();
With Dependency Injection
builder.Services.AddKernel()
.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4",
endpoint: config["AzureOpenAI:Endpoint"]!,
apiKey: config["AzureOpenAI:ApiKey"]!);
// Register plugins
builder.Services.AddSingleton<WeatherPlugin>();
builder.Services.AddSingleton<OrderPlugin>();
// In your service
public class AiService(Kernel kernel)
{
public async Task<string> ChatAsync(string message)
{
var response = await kernel.InvokePromptAsync(message);
return response.ToString();
}
}
Plugin Patterns
Creating a Plugin
public class WeatherPlugin
{
[KernelFunction]
[Description("Gets the current weather for a specified city")]
public async Task<string> GetWeather(
[Description("The city name, e.g., 'Seattle'")] string city,
[Description("Temperature unit: 'celsius' or 'fahrenheit'")] string unit = "celsius")
{
// Call actual weather API
var weather = await _weatherService.GetCurrentAsync(city);
return $"Weather in {city}: {weather.Temperature}° {unit}, {weather.Condition}";
}
[KernelFunction]
[Description("Gets the weather forecast for the next N days")]
public async Task<string> GetForecast(
[Description("The city name")] string city,
[Description("Number of days (1-7)")] int days = 3)
{
var forecast = await _weatherService.GetForecastAsync(city, days);
return FormatForecast(forecast);
}
}
Plugin Best Practices
| Practice | Why It Matters |
|---|---|
Clear [Description] |
LLM uses this to decide when to call |
| Specific parameter names | Helps LLM map user intent |
| Idempotent functions | Safe to retry on failures |
| Return meaningful strings | LLM needs to understand results |
| Validate inputs | LLM may hallucinate parameters |
Function Calling
Automatic Function Calling
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
kernel.Plugins.AddFromObject(new WeatherPlugin(), "Weather");
kernel.Plugins.AddFromObject(new OrderPlugin(), "Orders");
var result = await kernel.InvokePromptAsync(
"What's the weather in Seattle and do I have any pending orders?",
new KernelArguments(settings));
Manual Function Selection
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Required(
[kernel.Plugins["Weather"]["GetWeather"]])
};
Chat Completion Patterns
Multi-Turn Conversation
var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddSystemMessage("You are a helpful assistant.");
history.AddUserMessage(userMessage);
var response = await chatService.GetChatMessageContentAsync(
history,
executionSettings: new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
},
kernel: kernel);
history.AddAssistantMessage(response.Content!);
Streaming Response
await foreach (var chunk in chatService.GetStreamingChatMessageContentsAsync(
history, executionSettings, kernel))
{
Console.Write(chunk.Content);
}
Multi-Agent Plugin Isolation
// WRONG - agents share plugins
var sharedKernel = Kernel.CreateBuilder().Build();
sharedKernel.Plugins.AddFromObject(new AllPlugins());
var agent1 = new ChatCompletionAgent { Kernel = sharedKernel };
var agent2 = new ChatCompletionAgent { Kernel = sharedKernel };
// Both agents have same plugins!
// CORRECT - isolated kernels
var kernel1 = CreateKernelForAgent1();
kernel1.Plugins.AddFromObject(new WeatherPlugin());
var kernel2 = CreateKernelForAgent2();
kernel2.Plugins.AddFromObject(new OrderPlugin());
var agent1 = new ChatCompletionAgent { Kernel = kernel1 };
var agent2 = new ChatCompletionAgent { Kernel = kernel2 };
Anti-Patterns to Avoid
| Anti-Pattern | Why It's Bad | Better Approach |
|---|---|---|
Vague [Description] |
LLM won't call at right time | Be specific and actionable |
| Sharing kernel across agents | Plugin leakage | Clone or create new kernels |
| No input validation | Hallucinated parameters | Validate and return errors |
| Using deprecated Planners | Removed in favor of function calling | Use FunctionChoiceBehavior |
| Ignoring logging | Can't debug AI decisions | Enable Semantic Kernel logging |
Error Handling
[KernelFunction]
[Description("Places an order for a product")]
public async Task<string> PlaceOrder(
[Description("Product ID")] string productId,
[Description("Quantity (1-100)")] int quantity)
{
// Validate inputs
if (string.IsNullOrEmpty(productId))
return "Error: Product ID is required";
if (quantity < 1 || quantity > 100)
return "Error: Quantity must be between 1 and 100";
try
{
var order = await _orderService.CreateAsync(productId, quantity);
return $"Order {order.Id} placed successfully for {quantity} units";
}
catch (ProductNotFoundException)
{
return $"Error: Product '{productId}' not found";
}
}
Testing Plugins
[Fact]
public async Task GetWeather_ReturnsFormattedWeather()
{
var mockWeatherService = new Mock<IWeatherService>();
mockWeatherService.Setup(w => w.GetCurrentAsync("Seattle"))
.ReturnsAsync(new Weather { Temperature = 20, Condition = "Sunny" });
var plugin = new WeatherPlugin(mockWeatherService.Object);
var result = await plugin.GetWeather("Seattle", "celsius");
Assert.Contains("20°", result);
Assert.Contains("Sunny", result);
}
Microsoft Agent Framework
For complex multi-agent scenarios, consider dotnet-microsoft-agent-framework:
- Multi-agent orchestration
- Agent-to-agent communication
- Enterprise patterns
Deliver
- kernel setup with clear service and plugin composition
- AI features that fit naturally into the existing .NET app
- observable and testable function-calling behavior
- proper plugin isolation for multi-agent scenarios
Validate
- plugins have clear, specific descriptions
- function calling works as expected
- AI flows are logged and debuggable
- input validation prevents hallucination issues
- kernel instances are properly scoped
- deprecated APIs are not used
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
dotnet-project-setup
Create or reorganize .NET solutions with clean project boundaries, repeatable SDK settings, and a maintainable baseline for libraries, apps, tests, CI, and local development.
csharp-scripts
Run single-file C# programs as scripts (file-based apps) for quick experimentation, prototyping, and concept testing. Use when the user wants to write and execute a small C# program without creating a full project.
dotnet-pinvoke
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging existing native interop code, wrapping a C or C++ library for use in .NET, diagnosing crashes, memory leaks, or corruption at the managed/native boundary. DO NOT USE FOR: COM interop, C++/CLI mixed-mode assemblies, or pure managed code with no native dependencies.
nuget-trusted-publishing
Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to private feeds or Azure Artifacts (OIDC is nuget.org only). INVOKES: shell (powershell or bash), edit, create, ask_user for guided repo setup.
dotnet-legacy-aspnet
Maintain classic ASP.NET applications on .NET Framework, including Web Forms, older MVC, and legacy hosting patterns, while planning realistic modernization boundaries.
dotnet-code-review
Review .NET changes for bugs, regressions, architectural drift, missing tests, incorrect async or disposal behavior, and platform-specific pitfalls before you approve or merge them.
Didn't find tool you were looking for?