使用Aspire優雅的進行全棧開發——WinUI使用Semantic Kernel調用智普清言LLM總結Asp.Net Core通過Playwright解析的網頁內容

来源:https://www.cnblogs.com/GreenShade/p/18341903
-Advertisement-
Play Games

前言 這算是一篇學習記錄博客了,主要是學習語義內核(Semantic Kernel)的實踐,以及Aspire進行全棧開發的上手體驗,我是採用Aspire同時啟動API服務,Blazor前端服務以及WinUI的桌面端項目,同時進行三個項目的代碼修改,整體感覺很方便,如果代碼都修改了只需要啟動Aspir ...


前言

這算是一篇學習記錄博客了,主要是學習語義內核(Semantic Kernel)的實踐,以及Aspire進行全棧開發的上手體驗,我是採用Aspire同時啟動API服務,Blazor前端服務以及WinUI的桌面端項目,同時進行三個項目的代碼修改,整體感覺很方便,如果代碼都修改了只需要啟動Aspire項目,不用每個項目單獨起一遍了,而且速度很快,即使是有用容器服務的情況下。

技術方案

1. 框架選型

  • WebApi使用Asp.Net Core WebApi實現。
  • Bing搜索結果獲取,以及網頁解析內容提取使用的是PlayWright庫。
  • 網頁內容總結使用的是WinUI編寫的客戶端,結合語義內核(Semantic Kernel)調用國產智普清言LLM。
  • 後臺管理頁面使用的Blazor,不過只是一個demo頁面。

img

2. 為什麼這樣選

作為一個.Net開發,肯定優先使用.Net相關的技術了,也為了能實踐最新的技術,就進行了一些新技術的選擇。

主要說明一下選擇這幾個技術框架的原因:

  • Playwright 原因是通過測試發現它的表現最好,其他類型的庫也有測試,比如Selenium,HtmlAgilityPack,HtmlAgilityPack對靜態網頁解析比較好,但是如果遇到js渲染的數據很多的頁面就不好了,Selenium比Playwright提取的內容差了一些,Playwright是通過模擬用戶操作啟動瀏覽器,然後獲取內容,感覺如果一次性處理很多的頁面應該也會負載很大。

  • Aspire 這個是因為這是微軟最新的專門給開發人員開發的工具,那既然是給開發人員做的,那肯定要體驗一把了,體驗完感覺是真的不錯,能夠節省很多的步驟。

  • 語義內核(Semantic Kernel)選擇它是因為這算是.Net社區對接大語言模型最流行的框架了,提供了很多的開箱即用的功能,對於開發智能APP幫助很大,而且社區熱度也很高。

  • 智普清言LLM 選擇它是多方面考慮的結果,第一是它相容OpenAI的介面,這樣語義內核就可以通過配置就能使用它,第二是它是支持Function Call的,也就是說它可以作為OpenAI的國內平替,用它開發一些智能APP是很好的。

  • WinUI 選擇它是個人對客戶端開發主要使用的是WinUI,而且用它對接大語言模型不把對接放到後端也是為了後面對接離線大語言模型做基礎,比如微軟的Phi3之類的。

代碼講解

本博客涉及的代碼鏈接如下:

https://github.com/GreenShadeZhang/BingSearchSummary

1. 搜索結果獲取

示例代碼如下:

先創建Playwright實例,然後進行用戶操作模擬。

   var playwright = await Playwright.CreateAsync();
   var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = true });
   var page = await browser.NewPageAsync();

   // 設置 User-Agent 和視口大小
   var js = @"Object.defineProperties(navigator, {webdriver:{get:()=>false}});";
   await page.AddInitScriptAsync(js);

   await page.GotoAsync("https://www.bing.com");

   // 模擬用戶輸入搜索關鍵詞
   await page.FillAsync("input[name=q]", keyword);
   await page.Keyboard.PressAsync("Enter");

   // 等待搜索結果載入
   await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

   // 獲取搜索結果內容
   var content = await page.ContentAsync();
   var dataList = BingSearchHelper.ParseHtmlToJson(content);
   var result = new List<BingSearchItem>();
   

將搜索結果解析成json數據如下:

這一步是因為我沒有bing搜索的訂閱,所以只能解析頁面,如果有bing搜索的訂閱這一步可以省略。

using BingSearchSummary.ApiService.Models;
using HtmlAgilityPack;

namespace BingSearchSummary.ApiService;

public class BingSearchHelper
{
    public static List<BingSearchItem> ParseHtmlToJson(string htmlContent)
    {
        var htmlDocument = new HtmlDocument();
        htmlDocument.LoadHtml(htmlContent);

        var results = new List<BingSearchItem>();

        foreach (var node in htmlDocument.DocumentNode.SelectNodes("//li[@class='b_algo']"))
        {
            var titleNode = node.SelectSingleNode(".//h2/a");
            var snippetNode = node.SelectSingleNode(".//p");
            var urlNode = node.SelectSingleNode(".//cite");

            var title = titleNode?.InnerText.Trim();
            var snippet = snippetNode?.InnerText.Trim();
            var url = urlNode?.InnerText.Trim();

            if (string.IsNullOrEmpty(title))
            {
                continue;
            }

            var searchItem = new BingSearchItem
            {
                Title = title,
                Snippet = snippet ?? "",
                Url = url ?? ""
            };

            results.Add(searchItem);
        }

        return results;
    }
}

通過上面的代碼操作,關鍵詞搜索的網頁URL就已經拿到了,然後就可以繼續進行頁面內容的解析了。

2. 網頁內容解析

客戶端通過調用介面,然後獲取關鍵詞的前三條的搜索結果和網頁內容。

    // 獲取搜索結果內容
    var content = await page.ContentAsync();
    var dataList = BingSearchHelper.ParseHtmlToJson(content);
    var result = new List<BingSearchItem>();

    foreach (var data in dataList)
    {
        if (result.Count >= 3)
        {
            break;
        }//只處理三條數據
        await page.GotoAsync(data.Url);

        var divContent = await page.QuerySelectorAsync(".content");

        divContent ??= await page.QuerySelectorAsync("body");

        if (divContent != null)
        {
            var pageContent = await divContent.InnerTextAsync();

            result.Add(new BingSearchItem
            {
                Title = data.Title,
                Url = data.Url,
                Snippet = data.Snippet,
                PageContent = pageContent
            });
        }

swagger結果展示如下:

img

3. 網頁結果總結

這部分代碼在WinUI項目中實現,WinUI調用介面獲取到結果,並通過Microsoft.SemanticKernel.PromptTemplates.Liquid庫進行消息模板動態生成消息,調用語義內核(Semantic Kernel)進行內容總結。

語義內核(Semantic Kernel)註入代碼如下:

            //測試token被刪除 已經無效 請換成自己的智普token
            builder.AddOpenAIChatCompletion(modelId: "GLM-4-Air", apiKey: "4827638425a6b9d48bea3b0599246ff2.pFjhEKShPOZE8OFd", httpClient: GetProxyClient("https://open.bigmodel.cn/api/paas/v4/chat/completions"));

            builder.Plugins.AddFromType<TimeInformationPlugin>();

            services.AddSingleton(builder.Build());

#pragma warning disable SKEXP0040 // 類型僅用於評估,在將來的更新中可能會被更改或刪除。取消此診斷以繼續。
            services.AddSingleton<IPromptTemplateFactory, LiquidPromptTemplateFactory>();
#pragma warning restore SKEXP0040 // 類型僅用於評估,在將來的更新中可能會被更改或刪除。取消此診斷以繼續。

內容總結代碼如下:

  [RelayCommand]
  private async Task SummaryAndUploadAsync(BingSearchItem item)
  {
      _chatHistory.Clear();

      SummaryProcessRingStatus = true;
      try
      {
          var arguments = new KernelArguments
          {
              ["startTime"] = DateTimeOffset.Now.ToString("hh:mm:ss tt zz", CultureInfo.CurrentCulture),

              ["userMessage"] = item.PageContent
          };

          var systemMessage = await _promptTemplateFactory.Create(new PromptTemplateConfig(_systemPromptTemplate)
          {
              TemplateFormat = "liquid",
          }).RenderAsync(_kernel, arguments);

          var userMessage = await _promptTemplateFactory.Create(new PromptTemplateConfig(_userPromptTemplate)
          {
              TemplateFormat = "liquid",
          }).RenderAsync(_kernel, arguments);

          _chatHistory.AddSystemMessage(systemMessage);

          _chatHistory.AddUserMessage(userMessage);


          var chatResult = await _chatCompletionService.GetChatMessageContentAsync(_chatHistory, _openAIPromptExecutionSettings, _kernel);

          SummaryResult = chatResult.ToString();

          await _apiClient.PostContentsAsync(new BingSearchSummaryItem
          {
              Title = item.Title,
              Summary = chatResult.ToString(),
              Url = item.Url
          });

      }
      catch (Exception ex)
      {
          System.Diagnostics.Debug.WriteLine(ex.Message);
          SummaryProcessRingStatus = false;
      }

      SummaryProcessRingStatus = false;
  }

效果如下:

img

到此總結就已經完成了,大家可以去看看代碼,看看有沒有幫助。

個人心得體會

在進行一段時間的學習之後,對大語言模型有了一些全面的認識,意識到大語言模型並不是萬能的,但是它能夠很輕鬆的做到我們之前要很複雜才能做到的事情。輕鬆做到的前提就是要給出很好的提示詞。

如果把大語言模型比作戰鬥機,那提示詞就可以比作是駕駛員了,提示詞的好壞直接決定大語言模型輸出的準確度。

作為軟體開發人員,對於提示詞的編寫一定要多學習,多總結才行了。

參考推薦文檔項目如下:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • strcat 描述 char *strcat(char *dest, const char *src) 把 src 所指向的字元串追加到 dest 所指向的字元串的結尾。 聲明 下麵是 strcat() 函數的聲明。 char *strcat(char *dest, const char *src) ...
  • Java RMI(Remote Method Invocation)是一種允許Java虛擬機之間進行通信和交互的技術。它使得遠程Java對象能夠像本地對象一樣被訪問和操作,從而簡化了分散式應用程式的開發。一些應用依然會使用 RMI 來實現通信和交互,今天的內容我們來聊聊 RMI 的那些事兒。 一、先 ...
  • 作者:小塵 鏈接:https://juejin.cn/post/7357172505961578511 前言 見過幾千行代碼的 controller嗎?我見過。 見過全是 try catch 的 controller 嗎,我見過。 見過全是欄位校驗的 controller 嗎,我見過。 見過全是業務 ...
  • memset() 描述 C 庫函數 void *memset(void *str, int c, size_t n) 用於將一段記憶體區域設置為指定的值。 memset() 函數將指定的值 c 複製到 str 所指向的記憶體區域的前 n 個位元組中,這可以用於將記憶體塊清零或設置為特定值。 在一些情況下,需 ...
  • C 庫函數 - strcmp() 描述 C 庫函數 int strcmp(const char *str1, const char *str2) 把 str1 所指向的字元串和 str2 所指向的字元串進行比較。 聲明 下麵是 strcmp() 函數的聲明。 int strcmp(const cha ...
  • 前言 今天推薦一款用 .NET 和 Vue3 實現的開源許可權管理系統。它的界面清爽乾凈,功能強大,還具備靈活的角色許可權分配功能,能夠滿足不同規模企業的管理需求。無論你是開發新手還是大神,都能輕鬆上手,快速搭建起自己的許可權管理體系。別再猶豫了,趕快來試試吧! 項目簡介 Malus是海棠的意思,顧名思義 ...
  • 委托與事件是C#中歷史比較悠久的技術,從C#1.0開始就有了,核心作用就是將方法作為參數(變數)來傳遞和使用。其中委托是基礎,需要熟練掌握,編程中常用的Lambda表達式、Action、Func都是委托,包括事件也是基於委托實現的。 ...
  • 自動生成欄位值,咱們首先想到的是主鍵列(帶 IDENTITY 的主鍵)。EF Core 預設的主鍵配置也是啟用 Identity 自增長的,而且可以自動標識主鍵。前提是代表主鍵的實體屬性名要符合以下規則: 1、名字叫 ID、id、或 Id,就是不分大小寫; 2、名字由實體類名 + Id 構成。比如, ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...