使用SK與Azure AI Search的 RAG 實作

Ian Chen
playtech
Published in
7 min readMar 7, 2024

--

上一篇 Azure AI Search RAG 實作,使用Kernel Memory來實現RAG,這是基於如果你想打造的是僅僅是專用知識庫的LLM應用,不附帶其它的功能性,反之如果你想實現的是除了知識庫之外,還有其它額外功能性的LLM應用,那麼使用Semantic Kernel會比較好整合。

整個RAG概念是不變的,同樣的也是使用 Azure AI Search 做為向量儲存庫。當使用Semantic Kernel來實現RAG時,仍然是需要Kernel Memory的協助,用於進行向量的處理,此時Kernel Memory對Semantic Kernel來說就會是屬於Plugin的角色。以上一篇相同的範例,我們改用Semantic Kernel來改寫,整個程式碼仍然只有60行不到,相當精簡。

Step 1 : 安裝以下三個套件
Microsoft.KernelMemory.Core 0.30.240227.1
Microsoft.KernelMemory.SemanticKernelPlugin 0.30.240227.1
Microsoft.SemanticKernel 1.4.0

Step 2 : Semantic Kernel的使用一切以Kernel為主,包含配置要連接的LLM模型,這個案例中,我採用的是Azure OpenAI GPT-4模型。

var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: deploy_Name, // Azure OpenAI Deployment Name
endpoint: aoai_Endpoint, // Azure OpenAI Endpoint
apiKey: api_Key // Azure OpenAI Key
).Build();

Step 3 : 建立KernelMemory物件用於向量的處理,因此會使用到Embedding以及TextGeneration模型。另外由於本例採用的是Azure AI Search做為向量儲存庫,因此也需要加入連接Azure AI Search。(註:Kernel Memory實作了許多向量儲存庫的連接)

private static IKernelMemory GetMemoryConnector()
{
return new KernelMemoryBuilder()
.WithAzureOpenAITextEmbeddingGeneration(new AzureOpenAIConfig
{
APIKey = api_Key,
Deployment = deploy_embedding_Name,
Endpoint = aoai_Endpoint,
APIType = AzureOpenAIConfig.APITypes.ChatCompletion,
Auth = AzureOpenAIConfig.AuthTypes.APIKey
})
.WithAzureOpenAITextGeneration(new AzureOpenAIConfig
{
APIKey = api_Key,
Deployment = deploy_Name,
Endpoint = aoai_Endpoint,
APIType = AzureOpenAIConfig.APITypes.ChatCompletion,
Auth = AzureOpenAIConfig.AuthTypes.APIKey
})
.WithAzureAISearchMemoryDb(search_Endpoint, search_ApiKey)
.Build<MemoryServerless>();
}

Step 4 : Semantic Kernel的Kernel物件掛載 MemoryPlugin。
defaultIndex:用於指定向量索引的名稱,預設值為’default’
memory:指定Semantic Kernel Plugin名稱

var memoryConnector = GetMemoryConnector();
var memoryPlugin = kernel.ImportPluginFromObject(new MemoryPlugin(memoryConnector, defaultIndex: "LawSK", waitForIngestionToComplete: true), "memory");

Step 5 : 知識庫文件轉向量儲存。(註:實務上向量資料的維運是獨立的專案,不會與LLM應用綁一起,本例為了簡化,所以一併處理)

//Load pdf km file to memory (只需要run一次,之後就可以註解掉了,因為已經將pdf檔案轉向量至 azure ai search裡面了,實務上知識庫的維運會是獨立的專案,不會與LLM應用混合)
await memoryConnector.ImportDocumentAsync(filePath: Path.Combine(Directory.GetCurrentDirectory(), pdfFilename), documentId: "Law001", index: "LawSK");

Step 6: 建立Semantic Function。
這個步驟是實現RAG運作的核心,使用few shot提示工程技法,制做Prompt Template,組合原提問及經由向量資料庫相似搜尋得到的參考資料,做為後續生成回答的依據。並且利用If the reference answer is empty, please say “I don’t know.”來限制LLM模型不要自行腦補而產生幻覺回答。
另外{{memory.ask $message}}語法則是呼叫MemoryPlugin,實現向量資料庫相似搜尋,在本例中即為Azure AI Search。

var skPrompt = """
question: {{$message}}

reference answer : {{memory.ask $message}}

If the reference answer is empty, please say "I don't know." 否則請依據參考答案回答,回答內容請簡化,以不超過100個字為原則,並且不得丟失參考答案的原意。
""";
var skFun = kernel.CreateFunctionFromPrompt(skPrompt);

Step 7: 進行對話,經由Semantic Function觸發整個對話開始,經由Prompt Template再自動呼叫MemoryPlugin,最終取回生成後的回應。

while (true)
{
//eg : 一定要成立管委會嗎?
Console.Write("Input Question: ");
var question = Console.ReadLine();
var answer = await kernel.InvokeAsync(skFun, arguments: new() { { "message", question } });

Console.WriteLine($"Answer: {answer} \n\n");
}

在這個範例中,可以看到藉由Semantic Kernel,以一個Semantic Function制作Prompt Template,並且在Prompt Template能自動呼叫向量資料庫搜尋(MemoryPlugin),這就是Semantic Kernel的能力,它簡化封裝了很多技術細節的實作,提供開發者很便利的開發方式。

2024/04我與另二位作者即將發行一本新著作,在這本著作中會有專章的Semantic Kernel教學。有興趣的朋友可以訂閱這個blog,屆時出版時將同步在這個平台發佈訊息。

--

--

Ian Chen
playtech

Microsoft MVP / Microsoft Certified Trainer(MCT)