Semantic Kernel：Function Calling


&nbsp;&nbsp;&nbsp;&nbsp;前回のFunctionにおいて、開発者が構成した順番で、セマンティックFunctionとローカルFunctionをそれぞれ呼び出す混合方式を使用しました。

&nbsp;&nbsp;&nbsp;&nbsp;実際には、Semantic Kernel（SK）は自動で組織化できます。以下で定義されているローカルFunction「GetChineseDay」を「ImportPluginFromFunctions」方式でSKのプラグインライブラリに追加します。「Call1」で「今月餅を食べるのまで何日ありますか？」と質問されると、GetChineseDayが自動的に呼び出されます。しかし、質問が「1=1=？」のように日付と無関係な場合、ローカルFunctionはトリガーされません。注意すべき設定は「var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };」です。

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System;
using System.Globalization;
using System.Text;
#pragma warning disable SKEXP0001

var chatModelId = "gpt-4o";
var key = File.ReadAllText(@"C:\GPT\key.txt");

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(chatModelId, key);
Kernel kernel = builder.Build();
kernel.ImportPluginFromFunctions("HelperFunctions",
[
    kernel.CreateFunctionFromMethod(GetChineseDay, "GetChineseDay", "返回中国的农历")
]);

await Call1();

async Task Call1()
{
    Console.WriteLine("-----------------Call1 開始---------------------");
    var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
    await foreach (var content in kernel.InvokePromptStreamingAsync("现在离吃月饼还有多少天？", new(settings)))
    {
        Console.Write(content);
    }
    Console.WriteLine();
    Console.WriteLine("-----------------Call1 終了---------------------");
}

string GetChineseDay()
{
    var chineseCalendar = new ChineseLunisolarCalendar();
    var today = DateTime.Now;
    int lunarYear = chineseCalendar.GetYear(today);
    int lunarMonth = chineseCalendar.GetMonth(today);
    int lunarDay = chineseCalendar.GetDayOfMonth(today);
    bool isLeapMonth = chineseCalendar.IsLeapMonth(lunarYear, lunarMonth);
    Console.WriteLine("-------GetChineseDay--------");
    return $"农历日期: {lunarYear}年 {(isLeapMonth ? "闰" : "")}{lunarMonth}月 {lunarDay}日";
}
Console.WriteLine();
```

### 実行結果は次のとおり：

![画像](https://mmbiz.qpic.cn/sz_mmbiz_png/4CAA63nvHRA3oBfolTuznh5GsRuhp3DlJkfvsATQaZGx6ga2OPlicxWtYrWf5MA71RFSfOibHdPqDnPURzVXoO6Q/640?wx_fmt=png&from=appmsg)

## チャットでFunction Callingを使用するには？

```csharp
await Call2();

async Task Call2()
{
    Console.WriteLine("-----------------Call2 開始---------------------");
    var settings = new OpenAIPromptExecutionSettings() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
    var chat = kernel.GetRequiredService<IChatCompletionService>();
    var chatHistory = new ChatHistory("中国农历的表示方式是：九月初三，十二月二十三，请用这种表示方式表示农历日期。");
    chatHistory.AddUserMessage("现在离吃月饼还有多少天？");
    var contentBuilder = new StringBuilder();
    await foreach (var streamingContent in chat.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel))
    {
        if (streamingContent.Content is not null)
        {
            Console.Write(streamingContent.Content);
            contentBuilder.Append(streamingContent.Content);
        }
    }
    chatHistory.AddAssistantMessage(contentBuilder.ToString());
    Console.WriteLine();
    Console.WriteLine("-----------------Call2 終了---------------------");
}
```

### 実行結果は次のとおり：

![画像](https://mmbiz.qpic.cn/sz_mmbiz_png/4CAA63nvHRA3oBfolTuznh5GsRuhp3DlwKaQnrFpdQCWXYgiaS6yHxSdGUR3I2fZ8ibicFMYCn6EtrGBV4hrD0bNQ/640?wx_fmt=png&from=appmsg)

## FunctionCallContentBuilderを用いて、呼び出されたローカルFunctionを確認するには？

```csharp
await Call3();

async Task Call3()
{
    Console.WriteLine("-----------------Call4 開始---------------------");
    IChatCompletionService chat = kernel.GetRequiredService<IChatCompletionService>();
    OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.EnableKernelFunctions };
    ChatHistory chatHistory = new();
    chatHistory.AddUserMessage("中秋节吃月饼，现在离吃月饼还有多少天？");
    while (true)
    {
        AuthorRole? authorRole = null;
        var fccBuilder = new FunctionCallContentBuilder();
        await foreach (var streamingContent in chat.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel))
        {
            if (streamingContent.Content is not null)
            {
                Console.Write(streamingContent.Content);
            }
            authorRole ??= streamingContent.Role;
            fccBuilder.Append(streamingContent);
        }
        Console.WriteLine();
        var functionCalls = fccBuilder.Build();
        if (!functionCalls.Any())
        {
            break;
        }
        var fcContent = new ChatMessageContent(role: authorRole ?? default, content: null);
        chatHistory.Add(fcContent);

        foreach (var functionCall in functionCalls)
        {
            fcContent.Items.Add(functionCall);
            var functionResult = await functionCall.InvokeAsync(kernel);
            Console.WriteLine($"FunctionName：{functionResult.FunctionName}，Return：{functionResult.InnerContent}");
            chatHistory.Add(functionResult.ToChatMessage());
        }
        Console.WriteLine();
    }
    Console.WriteLine();
    Console.WriteLine("-----------------Call4 終了---------------------");
}
```

### 実行結果：

![画像](https://mmbiz.qpic.cn/sz_mmbiz_png/4CAA63nvHRA3oBfolTuznh5GsRuhp3Dl1MqzWedPthIoBhYickrJJSxfLHVVC2PyLjhPJYdeR3oxrRI0VbGb43g/640?wx_fmt=png&from=appmsg)

(Translated by GPT)