在前面幾天的內容中,探討了 Function Calling 的基本概念,也實作了簡單的 Plugin 來讓 AI Agent 具備調用工具的能力。但隨著業務需求的複雜化,很快就會遇到一個問題:當 Plugin 的參數變得複雜時,Semantic Kernel 還能處理得好嗎?
例如:你需要設計一個人力資源管理的 AI Agent,它不只要能查詢單一員工資訊,還要能根據「部門、職級、技能組合、工作經驗年數、是否遠端工作」等多個維度進行複雜的員工搜尋。這時候,傳統的 string employee_name
這種簡單參數就不夠用了。今天就來深入探討 Semantic Kernel 對於複雜參數的支援能力,從最基本的原始型別開始,一路進展到自定義物件、陣列進階參數類型。
讓我們先回顧一下 Semantic Kernel 對基本參數類型的支援。在之前的訂單查詢範例中,我們使用了最簡單的參數定義:
[KernelFunction]
[Description("查詢訂單狀態")]
public string Get_Order_Status(
[Description("訂單編號")] string order_id)
{
// 實作邏輯
}
這種寫法清楚明瞭,AI 模型能夠輕易理解需要一個字串參數,並且知道這個參數代表什麼,但當我們的業務邏輯變得複雜時,這些基本型別就顯得力不從心了,我想應該很少開發者會喜歡有 10 個參數的函式。
假設我們要建立一個人力資源管理的 Plugin,需要支援以下這種複雜的查詢需求:
「幫我找出工程部門或銷售部門的資深員工,必須具備程式設計和專案管理技能,工作經驗在 3-8 年之間,並且是遠端工作者。」
這個需求包含了:
如果我們試圖用基本參數來處理這個需求,函式簽名可能會變成這樣:
// 這樣的設計會讓人抓狂...
public string SearchEmployees(
string department1, string department2, string department3,
string level1, string level2,
string skill1, string skill2, string skill3,
int minExperience, int maxExperience,
bool isRemoteWorker)
不僅參數過多,而且無法彈性處理不同數量的部門或技能要求。更糟糕的是,AI 模型也很難理解這些參數之間的關聯性。
幸運的是,Semantic Kernel 支援複雜參數類型,讓我們能夠設計出更優雅、更具表達力的 Plugin API。
當我們需要處理多選情況時,陣列是最自然的選擇,這樣的設計讓我們可以靈活傳入多個部門、職級和技能,並且對經驗年數和工作模式進行篩選。AI 模型也能夠理解這些參數的意義,並且根據使用者的需求來填寫。
[KernelFunction]
[Description("根據複雜條件搜尋員工,支援多維度篩選")]
public async Task<string> SearchEmployees(
[Description("要搜尋的部門陣列")] Department[]? departments = null,
[Description("員工職級陣列")] EmployeeLevel[]? levels = null,
[Description("必備技能陣列")] SkillCategory[]? requiredSkills = null,
[Description("最少工作經驗年數")] int minExperience = 0,
[Description("最多工作經驗年數,不限制則輸入-1")] int maxExperience = -1,
[Description("是否限定遠端工作者,true=僅遠端,false=僅非遠端,空白=不限制")] string remoteOnly = "")
但是,當參數數量持續增加時,即便是陣列也會讓函式簽名變得冗長。這時候,可以考慮使用自定義物件來封裝相關參數,這種方式的好處是:
public class SearchEmployeesRequest
{
[Description("要搜尋的部門陣列")]
public Department[]? Departments { get; set; }
[Description("員工職級陣列")]
public EmployeeLevel[]? Levels { get; set; }
[Description("必備技能陣列")]
public SkillCategory[]? RequiredSkills { get; set; }
[Description("最少工作經驗年數")]
public int MinExperience { get; set; } = 0;
[Description("最多工作經驗年數,不限制則輸入-1")]
public int MaxExperience { get; set; } = -1;
[Description("是否限定遠端工作者,true=僅遠端,false=僅非遠端,空白=不限制")]
public string RemoteOnly { get; set; } = "";
}
然後函式就可以簡化為:
[KernelFunction]
[Description("根據複雜條件搜尋員工,支援多維度篩選")]
public async Task<string> SearchEmployees(
[Description("員工搜尋條件")] SearchEmployeesRequest searchRequest)
良好的參數描述是提升 AI 理解能力的關鍵。以下是一些實用的描述技巧:
// 描述過於簡潔
[Description("部門")]
public Department[]? Departments { get; set; }
// 描述清楚且具體
[Description("要搜尋的部門陣列,可多選,不指定則搜尋所有部門")]
public Department[]? Departments { get; set; }
// 沒有說明特殊值的含義
[Description("最大經驗年數")]
public int MaxExperience { get; set; }
// 說明特殊值和邊界條件
[Description("最多工作經驗年數,不限制則輸入-1")]
public int MaxExperience { get; set; } = -1;
合理的預設值可以讓 AI 更容易使用你的函式:
// 為數值參數提供合理預設值
public int MinExperience { get; set; } = 0; // 最小值通常是 0
// 為可選參數使用 null
public Department[]? Departments { get; set; } // null 表示不篩選
// 為字串參數使用空字串表示「不指定」
public string RemoteOnly { get; set; } = "";
實際測試 AI 是否能正確理解和使用這些複雜參數,非常重要,應該測試各種不同的使用者輸入,包含複雜的條件組合,以確保 AI 能夠根據描述正確填寫參數:
使用者:「幫我找工程部門的資深員工,要有程式設計技能」
AI:正確調用 SearchEmployees(departments: [Engineering], levels: [Senior], requiredSkills: [Programming])
使用者:「找 5 年以上經驗的遠端工作者」
AI:正確調用 SearchEmployees(minExperience: 5, remoteOnly: "true")
雖然複雜參數帶來了更強的表達能力,但我們也需要考慮Token 使用量影響,複雜參數會增加 Function Calling 的 token 消耗:
Semantic Kernel 對複雜參數的支援為我們打開了建構精緻 AI Agent 的大門。從基本的字串、數值參數,到進階的陣列、自定義物件,有了充分的工具設計來應對各種複雜的業務場景。
但技術能力只是基礎,真正的挑戰在於如何設計出既能被 AI 正確理解,又能滿足業務需求的 Plugin API。開發者們需要意識到,撰寫這些 function 是給 AI 使用的,不是給人類開發者使用的,所以 function 讓 AI 模型可以理解,這件事很重要。