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,进行更友好的加工后返回。关于自己和自己的应用结合,后面的文章会作介绍。

  文章来源微信公众号

  想要更快更方便的了解相关知识,可以关注微信公众号 

posted @   刘靖凯  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2019-02-25 第一课了解SQL
点击右上角即可分享
微信分享提示