SemanticKernel之Chat
去年写过几过几篇关于SemanticKernel的文章,由于正式发布的版本与之前的版本变化较大,加上前的东京《生成式AI应用开发》活动,想把演示的Demo逐一分享出来,的以再次开启SemanticKernel系统。
下面是一个Chat的例子,用户提问,如果本地有固定数据能对应,直接返回,如果没有,就进行查询时间段,返回mock数据。第一部分是把本地数据向量化后存储,然后在用户提问时,查询向量化的结果,找出评分最高的一条记录返回。第二部分调用GPT的API,从提问中寻找时间段,然后返回mock的数据。
引入Nuget包
<ItemGroup> <PackageReference Include="Bogus" Version="35.5.0" /> <PackageReference Include="Microsoft.SemanticKernel" Version="1.6.3" /> <PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.6.3-alpha" /> </ItemGroup>
后端CSharp代码如下:
using Bogus; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Plugins.Memory; using System.Text.Json; using System.Text.Unicode; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseStaticFiles(); const string MemoryCollectionName = "works"; var key = File.ReadAllText(@"C:\GPT\key.txt"); var chatModelId = "gpt-4-0125-preview"; #pragma warning disable SKEXP0001 #pragma warning disable SKEXP0050 #pragma warning disable SKEXP0010 //为向量化作准备 var kernel = Kernel.CreateBuilder() .AddOpenAITextEmbeddingGeneration("text-embedding-ada-002", key) .Build(); var embeddingGenerator = new OpenAITextEmbeddingGenerationService("text-embedding-ada-002", key); SemanticTextMemory memory = new(new VolatileMemoryStore(), embeddingGenerator); var memoryPlugin = kernel.ImportPluginFromObject(new TextMemoryPlugin(memory)); //利用Embedding,把本地数据向量化存储 await StoreData(); app.MapGet("/query", async (string question) => { app.Logger.LogInformation("question:{0}", question); var (message, relevance) = await SearchMemoryAsync(memory, question); if (relevance < 0.83) { var result = await QueryAsync(key, question); result.Message += "【热度:" + relevance + "】"; return result; } else { return new BackData { Message = message + "【热度:" + relevance + "】" }; } }); app.Run(); //从向量化存储中查询用户提问,找出相关的内容,注意参数limit,ninRelevanceScore等参数 async Task<(string, double)> SearchMemoryAsync(ISemanticTextMemory memory, string query) { await foreach (var answer in memory.SearchAsync( collection: MemoryCollectionName, query: "问题:" + query, limit: 1, minRelevanceScore: 0.8, withEmbeddings: true)) { return (answer.Metadata.Text, answer.Relevance); }; return ("非常抱歉,我没有找到你要的问题!", 0); } //向量化本地数据后存储 async Task StoreData() { var dic = new Dictionary<string, string> { ["info1-1"] = "我叫Bot", ["info1-2"] = "我的名字叫Bot", ["info2-1"] = "我是属于ABCDE公司的", ["info2-2"] = "我来自于ABCDE公司的", ["info3-1"] = "我的功能:交易查询,交易统计,结算查询等", ["info3-2"] = "我的主要作是:交易查询,交易统计,结算查询等", }; foreach (var (key, value) in dic) { await memory.SaveInformationAsync(MemoryCollectionName, id: key, text: value); } } //利用用户的提示信息,找出时间段的内容,然后按时间段查询,数据是用Bogus来mock的 async Task<BackData> QueryAsync(string key, string data) { var arr = await GetDatesAsync(key, data); if (arr.Length == 1) { arr = arr[0].Split("至"); } //判断有没有时间段 if (arr.Length != 2) { Console.WriteLine("非常抱歉,我没有找到你要的问题!"); return new BackData { Message = "非常抱歉,我没有找到你要的问题!" }; } else { Console.WriteLine($"正在为你查询{string.Join("到", arr)}的数据,请稍候……"); var rand = new Random(); var options = new JsonSerializerOptions(); //组装mock的数据 options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(UnicodeRanges.All); var orders = new List<Order>(); for (var i = 0; i < rand.Next(5, 10); i++) { var orderFaker = new Faker<Order>("zh_CN") .RuleFor(x => x.ID, x => x.Random.Int(1, 1000)) .RuleFor(x => x.OrderNumber, x => x.Random.Guid().ToString("N").ToUpper().Substring(0, 8)) .RuleFor(x => x.TotalAmount, x => x.Random.Int(100, 900)) .RuleFor(x => x.OrderDate, x => x.Date.Past(1)) .RuleFor(x => x.Status, x => x.PickRandom("已下单", "已发货", "已完成", "已取消")); orders.Add(orderFaker.Generate()); } return new BackData { Message = string.Join("到", arr), Data = orders }; } } //从提示中获取固定日期 async Task<string[]> GetDatesAsync(string key, string data) { var chatGPT = new OpenAIChatCompletionService(chatModelId, key); var chatHistory = new ChatHistory($"今天是{DateTime.Now}。"); chatHistory.AddUserMessage($"请注意上面给出的当前时间,然后分多行给出下面句子中的日期或时间。如果遇到今年,去年,本月等信息,转换成一个开始日期和结束日期,并且分行显示,"); chatHistory.AddUserMessage(data); var reply = await chatGPT.GetChatMessageContentAsync(chatHistory); Console.WriteLine(reply.Content); return reply.Content.Split('\r', '\n'); } public class Order { public int ID { get; set; } public string OrderNumber { get; set; } public DateTime OrderDate { get; set; } public int TotalAmount { get; set; } public string Status { get; set; } } public class BackData { public string Message { get; set; } public List<Order> Data { get; set; } }
前端代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Bot</title> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script> </head> <body> <div class="container"> <div class="row" style="margin:5px"> <div class="col"> <textarea rows="5" class="form-control" id="question"></textarea> </div> </div> <div class="row" style="margin:5px"> <div class="col-md-8"> </div> <div class="col"> </div> <div class="col"> </div> <div class="col" style="text-align:right"> <button class="btn btn-info" id="submit" onclick="answer()">发送</button> </div> </div> <div class="row" style="font-weight:900"> <span id="result"></span> </div> <div class="row" id="tablediv"> </div> </div> <script> function answer() { $('#result').css('color', 'black'); $("#result").html("思考中……"); //$("#tablediv").html("") var type = '' $.ajax({ url: '/query?question=' + $('#question').val(), type: 'GET', success: function (data) { $("#result").html(data.message); if (data.data.length == 0) { return } $('#question').val("") var table = '<table class="table" id="orderTable" border="1">' + '<thead>' + ' <tr>' + ' <th>ID</th>' + ' <th>Order Number</th>' + ' <th>Order Date</th>' + ' <th>Total Amount</th>' + ' <th>Status</th>' + ' </tr>' + ' </thead>' + ' <tbody id="tbody">' $.each(data.data, function (index, item) { var row = '<tr>' + '<td>' + item.id + '</td>' + '<td>' + item.orderNumber + '</td>' + '<td>' + item.orderDate + '</td>' + '<td>' + item.totalAmount + '</td>' + '<td>' + item.status + '</td>' + '</tr>'; table += row }); table += '</tbody></table>' $("#tablediv").html(table) }, error: function (xhr, status, error) { alter(error) } }); } </script> </body> </html>
本例只是简单的功能演示,作为SK的开胃菜,不具体商业务使用价值。更通用的做法是:从向量库里找出结果后,再传给GPT的ChatCompletion,进行更友好的加工后返回。关于自己和自己的应用结合,后面的文章会作介绍。
文章来源微信公众号
想要更快更方便的了解相关知识,可以关注微信公众号
****欢迎关注我的asp.net core系统课程****
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2010-02-25 ado.net连接池