LoginSignup
0
0

SemanticKernelのChatについて

Posted at

昨年、SemanticKernelに関する数篇の記事を書いたが、正式リリースバージョンは以前のバージョンと大きく異なるため、東京で開催された「生成型AIアプリケーション開発」イベントでのデモを一つずつ共有し、SemanticKernelシステムを再開することにした。

以下はChatの例で、ユーザーが質問をすると、ローカルに対応する固定データがあれば、直接返答し、なければ時間帯を問い合わせてモックデータを返す。一部はローカルデータをベクトル化して保存し、ユーザーが質問したときにベクトル化された結果を検索し、最も高いスコアの記録を返す。二部はGPTのAPIを呼び出し、質問から時間帯を見つけ出して、モックデータを返す。

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));

// ローカルデータをベクトル化して保存
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();

// ユーザー質問をベクトル化ストレージから検索し、関連するコンテンツを見つける
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でモック
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();
        // モックデータを組み立てる
        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("思考中……");
            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に渡して、より友好的に加工してから返すことだ。自分のアプリケーションとの組み合わせについては、後の記事で紹介する。

(Translated by GPT)

元のリンク:https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247487943&idx=1&sn=23f893d856fab6380ddd7d6f4c8b7bf7&chksm=9f004eeda877c7fbce952f9e8b11fc5bce26a91dc31db0a51d4f31158224fe260ad43c574f75&token=1672532928&lang=zh_CN#rd

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0