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 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;
namespaceLittleHelper
{
publicpartial 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}";
}
}
}
一次简单的购买记录结果如下: