当前位置: 欣欣网 > 码农

SemanticKernel之使用Plugins

2024-04-19码农

Plugins在SK中是一个神奇的功能。我们说大语言模型具有不确定性,我们的代码是确定性的,而Plugins有把这种不确定性转成确定性能功能。

下面的例子是一个通过 自然语言实现购买 的案例,客户可以通过文字或语音来输入自然语言,

<ItemGroup><PackageReferenceInclude="Microsoft.SemanticKernel"Version="1.7.1" /><PackageReferenceInclude="NAudio"Version="2.2.1" /></ItemGroup>

下面是具体代码,核心是在定义准确性的方法时,通过KernelFunction特性和Description特性来让SK知道这是一个Plugins,并且用 Description的描述来映射自然语言的语义,包括提取参数,具体代友好如下:

  • #pragmawarning disable SKEXP0001using Microsoft.SemanticKernel;using Microsoft.SemanticKernel.AudioToText;using Microsoft.SemanticKernel.ChatCompletion;using Microsoft.SemanticKernel.Connectors.OpenAI;using Microsoft.SemanticKernel.TextToAudio;using NAudio.Wave;using System.ComponentModel;using System.Data;namespaceLittleHelperpublicpartial classMainForm : Form {publicMainForm() { InitializeComponent(); } StoreSystem _store; Kernel _kernel; IChatCompletionService _chatCompletionService; OpenAIPromptExecutionSettings _openAIPromptExecutionSettings; ChatHistory _history;string _key; WaveOutEvent _player;privatevoidMainForm_Load(object sender, EventArgs e) { _store = new StoreSystem(); TotalLab.Text = "总价:" + _store.Total.ToString("0.00"); GoodsGrid.DataSource = _store.GoodsList; GoodsGrid.Rows[0].Cells[0].Selected = false;var chatModelId = "gpt-4-0125-preview"; _key = File.ReadAllText(@"C:\GPT\key.txt");var builder = Kernel.CreateBuilder(); builder.Services.AddOpenAIChatCompletion(chatModelId, _key); builder.Plugins.AddFromObject(_store); _kernel = builder.Build(); _history = new ChatHistory("所有水果的单价(Price)单位是斤),所有数量都是斤。"); _chatCompletionService = _kernel.GetRequiredService<IChatCompletionService>(); _openAIPromptExecutionSettings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; _player = new WaveOutEvent(); }privatevoidSubmitButton_Click(object sender, EventArgs e) {if (string.IsNullOrWhiteSpace(AskTextBox.Text)) { MessageBox.Show("提示不能为空!");return; }var askText = AskTextBox.Text; AskTextBox.Clear(); ResultTextBox.Clear(); InMessageLab.Text = "输入问题:\r\n" + askText; AskQuestion(askText); }voidReloadGrid() { GoodsGrid.DataSource = null; GoodsGrid.DataSource = _store.GoodsList;foreach (DataGridViewColumn col in GoodsGrid.Columns) {switch (col.DataPropertyName) {case"Name": col.HeaderText = "名称"; col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;break;case"Price": col.HeaderText = "单价";break;case"Quantity": col.HeaderText = "库存数量";break;case"BuyQuantity": col.HeaderText = "销售数量";break; } }foreach (DataGridViewRow row in GoodsGrid.Rows) {if (row.Cells["BuyQuantity"].Value != null) {var buyQuantity = (int)row.Cells["BuyQuantity"].Value;if (buyQuantity > 0) { row.Cells["BuyQuantity"]. style.BackColor = Color.LightGreen; } } } GridClearSelect(); TotalLab.Text = "总价:" + _store.Total.ToString("0.00"); }async Task AskQuestion(string askMessage) { _history.AddUserMessage(askMessage);var result = await _chatCompletionService.GetChatMessageContentAsync(_history, _openAIPromptExecutionSettings, _kernel);var fullMessage = result.Content; ResultTextBox.Text = fullMessage; _history.AddMessage(AuthorRole.Assistant, fullMessage);await TextToAudioAsync(fullMessage); ReloadGrid(); }async Task TextToAudioAsync(string speakText) {var kernel = Kernel.CreateBuilder() .AddOpenAITextToAudio( modelId: "tts-1", apiKey: _key) .Build();var textToAudioService = kernel.GetRequiredService<ITextToAudioService>();//转音频文件var executionSettings = new OpenAITextToAudioExecutionSettings("shimmer") { ResponseFormat = "mp3", Speed = 1.0f };var audioContent = await textToAudioService.GetAudioContentAsync(speakText, executionSettings);var outputFolder = Path.Combine(Directory.GetCurrentDirectory(), "NAudio"); Directory.CreateDirectory(outputFolder);var speakFile = DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".mp3";var audioFilePath = Path.Combine(outputFolder, speakFile); await File.WriteAllBytesAsync(audioFilePath, audioContent.Data.Value.ToArray());//读取音频文件usingvar reader = new AudioFileReader(audioFilePath); _player.Init(reader); _player.Play(); } WaveInEvent waveIn;bool audioMark = true;string outputFilePath = "audio.wav"; WaveFileWriter writer;privatevoidSpeekBut_Click(object sender, EventArgs e) {if (audioMark) { SpeekBut.Text = "停止语音";var recordFile = DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".wav";var outputFolder = Path.Combine("C://GPT/NAudio"); Directory.CreateDirectory(outputFolder); outputFilePath = Path.Combine(outputFolder, recordFile);if (waveIn == null) { waveIn = new WaveInEvent();if (writer == null) { writer = new WaveFileWriter(outputFilePath, waveIn.WaveFormat); } waveIn.DataAvailable += (s, a) => {if (writer != null) { writer.Write(a.Buffer, 0, a.BytesRecorded); } }; } waveIn.StartRecording(); }else { SpeekBut.Text = "开始语音"; waveIn.StopRecording(); writer?.Dispose(); writer = null; waveIn?.Dispose(); waveIn = null; AudioToTextAsync(outputFilePath).ContinueWith(t => {if (t.IsFaulted) { MessageBox.Show("转换失败!"); }else {var text = t.Result;this.Invoke(() => { AskTextBox.Text = text; AskQuestion(text); }); } }); } audioMark = !audioMark; }privateasync Task<string> AudioToTextAsync(string audioFilePath) {try {var kernel = Kernel.CreateBuilder() .AddOpenAIAudioToText( modelId: "whisper-1", apiKey: _key) .Build();var audioToTextService = kernel.GetRequiredService<IAudioToTextService>();var executionSettings = new OpenAIAudioToTextExecutionSettings(audioFilePath) { Language = "zh", Prompt = "给出简体中文的文本", ResponseFormat = "json", Temperature = 0.3f, Filename = "audio.wav" }; ReadOnlyMemory<byte> audioData = await File.ReadAllBytesAsync(audioFilePath);var audioContent = new AudioContent(new BinaryData(audioData));var textContent = await audioToTextService.GetTextContentAsync(audioContent, executionSettings);return textContent?.Text; }catch (Exception exc) {return exc.Message; } }privatevoidGoodsGrid_Leave(object sender, EventArgs e) { GridClearSelect(); }voidGridClearSelect() {foreach (DataGridViewRow row in GoodsGrid.Rows) { row.Selected = false;foreach (DataGridViewCell cell in row.Cells) { cell.Selected = false; } } } }public classStoreSystem {public List<Goods> GoodsList { get; set; } = new List<Goods> {new Goods("苹果",5,100),new Goods("香蕉",3,200),new Goods("橙子",4,150),new Goods("桃子",6,120),new Goods("梨",5,100),new Goods("葡萄",7,80),new Goods("西瓜",8,60),new Goods("菠萝",9,40),new Goods("芒果",10,30),new Goods("草莓",11,20),new Goods("柠檬",4,100),new Goods("橘子",3,100),new Goods("蓝莓",6,100),new Goods("樱桃",7,100),new Goods("葡萄柚",8,100),new Goods("柚子",9,100),new Goods("榴莲",10,100),new Goods("火龙果",11,100),new Goods("荔枝",12,100),new Goods("椰子",13,100),new Goods("桑葚",5,100),new Goods("杨梅",4,100),new Goods("树梅",6,100),new Goods("莓子",7,100),new Goods("石榴",8,100),new Goods("蜜桃",9,100), };publicdecimal Total { get; set; } = 0; [KernelFunction] [Description("按照水果名称(Name)查询水果")]publicstringGetGoodsByName([Description("水果名称")] string name) {return GoodsList.FirstOrDefault(g => g.Name == name)?.ToString() ?? "未找到水果"; } [KernelFunction] [Description("查询单价(Price)少于等于参数的所有水果")]publicstringGetGoodsLessEqualsPrice([Description("水果单价")] decimal price) {var goodses = GoodsList.Where(g => g.Price <= price);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [Description("查询单价(Price)少于参数的所有水果")]publicstringGetGoodsLessPrice([Description("水果单价")] decimal price) {var goodses = GoodsList.Where(g => g.Price < price);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [KernelFunction] [Description("查询单价(Price)大于等于参数的所有水果")]publicstringGetGoodsGreaterEqualsPrice([Description("水果单价")] decimal price) {var goodses = GoodsList.Where(g => g.Price >= price);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [KernelFunction] [Description("查询单价(Price)大于参数的所有水果")]publicstringGetGoodsGreaterPrice([Description("水果单价")] decimal price) {var goodses = GoodsList.Where(g => g.Price > price);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [KernelFunction] [Description("查询库存数量(Quantity)大于等于参数的所有水果")]publicstringGetGoodsGreaterEqualsQuantity([Description("水果库存数量")] int quantity) {var goodses = GoodsList.Where(g => g.Quantity >= quantity);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [KernelFunction] [Description("查询库存数量(Quantity)大于参数的所有水果")]publicstringGetGoodsGreaterQuantity([Description("水果库存数量")] int quantity) {var goodses = GoodsList.Where(g => g.Quantity > quantity);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [KernelFunction] [Description("查询库存数量(Quantity)少于等于参数的所有水果")]publicstringGetGoodsLessEqualsQuantity([Description("水果数量")] int quantity) {var goodses = GoodsList.Where(g => g.Quantity <= quantity);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [KernelFunction] [Description("查询库存数量(Quantity)少于参数的所有水果")]publicstringGetGoodsLessQuantity([Description("水果数量")] int quantity) {var goodses = GoodsList.Where(g => g.Quantity < quantity);if (goodses == null || goodses.Any() == false) {return"未找到水果"; }else {returnstring.Join("\n", goodses); } } [KernelFunction] [Description("购买水果")]publicstringBuyGoods([Description("水果名称")] string name, [Description("购买数量")] int quantity) {var goods = GoodsList.FirstOrDefault(g => g.Name == name);if (goods != null) {var newQuantity = goods.Quantity - quantity;if (newQuantity < 0) {return"库存不足"; }else { goods.Quantity = newQuantity; goods.BuyQuantity += quantity; Total += goods.Price * quantity;return"购买成功!"; } }else {return"未找到水果"; } } }public classGoods {publicGoods(string name, decimal price, int quantity) { Name = name; Price = price; Quantity = quantity; }publicstring Name { get; set; }publicdecimal Price { get; set; }publicint Quantity { get; set; }publicint BuyQuantity { get; set; } = 0;publicoverridestringToString() {return$"名称(Name):{Name},单价(Price):{Price},库存数量(Quantity):{Quantity},销售数量(BuyQuantity):{BuyQuantity}"; } }}

    一次简单的购买记录结果如下: