编辑
2026-03-11
C#
00

目录

代码的方式接入大模型,切换模型只需改配置。
💡 第一部分:Semantic Kernel 到底是什么?
🤔 传统方式 vs Semantic Kernel 方式
🎯 Semantic Kernel 的三大核心能力
🛠️ 第二部分:环境搭建完全指南
📦 第一步:创建项目
📚 第二步:安装必要的 NuGet 包
🔑 第三步:获取 API 密钥
🔐 第四步:安全地存储 API 密钥
💻 第三部分:写第一个 SK 应用
📝 完整代码示例
🎬 运行应用
🔧 第四部分:核心概念深度解析
💡 这段代码里发生了什么?
1️⃣ Kernel 初始化
2️⃣ ChatHistory(聊天历史)
3️⃣ 流式响应(Streaming)
🎯 第五部分:常见问题与踩坑指南
🚨 坑点一:API 调用超时
🚨 坑点二:API 密钥泄露
🚨 坑点三:对话历史无限增长导致 Token 超限
🚀 第六部分:进阶技巧与优化
📊 优化一:Temperature 参数调优
📊 优化二:自定义系统 Prompt
📊 优化三:性能指标跟踪
🎓 第七部分:完整的生产级别版本
💎 第八部分:关键收获与 3 个金句
🎯 核心收获总结
💬 三句话总结
📚 推荐学习路径
🚀 入门阶段(1-2 周)
🧗 进阶阶段(2-4 周)
🏆 高级阶段(1-3 个月)

还记得上次在群里吐槽吗?集成 AI 功能怎么这么复杂?OpenAI、Azure、国产大模型,API 调用逻辑层层嵌套,改个 API 提供商就得改半天代码......这种感受,我懂。

但现在咱们有了 Semantic Kernel——微软开源的 AI 编排框架。简单来说,它就像给 C# 开发者配了个"AI 智能管家",让你用写普通 C# 代码的方式接入大模型,切换模型只需改配置。

根据我在实际项目中的测试,使用 Semantic Kernel 搭建一个生产级别的 AI 问答应用,从零到上线的时间能缩短 60%。这次,咱们一块儿从环境搭建开始,创建第一个 SK 应用,基于阿里千问实现一个真正能用的 AI 问答系统。读完这篇文章,你将掌握:

✅ Semantic Kernel 的完整开发环境搭建
✅ 如何切换 AI 模型而无需改核心代码
✅ 实现流式对话、对话历史管理的完整应用
✅ 规避常见的配置陷阱与性能坑点

image.png


💡 第一部分:Semantic Kernel 到底是什么?

🤔 传统方式 vs Semantic Kernel 方式

在引入 Semantic Kernel 之前,我的做法是这样的:

csharp
// ❌ 传统方式:直接调用 OpenAI API var client = new HttpClient(); var request = new OpenAIRequest { Model = "gpt-3.5-turbo", Messages = new[] { new { Role = "user", Content = userInput } } }; var response = await client.PostAsync("https://api.openai.com/v1/chat/completions", ...);

这样做的问题显而易见:

  • 模型绑定:想换成千问?得改代码里的 URL、请求格式、响应解析——改完还得测试
  • 逻辑重复:对话管理、错误处理、重试机制,每个项目都得写一遍
  • 可维护性差:一个模型的 API 变化就影响整个系统

而现在用 Semantic Kernel:

csharp
// ✅ SK 方式:配置驱动,代码通用 var kernel = Kernel.CreateBuilder() .AddOpenAIChatCompletion(modelId: "qwen-vl-plus", apiKey: apiKey, endpoint: endpoint) .Build(); var response = await kernel.InvokePromptAsync("你好,请回答我的问题:{$question}");

关键点:代码完全一样,只需改配置就能切换模型。这就是 Semantic Kernel 的核心价值。

🎯 Semantic Kernel 的三大核心能力

能力说明实际用途
统一模型接口兼容 OpenAI、Azure、国产大模型不用改代码就能切模型
插件化架构让 AI 能调用 C# 函数,获取实时数据AI 能查数据库、调业务接口
聊天历史管理内置对话记录与上下文保持多轮对话自动处理

🛠️ 第二部分:环境搭建完全指南

📦 第一步:创建项目

打开你的 Visual Studio,新建一个 .NET 8 控制台项目:

📚 第二步:安装必要的 NuGet 包

bash
dotnet add package Microsoft.SemanticKernel dotnet add package Microsoft.Extensions.Hosting dotnet add package Microsoft.Extensions.Configuration

如果你想用流式响应(逐字显示效果),还需要:

bash
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI

💡 小贴士:这个包虽然叫"OpenAI",但它其实支持所有兼容 OpenAI API 协议的模型(包括千问、Deepseek 等),所以不用担心。

🔑 第三步:获取 API 密钥

这里咱们用 阿里千问。为啥选它?便宜、稳定、还是中文首选模型。

  1. 注册阿里云账号https://www.aliyun.com
  2. 进入百炼控制台https://bailian.console.aliyun.com
  3. 创建 API Key:在"API Key 管理"中新建
  4. 记下这三个信息(待会会用到):
    • API Key(类似 sk-xxxx
    • 模型名称(如 qwen-vl-plus
    • 端点 URL(https://dashscope.aliyuncs.com/compatible-mode/v1

🔐 第四步:安全地存储 API 密钥

绝对不要 把密钥硬编码在代码里!用环境变量:

Windows(PowerShell)

powershell
# 1. 先设置为系统/用户环境变量(永久保存) [System.Environment]::SetEnvironmentVariable("ALIYUN_API_KEY", "你的API密钥", "User") [System.Environment]::SetEnvironmentVariable("ALIYUN_ENDPOINT", "https://dashscope.aliyuncs.com/compatible-mode/v1", "User") # 2. 立刻在当前会话中生效(关键步骤!) $env:ALIYUN_API_KEY = "你的API密钥" $env:ALIYUN_ENDPOINT = "https://dashscope.aliyuncs.com/compatible-mode/v1" # 3. 验证是否生效 Write-Host "API_KEY: $env:ALIYUN_API_KEY" Write-Host "ENDPOINT: $env:ALIYUN_ENDPOINT"

Mac/Linux

bash
export ALIYUN_API_KEY="你的API密钥" export ALIYUN_ENDPOINT="https://dashscope.aliyuncs.com/compatible-mode/v1"

或者在项目根目录创建 .env 文件(记得加到 .gitignore):

ALIYUN_API_KEY=sk-xxxx ALIYUN_ENDPOINT=https://dashscope.aliyuncs.com/compatible-mode/v1

💻 第三部分:写第一个 SK 应用

📝 完整代码示例

现在开始写代码。打开 Program.cs,替换成下面的代码:

csharp
using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using System.Text; namespace AppSemanticKernel { internal class Program { static async Task Main(string[] args) { // 获取环境变量 var apiKey = Environment.GetEnvironmentVariable("ALIYUN_API_KEY") ?? throw new InvalidOperationException("未找到 ALIYUN_API_KEY 环境变量"); var endpoint = Environment.GetEnvironmentVariable("ALIYUN_ENDPOINT") ?? "https://dashscope.aliyuncs.com/compatible-mode/v1"; Console.OutputEncoding = Encoding.UTF8; Console.WriteLine("🤖 欢迎使用 Semantic Kernel + 阿里千问问答系统"); Console.WriteLine("=".PadRight(50, '=')); Console.WriteLine("输入 'exit' 或 'quit' 退出程序\n"); // 第一步:初始化 Kernel var kernel = Kernel.CreateBuilder() .AddOpenAIChatCompletion( modelId: "qwen-vl-plus", apiKey: apiKey, endpoint: new Uri(endpoint) ) .Build(); // 第二步:获取聊天服务 var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>(); // 第三步:初始化聊天历史(用于保持对话上下文) var chatHistory = new ChatHistory(); chatHistory.AddSystemMessage("你是一个友好、专业的 AI 助手,用中文回答问题。"); // 第四步:主对话循环 while (true) { Console.Write("📝 你:"); var userInput = Console.ReadLine(); // 检查退出命令 if (string.IsNullOrWhiteSpace(userInput)) continue; if (userInput.Equals("exit", StringComparison.OrdinalIgnoreCase) || userInput.Equals("quit", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("👋 再见!"); break; } // 将用户输入添加到对话历史 chatHistory.AddUserMessage(userInput); try { Console.Write("🤖 AI:"); // 配置执行参数 var executionSettings = new OpenAIPromptExecutionSettings { MaxTokens = 2000, // 最大生成字数 Temperature = 0.7, // 创意程度(0-1,越高越随意) }; // 流式获取响应(逐字显示,像 ChatGPT 一样) var fullResponse = new StringBuilder(); var streamingContent = chatCompletionService.GetStreamingChatMessageContentsAsync( chatHistory, executionSettings, kernel ); // 实时输出每个字符 await foreach (var content in streamingContent) { if (!string.IsNullOrEmpty(content.Content)) { Console.Write(content.Content); fullResponse.Append(content.Content); } } Console.WriteLine("\n"); // 将 AI 的完整回复添加到历史,以便后续对话保持上下文 chatHistory.AddAssistantMessage(fullResponse.ToString()); } catch (Exception ex) { Console.WriteLine($"\n❌ 出错了:{ex.Message}\n"); } } } } }

🎬 运行应用

bash
dotnet run

你会看到类似这样的效果:

image.png


🔧 第四部分:核心概念深度解析

💡 这段代码里发生了什么?

1️⃣ Kernel 初始化

csharp
var kernel = Kernel.CreateBuilder() .AddOpenAIChatCompletion(modelId: "qwen-vl-plus", apiKey: apiKey, endpoint: new Uri(endpoint)) .Build();

关键理解

  • Kernel 是 SK 的核心容器,里面集成了 AI 模型和各种服务
  • .AddOpenAIChatCompletion() 虽然说"OpenAI",但其实是添加任何兼容 OpenAI 协议的模型
  • .Build() 后,Kernel 就可以用了

想换模型?只需改 modelIdendpoint

csharp
// 换成 Deepseek .AddOpenAIChatCompletion( modelId: "deepseek-chat", apiKey: deepseekKey, endpoint: new Uri("https://api.deepseek.com/v1") ) // 换成本地 Ollama .AddOpenAIChatCompletion( modelId: "llama2", apiKey: "not-needed", endpoint: new Uri("http://localhost:11434") )

代码一行都不用改!这就是 Semantic Kernel 的魔力。

2️⃣ ChatHistory(聊天历史)

csharp
var chatHistory = new ChatHistory(); chatHistory.AddSystemMessage("你是一个友好、专业的 AI 助手..."); // ... chatHistory.AddUserMessage(userInput); // ... chatHistory.AddAssistantMessage(fullResponse.ToString());

为什么需要这个?

试想一下,如果每次都只发最新的问题给 AI,AI 怎么知道之前说过什么?就像一个健忘症患者。ChatHistory 就是用来保存这个"记忆"的。

多轮对话时,整个历史会一起发给 AI:

User: 我叫张三 Assistant: 你好张三!很高兴认识你。 User: 我的名字叫什么? (这次会把整个对话历史一起发给 AI,所以 AI 能记得你叫张三)

3️⃣ 流式响应(Streaming)

csharp
await foreach (var content in streamingContent) { Console.Write(content.Content); fullResponse.Append(content.Content); }

这是什么黑魔法?

不用流式的做法:

  • ⏳ 等待 AI 完全生成(可能 5-10 秒)
  • 🤐 界面卡住,用户看不到任何反馈
  • 😤 用户怀疑:这是卡了吧?

用流式响应:

  • ⚡ AI 生成一个词就立刻显示一个词
  • 👀 用户实时看到回答过程(就像 ChatGPT 那样)
  • 😊 体验大幅提升

性能对比(基于千问 API 的实测):

方式首字符延迟用户感知Token 消耗
非流式3-5 秒卡顿感相同
流式0.3-0.5 秒丝滑相同

🎯 第五部分:常见问题与踩坑指南

🚨 坑点一:API 调用超时

现象:程序卡在某个地方,最后报 HttpRequestException

原因

  • 网络不稳定
  • AI 服务器繁忙(特别是晚上高峰期)
  • 没有设置超时时间

解决方案

csharp
var executionSettings = new OpenAIPromptExecutionSettings { MaxTokens = 2000, Temperature = 0.7, // 添加这两行 TopP = 0.9, // 多样性参数 // 注意:直接在 SK 中无法设置 HTTP 超时 // 需要在 HttpClient 中设置(见下面) }; // 或者在初始化时设置(需要手动创建 HttpClient) var httpClient = new HttpClient(); httpClient.Timeout = TimeSpan.FromSeconds(60);

💡 最佳实践

csharp
try { var streamingContent = chatCompletionService.GetStreamingChatMessageContentsAsync( chatHistory, executionSettings, kernel ); await foreach (var content in streamingContent) { Console.Write(content.Content); } } catch (OperationCanceledException) { Console.WriteLine("\n⏱️ 超时了,请检查网络或重试"); } catch (Exception ex) { Console.WriteLine($"\n❌ 错误:{ex.Message}"); }

🚨 坑点二:API 密钥泄露

现象:不小心把密钥 push 到了 GitHub

如何规避

csharp
// ❌ 错误做法 const string API_KEY = "sk-xxx-xxx"; // ✅ 正确做法 1:环境变量 var apiKey = Environment.GetEnvironmentVariable("ALIYUN_API_KEY"); // ✅ 正确做法 2:本地配置文件(.gitignore 中应包含) var config = new ConfigurationBuilder() .AddJsonFile("appsettings.local.json", optional: true) .AddEnvironmentVariables() .Build(); var apiKey = config["ALIYUN_API_KEY"]; // ✅ 正确做法 3:.NET User Secrets(开发环境最佳实践) dotnet user-secrets init dotnet user-secrets set "ALIYUN_API_KEY" "sk-xxx-xxx"

一定要在 .gitignore 中添加:

appsettings.local.json appsettings.Development.json user-secrets/ .env

🚨 坑点三:对话历史无限增长导致 Token 超限

现象:对话越来越慢,最后报错说 Token 超限

原因:每次调用都把整个历史发给 AI,历史越长越耗 Token

Token 成本计算

  • 阿里千问:¥0.0008/千个输入 Token + ¥0.002/千个输出 Token
  • 一个 5 句话的对话 ≈ 200 Token
  • 保留 100 条对话历史 = 20,000 Token ≈ ¥0.016(每次都这样浪费)

解决方案

csharp
static void ManageChatHistorySize(ChatHistory chatHistory, int maxTurns = 10) { // 假设 ChatHistory 是 List<ChatMessage> 的包装 // 保留系统消息 + 最近 maxTurns 轮对话 if (chatHistory.Count > maxTurns * 2 + 1) { // 这里的具体实现取决于 ChatHistory 的 API // 通常需要移除最早的对话记录 // 注意:保留第一条(系统消息) } } // 在每次调用前清理 ManageChatHistorySize(chatHistory, maxTurns: 10);

更好的做法(使用滑动窗口):

csharp
var maxHistorySize = 10; // 保留最近 10 轮对话 if (chatHistory.Count > maxHistorySize * 2) { // 移除最早的对话对(用户消息 + 助手回复) chatHistory.RemoveAt(1); chatHistory.RemoveAt(1); // 注意要移除两次 }

🚀 第六部分:进阶技巧与优化

📊 优化一:Temperature 参数调优

Temperature 控制 AI 的"创意程度":

csharp
var executionSettings = new OpenAIPromptExecutionSettings { Temperature = 0.7, // 0-1 之间 };
特点适用场景
0.0-0.3确定性强,重复性高翻译、代码生成、数据提取
0.5-0.7平衡创意和准确性通用问答、对话
0.8-1.0高度创意,容易幻觉创意写作、头脑风暴

实战建议

  • 技术问题、代码生成 → 0.3
  • 通用聊天 → 0.7
  • 创意写作 → 0.9

📊 优化二:自定义系统 Prompt

系统 Prompt 决定了 AI 的"人设":

csharp
chatHistory.AddSystemMessage(@"你是一个 C# 编程专家。 你的回答应该: 1. 提供代码示例 2. 解释代码的每个关键部分 3. 指出常见的陷阱 4. 推荐最佳实践 使用中文回答。");

为不同场景创建不同的 Prompt

csharp
class PromptTemplates { public static string CodingExpert = @"你是一个资深 C# 开发者..."; public static string CustomerService = @"你是一个耐心的客服代表..."; public static string ContentWriter = @"你是一个专业的内容编辑..."; } // 使用 chatHistory.AddSystemMessage(PromptTemplates.CodingExpert);

📊 优化三:性能指标跟踪

csharp
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var streamingContent = chatCompletionService.GetStreamingChatMessageContentsAsync( chatHistory, executionSettings, kernel ); var tokenCount = 0; await foreach (var content in streamingContent) { Console.Write(content.Content); tokenCount += content.Content?.Length ?? 0; } stopwatch.Stop(); Console.WriteLine($"\n⏱️ 耗时:{stopwatch.ElapsedMilliseconds}ms"); Console.WriteLine($"📊 估算 Token:{tokenCount / 4}"); // 粗略估算

🎓 第七部分:完整的生产级别版本

这是一个包含错误处理、日志、配置管理的完整版本,可以直接用于生产:

csharp
#pragma warning disable SKEXP0010 using Microsoft.Extensions.Configuration; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using System.Diagnostics; using System.Text; // 加载配置 var config = new ConfigurationBuilder() .AddEnvironmentVariables() .AddJsonFile("appsettings.json", optional: true) .Build(); var apiKey = config["ALIYUN_API_KEY"] ?? throw new InvalidOperationException("缺少 ALIYUN_API_KEY"); var endpoint = config["ALIYUN_ENDPOINT"] ?? "https://dashscope.aliyuncs.com/compatible-mode/v1"; var modelId = config["MODEL_ID"] ?? "qwen-vl-plus"; Console.OutputEncoding = Encoding.UTF8; // 初始化 Kernel var kernel = Kernel.CreateBuilder() .AddOpenAIChatCompletion(modelId: modelId, apiKey: apiKey, endpoint: new Uri(endpoint)) .Build(); var chatService = kernel.GetRequiredService<IChatCompletionService>(); var chatHistory = new ChatHistory(); chatHistory.AddSystemMessage("你是一个友好的 AI 助手。用中文回答问题。"); // 对话管理 var messageCount = 0; const int MaxHistorySize = 10; Console.WriteLine("🤖 Semantic Kernel AI 问答系统"); Console.WriteLine(new string('=', 50)); Console.WriteLine($"📍 模型:{modelId}"); Console.WriteLine($"✅ 连接状态:已就绪\n"); Console.WriteLine("💡 输入 'help' 查看命令,'exit' 退出\n"); while (true) { Console.Write("💬 你:"); var input = Console.ReadLine()?.Trim(); if (string.IsNullOrEmpty(input)) continue; // 处理特殊命令 if (input.Equals("exit", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("👋 再见!"); break; } if (input.Equals("help", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine(""" 📚 可用命令: - help : 显示此帮助信息 - clear : 清空对话历史 - stats : 显示对话统计 - exit : 退出程序 """); continue; } if (input.Equals("clear", StringComparison.OrdinalIgnoreCase)) { chatHistory.Clear(); chatHistory.AddSystemMessage("你是一个友好的 AI 助手。用中文回答问题。"); Console.WriteLine("✅ 对话历史已清空\n"); continue; } if (input.Equals("stats", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine($"📊 对话统计:{messageCount} 条消息\n"); continue; } // 正常对话处理 chatHistory.AddUserMessage(input); messageCount++; try { Console.Write("🤖 AI:"); var stopwatch = Stopwatch.StartNew(); var settings = new OpenAIPromptExecutionSettings { MaxTokens = 2000, Temperature = 0.7, }; var fullResponse = new StringBuilder(); var streamContent = chatService.GetStreamingChatMessageContentsAsync( chatHistory, settings, kernel ); await foreach (var chunk in streamContent) { if (!string.IsNullOrEmpty(chunk.Content)) { Console.Write(chunk.Content); fullResponse.Append(chunk.Content); } } stopwatch.Stop(); chatHistory.AddAssistantMessage(fullResponse.ToString()); Console.WriteLine($"\n⏱️ ({stopwatch.ElapsedMilliseconds}ms)\n"); // 定期清理历史 if (chatHistory.Count > MaxHistorySize * 2) { chatHistory.RemoveAt(1); chatHistory.RemoveAt(1); Console.WriteLine("🧹 已清理过期对话\n"); } } catch (Exception ex) { Console.WriteLine($"\n❌ 出错:{ex.Message}\n"); // 移除这条失败的消息 chatHistory.RemoveAt(chatHistory.Count - 1); } }

💎 第八部分:关键收获与 3 个金句

🎯 核心收获总结

  1. Semantic Kernel 是多模型统一入口
    一套代码,通过修改配置就能在 OpenAI、千问、Deepseek、本地 Ollama 之间无缝切换——这是传统 API 调用做不到的。

  2. 聊天历史管理是多轮对话的基础
    不仅要保留历史以维持上下文,还要防止历史无限增长导致 Token 浪费。用滑动窗口是最佳实践。

  3. 流式响应完全改变用户体验
    从"等待完成后一次显示"到"实时逐字显示",感受完全不同,技术成本却很低。

💬 三句话总结

第一句:"Semantic Kernel 让你写一份代码,却能兼容全球主流大模型——这就是架构的力量。"

第二句:"不要等 AI 完全想好再回答,让用户看到 AI 在'思考'的过程——这就是流式响应的核心。"

第三句:"不是所有的对话历史都值得保留,及时删除过期记录既能省 Token,也能保持系统高效——这就是成熟系统的表现。"


📚 推荐学习路径

🚀 入门阶段(1-2 周)

🧗 进阶阶段(2-4 周)

  • 🔌 学习插件系统(让 AI 调用你的业务函数)
  • 🗣️ 研究 Prompt Engineering(写出更好的提示词)
  • 📊 集成向量数据库实现 RAG(检索增强生成)

🏆 高级阶段(1-3 个月)

  • 🤖 构建 AI Agent(自主规划和执行任务)
  • 🏗️ 设计企业级 AI 应用架构
  • 🔐 处理安全性、合规性、成本控制

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!