使用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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...