.NET AI 插件热加载机制:从 DLL 扫描到 Kernel 注册
系列: .NET C# Semantic Kernel 集成深度解析 ③/6
难度: ⭐⭐⭐(进阶源码,需.NET基础)
源码仓库: github.com/zongge0812/zonggeAgent
适用环境: .NET 9.0 + WPF
一、引言
在 AI 桌面应用中,插件的意义远超传统软件——SK 的插件系统直接赋予 AI “动手能力”。AI 不是光说不练,而是可以通过插件读取文件、分析代码、生成文档。
zonggeAgent 的插件系统实现了 3 个关键能力:
┌──────────────────────────────────────────────────────────────┐
│ ① 热加载:新插件编译后直接放入目录,应用无需重启即可生效 │
│ ② 自动发现:DLL 扫描 + 反射 + ITools 接口自动匹配 │
│ ③ 权限过滤:聊天模式只读,开发模式全读写(安全隔离) │
└──────────────────────────────────────────────────────────────┘
二、整体架构
插件系统的核心是 PluginLoader 静态类,位于 Tools/Implementations/ 目录下:
PluginLoader(静态类)
│
├── 缓存:_pluginsCache(List<PluginsCache>)
│
├── RegisterPlugin(Kernel) → 入口:将插件注册到SK Kernel
│
├── LoadPluginsFromDirectories() → 核心:多目录扫描 + 反射加载
│ ├── ScanDirectoryForPlugins(baseDir) → 主目录
│ └── ScanDirectoryForPlugins("plugins/") → 热更新目录
│
├── ClearPluginCache() → 清空缓存,支持热替换时重新加载
│
└── RegisterPluginDependencies() → 注册插件依赖到SK的IServiceCollection
调用位置
在 BaseAIProviderService.SendMessageAsync() 和 SendMessageStreamAsync() 中调用:
// 每次对话都会执行
var chatService = await CreateChatServiceAsync(config);
var kernel = CreateKernel(chatService);
// ⚡ 热加载注册插件工具
PluginLoader.RegisterPlugin(kernel);
// 💬 聊天模式过滤
kernel = ApplyChatModeFilter(kernel, chatService);
这意味着:每次对话都会重新检查插件,热更新后的 DLL 在下一次对话立即生效。
三、DLL 扫描策略
3.1 双目录扫描
private static void LoadPluginsFromDirectories(
Kernel kernel,
string pluginDir = "plugins",
string searchPattern = "*.dll")
{
var scannedAssemblies = new List<Assembly>();
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
// 1️⃣ 扫描程序主目录(bin/Debug/net9.0-windows/)
// 编译后的插件DLL所在位置
ScanDirectoryForPlugins(baseDir, searchPattern, scannedAssemblies);
// 2️⃣ 扫描 plugins/ 子目录(热加载扩展点)
var pluginDirectory = Path.Combine(baseDir, pluginDir);
if (Directory.Exists(pluginDirectory))
{
ScanDirectoryForPlugins(pluginDirectory, searchPattern, scannedAssemblies);
}
else
{
Directory.CreateDirectory(pluginDirectory); // 创建空目录
}
}
| 扫描目录 | 用途 | 更新方式 |
|---|---|---|
主目录 bin/Debug/net9.0-windows/ |
项目编译时自动输出的插件DLL | 重新编译项目 |
| plugins/ | 外部开发者/社区贡献的热加载DLL | 直接放入DLL文件,无需编译 |
3.2 程序集过滤
不是所有 DLL 都是插件——ScanDirectoryForPlugins 内部实现了严格的过滤逻辑:
private static void ScanDirectoryForPlugins(
string directory,
string searchPattern,
List<Assembly> scannedAssemblies)
{
var dllFiles = Directory.GetFiles(directory, searchPattern, SearchOption.TopDirectoryOnly);
foreach (var dllPath in dllFiles)
{
var assemblyName = AssemblyName.GetAssemblyName(dllPath);
var name = assemblyName.Name ?? "";
// 🚫 跳过系统/框架程序集
if (name.StartsWith("Microsoft.") || name.StartsWith(
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/Myasdfg0812/article/details/162954905



