From 657677a43d4b5603970d4d9498dbbedd14fd02c8 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 18 Mar 2024 14:36:41 -0500 Subject: [PATCH 1/2] add botsharp options --- .../BotSharp.Abstraction.csproj | 2 +- .../RichContentJsonConverter .cs | 2 +- .../Options/BotSharpOptions.cs | 47 +++++++++++++++++++ .../BotSharp.Core/BotSharpCoreExtensions.cs | 41 ++++++++++++++-- .../ConversationService.SendMessage.cs | 1 - .../Services/ConversationStorage.cs | 5 +- .../FileRepository.Conversation.cs | 13 ++--- .../Hooks/ChatHubConversationHook.cs | 23 ++++----- .../Hooks/StreamingLogHook.cs | 24 ++++------ .../Hooks/WelcomeHook.cs | 21 +++------ src/WebStarter/Program.cs | 10 ++-- 11 files changed, 129 insertions(+), 60 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj index 560b0f022..bbade11e3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj +++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs index 6545214e7..a490415dd 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs @@ -47,4 +47,4 @@ public override void Write(Utf8JsonWriter writer, IRichMessage value, JsonSerial { JsonSerializer.Serialize(writer, value, value.GetType(), options); } -} +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs new file mode 100644 index 000000000..9d6f5ef03 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs @@ -0,0 +1,47 @@ +using BotSharp.Abstraction.Messaging.JsonConverters; +using System.Text.Json; + +namespace BotSharp.Abstraction.Options; + +public class BotSharpOptions +{ + private readonly static JsonSerializerOptions defaultJsonOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + AllowTrailingCommas = true, + WriteIndented = true, + Converters = + { + new RichContentJsonConverter(), + new TemplateMessageJsonConverter() + } + }; + + private JsonSerializerOptions _jsonSerializerOptions; + + public JsonSerializerOptions JsonSerializerOptions + { + get + { + return _jsonSerializerOptions ?? defaultJsonOptions; + } + set + { + if (value == null) + { + _jsonSerializerOptions = defaultJsonOptions; + } + else + { + _jsonSerializerOptions = value; + } + } + } + + + public BotSharpOptions() + { + + } +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index b3b2a6291..7cae4899b 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -1,21 +1,24 @@ using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Repositories; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; -using BotSharp.Abstraction.Routing; using BotSharp.Core.Plugins; using BotSharp.Abstraction.Settings; +using BotSharp.Abstraction.Options; +using BotSharp.Abstraction.Messaging; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; namespace BotSharp.Core; public static class BotSharpCoreExtensions { - public static IServiceCollection AddBotSharpCore(this IServiceCollection services, IConfiguration config) + public static IServiceCollection AddBotSharpCore(this IServiceCollection services, IConfiguration config, Action? configOptions = null) { services.AddScoped(); services.AddScoped(); RegisterPlugins(services, config); + ConfigureBotSharpOptions(services, configOptions); return services; } @@ -53,6 +56,38 @@ public static IApplicationBuilder UseBotSharp(this IApplicationBuilder app) return app; } + private static void ConfigureBotSharpOptions(IServiceCollection services, Action? configure) + { + var options = new BotSharpOptions(); + if (configure != null) + { + configure(options); + } + + ValidateJsonConverters(options); + services.AddSingleton(options); + } + + private static void ValidateJsonConverters(BotSharpOptions options) + { + var jsonConverters = options.JsonSerializerOptions.Converters; + if (jsonConverters != null) + { + // Remove the default rich message/template message converters if there are user-defined converters + if (jsonConverters.Count(x => x.Type?.Name == nameof(IRichMessage)) > 1) + { + var defaultRichMessageConverter = jsonConverters.First(x => x.Type?.Name == nameof(IRichMessage)); + jsonConverters.Remove(defaultRichMessageConverter); + } + + if (jsonConverters.Count(x => x.Type?.Name == nameof(ITemplateMessage)) > 1) + { + var defaultTemplateMessageConverter = jsonConverters.First(x => x.Type?.Name == nameof(ITemplateMessage)); + jsonConverters.Remove(defaultTemplateMessageConverter); + } + } + } + public static void RegisterPlugins(IServiceCollection services, IConfiguration config) { var pluginSettings = new PluginSettings(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 9aaa75eff..e420152dc 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Routing.Settings; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index f6c2a02c8..24ee97be6 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Messaging.JsonConverters; using BotSharp.Abstraction.Messaging.Models.RichContent; +using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Repositories; using System; using System.IO; @@ -15,6 +16,7 @@ public class ConversationStorage : IConversationStorage public ConversationStorage( BotSharpDatabaseSettings dbSettings, + BotSharpOptions options, IServiceProvider services) { _dbSettings = dbSettings; @@ -64,6 +66,7 @@ public void Append(string conversationId, RoleDialogModel dialog) AgentId = agentId, MessageId = dialog.MessageId, SenderId = dialog.SenderId, + FunctionName = dialog.FunctionName, CreateTime = dialog.CreatedAt }; @@ -93,7 +96,7 @@ public List GetDialogs(string conversationId) var role = meta.Role; var currentAgentId = meta.AgentId; var messageId = meta.MessageId; - var function = role == AgentRole.Function ? meta.FunctionName : null; + var function = meta.FunctionName; var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId; var createdAt = meta.CreateTime; var richContent = !string.IsNullOrEmpty(dialog.RichContent) ? diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index b55fb3654..d9ea0b5c0 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -408,16 +408,12 @@ private List CollectDialogElements(string dialogDir) Role = blocks[1], AgentId = blocks[2], MessageId = blocks[3], - FunctionName = blocks[1] == AgentRole.Function ? blocks[4] : null, - SenderId = blocks[1] == AgentRole.Function ? null : blocks[4], + SenderId = !string.IsNullOrWhiteSpace(blocks[4]) ? blocks[4] : null, + FunctionName = !string.IsNullOrWhiteSpace(blocks[5]) ? blocks[5] : null, CreateTime = DateTime.Parse(blocks[0]) }; - string? richContent = null; - if (blocks.Count() > 5) - { - richContent = blocks[5]; - } + var richContent = blocks.Count() > 6 ? blocks[6] : null; dialogs.Add(new DialogElement(meta, trimmed, richContent)); } } @@ -433,8 +429,7 @@ private List ParseDialogElements(List dialogs) { var meta = element.MetaData; var createTime = meta.CreateTime.ToString("MM/dd/yyyy hh:mm:ss.fff tt", CultureInfo.InvariantCulture); - var source = meta.FunctionName ?? meta.SenderId; - var metaStr = $"{createTime}|{meta.Role}|{meta.AgentId}|{meta.MessageId}|{source}|{element.RichContent}"; + var metaStr = $"{createTime}|{meta.Role}|{meta.AgentId}|{meta.MessageId}|{meta.SenderId}|{meta.FunctionName}|{element.RichContent}"; dialogTexts.Add(metaStr); var content = $" - {element.Content}"; dialogTexts.Add(content); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 045a707a4..0ebff14c5 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -1,5 +1,5 @@ using BotSharp.Abstraction.Messaging.Enums; -using BotSharp.Abstraction.Messaging.JsonConverters; +using BotSharp.Abstraction.Options; using Microsoft.AspNetCore.SignalR; namespace BotSharp.Plugin.ChatHub.Hooks; @@ -9,24 +9,16 @@ public class ChatHubConversationHook : ConversationHookBase private readonly IServiceProvider _services; private readonly IHubContext _chatHub; private readonly IUserIdentity _user; - private readonly JsonSerializerOptions _serializerOptions; + private readonly BotSharpOptions _options; public ChatHubConversationHook(IServiceProvider services, IHubContext chatHub, + BotSharpOptions options, IUserIdentity user) { _services = services; _chatHub = chatHub; _user = user; - - _serializerOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new RichContentJsonConverter(), - new TemplateMessageJsonConverter(), - } - }; + _options = options; } public override async Task OnConversationInitialized(Conversation conversation) @@ -67,6 +59,11 @@ public override async Task OnMessageReceived(RoleDialogModel message) await base.OnMessageReceived(message); } + public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg) + { + await this.OnMessageReceived(message); + } + public override async Task OnResponseGenerated(RoleDialogModel message) { var conv = _services.GetRequiredService(); @@ -84,7 +81,7 @@ public override async Task OnResponseGenerated(RoleDialogModel message) LastName = "Assistant", Role = AgentRole.Assistant } - }, _serializerOptions); + }, _options.JsonSerializerOptions); // Send typing-off to client await _chatHub.Clients.User(_user.Id).SendAsync("OnSenderActionGenerated", new ConversationSenderActionModel diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 1dac0af1d..ffc1c987d 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -3,6 +3,7 @@ using BotSharp.Abstraction.Loggers; using BotSharp.Abstraction.Loggers.Enums; using BotSharp.Abstraction.Loggers.Models; +using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing; using Microsoft.AspNetCore.SignalR; @@ -12,7 +13,7 @@ namespace BotSharp.Plugin.ChatHub.Hooks; public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IRoutingHook { private readonly ConversationSetting _convSettings; - private readonly JsonSerializerOptions _serializerOptions; + private readonly BotSharpOptions _options; private readonly IServiceProvider _services; private readonly IHubContext _chatHub; private readonly IConversationStateService _state; @@ -22,6 +23,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR public StreamingLogHook( ConversationSetting convSettings, + BotSharpOptions options, IServiceProvider serivces, IHubContext chatHub, IConversationStateService state, @@ -30,19 +32,13 @@ public StreamingLogHook( IRoutingContext routingCtx) { _convSettings = convSettings; + _options = options; _services = serivces; _chatHub = chatHub; _state = state; _user = user; _agentService = agentService; _routingCtx = routingCtx; - _serializerOptions = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - AllowTrailingCommas = true, - WriteIndented = true, - }; } public override async Task OnMessageReceived(RoleDialogModel message) @@ -63,7 +59,7 @@ public override async Task OnPostbackMessageReceived(RoleDialogModel message, Po { var conversationId = _state.GetConversationId(); var log = $"{message.Content}"; - var replyContent = JsonSerializer.Serialize(replyMsg, _serializerOptions); + var replyContent = JsonSerializer.Serialize(replyMsg, _options.JsonSerializerOptions); log += $"\r\n```json\r\n{replyContent}\r\n```"; var input = new ContentLogInputModel(conversationId, message) @@ -90,7 +86,7 @@ public override async Task OnFunctionExecuted(RoleDialogModel message) var conversationId = _state.GetConversationId(); var agent = await _agentService.LoadAgent(message.CurrentAgentId); message.FunctionArgs = message.FunctionArgs ?? "{}"; - var args = JsonSerializer.Serialize(JsonDocument.Parse(message.FunctionArgs), _serializerOptions); + var args = JsonSerializer.Serialize(JsonDocument.Parse(message.FunctionArgs), _options.JsonSerializerOptions); var log = $"*{message.FunctionName}*\r\n```json\r\n{args}\r\n```\r\n=> {message.Content?.Trim()}"; var input = new ContentLogInputModel(conversationId, message) @@ -145,7 +141,7 @@ public override async Task OnResponseGenerated(RoleDialogModel message) var log = $"{message.Content}"; if (message.RichContent != null && message.RichContent.Message.RichType != "text") { - var richContent = JsonSerializer.Serialize(message.RichContent, _serializerOptions); + var richContent = JsonSerializer.Serialize(message.RichContent, _options.JsonSerializerOptions); log += $"\r\n```json\r\n{richContent}\r\n```"; } @@ -248,7 +244,7 @@ public async Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, Rol { var conversationId = _state.GetConversationId(); var agent = await _agentService.LoadAgent(message.CurrentAgentId); - var log = JsonSerializer.Serialize(instruct, _serializerOptions); + var log = JsonSerializer.Serialize(instruct, _options.JsonSerializerOptions); log = $"```json\r\n{log}\r\n```"; var input = new ContentLogInputModel(conversationId, message) @@ -293,7 +289,7 @@ private string BuildContentLog(ContentLogInputModel input) CreateTime = DateTime.UtcNow }; - var json = JsonSerializer.Serialize(output, _serializerOptions); + var json = JsonSerializer.Serialize(output, _options.JsonSerializerOptions); var convSettings = _services.GetRequiredService(); if (convSettings.EnableContentLog) @@ -322,6 +318,6 @@ private string BuildStateLog(string conversationId, Dictionary s db.SaveConversationStateLog(log); } - return JsonSerializer.Serialize(log, _serializerOptions); + return JsonSerializer.Serialize(log, _options.JsonSerializerOptions); } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs index 9c8237e7e..22c02fdf1 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs @@ -1,8 +1,8 @@ using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Templating; -using BotSharp.Abstraction.Messaging.JsonConverters; using Microsoft.AspNetCore.SignalR; +using BotSharp.Abstraction.Options; namespace BotSharp.Plugin.ChatHub.Hooks; @@ -12,26 +12,19 @@ public class WelcomeHook : ConversationHookBase private readonly IHubContext _chatHub; private readonly IUserIdentity _user; private readonly IConversationStorage _storage; - private readonly JsonSerializerOptions _serializerOptions; + private readonly BotSharpOptions _options; + public WelcomeHook(IServiceProvider services, IHubContext chatHub, IUserIdentity user, - IConversationStorage storage) + IConversationStorage storage, + BotSharpOptions options) { _services = services; _chatHub = chatHub; _user = user; _storage = storage; - - _serializerOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new RichContentJsonConverter(), - new TemplateMessageJsonConverter(), - } - }; + _options = options; } public override async Task OnUserAgentConnectedInitially(Conversation conversation) @@ -66,7 +59,7 @@ public override async Task OnUserAgentConnectedInitially(Conversation conversati LastName = "", Role = AgentRole.Assistant } - }, _serializerOptions); + }, _options.JsonSerializerOptions); await Task.Delay(300); diff --git a/src/WebStarter/Program.cs b/src/WebStarter/Program.cs index 4fd7e3a09..f30882a42 100644 --- a/src/WebStarter/Program.cs +++ b/src/WebStarter/Program.cs @@ -3,6 +3,7 @@ using BotSharp.Logger; using BotSharp.Plugin.ChatHub; using Serilog; +using BotSharp.Abstraction.Messaging.JsonConverters; var builder = WebApplication.CreateBuilder(args); @@ -28,9 +29,12 @@ }; // Add BotSharp - builder.Services.AddBotSharpCore(builder.Configuration) - .AddBotSharpOpenAPI(builder.Configuration, allowedOrigins, builder.Environment, true) - .AddBotSharpLogger(builder.Configuration); + builder.Services.AddBotSharpCore(builder.Configuration, options => + { + options.JsonSerializerOptions.Converters.Add(new RichContentJsonConverter()); + options.JsonSerializerOptions.Converters.Add(new TemplateMessageJsonConverter()); + }).AddBotSharpOpenAPI(builder.Configuration, allowedOrigins, builder.Environment, true) + .AddBotSharpLogger(builder.Configuration); // Add SignalR for WebSocket builder.Services.AddSignalR(); From a6eee9187b3a717d83ab4474f603a0438894d717 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 18 Mar 2024 14:52:38 -0500 Subject: [PATCH 2/2] refine code --- .../Options/BotSharpOptions.cs | 7 +----- .../BotSharp.Core/BotSharpCoreExtensions.cs | 23 ++++--------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs index 9d6f5ef03..e3a2f2ef9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs @@ -10,12 +10,7 @@ public class BotSharpOptions PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, AllowTrailingCommas = true, - WriteIndented = true, - Converters = - { - new RichContentJsonConverter(), - new TemplateMessageJsonConverter() - } + WriteIndented = true }; private JsonSerializerOptions _jsonSerializerOptions; diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 7cae4899b..44177b093 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -7,6 +7,7 @@ using BotSharp.Abstraction.Messaging; using System.Text.Json.Serialization; using Microsoft.Extensions.Options; +using BotSharp.Abstraction.Messaging.JsonConverters; namespace BotSharp.Core; @@ -64,28 +65,14 @@ private static void ConfigureBotSharpOptions(IServiceCollection services, Action configure(options); } - ValidateJsonConverters(options); + AddDefaultJsonConverters(options); services.AddSingleton(options); } - private static void ValidateJsonConverters(BotSharpOptions options) + private static void AddDefaultJsonConverters(BotSharpOptions options) { - var jsonConverters = options.JsonSerializerOptions.Converters; - if (jsonConverters != null) - { - // Remove the default rich message/template message converters if there are user-defined converters - if (jsonConverters.Count(x => x.Type?.Name == nameof(IRichMessage)) > 1) - { - var defaultRichMessageConverter = jsonConverters.First(x => x.Type?.Name == nameof(IRichMessage)); - jsonConverters.Remove(defaultRichMessageConverter); - } - - if (jsonConverters.Count(x => x.Type?.Name == nameof(ITemplateMessage)) > 1) - { - var defaultTemplateMessageConverter = jsonConverters.First(x => x.Type?.Name == nameof(ITemplateMessage)); - jsonConverters.Remove(defaultTemplateMessageConverter); - } - } + options.JsonSerializerOptions.Converters.Add(new RichContentJsonConverter()); + options.JsonSerializerOptions.Converters.Add(new TemplateMessageJsonConverter()); } public static void RegisterPlugins(IServiceCollection services, IConfiguration config)