中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

基于(yu)Microsoft.Extensions.AI核心(xin)庫實(shi)現RAG應用

大(da)家好,我是Edison。

之前我(wo)們(men)了解  和  兩個重要的(de)AI應(ying)用核心庫。基于對他(ta)們(men)的(de)了解,今(jin)天(tian)我(wo)們(men)就可(ke)以來實(shi)戰一個RAG問(wen)答(da)應(ying)用,把(ba)之前所學的(de)串起來。

前(qian)提(ti)知識(shi)點:向量存(cun)儲(chu)、詞(ci)嵌入、向量搜索、提(ti)示詞(ci)工程、函數(shu)調用。

案例需求背景

假(jia)設我(wo)們(men)在一(yi)(yi)家名叫“易(yi)速鮮(xian)花(hua)”的(de)(de)電(dian)商網站工作,顧名思義,這(zhe)是一(yi)(yi)家從事鮮(xian)花(hua)電(dian)商的(de)(de)網站。我(wo)們(men)有一(yi)(yi)些運(yun)營手冊(ce)、員工手冊(ce)之類的(de)(de)文檔(例如下(xia)圖(tu)所示的(de)(de)一(yi)(yi)些pdf文件),想要將其導入知識庫并創建一(yi)(yi)個AI機器人,負責日常為(wei)員工解答一(yi)(yi)些政策(ce)性的(de)(de)問題。

例如,員(yuan)工想要了解(jie)獎勵標準(zhun)(zhun)、行為準(zhun)(zhun)備、報銷流程(cheng)等等,都可以通過(guo)和這個AI機器人對話就可以快速了解(jie)最新的(de)政策和流程(cheng)。

在接下(xia)來的(de)Demo中,我(wo)們會使用以下(xia)工(gong)具:

(1) LLM 采用 Qwen2.5-7B-Instruct,可以使(shi)用SiliconFlow平臺提供的API,你也可以改(gai)為你喜歡的其他(ta)模型如DeepSeek,但是建議不要用大炮打蚊(wen)子(zi)哈。

注冊地址:

(2) Qdrant 作為(wei) 向量數據庫,可以使用Docker在(zai)你本地(di)運行一個:

docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant

(3) Ollama 運行 bge-m3 模型 作為(wei) Emedding生成(cheng)器,可以自行拉取一個(ge)在你本地運行:

ollama pull bge-m3

構建你的RAG應用

創建一(yi)個控制臺(tai)應(ying)用程序(xu),添加一(yi)些(xie)必要的文件(jian)目錄(lu) 和 配(pei)置(zhi)文件(jian)(json),最終的解決方案(an)如(ru)下圖(tu)所(suo)示。

在(zai)Documents目錄下放了我們要(yao)導(dao)入的一些pdf文檔,例如(ru)公司運營手(shou)冊(ce)、員工(gong)手(shou)冊(ce)等(deng)等(deng)。

在Models目(mu)錄下(xia)放了一(yi)些(xie)公用的model類(lei),其中TextSnippet類(lei)作為(wei)向量(liang)存(cun)儲的實(shi)體類(lei),而TextSearchResult類(lei)則作為(wei)向量(liang)搜(sou)索(suo)結果的模型類(lei)。

(1)TextSnippet

這里我(wo)(wo)們的TextEmbedding字段就是我(wo)(wo)們的向量值,它有1024維。

注意:這里的(de)維度(du)是我們自(zi)己定義(yi)的(de),你也可以改為你想要的(de)維度(du)數量(liang),但是你的(de)詞嵌入模型(xing)需要支持(chi)你想要的(de)維度(du)數量(liang)。

public sealed class TextSnippet<TKey>
{
    [VectorStoreRecordKey]
    public required TKey Key { get; set; }

    [VectorStoreRecordData]
    public string? Text { get; set; }

    [VectorStoreRecordData]
    public string? ReferenceDescription { get; set; }

    [VectorStoreRecordData]
    public string? ReferenceLink { get; set; }

    [VectorStoreRecordVector(Dimensions: 1024)]
    public ReadOnlyMemory<float> TextEmbedding { get; set; }
}

(2)TextSearchResult

這(zhe)個類主要用(yong)來返(fan)回給LLM做推理用(yong)的,我這(zhe)里只需要三個字段:Value, Link 和 Score 即可。

public class TextSearchResult
{
    public string  Value { get; set; }
    public string? Link { get; set; }
    public double? Score { get; set; }
}

(3)RawContent

這個類主要(yao)用(yong)來在(zai)PDF導入時(shi)作為一個臨時(shi)存儲源數據文檔(dang)內容。

public sealed class RawContent
{
    public string? Text { get; init; }

    public int PageNumber { get; init; }
}

在Plugins目錄下(xia)放了一些公用(yong)(yong)的(de)幫助類,如PdfDataLoader可(ke)(ke)以實(shi)現PDF文件的(de)讀取(qu)和導入向量數據庫,VectorDataSearcher可(ke)(ke)以實(shi)現根(gen)據用(yong)(yong)戶的(de)query搜索向量數據庫獲取(qu)TopN個近似文檔,而UniqueKeyGenerator則用(yong)(yong)來(lai)生(sheng)成(cheng)唯一的(de)ID Key。

(1)PdfDataLoader

作為PDF文(wen)件的導入(ru)核心邏輯,它實現了PDF文(wen)檔讀(du)取、切分、生成指(zhi)定維(wei)度(du)的向(xiang)量(liang) 并(bing) 存(cun)入(ru)向(xiang)量(liang)數據庫。

注意:這里(li)只考(kao)(kao)慮了文(wen)本(ben)格式的內容,如果你(ni)還想考(kao)(kao)慮文(wen)件中的圖(tu)片(pian)將其轉成文(wen)本(ben),你(ni)需要增(zeng)加一個LLM來幫你(ni)做圖(tu)片(pian)轉文(wen)本(ben)的工作。

public sealed class PdfDataLoader<TKey> where TKey : notnull
{
    private readonly IVectorStoreRecordCollection<TKey, TextSnippet<TKey>> _vectorStoreRecordCollection;
    private readonly UniqueKeyGenerator<TKey> _uniqueKeyGenerator;
    private readonly IEmbeddingGenerator<string, Embedding<float>> _embeddingGenerator;

    public PdfDataLoader(
        UniqueKeyGenerator<TKey> uniqueKeyGenerator,
        IVectorStoreRecordCollection<TKey, TextSnippet<TKey>> vectorStoreRecordCollection,
        IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator)
    {
        _vectorStoreRecordCollection = vectorStoreRecordCollection;
        _uniqueKeyGenerator = uniqueKeyGenerator;
        _embeddingGenerator = embeddingGenerator;
    }

    public async Task LoadPdf(string pdfPath, int batchSize, int betweenBatchDelayInMs)
    {
        // Create the collection if it doesn't exist.
        await _vectorStoreRecordCollection.CreateCollectionIfNotExistsAsync();

        // Load the text and images from the PDF file and split them into batches.
        var sections = LoadAllTexts(pdfPath);
        var batches = sections.Chunk(batchSize);

        // Process each batch of content items.
        foreach (var batch in batches)
        {
            // Get text contents
            var textContentTasks = batch.Select(async content =>
            {
                if (content.Text != null)
                    return content;

                return new RawContent { Text = string.Empty, PageNumber = content.PageNumber };
            });
            var textContent = (await Task.WhenAll(textContentTasks))
                .Where(c => !string.IsNullOrEmpty(c.Text))
                .ToList();

            // Map each paragraph to a TextSnippet and generate an embedding for it.
            var recordTasks = textContent.Select(async content => new TextSnippet<TKey>
            {
                Key = _uniqueKeyGenerator.GenerateKey(),
                Text = content.Text,
                ReferenceDescription = $"{new FileInfo(pdfPath).Name}#page={content.PageNumber}",
                ReferenceLink = $"{new Uri(new FileInfo(pdfPath).FullName).AbsoluteUri}#page={content.PageNumber}",
                TextEmbedding = await _embeddingGenerator.GenerateEmbeddingVectorAsync(content.Text!)
            });

            // Upsert the records into the vector store.
            var records = await Task.WhenAll(recordTasks);
            var upsertedKeys = _vectorStoreRecordCollection.UpsertBatchAsync(records);
            await foreach (var key in upsertedKeys)
            {
                Console.WriteLine($"Upserted record '{key}' into VectorDB");
            }

            await Task.Delay(betweenBatchDelayInMs);
        }
    }

    private static IEnumerable<RawContent> LoadAllTexts(string pdfPath)
    {
        using (PdfDocument document = PdfDocument.Open(pdfPath))
        {
            foreach (Page page in document.GetPages())
            {
                var blocks = DefaultPageSegmenter.Instance.GetBlocks(page.GetWords());
                foreach (var block in blocks)
                    yield return new RawContent { Text = block.Text, PageNumber = page.Number };
            }
        }
    }
}

(2)VectorDataSearcher

介紹的內容類似,主要做語義搜(sou)索,獲取(qu)TopN個近似內容。

public class VectorDataSearcher<TKey> where TKey : notnull
{
    private readonly IVectorStoreRecordCollection<TKey, TextSnippet<TKey>> _vectorStoreRecordCollection;
    private readonly IEmbeddingGenerator<string, Embedding<float>> _embeddingGenerator;

    public VectorDataSearcher(IVectorStoreRecordCollection<TKey, TextSnippet<TKey>> vectorStoreRecordCollection, IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator)
    {
        _vectorStoreRecordCollection = vectorStoreRecordCollection;
        _embeddingGenerator = embeddingGenerator;
    }

    [Description("Get top N text search results from vector store by user's query (N is 1 by default)")]
    [return: Description("Collection of text search result")]
    public async Task<IEnumerable<TextSearchResult>> GetTextSearchResults(string query, int topN = 1)
    {
        var queryEmbedding = await _embeddingGenerator.GenerateEmbeddingVectorAsync(query);
        // Query from vector data store
        var searchOptions = new VectorSearchOptions()
        {
            Top = topN,
            VectorPropertyName = nameof(TextSnippet<TKey>.TextEmbedding)
        };
        var searchResults = await _vectorStoreRecordCollection.VectorizedSearchAsync(queryEmbedding, searchOptions);
        var responseResults = new List<TextSearchResult>();
        await foreach (var result in searchResults.Results)
        {
            responseResults.Add(new TextSearchResult()
            {
                Value = result.Record.Text ?? string.Empty,
                Link = result.Record.ReferenceLink ?? string.Empty,
                Score = result.Score
            });
        }

        return responseResults;
    }
}

(3)UniqueKeyGenerator

這個主要(yao)是一個代理(li),后續我們主要(yao)使用(yong)Guid作為Key。

public sealed class UniqueKeyGenerator<TKey>(Func<TKey> generator)
    where TKey : notnull
{
    /// <summary>
    /// Generate a unique key.
    /// </summary>
    /// <returns>The unique key that was generated.</returns>
    public TKey GenerateKey() => generator();
}

串聯實現RAG問答

安裝NuGet包:

Microsoft.Extensions.AI (preview)
Microsoft.Extensions.Ollama (preivew)
Microsoft.Extensions.AI.OpenAI (preivew)
Microsoft.Extensions.VectorData.Abstractions (preivew)
Microsoft.SemanticKernel.Connectors.Qdrant (preivew)
PdfPig (0.1.9)
Microsoft.Extensions.Configuration (8.0.0)
Microsoft.Extensions.Configuration.Json (8.0.0)

下面我們分解(jie)幾個核心步驟來實現(xian)RAG問答。

Step1. 配置文件appsettings.json:

{
  "LLM": {
    "EndPoint": "//api.siliconflow.cn",
    "ApiKey": "sk-**********************", // Replace with your ApiKey
    "ModelId": "Qwen/Qwen2.5-7B-Instruct"
  },
  "Embeddings": {
    "Ollama": {
      "EndPoint": "//localhost:11434",
      "ModelId": "bge-m3"
    }
  },
  "VectorStores": {
    "Qdrant": {
      "Host": "edt-dev-server",
      "Port": 6334,
      "ApiKey": "EdisonTalk@2025"
    }
  },
  "RAG": {
    "CollectionName": "oneflower",
    "DataLoadingBatchSize": 10,
    "DataLoadingBetweenBatchDelayInMilliseconds": 1000,
    "PdfFileFolder": "Documents"
  }
}

Step2. 加載配置:

var config = new ConfigurationBuilder()
    .AddJsonFile($"appsettings.json")
    .Build();

Step3. 初始化ChatClient、Embedding生成器 以及 VectorStore:

# ChatClient
var apiKeyCredential = new ApiKeyCredential(config["LLM:ApiKey"]);
var aiClientOptions = new OpenAIClientOptions();
aiClientOptions.Endpoint = new Uri(config["LLM:EndPoint"]);
var aiClient = new OpenAIClient(apiKeyCredential, aiClientOptions)
    .AsChatClient(config["LLM:ModelId"]);
var chatClient = new ChatClientBuilder(aiClient)
    .UseFunctionInvocation()
    .Build();
# EmbeddingGenerator
var embedingGenerator =
    new OllamaEmbeddingGenerator(new Uri(config["Embeddings:Ollama:EndPoint"]), config["Embeddings:Ollama:ModelId"]);
# VectorStore
var vectorStore = 
    new QdrantVectorStore(new QdrantClient(host: config["VectorStores:Qdrant:Host"], port: int.Parse(config["VectorStores:Qdrant:Port"]), apiKey: config["VectorStores:Qdrant:ApiKey"]));

Step4. 導入PDF文檔到VectorStore:

var ragConfig = config.GetSection("RAG");
// Get the unique key genrator
var uniqueKeyGenerator = new UniqueKeyGenerator<Guid>(() => Guid.NewGuid());
// Get the collection in qdrant
var ragVectorRecordCollection = vectorStore.GetCollection<Guid, TextSnippet<Guid>>(ragConfig["CollectionName"]);
// Get the PDF loader
var pdfLoader = new PdfDataLoader<Guid>(uniqueKeyGenerator, ragVectorRecordCollection, embedingGenerator);
// Start to load PDF to VectorStore
var pdfFilePath = ragConfig["PdfFileFolder"];
var pdfFiles = Directory.GetFiles(pdfFilePath);
try
{
    foreach (var pdfFile in pdfFiles)
    {
        Console.WriteLine($"[LOG] Start Loading PDF into vector store: {pdfFile}");
        await pdfLoader.LoadPdf(
            pdfFile,
            int.Parse(ragConfig["DataLoadingBatchSize"]),
            int.Parse(ragConfig["DataLoadingBetweenBatchDelayInMilliseconds"]));
        Console.WriteLine($"[LOG] Finished Loading PDF into vector store: {pdfFile}");
    }
    Console.WriteLine($"[LOG] All PDFs loaded into vector store succeed!");
}
catch (Exception ex)
{
    Console.WriteLine($"[ERROR] Failed to load PDFs: {ex.Message}");
    return;
}

Step5. 構建AI對話機器人:

重點關注這里(li)的提(ti)示(shi)詞(ci)模板(ban),我們做了(le)幾件事情(qing):

(1)給AI設定一個(ge)人設:鮮花網站的AI對話機器人,告知其負(fu)責的職(zhi)責。

(2)告訴AI要使用(yong)相(xiang)(xiang)關工具(向量搜索插件)進行相(xiang)(xiang)關背景信息(xi)的搜索獲取,然后將結(jie)果 連同 用(yong)戶的問題 組(zu)成(cheng)一(yi)個新的提(ti)示(shi)詞,最后將這個新的提(ti)示(shi)詞發給大模型進行處理(li)。

(3)告訴AI在輸出信息時(shi)要(yao)把引用的文檔(dang)信息鏈接(jie)也一(yi)同輸出。

Console.WriteLine("[LOG] Now starting the chatting window for you...");
Console.ForegroundColor = ConsoleColor.Green;
var promptTemplate = """
          你是一(yi)個專(zhuan)業的(de)AI聊(liao)天機器(qi)人,為易速(su)鮮花(hua)網站的(de)所(suo)有員工提(ti)(ti)供(gong)信(xin)息(xi)咨詢服務。
          請使用(yong)下面的(de)提(ti)(ti)示使用(yong)工具(ju)從向量數據庫(ku)中獲取(qu)相關信(xin)息(xi)來回答用(yong)戶提(ti)(ti)出的(de)問(wen)題:
          {{#with (SearchPlugin-GetTextSearchResults question)}}  
            {{#each this}}  
              Value: {{Value}}
              Link: {{Link}}
              Score: {{Score}}
              -----------------
             {{/each}}
            {{/with}}
            
            輸出要求:請在回復中引用相(xiang)關(guan)信息的地方包(bao)括對(dui)相(xiang)關(guan)信息的引用。

            用戶問題: {{question}}
            """;
var history = new List<ChatMessage>();
var vectorSearchTool = new VectorDataSearcher<Guid>(ragVectorRecordCollection, embedingGenerator);
var chatOptions = new ChatOptions()
{
    Tools =
    [
      AIFunctionFactory.Create(vectorSearchTool.GetTextSearchResults)
    ]
};
// Prompt the user for a question.
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"助手> 今天有什么可(ke)以幫到你的?");
while (true)
{
    // Read the user question.
    Console.ForegroundColor = ConsoleColor.White;
    Console.Write("用戶> ");
    var question = Console.ReadLine();
    // Exit the application if the user didn't type anything.
    if (!string.IsNullOrWhiteSpace(question) && question.ToUpper() == "EXIT")
        break;

    var ragPrompt = promptTemplate.Replace("{question}", question);
    history.Add(new ChatMessage(ChatRole.User, ragPrompt));
    Console.ForegroundColor = ConsoleColor.Green;
    Console.Write("助手> ");
    var result = await chatClient.GetResponseAsync(history, chatOptions);
var response = result.ToString(); Console.Write(response); history.Add(new ChatMessage(ChatRole.Assistant, response)); Console.WriteLine(); }

調試驗證(zheng)

首先(xian),看(kan)看(kan)PDF導入中的(de)log顯示:

其次,驗證下(xia)Qdrant中是否新增了導入(ru)的PDF文檔(dang)數(shu)據(ju):

最后,和AI機器人對(dui)話咨詢問題(ti):

問題1及其回(hui)復:

問題2及(ji)其回復:

更多(duo)的問題,就留給你去調戲(xi)了。

小(xiao)結

本文(wen)介(jie)紹(shao)了如(ru)何基于Microsoft.Extensions.AI + Microsoft.Extensions.VectorData 一(yi)步(bu)一(yi)步(bu)地實現(xian)一(yi)個RAG(檢(jian)索增(zeng)強生成)應(ying)用,相(xiang)信會對你有所幫(bang)助。

如果你也是.NET程序員希(xi)望參與AI應用的開發,那就(jiu)快快了解和(he)使用基于Microsoft.Extensioins.AI + Microsoft.Extensions.VectorData 的生(sheng)態組件庫吧。

示例源碼

GitHub:

參考內容

Semantic Kernel 《》

推薦(jian)內容

 

var
posted @ 2025-03-06 18:30  EdisonZhou  閱讀(2024)  評論(4)    收藏  舉報