One of the most elegant ways to create native plugins in Semantic Kernel is using the [KernelFunction] attribute. Unlike the reflection-based approach where you manually create functions from methods, the attribute-based approach enables automatic discovery - Semantic Kernel scans your class and automatically registers all methods marked with [KernelFunction].
This article provides a comprehensive guide to creating attribute-based native plugins. We'll build two real-world examples:
- DataAnalyzerPlugin - Statistical analysis and data processing (parameterless constructor)
- FileManagerPlugin - File system operations with dependency injection (parameterized constructor)
By the end, you'll understand:
- How
[KernelFunction] attribute auto-discovery works - The difference between
ImportPluginFromType and ImportPluginFromObject - When to use parameterless vs parameterized constructors
- Best practices for attribute-based plugin development
- Handling state and dependencies in plugins
Understanding the [KernelFunction] Attribute
The [KernelFunction] attribute is a marker that tells Semantic Kernel:
"This method should be exposed as a kernel function"
Key Benefits
- Automatic Discovery: No manual reflection code needed
- Clean Syntax: Methods are self-documenting
- Type Safety: Compile-time checking of signatures
- Convention over Configuration: Less boilerplate code
- IDE Support: Full IntelliSense and refactoring support
Basic Syntax
using Microsoft.SemanticKernel;
using System.ComponentModel;
public class MyPlugin
{
[KernelFunction("function_name")]
[Description("What this function does")]
public string MyMethod([Description("Parameter description")] string input)
{
return input.ToUpper();
}
}
Attribute Breakdown
[KernelFunction("function_name")]: Marks method as a kernel function with a specific name[Description("...")]: Provides documentation (on class, method, and parameters)- Parameter descriptions: Help AI understand what inputs are expected
ImportPluginFromType vs ImportPluginFromObject
Semantic Kernel provides two methods for importing attribute-based plugins:
1. ImportPluginFromType
Use when: Your plugin class has a parameterless constructor
kernel.ImportPluginFromType<DataAnalyzerPlugin>("data_analyzer");
What happens:
- Semantic Kernel creates an instance using
new DataAnalyzerPlugin() - Scans all methods marked with
[KernelFunction] - Registers them as kernel functions
- Associates them with the plugin name
Advantages:
- Simple, one-line registration
- Semantic Kernel manages instance lifecycle
- Perfect for stateless plugins
Limitations:
- Requires parameterless constructor
- Cannot inject dependencies
- Cannot pass configuration
2. ImportPluginFromObject
Use when: Your plugin class has a parameterized constructor or needs dependencies
var logger = new Logger("AppLogger");
var plugin = new FileManagerPlugin(logger, @"C:\temp");
kernel.ImportPluginFromObject(plugin, "file_manager");
What happens:
- You create the instance with your dependencies
- Pass the pre-configured instance to Semantic Kernel
- Semantic Kernel scans it for
[KernelFunction] methods - Registers all discovered functions
Advantages:
- Full control over instance creation
- Dependency injection support
- Configuration flexibility
- Supports stateful plugins
Use cases:
- Plugins requiring database connections
- Services needing logging or telemetry
- Plugins with configuration settings
- Stateful operations
Example 1: DataAnalyzerPlugin (Parameterless Constructor)
Let's build a statistical analysis plugin with a parameterless constructor:
using Microsoft.SemanticKernel;
using System.ComponentModel;
[Description("Analyzes numerical data and provides statistical information")]
public class DataAnalyzerPlugin
{
[KernelFunction("calculate_average")]
[Description("Calculates the arithmetic mean of a collection of numbers")]
public double CalculateAverage(
[Description("Array of numbers to calculate average")] double[] numbers)
{
if (numbers == null || numbers.Length == 0)
return 0;
return numbers.Average();
}
[KernelFunction("find_min_max")]
[Description("Finds the minimum and maximum values in a collection")]
public string FindMinMax(
[Description("Array of integers to analyze")] int[] numbers)
{
if (numbers == null || numbers.Length == 0)
return "No data provided";
var min = numbers.Min();
var max = numbers.Max();
return $"Min: {min}, Max: {max}";
}
[KernelFunction("sort_numbers")]
[Description("Sorts a collection of numbers in ascending or descending order")]
public string SortNumbers(
[Description("Array of integers to sort")] int[] numbers,
[Description("True for ascending, false for descending")] bool ascending = true)
{
if (numbers == null || numbers.Length == 0)
return "[]";
var sorted = ascending
? numbers.OrderBy(x => x).ToArray()
: numbers.OrderByDescending(x => x).ToArray();
return $"[{string.Join(", ", sorted)}]";
}
[KernelFunction("calculate_median")]
[Description("Calculates the median value of a collection of numbers")]
public double CalculateMedian(
[Description("Array of numbers to find median")] double[] numbers)
{
if (numbers == null || numbers.Length == 0)
return 0;
var sorted = numbers.OrderBy(x => x).ToArray();
int mid = sorted.Length / 2;
if (sorted.Length % 2 == 0)
return (sorted[mid - 1] + sorted[mid]) / 2.0;
return sorted[mid];
}
}
Key Features of DataAnalyzerPlugin
- Class-Level Description: Documents the plugin's overall purpose
- Multiple Functions: Four different statistical operations
- Various Return Types:
double, string - Semantic Kernel handles all .NET types - Array Parameters: Demonstrates handling collections
- Optional Parameters:
ascending parameter has a default value - Input Validation: Checks for null/empty arrays
Registering the Plugin
var kernel = builder.Build();
// Simple one-line registration
kernel.ImportPluginFromType<DataAnalyzerPlugin>("data_analyzer");
That's it! Semantic Kernel:
- Creates a
new DataAnalyzerPlugin() - Discovers all 4 methods with
[KernelFunction] - Registers them as:
calculate_average, find_min_max, sort_numbers, calculate_median
Using the Plugin
// Calculate average
var avgResult = await kernel.InvokeAsync(
"data_analyzer",
"calculate_average",
new KernelArguments { ["numbers"] = new[] { 10.5, 20.3, 15.7, 8.9, 12.1 } });
Console.WriteLine($"Average: {avgResult}");
// Output: Average: 13.5
// Find min and max
var minMaxResult = await kernel.InvokeAsync(
"data_analyzer",
"find_min_max",
new KernelArguments { ["numbers"] = new[] { 45, 12, 78, 23, 56, 8, 90 } });
Console.WriteLine($"Min-Max: {minMaxResult}");
// Output: Min-Max: Min: 8, Max: 90
// Sort numbers
var sortResult = await kernel.InvokeAsync(
"data_analyzer",
"sort_numbers",
new KernelArguments
{
["numbers"] = new[] { 45, 12, 78, 23, 56 },
["ascending"] = false // Sort descending
});
Console.WriteLine($"Sorted: {sortResult}");
// Output: Sorted: [78, 56, 45, 23, 12]
Example 2: FileManagerPlugin (Parameterized Constructor)
Now let's build a plugin that requires dependencies - a file manager with logging:
[Description("Manages file operations with logging capabilities")]
public class FileManagerPlugin
{
private readonly Logger _logger;
private readonly string _baseDirectory;
// Constructor with dependencies
public FileManagerPlugin(Logger logger, string baseDirectory)
{
_logger = logger;
_baseDirectory = baseDirectory;
_logger.Log($"FileManagerPlugin initialized with base directory: {baseDirectory}");
}
[KernelFunction("list_files")]
[Description("Lists all files in the base directory with optional extension filter")]
public string ListFiles(
[Description("File extension filter (e.g., '.txt', '.pdf')")] string extension = "*")
{
_logger.Log($"Listing files with extension: {extension}");
try
{
if (!Directory.Exists(_baseDirectory))
{
_logger.Log($"Directory not found: {_baseDirectory}");
return $"Directory not found: {_baseDirectory}";
}
var searchPattern = extension == "*" ? "*.*" : $"*{extension}";
var files = Directory.GetFiles(_baseDirectory, searchPattern);
if (files.Length == 0)
{
return $"No files found with extension '{extension}'";
}
var fileNames = files.Select(Path.GetFileName);
return string.Join("\n", fileNames);
}
catch (Exception ex)
{
_logger.Log($"Error listing files: {ex.Message}");
return $"Error: {ex.Message}";
}
}
[KernelFunction("get_file_info")]
[Description("Gets detailed information about a specific file")]
public string GetFileInfo(
[Description("Name of the file to get information about")] string fileName)
{
_logger.Log($"Getting info for file: {fileName}");
try
{
var fullPath = Path.Combine(_baseDirectory, fileName);
if (!File.Exists(fullPath))
{
return $"File not found: {fileName}";
}
var fileInfo = new FileInfo(fullPath);
return $"Name: {fileInfo.Name}\n" +
$"Size: {fileInfo.Length} bytes\n" +
$"Created: {fileInfo.CreationTime}\n" +
$"Modified: {fileInfo.LastWriteTime}\n" +
$"Extension: {fileInfo.Extension}";
}
catch (Exception ex)
{
_logger.Log($"Error getting file info: {ex.Message}");
return $"Error: {ex.Message}";
}
}
[KernelFunction("count_files")]
[Description("Counts the total number of files in the base directory")]
public int CountFiles()
{
_logger.Log("Counting total files");
try
{
if (!Directory.Exists(_baseDirectory))
return 0;
return Directory.GetFiles(_baseDirectory).Length;
}
catch (Exception ex)
{
_logger.Log($"Error counting files: {ex.Message}");
return -1;
}
}
[KernelFunction("get_directory_size")]
[Description("Calculates the total size of all files in the base directory in MB")]
public string GetDirectorySize()
{
_logger.Log("Calculating directory size");
try
{
if (!Directory.Exists(_baseDirectory))
return "Directory not found";
var files = Directory.GetFiles(_baseDirectory);
long totalBytes = files.Sum(file => new FileInfo(file).Length);
double totalMB = totalBytes / (1024.0 * 1024.0);
return $"{totalMB:F2} MB ({totalBytes:N0} bytes)";
}
catch (Exception ex)
{
_logger.Log($"Error calculating directory size: {ex.Message}");
return $"Error: {ex.Message}";
}
}
}
Supporting Logger Class
public class Logger
{
private readonly string _name;
private readonly List<string> _logs = [];
public Logger(string name)
{
_name = name;
}
public void Log(string message)
{
var logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{_name}] {message}";
_logs.Add(logEntry);
Console.WriteLine(logEntry);
}
public IReadOnlyList<string> GetLogs() => _logs.AsReadOnly();
}
Key Features of FileManagerPlugin
- Dependency Injection: Constructor accepts
Logger and baseDirectory - State Management: Maintains
_logger and _baseDirectory as private fields - Error Handling: Try-catch blocks with logging
- Optional Parameters:
extension parameter with default value - Real-World Functionality: Actual file system operations
- Logging Integration: Every operation is logged
Registering with Dependencies
// Create dependencies
var logger = new Logger("AppLogger");
var baseDirectory = @"C:\temp";
// Create plugin instance with dependencies
var fileManager = new FileManagerPlugin(logger, baseDirectory);
// Register the pre-configured instance
kernel.ImportPluginFromObject(fileManager, "file_manager");
Why ImportPluginFromObject?
- We need to pass
logger and baseDirectory to the constructor ImportPluginFromType cannot handle this - it requires parameterless constructor- We create the instance ourselves and hand it to Semantic Kernel
Using the FileManagerPlugin
// List all text files
var listResult = await kernel.InvokeAsync(
"file_manager",
"list_files",
new KernelArguments { ["extension"] = ".txt" });
Console.WriteLine($"Files Found:\n{listResult}");
// Output:
// document.txt
// notes.txt
// readme.txt
// Get info about a specific file
var infoResult = await kernel.InvokeAsync(
"file_manager",
"get_file_info",
new KernelArguments { ["fileName"] = "example.txt" });
Console.WriteLine($"File Info:\n{infoResult}");
// Output:
// Name: example.txt
// Size: 1024 bytes
// Created: 2024-01-15 10:30:00
// Modified: 2024-01-20 14:45:00
// Extension: .txt
// Count all files
var countResult = await kernel.InvokeAsync(
"file_manager",
"count_files",
new KernelArguments());
Console.WriteLine($"Total Files: {countResult}");
// Output: Total Files: 15
// Get directory size
var sizeResult = await kernel.InvokeAsync(
"file_manager",
"get_directory_size",
new KernelArguments());
Console.WriteLine($"Directory Size: {sizeResult}");
// Output: Directory Size: 2.45 MB (2,568,192 bytes)
Observing the Logging
Every operation is logged:
[2024-01-22 15:30:45] [AppLogger] FileManagerPlugin initialized with base directory: C:\temp
[2024-01-22 15:30:46] [AppLogger] Listing files with extension: .txt
[2024-01-22 15:30:47] [AppLogger] Getting info for file: example.txt
[2024-01-22 15:30:48] [AppLogger] Counting total files
[2024-01-22 15:30:49] [AppLogger] Calculating directory size
Complete Working Example
Here's the full Program.cs demonstrating both approaches:
using Microsoft.SemanticKernel;
using System.ComponentModel;
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY");
if (string.IsNullOrWhiteSpace(apiKey))
{
Console.WriteLine("Please set the OPEN_AI_KEY environment variable.");
return;
}
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
modelId: "gpt-4o",
apiKey: apiKey);
var kernel = builder.Build();
Console.WriteLine("=== Native Plugin Using [KernelFunction] Attribute ===\n");
// Example 1: Parameterless constructor - ImportPluginFromType
Console.WriteLine("--- Example 1: Using ImportPluginFromType ---\n");
kernel.ImportPluginFromType<DataAnalyzerPlugin>("data_analyzer");
// Example 2: Parameterized constructor - ImportPluginFromObject
Console.WriteLine("\n--- Example 2: Using ImportPluginFromObject ---\n");
var logger = new Logger("AppLogger");
var fileManager = new FileManagerPlugin(logger, @"C:\temp");
kernel.ImportPluginFromObject(fileManager, "file_manager");
// Test both plugins
var avgResult = await kernel.InvokeAsync(
"data_analyzer",
"calculate_average",
new KernelArguments { ["numbers"] = new[] { 10.5, 20.3, 15.7 } });
var listResult = await kernel.InvokeAsync(
"file_manager",
"list_files",
new KernelArguments { ["extension"] = ".txt" });
Inspecting Plugin Metadata
Examine what Semantic Kernel discovered:
static void PrintPluginsWithFunctions(Kernel kernel)
{
Console.WriteLine("Registered Plugins and Functions:\n");
foreach (var plugin in kernel.Plugins)
{
Console.WriteLine($"Plugin: {plugin.Name}");
Console.WriteLine($"Description: {plugin.Description}");
Console.WriteLine($"Function Count: {plugin.FunctionCount}\n");
foreach (var function in plugin.GetFunctionsMetadata())
{
Console.WriteLine($" Function: {function.Name}");
Console.WriteLine($" Description: {function.Description}");
Console.WriteLine($" Return Type: {function.ReturnParameter.Schema}");
if (function.Parameters.Count > 0)
{
Console.WriteLine(" Parameters:");
foreach (var parameter in function.Parameters)
{
Console.WriteLine($" - {parameter.Name}: {parameter.Schema}");
}
}
Console.WriteLine();
}
}
}
PrintPluginsWithFunctions(kernel);
Output:
Registered Plugins and Functions:
Plugin: data_analyzer
Description: Analyzes numerical data and provides statistical information
Function Count: 4
Function: calculate_average
Description: Calculates the arithmetic mean of a collection of numbers
Return Type: {"type":"number"}
Parameters:
- numbers: {"type":"array","items":{"type":"number"}}
Function: find_min_max
Description: Finds the minimum and maximum values in a collection
Return Type: {"type":"string"}
Parameters:
- numbers: {"type":"array","items":{"type":"integer"}}
Function: sort_numbers
Description: Sorts a collection of numbers in ascending or descending order
Return Type: {"type":"string"}
Parameters:
- numbers: {"type":"array","items":{"type":"integer"}}
- ascending: {"type":"boolean"}
Function: calculate_median
Description: Calculates the median value of a collection of numbers
Return Type: {"type":"number"}
Parameters:
- numbers: {"type":"array","items":{"type":"number"}}
Plugin: file_manager
Description: Manages file operations with logging capabilities
Function Count: 4
Function: list_files
Description: Lists all files in the base directory with optional extension filter
Return Type: {"type":"string"}
Parameters:
- extension: {"type":"string"}
Function: get_file_info
Description: Gets detailed information about a specific file
Return Type: {"type":"string"}
Parameters:
- fileName: {"type":"string"}
Function: count_files
Description: Counts the total number of files in the base directory
Return Type: {"type":"integer"}
Parameters: (none)
Function: get_directory_size
Description: Calculates the total size of all files in the base directory in MB
Return Type: {"type":"string"}
Parameters: (none)
When to Use Each Approach
Use ImportPluginFromType When:
✅ Plugin is stateless ✅ No dependencies required ✅ Simple, self-contained operations ✅ Parameterless constructor is acceptable
Examples:
- Mathematical calculators
- String utilities
- Data validators
- Format converters
Use ImportPluginFromObject When:
✅ Plugin needs dependencies (logger, database, config) ✅ Constructor requires parameters ✅ Plugin maintains state ✅ Integration with DI container
Examples:
- File system operations
- Database access
- External API calls
- Services with configuration
- Stateful services (counters, caches)
Advanced Patterns
Pattern 1: Dependency Injection with DI Container
// Register plugin in DI container
services.AddSingleton<IFileService, FileService>();
services.AddSingleton<FileOperationsPlugin>();
// Get from container
var serviceProvider = services.BuildServiceProvider();
var filePlugin = serviceProvider.GetRequiredService<FileOperationsPlugin>();
// Register in kernel
kernel.ImportPluginFromObject(filePlugin, "file_ops");
Pattern 2: Configuration-Based Plugin
public class DatabasePlugin
{
private readonly string _connectionString;
public DatabasePlugin(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("Default");
}
[KernelFunction("query_users")]
[Description("Queries users from database")]
public async Task<string> QueryUsers(string filter)
{
// Use _connectionString
}
}
// Usage
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var dbPlugin = new DatabasePlugin(config);
kernel.ImportPluginFromObject(dbPlugin, "database");
Pattern 3: Stateful Plugin with Singleton Instance
[Description("Maintains application-wide counter state")]
public class CounterPlugin
{
private int _count = 0;
[KernelFunction("increment")]
[Description("Increments the counter and returns new value")]
public int Increment()
{
return Interlocked.Increment(ref _count);
}
[KernelFunction("get_count")]
[Description("Gets current counter value")]
public int GetCount() => _count;
[KernelFunction("reset")]
[Description("Resets counter to zero")]
public void Reset()
{
_count = 0;
}
}
// Create singleton instance
var counter = new CounterPlugin();
kernel.ImportPluginFromObject(counter, "counter");
// All invocations share the same state
await kernel.InvokeAsync("counter", "increment"); // Returns 1
await kernel.InvokeAsync("counter", "increment"); // Returns 2
await kernel.InvokeAsync("counter", "get_count"); // Returns 2
Pattern 4: Multiple Instances of Same Plugin
// Create multiple file managers for different directories
var tempManager = new FileManagerPlugin(logger, @"C:\temp");
var docsManager = new FileManagerPlugin(logger, @"C:\Documents");
var downloadsManager = new FileManagerPlugin(logger, @"C:\Downloads");
// Register with different names
kernel.ImportPluginFromObject(tempManager, "temp_files");
kernel.ImportPluginFromObject(docsManager, "document_files");
kernel.ImportPluginFromObject(downloadsManager, "download_files");
// Use separately
await kernel.InvokeAsync("temp_files", "count_files");
await kernel.InvokeAsync("document_files", "count_files");
await kernel.InvokeAsync("download_files", "count_files");
Best Practices
1. Always Use Descriptions
// ✅ GOOD: Comprehensive descriptions
[Description("Analyzes numerical data and provides statistical information")]
public class DataAnalyzerPlugin
{
[KernelFunction("calculate_average")]
[Description("Calculates the arithmetic mean of a collection of numbers")]
public double CalculateAverage(
[Description("Array of numbers to calculate average")] double[] numbers)
{ }
}
// ❌ BAD: No descriptions
public class DataAnalyzerPlugin
{
[KernelFunction("calculate_average")]
public double CalculateAverage(double[] numbers)
{ }
}
2. Validate Inputs
// ✅ GOOD: Input validation
[KernelFunction("calculate_average")]
public double CalculateAverage(double[] numbers)
{
if (numbers == null || numbers.Length == 0)
return 0;
return numbers.Average();
}
// ❌ BAD: No validation (throws exception on null)
[KernelFunction("calculate_average")]
public double CalculateAverage(double[] numbers)
{
return numbers.Average();
}
3. Use Meaningful Function Names
// ✅ GOOD: Clear, action-oriented names
[KernelFunction("calculate_average")]
[KernelFunction("find_min_max")]
[KernelFunction("list_files")]
// ❌ BAD: Vague or cryptic names
[KernelFunction("func1")]
[KernelFunction("process")]
[KernelFunction("do_stuff")]
4. Handle Errors Gracefully
// ✅ GOOD: Try-catch with meaningful error messages
[KernelFunction("read_file")]
public string ReadFile(string fileName)
{
try
{
return File.ReadAllText(fileName);
}
catch (FileNotFoundException)
{
return $"File not found: {fileName}";
}
catch (UnauthorizedAccessException)
{
return $"Access denied: {fileName}";
}
catch (Exception ex)
{
return $"Error reading file: {ex.Message}";
}
}
// ❌ BAD: Let exceptions bubble up (breaks AI flow)
[KernelFunction("read_file")]
public string ReadFile(string fileName)
{
return File.ReadAllText(fileName); // Can throw
}
5. Keep Functions Focused
// ✅ GOOD: Single responsibility
[KernelFunction("calculate_average")]
public double CalculateAverage(double[] numbers) { }
[KernelFunction("calculate_median")]
public double CalculateMedian(double[] numbers) { }
// ❌ BAD: Doing too much
[KernelFunction("analyze_data")]
public string AnalyzeData(double[] numbers, string operation)
{
// Branches based on operation - should be separate functions
}
Comparison: Reflection vs Attribute-Based Approaches
AspectReflection (CreateFunctionFromMethod)Attribute ([KernelFunction]) |
| Registration | Manual, explicit | Automatic discovery |
| Code Volume | More boilerplate | Less boilerplate |
| Flexibility | Can wrap any method | Requires attribute marking |
| Discoverability | Manual scanning | Automatic |
| Best For | Wrapping existing code | New plugins from scratch |
| Learning Curve | Steeper | Gentler |
| Type Safety | Compile-time | Compile-time |
| Dependencies | Manual instance creation | Flexible (both approaches) |
Real-World Use Cases
1. Weather Service Plugin
[Description("Provides weather information")]
public class WeatherPlugin
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
public WeatherPlugin(HttpClient httpClient, string apiKey)
{
_httpClient = httpClient;
_apiKey = apiKey;
}
[KernelFunction("get_current_weather")]
[Description("Gets current weather for a city")]
public async Task<string> GetCurrentWeather(
[Description("City name")] string city)
{
var response = await _httpClient.GetAsync(
$"https://api.weather.com/v1/current?city={city}&key={_apiKey}");
return await response.Content.ReadAsStringAsync();
}
}
2. Database Operations Plugin
[Description("Performs database queries")]
public class DatabasePlugin
{
private readonly IDbConnection _connection;
public DatabasePlugin(IDbConnection connection)
{
_connection = connection;
}
[KernelFunction("query_users")]
[Description("Queries users from database")]
public async Task<string> QueryUsers(
[Description("Filter criteria")] string filter)
{
var users = await _connection.QueryAsync<User>(
"SELECT * FROM Users WHERE Name LIKE @Filter",
new { Filter = $"%{filter}%" });
return JsonSerializer.Serialize(users);
}
}
3. Email Service Plugin
[Description("Sends emails")]
public class EmailPlugin
{
private readonly IEmailService _emailService;
public EmailPlugin(IEmailService emailService)
{
_emailService = emailService;
}
[KernelFunction("send_email")]
[Description("Sends an email to a recipient")]
public async Task<string> SendEmail(
[Description("Recipient email address")] string to,
[Description("Email subject")] string subject,
[Description("Email body")] string body)
{
await _emailService.SendAsync(to, subject, body);
return $"Email sent successfully to {to}";
}
}
Common Pitfalls and Solutions
Pitfall 1: Forgetting [KernelFunction] Attribute
// ❌ This method won't be discovered
public string Process(string input) { }
// ✅ Add the attribute
[KernelFunction("process_data")]
public string Process(string input) { }
Pitfall 2: Using ImportPluginFromType with Parameterized Constructor
public class MyPlugin
{
public MyPlugin(string config) { } // Parameterized constructor
}
// ❌ This will fail - no parameterless constructor
kernel.ImportPluginFromType<MyPlugin>("my_plugin");
// ✅ Use ImportPluginFromObject instead
var plugin = new MyPlugin("config_value");
kernel.ImportPluginFromObject(plugin, "my_plugin");
Pitfall 3: Not Handling Null/Empty Inputs
// ❌ Will throw NullReferenceException
[KernelFunction("process")]
public int Count(string[] items)
{
return items.Length;
}
// ✅ Add validation
[KernelFunction("process")]
public int Count(string[] items)
{
if (items == null || items.Length == 0)
return 0;
return items.Length;
}
Summary: Attribute-Based Plugin Creation Workflow
┌─────────────────────────────────────────┐
│ 1. CREATE PLUGIN CLASS │
│ - Add [Description] on class │
│ - Define constructor (with/without │
│ parameters) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 2. MARK METHODS WITH [KernelFunction] │
│ - [KernelFunction("function_name")] │
│ - [Description("What it does")] │
│ - Add parameter descriptions │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 3. REGISTER PLUGIN │
│ Parameterless constructor: │
│ ImportPluginFromType<T>() │
│ │
│ Parameterized constructor: │
│ Create instance + pass deps │
│ ImportPluginFromObject() │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 4. SEMANTIC KERNEL AUTO-DISCOVERS │
│ - Scans for [KernelFunction] │
│ - Reads descriptions │
│ - Extracts parameter info │
│ - Registers all functions │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 5. INVOKE FUNCTIONS │
│ kernel.InvokeAsync( │
│ "plugin_name", │
│ "function_name", │
│ new KernelArguments { ... }) │
└─────────────────────────────────────────┘