The library allows you to use any CLR type or object as a plugin without requiring KernelFunction
attributes. This enables you to create plugins from existing objects or types, making it easier to integrate with existing codebases.
Unlike traditional Semantic Kernel plugins, CLR types don’t need:
[KernelFunction]
attributes on methods[Description]
attributes for documentationpublic class ShortDate
{
public string ToShortDateString()
{
return DateTime.Now.ToShortDateString();
}
public string ToLongDateString()
{
return DateTime.Now.ToLongDateString();
}
public string AddDays(int days)
{
return DateTime.Now.AddDays(days).ToShortDateString();
}
}
public class DateMetadataProvider : IPluginMetadataProvider
{
public PluginMetadata? GetPluginMetadata(KernelPlugin plugin) =>
plugin.Name == "ShortDatePlugin" ? new PluginMetadata
{
Description = "This plugin returns date and time information."
} : null;
public FunctionMetadata? GetFunctionMetadata(KernelPlugin plugin, KernelFunctionMetadata metadata)
{
if (plugin.Name != "ShortDatePlugin") return null;
return metadata.Name switch
{
"ToShortDateString" => new FunctionMetadata(metadata.Name)
{
Description = "Returns the current date in short format (MM/dd/yyyy)."
},
"ToLongDateString" => new FunctionMetadata(metadata.Name)
{
Description = "Returns the current date in long format."
},
"AddDays" => new FunctionMetadata(metadata.Name)
{
Description = "Adds the specified number of days to the current date.",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("days")
{
Description = "Number of days to add (can be negative)"
}
}
},
_ => null
};
}
}
kernelBuilder.Plugins.AddFromClrTypeWithMetadata<ShortDate>("ShortDatePlugin");
var dateInstance = new ShortDate();
kernelBuilder.Plugins.AddFromClrObjectWithMetadata(dateInstance, "ShortDatePlugin");
public class MathUtilities
{
public double Add(double a, double b) => a + b;
public double Subtract(double a, double b) => a - b;
public double Multiply(double a, double b) => a * b;
public double Divide(double a, double b) => b != 0 ? a / b : throw new DivideByZeroException();
public double Power(double baseNum, double exponent) => Math.Pow(baseNum, exponent);
public double SquareRoot(double number) => Math.Sqrt(number);
}
public class MathMetadataProvider : IPluginMetadataProvider
{
public PluginMetadata? GetPluginMetadata(KernelPlugin plugin) =>
plugin.Name == "MathPlugin" ? new PluginMetadata
{
Description = "Provides basic mathematical operations"
} : null;
public FunctionMetadata? GetFunctionMetadata(KernelPlugin plugin, KernelFunctionMetadata metadata)
{
if (plugin.Name != "MathPlugin") return null;
return metadata.Name switch
{
"Add" => new FunctionMetadata(metadata.Name)
{
Description = "Adds two numbers together",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("a") { Description = "First number" },
new ParameterMetadata("b") { Description = "Second number" }
}
},
"Divide" => new FunctionMetadata(metadata.Name)
{
Description = "Divides the first number by the second number",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("a") { Description = "Dividend" },
new ParameterMetadata("b") { Description = "Divisor (cannot be zero)" }
}
},
// ... other operations
_ => null
};
}
}
// Registration
kernelBuilder.Plugins.AddFromClrTypeWithMetadata<MathUtilities>("MathPlugin");
public class FileSystemHelper
{
public bool FileExists(string path) => File.Exists(path);
public string ReadTextFile(string path) => File.ReadAllText(path);
public void WriteTextFile(string path, string content) => File.WriteAllText(path, content);
public string[] ListFiles(string directory) => Directory.GetFiles(directory);
public long GetFileSize(string path) => new FileInfo(path).Length;
}
public class FileSystemMetadataProvider : IPluginMetadataProvider
{
private readonly IConfiguration _configuration;
public FileSystemMetadataProvider(IConfiguration configuration)
{
_configuration = configuration;
}
public PluginMetadata? GetPluginMetadata(KernelPlugin plugin) =>
plugin.Name == "FileSystemPlugin" ? new PluginMetadata
{
Description = "Provides safe file system operations"
} : null;
public FunctionMetadata? GetFunctionMetadata(KernelPlugin plugin, KernelFunctionMetadata metadata)
{
if (plugin.Name != "FileSystemPlugin") return null;
var allowedPath = _configuration.GetValue<string>("AllowedBasePath", @"C:\SafeDirectory");
return metadata.Name switch
{
"ReadTextFile" => new FunctionMetadata(metadata.Name)
{
Description = "Reads the contents of a text file",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("path")
{
Description = $"File path (must be within {allowedPath})"
}
}
},
"WriteTextFile" => _configuration.GetValue<bool>("AllowWrite", false)
? new FunctionMetadata(metadata.Name)
{
Description = "Writes content to a text file",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("path") { Description = "File path" },
new ParameterMetadata("content") { Description = "Content to write" }
}
}
: new FunctionMetadata(metadata.Name) { Suppress = true }, // Hide in read-only mode
_ => null
};
}
}
public class DatabaseHelper
{
private readonly string _connectionString;
public DatabaseHelper(string connectionString)
{
_connectionString = connectionString;
}
public async Task<List<Dictionary<string, object>>> ExecuteQuery(string sql)
{
// Implementation for safe query execution
// Note: In real scenarios, use parameterized queries and validation
throw new NotImplementedException("Implement safe query execution");
}
public async Task<int> GetRecordCount(string tableName)
{
// Implementation for getting record counts
throw new NotImplementedException("Implement record count logic");
}
}
public class DatabaseMetadataProvider : IPluginMetadataProvider
{
public PluginMetadata? GetPluginMetadata(KernelPlugin plugin) =>
plugin.Name == "DatabasePlugin" ? new PluginMetadata
{
Description = "Provides safe database query operations"
} : null;
public FunctionMetadata? GetFunctionMetadata(KernelPlugin plugin, KernelFunctionMetadata metadata)
{
if (plugin.Name != "DatabasePlugin") return null;
return metadata.Name switch
{
"ExecuteQuery" => new FunctionMetadata(metadata.Name)
{
Description = "Executes a SELECT query against the database",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("sql")
{
Description = "SQL SELECT statement (READ-ONLY queries only)"
}
}
},
"GetRecordCount" => new FunctionMetadata(metadata.Name)
{
Description = "Gets the number of records in a table",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("tableName")
{
Description = "Name of the table to count records"
}
}
},
_ => null
};
}
}
// Registration with dependency injection
services.AddSingleton(provider =>
new DatabaseHelper(provider.GetService<IConfiguration>().GetConnectionString("Default")));
kernelBuilder.Plugins.AddFromClrObjectWithMetadata(
serviceProvider.GetService<DatabaseHelper>(),
"DatabasePlugin"
);
Only public methods are automatically discovered and converted to kernel functions:
public class ExampleClass
{
public string PublicMethod() => "Available"; // ✅ Will be available
private string PrivateMethod() => "Hidden"; // ❌ Will not be available
internal string InternalMethod() => "Hidden"; // ❌ Will not be available
protected string ProtectedMethod() => "Hidden"; // ❌ Will not be available
}
The following parameter types are automatically supported:
string
, int
, double
, bool
, DateTime
, etc.int?
, DateTime?
, etc.List<T>
, T[]
, IEnumerable<T>
Supported return types include:
Task<T>
, ValueTask<T>
void
, Task
(for side-effect operations)Design methods with clear, single responsibilities:
// Good: Simple, focused methods
public class WeatherHelper
{
public double CelsiusToFahrenheit(double celsius) => (celsius * 9/5) + 32;
public double FahrenheitToCelsius(double fahrenheit) => (fahrenheit - 32) * 5/9;
}
// Avoid: Complex methods with multiple responsibilities
public class ComplicatedHelper
{
public string DoEverything(string input, bool flag, int mode, double[] data)
{
// Too complex for AI to understand easily
}
}
Choose method and parameter names that clearly indicate their purpose:
public class EmailHelper
{
// Good: Clear method and parameter names
public bool SendEmail(string recipientEmail, string subject, string body) { /* ... */ }
// Avoid: Unclear names
public bool Process(string data1, string data2, string data3) { /* ... */ }
}
Always provide detailed descriptions through metadata providers:
public FunctionMetadata? GetFunctionMetadata(KernelPlugin plugin, KernelFunctionMetadata metadata)
{
return metadata.Name switch
{
"SendEmail" => new FunctionMetadata(metadata.Name)
{
Description = "Sends an email to the specified recipient with the given subject and body",
Parameters = new List<ParameterMetadata>
{
new ParameterMetadata("recipientEmail")
{
Description = "Valid email address of the recipient"
},
new ParameterMetadata("subject")
{
Description = "Email subject line (max 200 characters)"
},
new ParameterMetadata("body")
{
Description = "Email content (supports plain text and HTML)"
}
}
},
_ => null
};
}
Design methods to handle errors appropriately:
public class SafeFileHelper
{
public string ReadFileContent(string path)
{
try
{
if (!File.Exists(path))
return "File not found";
return File.ReadAllText(path);
}
catch (UnauthorizedAccessException)
{
return "Access denied";
}
catch (Exception ex)
{
return $"Error reading file: {ex.Message}";
}
}
}
Always validate inputs and limit capabilities:
public class SecureFileHelper
{
private readonly string _allowedBasePath;
public SecureFileHelper(string allowedBasePath)
{
_allowedBasePath = Path.GetFullPath(allowedBasePath);
}
public string ReadFile(string relativePath)
{
var fullPath = Path.Combine(_allowedBasePath, relativePath);
var normalizedPath = Path.GetFullPath(fullPath);
// Ensure the path is within the allowed directory
if (!normalizedPath.StartsWith(_allowedBasePath))
throw new UnauthorizedAccessException("Path not allowed");
return File.ReadAllText(normalizedPath);
}
}