使用 SemanticKernel 制作天气预报智能体
十年河东,十年河西,莫欺少年穷
学无止境,精益求精
之前写过几篇关于OpenAiClient作为客户端的智能体,今天尝试使用 Microsoft.SemanticKernel
Microsoft.SemanticKernel vs OpenAIClient 全面对比

场景1:单轮/多轮对话
OpenAIClient
var messages = new List<ChatRequestMessage> { new ChatRequestSystemMessage("你是助手") }; while (true) { var input = Console.ReadLine(); messages.Add(new ChatRequestUserMessage(input)); var response = await client.GetChatCompletionsAsync(new ChatCompletionsOptions { Model = "qwen-plus", Messages = messages }); var reply = response.Value.Choices[0].Message.Content; messages.Add(new ChatRequestAssistantMessage(reply)); Console.WriteLine(reply); }
SemanticKernel
var history = new ChatHistory(); history.AddSystemMessage("你是助手"); while (true) { var input = Console.ReadLine(); history.AddUserMessage(input); var reply = await chatService.GetChatMessageContentAsync(history); history.AddAssistantMessage(reply.Content); Console.WriteLine(reply.Content); }
单纯对话场景,两者差别不大!
场景2:需要调用第三方工具-API,譬如查询天气预报
OpenAIClient
// ❌ 需要手动处理函数调用全流程 // 1. 定义函数 schema // 2. 发送给模型 // 3. 解析模型返回的函数调用 // 4. 执行函数 // 5. 把结果发回模型 // 6. 获取最终回复 var functions = new[] { new ChatCompletionsFunctionToolDefinition { Name = "get_weather", Description = "查询天气", Parameters = BinaryData.FromObjectAsJson(new { type = "object", properties = new { city = new { type = "string" } } }) } }; var response = await client.GetChatCompletionsAsync(new ChatCompletionsOptions { Model = "qwen-plus", Messages = messages, Tools = functions }); // 手动解析 tool_calls... // 手动执行函数... // 手动把结果发回... // 至少 50+ 行代码
SemanticKernel
// ✅ 自动处理全流程 // 1. 定义插件类 public class WeatherPlugin { [KernelFunction("get_weather")] public async Task<string> GetWeatherAsync(string city) { ... } } // 2. 注册插件 kernel.Plugins.AddFromObject(new WeatherPlugin()); // 3. 调用(自动执行函数调用循环) var result = await kernel.InvokePromptAsync("苏州天气怎么样?"); Console.WriteLine(result.GetValue<string>()); // 总共 10 行左右!


总之,OpenAIClient 适合简单场景,SemantickKernel 适用大多数场景!
正文开始
vs-2026 创建 ConsoleApp2 项目,打开 程序包管理控制器 ,输入如下指令
1、查看当前目录所在位置
dir
2、切换到 ConsoleApp2 主目录下
cd ConsoleApp2
3、安装依赖
dotnet add package Microsoft.SemanticKernel --version 1.78.0
以上便完成了项目基本配置,在贴代码之前,先了解下什么是 【为AI准备的代码】 。
属性标签:[KernelFunction("get_weather")] 和 描述进行配对:[Description("获取指定城市的实时天气信息,包括温度、湿度、天气现象、风向风力等")]
KernelFunction声明后,说明该方法是被LLM-AI调用的方法,Description 也是喂给LLM-AI的描述,使LLM-AI更好的理解该函数的主要作用
【为AI准备的代码】可被 SemanticKernel 注册为自定义插件,当然 SemanticKernel 有许多内置插件,譬如:
kernel.Plugins.AddFromType<TimePlugin>(); --时间插件 kernel.Plugins.AddFromType<TextPlugin>(); --文本插件 kernel.Plugins.AddFromType<HttpPlugin>(); --http请求插件
SemanticKernel 的工作流程


根据以上流程图,无非分为需要调用插件函数 和 不需要调用插件函数两种,那么如果需要调用插件函数,必须显式声明
var builder = Kernel.CreateBuilder(); // 配置千问 builder.AddOpenAIChatCompletion( modelId: ConstParm.modelId, apiKey: ConstParm.apiKey, endpoint: new Uri(ConstParm.endpoint) ); var kernel = builder.Build(); // 注册插件 var httpClient = new HttpClient(); //高德天气APIkey var amapApiKey = ConstParm.amapApiKey; var weatherPlugin = new WeatherPlugin(httpClient, amapApiKey); kernel.Plugins.AddFromObject(weatherPlugin); Console.WriteLine("✅ Kernel 初始化完成,高德天气插件已注册"); Console.WriteLine($"📦 已注册插件:{string.Join(", ", kernel.Plugins.Select(p => p.Name))}"); // 获取聊天服务 var chatService = kernel.GetRequiredService<IChatCompletionService>(); // 创建聊天历史 var history = new ChatHistory(); history.AddSystemMessage("你是天气预报分析师。当用户询问天气时,必须调用 get_weather 函数获取真实数据,不要编造天气信息。"); // 👇 关键:配置函数调用 var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }; Console.WriteLine("\n========================================"); Console.WriteLine("🌤️ 天气智能体已就绪!");
最后,贴出完整代码:
using ConsoleApp1; using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using System.ComponentModel; using System.Text.Json; namespace ConsoleApp2 { public class WeatherPlugin { private readonly HttpClient _httpClient; private readonly string _apiKey; public WeatherPlugin(HttpClient httpClient, string apiKey) { _httpClient = httpClient; _apiKey = apiKey; } [KernelFunction("get_weather")] [Description("获取指定城市的实时天气信息,包括温度、湿度、天气现象、风向风力等")] public async Task<string> GetWeatherAsync(string city) { try { // 1. 查城市 ADCode var adcodeUrl = $"https://restapi.amap.com/v3/config/district?keywords={city}&subdistrict=0&key={_apiKey}"; var adcodeData = await _httpClient.GetStringAsync(adcodeUrl); var adcodeJson = JsonDocument.Parse(adcodeData); if (adcodeJson.RootElement.GetProperty("status").GetString() != "1") { return $"❌ 未找到城市:{city}"; } var adcode = adcodeJson.RootElement.GetProperty("districts")[0] .GetProperty("adcode").GetString(); // 2. 查实时天气 var weatherUrl = $"https://restapi.amap.com/v3/weather/weatherInfo?city={adcode}&key={_apiKey}"; var weatherData = await _httpClient.GetStringAsync(weatherUrl); var weatherJson = JsonDocument.Parse(weatherData); if (weatherJson.RootElement.GetProperty("status").GetString() != "1") { return $"❌ 天气查询失败:{city}"; } var info = weatherJson.RootElement.GetProperty("lives")[0]; var result = new { city = info.GetProperty("city").GetString(), weather = info.GetProperty("weather").GetString(), temperature = info.GetProperty("temperature").GetString(), humidity = info.GetProperty("humidity").GetString(), windDirection = info.GetProperty("winddirection").GetString(), windPower = info.GetProperty("windpower").GetString(), reportTime = info.GetProperty("reporttime").GetString() }; return JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); } catch (Exception ex) { return $"❌ 错误:{ex.Message}"; } } } internal class Program { static async Task Main(string[] args) { // 👇 修复控制台编码(必须在第一行!)-- 在控制台输入的内容,传到千问可能乱码 Console.OutputEncoding = System.Text.Encoding.Unicode; Console.InputEncoding = System.Text.Encoding.Unicode; Console.WriteLine("🚀 正在初始化 Kernel..."); var builder = Kernel.CreateBuilder(); // 配置千问 builder.AddOpenAIChatCompletion( modelId: ConstParm.modelId, apiKey: ConstParm.apiKey, endpoint: new Uri(ConstParm.endpoint) ); var kernel = builder.Build(); // 注册插件 var httpClient = new HttpClient(); //高德天气APIkey var amapApiKey = ConstParm.amapApiKey; var weatherPlugin = new WeatherPlugin(httpClient, amapApiKey); kernel.Plugins.AddFromObject(weatherPlugin); Console.WriteLine("✅ Kernel 初始化完成,高德天气插件已注册"); Console.WriteLine($"📦 已注册插件:{string.Join(", ", kernel.Plugins.Select(p => p.Name))}"); // 获取聊天服务 var chatService = kernel.GetRequiredService<IChatCompletionService>(); // 创建聊天历史 var history = new ChatHistory(); history.AddSystemMessage("你是天气预报分析师。当用户询问天气时,必须调用 get_weather 函数获取真实数据,不要编造天气信息。"); // 👇 关键:配置函数调用 var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }; Console.WriteLine("\n========================================"); Console.WriteLine("🌤️ 天气智能体已就绪!"); Console.WriteLine("💬 输入城市名查询天气,输入 'quit' 退出"); Console.WriteLine("========================================\n"); while (true) { Console.Write("👤 你:"); var input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input) || input.ToLower() == "quit" || input.ToLower() == "exit") { Console.WriteLine("\n👋 再见!"); break; } history.AddUserMessage(input); try { // 👇 使用 InvokePromptAsync 而不是 GetChatMessageContentAsync var result = await kernel.InvokePromptAsync(input, new(executionSettings)); var reply = result.GetValue<string>(); history.AddAssistantMessage(reply); Console.WriteLine($"\n🤖 助手:{reply}\n"); } catch (Exception ex) { Console.WriteLine($"\n❌ 错误:{ex.Message}"); if (ex.InnerException != null) { Console.WriteLine($"详细信息:{ex.InnerException.Message}"); } Console.WriteLine(); } } } } }
上述提到了插件,而且提到了 HttpPlugin ,那么我们能否使用HttpPlugin 代替代码中的 HttpClient 吗?
思考一下,下节学习 内置插件及自定义插件!

浙公网安备 33010602011771号