前言
这算是一篇学习记载博客了,主要是学习语义内核(Semantic Kernel)的实践,以及Aspire进行全栈开发的上手体验,我是接纳Aspire同时启动API服务,Blazor前端服务以及WinUI的桌面端项目,同时进行三个项目的代码修改,团体感觉很方便,如果代码都修改了只需要启动Aspire项目,不消每个项目单独起一遍了,而且速度很快,即使是有效容器服务的情况下。
技术方案
1. 框架选型
- WebApi利用Asp.Net Core WebApi实现。
- Bing搜索结果获取,以及网页解析内容提取利用的是PlayWright库。
- 网页内容总结利用的是WinUI编写的客户端,结合语义内核(Semantic Kernel)调用国产智普清言LLM。
- 后台管理页面利用的Blazor,不过只是一个demo页面。
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结果展示如下:
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;
- }
复制代码 结果如下:
到此总结就已经完成了,大家可以去看看代码,看看有没有资助。
个人心得体会
在进行一段时间的学习之后,对大语言模型有了一些全面的认识,意识到大语言模型并不是万能的,但是它能够很轻松的做到我们之前要很复杂才能做到的事情。轻松做到的前提就是要给出很好的提示词。
如果把大语言模型比作战斗机,那提示词就可以比作是驾驶员了,提示词的优劣直接决定大语言模型输出的准确度。
作为软件开发职员,对于提示词的编写一定要多学习,多总结才行了。
参考推荐文档项目如下:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |