在上两篇文章中,我们从Semantic Kernel的整体介绍到安装入门,已经铺好了基础。作为一个专注.NET开发的博主,我特别喜欢看到这些AI工具如何融入我们日常的项目流程中。比如,在传统的企业应用里,我们常常用依赖注入来管理服务,而Semantic Kernel的核心组件Kernel和Plugins,正好能以类似方式扩展你的代码库,让AI成为自然的一部分。
今天,这篇系列的第三篇,我们深入Kernel和Plugins这两个关键概念。我会从Kernel的构建入手,一步步展开Plugins的设计、导入、函数调用、扩展性,最后通过一个天气查询案例来串联一切。整个讨论会结合C#代码示例,让你看到如何在实际项目中应用这些概念。相信通过这篇,你能更自信地将SK集成到自己的工作中去。
Kernel是Semantic Kernel的中心枢纽,它负责协调AI模型、插件和各种资源的执行,就好像一个智能的调度器,能让你的应用从静态转向动态响应。在传统.NET开发中,我们常用IServiceProvider来管理服务的生命周期和依赖,而Kernel的设计灵感与之类似,它通过构建器模式来组装组件,确保一切高效运行。目前Semantic Kernel的最新版本已达到1.16.0,这个版本强化了异步支持和多模型兼容,让Kernel在高并发场景下更稳定。
构建Kernel的过程很简单,却蕴含着灵活性。拿一个典型的C#控制台应用来说,你可以这样起步:
using Microsoft.SemanticKernel;
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: "https://your-endpoint.openai.azure.com/",
apiKey: "your-api-key"
);
var kernel = builder.Build();
这里,Kernel.CreateBuilder()创建了一个构建器实例,然后通过AddAzureOpenAIChatCompletion添加聊天完成服务。这不只是添加一个模型连接器,还隐含了Kernel对资源的统一管理:它会自动处理令牌认证、请求重试和错误传播。相比传统开发中的HttpClient直接调用API,Kernel的抽象层让代码更干净,你不用担心底层网络细节,就能专注于业务逻辑。
在实际项目中,这种构建方式特别实用。比如,在一个ASP.NET Core的Web API中,你可以将Kernel注册到依赖注入容器里:
public void ConfigureServices(IServiceCollection services)
{
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion("gpt-4o", "your-openai-key")
.Build();
services.AddSingleton(kernel);
}
然后,在控制器中注入Kernel使用它。这就让你的API endpoints能智能处理用户输入,而非硬编码逻辑。Kernel的作用远不止调度,它还维护上下文状态,确保多次调用间的信息连贯性,就像传统数据库会话一样,但更注重语义理解。通过这种方式,你的项目能逐步从规则驱动转向AI驱动,减少维护成本。
Kernel的构建还支持插件和服务扩展,这让我们在设计时能预留钩子,便于后期迭代。举例来说,如果你后期想切换模型,从Azure OpenAI到Hugging Face,只需替换添加方法,而不改动核心代码。这种模块化设计,在大型团队协作中价值巨大,能避免重构带来的风险。
Plugins是Semantic Kernel中封装可复用功能的机制,它们让AI能调用外部工具或自定义逻辑,就像传统.NET中的扩展方法或NuGet包一样。Plugins分为本土插件(Native Plugins)和语义插件(Semantic Plugins),前者是纯C#代码实现,后者基于提示模板。设计上,本土插件更适合性能敏感的任务,因为它们直接执行托管代码,而语义插件则依赖LLM生成输出,灵活但有延迟。
导入Plugins的过程直观,先定义一个类,然后用Kernel导入。拿本土插件为例,假设你有一个简单的数学计算类:
using Microsoft.SemanticKernel;
public class MathPlugin
{
[KernelFunction("SquareRoot")]
[Description("计算一个数的平方根")]
public double SquareRoot(double number)
{
return Math.Sqrt(number);
}
}
然后,在Kernel中导入:
var kernel = Kernel.CreateBuilder().Build(); // 假设已构建
kernel.ImportPluginFromObject(new MathPlugin(), "Math");
这里,ImportPluginFromObject扫描类中的KernelFunction属性,将方法转换为插件函数。属性如Description提供元数据,让LLM在规划时理解函数用途。这与传统反射机制类似,但SK优化了它,确保类型安全和参数校验。
对于OpenAPI插件,SK支持从Swagger定义导入,这特别适合集成现有REST服务。比方说,你有一个天气API的OpenAPI规范文件:
kernel.ImportPluginFromOpenApi("Weather", new Uri("https://api.weather.com/swagger.json"));
这会自动生成插件函数,AI就能调用API端点。相比手动写Http请求,Plugins的导入让集成更声明式,你只需关注接口契约。
在设计Plugins时,考虑参数类型很重要。SK支持基本类型和复杂对象,但建议用JSON序列化复杂输入,以防模型误解。实际落地中,这意味着你能将遗留系统的C#方法快速转为AI工具,比如一个库存检查函数,直接导入后,AI就能在对话中调用它,优化用户体验。
语义插件的导入则用提示文件,比如一个YAML或JSON模板:
name: FunFact
description: 生成有趣的事实
template: |
生成关于{{$topic}}的有趣事实。
用kernel.ImportPluginFromPromptDirectory("path/to/plugins");导入目录。这种方式让非开发者也能贡献插件,扩展了团队协作。
Plugins的设计强调可观测性,你可以加日志钩子监控调用,这在调试复杂工作流时特别有用。总体上,Plugins让SK从简单聊天转向工具增强AI,类似于传统MVC中中间件的角色,但更智能。
一旦Plugins导入,函数调用就成为核心玩法。SK通过InvokeAsync方法执行函数,支持参数传递和链式操作。基本调用像这样:
var arguments = new KernelArguments { ["number"] = 16.0 };
var result = await kernel.InvokeAsync("Math", "SquareRoot", arguments);
Console.WriteLine(result); // 输出4.0
这里,KernelArguments是键值对字典,确保类型匹配。原理上,SK用反射调用方法,并处理异步返回,这与Task.Run类似,但加了AI上下文。
链式操作更强大,你可以串联多个函数:
var chain = kernel.CreateFunctionFromPrompty("先计算平方根,再加5。");
var chainResult = await chain.InvokeAsync(kernel, new KernelArguments { ["number"] = 25.0 });
这隐含了规划逻辑,SK会分解提示成步骤调用。错误处理内置:如果参数缺失,抛ArgumentException;网络错误则重试。你可以自定义错误钩子:
kernel.FunctionInvoking += (sender, e) => { /* 日志 */ };
kernel.FunctionInvocationFailed += (sender, e) => { /* 处理 */ };
这让链式操作可靠,在生产环境中价值明显。比如,在一个订单处理系统中,链式调用库存检查、支付验证和邮件通知,AI自动 orchestration,减少手动编码。
参数传递支持复杂类型,通过JSON序列化:
public class Person { public string Name { get; set; } }
[KernelFunction("Greet")]
public string Greet(Person person) => $"Hello, {person.Name}!";
调用时arguments["person"] = JsonSerializer.Serialize(new Person { Name = "Alice" });。这扩展了传统方法调用的边界,让AI处理结构化数据。
链式操作的实际应用在于自动化工作流,比如报告生成:先调用数据插件拉取SQL结果,再用语义插件总结。错误处理确保鲁棒性,如果一个环节失败,回滚或切换备选路径。这种机制在微服务架构中特别契合,能桥接AI与现有系统。
Plugins的真正魅力在于扩展性,你能集成第三方服务,如MongoDB数据库,让AI直接操作数据。这超越了传统ORM的范畴,因为Plugins允许LLM动态生成查询。
拿MongoDB集成为例,先安装NuGet包Microsoft.SemanticKernel.Plugins.Document,然后定义插件:
using Microsoft.SemanticKernel.Plugins.Document;
using MongoDB.Driver;
publicclassMongoPlugin
{
privatereadonly IMongoCollection<BsonDocument> _collection;
public MongoPlugin(string connectionString, string dbName, string collectionName)
{
var client = new MongoClient(connectionString);
_collection = client.GetDatabase(dbName).GetCollection<BsonDocument>(collectionName);
}
[KernelFunction("QueryData")]
public async Task<string> QueryData(string filterJson)
{
var filter = BsonDocument.Parse(filterJson);
var results = await _collection.Find(filter).ToListAsync();
return JsonSerializer.Serialize(results);
}
}
导入后,AI就能用自然语言查询数据库,比如提示“从用户集合中找年龄大于30的”。SK会生成filterJson参数调用函数。
扩展到其他服务,如Redis缓存或Azure Blob存储,原理一致:封装第三方SDK为插件函数,确保线程安全和异步支持。这在云原生应用中落地顺畅,你能构建一个AI代理,实时同步数据源,而非定时任务。
安全性是扩展的关键,SK支持输入过滤器防止注入攻击:
kernel.PromptRendering += (sender, e) => { e.RenderedPrompt = Sanitize(e.RenderedPrompt); };
这让插件扩展可靠,适合企业级部署。实际中,这种集成能改造遗留系统,比如一个老旧的CRM,用插件桥接AI,提升搜索智能。
扩展还包括多插件协作,你可以用规划器(下篇详解)自动选择插件,优化执行路径。这鼓励开发者设计更解耦的架构,插件如微服务,Kernel如网关。
现在,来个完整案例:构建天气查询插件,结合API调用。这在实际App中很常见,比如移动端天气小部件。
先定义插件类,集成OpenWeatherMap API:
using System.Net.Http;
using Microsoft.SemanticKernel;
publicclassWeatherPlugin
{
privatereadonly HttpClient _httpClient = new HttpClient();
[KernelFunction("GetCurrentWeather")]
[Description("获取指定城市的当前天气")]
public async Task<string> GetCurrentWeather(string city)
{
var apiKey = "your-openweathermap-key";
var url = $"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={apiKey}&units=metric";
var response = await _httpClient.GetStringAsync(url);
return response; // 返回JSON
}
}
导入到Kernel:
kernel.ImportPluginFromObject(new WeatherPlugin(), "Weather");
然后,用提示调用:
var prompt = "告诉我北京的当前天气。";
var settings = new OpenAIPromptExecutionSettings { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
var result = await kernel.InvokePromptAsync(prompt, new(settings));
Console.WriteLine(result);
SK会解析提示,调用GetCurrentWeather("北京"),返回天气数据。你可以进一步处理JSON,格式化输出。
这个案例的扩展:在Blazor App中,注入Kernel,用户输入城市,AI查询并显示。这结合了传统UI开发与AI,实际价值在于快速迭代功能,而非从头写API客户端。如果API失败,加错误处理:函数内try-catch,返回友好消息。加另一个插件解析JSON,生成自然语言描述。
通过这个案例,你看到Plugins如何落地:从简单查询到复杂集成,逐步增强应用。天气插件还能扩展到预测或警报,适用于IoT项目。
总结这篇,Kernel和Plugins是SK的基石,让AI与传统开发无缝融合。下一篇我们聊提示工程,敬请期待!