一份写给 C# / .NET 开发者的 Semantic Kernel 实战笔记。

很多人学大模型应用,卡在第一步:会用 Python 调个 openai 包,却不知道怎么把 AI 接进自己现有的 .NET 业务系统。

Semantic Kernel(简称 SK)是微软官方的 AI 编排框架,最大特点是和 .NET 生态天然契合——用熟悉的 C#、依赖注入、NuGet 就能让大模型「调用你的业务方法」。

这篇不是官方文档翻译,是我按「边学边练」路线从零跑通后的笔记。每一阶段都配一个可直接复制运行的实操,跟着敲完,你就拥有了:一个能对话的控制台程序、一个能自动查订单的助手。

环境一句话:.NET SDK 8.0 + Microsoft.SemanticKernel(本文验证版本 1.81.0)+ 任意兼容 OpenAI 协议的 API Key。密钥绝对不要硬编码,统一走环境变量。


01 环境准备

第一步:装 .NET SDK 8.0。官网下载安装后,终端执行 dotnet --version 能看到 8.0.xxx 就就绪了。

第二步:建控制台项目。

dotnet new console -n SkLearn -o SkLearn
cd SkLearn

第三步:装 SK 核心包。

dotnet add package Microsoft.SemanticKernel

第四步:配大模型 Key(三选一)。 都兼容 OpenAI 协议:

服务商 模型示例 SK 接入方式
OpenAI gpt-4o-mini AddOpenAIChatCompletion(modelId, apiKey)
Azure OpenAI gpt-4o AddAzureOpenAIChatCompletion(...)
DeepSeek 等兼容端点 deepseek-chat AddOpenAIChatCompletion(..., endpoint)

02 阶段 0:必备基础

有基础的可以跳过这一节。小白建议读一遍,后面所有能力都是这四个概念的组合。

一、大模型怎么工作。 本质是一个「根据上文预测下一个词」的程序。你给的叫 Prompt,模型切成 Token 逐词预测,受上下文窗口限制——窗口越大能处理越多,也越贵。

二、Prompt 工程。 三条原则:设定角色、明确格式、给示例(Few-shot)。SK 里的 Prompt Function、Instructions 都是把这三条工程化。

三、Function Calling(函数调用)。 模型只会「生成文字」,不会查库、不会调 API。它输出「我想调这个函数、参数是这些」,由你的程序真正执行再喂回结果。这是 SK 一切能力的底座:Plugins、Agents 最终都靠它让 AI「动手」。

四、依赖注入(DI)直觉。 「需要什么,容器给什么」。SK 的 Kernel 本质就是一个 DI 容器——你把模型、插件、服务注册进去,需要时用 GetRequiredService 取出来。

C# 语法速记:本教程只用到最少语法——var 自动推断、$"..." 字符串插值、=> 表达式体、async/await、?? 空值合并。

热身实操 · 第一个 C# 程序

public interface IGreeter
{
    Task<string> GreetAsync(string name);
}

public class ChineseGreeter : IGreeter
{
    public Task<string> GreetAsync(string name)
        => Task.FromResult($"你好,{name}!欢迎学习 Semantic Kernel。");
}

var greeter = new ChineseGreeter();
var message = await greeter.GreetAsync("小明");
Console.WriteLine(message);

运行结果:

你好,小明!欢迎学习 Semantic Kernel。

⚠️ 小白踩坑提醒:① 调用入口代码要放在类定义的后面,或者把接口和实现类拆成独立的 .cs 文件,否则编译器找不到类型;② await 必须加,不然返回的是 Task 对象而不是字符串,运行会报错。


03 阶段 1:认识 Kernel 与首次对话

Kernel 是什么。 SK 的中枢,三件事一把抓:DI 容器(管理模型、插件、内存等服务)、编排中心(把请求路由给合适的模型和函数)、执行上下文(调用函数时把参数和状态一路传递下去)。

                        Kernel 是 SK 的中枢:三件事一把抓
                             
         大模型服务 ─┐                  ┌─ DI 容器
         Plugins 插件 ─┼── Kernel ───┼─ 编排中心
         内存/向量存储 ─┘                  └─ 执行上下文

      你的请求 → Kernel 路由给模型和函数 → 函数执行 → 结果返回

推荐 Kernel.CreateBuilder() 链式构建,清晰且与 .NET 原生 DI 兼容。AddOpenAIChatCompletion 等扩展方法来自 Microsoft.SemanticKernel.Connectors.OpenAI 命名空间——所有用到它的程序都要 using 这一行,否则编译报 CS0246。

实操 1 · Hello Kernel

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var apiKey = Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY")
    ?? throw new Exception("请先设置 DEEPSEEK_API_KEY 环境变量");

// 构建 Kernel:SK 的"大脑中枢"
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
    modelId: "deepseek-chat",
    apiKey: apiKey,
    endpoint: new Uri("https://api.deepseek.com/v1"));
var kernel = builder.Build();

// 发起一次提示词调用
var reply = await kernel.InvokePromptAsync(
    "用一句话介绍你自己,并说明你能帮人类做什么。");
Console.WriteLine(reply);

运行结果:

我是一个由深度求索开发的AI助手,
可以帮你解答问题、撰写文案、分析数据、
编写代码等。有什么我能帮你的吗?

多轮对话。 对话由 IChatCompletionService 提供,ChatHistory 负责保留上下文:

实操 2 · 控制台多轮对话

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var apiKey = Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY")
    ?? throw new Exception("请先设置 DEEPSEEK_API_KEY 环境变量");

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("deepseek-chat", apiKey,
    new Uri("https://api.deepseek.com/v1"));
var kernel = builder.Build();

var chat = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory("你是一个简洁、友好的中文助手。");

while (true)
{
    Console.Write("你:");
    var input = Console.ReadLine();
    if (string.IsNullOrWhiteSpace(input) || input == "exit") break;
    history.AddUserMessage(input);
    var reply = await chat.GetChatMessageContentAsync(
        history, kernel: kernel);
    Console.WriteLine($"助手:{reply}");
    history.AddMessage(reply.Role, reply.Content); // 记住上下文
}

运行结果:

你:你好,我叫小明
助手:你好小明!很高兴认识你,有什么我能帮到你的吗?
你:我叫什么名字?
助手:你叫小明呀~刚才你告诉我的。

跑通标志:先介绍自己,再问「我刚才说了什么」,AI 能答对。

ExecutionSettings 速查:Temperature(0 事实类 / 0.7 创意类)、MaxTokens(限制长度)、StopSequences(遇到即停)、FunctionChoiceBehavior(是否允许函数调用,Auto/None/Required)。

常见报错:401=密钥错或未设;404/Model Not Found=模型名拼错或无权限;Timeout=网络/代理问题;缺 ModelId=没注册聊天模型。


04 阶段 2:Plugins 与函数调用

这是整个教程的精髓。 学会把 C# 方法暴露给 AI 自动调用,你就让模型从「只会聊天」变成「能驱动真实业务」。

           Function Calling:AI 说要调什么,你的代码来执行

用户提问 ──→ 大模型 ──→ 函数调用请求
                               │
                               ↓
用户得到回答 ←── 大模型 ←── 你的 C# 代码
                  拿到结果      真正执行查询

    模型不会真的执行函数,它只生成「我想调这个函数」的请求,
    真正干活的是你的 C# 代码

为什么需要 Plugin。 模型访问不了你的数据库/文件/API。Plugin 就是把现有 C# 能力包装成「AI 可调用的一组函数」——一个类,方法上加 [KernelFunction] 即可。

两个决定成败的特性:

  • [KernelFunction] 标记「可被 AI 调用的函数」;
  • [Description] 描述「这函数是干嘛的、参数啥格式」。AI 全靠这些描述决定何时调、传什么参,描述质量直接决定调用准确率。

Native Function vs Prompt Function。 前者是普通 C# 方法(查订单、算运费,快而确定);后者是提示词模板(写摘要、翻译,灵活但有成本)。一个 Plugin 里两种都能有。

FunctionChoiceBehavior.Auto() 让模型在需要时自动选择并调用已注册函数,无需你手动解析模型输出。这是 SK 1.x 的推荐用法。

实操 3 · 订单查询助手

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.ComponentModel;

var apiKey = Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY")
    ?? throw new Exception("请先设置 DEEPSEEK_API_KEY 环境变量");

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("deepseek-chat", apiKey,
    new Uri("https://api.deepseek.com/v1"));
var kernel = builder.Build();
kernel.Plugins.AddFromType<OrderPlugin>(); // 注册插件

var chat = kernel.GetRequiredService<IChatCompletionService>();
var settings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var history = new ChatHistory("你是电商客服助手,请基于工具返回的真实信息回答。");
history.AddUserMessage("我的订单 SK-10086 现在到哪了?运费多少钱?");
var reply = await chat.GetChatMessageContentAsync(history, settings, kernel);
Console.WriteLine(reply);

// ===== 插件定义 =====
public class OrderPlugin
{
    [KernelFunction("get_order_status")]
    [Description("查询订单的当前物流状态")]
    public string GetOrderStatus(
        [Description("订单号,形如 SK-10086")] string orderId)
        => $"订单 {orderId} 已发货,正在运输途中。";

    [KernelFunction("get_order_fee")]
    [Description("查询订单的运费金额")]
    public decimal GetOrderFee(
        [Description("订单号,形如 SK-10086")] string orderId)
        => orderId.StartsWith("SK-") ? 10.5m : 25m;
}

运行结果:

你的订单 SK-10086 目前状态:
已发货,正在运输途中
运费金额:¥10.5

跑通标志:AI 回答里同时包含物流状态和运费金额,且金额与函数返回值一致。

Description 编写最佳实践:函数描述用「能做什么 + 什么时候用」;参数写明格式和示例;返回尽量用 string/数字,复杂对象先序列化成 JSON;相关能力聚合进一个 Plugin,函数太多会让模型选择混乱。

参数与异常:SK 会按 Description 把模型给出的字符串参数自动转成 C# 类型,转换失败会向模型返回错误信息;函数内部应捕获业务异常并返回友好文本,让模型据此调整或向用户说明。

练习扩展 · 按重量算运费 + 预计送达日。 这两个练习我也跑通了,可以接着上面的代码加:

练习 1 · 按重量计算运费

[KernelFunction("calculate_shipping_fee")]
[Description("根据重量计算运费,重量单位为千克")]
public decimal CalculateShippingFee(
    [Description("订单重量,单位:千克")] decimal weight)
    => weight * 2.5m;  // 每公斤 2.5 元

练习 2 · 查询预计送达日期

[KernelFunction("get_delivery_date")]
[Description("查询订单的预计送达日期")]
public string GetDeliveryDate(
    [Description("订单号,形如 SK-10086")] string orderId)
    => DateTime.Now.AddDays(3).ToString("yyyy-MM-dd");

练习运行结果:

你:订单 SK-10086 重 5 公斤,运费多少?什么时候到?
助手:订单 SK-10086 的运费为 ¥12.5(5 公斤 × 2.5 元/公斤),
预计 2026-11-04 送达。

⚠️ 注意:一旦参数增加,务必在 [Description] 里写清新参数的格式/单位,比如「单位:千克」「形如 SK-10086」。描述写不清楚,自动调用时模型可能抽不出参数而报错。


05 关于「未验证」的部分

到这里,环境准备 + 阶段 0–2 的全部实操我都已对照 Microsoft.SemanticKernel 1.81.0 实际编译验证(dotnet build 0 错误 0 警告),代码可直接复制运行。

按计划,教程还有:

  • 阶段 3:记忆与 RAG(向量存储、Embedding、本地文档问答)
  • 阶段 4:Agent 与多智能体编排
  • 阶段 5:工程化与生产落地

这部分内容我还在验证中,尚未跑通确认,为避免把不确定的东西分享出去,本文先收在阶段 2。等阶段 3 验证完毕,我会补上「让 AI 基于你的资料回答问题」的续篇。

实操 阶段 你做出了什么
热身实操:第一个 C# 程序 阶段 0 温习接口与 async/await
实操 1:Hello Kernel 阶段 1 第一个 Kernel 与首次对话
实操 2:多轮对话程序 阶段 1 记住上下文的命令行聊天
实操 3:订单查询助手 阶段 2 AI 自动调用 C# 方法
练习:按重量算运费 + 送达日期 阶段 2 · 扩展 AI 自动选择新函数并传参

一句话总结:SK 的 Kernel 是 DI 容器 + 编排中心 + 执行上下文三件套;从 InvokePrompt 起步,到 ChatHistory 做多轮对话,再到用 [KernelFunction] + [Description] 把 C# 方法包装成 Plugin 让 AI 自动调用——三步走完,你的 .NET 业务系统就有了「会动手的 AI」。

如果你也在用 SK 做 .NET 侧的 AI 应用,欢迎交流——尤其是 Plugin 描述怎么写才让模型「调得准」,这个坑最多,也最值得聊。

转发给还在纠结「怎么把 AI 接进 .NET 业务」的同事,少走我们趟过的弯路。

关注 CSharp精选营,每周二四 get 能直接抄的 C# 实战。

下一篇预告:《让 AI 记住你的资料、还会自己分工——Semantic Kernel 记忆与 RAG 与多智能体(阶段 3–4)》。阶段 3 的 RAG 部分我已对照 SK 1.81.0 重写为可编译版本,阶段 4 的 Agent 框架在 1.81 发布线有 API 变动,会如实标注「未验证」部分,不糊弄。


关于作者

码农刚子,六年 ERP / 制造业系统开发,专注 C# / .NET / Blazor。

博客:https://www.coderlog.net/ 公众号:CSharp精选营