一份写给 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精选营
暂无评论,来抢沙发吧~