在Semantic Kernel裡監管Prompt Engineering與Plugins

Ian Chen
playtech
Published in
7 min readJun 2, 2024

--

隨著大型語言模型(LLMs)的廣泛應用,生成式AI的強大能力被逐漸發掘。然而,生成式AI在帶來便利的同時,也可能產生不正確的生成結果,甚至帶來潛在的危害。因此,如何在開發過程中有效監控和管理這些生成過程,將成為開發者面臨的一大挑戰。

Semantic Kernel SDK提供了一個 Filters 的機制,能夠在Prompt Engineering 和 Plugins 的過程中進行監控和管理,進而能在需要時進行適當的介入,避免不正確的生成結果。

極速 ChatGPT 開發者兵器指南:跨界整合 Prompt Flow、LangChain 與 Semantic Kernel 框架 一書,我在5–5節有提到如何使用 Filters 觀測 Prompt Engineering 與 Token 耗用(5–5節Demo4範例),而在 Semantic Kernel 最新版本裡 Filters 做了些變化,本篇就來說明新版 Filters 如何使用。

  1. 首先是介面名稱的調整(Semantic Kernel 1.13.0 版)
    IFunctionFilter -> IFunctionInvocationFilter
    IPromptFilter -> IPromptRenderFilter
  2. FunctionFilter 方法名稱的調整
    由原本的二個獨立分開的方法 OnFunctionInvoking OnFunctionInvoked 改為 OnFunctionInvocationAsync 非同步方法,以await next(context) 做為區分 FunctionInvok 的前後,在這個方法裡,我們可以實現 Plugins的觀測,如果有寫過.NET Web API 或 MVC 的開發者看到這個方式應該不陌生。而整合成單一個 OnFunctionInvocationAsync 方法可以輕易的使用共用變數,對於要前後連貫性的觀測也更加方便,例如:觀測特定 Plugin 的執行效能時間等,或是過濾不正確的執行結果等。
class MyFunctionFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
{
// before function invocation
Console.WriteLine($"\n\n ========= before function invocation ===================");
Console.WriteLine($"Invoking: {context.Function.Name} \n\n");
// Console.WriteLine($"FunctionInvokingContext Arguments: {JsonConvert.SerializeObject(context.Arguments)}");
// Console.WriteLine($"FunctionInvokingContext Metadata: {JsonConvert.SerializeObject(context.Metadata)}");

await next(context);

// after function invocation
Console.WriteLine($"\n\n ========= after function invocation ===================");
var metadata = context.Result.Metadata;
if (metadata is not null && metadata.ContainsKey("Usage"))
{
Console.WriteLine($"Token usage: {JsonConvert.SerializeObject(metadata["Usage"])} \n\n");
}
}
}

3. PromptRenderFilter 方法名稱的調整
與 FunctionFilter 方法一樣,由原本的二個獨立分開的方法 OnPromptRendering OnPromptRendered 改為 OnPromptRenderAsync非同步方法,以await next(context) 做為區分 PromptRender 的前後,在這個方法裡,我們可以實現 Prompt 的觀測,例如:在RAG應用中,可以實現特定生成內容的過濾,或是生成內容的檢核。

class MyPromptFilter : IPromptRenderFilter
{
public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
{
Console.WriteLine($"\n\n ========= Rendering prompt ===================");
Console.WriteLine($"Function Name : {context.Function.Name} \n\n");
Console.WriteLine(JsonConvert.SerializeObject(context.Arguments));

await next(context);

Console.WriteLine($"\n\n ========= Rendered prompt ===================");
Console.WriteLine(JsonConvert.SerializeObject(context.RenderedPrompt));
}

}

4. 撰寫好 Filters 之後,只需要在 Kernel 物件中進行 PromptRenderFilters 與 FunctionInvocationFilters 配置即可

kernel.PromptRenderFilters.Add(new MyPromptFilter());
kernel.FunctionInvocationFilters.Add(new MyFunctionFilter());

常有人問我使用直接使用 Model 的 API 與 Semantic Kernel 有什麼差別 ? 我想這就是一個很好的情境,使用 API 你必須自行打造這樣的監控機制,而使用 Semantic Kernel 這一切都幫你設計好彈性,提供了統一的設計模式和工具,此外開發者可以方便的管理和調用多個LLM模型以及相關的Plugins。SDK提供了統一的接口和豐富的開發工具,在不同模型之間的切換和整合上變得相對簡單,也不用花太多時間研究不同模型或平台服務的API。相較之下,API通常只提供固定的功能調用,較難以根據特定需求進行靈活調整和擴展。當然使用 Semantic Kernel 也相對會受限於框架版本的快速更新,這部份就由開發者自行評估囉。

完整範例程式碼,同樣發佈在 github上,有需要的朋友們可以自行參考。歡迎斗內一下我們的新書,給還願意出版繁體中文技術書籍的作者們一點動力 :)

github : 完整範例程式碼

書籍銷售 : 極速 ChatGPT 開發者兵器指南:跨界整合 Prompt Flow、LangChain 與 Semantic Kernel 框架

--

--

Ian Chen
playtech

Microsoft MVP / Microsoft Certified Trainer(MCT)