diff --git a/docs/channels/components.md b/docs/channels/components.md index 3754a1c3..89ab8cb4 100644 --- a/docs/channels/components.md +++ b/docs/channels/components.md @@ -1,6 +1,6 @@ # Messaging Components -Conversations are a lot more than simple text messages when you are building a AI chatbot. In addition to text, the `BotSharp`` allows you to send rich-media, like audio, video, and images, and provides a set of structured messaging options in the form of message templates, quick replies, buttons and more. The UI rendering program can render components according to the returned data format. +Conversations are a lot more than simple text messages when you are building a AI chatbot. In addition to text, the `BotSharp` allows you to send rich-media, like audio, video, and images, and provides a set of structured messaging options in the form of message templates, quick replies, buttons and more. The UI rendering program can render components according to the returned data format. ## Text Messages @@ -75,7 +75,7 @@ Message templates are structured message formats used for various purposes to pr ... } ] - } + } } } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs index 98381559..903e5d0b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs @@ -1,36 +1,9 @@ +using BotSharp.Abstraction.Models; + namespace BotSharp.Abstraction.Conversations.Models; -public class IncomingMessageModel +public class IncomingMessageModel : MessageConfig { public string Text { get; set; } = string.Empty; - - public virtual string Channel { get; set; } = string.Empty; - - /// - /// Completion Provider - /// - [JsonPropertyName("provider")] - public virtual string? Provider { get; set; } = null; - - /// - /// Model name - /// - [JsonPropertyName("model")] - public virtual string? Model { get; set; } = null; - - /// - /// The sampling temperature to use that controls the apparent creativity of generated completions. - /// - public float Temperature { get; set; } = 0.5f; - - /// - /// An alternative value to Temperature, called nucleus sampling, that causes - /// the model to consider the results of the tokens with probability mass. - /// - public float SamplingFactor { get; set; } = 0.5f; - - /// - /// Conversation states from input - /// - public List States { get; set; } = new List(); + public virtual string Channel { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index ae798160..6b8624f9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -1,7 +1,11 @@ +using BotSharp.Abstraction.Models; + namespace BotSharp.Abstraction.Conversations.Models; -public class RoleDialogModel +public class RoleDialogModel : ITrackableMessage { + public string MessageId { get; set; } + /// /// user, system, assistant, function /// @@ -34,10 +38,15 @@ public class RoleDialogModel [JsonIgnore(Condition = JsonIgnoreCondition.Always)] public bool StopCompletion { get; set; } + private RoleDialogModel() + { + } + public RoleDialogModel(string role, string text) { Role = role; Content = text; + MessageId = Guid.NewGuid().ToString(); } public override string ToString() diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs index 10635152..67bd2039 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructResult.cs @@ -1,7 +1,10 @@ +using BotSharp.Abstraction.Models; + namespace BotSharp.Abstraction.Instructs.Models; -public class InstructResult +public class InstructResult : ITrackableMessage { + public string MessageId { get; set; } public string Text { get; set; } public object Data { get; set; } public ConversationState States { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/ITrackableMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Models/ITrackableMessage.cs new file mode 100644 index 00000000..5756cfd6 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Models/ITrackableMessage.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.Models; + +/// +/// Define a message ID to extend message-level applications, such as model fees, token usage, and data collection +/// +public interface ITrackableMessage +{ + string MessageId { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/MessageConfig.cs b/src/Infrastructure/BotSharp.Abstraction/Models/MessageConfig.cs new file mode 100644 index 00000000..95d72492 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Models/MessageConfig.cs @@ -0,0 +1,32 @@ +namespace BotSharp.Abstraction.Models; + +public class MessageConfig +{ + /// + /// Completion Provider + /// + [JsonPropertyName("provider")] + public virtual string? Provider { get; set; } = null; + + /// + /// Model name + /// + [JsonPropertyName("model")] + public virtual string? Model { get; set; } = null; + + /// + /// The sampling temperature to use that controls the apparent creativity of generated completions. + /// + public float Temperature { get; set; } = 0.5f; + + /// + /// An alternative value to Temperature, called nucleus sampling, that causes + /// the model to consider the results of the tokens with probability mass. + /// + public float SamplingFactor { get; set; } = 0.5f; + + /// + /// Conversation states from input + /// + public List States { get; set; } = new List(); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs index bca8018e..96e133d9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs @@ -1,7 +1,10 @@ namespace BotSharp.Abstraction.Routing.Models; -public class RoutingArgs +public class RoutingArgs : ITrackableMessage { + [JsonPropertyName("message_id")] + public string MessageId { get; set; } + [JsonPropertyName("function")] public string Function { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index 87d42791..39ececfe 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Using.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs @@ -7,4 +7,5 @@ global using System.ComponentModel.DataAnnotations; global using System.Text.Json.Serialization; global using BotSharp.Abstraction.Agents.Models; global using BotSharp.Abstraction.Conversations.Models; -global using BotSharp.Abstraction.Agents.Enums; \ No newline at end of file +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Models; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index efcb9cf9..db6fc90a 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -78,11 +78,11 @@ public partial class ConversationService : IConversationService throw new NotImplementedException(); } - public List GetDialogHistory(int lastCount = 20) + public List GetDialogHistory(int lastCount = 50) { var dialogs = _storage.GetDialogs(_conversationId); return dialogs - .Where(x => x.CreatedAt > DateTime.UtcNow.AddHours(-8)) + .Where(x => x.CreatedAt > DateTime.UtcNow.AddHours(-24)) .TakeLast(lastCount) .ToList(); } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index 2d8af0ad..3d8b3085 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -6,19 +6,13 @@ namespace BotSharp.Core.Conversations.Services; public class ConversationStorage : IConversationStorage { private readonly BotSharpDatabaseSettings _dbSettings; - private readonly AgentSettings _agentSettings; private readonly IServiceProvider _services; - private readonly IUserIdentity _user; public ConversationStorage( BotSharpDatabaseSettings dbSettings, - AgentSettings agentSettings, - IServiceProvider services, - IUserIdentity user) + IServiceProvider services) { _dbSettings = dbSettings; - _agentSettings = agentSettings; _services = services; - _user = user; } public void Append(string conversationId, RoleDialogModel dialog) @@ -32,7 +26,7 @@ public class ConversationStorage : IConversationStorage { var args = dialog.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim(); - sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{dialog.FunctionName}|{args}"); + sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{dialog.MessageId}"); var content = dialog.Content; content = content.Replace("\r", " ").Replace("\n", " ").Trim(); @@ -44,9 +38,7 @@ public class ConversationStorage : IConversationStorage } else { - var agentName = db.GetAgent(agentId)?.Name; - - sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{agentName}|"); + sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{dialog.MessageId}"); var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim(); if (string.IsNullOrEmpty(content)) { @@ -73,15 +65,13 @@ public class ConversationStorage : IConversationStorage var createdAt = DateTime.Parse(meta.Split('|')[0]); var role = meta.Split('|')[1]; var currentAgentId = meta.Split('|')[2]; - var funcName = meta.Split('|')[3]; - var funcArgs= meta.Split('|')[4]; + var messageId = meta.Split('|')[3]; var text = dialog.Substring(4); results.Add(new RoleDialogModel(role, text) { CurrentAgentId = currentAgentId, - FunctionName = funcName, - FunctionArgs = funcArgs, + MessageId = messageId, Content = text, CreatedAt = createdAt }); diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs index bb328753..8ee49eac 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs @@ -33,6 +33,7 @@ public partial class InstructService : IInstructService { return new InstructResult { + MessageId = message.MessageId, Text = message.Content }; } @@ -42,6 +43,7 @@ public partial class InstructService : IInstructService var result = await completer.GetCompletion(agent.Instruction); var response = new InstructResult { + MessageId = message.MessageId, Text = result }; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs index 984651f0..8e167855 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs @@ -33,6 +33,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan var result = new RoleDialogModel(AgentRole.Function, inst.Question) { + MessageId = inst.MessageId, FunctionName = inst.Function, FunctionArgs = JsonSerializer.Serialize(inst.Arguments), CurrentAgentId = record.Id diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs index 44563f42..69a5c9f7 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs @@ -28,6 +28,7 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler { var result = new RoleDialogModel(AgentRole.Assistant, inst.Response) { + MessageId = inst.MessageId, CurrentAgentId = _settings.RouterId, FunctionName = inst.Function, Data = inst diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs index 62247445..1ebf9c43 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs @@ -29,6 +29,7 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle { var result = new RoleDialogModel(AgentRole.Assistant, inst.Response) { + MessageId = inst.MessageId, CurrentAgentId = _settings.RouterId, FunctionName = inst.Function, Data = inst diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs index 096f9d61..c62102e2 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs @@ -28,6 +28,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting { var result = new RoleDialogModel(AgentRole.User, inst.Reason) { + MessageId = inst.MessageId, FunctionName = inst.Function, StopCompletion = true }; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs index ec1c5bcf..9da370b8 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs @@ -28,6 +28,7 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler { var result = new RoleDialogModel(AgentRole.Assistant, inst.Response) { + MessageId = inst.MessageId, CurrentAgentId = _settings.RouterId, FunctionName = inst.Function, Data = inst, diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs index d4492d2b..04d78bdb 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs @@ -45,6 +45,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH /*_dialogs.Add(new RoleDialogModel(AgentRole.Function, inst.Parameters.Answer) { + MessageId = inst.MessageId, FunctionName = inst.Function, FunctionArgs = JsonSerializer.Serialize(inst.Parameters.Arguments), ExecutionResult = inst.Parameters.Answer, diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index d4fba5ff..52766548 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -35,6 +35,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler var function = _services.GetServices().FirstOrDefault(x => x.Name == inst.Function); var message = new RoleDialogModel(AgentRole.Function, inst.Question) { + MessageId = inst.MessageId, FunctionName = inst.Function, FunctionArgs = JsonSerializer.Serialize(inst), CurrentAgentId = context.GetCurrentAgentId(), @@ -46,6 +47,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler // Keep last message data for debug result.Data = result.Data ?? message.Data; result.FunctionName = result.FunctionName ?? message.FunctionName; + return result; } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs index 9190bba5..ba7f2e2c 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs @@ -27,6 +27,7 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler { var result = new RoleDialogModel(AgentRole.Assistant, inst.Response) { + MessageId = inst.MessageId, CurrentAgentId = _settings.RouterId, FunctionName = inst.Function, Data = inst diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 9159434d..ba4f8f1b 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -76,7 +76,7 @@ public partial class RoutingService : IRoutingService CurrentAgentId = router.Id }; - var message = Dialogs.Last().Content; + var inputMsg = Dialogs.Last(); var handlers = _services.GetServices(); @@ -87,7 +87,8 @@ public partial class RoutingService : IRoutingService loopCount++; var inst = await GetNextInstruction(); - inst.Question = inst.Question ?? message; + inst.MessageId = inputMsg.MessageId; + inst.Question = inst.Question ?? inputMsg.Content; var handler = handlers.FirstOrDefault(x => x.Name == inst.Function); if (handler == null) @@ -99,6 +100,7 @@ public partial class RoutingService : IRoutingService handler.SetDialogs(Dialogs); result = await handler.Handle(this, inst); + result.MessageId = inputMsg.MessageId; stop = !_settings.EnableReasoning; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 3ca6ca9c..4fd4be21 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.ApiAdapters; using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Models; using BotSharp.OpenAPI.ViewModels.Conversations; namespace BotSharp.OpenAPI.Controllers; @@ -19,15 +20,17 @@ public class ConversationController : ControllerBase, IApiAdapter } [HttpPost("/conversation/{agentId}")] - public async Task NewConversation([FromRoute] string agentId) + public async Task NewConversation([FromRoute] string agentId, [FromBody] MessageConfig config) { var service = _services.GetRequiredService(); - var sess = new Conversation + var conv = new Conversation { AgentId = agentId }; - sess = await service.NewConversation(sess); - return ConversationViewModel.FromSession(sess); + conv = await service.NewConversation(conv); + config.States.ForEach(x => conv.States[x.Split('=')[0]] = x.Split('=')[1]); + + return ConversationViewModel.FromSession(conv); } [HttpDelete("/conversation/{agentId}/{conversationId}")] @@ -51,9 +54,8 @@ public class ConversationController : ControllerBase, IApiAdapter var response = new MessageResponseModel(); var stackMsg = new List(); - - await conv.SendMessage(agentId, - new RoleDialogModel("user", input.Text), + var inputMsg = new RoleDialogModel("user", input.Text); + await conv.SendMessage(agentId, inputMsg, async msg => { stackMsg.Add(msg); @@ -71,6 +73,7 @@ public class ConversationController : ControllerBase, IApiAdapter response.Text = string.Join("\r\n", stackMsg.Select(x => x.Content)); response.Data = response.Data ?? stackMsg.Last().Data; response.Function = stackMsg.Last().FunctionName; + response.MessageId = inputMsg.MessageId; return response; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs index 5f7241c0..dc88faaf 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs @@ -1,7 +1,10 @@ +using BotSharp.Abstraction.Models; + namespace BotSharp.OpenAPI.ViewModels.Conversations; -public class MessageResponseModel +public class MessageResponseModel : ITrackableMessage { + public string MessageId { get; set; } public string Text { get; set; } public string Function { get; set; } public object Data { get; set; }