Plugins在SK中是一个神奇的功能。我们说大语言模型具有不确定性,我们的代码是确定性的,而Plugins有把这种不确定性转成确定性能功能。
下面的例子是一个通过自然语言实现购买的案例,客户可以通过文字或语音来输入自然语言,
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" Version="1.7.1" />
<PackageReference Include="NAudio" Version="2.2.1" />
</ItemGroup>
下面是具体代码,核心是在定义准确性的方法时,通过KernelFunction特性和Description特性来让SK知道这是一个Plugins,并且用Description的描述来映射自然语言的语义,包括提取参数,具体代友好如下:
#pragma warning disable SKEXP0001
using 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;
namespace LittleHelper
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
StoreSystem _store;
Kernel _kernel;
IChatCompletionService _chatCompletionService;
OpenAIPromptExecutionSettings _openAIPromptExecutionSettings;
ChatHistory _history;
string _key;
WaveOutEvent _player;
private void MainForm_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();
}
private void SubmitButton_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);
}
void ReloadGrid()
{
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());
//读取音频文件
using var reader = new AudioFileReader(audioFilePath);
_player.Init(reader);
_player.Play();
}
WaveInEvent waveIn;
bool audioMark = true;
string outputFilePath = "audio.wav";
WaveFileWriter writer;
private void SpeekBut_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;
}
private async 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;
}
}
private void GoodsGrid_Leave(object sender, EventArgs e)
{
GridClearSelect();
}
void GridClearSelect()
{
foreach (DataGridViewRow row in GoodsGrid.Rows)
{
row.Selected = false;
foreach (DataGridViewCell cell in row.Cells)
{
cell.Selected = false;
}
}
}
}
public class StoreSystem
{
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),
};
public decimal Total { get; set; } = 0;
[KernelFunction]
[Description("按照水果名称(Name)查询水果")]
public string GetGoodsByName([Description("水果名称")] string name)
{
return GoodsList.FirstOrDefault(g => g.Name == name)?.ToString() ?? "未找到水果";
}
[KernelFunction]
[Description("查询单价(Price)少于等于参数的所有水果")]
public string GetGoodsLessEqualsPrice([Description("水果单价")] decimal price)
{
var goodses = GoodsList.Where(g => g.Price <= price);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[Description("查询单价(Price)少于参数的所有水果")]
public string GetGoodsLessPrice([Description("水果单价")] decimal price)
{
var goodses = GoodsList.Where(g => g.Price < price);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[KernelFunction]
[Description("查询单价(Price)大于等于参数的所有水果")]
public string GetGoodsGreaterEqualsPrice([Description("水果单价")] decimal price)
{
var goodses = GoodsList.Where(g => g.Price >= price);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[KernelFunction]
[Description("查询单价(Price)大于参数的所有水果")]
public string GetGoodsGreaterPrice([Description("水果单价")] decimal price)
{
var goodses = GoodsList.Where(g => g.Price > price);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[KernelFunction]
[Description("查询库存数量(Quantity)大于等于参数的所有水果")]
public string GetGoodsGreaterEqualsQuantity([Description("水果库存数量")] int quantity)
{
var goodses = GoodsList.Where(g => g.Quantity >= quantity);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[KernelFunction]
[Description("查询库存数量(Quantity)大于参数的所有水果")]
public string GetGoodsGreaterQuantity([Description("水果库存数量")] int quantity)
{
var goodses = GoodsList.Where(g => g.Quantity > quantity);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[KernelFunction]
[Description("查询库存数量(Quantity)少于等于参数的所有水果")]
public string GetGoodsLessEqualsQuantity([Description("水果数量")] int quantity)
{
var goodses = GoodsList.Where(g => g.Quantity <= quantity);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[KernelFunction]
[Description("查询库存数量(Quantity)少于参数的所有水果")]
public string GetGoodsLessQuantity([Description("水果数量")] int quantity)
{
var goodses = GoodsList.Where(g => g.Quantity < quantity);
if (goodses == null || goodses.Any() == false)
{
return "未找到水果";
}
else
{
return string.Join("\n", goodses);
}
}
[KernelFunction]
[Description("购买水果")]
public string BuyGoods([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 class Goods
{
public Goods(string name, decimal price, int quantity)
{
Name = name;
Price = price;
Quantity = quantity;
}
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public int BuyQuantity { get; set; } = 0;
public override string ToString()
{
return $"名称(Name):{Name},单价(Price):{Price},库存数量(Quantity):{Quantity},销售数量(BuyQuantity):{BuyQuantity}";
}
}
}
一次简单的购买记录结果如下: