SemanticKernel/C#:檢索增強生成(RAG)簡易實踐

mingupupup發表於2024-08-01

檢索增強生成(RAG)是什麼?

RAG是“Reference-based Generative model with Attention”的縮寫,也可以被稱為“Retrieval-Augmented Generation”,是一種結合了檢索技術和生成模型的方法,主要用於自然語言處理任務,如文字生成、對話系統、機器翻譯等。RAG模型透過從外部知識庫中檢索相關資訊,並將其與輸入文字結合,以生成更準確、更豐富的輸出。這種方法可以提高模型的準確性和可解釋性,因為它可以明確地指出生成的文字與哪些外部知識相關。RAG模型在處理需要大量背景知識的任務時特別有用,如專業領域的問答系統或對話代理。

本示例實現的效果

在使用大語言模型的過程中,會發現大語言模型在通用知識上很強,但是如果你問的是跟私有資料有關的事情,它就不知道了。比如有一段私有文字資料如下所示:

小X於2000年建立了一家名為“小X的世界”的公司,公司總部在湖北武漢,員工有300人。小X最喜歡的程式語言是C#,小X最喜歡的書是《平凡的世界》。

這只是個簡單的例子,所以文字先取的很短,實際上可以換成是你的一些私有文件,然後讓大語言模型根據你的私有文件進行回答,現在你如果問大語言模型,“小X建立的公司叫什麼?”、”小X最喜歡的程式語言是什麼?“等等一些根據私有文件才能回答的問題,大語言模型是不知道的,但是透過RAG就可以讓大語言模型回答諸如此類的需要根據私有文件才能回答的問題。

image-20240801082308221

image-20240801082408638

實現的思路是透過嵌入模型將文字轉化為向量,將向量存入資料庫,檢索時基於輸入查詢的向量表示,從知識庫中檢索出最相關的文件或片段。將獲取的相關片段,嵌入到Prompt中,讓大語言模型根據獲取到的片段進行回答。

開始實踐

安裝所需的nuget包:

image-20240801082520136

首先先初始化一個Kernel,這裡我使用的大語言模型是矽基流動平臺提供的開源的Qwen/Qwen2-7B-Instruct。

 private readonly Kernel _kernel;
 public SemanticKernelService()
 {
     var handler = new OpenAIHttpClientHandler();
     var builder = Kernel.CreateBuilder()
     .AddOpenAIChatCompletion(
       modelId: "Qwen/Qwen2-7B-Instruct",
       apiKey: "api key",
       httpClient: new HttpClient(handler));         
     var kernel = builder.Build();
     _kernel = kernel;
 }

由於矽基流動平臺已經提供了與OpenAI相容的格式,只需要在傳入一個HttpClient將請求轉發到矽基流動平臺的api即可,OpenAIHttpClientHandler類如下所示:

 public class OpenAIHttpClientHandler : HttpClientHandler
 {
     protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
     {
         UriBuilder uriBuilder;
         switch (request.RequestUri?.LocalPath)
         {
             case "/v1/chat/completions":
                 uriBuilder = new UriBuilder(request.RequestUri)
                 {
                     // 這裡是你要修改的 URL
                     Scheme = "https",
                     Host = "api.siliconflow.cn",
                     Path = "v1/chat/completions",
                 };
                 request.RequestUri = uriBuilder.Uri;
                 break;

             case "/v1/embeddings":
                 uriBuilder = new UriBuilder(request.RequestUri)
                 {
                     // 這裡是你要修改的 URL
                     Scheme = "https",
                     Host = "api.siliconflow.cn",
                     Path = "v1/embeddings",
                 };
                 request.RequestUri = uriBuilder.Uri;
                 break;
         }

         HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

         return response;
     }
 }

現在需要將文字轉化為向量,需要先構建一個ISemanticTextMemory:

image-20240801083237027

現在先來看看如何構建一個ISemanticTextMemory:

  public async Task<ISemanticTextMemory> GetTextMemory2()
  {
      var memoryBuilder = new MemoryBuilder();
      memoryBuilder.WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", "api key");           
      IMemoryStore memoryStore = await SqliteMemoryStore.ConnectAsync("memstore.db");
      memoryBuilder.WithMemoryStore(memoryStore);
      var textMemory = memoryBuilder.Build();
      return textMemory;
  }

首先需要有一個嵌入模型,這裡使用的是OpenAI的text-embedding-ada-002模型,也嘗試過使用矽基流動平臺提供的嵌入模型,生成向量是沒有問題的,但是在搜尋的時候會報錯,還沒有解決。

使用SQLite來儲存生成的向量。

 var lines = TextChunker.SplitPlainTextLines(input, 100);
 var paragraphs = TextChunker.SplitPlainTextParagraphs(lines, 1000);

 foreach (var para in paragraphs)
 {
     await textMemory.SaveInformationAsync(index, id: Guid.NewGuid().ToString(), text: para, cancellationToken: default);
 }

將文字分段,本示例文字內容很少,只有一段。

檢視資料庫:

image-20240801084335696

已經將向量資料存入資料庫了。

現在根據問題,搜尋最相關的片段:

image-20240801084459909

以“小X最喜歡的程式語言是什麼?”這個問題為例。

image-20240801084808470

將問題轉化為向量並利用餘弦相似度進行檢索搜尋最相關的片段:

image-20240801085241311

將獲取到的最相關的文字與問題嵌入到Prompt中,讓大語言模型回答:

image-20240801085445873

大語言模型的回答結果:

image-20240801085524229

以上就基於SemanticKernel實現了一個簡單的RAG應用。

下一步探索方向

雖然說我的電腦本地執行大語言模型不太行,但是在本地執行大語言模型還是有很多需求場景的,下一步探索如何在SemanticKernel中使用本地的大語言模型與嵌入模型。如果大語言模型執行不太行的話,再換成國內的平臺,嵌入模型我試過,本地執行也還可以的。

本地執行使用的是Ollama,官方也有計劃釋出一個Ollama Connector:

image-20240801090623386

image-20240801090825349

網上查了一些資料,有些大佬已經實現了在SemanticKernel中使用Ollama中的對話模型與嵌入模型。可以等官方支援,也可以根據大佬們的分享,自己去實踐一下。

Local Memory: C# Semantic Kernel, Ollama and SQLite to manage Chat Memories locally | by John Kane | Medium

Using local LLM with Ollama and Semantic Kernel - Learnings in IT (sachinsu.github.io)

Use Custom and Local AI Models with the Semantic Kernel SDK for .NET | Microsoft Learn

參考

1、https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/06-memory-and-embeddings.ipynb

2、https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/09-memory-with-chroma.ipynb

3、https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/MemoryStore_CustomReadOnly.cs

4、https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/SemanticTextMemory_Building.cs

5、https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Memory/TextChunkingAndEmbedding.cs

相關文章