One of the most powerful features of Microsoft Semantic Kernel is its ability to automatically generate plugins from OpenAPI specifications. This capability allows developers to seamlessly integrate existing REST APIs into their AI-powered applications without writing manual wrapper code.
In this article, we'll demonstrate how to create an OpenAPI plugin that communicates with a Todo API, enabling an AI assistant to manage tasks through natural language commands.
What is OpenAPI?
OpenAPI (formerly known as Swagger) is a specification format for describing HTTP APIs. It provides a standardized, machine-readable, and human-friendly way to define:
- API endpoints and operations
- Request/response parameters
- Data schemas
- Authentication methods
When Semantic Kernel imports an OpenAPI specification, it automatically:
- Discovers all available endpoints
- Creates corresponding kernel functions
- Maps parameters and return types
- Enables AI models to call these functions
Architecture Overview
+---------------------+ +----------------------+ +-----------------+
| Semantic Kernel |---->| OpenAPI Plugin |---->| Todo API |
| + AI Model | | (Auto-generated) | | (REST API) |
+---------------------+ +----------------------+ +-----------------+
| | |
| Natural Language | HTTP Requests | Database
| "Show my pending tasks" | GET /api/todos/pending | Operations
v v v
Part 1: Building the Todo API
First, let's create a minimal API with OpenAPI/Swagger support:
Project Setup
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
</ItemGroup>
</Project>
API Implementation
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDbContext>(options =>
options.UseInMemoryDatabase("TodoDb"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new()
{
Title = "Todo API",
Version = "v1",
Description = "A Todo API for Semantic Kernel OpenAPI Plugin demo"
});
});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
var todoGroup = app.MapGroup("/api/todos").WithTags("Todos");
// GET all todos
todoGroup.MapGet("/", async (TodoDbContext db) =>
await db.Todos.ToListAsync())
.WithName("GetAllTodos")
.WithDescription("Gets all todo items")
.WithOpenApi();
// GET pending todos
todoGroup.MapGet("/pending", async (TodoDbContext db) =>
await db.Todos.Where(t => !t.IsCompleted).ToListAsync())
.WithName("GetPendingTodos")
.WithDescription("Gets all pending todo items")
.WithOpenApi();
// GET completed todos
todoGroup.MapGet("/completed", async (TodoDbContext db) =>
await db.Todos.Where(t => t.IsCompleted).ToListAsync())
.WithName("GetCompletedTodos")
.WithDescription("Gets all completed todo items")
.WithOpenApi();
// GET todo by id
todoGroup.MapGet("/{id:int}", async (int id, TodoDbContext db) =>
await db.Todos.FindAsync(id) is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.WithName("GetTodoById")
.WithDescription("Gets a todo item by id")
.WithOpenApi();
// POST create todo
todoGroup.MapPost("/", async (CreateTodoRequest request, TodoDbContext db) =>
{
var todo = new Todo
{
Title = request.Title,
Description = request.Description,
CreatedAt = DateTime.UtcNow
};
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/api/todos/{todo.Id}", todo);
})
.WithName("CreateTodo")
.WithDescription("Creates a new todo item")
.WithOpenApi();
// DELETE todo
todoGroup.MapDelete("/{id:int}", async (int id, TodoDbContext db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
})
.WithName("DeleteTodo")
.WithDescription("Deletes a todo item")
.WithOpenApi();
app.Run();
public class Todo
{
public int Id { get; set; }
public required string Title { get; set; }
public string? Description { get; set; }
public bool IsCompleted { get; set; }
public DateTime CreatedAt { get; set; }
}
public record CreateTodoRequest(string Title, string? Description);
public class TodoDbContext(DbContextOptions<TodoDbContext> options)
: DbContext(options)
{
public DbSet<Todo> Todos => Set<Todo>();
}
Key Points for OpenAPI Compatibility
| Method Purpose |
.WithName() | Sets the operation ID (becomes function name) |
.WithDescription() | Describes the operation (helps AI understand) |
.WithOpenApi() | Includes endpoint in OpenAPI spec |
Part 2: Creating the OpenAPI Plugin
Now let's create a Semantic Kernel application that consumes this API:
Required Package
<PackageReference Include="Microsoft.SemanticKernel.Plugins.OpenApi" Version="1.68.0-preview" />
Plugin Implementation
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.OpenApi;
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o",
apiKey: Environment.GetEnvironmentVariable("OPEN_AI_KEY")!);
var kernel = builder.Build();
// Import plugin from OpenAPI specification
#pragma warning disable SKEXP0040
var todoPlugin = await kernel.CreatePluginFromOpenApiAsync(
pluginName: "TodoAPI",
uri: new Uri("https://localhost:7001/swagger/v1/swagger.json"),
executionParameters: new OpenApiFunctionExecutionParameters
{
EnableDynamicPayload = true,
EnablePayloadNamespacing = true
});
kernel.Plugins.Add(todoPlugin);
Invoking Functions Directly
// Get all todos
var allTodos = await kernel.InvokeAsync(todoPlugin["GetAllTodos"]);
Console.WriteLine($"All Todos: {allTodos}");
// Get todo by ID
var todo = await kernel.InvokeAsync(
todoPlugin["GetTodoById"],
new KernelArguments { ["id"] = 1 });
Console.WriteLine($"Todo #1: {todo}");
// Create a new todo
var newTodo = await kernel.InvokeAsync(
todoPlugin["CreateTodo"],
new KernelArguments
{
["payload"] = """{"title": "New Task", "description": "Created via SK"}"""
});
Console.WriteLine($"Created: {newTodo}");
Part 3: AI-Powered Task Management
The real power comes when combining OpenAPI plugins with AI:
var executionSettings = new PromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var response = await kernel.InvokePromptAsync(
"""
You are a helpful task management assistant.
Use the TodoAPI functions to help users manage their tasks.
User: Show me my pending tasks and create a reminder to review them tomorrow.
""",
new KernelArguments(executionSettings));
Console.WriteLine(response);
How It Works
- AI Receives Request: The model receives the user's natural language request
- Function Discovery: AI examines available functions from the OpenAPI plugin
- Automatic Invocation: AI decides which functions to call and with what parameters
- Response Generation: AI combines API results into a natural language response
OpenApiFunctionExecutionParameters
This class provides important configuration options:
| Parameter Description |
EnableDynamicPayload | Allows dynamic JSON payloads for POST/PUT |
EnablePayloadNamespacing | Namespaces payload parameters |
AuthCallback | Authentication callback for secured APIs |
OperationsToExclude | List of operations to hide from the plugin |
HttpClient | Custom HTTP client for requests |
Authentication Example
var parameters = new OpenApiFunctionExecutionParameters
{
AuthCallback = async (request, cancellationToken) =>
{
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", "your-token");
}
};
Expected Output
[Package] Plugin: TodoAPI
Functions discovered: 6
[Function] GetAllTodos
Description: Gets all todo items
[Function] GetPendingTodos
Description: Gets all pending todo items
[Function] GetCompletedTodos
Description: Gets all completed todo items
[Function] GetTodoById
Description: Gets a todo item by id
Parameters:
- id (required): Format - int32.
[Function] CreateTodo
Description: Creates a new todo item
Parameters:
- payload (required): The request body
[Function] DeleteTodo
Description: Deletes a todo item
Parameters:
- id (required): Format - int32.
[Test] TEST 1: Get All Todos
Result: [{"id":1,"title":"Learn Semantic Kernel",...},...]
[AI] AI Response:
Here are your pending tasks:
1. Learn Semantic Kernel - Study plugins and functions
2. Build OpenAPI Plugin - Create a plugin from OpenAPI spec
You have 2 pending tasks to complete. I recommend starting with
"Learn Semantic Kernel" as it will help you better understand
the OpenAPI Plugin task.
Benefits of OpenAPI Plugins
| BenefitDescription |
| Zero Boilerplate | No manual HTTP client code needed |
| Type Safety | Parameters are validated against schema |
| AI Integration | Functions are automatically available to AI |
| Maintainability | API changes reflect automatically |
| Documentation | Function descriptions come from OpenAPI spec |
Best Practices
- Use Descriptive Names: Operation IDs become function names
- Write Clear Descriptions: AI uses descriptions to understand functions
- Include Parameter Docs: Help AI provide correct parameter values
- Handle Errors: Implement proper error responses in your API
- Secure Your API: Use authentication for production APIs
- Test Independently: Verify API works before integrating with SK
Conclusion
OpenAPI plugins in Semantic Kernel provide a powerful bridge between existing REST APIs and AI-powered applications. By following the OpenAPI specification and using descriptive metadata, you can create intelligent assistants that naturally interact with your services.
The Todo API example demonstrates how a simple CRUD API can become an AI-accessible service, enabling natural language task management. This pattern can be applied to any OpenAPI-compliant service, from internal microservices to third-party APIs.