在前幾天的文章中,我們已經學會了如何準備食材(提示詞)、認識了我們的頂級廚師(Kernel),甚至還學會了他的招牌絕活 Function Calling。今天的第 10 天,我們要來探討一個更深層的廚房哲學問題:我們的 AI 廚房,應該是全自動化,還是需要總鋪師我本人——也就是你,開發者——來手動確認每一步關鍵操作呢?
這對應到 Semantic Kernel (SK) 中一個非常重要的概念:函式調用模式 (Function Invocation Modes)。
想像一下,一間配備了最先進 AI 機器人的米其林廚房。客人點餐後,從切菜、烹煮到擺盤,所有流程一氣呵成,總鋪師只需要在旁邊優雅地喝著咖啡,監督著一切順利進行。這就是 SK 的預設行為——
自動調用 (Auto Invoke)。
當你像前幾天那樣,把 FunctionChoiceBehavior
設置為 Auto
且不做任何額外設定時,SK 就會進入這個模式。整個 Function Calling 的生命週期會由 SK 自動完成:
這個過程行雲流水,完全不需要你介入干預。對於大多數情境,例如查詢天氣、簡單的資料檢索,這種全自動模式效率最高,也是我們最推薦的起手式。
然而,有些菜色需要總鋪師的精湛手藝與親自確認。比如,當客人點了一道需要「炙燒干貝佐魚子醬」的昂貴料理時,AI 助手的建議很好,但你可能想親自檢查干貝的品質,並決定炙燒的火侯。這就是
手動調用 (Manual Invoke) 的精神。
手動調用賦予了開發者
完全的控制權。在 AI 決定要呼叫某個函式之後,流程會暫停,將「決策」交還給你。你可以決定是否、何時、以及如何執行這個函式。
這在以下場景中至關重要:
要啟用手動模式,你只需要在設定 FunctionChoiceBehavior
時,將 autoInvoke
參數設為 false
。
接下來,讓我們用一段完整的程式碼,來看看手動模式是如何運作的。我們將建立一個天氣機器人,但在它實際呼叫 WeatherForecastUtils
之前,會先在主控台印出一條訊息,模擬開發者介入確認的過程。
C#
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.Text.Json;
// 假設我們有這些 Plugin
public class WeatherForecastUtils
{
[KernelFunction, Description("Gets the weather for a city.")]
public static string GetWeatherForCity(string cityName)
{
// 假裝查詢天氣
Console.WriteLine($"[Plugin] 正在查詢 {cityName} 的天氣...");
return $"台灣 {cityName} 今天天氣晴朗,溫度 28 度。";
}
}
public class DateTimeUtils
{
[KernelFunction, Description("Gets the current time.")]
public static string GetCurrentTime() => DateTime.Now.ToString("HH:mm");
}
// --- 主程式 ---
var builder = Kernel.CreateBuilder();
// 請記得換成你自己的 Azure OpenAI 設定
builder.AddAzureOpenAIChatCompletion(
"your-deployment-name",
"your-endpoint",
"your-api-key");
builder.Plugins.AddFromType<WeatherForecastUtils>();
builder.Plugins.AddFromType<DateTimeUtils>();
var kernel = builder.Build();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// 透過設定 autoInvoke: false 來啟用手動調用
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false)
};
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage("現在台北的天氣如何?");
while (true)
{
// 1. 讓 AI 決定下一步
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, kernel);
// 2. 檢查 AI 是否只回傳了文字訊息
if (!string.IsNullOrEmpty(result.Content))
{
Console.WriteLine($"[AI Assistant] > {result.Content}");
}
// 3. 取得 AI 想要呼叫的函式
var functionCalls = FunctionCallContent.GetFunctionCalls(result).ToList();
if (functionCalls.Count == 0)
{
// 如果沒有函式要呼叫,表示對話結束
break;
}
// 4. 將 AI 的函式呼叫請求也加入歷史紀錄
chatHistory.Add(result);
// 5. 迭代所有函式呼叫並手動執行它們
foreach (var functionCall in functionCalls)
{
Console.WriteLine($"[總鋪師的確認] > AI 想要呼叫 '{functionCall.PluginName}.{functionCall.FunctionName}'。準備執行...");
// 6. 手動調用函式!
var functionResultContent = await functionCall.InvokeAsync(kernel);
// 7. 將函式執行的結果轉成 ChatMessageContent 並加入歷史紀錄
chatHistory.Add(functionResultContent.ToChatMessage());
Console.WriteLine($"[函式執行結果] > {JsonSerializer.Serialize(functionResultContent.Result)}");
}
// 8. 帶著新的結果回到迴圈,讓 AI 繼續思考
Console.WriteLine("\n--- 將結果送回 AI,進行下一輪思考 ---\n");
}
當你執行這段程式碼,你會看到類似以下的輸出:
[總鋪師的確認] > AI 想要呼叫 'WeatherForecastUtils.GetWeatherForCity'。準備執行...
[Plugin] 正在查詢 台北 的天氣...
[函式執行結果] > "台灣 台北 今天天氣晴朗,溫度 28 度。"
--- 將結果送回 AI,進行下一輪思考 ---
[AI Assistant] > 台北現在天氣晴朗,氣溫是 28 度。
看到了嗎?在真正查詢天氣之前,我們的程式印出了「總鋪師的確認」,這就是我們介入的時機點!在這個 foreach
迴圈中,你可以加入任何你需要的邏輯。
選擇哪種模式,取決於你想給予 AI 廚師多大的自主權。Semantic Kernel 的美妙之處,就在於它提供了這種彈性,讓你既能享受自動化的便利,也能在關鍵時刻親自掌勺。