diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs index c8332a3df..45dbc2280 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs @@ -30,22 +30,24 @@ public class Conversation public class DialogElement { - public DialogMeta MetaData { get; set; } + public DialogMetaData MetaData { get; set; } public string Content { get; set; } + public string? RichContent { get; set; } public DialogElement() { } - public DialogElement(DialogMeta meta, string content) + public DialogElement(DialogMetaData meta, string content, string? richContent = null) { MetaData = meta; Content = content; + RichContent = richContent; } } -public class DialogMeta +public class DialogMetaData { public string Role { get; set; } public string AgentId { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs new file mode 100644 index 000000000..f69737709 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.Loggers.Enums; + +public static class ContentLogSource +{ + public const string UserInput = "user input"; + public const string Prompt = "prompt"; + public const string FunctionCall = "function call"; + public const string AgentResponse = "agent response"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ConversationContentLogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ConversationContentLogModel.cs index ee01feb8f..82a1ca114 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ConversationContentLogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ConversationContentLogModel.cs @@ -4,13 +4,19 @@ public class ConversationContentLogModel { [JsonPropertyName("conversation_id")] public string ConversationId { get; set; } + [JsonPropertyName("message_id")] public string MessageId { get; set; } + [JsonPropertyName("name")] public string? Name { get; set; } + [JsonPropertyName("role")] public string Role { get; set; } + [JsonPropertyName("source")] + public string Source { get; set; } + [JsonPropertyName("content")] public string Content { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs index 7f01132e4..33bb30fae 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Messaging.Models.RichContent.Template; using System.Text.Json; namespace BotSharp.Abstraction.Messaging.JsonConverters; @@ -6,7 +7,22 @@ public class RichContentJsonConverter : JsonConverter { public override IRichMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - throw new NotImplementedException(); + using var jsonDoc = JsonDocument.ParseValue(ref reader); + var root = jsonDoc.RootElement; + var jsonText = root.GetRawText(); + JsonElement element; + object? res = null; + + if (root.TryGetProperty("buttons", out element)) + { + res = JsonSerializer.Deserialize(jsonText, options); + } + else if (root.TryGetProperty("options", out element)) + { + res = JsonSerializer.Deserialize(jsonText, options); + } + + return res as IRichMessage; } public override void Write(Utf8JsonWriter writer, IRichMessage value, JsonSerializerOptions options) diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs index 9b9901936..db09b7f40 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Messaging.Models.RichContent.Template; using System.Text.Json; namespace BotSharp.Abstraction.Messaging.JsonConverters; @@ -6,7 +7,22 @@ public class TemplateMessageJsonConverter : JsonConverter { public override ITemplateMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - throw new NotImplementedException(); + using var jsonDoc = JsonDocument.ParseValue(ref reader); + var root = jsonDoc.RootElement; + var jsonText = root.GetRawText(); + JsonElement element; + object? res = null; + + if (root.TryGetProperty("buttons", out element)) + { + res = JsonSerializer.Deserialize(jsonText, options); + } + else if (root.TryGetProperty("options", out element)) + { + res = JsonSerializer.Deserialize(jsonText, options); + } + + return res as ITemplateMessage; } public override void Write(Utf8JsonWriter writer, ITemplateMessage value, JsonSerializerOptions options) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index cf092b510..f6c2a02c8 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -1,3 +1,6 @@ +using BotSharp.Abstraction.Messaging; +using BotSharp.Abstraction.Messaging.JsonConverters; +using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Repositories; using System; using System.IO; @@ -8,12 +11,25 @@ public class ConversationStorage : IConversationStorage { private readonly BotSharpDatabaseSettings _dbSettings; private readonly IServiceProvider _services; + private readonly JsonSerializerOptions _options; + public ConversationStorage( BotSharpDatabaseSettings dbSettings, IServiceProvider services) { _dbSettings = dbSettings; _services = services; + _options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + AllowTrailingCommas = true, + Converters = + { + new RichContentJsonConverter(), + new TemplateMessageJsonConverter(), + } + }; } public void Append(string conversationId, RoleDialogModel dialog) @@ -24,7 +40,7 @@ public void Append(string conversationId, RoleDialogModel dialog) if (dialog.Role == AgentRole.Function) { - var meta = new DialogMeta + var meta = new DialogMetaData { Role = dialog.Role, AgentId = agentId, @@ -42,7 +58,7 @@ public void Append(string conversationId, RoleDialogModel dialog) } else { - var meta = new DialogMeta + var meta = new DialogMetaData { Role = dialog.Role, AgentId = agentId, @@ -56,8 +72,8 @@ public void Append(string conversationId, RoleDialogModel dialog) { return; } - - dialogElements.Add(new DialogElement(meta, content)); + var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _options) : null; + dialogElements.Add(new DialogElement(meta, content, richContent)); } db.AppendConversationDialogs(conversationId, dialogElements); @@ -80,6 +96,8 @@ public List GetDialogs(string conversationId) var function = role == AgentRole.Function ? meta.FunctionName : null; var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId; var createdAt = meta.CreateTime; + var richContent = !string.IsNullOrEmpty(dialog.RichContent) ? + JsonSerializer.Deserialize>(dialog.RichContent, _options) : null; var record = new RoleDialogModel(role, content) { @@ -87,7 +105,8 @@ public List GetDialogs(string conversationId) MessageId = messageId, CreatedAt = createdAt, SenderId = senderId, - FunctionName = function + FunctionName = function, + RichContent = richContent }; results.Add(record); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 2ae0d1a30..e8609f6fc 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -317,7 +317,7 @@ private List CollectDialogElements(string dialogDir) var blocks = rawDialogs[i].Split("|"); var content = rawDialogs[i + 1]; var trimmed = content.Substring(4); - var meta = new DialogMeta + var meta = new DialogMetaData { Role = blocks[1], AgentId = blocks[2], @@ -326,7 +326,13 @@ private List CollectDialogElements(string dialogDir) SenderId = blocks[1] == AgentRole.Function ? null : blocks[4], CreateTime = DateTime.Parse(blocks[0]) }; - dialogs.Add(new DialogElement(meta, trimmed)); + + string? richContent = null; + if (blocks.Count() > 5) + { + richContent = blocks[5]; + } + dialogs.Add(new DialogElement(meta, trimmed, richContent)); } } return dialogs; @@ -342,7 +348,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}"; + var metaStr = $"{createTime}|{meta.Role}|{meta.AgentId}|{meta.MessageId}|{source}|{element.RichContent}"; dialogTexts.Add(metaStr); var content = $" - {element.Content}"; dialogTexts.Add(content); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 7443b82d2..19bcb5a7d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -102,7 +102,8 @@ public async Task> GetDialogs([FromRoute] string { FirstName = agent.Name, Role = message.Role, - } + }, + RichContent = message.RichContent }); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 684b02d3e..139978253 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -1,11 +1,11 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Loggers; +using BotSharp.Abstraction.Loggers.Enums; using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Repositories; -using BotSharp.Abstraction.Repositories.Filters; -using BotSharp.Abstraction.Routing.Settings; using Microsoft.AspNetCore.SignalR; +using Serilog; namespace BotSharp.Plugin.ChatHub.Hooks; @@ -37,11 +37,13 @@ public StreamingLogHook( AllowTrailingCommas = true }; } + public override async Task OnMessageReceived(RoleDialogModel message) { var conversationId = _state.GetConversationId(); var log = $"MessageId: {message.MessageId} ==>\r\n{message.Role}: {message.Content}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, _user.UserName, log, message)); + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", + BuildContentLog(conversationId, _user.UserName, log, ContentLogSource.UserInput, message)); } public async Task BeforeGenerating(Agent agent, List conversations) @@ -62,7 +64,8 @@ public override async Task OnFunctionExecuted(RoleDialogModel message) var agent = await agentService.LoadAgent(message.CurrentAgentId); var log = $"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs}) => {message.Content}"; log += $"\r\n<== MessageId: {message.MessageId}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, agent?.Name, log, message)); + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", + BuildContentLog(conversationId, agent?.Name, log, ContentLogSource.FunctionCall, message)); } /// @@ -78,30 +81,25 @@ public async Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenS var agentService = _services.GetRequiredService(); var conversationId = _state.GetConversationId(); var agent = await agentService.LoadAgent(message.CurrentAgentId); + var logSource = string.Empty; // Log routing output try { var inst = message.Content.JsonContent(); - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, agent?.Name, message.Content, message)); + logSource = ContentLogSource.AgentResponse; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", + BuildContentLog(conversationId, agent?.Name, message.Content, logSource, message)); } catch { // ignore } - string log; - if (message.Role == AgentRole.Function) - { - log = $"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs}) => {message.Content}"; - log += $"\r\n<== MessageId: {message.MessageId}"; - } - else - { - log = tokenStats.Prompt; - } - - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, agent?.Name, log, message)); + var log = tokenStats.Prompt; + logSource = ContentLogSource.Prompt; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", + BuildContentLog(conversationId, agent?.Name, log, logSource, message)); } /// @@ -127,11 +125,12 @@ public override async Task OnResponseGenerated(RoleDialogModel message) log += $"\r\n{richContent}"; } log += $"\r\n<== MessageId: {message.MessageId}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conv.ConversationId, agent?.Name, log, message)); + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", + BuildContentLog(conv.ConversationId, agent?.Name, log, ContentLogSource.AgentResponse, message)); } } - private string BuildContentLog(string conversationId, string? name, string content, RoleDialogModel message) + private string BuildContentLog(string conversationId, string? name, string logContent, string logSource, RoleDialogModel message) { var log = new ConversationContentLogModel { @@ -139,7 +138,8 @@ private string BuildContentLog(string conversationId, string? name, string conte MessageId = message.MessageId, Name = name, Role = message.Role, - Content = content, + Content = logContent, + Source = logSource, CreateTime = DateTime.UtcNow }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs index eec715dcd..32d751fe5 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs @@ -6,6 +6,7 @@ public class ConversationContentLogDocument : MongoBase public string MessageId { get; set; } public string? Name { get; set; } public string Role { get; set; } + public string Source { get; set; } public string Content { get; set; } public DateTime CreateTime { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs index 1940d5d3b..a35d3fc1e 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs @@ -4,8 +4,9 @@ namespace BotSharp.Plugin.MongoStorage.Models; public class DialogMongoElement { - public DialogMetaMongoElement MetaData { get; set; } + public DialogMetaDataMongoElement MetaData { get; set; } public string Content { get; set; } + public string? RichContent { get; set; } public DialogMongoElement() { @@ -16,8 +17,9 @@ public static DialogMongoElement ToMongoElement(DialogElement dialog) { return new DialogMongoElement { - MetaData = DialogMetaMongoElement.ToMongoElement(dialog.MetaData), - Content = dialog.Content + MetaData = DialogMetaDataMongoElement.ToMongoElement(dialog.MetaData), + Content = dialog.Content, + RichContent = dialog.RichContent }; } @@ -25,13 +27,14 @@ public static DialogElement ToDomainElement(DialogMongoElement dialog) { return new DialogElement { - MetaData = DialogMetaMongoElement.ToDomainElement(dialog.MetaData), - Content = dialog.Content + MetaData = DialogMetaDataMongoElement.ToDomainElement(dialog.MetaData), + Content = dialog.Content, + RichContent = dialog.RichContent }; } } -public class DialogMetaMongoElement +public class DialogMetaDataMongoElement { public string Role { get; set; } public string AgentId { get; set; } @@ -40,14 +43,14 @@ public class DialogMetaMongoElement public string? SenderId { get; set; } public DateTime CreateTime { get; set; } - public DialogMetaMongoElement() + public DialogMetaDataMongoElement() { } - public static DialogMeta ToDomainElement(DialogMetaMongoElement meta) + public static DialogMetaData ToDomainElement(DialogMetaDataMongoElement meta) { - return new DialogMeta + return new DialogMetaData { Role = meta.Role, AgentId = meta.AgentId, @@ -58,9 +61,9 @@ public static DialogMeta ToDomainElement(DialogMetaMongoElement meta) }; } - public static DialogMetaMongoElement ToMongoElement(DialogMeta meta) + public static DialogMetaDataMongoElement ToMongoElement(DialogMetaData meta) { - return new DialogMetaMongoElement + return new DialogMetaDataMongoElement { Role = meta.Role, AgentId = meta.AgentId, diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs index efe7537c9..bc4d8e0eb 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs @@ -73,6 +73,7 @@ public void SaveConversationContentLog(ConversationContentLogModel log) MessageId = messageId, Name = log.Name, Role = log.Role, + Source = log.Source, Content = log.Content, CreateTime = log.CreateTime }; @@ -91,6 +92,7 @@ public List GetConversationContentLogs(string conve MessageId = x.MessageId, Name = x.Name, Role = x.Role, + Source = x.Source, Content = x.Content, CreateTime = x.CreateTime })