dotnet 簡單控制檯使用 KernelMemory 向量化文字嵌入生成和查詢

lindexi發表於2024-06-16

本文將和大家簡單介紹一下如何在控制檯裡面使用 Microsoft.KernelMemory 呼叫 TextEmbedding 對一些文字知識庫內容生成向量化資訊,以及進行向量化查詢

本文屬於 SemanticKernel 入門系列部落格,更多部落格內容請參閱我的 部落格導航部落格園的合集

根據 new bing 對 Microsoft.KernelMemory 的如下介紹,可以知道 KernelMemory 的基礎功能

Microsoft.KernelMemory 是一個開源的服務和外掛,專門用於透過自定義的連續資料混合管道對資料集進行高效的索引。它的目標是模擬人類大腦如何儲存和檢索關於世界的知識。其中,嵌入(Embeddings) 是一項關鍵功能,用於建立語義對映,將概念或實體表示為高維空間中的向量。

嵌入是一種強大的工具,用於幫助軟體開發人員處理人工智慧和自然語言處理。它們透過將單詞表示為高維向量而不是簡單的字元字串,以更復雜的方式幫助計算機理解單詞的含義。嵌入通常以數值向量的形式存在,例如由數百個浮點陣列成的列表。這些向量透過將每個已知的標記(token)對映到高維空間中的一個點來工作。設計這個空間和標記詞彙表的目的是使具有相似含義的單詞位於彼此附近。這使得演算法能夠在不需要顯式規則或人工監督的情況下識別單詞之間的關係,例如同義詞或反義詞。

在開始之前,期望大家已經有了 Azure 的 AI 賬號許可權,如果現在還沒有許可權,請填寫 https://aka.ms/oai/access 進行申請。網上有很多申請攻略,如果大家不知道如何填寫的話,還請自行參考網上大佬們寫的攻略

先進入到 https://ai.azure.com/ 進行部署專案,如果大家現在沒有許可權但只是想測試一下,可以找我要借賬號給你自己測試一下

由於 AzureAI 的介面更新比較快,本文以下截圖都是在 2024.06 截圖的,如果你閱讀本文的時間距離本文編寫時間過長,那可能介面很多都會不一樣,但相信大家看介面還是會建立模型的

先點選建立部署按鈕,如下圖

接著在選擇模型這裡選擇 TextEmbedding 模型,本文這裡選擇的是 text-embedding-ada-002 模型,這是當前總體表現都很好的模型

建立模型時需要給模型命名,這個命名將在後續咱的程式碼裡面呼叫。我這裡的部署名稱使用的是 Embedding 名稱,完成部署之後的介面內容大概如下

完成以上準備工作之後,接下來可以開始新建控制檯編寫程式碼了。先新建一個 .NET 8 框架的控制檯,當然了,這個時間點你要是激進一些也可以建立 .NET 9 框架的

先按照 .NET 的慣例安裝 Microsoft.KernelMemory.Core 這個庫,安裝之後的 csproj 專案檔案的程式碼大概如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.KernelMemory.Core" Version="0.62.240605.1" />
  </ItemGroup>

</Project>

先引用名稱空間

using Microsoft.KernelMemory;

接著初始化進行一些配置,配置程式碼如下

var endpoint = "https://lindexi.openai.azure.com/"; // 請換成你的地址
var apiKey = File.ReadAllText(@"C:\lindexi\CA\Key"); // 請換成你的金鑰

var kernel = new KernelMemoryBuilder()
    .WithSimpleFileStorage("Folder")
    .WithoutTextGenerator()
    .WithAzureOpenAITextEmbeddingGeneration(new AzureOpenAIConfig()
    {
        Endpoint = endpoint,
        APIKey = apiKey,
        Deployment = "Embedding",
        APIType = AzureOpenAIConfig.APITypes.EmbeddingGeneration,
        Auth = AzureOpenAIConfig.AuthTypes.APIKey
    })
    .Build();

以上程式碼裡面的 endpoint 和 apiKey 和 Deployment 分別換成你的地址和你的金鑰以及你的部署名稱

本文只是演示如何呼叫文字嵌入向量化,不涉及到文字生成,於是加上了 WithoutTextGenerator 配置,加上了此配置之後,後續的 Ask 系列方法將不能呼叫。本文這裡為了方便起見,將知識庫向量化之後存放到本地資料夾裡面,即透過 .WithSimpleFileStorage("Folder") 配置存放到相對工作路徑的 Folder 資料夾。大家可以嘗試執行專案之後看看你的這個資料夾裡面的內容

拿到了 IKernelMemory 型別的 kernel 物件之後,接下來就可以匯入知識庫的知識了。如以下我匯入了一些我的部落格內容作為知識

await kernel.ImportTextAsync("本文記錄在 WPF 專案裡面設定 IncludePackageReferencesDuringMarkupCompilation 屬性為 False 導致了專案所安裝的分析器不能符合預期工作 設定 IncludePackageReferencesDuringMarkupCompilation 屬性為 false 將配置 WPF 在構建 XAML 過程中建立的 tmp.csproj 過程中將不引用依賴的 nuget 包。分析器預設也是透過 nuget 包方式安裝的,這就導致了分析器專案沒有被 tmp.csproj 專案正確使用到 如果專案裡面有程式碼依賴分析器生成的影響語義的程式碼,那這部分程式碼將會構建不透過");

await kernel.ImportTextAsync("在 dotnet 6 時,官方為了適配好 Source Generators 功能,於是預設就將 WPF 的 XAML 構建過程中,引入第三方庫的 cs 檔案,這個功能預設設定為開啟。剛好原始碼包為了修復在使用 dotnet 6 SDK 之前,在 WPF 的構建 XAML 過程中,不包含第三方庫的程式碼檔案,從而使用黑科技將原始碼包加入到 WPF 構建 XAML 中。在 VisualStudio 升級到 2022 版本,或者是升級 dotnet sdk 到 dotnet 6 版本,將會更新構建排程,讓原始碼包裡的程式碼檔案被加入兩次,從而構建失敗\r\n構建失敗的提示如下\r\n\r\n```\r\nC:\\Program Files\\dotnet\\sdk\\6.0.101\\Sdks\\Microsoft.NET.Sdk\\targets\\Microsoft.NET.Sdk.DefaultItems.Shared.targets(190,5): error NETSDK1022: 包含了重複的“Compile”項。.NET SDK 預設包含你專案目錄中的“Compile”項。可從專案檔案中刪除這些項;如果希望將其顯式包含在專案檔案中,可將“EnableDefaultCompileItems”屬性設定為“false”。有關詳細資訊,請參閱 https://aka.ms/sdkimplicititems。重複項為: \r\n```重複的原因是 WPF 在 .NET SDK 裡修復了在 XAML 構建過程中,沒有引用 NuGet 包裡面的檔案。而原始碼包許多都是在此修復之前打出來的,原始碼包為了修復在 XAML 裡面沒有引用檔案,就強行加上修復邏輯引用檔案。而在 dotnet 6 修復了之後,自然就會導致引用了多次 修復方法很簡單,在不更改原始碼包的前提下,可以在 csproj 專案檔案里加入以下程式碼```xml\r\n    <IncludePackageReferencesDuringMarkupCompilation>False</IncludePackageReferencesDuringMarkupCompilation>\r\n```");

await kernel.ImportTextAsync("預設情況下的 WPF 專案都是帶 -windows 的 TargetFramework 方式,但有一些專案是不期望加上 -windows 做平臺限制的,本文將介紹如何實現不新增 -windows 而引用 WPF 框架 對於一些特殊的專案來說,也許只是在某些模組下期望引用 WPF 的某些型別,而不想自己的專案限定平臺。這時候可以去掉 `-windows` 換成 FrameworkReference 的方式 透過 `<FrameworkReference Include=\"Microsoft.WindowsDesktop.App.WPF\" />` 即可設定對 WPF 程式集的引用,也就是僅僅只是將 WPF 的程式集取出來當成引用,而不是加上 WPF 的負載");

await kernel.ImportTextAsync("dotnet 如何訪問到 UNO 框架裡面的 internal 不公開成員? 核心原理是基於 UNO 框架裡面的 InternalsVisibleToAttribute 程式集特性,指定給到 SamplesApp 等程式集可見。因此只需要新建一個程式集,設定 AssemblyName 為 SamplesApp 即可");

以上的匯入邏輯將會呼叫上文部署的 text-embedding-ada-002 模型,將文字內容進行向量化,將向量化之後的結果存放到本地的檔案裡面,使用本地檔案系統作為知識資料庫。後續的查詢邏輯即可讀取本地檔案的向量進行向量距離對比,支援語義化查詢

傳統的查詢大部分都是關鍵詞進行字串比較,而透過 text-embedding-ada-002 等 TextEmbedding 模型,即可進行語義化查詢。核心原理是計算出查詢字串的向量值,與知識資料庫裡面存放的知識的向量進行比較,從而獲取到向量距離較近的知識,向量距離越近表示約有相關性。透過此方法可以更好的進行查詢知識,特別是處理海量知識庫資訊查詢的時候

建立知識庫步驟只需要做一次呼叫 TextEmbedding 模型,不需要每次查詢資料都重新對整個知識庫進行呼叫 TextEmbedding 模型。因為呼叫一次之後,就獲取到 TextEmbedding 模型返回的向量化資訊。之後只需要對查詢的資訊的內容呼叫 TextEmbedding 模型獲取查詢資訊的向量化資訊,再將查詢資訊的向量化資訊與知識庫裡面的各個知識的向量化資訊進行比較即可,即可找到查詢資訊與各個知識的相關性

如以下程式碼嘗試進行一條查詢

var searchResult = await kernel.SearchAsync("如何訪問 UNO 不公開成員");

以上查詢可以拿到查詢結果,一般來說查詢結果需要關注其相關性,即 Relevance 屬性的值。大部分情況下的業務只取第一條,即最相關的內容

var searchResult = await kernel.SearchAsync("如何訪問 UNO 不公開成員");

if (searchResult.NoResult)
{
    Console.WriteLine("沒有找到相關項");
    return;
}

foreach (var citation in searchResult.Results)
{
    // 大部分情況下,只取第一項
    foreach (var partition in citation.Partitions)
    {
        Console.WriteLine($"關聯性: {partition.Relevance:0.00} 內容: {partition.Text}");
    }
}

執行以上程式碼的輸出如下

關聯性: 0.84 內容: dotnet 如何訪問到 UNO 框架裡面的 internal 不公開成員? 核心原理是基於 UNO 框架裡面的 InternalsVisibleToAttribute 程式集特性,指定給到 SamplesApp 等程式集可見。因此只需要新建一個程式集,設定 AssemblyName 為 SamplesApp 即 可
關聯性: 0.66 內容: NET SDK 裡修復了在 XAML 構建過程中,沒有引用 NuGet 包裡面的檔案。而原始碼包許多都是在此修復之前打出來的,原始碼包為了修復在 XAML 裡面沒有引用檔案,就強行加上修復邏輯引用檔案。而在 dotnet 6 修復了之後,自然就會導致引用了多次 修復方法很簡單,在不更改原始碼包的前提下,可以在 csproj 專案檔案里加入以下程式碼```xml
<IncludePackageReferencesDuringMarkupCompilation>False</IncludePackageReferencesDuringMarkupCompilation>`` `
關聯性: 0.64 內容: 在 dotnet 6 時,官方為了適配好 Source Generators 功能,於是預設就將 WPF 的 XAML 構建過程中,引入第 三方庫的 cs 檔案,這個功能預設設定為開啟。剛好原始碼包為了修復在使用 dotnet 6 SDK 之前,在 WPF 的構建 XAML
過程中,不包含第三方庫的程式碼檔案,從而使用黑科技將原始碼包加入到 WPF 構建 XAML 中。在 VisualStudio 升級到 2022 版本,或 者是升級 dotnet sdk 到 dotnet 6 版本,將會更新構建排程,讓原始碼包裡的程式碼檔案被加入兩次,從而構建失敗
構建失敗的提示如下` ``
C:\Program Files\dotnet\sdk\6.0.101\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.DefaultItems.Shared.targets(190,5): error NETSDK1022: 包含了重複的“Compile”項。.NET SDK 預設包含你專案目錄中的“Compile”項。可從專案檔案中刪除這些項;如果希望將其顯式包含在專案檔案中,可將“EnableDefaultCompileItems”屬性設定為“false”。有關詳細資訊,請參閱 https://aka.ms/sdkimplicititems。重複項為:
` ``重複的原因是 WPF 在 . NET SDK 裡修復了在 XAML 構建過程中,沒有引用 NuGet
關聯性: 0.65 內容: 本文記錄在 WPF 專案裡面設定 IncludePackageReferencesDuringMarkupCompilation 屬性為 False 導致了專案所安裝的分析器不能符合預期工作 設定 IncludePackageReferencesDuringMarkupCompilation 屬性為 false 將配置 WPF 在構建 XAML 過程中建立的 tmp.
csproj 過程中將不引用依賴的 nuget 包。分析器預設也是透過 nuget 包方式安裝的,這就導致了分析器專案沒有被 tmp.csproj 專案 正確使用到 如果專案裡面有程式碼依賴分析器生成的影響語義的程式碼,那這部分程式碼將會構建不透過
關聯性: 0.64 內容: 預設情況下的 WPF 專案都是帶 -windows 的 TargetFramework 方式,但有一些專案是不期望加上 -windows 做 平臺限制的,本文將介紹如何實現不新增 -windows 而引用 WPF 框架 對於一些特殊的專案來說,也許只是在某些模組下期望引用 WPF  的某些型別,而不想自己的專案限定平臺。這時候可以去掉 `-windows` 換成 FrameworkReference 的方式 透過 `<FrameworkReference Include="Microsoft.
WindowsDesktop.App.WPF" />` 即可設定對 WPF 程式集的引用,也就是僅僅只是將 WPF 的程式集取出來當成引用,而不是加上 WPF 的 負載

使用 TextEmbedding 查詢的好處在於其支援語義化,即換個說法查詢也是可以的,比如我換成如下程式碼進行查詢

var searchResult = await kernel.SearchAsync("如何呼叫非公開方法");

此時依然能夠輸出如下內容

關聯性: 0.77 內容: dotnet 如何訪問到 UNO 框架裡面的 internal 不公開成員? 核心原理是基於 UNO 框架裡面的 InternalsVisibleToAttribute 程式集特性,指定給到 SamplesApp 等程式集可見。因此只需要新建一個程式集,設定 AssemblyName 為 SamplesApp 即 可

在 Microsoft.KernelMemory 裡面還新增了 AskAsync 方法,這個 AskAsync 方法裡面包含兩個步驟的內容。第一步就是執行 SearchAsync 的核心功能,查詢到相關的知識。第二步就是呼叫 TextGenerator 的功能,根據查詢到相關的知識讓 AI 如 GPT 生成人類更加友好的回答內容

由於本文開始配置裡面設定了 WithoutTextGenerator 因此呼叫以下程式碼將會丟擲異常

var answer = await kernel.AskAsync("為什麼分析器和原始碼衝突");

如果大家想要測試此功能,還請執行配置

本文程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin eac6a40cab7f99151d01637e646320d7063b5404

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin eac6a40cab7f99151d01637e646320d7063b5404

獲取程式碼之後,進入 SemanticKernelSamples/NekawlaicaYegemwheechijahafea 資料夾,即可獲取到原始碼

歡迎加入 SemanticKernel 群交流:623349574

相關文章