在聊到 AI Agent 的核心能力時,「Function Calling(功能調用)」幾乎是繞不開的一塊。這個機制讓大型語言模型(LLM)不再只是個文字生成器,而是能夠真正「做事」的助手。簡單來說,Function Calling 讓開發者可以指引模型:當使用者問了某些特定問題,或提出某些需求時,你不用自己編故事,而是可以「去問問外部的 API、查查資料庫、甚至執行一段真正的程式碼」。這樣一來,模型的回應就能結合即時、準確的資訊,甚至真的去幫你完成某些任務。
舉個例子,當你對 AI 說:「幫我查一下今天台北的天氣」,模型不是「憑空猜測」一個答案,而是會知道自己要完成這件任務時,應該需要呼叫某個天氣 API,把實際資料撈回來,再轉換成一段自然語言的回答給你。這不只是讓回答變得更精準,也讓整個互動邏輯更像是一個具備「行動力」的智能助理。
但問題是模型不是真的會執行程式碼,它只是「知道」什麼時候會需要呼叫函式,實際上模型會「產生」一段結構化的輸出(通常是 JSON 格式),告訴系統:「嘿,我需要呼叫這個函式,這是我需要的參數」,接著系統會負責把這個呼叫送出去,拿回結果,再回饋給模型,讓它能夠基於最新的資訊來生成最終的回答。
所以 Function Calling 的背後其實是一個清楚的流程:
在 Semantic Kernel(SK)這個框架裡,Function Calling 的整合也變得相對簡單。SK 幫你處理好了很多底層的溝通細節,讓你只要定義好哪些函數要提供給模型用、怎麼描述這些函數,模型就能自己決定什麼時候該用哪一個,並且你也不用擔心怎麼實作呼叫函式,怎麼把結果再回饋給模型,這些都由 SK 來幫你處理。
所以在這一篇內容中,讓我們來理解 Function Calling 的原理與實作,先講清楚「為什麼模型需要呼叫外部函式」與「呼叫過程中誰做什麼、資料如何流動」,然後再來看看如何利用 Semantic Kernel(SK)來裝配 Function Calling 能力。
首先聚焦兩個問題:為什麼需要外部函式、誰做什麼與資料如何流動。
我們得先搞清楚一件事:LLM 模型本身只看得到你丟給它的提示詞和上下文,無法主動存取即時資料(像是天氣、庫存、內部系統狀態),也不能直接執行操作(例如寫入資料庫、發送訂單)。所以這就需要一個「中介層」來讓模型有行動力,而這個中介層的關鍵就是所謂的「外部函式(Function)」。
這些外部函式可以是查天氣、訂機票、查庫存、下訂單等等的功能。以 OpenAI 的 GPT 模型為例,它的 Function Calling 並不是模型本身會寫程式碼或操作 API,而是經過微調後,模型能夠辨識何時需要額外工具的協助,然後自動產出一段結構化的 JSON,其中包含要調用的函式名稱、所需的參數和對應的說明。這時候中介層就會出場,它負責接收這個 JSON、執行實際的函式呼叫,並把結果回傳給模型。最後模型再根據這些新資訊,生成一段更貼近現實、更具參考價值的回應。
所以,Function Calling 本質上就是透過提示工程(Prompt Engineering)和模型微調,賦予 LLM 延伸能力,讓它能夠跨出自己的知識框架,跟外部世界互動。不只可以幫助模型取得最新資料,也能處理複雜計算、甚至動手操作系統。
可以分解為以下五個核心步驟
{
"role": "assistant",
"content": null,
"function_call": {
"name": "query_delivery_date",
"arguments": "{ \"order_id\": \"168888\" }"
}
}
最後,LLM 會基於這次收到的完整上下文(包含函數的輸出),生成一個最終的、通順的自然語言回覆給用戶,例如「您訂單 order_168888 的預計配送日期是 2025 年 9 月 10 日。」。
在 Semantic Kernel 中,以上 Function Calling 流程會由「定義 Plugins → 註冊到 Kernel → 提供工具描述給模型 → 攔截並執行 function_call → 回饋結果 → 控制終止條件與處理」構成完整的循環。
接下來,我們將逐步實作在 Semantic Kernel 中啟用 Function Calling 的過程。假設我們要打造一個訂單助理,能根據使用者輸入的「訂單編號」,先查詢訂單狀況,再回覆給使用者。
定義 Plugins
我們需要定義一個訂單服務的Plugin,讓模型能夠調用它來處理有關訂單的功能服務,例如獲取訂單資訊。簡單來說,我們會撰寫一個 C# 類別,並提供相關的方法,而方法中會使用 SK 的標註(Attribute)來描述這個函式的用途、參數。這些 Attribute 將會被用來生成對應的 API 文件說明,並幫助模型理解如何正確調用這些函式,是相當重要且關鍵的部分,會影響到整個 Function Calling 的正確性。
public class OrderService
{
// 模擬的訂單資料(訂單編號 -> 狀態)
private readonly Dictionary<string, string> _orders = new Dictionary<string, string>
{
{ "A001", "已出貨" },
{ "A002", "處理中" },
{ "A003", "已取消" },
{ "A004", "已完成" }
};
// 查詢訂單狀態的方法
[KernelFunction]
[Description("Retrieves the order status by order ID.")]
public string GetOrderStatus(
[Description("The ID of the order to retrieve the status for.")]
string orderId)
{
if (string.IsNullOrWhiteSpace(orderId))
{
return "訂單編號不可為空";
}
if (_orders.TryGetValue(orderId, out var status))
{
return status;
}
else
{
return "查無此訂單";
}
}
}
註冊 Plugins 到 Kernel
將定義好的 Plugin 註冊到 Kernel 中,讓模型能夠識別並在需要時能夠調用它。
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.AddOpenAIChatCompletion(
apiKey: Config.OpenAI_ApiKey,
modelId: Config.ModelId).Build();
kernelBuilder.Services.AddSingleton<IFunctionInvocationFilter, WatchFunctionFilter>();
var kernel = kernelBuilder.Build();
kernel.Plugins.AddFromType<OrderService>();
設定 Kernel 啟用 Function 自動調用
在 Kernel 中啟用 Function 自動調用,讓模型能夠在需要時自動選擇 Plugin 並調用相應的 Function。
OpenAIPromptExecutionSettings settings =
new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
設計系統提示與使用規則(Prompt)
另外也可以在系統提示內撰寫指引,引導模型在需要時使用 Plugin,而非臆測。例如:
ChatHistory history = new();
string devPrompt = $"""
你是一個訂單智能助手,能夠根據使用者的需求調用相應的功能。請遵循以下規則:
1. 當你需要獲取訂單狀態時,請使用 `GetOrderStatus` 函數。
2. 當你需要額外的上下文資訊時,請主動詢問使用者。
3. 請始終使用繁體中文回答問題。
4. 嚴禁提供訂單服務以外的功能。
5. 所有回覆必須基於已知的訂單資訊,並不得臆測或編造內容。
""";
history.AddDeveloperMessage(devPrompt);
// 從 kernel 取得聊天服務
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
進行對話
// 開始聊天對話
Console.Write("User > ");
string? userInput;
while ((userInput = Console.ReadLine()) is not null)
{
if (string.IsNullOrWhiteSpace(userInput) || userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
break;
// 加入使用者訊息到對話歷史
history.AddUserMessage(userInput);
// 以串流方式獲取 AI 回應
var result = chatCompletionService.GetStreamingChatMessageContentsAsync(
history,
settings,
kernel: kernel);
// Stream the results
string fullMessage = "";
var first = true;
await foreach (var content in result)
{
if (content.Role.HasValue && first)
{
Console.Write("Assistant > ");
first = false;
}
Console.Write(content.Content);
fullMessage += content.Content;
}
Console.WriteLine();
// 加入 AI 回應到對話歷史
history.AddAssistantMessage(fullMessage);
// Get user input again
Console.Write("User > ");
}
Console.WriteLine("\n Bye!");
AI 回應結果
User > 我要問訂單目前處理的如何了
Assistant > 請提供您的訂單編號,以便我為您查詢訂單的最新處理狀態。
User > A003
Assistant > 您的訂單編號 A003 目前的狀態為「已取消」。若您需要進一步協助,請告知!
User > 說個笑話
Assistant > 很抱歉,我只能協助您查詢與訂單相關的服務。如需查詢訂單狀態或有其他訂單問題,歡迎隨時告訴我!
從上面的實作過程中,我們可以看到 Function Calling 主要依賴於幾個關鍵點:函式的正確定義與描述、模型的決策能力、以及系統提示的設計。這些環節都可能導致整個流程無法正確呼叫外部函式,進而影響最終的生成結果。因此,在實際應用中,我們需要特別注意這些潛在的問題,並採取相應的解決策略:
Function Calling 是將靜態的大型語言模型轉變為動態 AI Agent 的關鍵技術。透過這個機制,我們的 AI 系統不再只能基於訓練資料回答問題,而能夠真正與外部世界互動,取得即時資訊並執行實際操作。
在本篇的實作中,我們看到了 Semantic Kernel 如何簡化 Function Calling 的複雜度,讓開發者能夠專注於業務邏輯的實作,而不需要處理底層的模型溝通細節。從定義 Plugin、註冊到 Kernel、設計系統提示,到最終的對話執行,每個步驟都有其重要性,而任何一個環節的疏忽都可能影響整體的表現。
更重要的是,也提出了一些實作上常見的問題與解決策略。在實際的產品開發中,這些「細節」會決定 AI Agent 的實用性與它的可靠性。往後還會探討更多進階的主題,看看如何讓 AI Agent 具備更複雜的推理能力或是多步驟任務執行能力。Function Calling 只是第一步,真正的挑戰在於如何讓 AI 在複雜的任務場景中保持穩定與可控。