使用 SemanticKernel 制作天气预报智能体

十年河东,十年河西,莫欺少年穷

学无止境,精益求精

之前写过几篇关于OpenAiClient作为客户端的智能体,今天尝试使用 Microsoft.SemanticKernel  

 Microsoft.SemanticKernel vs OpenAIClient 全面对比

image

 场景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);
}
View Code

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);
}
View Code

单纯对话场景,两者差别不大!

场景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+ 行代码
View Code

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 行左右!
View Code

image

image

 总之,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 的工作流程

image

image

根据以上流程图,无非分为需要调用插件函数 和 不需要调用插件函数两种,那么如果需要调用插件函数,必须显式声明

  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();
                }
            }
        }
    }
}
View Code

上述提到了插件,而且提到了  HttpPlugin ,那么我们能否使用HttpPlugin 代替代码中的 HttpClient 吗?

思考一下,下节学习 内置插件及自定义插件!

 

posted @ 2026-07-21 17:03  天才卧龙  阅读(6)  评论(0)    收藏  举报