午夜视频在线观看区二区-午夜视频在线观看视频-午夜视频在线观看视频在线观看-午夜视频在线观看完整高清在线-午夜视频在线观看网站-午夜视频在线观看亚洲天堂

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

基于Microsoft.Extensions.AI核心庫實(shí)現(xiàn)RAG應(yīng)用

freeflydom
2025年3月10日 9:41 本文熱度 255

大家好,我是Edison。

之前我們了解 Microsoft.Extensions.AI 和 Microsoft.Extensions.VectorData 兩個重要的AI應(yīng)用核心庫。基于對他們的了解,今天我們就可以來實(shí)戰(zhàn)一個RAG問答應(yīng)用,把之前所學(xué)的串起來。

前提知識點(diǎn):向量存儲、詞嵌入、向量搜索、提示詞工程、函數(shù)調(diào)用。

案例需求背景

假設(shè)我們在一家名叫“易速鮮花”的電商網(wǎng)站工作,顧名思義,這是一家從事鮮花電商的網(wǎng)站。我們有一些運(yùn)營手冊、員工手冊之類的文檔(例如下圖所示的一些pdf文件),想要將其導(dǎo)入知識庫并創(chuàng)建一個AI機(jī)器人,負(fù)責(zé)日常為員工解答一些政策性的問題。

例如,員工想要了解獎勵標(biāo)準(zhǔn)、行為準(zhǔn)備、報銷流程等等,都可以通過和這個AI機(jī)器人對話就可以快速了解最新的政策和流程。

在接下來的Demo中,我們會使用以下工具:

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

注冊地址:點(diǎn)此注冊

(2) Qdrant 作為 向量數(shù)據(jù)庫,可以使用Docker在你本地運(yùn)行一個:

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

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

ollama pull bge-m3

構(gòu)建你的RAG應(yīng)用

創(chuàng)建一個控制臺應(yīng)用程序,添加一些必要的文件目錄 和 配置文件(json),最終的解決方案如下圖所示。

在Documents目錄下放了我們要導(dǎo)入的一些pdf文檔,例如公司運(yùn)營手冊、員工手冊等等。

在Models目錄下放了一些公用的model類,其中TextSnippet類作為向量存儲的實(shí)體類,而TextSearchResult類則作為向量搜索結(jié)果的模型類。

(1)TextSnippet

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

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

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

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

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

(3)RawContent

這個類主要用來在PDF導(dǎo)入時作為一個臨時存儲源數(shù)據(jù)文檔內(nèi)容。

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

在Plugins目錄下放了一些公用的幫助類,如PdfDataLoader可以實(shí)現(xiàn)PDF文件的讀取和導(dǎo)入向量數(shù)據(jù)庫,VectorDataSearcher可以實(shí)現(xiàn)根據(jù)用戶的query搜索向量數(shù)據(jù)庫獲取TopN個近似文檔,而UniqueKeyGenerator則用來生成唯一的ID Key。

(1)PdfDataLoader

作為PDF文件的導(dǎo)入核心邏輯,它實(shí)現(xiàn)了PDF文檔讀取、切分、生成指定維度的向量 并 存入向量數(shù)據(jù)庫。

注意:這里只考慮了文本格式的內(nèi)容,如果你還想考慮文件中的圖片將其轉(zhuǎn)成文本,你需要增加一個LLM來幫你做圖片轉(zhuǎn)文本的工作。

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

上一篇文章介紹的內(nèi)容類似,主要做語義搜索,獲取TopN個近似內(nèi)容。

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

這個主要是一個代理,后續(xù)我們主要使用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();
}

串聯(lián)實(shí)現(xiàn)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)

下面我們分解幾個核心步驟來實(shí)現(xiàn)RAG問答。

Step1. 配置文件appsettings.json:

{
  "LLM": {
    "EndPoint": "https://api.siliconflow.cn",
    "ApiKey": "sk-**********************", // Replace with your ApiKey
    "ModelId": "Qwen/Qwen2.5-7B-Instruct"
  },
  "Embeddings": {
    "Ollama": {
      "EndPoint": "http://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. 導(dǎo)入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. 構(gòu)建AI對話機(jī)器人:

重點(diǎn)關(guān)注這里的提示詞模板,我們做了幾件事情:

(1)給AI設(shè)定一個人設(shè):鮮花網(wǎng)站的AI對話機(jī)器人,告知其負(fù)責(zé)的職責(zé)。

(2)告訴AI要使用相關(guān)工具(向量搜索插件)進(jìn)行相關(guān)背景信息的搜索獲取,然后將結(jié)果 連同 用戶的問題 組成一個新的提示詞,最后將這個新的提示詞發(fā)給大模型進(jìn)行處理。

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

Console.WriteLine("[LOG] Now starting the chatting window for you...");
Console.ForegroundColor = ConsoleColor.Green;
var promptTemplate = """
          你是一個專業(yè)的AI聊天機(jī)器人,為易速鮮花網(wǎng)站的所有員工提供信息咨詢服務(wù)。
          請使用下面的提示使用工具從向量數(shù)據(jù)庫中獲取相關(guān)信息來回答用戶提出的問題:
          {{#with (SearchPlugin-GetTextSearchResults question)}}  
            {{#each this}}  
              Value: {{Value}}
              Link: {{Link}}
              Score: {{Score}}
              -----------------
             {{/each}}
            {{/with}}
            
            輸出要求:請?jiān)诨貜?fù)中引用相關(guān)信息的地方包括對相關(guān)信息的引用。
            用戶問題: {{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($"助手> 今天有什么可以幫到你的?");
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(); }

調(diào)試驗(yàn)證

首先,看看PDF導(dǎo)入中的log顯示:

其次,驗(yàn)證下Qdrant中是否新增了導(dǎo)入的PDF文檔數(shù)據(jù):

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

問題1及其回復(fù):

問題2及其回復(fù):

更多的問題,就留給你去調(diào)戲了。

小結(jié)

本文介紹了如何基于Microsoft.Extensions.AI + Microsoft.Extensions.VectorData 一步一步地實(shí)現(xiàn)一個RAG(檢索增強(qiáng)生成)應(yīng)用,相信會對你有所幫助。

如果你也是.NET程序員希望參與AI應(yīng)用的開發(fā),那就快快了解和使用基于Microsoft.Extensioins.AI + Microsoft.Extensions.VectorData 的生態(tài)組件庫吧。

示例源碼

GitHub:點(diǎn)此查看

轉(zhuǎn)自https://www.cnblogs.com/edisonchou/p/-/introduction-to-vector-rag-demo


該文章在 2025/3/10 9:41:31 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財務(wù)費(fèi)用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 成人免费无码大片a毛片抽搐色欲 | 成人综合国内精品久久久久久影院 | 国产aⅴ天堂亚洲国产a | 国产精品视频一区二区三区无码 | 国产成人精品cāo在线 | 国产在线精品福利大全 | 国产午夜精品91久久影院无码 | 国产免费区在线观看十分钟 | 国产夜趣福利免费视频 | 高清无码一区二区 | 91精品一区二区 | 69久久国产精品亚洲大片 | 国产精品日本亚洲77 | 精品人妻va出轨中文字幕 | av一区二区三区 | 国产一区二区三区91在线 | 国产精品免费播放 | 2025国产精华国产精品 | 国产一区精品普通话对白 | 囯产精品国产三级国 | 国产成人av无码永久免费 | 国产精品嫩草影院入口一二三 | 高潮又爽又无遮挡又免费 | 国产午夜福利片 | 国产日韩一区二区三区免费高清 | 精品韩国av无码一区二区三区 | 国产美女精品久久久 | 国产精品毛片无码一区二区蜜 | 国产专区在线播放 | 99国产欧美精品久久久蜜芽 | 国产麻豆a一级毛片爽爽影院 | 丰满少妇又爽又紧又丰满在线 | 国产成人精品亚洲日本 | 国产精品午夜爆乳美女视频免费 | 观看国产一区二区三区 | 国产精品极品白嫩在线播放 | 国产尤物精品无码成人 | 国产午夜免费一区二区三区 | 国产精品日本一区二 | 国产丝袜视频 | 国产精品成av人在线观看片 |