下载百度安装到桌面优化网站使用体验
2026/6/1 12:43:36 网站建设 项目流程
下载百度安装到桌面,优化网站使用体验,涞水住房和城乡建设厅网站,新手学做网站内容今天我们就来实战一个RAG问答应用#xff0c;把之前所学的串起来。如果你觉得对你有帮助#xff0c;可以V我50#xff0c;毕竟今天是Crazy星期四。前提知识点#xff1a;向量存储、词嵌入、语义搜索、提示词工程、函数调用。案例需求背景假设我们在一家名叫“易速鲜花”的电…今天我们就来实战一个RAG问答应用把之前所学的串起来。如果你觉得对你有帮助可以V我50毕竟今天是Crazy星期四。前提知识点向量存储、词嵌入、语义搜索、提示词工程、函数调用。案例需求背景假设我们在一家名叫“易速鲜花”的电商网站工作顾名思义这是一家从事鲜花电商的网站。我们有一些运营手册、员工手册之类的文档例如下图所示的一些pdf文件想要将其导入知识库并创建一个AI机器人负责日常为员工解答一些政策性的问题。例如员工想要了解奖励标准、行为准则、报销流程等等都可以通过和这个AI机器人对话就可以快速了解最新的政策和流程。在接下来的Demo中我们会使用以下工具(1) LLM 采用 Qwen2.5-7B-Instruct可以使用SiliconFlow平台提供的API你也可以改为你喜欢的其他模型如DeepSeek但是建议不要用大炮打蚊子哈。注册地址https://cloud.siliconflow.cn/i/DomqCefW(2) Qdrant 作为 向量数据库可以使用Docker在你本地运行一个docker run -p 6333:6333 -p 6334:6334 \-v $(pwd)/qdrant_storage:/qdrant/storage \qdrant/qdrant(3) Ollama 运行 bge-m3 模型 作为 Emedding生成器可以自行拉取一个在你本地运行ollama pull bge-m3构建你的RAG应用创建一个控制台应用程序添加一些必要的文件目录 和 配置文件json最终的解决方案如下图所示。在Documents目录下放了我们要导入的一些pdf文档例如公司运营手册、员工手册等等。在Models目录下放了一些公用的model类其中TextSnippet类作为向量存储的实体类而TextSearchResult类则作为向量搜索结果的模型类。1TextSnippet这里我们的TextEmbedding字段就是我们的向量值它有1024维。注意这里的维度是我们自己定义的你也可以改为你想要的维度数量但是你的词嵌入模型需要支持你想要的维度数量。public sealed class TextSnippetTKey{ [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 ReadOnlyMemoryfloat TextEmbedding { get; set; }}2TextSearchResult这个类主要用来返回给LLM做推理用的我这里只需要三个字段Value, Link 和 Score 即可。public class TextSearchResult{ public string Value { get; set; } public string? Link { get; set; } public double? Score { get; set; }}3RawContent这个类主要用来在PDF导入时作为一个临时存储源数据文档内容。public sealed class RawContent{ public string? Text { get; init; } public int PageNumber { get; init; }}在Plugins目录下放了一些公用的帮助类如PdfDataLoader可以实现PDF文件的读取和导入向量数据库VectorDataSearcher可以实现根据用户的query搜索向量数据库获取TopN个近似文档而UniqueKeyGenerator则用来生成唯一的ID Key。1PdfDataLoader作为PDF文件的导入核心逻辑它实现了PDF文档读取、切分、生成指定维度的向量 并 存入向量数据库。注意这里只考虑了文本格式的内容如果你还想考虑文件中的图片将其转成文本你需要增加一个LLM来帮你做图片转文本的工作。public sealed class PdfDataLoaderTKey where TKey : notnull{ private readonly IVectorStoreRecordCollectionTKey, TextSnippetTKey _vectorStoreRecordCollection; private readonly UniqueKeyGeneratorTKey _uniqueKeyGenerator; private readonly IEmbeddingGeneratorstring, Embeddingfloat _embeddingGenerator; public PdfDataLoader( UniqueKeyGeneratorTKey uniqueKeyGenerator, IVectorStoreRecordCollectionTKey, TextSnippetTKey vectorStoreRecordCollection, IEmbeddingGeneratorstring, Embeddingfloat embeddingGenerator) { _vectorStoreRecordCollection vectorStoreRecordCollection; _uniqueKeyGenerator uniqueKeyGenerator; _embeddingGenerator embeddingGenerator; } public async Task LoadPdf(string pdfPath, int batchSize, int betweenBatchDelayInMs) { // Create the collection if it doesnt 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 TextSnippetTKey { 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 IEnumerableRawContent 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 }; } } }}2VectorDataSearcher和上一篇文章介绍的内容类似主要做语义搜索获取TopN个近似内容。public class VectorDataSearcherTKey where TKey : notnull{ private readonly IVectorStoreRecordCollectionTKey, TextSnippetTKey _vectorStoreRecordCollection; private readonly IEmbeddingGeneratorstring, Embeddingfloat _embeddingGenerator; public VectorDataSearcher(IVectorStoreRecordCollectionTKey, TextSnippetTKey vectorStoreRecordCollection, IEmbeddingGeneratorstring, Embeddingfloat embeddingGenerator) { _vectorStoreRecordCollection vectorStoreRecordCollection; _embeddingGenerator embeddingGenerator; } [Description(Get top N text search results from vector store by users query (N is 1 by default))] [return: Description(Collection of text search result)] public async TaskIEnumerableTextSearchResult 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(TextSnippetTKey.TextEmbedding) }; var searchResults await _vectorStoreRecordCollection.VectorizedSearchAsync(queryEmbedding, searchOptions); var responseResults new ListTextSearchResult(); 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; }}3UniqueKeyGenerator这个主要是一个代理后续我们主要使用Guid作为Key。public sealed class UniqueKeyGeneratorTKey(FuncTKey generator) where TKey : notnull{ /// summary /// Generate a unique key. /// /summary /// returnsThe 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)下面我们分解几个核心步骤来实现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: EdisonTalk2025 } }, RAG: { CollectionName: oneflower, DataLoadingBatchSize: 10, DataLoadingBetweenBatchDelayInMilliseconds: 1000, PdfFileFolder: Documents }}Step2. 加载配置var config new ConfigurationBuilder() .AddJsonFile($appsettings.json) .Build();Step3. 初始化ChatClient、Embedding生成器 以及 VectorStore# ChatClientvar 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();# EmbeddingGeneratorvar embedingGenerator new OllamaEmbeddingGenerator(new Uri(config[Embeddings:Ollama:EndPoint]), config[Embeddings:Ollama:ModelId]);# VectorStorevar vectorStore new QdrantVectorStore(new QdrantClient(host: config[VectorStores:Qdrant:Host], port: int.Parse(config[VectorStores:Qdrant:Port]), apiKey: config[VectorStores:Qdrant:ApiKey]));Step4. 导入PDF文档到VectorStorevar ragConfig config.GetSection(RAG);// Get the unique key genratorvar uniqueKeyGenerator new UniqueKeyGeneratorGuid(() Guid.NewGuid());// Get the collection in qdrantvar ragVectorRecordCollection vectorStore.GetCollectionGuid, TextSnippetGuid(ragConfig[CollectionName]);// Get the PDF loadervar pdfLoader new PdfDataLoaderGuid(uniqueKeyGenerator, ragVectorRecordCollection, embedingGenerator);// Start to load PDF to VectorStorevar 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对话机器人重点关注这里的提示词模板我们做了几件事情1给AI设定一个人设鲜花网站的AI对话机器人告知其负责的职责。2告诉AI要使用相关工具向量搜索插件进行相关背景信息的搜索获取然后将结果 连同 用户的问题 组成一个新的提示词最后将这个新的提示词发给大模型进行处理。3告诉AI在输出信息时要把引用的文档信息链接也一同输出。Console.WriteLine([LOG] Now starting the chatting window for you...);Console.ForegroundColor ConsoleColor.Green;var promptTemplate 你是一个专业的AI聊天机器人为易速鲜花网站的所有员工提供信息咨询服务。 请使用下面的提示使用工具从向量数据库中获取相关信息来回答用户提出的问题 {{#with (SearchPlugin-GetTextSearchResults question)}} {{#each this}} Value: {{Value}} Link: {{Link}} Score: {{Score}} ----------------- {{/each}} {{/with}} 输出要求请在回复中引用相关信息的地方包括对相关信息的引用。 用户问题: {{question}} ;var chatHistory new ListChatMessage{ new ChatMessage(ChatRole.System, 你是一个专业的AI聊天机器人为易速鲜花网站的所有员工提供信息咨询服务。)};var vectorSearchTool new VectorDataSearcherGuid(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 didnt 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();}调试验证首先看看PDF导入中的log显示其次验证下Qdrant中是否新增了导入的PDF文档数据最后和AI机器人对话咨询问题问题1及其回复问题2及其回复更多的问题就留给你去调戏了。小结本文介绍了如何基于Microsoft.Extensions.AI Microsoft.Extensions.VectorData 一步一步地实现一个RAG检索增强生成应用相信会对你有所帮助。如果你也是.NET程序员希望参与AI应用的开发那就快快了解和使用基于Microsoft.Extensioins.AI Microsoft.Extensions.VectorData 的生态组件库吧。示例源码GitHub https://github.com/edisontalk/EdisonTalk.AI.Agents 点击本文底部“阅读原文”即可直达参考内容Semantic Kernel .NET Sample Demos : https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/Demos?wt.mc_idMVP_397012推荐内容Microsoft Learn: https://learn.microsoft.com/zh-cn/dotnet/ai/ai-extensions?wt.mc_idMVP_397012eShopSupport: https://github.com/dotnet/eShopSupport?wt.mc_idMVP_397012devblogs: https://devblogs.microsoft.com/dotnet/e-shop-infused-with-ai-comprehensive-intelligent-dotnet-app-sample?wt.mc_idMVP_397012年终总结Edison的2024年终总结数字化转型我在传统企业做数字化转型C#刷算法题C#刷剑指Offer算法题系列文章目录C#刷设计模式C#刷23种设计模式系列文章目录.NET面试.NET开发面试知识体系

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询