翼度科技»论坛 编程开发 .net 查看内容

WinUI(WASDK)使用BotSharp框架开发多智能体桌面机器人管理助手(生图开关

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
前言

大语言模型(Large Language Models, LLMs)近年来在各行各业中展现出了巨大的潜力和影响力。从自然语言处理到自动化客服,从内容生成到智能助手,LLMs正在改变我们与技术互动的方式。随着技术的不断进步,LLMs的应用场景也在不断扩展,成为未来发展的重要趋势。这篇文章将介绍如何使用WinUI(WASDK)和BotSharp开发一个多智能体桌面机器人管理助手,展示LLMs在实际应用中的强大功能和广阔前景。
技术介绍

.NET

.NET 是免费的、开源的、跨平台的框架,用于构建新式应用和强大的云服务。

WinUI(WASDK)

Windows 应用 SDK 是一组新的开发人员组件和工具,它们代表着 Windows 应用开发平台的下一步发展。 Windows 应用 SDK 提供一组统一的 API 和工具,可供从 Windows 11 到 Windows 10 版本 1809 上的任何桌面应用以一致的方式使用。
Windows 应用 SDK 不会用 C++ 替换 Windows SDK 或现有桌面 Windows 应用类型,例如 .NET(包括 Windows 窗体和 WPF)和桌面 Win32。 相反,Windows 应用 SDK 使用一组通用 API 来补充这些现有工具和应用类型,开发人员可以在这些平台上依赖这些 API 来执行操作。 有关更多详细信息,请参阅 Windows 应用 SDK 的优势。

BotSharp

BotSharp 是一个开源应用程序框架,可加快将 LLM 集成到您当前的业务系统中的速度。本项目涉及自然语言理解和音频处理技术,旨在推动智能机器人助手在信息系统中的开发和应用。开箱即用的机器学习算法使普通程序员能够更快、更轻松地开发人工智能应用程序。
BotSharp 是一个高度兼容且高度可扩展的平台构建器。它严格按照组件原则,将平台构建器中需要的每个部分解耦。因此,您可以选择不同的 UI/UX,或者选择不同的 NLP 标记器,或者选择更高级的算法来执行 NER 任务。它们都是基于未加密的接口进行调制的。

大语言模型的函数调用(这个是理解BotSharp框架的核心知识点)

函数调用允许您将模型连接到外部工具和系统。这对于许多事情都很有用,例如为 AI 助手提供功能,或在应用程序和模型之间构建深度集成。
openai官方文档函数调用介绍文档
助手功能介绍

助手名为电子脑壳本身是负责开源硬件ElectronBot桌面机器人和瀚文键盘的操作配置。
新版本重构方向是深度集成多智能体交互的能力,目前新版本重点优化功能如下:

  • 增强对话能力,添加大语言模型对话能力。
  • 增强交互,添加生图和自然语言理解进行硬件控制,例如生图之后直接设置到桌面机器人屏幕上或者键盘屏幕上。
  • 增强语音对话能力。
  • 以前硬编码的逻辑,现在都可以通过大语言模型的函数调用进行语义化理解,更灵活。

目前软件还在开发中,但是BotSharp和大语言模型交互的功能已经开发差不多了,所以编写这篇博客记录一下。
博客演示的代码是在电子脑壳源码的dev分支。
目前文字大模型使用的是阿里的通义千问2.5 72b(qwen2.5-72b-instruct)社区开源版本,图片大模型使用的是通义万相(wanx-v1)。
可以通过聊天进行天气查询,开关等,以及学单词,生图片等等其他功能,这些功能可以和上图的一些机器人进行互动。
演示效果如下:

代码实现过程

1. 实现BotSharp的LiteDB存储

做这个实现的原因是我想替换掉框架本身默认的文件存储,因为我是开发桌面程序,所以mongodb这类的数据库也不在考虑范围,LiteDB也是文档数据库,使用上也比较简单,就作为数据存储的选项了。而且原本的软件的数据也可以都迁移到LiteDB上,算是统一了一些。

源码我fork到我的名下了修改代码在litedb分支
2. 针对OpenAI插件进行改造

做这个操作的原因是为了兼容国内的大语言模型,有些时候OpenAI访问不了,可以通过国内的一些模型进行替换,例如智普清言,通义千问,以及讯飞的一些模型。
通过代码兼容自定义Endpoint,就可以随意切换兼容的模型了。
代码段如下防止图挂了:
  1.     public static OpenAIClient GetClient(string provider, string model, IServiceProvider services)
  2.     {
  3.         var settingsService = services.GetRequiredService<ILlmProviderService>();
  4.         var settings = settingsService.GetSetting(provider, model);
  5.         var options = string.IsNullOrEmpty(settings.Endpoint)
  6.             ? null
  7.             : new OpenAIClientOptions { Endpoint = new Uri(settings.Endpoint) };
  8.         return new OpenAIClient(new ApiKeyCredential(settings.ApiKey), options);
  9.     }
复制代码

3. 基于核心模块编写UI代码

BotSharp本身的demo是基于web服务编写的,有一套webui和一套封装好的api,但是我是基于桌面程序编写的,所以我就借鉴了社区一些开源的软件的代码,以及一些设计理念,整合了一个简单的聊天UI,针对发送消息,聊天列表,以及生成产物的保存等。
最左边的是机器人功能区域,中间为聊天区域,右侧为灵犀空间,生成的图片,单词以及天气内容都会保存一下,便于后期的查找。

4. 功能模块的智能体代码

代码目录结构如下:

以生图函数为例 下面是传给大模型的生图函数定义
  1. {
  2.   "name": "custom_generate_image",
  3.   "description": "如果用户想生成图片可以调用此方法进行图片生成。",
  4.   "parameters": {
  5.     "type": "object",
  6.     "properties": {
  7.       "image_name": {
  8.         "type": "string",
  9.         "description": "根据用户描述给图片起个名称。"
  10.       },
  11.       "image_description": {
  12.         "type": "string",
  13.         "description": "用户进行的图片描述。"
  14.       }
  15.     },
  16.     "required": [ "image_description" ]
  17.   }
  18. }
复制代码
关联的生图函数实现类,可以被大语言模型调用。
  1. public class CustomGenerateImageFn : IFunctionCallback
  2. {
  3.     public string Name => "custom_generate_image"; //和json配置的函数名字匹配
  4.     private readonly IServiceProvider _service;
  5.     private readonly IBotToolService _botToolService;
  6.     private readonly JsonSerializerOptions _options;
  7.     private readonly ILingxiSpaceService _lingxiSpaceService;
  8.     private readonly IConversationService _conversationService;
  9.     public CustomGenerateImageFn(IServiceProvider service,
  10.         IBotToolService botToolService,
  11.         ILingxiSpaceService lingxiSpaceService,
  12.         IConversationService conversationService)
  13.     {
  14.         _service = service;
  15.         _options = new JsonSerializerOptions
  16.         {
  17.             PropertyNameCaseInsensitive = true,
  18.             PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  19.             WriteIndented = true,
  20.             AllowTrailingCommas = true,
  21.             Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
  22.         };
  23.         _botToolService = botToolService;
  24.         _lingxiSpaceService = lingxiSpaceService;
  25.         _conversationService = conversationService;
  26.     }
  27.     public async Task<bool> Execute(RoleDialogModel message)
  28.     {
  29.         // 函数反序列化之后的参数
  30.         var args = JsonSerializer.Deserialize<CustomGenerateImageFunctionArgs>(message.FunctionArgs ?? "", _options) ?? new CustomGenerateImageFunctionArgs();
  31.         message.StopCompletion = true;
  32.         var clientFactory = _service.GetRequiredService<IHttpClientFactory>();
  33.         using var httpClient = clientFactory.CreateClient();
  34.         var llmProviderService = _service.GetRequiredService<ILlmProviderService>();
  35.         var model = llmProviderService.GetSetting("tongyi", "wanx-v1");
  36.         if (model == null)
  37.         {
  38.             return false;
  39.         }
  40.         var request = new GenerateImageRequest
  41.         {
  42.             Model = "wanx-v1",
  43.             Input = new GenerateImageInput
  44.             {
  45.                 Prompt = args.ImageDescription
  46.             },
  47.             Parameters = new GenerateImageParameters
  48.             {
  49.                 Style = "<auto>",
  50.                 Size = "1024*1024",
  51.                 N = 1
  52.             }
  53.         };
  54.         var generateImageUrl = $"{model.Endpoint.TrimEnd('/')}/services/aigc/text2image/image-synthesis";
  55.         // 添加认证头部请求头
  56.         httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", model.ApiKey);
  57.         httpClient.DefaultRequestHeaders.Add("X-DashScope-Async", "enable");
  58.         var result = await httpClient.PostAsJsonAsync(generateImageUrl, request);
  59.         if (!result.IsSuccessStatusCode)
  60.         {
  61.             return false;
  62.         }
  63.         var taskContent = await result.Content.ReadAsStringAsync();
  64.         var resultData = JsonSerializer.Deserialize<GenerateImageResponse>(taskContent, _options);
  65.         var taskUrl = $"{model.Endpoint.TrimEnd('/')}/tasks/{resultData?.Output.TaskId}";
  66.         var maxRetries = 5;
  67.         var retryCount = 0;
  68.         while (retryCount < maxRetries)
  69.         {
  70.             var taskResult = await httpClient.GetAsync(taskUrl);
  71.             if (!taskResult.IsSuccessStatusCode)
  72.             {
  73.                 return false;
  74.             }
  75.             var taskResultContent = await taskResult.Content.ReadAsStringAsync();
  76.             var taskResponse = JsonSerializer.Deserialize<ImageTaskResponse>(taskResultContent, _options);
  77.             if (taskResponse?.Output.TaskStatus == "SUCCEEDED")
  78.             {
  79.                 var url = taskResponse?.Output.Results.FirstOrDefault()?.Url;
  80.                 if (string.IsNullOrEmpty(url))
  81.                 {
  82.                     return false;
  83.                 }
  84.                 // 下载图片并转换为Base64
  85.                 var imageBytes = await httpClient.GetByteArrayAsync(url);
  86.                 var base64Image = Convert.ToBase64String(imageBytes);
  87.                 var generateImageContent = new GenerateImageContent
  88.                 {
  89.                     Name = args.ImageName,
  90.                     Description = args.ImageDescription,
  91.                     ImageData = $"data:{MediaTypeNames.Image.Png};base64,{base64Image}"
  92.                 };
  93.                 //保存生成的图片
  94.                 var lingxiSpace = await _lingxiSpaceService.AddAsync(new LingxiSpace
  95.                 {
  96.                     Id = Guid.NewGuid().ToString(),
  97.                     ConversationId = _conversationService.ConversationId,
  98.                     Content = JsonSerializer.SerializeToDocument(generateImageContent, _options),
  99.                     Name = args.ImageName,
  100.                     Desc = args.ImageDescription,
  101.                     Type = LingxiSpaceType.Image,
  102.                     CreatedTime = DateTime.UtcNow
  103.                 });
  104.                 WeakReferenceMessenger.Default.Send(lingxiSpace);
  105.                 break;
  106.             }
  107.             await Task.Delay(10000); // 等待10秒后再次轮询
  108.             retryCount++;
  109.         }
  110.         return retryCount < maxRetries;
  111.     }
复制代码
5. 功能模块的加载

BotSharp采用插件模式开发,需要在配置中配置要加载的模块,然后项目启动就会加载模块注入服务。
目前我启用的模块配置如下:
  1.   "PluginLoader": {
  2.     "Assemblies": [
  3.       "BotSharp.Core",
  4.       "BotSharp.Logger",
  5.       "BotSharp.Plugin.OpenAI",
  6.       "BotSharp.Plugin.AzureOpenAI",
  7.       "BotSharp.Plugin.MetaGLM",
  8.       "BotSharp.Plugin.LiteDBStorage",
  9.       "Verdure.Braincase.Copilot.Plugin"
  10.     ]
  11.   }
复制代码
服务注入也很简单,主要是AddBotSharpCore的注入,BotSharp本身是有用户的概念的,所以我实现了一个BotUserIdentity做了用户的默认数据初始化,大家可以根据需要操作。
  1.            // add botsharp
  2.            .AddTransient<AgentViewModel>()
  3.            .AddTransient<AgentPage>()
  4.            .AddTransient<ChatViewModel>()
  5.            .AddTransient<LingxiSpaceViewModel>()
  6.            .AddTransient<ILingxiSpaceService, LiteDBLingxiSpaceService>()
  7.            .AddBotSharpCore(config, options =>
  8.            {
  9.                options.JsonSerializerOptions.Converters.Add(new RichContentJsonConverter());
  10.                options.JsonSerializerOptions.Converters.Add(new TemplateMessageJsonConverter());
  11.            })
  12.            .AddSingleton(dbSettings)
  13.            .AddHttpContextAccessor()
  14.            .AddScoped<IUserIdentity, BotUserIdentity>()
  15.            .AddScoped<IBotToolService, BotToolService>()
  16.            .AddScoped<IBotIotService, BotIotService>()
  17.            .AddBotSharpLogger(config)
复制代码
如果看到这里,大家还是一头雾水的话,可以多看看BotSharp的设计理念,当然如果有需要我可以再写一篇BotSharp的讲解文章。
心得体会

随着大模型能力的提升,大模型的应用场景也会越来越多,以后的大模型应该会作为基础设施供人们使用,基于大模型进行开发的岗位应该会越来越多,感觉大模型真的是生产力工具,我最近在开发这些功能的时候,也会借助Github Copilot进行一些功能的开发,效率高很多。
希望在未来人类是驾驭AI,而不是被AI给取代了。
参考推荐文档项目如下:


来源:https://www.cnblogs.com/GreenShade/p/18570348
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具