C# Semantic Kernel 讓 AI 執行本機程式碼(Azure OpenAI)

在之前有介紹 C# Semantic Kernel, 那時候是呼叫 Hugging Face 的免費 LLM 。
現在想要讓 Semantic Kernel 能夠執行本機的程式碼就需要使用到更聰明的模型,例如 GPT4, GPT-4o 等。而目前這些都是要付費的。筆者這裡使用 Azure OpenAI 做示範,實際使用時需要自備金鑰。

假設在 Azure 上啟用 Azure OpenAI 服務後,可以點選「金鑰與端點」,就可以複製到等等要使用的金鑰和端點:

模型名稱則是在 Azure AI 的 Azure OpenAI Studio 的「部署」中

本次會示範讓 GPT-4o 讀取文字輸入,執行 UserService 提供的方法,這個是會使用到的 User 類別,我們使用 Description Attribute 讓 AI 能夠了解欄位定義:
    
using System.ComponentModel;


public class User
{
    [Description("代號")] public int Id { get; set; }
    [Description("名稱")] public string Name { get; set; }
    [Description("電話號碼")] public string? PhoneNumber { get; set; }
}

    

這裡建立一個簡單的 UserService,有一個 Users 清單,裡面有一些預設的資料,有提供一些方法讓 AI 來操作。
使用 KernelFunction 定義方法名稱,因為模型主要使用 python 命名,所以建議使用 snake_case 命名方式。
使用 Description 解釋方法用途
使用 return: Description 解釋方法會回傳的內容
    
using System.ComponentModel;
using Microsoft.SemanticKernel;


public class UserService
{
    private List<User> Users { get; set; } = new List<User>();

    public UserService()
    {
        Users.Add(new User { Id = 1, Name = "小明", PhoneNumber = "0912345678" });
        Users.Add(new User { Id = 2, Name = "老王", PhoneNumber = "0987654321" });
        Users.Add(new User { Id = 3, Name = "花花", PhoneNumber = "0900000000" });
    }

    [KernelFunction("get_users")]
    [Description("取得使用者清單")]
    [return: Description("使用者清單")]
    public List<User> GetUsers()
    {
        return Users;
    }

    [KernelFunction("get_user_by_id")]
    [Description("根據代號取得使用者")]
    [return: Description("使用者; 如果找不到則回傳 null")]
    public User? GetUserById(int id)
    {
        return Users.FirstOrDefault(u => u.Id == id);
    }

    [KernelFunction("get_user_by_name")]
    [Description("根據名稱取得使用者")]
    [return: Description("使用者; 如果找不到則回傳 null")]
    public User? GetUserByName(string name)
    {
        return Users.FirstOrDefault(u => u.Name == name);
    }

    [KernelFunction("update_user")]
    [Description("更新使用者")]
    [return: Description("更新後的使用者; 如果找不到則回傳 null")]
    public User? UpdateUser(User user)
    {
        var userToUpdate = Users.FirstOrDefault(u => u.Id == user.Id);
        if (userToUpdate == null)
        {
            return null;
        }

        userToUpdate.Name = user.Name;
        userToUpdate.PhoneNumber = user.PhoneNumber;
        return userToUpdate;
    }
}
    

本次是使用 Azure OpenAI ,安裝套件:
    
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
    

最主要就是使用 Plugins.AddFromObject 讓 AI 知道可以呼叫這個方法,並且在 executionSettings 設定中讓 AI 可以自動呼叫:
    
string modelId = "gpt4o"; // 圖二的部屬名稱
string endpoint = "https://aaa.openai.azure.com/"; // 圖一的圈圈 3
string apiKey = "ccc"; // 圖一的圈圈 2


var builder = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);
builder.Services.AddLogging(services => services.AddConsole().SetMinimumLevel(LogLevel.Trace));

Kernel kernel = builder.Build();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

kernel.Plugins.AddFromObject(new UserService());

var history = new ChatHistory();

// 傳入訊息
history.AddUserMessage("我是老王,我的電話號碼寫錯了,應該是 0911223344");

var result = await chatCompletionService.GetChatMessageContentAsync(
    history,
    executionSettings: new OpenAIPromptExecutionSettings()
    {
        ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
    },
    kernel: kernel);

Console.WriteLine("AI 回應:" + result);
    

註:如果沒有安裝 Log 服務則要增加下面兩個套件:
    
dotnet add package Microsoft.Extensions.Logging
dotnet add package Microsoft.Extensions.Logging.Console
    

執行

我們傳入的內容如下:
    
我是老王,我的電話號碼寫錯了,應該是 0911223344
    

GPT-4o 自動的執行了以下動作:
呼叫 GetUserByName 取得使用者物件
更新使用者物件
回傳訊息:「老王,你的電話號碼已經更新為 **0911223344** 了。」
    
trce: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      ChatHistory: [{"Role":{"Label":"user"},"Items":[{"$type":"TextContent","Text":"\u6211\u662F\u8001\u738B\uFF0C\u6211\u7684\u96FB\u8A71\u865F\u78BC\u5BEB\u932F\u4E86\uFF0C\u61C9\u8A72\u662F 0911223344"}]}], Settings: {"temperature":1,"top_p":1,"presence_penalty":0,"frequency_penalty":0,"max_tokens":null,"stop_sequences":null,"results_per_prompt":1,"seed":null,"response_format":null,"chat_system_prompt":null,"token_selection_biases":null,"ToolCallBehavior":{"ToolCallResultSerializerOptions":null},"User":null,"logprobs":null,"top_logprobs":null,"service_id":null,"model_id":null}
info: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      Prompt tokens: 175. Completion tokens: 19. Total tokens: 194.
dbug: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      Tool requests: 1
trce: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      Function call requests: UserService-get_user_by_name({"name":"老王"})
info: get_user_by_name[0]
      Function get_user_by_name invoking.
trce: get_user_by_name[0]
      Function arguments: {"name":"老王"}
info: get_user_by_name[0]
      Function get_user_by_name succeeded.
trce: get_user_by_name[0]
      Function result: {"Id":2,"Name":"老王","PhoneNumber":"0987654321"}
info: get_user_by_name[0]
      Function completed. Duration: 0.0070164s
info: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      Prompt tokens: 229. Completion tokens: 26. Total tokens: 255.
dbug: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      Tool requests: 1
trce: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      Function call requests: UserService-update_user({"user":{"Id":2,"PhoneNumber":"0911223344"}})
info: update_user[0]
      Function update_user invoking.
trce: update_user[0]
      Function arguments: {"user":"{\"Id\":2,\"PhoneNumber\":\"0911223344\"}"}
info: update_user[0]
      Function update_user succeeded.
trce: update_user[0]
      Function result: {"Id":2,"Name":null,"PhoneNumber":"0911223344"}
info: update_user[0]
      Function completed. Duration: 0.0043001s
info: Microsoft.SemanticKernel.Connectors.OpenAI.AzureOpenAIChatCompletionService[0]
      Prompt tokens: 282. Completion tokens: 21. Total tokens: 303.
AI 回應: 老王,你的電話號碼已經更新為 **0911223344** 了。
    

看起來,要讓程式能夠被 AI 執行只要加上方法名稱(KernelFunction)和描述(Description)就可以了!

不過讓 AI 使用的方法要記得限制筆數,不然錢包會空的很快...

留言