Semantic Kernel Plugins Created: 01 Feb 2026 Updated: 01 Feb 2026

How to Build Semantic Kernel Plugins: A Step-by-Step Guide Using Culinary Assistant

Semantic Kernel plugins are the building blocks of AI-powered applications. Unlike traditional plugins that contain compiled code, semantic plugins are composed of semantic functions - AI-powered functions defined through natural language prompts.

This article provides a comprehensive, step-by-step guide to creating semantic plugins using Microsoft Semantic Kernel. We'll build a Smart Culinary Assistant plugin from scratch, demonstrating every stage of the plugin creation process:

  1. Understanding Plugin Architecture - Core concepts and structure
  2. Designing Prompt Templates - Crafting effective prompts
  3. Creating Semantic Functions - Converting prompts to functions
  4. Assembling the Plugin - Grouping functions into a cohesive plugin
  5. Plugin Registration - Making plugins available to the kernel
  6. Plugin Invocation - Using the plugin in applications

By the end of this article, you'll understand not just how to use plugins, but how to architect, design, and build your own semantic plugins for any domain.

What Are Semantic Plugins?

Before diving into implementation, let's clarify key concepts:

Semantic Functions

A semantic function is an AI-powered function where the logic is defined by a natural language prompt rather than code. The function takes parameters, sends them to an LLM with the prompt template, and returns the LLM's response.

Plugins

A plugin is a named collection of related functions (semantic or native). Plugins provide:

  1. Organization: Group related functions logically
  2. Discoverability: Functions can be found by name or description
  3. Reusability: Plugins can be shared across applications
  4. Composability: Multiple plugins work together seamlessly

Architecture Overview

Application
Kernel (orchestrator)
Plugin (collection of functions)
Semantic Function (prompt template + parameters)
LLM (GPT-4, etc.)

Step 1: Setting Up the Kernel

Every plugin needs a kernel to run in. The kernel is the orchestration layer that manages plugins, functions, and LLM connections.

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY");

if (string.IsNullOrWhiteSpace(apiKey))
{
Console.WriteLine("Please set the OPEN_AI_KEY environment variable.");
return;
}

// Step 1: Create a kernel builder
var builder = Kernel.CreateBuilder();

// Step 2: Add an LLM service (OpenAI in this case)
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o",
apiKey: apiKey);

// Step 3: Build the kernel
var kernel = builder.Build();

Key Points:

  1. Kernel.CreateBuilder() initializes the builder pattern
  2. AddOpenAIChatCompletion() configures which LLM to use
  3. The kernel will route all semantic function calls to this LLM

Step 2: Understanding Prompt Templates

Prompt templates are the heart of semantic functions. They define how the LLM should process inputs and generate outputs.

Anatomy of a Prompt Template

A well-designed prompt template has four key components:

var promptTemplate = """
[1. ROLE/CONTEXT]
You are a professional chef's assistant specializing in ingredient substitutions.
[2. TASK DESCRIPTION]
Analyze the following missing ingredient and suggest appropriate substitutes
that will maintain the dish's flavor profile and texture.
[3. INPUT PARAMETERS]
[MISSING INGREDIENT]
{{$missing_ingredient}}
[END]
Recipe Type: {{$recipe_type}}
Dietary Restrictions: {{$dietary_restrictions}}
[4. OUTPUT FORMAT]
Provide 3-5 substitute options with:
1. Substitute ingredient name
2. Substitution ratio (e.g., 1:1, 1:2)
3. Impact on flavor/texture
4. Any preparation notes
""";

Parameter Syntax

Parameters use the {{$parameter_name}} syntax:

  1. {{$input}} - By convention, the primary input parameter
  2. {{$missing_ingredient}} - Named parameters for specific data
  3. Parameters become function arguments when invoked

Prompt Design Best Practices

  1. Be Specific: Vague prompts yield inconsistent results
  2. ❌ "Help with cooking"
  3. ✅ "Suggest ingredient substitutes maintaining flavor and texture"
  4. Use Delimiters: Clearly separate input from instructions
[MISSING INGREDIENT]
{{$missing_ingredient}}
[END]
  1. Request Structured Output: Specify the format you need
  2. "Provide a numbered list..."
  3. "Format as: Name, Ratio, Impact, Notes"
  4. Set Context: Establish the AI's role
  5. "You are a professional chef's assistant..."
  6. "You are a culinary mathematics expert..."

Step 3: Creating Semantic Functions

Once you have a prompt template, create a function using CreateFunctionFromPrompt.

Basic Function Creation

var ingredientSubstitutionPrompt = """
You are a professional chef's assistant specializing in ingredient substitutions.
Analyze the following missing ingredient and suggest appropriate substitutes
that will maintain the dish's flavor profile and texture.
[MISSING INGREDIENT]
{{$missing_ingredient}}
[END]
Recipe Type: {{$recipe_type}}
Dietary Restrictions: {{$dietary_restrictions}}
Provide 3-5 substitute options with:
1. Substitute ingredient name
2. Substitution ratio (e.g., 1:1, 1:2)
3. Impact on flavor/texture
4. Any preparation notes
""";

// Create the function
var ingredientSubstitution = kernel.CreateFunctionFromPrompt(
ingredientSubstitutionPrompt,
functionName: "substitute_ingredient",
description: "Suggests appropriate ingredient substitutes based on recipe type and dietary restrictions.");

Important Parameters:

  1. Prompt Template (required): The actual prompt text
  2. Function Name (required): Unique identifier within the plugin
  3. Use snake_case by convention
  4. Make it descriptive and action-oriented
  5. Description (required): Explains what the function does
  6. Used for function discovery
  7. Critical for AI planners that auto-select functions

Creating Multiple Functions

Let's create three more functions for our plugin:

// Function 2: Recipe Scaling
var recipeScalingPrompt = """
You are a culinary mathematics expert. Scale the following recipe ingredients
from the original serving size to the target serving size.
[ORIGINAL RECIPE]
{{$original_recipe}}
[END RECIPE]
Original Servings: {{$original_servings}}
Target Servings: {{$target_servings}}
Provide:
1. Scaled ingredient measurements
2. Adjusted cooking time and temperature if necessary
3. Any special considerations for the scaled recipe
""";

var recipeScaling = kernel.CreateFunctionFromPrompt(
recipeScalingPrompt,
functionName: "scale_recipe",
description: "Scales recipe ingredients and adjusts cooking parameters for different serving sizes.");

// Function 3: Meal Planning
var mealPlanningPrompt = """
Create a balanced meal plan based on the following requirements:
Dietary Preference: {{$dietary_preference}}
Calorie Target: {{$calorie_target}}
Number of Meals: {{$meal_count}}
Cuisine Preferences: {{$cuisine_preferences}}
Available Time: {{$available_time}}
For each meal, provide:
1. Meal name
2. Main ingredients (5-7 items)
3. Estimated preparation time
4. Approximate calorie count
5. Nutritional highlights (protein, fiber, vitamins)
Ensure variety and nutritional balance across all meals.
""";

var mealPlanning = kernel.CreateFunctionFromPrompt(
mealPlanningPrompt,
functionName: "plan_meals",
description: "Creates balanced meal plans based on dietary preferences, calorie targets, and time constraints.");

// Function 4: Cooking Techniques
var cookingTechniquePrompt = """
Explain the following cooking technique in detail for someone with
{{$skill_level}} cooking experience:
Technique: {{$technique}}
Context: {{$context}}
Provide:
1. Step-by-step instructions
2. Common mistakes to avoid
3. Tips for mastering the technique
4. Equipment needed
5. Expected results/how to know it's done correctly
""";

var cookingTechnique = kernel.CreateFunctionFromPrompt(
cookingTechniquePrompt,
functionName: "explain_technique",
description: "Provides detailed explanations of cooking techniques tailored to user's skill level.");

Step 4: Assembling the Plugin

Now we group related functions into a plugin using ImportPluginFromFunctions.

// Import (create + register) the plugin
kernel.ImportPluginFromFunctions(
"culinary_assistant_plugin", // Plugin name
"A comprehensive cooking assistant plugin for ingredient substitution, recipe scaling, meal planning, and technique guidance.", // Plugin description
[ingredientSubstitution, recipeScaling, mealPlanning, cookingTechnique] // Array of functions
);

What Happens Here:

  1. Plugin Creation: A new plugin named culinary_assistant_plugin is created
  2. Function Registration: All four functions are added to the plugin
  3. Kernel Registration: The plugin is registered with the kernel
  4. Metadata Storage: Names and descriptions are stored for discovery

Plugin Design Principles

When grouping functions into plugins:

  1. Cohesion: Functions should be related (all about cooking)
  2. Single Responsibility: Plugin serves one domain (culinary assistance)
  3. Reasonable Size: 3-10 functions per plugin (not too small, not too large)
  4. Clear Naming: Plugin name reflects its purpose

Good Plugin Groupings:

  1. culinary_assistant_plugin - ingredient substitution, recipe scaling, meal planning
  2. travel_planning_plugin - destination analysis, itinerary building, budget optimization
  3. code_review_plugin - style checking, bug detection, documentation generation

Poor Plugin Groupings:

  1. utility_plugin - Too generic, unclear purpose
  2. ai_helper - Too vague, could contain anything
  3. function_collection - Just a bag of unrelated functions

Step 5: Plugin Registration and Discovery

After importing, the plugin is available in the kernel. Let's explore how to inspect plugin metadata.

Exploring Plugin Metadata

static void PrintPluginDetails(Kernel kernel)
{
Console.WriteLine("Loaded Plugins and Functions:\n");
// Iterate through all registered plugins
foreach (var plugin in kernel.Plugins)
{
Console.WriteLine($"Plugin: {plugin.Name}");
Console.WriteLine($"Description: {plugin.Description}");
Console.WriteLine($"Function Count: {plugin.FunctionCount}\n");
// Iterate through functions in this plugin
foreach (var function in plugin.GetFunctionsMetadata())
{
Console.WriteLine($" Function: {function.Name}");
Console.WriteLine($" Description: {function.Description}");
// List parameters
if (function.Parameters.Count > 0)
{
Console.WriteLine(" Parameters:");
foreach (var parameter in function.Parameters)
{
Console.WriteLine($" - {parameter.Name}: {parameter.Description ?? "No description"}");
if (!string.IsNullOrEmpty(parameter.Schema?.ToString()))
{
Console.WriteLine($" Schema: {parameter.Schema}");
}
}
}
Console.WriteLine();
}
}
}

// Call it to see what's registered
PrintPluginDetails(kernel);

Example Output:

Loaded Plugins and Functions:

Plugin: culinary_assistant_plugin
Description: A comprehensive cooking assistant plugin...
Function Count: 4

Function: substitute_ingredient
Description: Suggests appropriate ingredient substitutes...
Parameters:
- missing_ingredient: No description
Schema: {"type":"string"}
- recipe_type: No description
Schema: {"type":"string"}
- dietary_restrictions: No description
Schema: {"type":"string"}

Function: scale_recipe
Description: Scales recipe ingredients...
Parameters:
- original_recipe: No description
Schema: {"type":"string"}
- original_servings: No description
Schema: {"type":"string"}
- target_servings: No description
Schema: {"type":"string"}

Why Metadata Matters:

  1. Debugging: See exactly what's registered
  2. Discovery: Functions can be found programmatically
  3. Auto-Planning: AI planners use metadata to select appropriate functions
  4. Documentation: Self-documenting plugin capabilities

Step 6: Invoking Plugin Functions

Now that we've created and registered our plugin, let's use it!

Basic Invocation

// Invoke a function by plugin name and function name
var result = await kernel.InvokeAsync(
"culinary_assistant_plugin", // Plugin name
"substitute_ingredient", // Function name
new KernelArguments // Arguments
{
["missing_ingredient"] = "butter",
["recipe_type"] = "chocolate chip cookies",
["dietary_restrictions"] = "none"
});

Console.WriteLine(result);

What Happens During Invocation:

  1. Argument Binding: Parameters are matched to prompt template placeholders
  2. Prompt Generation: Template is filled with actual values
  3. LLM Call: Complete prompt is sent to GPT-4o
  4. Response Processing: LLM response is returned as result

Complete Example - Ingredient Substitution

Console.WriteLine("\n=== Example 1: Ingredient Substitution ===\n");
Console.WriteLine("Scenario: Making chocolate chip cookies but out of butter\n");

var substitutionResult = await kernel.InvokeAsync(
"culinary_assistant_plugin",
"substitute_ingredient",
new KernelArguments
{
["missing_ingredient"] = "butter",
["recipe_type"] = "chocolate chip cookies",
["dietary_restrictions"] = "none"
});

Console.WriteLine($"Substitution Suggestions:\n{substitutionResult}\n");

Multi-Parameter Function Invocation

Console.WriteLine("\n=== Example 2: Recipe Scaling ===\n");

var recipeToScale = """
Ingredients:
- 2 cups all-purpose flour
- 1 teaspoon baking powder
- 1/2 teaspoon salt
- 3/4 cup sugar
- 1/3 cup butter
- 2 eggs
- 1 teaspoon vanilla extract
- 1/2 cup milk
""";

var scalingResult = await kernel.InvokeAsync(
"culinary_assistant_plugin",
"scale_recipe",
new KernelArguments
{
["original_recipe"] = recipeToScale,
["original_servings"] = "6",
["target_servings"] = "12"
});

Console.WriteLine($"Scaled Recipe:\n{scalingResult}\n");

Complex Parameter Example - Meal Planning

Console.WriteLine("\n=== Example 3: Meal Planning ===\n");

var mealPlanResult = await kernel.InvokeAsync(
"culinary_assistant_plugin",
"plan_meals",
new KernelArguments
{
["dietary_preference"] = "Mediterranean",
["calorie_target"] = "2000 calories per day",
["meal_count"] = "3 meals per day for 2 days",
["cuisine_preferences"] = "Italian, Greek, Middle Eastern",
["available_time"] = "30-45 minutes per meal"
});

Console.WriteLine($"Meal Plan:\n{mealPlanResult}\n");

Adaptive Function - Skill Level

Console.WriteLine("\n=== Example 4: Cooking Technique Explanation ===\n");

var techniqueResult = await kernel.InvokeAsync(
"culinary_assistant_plugin",
"explain_technique",
new KernelArguments
{
["technique"] = "sous vide cooking",
["skill_level"] = "intermediate",
["context"] = "cooking a medium-rare steak"
});

Console.WriteLine($"Technique Guide:\n{techniqueResult}\n");

Understanding the Plugin Creation Workflow

Let's recap the entire workflow with a visual representation:

┌─────────────────────────────────────────────────────────┐
│ 1. DESIGN PHASE │
│ - Identify domain (culinary assistance) │
│ - List required functions (substitute, scale, etc.) │
│ - Define parameters for each function │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 2. PROMPT TEMPLATE CREATION │
│ For each function: │
│ - Set role/context │
│ - Describe task │
│ - Define input parameters with {{$name}} │
│ - Specify output format │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 3. FUNCTION CREATION │
│ kernel.CreateFunctionFromPrompt( │
│ promptTemplate, │
│ functionName: "function_name", │
│ description: "What it does" │
│ ) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 4. PLUGIN ASSEMBLY │
│ kernel.ImportPluginFromFunctions( │
│ "plugin_name", │
│ "Plugin description", │
│ [function1, function2, function3, ...] │
│ ) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 5. INVOCATION │
│ kernel.InvokeAsync( │
│ "plugin_name", │
│ "function_name", │
│ new KernelArguments { ... } │
│ ) │
└─────────────────────────────────────────────────────────┘


Share this lesson: