Semantic Kernel 内核详解

Semantic Kernel 内核详解

企业级 AI 应用真正的挑战在于 认知与行动:如何让 AI 理解复杂的业务上下文,如何让它自主调用外部工具,如何编排多步骤的推理任务?

Semantic Kernel (简称 SK)正是微软为 .NET 生态打造的大语言模型(LLM)编排框架。它不仅仅是 OpenAI 的 .NET SDK 封装,更是一个 内核------一个将 LLM 的推理能力与现有 C# 业务逻辑深度融合的桥梁。掌握 SK 的核心抽象,将其作为构建企业智能体的基石。

将深入 Semantic Kernel 的内核,从生命周期管理、提示词工程、函数调用到插件化架构,带你构建一个可扩展、可测试、可观测的 AI 编排层。

1 Kernel 对象生命周期与依赖注入集成

1.1 Kernel 是什么?

Kernel 是 Semantic Kernel 的核心容器,它聚合了三个关键组件:

  • AI 服务(如聊天补全、文本嵌入)
  • 插件(封装为业务能力的函数集合)
  • 记忆(向量存储,用于 RAG)

每个 Kernel 实例代表一个独立的 AI 运行时环境。在 ASP.NET Core 应用中,我们需要谨慎管理 Kernel 的生命周期,确保线程安全、资源复用以及与依赖注入容器的集成。

1.2 构建 Kernel:从 Builder 模式开始

SK 推荐使用 KernelBuilder 来构建实例。一个典型的构建流程包括:

  1. 添加 AI 服务(OpenAI、Azure OpenAI、本地模型等)
  2. 注册插件(从类型、对象或目录加载)
  3. 配置日志和 HttpClient 工厂
csharp 复制代码
using Microsoft.SemanticKernel;

var builder = Kernel.CreateBuilder();

// 添加 Azure OpenAI 服务
builder.AddAzureOpenAIChatCompletion(
    deploymentName: "...",
    endpoint: "https://openai.com/",
    apiKey: "your-api-key");

// 添加 OpenAI 服务(可选,支持多模型)
builder.AddOpenAIChatCompletion(
    modelId: "...",
     endpoint: "...",
    apiKey: "your-openai-key");

// 添加嵌入生成服务(用于语义内存)
builder.AddAzureOpenAITextEmbeddingGeneration(
    deploymentName: "text-embedding-ada-002",
    endpoint: "...",
    apiKey: "...");

// 添加插件
builder.Plugins.AddFromType<TimePlugin>(); // 从类型加载插件
builder.Plugins.AddFromObject(new EmailPlugin()); // 从实例加载

// 配置日志
builder.Services.AddLogging(configure => configure.AddConsole());

// 使用 IHttpClientFactory(推荐)
builder.Services.AddHttpClient();

var kernel = builder.Build();
1.3 在 ASP.NET Core 中集成 Kernel

Kernel 对象是 线程安全 的,可以被多个请求共享。但考虑到不同的请求可能需要不同的 AI 配置(如不同的部署名称、插件集),更常见的做法是将 Kernel 注册为 Scoped 服务,并在每个请求中根据用户上下文动态构建。

方案一:注册为单例(简单场景)

如果所有请求共享相同的模型和插件,可以将 Kernel 注册为单例,减少构建开销。

csharp 复制代码
// Program.cs
builder.Services.AddSingleton(sp =>
{
    var kernelBuilder = Kernel.CreateBuilder();
    kernelBuilder.AddAzureOpenAIChatCompletion(
        deploymentName: config["AI:LLM:DeploymentName"],
        endpoint: config["AI:LLM:Endpoint"],
        apiKey: config["AI:LLM:ApiKey"]);
    kernelBuilder.Plugins.AddFromType<TimePlugin>();
    kernelBuilder.Services.AddLogging();
    return kernelBuilder.Build();
});

方案二:注册为 Scoped(动态配置)

当不同租户需要不同的模型或插件时,可以使用工厂模式,在请求范围内动态构建 Kernel。

csharp 复制代码
// 注册一个 KernelFactory
builder.Services.AddScoped<IKernelFactory, KernelFactory>();

// 在服务中注入工厂,按需创建
public class ChatService
{
    private readonly IKernelFactory _kernelFactory;
    public ChatService(IKernelFactory kernelFactory) => _kernelFactory = kernelFactory;

    public async Task<string> ChatAsync(string userInput, string tenantId)
    {
        var kernel = _kernelFactory.CreateForTenant(tenantId);
        return await kernel.InvokePromptAsync(userInput);
    }
}
1.4 资源清理与生命周期管理

Kernel 本身不持有非托管资源,但它内部使用的 HttpClient 等对象需要妥善管理。通过 IHttpClientFactory 注册的 HttpClient 会自动复用和清理,无需担心。但如果 Kernel 内部创建了需要释放的对象(如某些插件中的数据库连接),应实现 IDisposable 并通过 kernelBuilder.Services.AddSingleton<T>(...) 注册,由 DI 容器管理生命周期。

2 提示词模板引擎与函数调用(Function Calling)

2.1 提示词模板语法

Semantic Kernel 提供了一套强大的提示词模板引擎,支持变量、函数调用、循环等特性。模板语法以 {``{$variable}} 引用上下文变量,以 {``{namespace.functionName}} 调用插件函数。

基础示例

csharp 复制代码
var prompt = """
    You are an AI assistant. Answer the user's question using the provided context.

    Context: {{$context}}
    Question: {{$userInput}}
    Answer:
    """;

var result = await kernel.InvokePromptAsync(prompt, new()
{
    ["context"] = "The user is asking about Semantic Kernel.",
    ["userInput"] = "What is SK?"
});

调用插件函数:在模板中直接调用插件函数,可以实现动态数据注入。

csharp 复制代码
// 定义插件
public class WeatherPlugin
{
    [KernelFunction]
    [Description("Get the current weather for a city")]
    public string GetWeather(string city)
    {
        return $"The weather in {city} is sunny, 25°C.";
    }
}

// 注册插件
kernel.Plugins.AddFromType<WeatherPlugin>();

// 提示词中调用插件
var prompt = "What's the weather in {{$city}}? {{WeatherPlugin.GetWeather $city}}";
var result = await kernel.InvokePromptAsync(prompt, new() { ["city"] = "London" });
2.2 函数调用(Function Calling)原理

大语言模型原生支持 函数调用 (Function Calling)能力。模型可以根据用户输入,自主决定调用哪个函数,并生成符合函数签名的 JSON 参数。Semantic Kernel 将这个能力封装为 KernelFunction,并自动处理函数调用的往返过程。

手动定义函数

csharp 复制代码
// 使用 KernelFunction 特性标注方法
public class EmailPlugin
{
    [KernelFunction]
    [Description("Send an email to the specified recipient")]
    public async Task<string> SendEmailAsync(
        [Description("Recipient email address")] string to,
        [Description("Email subject")] string subject,
        [Description("Email body")] string body)
    {
        // 实际发送逻辑
        await Task.Delay(100); // 模拟异步操作
        return $"Email sent to {to}";
    }
}

让模型自动调用 :当调用 InvokePromptAsync 时,如果提示词中包含了需要函数调用的逻辑,SK 会自动与 LLM 协商,执行函数并将结果返回给模型。

csharp 复制代码
kernel.Plugins.AddFromType<EmailPlugin>();

var prompt = "Send an email to john@example.com with subject 'Hello' and body 'How are you?'";
var result = await kernel.InvokePromptAsync(prompt);
// 模型会决定调用 SendEmailAsync 函数,最终返回 "Email sent to john@example.com"

强制函数调用 :某些场景下,我们希望模型必须调用特定函数。可以通过 KernelArgumentsExecutionSettings 指定函数名称。

csharp 复制代码
var arguments = new KernelArguments
{
    ["function_call"] = "EmailPlugin-SendEmailAsync"
};
var result = await kernel.InvokePromptAsync(prompt, arguments);
2.3 高级模板技巧
  • 循环与条件:模板引擎支持简单的循环和条件语句(通过 Handlebars 风格的语法,需启用 Handlebars 插件)。
  • 自动函数参数注入:SK 会自动将函数参数与上下文变量匹配,无需手动传递。
  • 流式输出 :使用 InvokePromptStreamingAsync 可以实时获取 LLM 的流式响应,适合聊天场景。
csharp 复制代码
await foreach (var update in kernel.InvokePromptStreamingAsync(prompt))
{
    Console.Write(update);
}

3 插件化架构:将现有 C# 业务逻辑封装为 AI 能力

插件(Plugin)是 Semantic Kernel 的核心扩展点。通过插件,我们可以将任何现有的 C# 方法、API、数据库操作等暴露给 AI,让 LLM 能够 调用 这些能力来完成复杂任务。

3.1 插件的三种定义方式

1. 从类加载(推荐)

使用 [KernelFunction] 特性标记方法,SK 会自动发现并注册。

csharp 复制代码
public class OrderPlugin
{
    private readonly IOrderRepository _orderRepository;

    public OrderPlugin(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    [KernelFunction]
    [Description("Get recent orders for a user")]
    public async Task<List<Order>> GetRecentOrdersAsync(
        [Description("User ID")] string userId,
        [Description("Number of days to look back")] int days = 30)
    {
        return await _orderRepository.GetRecentOrdersAsync(userId, days);
    }
}

注册时,需要将依赖的服务也注册到 DI 容器中。

2. 从对象加载

对于已有实例,可以直接添加到插件集合。

csharp 复制代码
var orderPlugin = new OrderPlugin(orderRepository);
kernel.Plugins.AddFromObject(orderPlugin);

3. 从目录加载(OpenAPI/Swagger)

SK 支持从 OpenAPI 文档自动生成插件,可以快速集成第三方 REST API。

csharp 复制代码
// 1. 注册 OpenAPI 插件(自动生成设备查询函数)
builder.Plugins.AddFromOpenApiAsync(
    pluginName: "DeviceDataPlatform",
    openApiDocumentPath: "https://factory-api/device-platform/swagger.json",
    executionParameters: new OpenApiFunctionExecutionParameters
    {
        // 处理 API Key 认证
        AuthCallback = async (request, cancellationToken) =>
        {
            request.Headers.Add("X-API-Key", GetApiKeyFromSecretStore());
            await Task.CompletedTask;
        },
        // 只导入我们关心的操作,避免 AI 产生幻觉
        OperationsToExclude = new List<string> { "POST_/write-data", "DELETE_/history" }
    }
);
3.2 插件的描述与参数说明

为了让 LLM 正确选择并调用插件,我们必须提供清晰、详细的描述:

  • 插件类 :可以添加 [Description] 特性,说明插件的整体用途。
  • 方法[KernelFunction]Description 参数描述该方法的功能。
  • 参数 :使用 [Description] 描述每个参数的含义和格式。

良好的描述能够显著提升函数调用的准确性。例如:

csharp 复制代码
[KernelFunction, Description("Calculates the total price including tax and shipping for a given product")]
public decimal CalculateTotalPrice(
    [Description("Product identifier (SKU)")] string sku,
    [Description("Quantity, must be positive integer")] int quantity,
    [Description("Promo code, optional")] string promoCode = null)
{
    // ...
}
3.3 依赖注入与插件实例化

ASP.NET Core 中,插件通常会依赖其他服务(如数据库、HttpClient)。我们可以利用 DI 容器来创建插件实例,确保依赖关系被正确解析。

方案一:手动解析(适用于简单场景)

csharp 复制代码
var orderPlugin = sp.GetRequiredService<OrderPlugin>();
kernel.Plugins.AddFromObject(orderPlugin);

方案二:使用插件工厂(更优雅)

SK 提供了 IPlugin 接口,我们可以自定义一个 PluginLoader 从容器中获取插件实例。

csharp 复制代码
public class KernelFactory
{
    private readonly IServiceProvider _services;
    public KernelFactory(IServiceProvider services) => _services = services;

    public Kernel CreateKernel()
    {
        var builder = Kernel.CreateBuilder();
        // 添加 AI 服务...

        // 从 DI 容器中获取插件并添加
        var orderPlugin = _services.GetRequiredService<OrderPlugin>();
        builder.Plugins.AddFromObject(orderPlugin);

        var emailPlugin = _services.GetRequiredService<EmailPlugin>();
        builder.Plugins.AddFromObject(emailPlugin);

        return builder.Build();
    }
}
3.4 插件的最佳实践
  1. 单一职责:每个插件应该聚焦于一个明确的业务领域(如订单、用户、库存)。
  2. 异步友好 :插件方法应返回 TaskValueTask,避免阻塞。
  3. 幂等性:如果可能,让插件方法具备幂等性,因为 LLM 可能会重试调用。
  4. 安全校验:插件方法内部必须进行权限校验,因为 LLM 可能被诱导执行危险操作(如删除数据)。
  5. 可观测性:在插件方法中添加日志和遥测,记录调用来源、参数和执行耗时。
3.5 插件与 RAG 的结合

插件不仅用于函数调用,还可以用于 检索增强生成(RAG)。通过插件,我们可以将向量数据库查询、搜索引擎结果等包装为函数,让 LLM 在回答问题时自主决定是否检索外部知识。

csharp 复制代码
public class KnowledgeBasePlugin
{
    private readonly IVectorDatabase _vectorDb;

    public KnowledgeBasePlugin(IVectorDatabase vectorDb) => _vectorDb = vectorDb;

    [KernelFunction]
    [Description("Search the internal knowledge base for relevant information")]
    public async Task<string> SearchKnowledgeBaseAsync(
        [Description("Search query")] string query)
    {
        var embedding = await GenerateEmbeddingAsync(query);
        var results = await _vectorDb.SimilaritySearchAsync(embedding, topK: 3);
        return string.Join("\n", results.Select(r => r.Content));
    }
}

然后在提示词中,让模型自主决定何时调用 SearchKnowledgeBaseAsync。这样,模型既能利用自身知识,又能按需获取最新信息。

总结

深入 Semantic Kernel 的内核,掌握了:

  • Kernel 的生命周期管理:如何在 ASP.NET Core 中集成和复用 Kernel 实例。
  • 提示词模板引擎:使用变量、函数调用和流式输出构建动态提示词。
  • 函数调用机制:让 LLM 自主选择并调用 C# 方法,实现 AI 与业务逻辑的无缝对接。
  • 插件化架构:将现有代码封装为插件,利用依赖注入和描述特性,构建可扩展的 AI 能力层。
相关推荐
B站_计算机毕业设计之家1 小时前
计算机毕业设计:Python城市地铁网络可视化分析系统 Flask框架 数据分析 可视化 高德地图 数据挖掘 机器学习 爬虫(建议收藏)✅
网络·python·信息可视化·数据挖掘·flask·课程设计·美食
源码之家1 小时前
计算机毕业设计:Python地铁数据可视化分析系统 Flask框架 数据分析 可视化 高德地图 数据挖掘 机器学习 爬虫(建议收藏)✅
大数据·python·信息可视化·数据挖掘·flask·汽车·课程设计
zhishidi2 小时前
使用python给pdf文档自动添加目录书签
java·python·pdf
青柠代码录3 小时前
【SpringCloud】Nacos 组件:服务注册与发现
后端
chushiyunen11 小时前
python中的@Property和@Setter
java·开发语言·python
禾小西11 小时前
Java中使用正则表达式核心解析
java·python·正则表达式
2401_8955213412 小时前
SpringBoot Maven快速上手
spring boot·后端·maven
yoyo_zzm12 小时前
JAVA (Springboot) i18n国际化语言配置
java·spring boot·python