From ba855bc7cc2706457777c2b9d12eae96e6f073fa Mon Sep 17 00:00:00 2001 From: Kerry Jiang Date: Sat, 7 Dec 2024 13:47:17 -0800 Subject: [PATCH 01/16] introduce ConversationHookProvider to avoid ordering conversartion hooks in runtime --- .../Conversations/ConversationHookProvider.cs | 20 +++++++++++++++++++ .../ConversationService.TruncateMessage.cs | 2 +- .../ConversationService.UpdateBreakpoint.cs | 6 +++--- .../Services/ConversationService.cs | 3 ++- .../Functions/HumanInterventionNeededFn.cs | 6 +++--- .../Routing/RoutingService.InvokeFunction.cs | 6 +++--- .../BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 1 + 7 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookProvider.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookProvider.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookProvider.cs new file mode 100644 index 00000000..00393da4 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookProvider.cs @@ -0,0 +1,20 @@ +namespace BotSharp.Abstraction.Conversations; + +public class ConversationHookProvider +{ + public IEnumerable Hooks { get; } + + private readonly Lazy> _hooksOrderByPriority; + + public IEnumerable HooksOrderByPriority + => _hooksOrderByPriority.Value; + + public ConversationHookProvider(IEnumerable conversationHooks) + { + Hooks = conversationHooks; + _hooksOrderByPriority = new Lazy>(() => + { + return conversationHooks.OrderBy(hook => hook.Priority).ToArray(); + }); + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs index ccc074e8..1a75d717 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs @@ -9,7 +9,7 @@ public partial class ConversationService : IConversationService var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true); fileStorage.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId); - var hooks = _services.GetServices().ToList(); + var hooks = _services.GetServices(); foreach (var hook in hooks) { await hook.OnMessageDeleted(conversationId, messageId); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs index 8f88f44f..75a0bbfb 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs @@ -31,9 +31,9 @@ public partial class ConversationService : IConversationService states.CleanStates(excludedStates); } - var hooks = _services.GetServices() - .OrderBy(x => x.Priority) - .ToList(); + var hooks = _services + .GetRequiredService() + .HooksOrderByPriority; // Before executing functions foreach (var hook in hooks) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 55d80c18..03ff8aa4 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -103,7 +103,8 @@ public partial class ConversationService : IConversationService db.CreateNewConversation(record); - var hooks = _services.GetServices().ToList(); + var hooks = _services.GetServices(); + foreach (var hook in hooks) { // If user connect agent first time diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/HumanInterventionNeededFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/HumanInterventionNeededFn.cs index d0c26be0..719dd805 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Functions/HumanInterventionNeededFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/HumanInterventionNeededFn.cs @@ -15,9 +15,9 @@ public class HumanInterventionNeededFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { - var hooks = _services.GetServices() - .OrderBy(x => x.Priority) - .ToList(); + var hooks = _services + .GetRequiredService() + .HooksOrderByPriority; foreach (var hook in hooks) { diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 64b96136..fb074dfc 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -18,9 +18,9 @@ public partial class RoutingService var clonedMessage = RoleDialogModel.From(message); clonedMessage.FunctionName = name; - var hooks = _services.GetServices() - .OrderBy(x => x.Priority) - .ToList(); + var hooks = _services + .GetRequiredService() + .HooksOrderByPriority; var progressService = _services.GetService(); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index 1eaad66d..e7cf082f 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -22,6 +22,7 @@ public class ChatHubPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); From 1e974441ff47ec43658492e15cb64549900b0dab Mon Sep 17 00:00:00 2001 From: Kerry Jiang Date: Wed, 11 Dec 2024 17:16:59 -0800 Subject: [PATCH 02/16] ChatHubConversationHook priority --- .../Conversations/ConversationHookBase.cs | 18 +++++++----------- .../ConversationService.SendMessage.cs | 6 ++---- .../Hooks/ChatHubConversationHook.cs | 1 + 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs index 9239b865..da87646e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs @@ -2,27 +2,23 @@ namespace BotSharp.Abstraction.Conversations; public abstract class ConversationHookBase : IConversationHook { - protected Agent _agent; - public Agent Agent => _agent; + public Agent Agent { get; private set; } - protected Conversation _conversation; - public Conversation Conversation => _conversation; + public Conversation Conversation { get; private set; } - protected List _dialogs; - public List Dialogs => _dialogs; + public List Dialogs { get; private set; } - protected int _priority = 0; - public int Priority => _priority; + public int Priority { get; protected set; } = 0; public IConversationHook SetAgent(Agent agent) { - _agent = agent; + Agent = agent; return this; } public IConversationHook SetConversation(Conversation conversation) { - _conversation = conversation; + Conversation = conversation; return this; } @@ -37,7 +33,7 @@ public abstract class ConversationHookBase : IConversationHook public virtual Task OnDialogsLoaded(List dialogs) { - _dialogs = dialogs; + Dialogs = dialogs; return Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 375cf3e6..bdf43c8a 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -28,7 +28,7 @@ public partial class ConversationService var dialogs = conv.GetDialogHistory(); var statistics = _services.GetRequiredService(); - var hooks = _services.GetServices().ToList(); + var hookProvider = _services.GetRequiredService(); RoleDialogModel response = message; bool stopCompletion = false; @@ -44,9 +44,7 @@ public partial class ConversationService message.Payload = replyMessage.Payload; } - // Before chat completion hook - hooks = ReOrderConversationHooks(hooks); - foreach (var hook in hooks) + foreach (var hook in hookProvider.HooksOrderByPriority) { hook.SetAgent(agent) .SetConversation(conversation); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index b282fa53..6b425b4b 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -29,6 +29,7 @@ public class ChatHubConversationHook : ConversationHookBase _chatHub = chatHub; _user = user; _options = options; + Priority = -1; // Make sure this hook is the top one. } public override async Task OnConversationInitialized(Conversation conversation) From 4489683eebbc82720d2ab676dde9660fc7ba8998 Mon Sep 17 00:00:00 2001 From: Kerry Jiang Date: Wed, 11 Dec 2024 17:44:29 -0800 Subject: [PATCH 03/16] access public properties directly --- .../Evaluations/EvaluationConversationHook.cs | 22 +++++++++---------- .../Hooks/RateLimitConversationHook.cs | 2 +- .../RoutingConversationHook.cs | 6 ++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs index bfc0c1ec..b315b683 100644 --- a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs +++ b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs @@ -15,45 +15,45 @@ public class EvaluationConversationHook : ConversationHookBase public override Task OnMessageReceived(RoleDialogModel message) { - if (_conversation != null && _convSettings.EnableExecutionLog) + if (Conversation != null && _convSettings.EnableExecutionLog) { - _logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}"); + _logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}"); } return base.OnMessageReceived(message); } public override Task OnFunctionExecuted(RoleDialogModel message) { - if (_conversation != null && _convSettings.EnableExecutionLog) + if (Conversation != null && _convSettings.EnableExecutionLog) { - _logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.FunctionName}({message.FunctionArgs}) => {message.Content}"); + _logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.FunctionName}({message.FunctionArgs}) => {message.Content}"); } return base.OnFunctionExecuted(message); } public override Task OnResponseGenerated(RoleDialogModel message) { - if (_conversation != null && _convSettings.EnableExecutionLog) + if (Conversation != null && _convSettings.EnableExecutionLog) { - _logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}"); - } + _logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}"); + } return base.OnResponseGenerated(message); } public override Task OnHumanInterventionNeeded(RoleDialogModel message) { - if (_conversation != null && _convSettings.EnableExecutionLog) + if (Conversation != null && _convSettings.EnableExecutionLog) { - _logger.Append(_conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})"); + _logger.Append(Conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})"); } return base.OnHumanInterventionNeeded(message); } public override Task OnConversationEnding(RoleDialogModel message) { - if (_conversation != null && _convSettings.EnableExecutionLog) + if (Conversation != null && _convSettings.EnableExecutionLog) { - _logger.Append(_conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})"); + _logger.Append(Conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})"); } return base.OnConversationEnding(message); } diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs index 1cb09f3e..a9a9478f 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs @@ -34,7 +34,7 @@ public class RateLimitConversationHook : ConversationHookBase } // Check message sending frequency - var userSents = _dialogs.Where(x => x.Role == AgentRole.User) + var userSents = Dialogs.Where(x => x.Role == AgentRole.User) .TakeLast(2).ToList(); if (userSents.Count > 1) diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs index b7fab29e..b65e45c5 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs @@ -42,7 +42,7 @@ public class RoutingConversationHook: ConversationHookBase // Render by template var templateService = _services.GetRequiredService(); - var response = await templateService.RenderIntentResponse(_agent.Id, message); + var response = await templateService.RenderIntentResponse(Agent.Id, message); if (!string.IsNullOrEmpty(response)) { @@ -54,7 +54,7 @@ public class RoutingConversationHook: ConversationHookBase public override async Task OnResponseGenerated(RoleDialogModel message) { var routerSettings = _services.GetRequiredService(); - bool saveFlag = _agent.Type != AgentType.Routing; + bool saveFlag = Agent.Type != AgentType.Routing; if (saveFlag) { @@ -63,7 +63,7 @@ public class RoutingConversationHook: ConversationHookBase var rootDataPath = agentService.GetDataDir(); string rawDataDir = Path.Combine(rootDataPath, "raw_data", $"agent.{message.CurrentAgentId}.txt"); - var lastThreeDialogs = _dialogs.Where(x => x.Role == AgentRole.User || x.Role == AgentRole.Assistant) + var lastThreeDialogs = Dialogs.Where(x => x.Role == AgentRole.User || x.Role == AgentRole.Assistant) .Select(x => x.Content.Replace('\r', ' ').Replace('\n', ' ')) .TakeLast(3) .ToArray(); From 96eac1f26ffcfa6c7013f923e8aac21c14e48ffc Mon Sep 17 00:00:00 2001 From: Gil Zhang Date: Thu, 12 Dec 2024 11:28:00 +0800 Subject: [PATCH 04/16] Add Conversation Title Alias --- .../Conversations/IConversationService.cs | 1 + .../Conversations/Models/Conversation.cs | 1 + .../Filters/ConversationFilter.cs | 1 + .../Repositories/IBotSharpRepository.cs | 1 + .../Services/ConversationService.cs | 8 +++++++ .../Repository/BotSharpDbContext.cs | 2 ++ .../FileRepository.Conversation.cs | 20 ++++++++++++++++ .../Controllers/ConversationController.cs | 24 +++++++++++++++++++ .../Conversations/ConversationViewModel.cs | 4 ++++ .../UpdateConversationTitleAliasModel.cs | 9 +++++++ .../Collections/ConversationDocument.cs | 1 + .../MongoRepository.Conversation.cs | 15 ++++++++++++ 12 files changed, 87 insertions(+) create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/UpdateConversationTitleAliasModel.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index ffb4986a..6a04e796 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -12,6 +12,7 @@ public interface IConversationService Task GetConversation(string id); Task> GetConversations(ConversationFilter filter); Task UpdateConversationTitle(string id, string title); + Task UpdateConversationTitleAlias(string id, string titleAlias); Task UpdateConversationTags(string conversationId, List tags); Task UpdateConversationMessage(string conversationId, UpdateMessageRequest request); Task> GetLastConversations(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs index 5ded2c7c..a485a9c4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs @@ -13,6 +13,7 @@ public class Conversation /// public string? TaskId { get; set; } public string Title { get; set; } = string.Empty; + public string TitleAlias { get; set; } = string.Empty; [JsonIgnore] public List Dialogs { get; set; } = new(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index ab1a0a9f..23543049 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -8,6 +8,7 @@ public class ConversationFilter /// public string? Id { get; set; } public string? Title { get; set; } + public string? TitleAlias { get; set; } public string? AgentId { get; set; } public string? Status { get; set; } public string? Channel { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 61982cc4..fa6f074c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -86,6 +86,7 @@ public interface IBotSharpRepository : IHaveServiceProvider Conversation GetConversation(string conversationId); PagedItems GetConversations(ConversationFilter filter); void UpdateConversationTitle(string conversationId, string title); + void UpdateConversationTitleAlias(string conversationId, string titleAlias); bool UpdateConversationTags(string conversationId, List tags); bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request); void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 55d80c18..67b0b1b0 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -51,6 +51,14 @@ public partial class ConversationService : IConversationService return conversation; } + public async Task UpdateConversationTitleAlias(string id, string titleAlias) + { + var db = _services.GetRequiredService(); + db.UpdateConversationTitleAlias(id, titleAlias); + var conversation = db.GetConversation(id); + return conversation; + } + public async Task UpdateConversationTags(string conversationId, List tags) { var db = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index d2c519eb..165dc34b 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -105,6 +105,8 @@ public class BotSharpDbContext : Database, IBotSharpRepository public void UpdateConversationTitle(string conversationId, string title) => throw new NotImplementedException(); + public void UpdateConversationTitleAlias(string conversationId, string titleAlias) + => throw new NotImplementedException(); public bool UpdateConversationTags(string conversationId, List tags) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index fd13936c..34345271 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -134,6 +134,22 @@ namespace BotSharp.Core.Repository } } } + public void UpdateConversationTitleAlias(string conversationId, string titleAlias) + { + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) + { + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + var content = File.ReadAllText(convFile); + var record = JsonSerializer.Deserialize(content, _options); + if (record != null) + { + record.TitleAlias = titleAlias; + record.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(record, _options)); + } + } + } public bool UpdateConversationTags(string conversationId, List tags) { @@ -356,6 +372,10 @@ namespace BotSharp.Core.Repository { matched = matched && record.Title.Contains(filter.Title); } + if (filter?.TitleAlias != null) + { + matched = matched && record.TitleAlias.Contains(filter.TitleAlias); + } if (filter?.AgentId != null) { matched = matched && record.AgentId == filter.AgentId; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 389bf96f..266c5ac5 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -223,6 +223,30 @@ public class ConversationController : ControllerBase return response != null; } + [HttpPut("/conversation/{conversationId}/update-title-alias")] + public async Task UpdateConversationTitleAlias([FromRoute] string conversationId, [FromBody] UpdateConversationTitleAliasModel newTile) + { + var userService = _services.GetRequiredService(); + var conversationService = _services.GetRequiredService(); + + var user = await userService.GetUser(_user.Id); + var filter = new ConversationFilter + { + Id = conversationId, + UserId = user.Role != UserRole.Admin ? user.Id : null + }; + var conversations = await conversationService.GetConversations(filter); + + if (conversations.Items.IsNullOrEmpty()) + { + return false; + } + + var response = await conversationService.UpdateConversationTitleAlias(conversationId, newTile.NewTitleAlias); + return response != null; + } + + [HttpPut("/conversation/{conversationId}/update-tags")] public async Task UpdateConversationTags([FromRoute] string conversationId, [FromBody] UpdateConversationRequest request) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs index 90b4ff67..a660a525 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs @@ -15,6 +15,9 @@ public class ConversationViewModel [JsonPropertyName("title")] public string Title { get; set; } = string.Empty; + [JsonPropertyName("title_alias")] + public string TitleAlias { get; set; } = string.Empty; + public UserViewModel User { get; set; } = new UserViewModel(); public string Event { get; set; } @@ -48,6 +51,7 @@ public class ConversationViewModel }, AgentId = sess.AgentId, Title = sess.Title, + TitleAlias = sess.TitleAlias, Channel = sess.Channel, Status = sess.Status, TaskId = sess.TaskId, diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/UpdateConversationTitleAliasModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/UpdateConversationTitleAliasModel.cs new file mode 100644 index 00000000..5d9ac2fd --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/UpdateConversationTitleAliasModel.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace BotSharp.OpenAPI.ViewModels.Conversations; + +public class UpdateConversationTitleAliasModel +{ + [Required] + public string NewTitleAlias { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs index 28b0391a..52c26b52 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs @@ -6,6 +6,7 @@ public class ConversationDocument : MongoBase public string UserId { get; set; } public string? TaskId { get; set; } public string Title { get; set; } + public string TitleAlias { get; set; } public string Channel { get; set; } public string ChannelId { get; set; } public string Status { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index cdd78ae2..468f467a 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -114,6 +114,17 @@ public partial class MongoRepository _dc.Conversations.UpdateOne(filterConv, updateConv); } + public void UpdateConversationTitleAlias(string conversationId, string titleAlias) + { + if (string.IsNullOrEmpty(conversationId)) return; + + var filterConv = Builders.Filter.Eq(x => x.Id, conversationId); + var updateConv = Builders.Update + .Set(x => x.UpdatedTime, DateTime.UtcNow) + .Set(x => x.TitleAlias, titleAlias); + + _dc.Conversations.UpdateOne(filterConv, updateConv); + } public bool UpdateConversationTags(string conversationId, List tags) { @@ -301,6 +312,10 @@ public partial class MongoRepository { convFilters.Add(convBuilder.Regex(x => x.Title, new BsonRegularExpression(filter.Title, "i"))); } + if (!string.IsNullOrEmpty(filter?.TitleAlias)) + { + convFilters.Add(convBuilder.Regex(x => x.Title, new BsonRegularExpression(filter.TitleAlias, "i"))); + } if (!string.IsNullOrEmpty(filter?.AgentId)) { convFilters.Add(convBuilder.Eq(x => x.AgentId, filter.AgentId)); From 7f6845b2686b5a431f361dd17947afc85ee8ea11 Mon Sep 17 00:00:00 2001 From: "songguo.zeng" Date: Thu, 12 Dec 2024 16:02:09 +0800 Subject: [PATCH 05/16] AII-400 --- .../Agents/AgentHookBase.cs | 5 ++++ .../BotSharp.Abstraction/Agents/IAgentHook.cs | 2 ++ .../Agents/Services/AgentService.LoadAgent.cs | 27 ++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs index a3746ca8..f4d30523 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs @@ -56,4 +56,9 @@ public abstract class AgentHookBase : IAgentHook public virtual void OnAgentUtilityLoaded(Agent agent) { } + + public virtual void OnAgentLoadFilter(Agent agent) + { + + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs index a3f53fb6..a329134b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs @@ -33,4 +33,6 @@ public interface IAgentHook /// /// void OnAgentLoaded(Agent agent); + + void OnAgentLoadFilter(Agent agent); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 396a7d15..3ae49385 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -5,9 +5,8 @@ namespace BotSharp.Core.Agents.Services; public partial class AgentService { - public static ConcurrentDictionary> AgentParameterTypes = new(); + public static ConcurrentDictionary> AgentParameterTypes = new(); - [MemoryCache(10 * 60, perInstanceCache: true)] public async Task LoadAgent(string id) { if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) @@ -15,6 +14,26 @@ public partial class AgentService return null; } + Agent agent = await GetLoadAgent(id); + OnAgentLoadFilter(agent); + return agent; + } + + private void OnAgentLoadFilter(Agent? agent) + { + if (agent != null && agent.Type == AgentType.Routing) + { + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + hook.OnAgentLoadFilter(agent); + } + } + } + + [MemoryCache(10 * 60, perInstanceCache: true)] + private async Task GetLoadAgent(string id) + { var hooks = _services.GetServices(); // Before agent is loaded. @@ -106,14 +125,14 @@ public partial class AgentService { var agentId = agent.Id ?? agent.Name; if (AgentParameterTypes.ContainsKey(agentId)) return; - + AddOrUpdateRoutesParameters(agentId, agent.RoutingRules); AddOrUpdateFunctionsParameters(agentId, agent.Functions); } private void AddOrUpdateRoutesParameters(string agentId, List routingRules) { - if(!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes)) + if (!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes)) { parameterTypes = new(); } From 8d8fd758d934952f4f980b7b10ae71401d8f5a1a Mon Sep 17 00:00:00 2001 From: "songguo.zeng" Date: Thu, 12 Dec 2024 18:02:00 +0800 Subject: [PATCH 06/16] AII-400 --- .../Agents/AgentHookBase.cs | 5 ----- .../BotSharp.Abstraction/Agents/IAgentHook.cs | 2 -- .../Agents/Services/AgentService.LoadAgent.cs | 21 +------------------ 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs index f4d30523..a3746ca8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs @@ -56,9 +56,4 @@ public abstract class AgentHookBase : IAgentHook public virtual void OnAgentUtilityLoaded(Agent agent) { } - - public virtual void OnAgentLoadFilter(Agent agent) - { - - } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs index a329134b..a3f53fb6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs @@ -33,6 +33,4 @@ public interface IAgentHook /// /// void OnAgentLoaded(Agent agent); - - void OnAgentLoadFilter(Agent agent); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 3ae49385..0e9c2e3c 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -7,6 +7,7 @@ public partial class AgentService { public static ConcurrentDictionary> AgentParameterTypes = new(); + [MemoryCache(10 * 60, perInstanceCache: true)] public async Task LoadAgent(string id) { if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) @@ -14,26 +15,6 @@ public partial class AgentService return null; } - Agent agent = await GetLoadAgent(id); - OnAgentLoadFilter(agent); - return agent; - } - - private void OnAgentLoadFilter(Agent? agent) - { - if (agent != null && agent.Type == AgentType.Routing) - { - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - hook.OnAgentLoadFilter(agent); - } - } - } - - [MemoryCache(10 * 60, perInstanceCache: true)] - private async Task GetLoadAgent(string id) - { var hooks = _services.GetServices(); // Before agent is loaded. From f78425cffab83f0ccd86a034fbfa740bdf6542b5 Mon Sep 17 00:00:00 2001 From: Kerry Jiang Date: Thu, 12 Dec 2024 20:44:26 -0800 Subject: [PATCH 07/16] added unit test --- tests/UnitTest/MainTest.cs | 30 ++++++++++++++++++++++++++++++ tests/UnitTest/UnitTest.csproj | 8 +++++++- tests/UnitTest/UnitTest1.cs | 11 ----------- 3 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 tests/UnitTest/MainTest.cs delete mode 100644 tests/UnitTest/UnitTest1.cs diff --git a/tests/UnitTest/MainTest.cs b/tests/UnitTest/MainTest.cs new file mode 100644 index 00000000..2e4c29bf --- /dev/null +++ b/tests/UnitTest/MainTest.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.DependencyInjection; +using BotSharp.Abstraction.Conversations; +using BotSharp.Core.Evaluations; +using BotSharp.Plugin.ChatHub.Hooks; + +namespace UnitTest +{ + [TestClass] + public class MainTest + { + [TestMethod] + public void TestConversationHookProvider() + { + var services = new ServiceCollection(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + var serviceProvider = services.BuildServiceProvider(); + var conversationHookProvider = serviceProvider.GetService(); + + Assert.AreEqual(3, conversationHookProvider.Hooks.Count()); + + // ChatHubConversationHook has the top priority + Assert.IsInstanceOfType(conversationHookProvider.HooksOrderByPriority.FirstOrDefault()); + } + } +} \ No newline at end of file diff --git a/tests/UnitTest/UnitTest.csproj b/tests/UnitTest/UnitTest.csproj index be924573..3e1831ae 100644 --- a/tests/UnitTest/UnitTest.csproj +++ b/tests/UnitTest/UnitTest.csproj @@ -13,10 +13,16 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + diff --git a/tests/UnitTest/UnitTest1.cs b/tests/UnitTest/UnitTest1.cs deleted file mode 100644 index ab3cd866..00000000 --- a/tests/UnitTest/UnitTest1.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace UnitTest -{ - [TestClass] - public class UnitTest1 - { - [TestMethod] - public void TestMethod1() - { - } - } -} \ No newline at end of file From b2334decff0c714bfdb9c1a97a69e72a8a529a1c Mon Sep 17 00:00:00 2001 From: Kerry Jiang Date: Sat, 14 Dec 2024 08:35:37 -0800 Subject: [PATCH 08/16] tried to fix the unit tests --- tests/UnitTest/MainTest.cs | 49 ++++++++++++++++++++++++++++------ tests/UnitTest/UnitTest.csproj | 2 -- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/tests/UnitTest/MainTest.cs b/tests/UnitTest/MainTest.cs index 2e4c29bf..4d2698d5 100644 --- a/tests/UnitTest/MainTest.cs +++ b/tests/UnitTest/MainTest.cs @@ -1,7 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using BotSharp.Abstraction.Conversations; -using BotSharp.Core.Evaluations; -using BotSharp.Plugin.ChatHub.Hooks; namespace UnitTest { @@ -13,18 +11,53 @@ namespace UnitTest { var services = new ServiceCollection(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); var conversationHookProvider = serviceProvider.GetService(); - + Assert.AreEqual(3, conversationHookProvider.Hooks.Count()); - // ChatHubConversationHook has the top priority - Assert.IsInstanceOfType(conversationHookProvider.HooksOrderByPriority.FirstOrDefault()); + var prevHook = default(IConversationHook); + + // Assert priority + foreach (var hook in conversationHookProvider.HooksOrderByPriority) + { + if (prevHook != null) + { + Assert.IsTrue(prevHook.Priority < hook.Priority); + } + + prevHook = hook; + } + } + + class TestHookA : ConversationHookBase + { + public TestHookA() + { + Priority = 1; + } + } + + class TestHookB : ConversationHookBase + { + public TestHookA() + { + Priority = 2; + } + } + + class TestHookC : ConversationHookBase + { + public TestHookA() + { + Priority = 3; + } } } } \ No newline at end of file diff --git a/tests/UnitTest/UnitTest.csproj b/tests/UnitTest/UnitTest.csproj index 3e1831ae..67e7d504 100644 --- a/tests/UnitTest/UnitTest.csproj +++ b/tests/UnitTest/UnitTest.csproj @@ -22,7 +22,5 @@ - - From 4fde52456a18a9c12b503f3ff0073ac9577eaba2 Mon Sep 17 00:00:00 2001 From: Kerry Jiang Date: Sat, 14 Dec 2024 08:35:45 -0800 Subject: [PATCH 09/16] removed unused code --- .../Services/ConversationService.SendMessage.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index bdf43c8a..f60d32cf 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -171,18 +171,4 @@ public partial class ConversationService // Add to dialog history _storage.Append(_conversationId, response); } - - private List ReOrderConversationHooks(List hooks) - { - var target = "ChatHubConversationHook"; - var chathub = hooks.FirstOrDefault(x => x.GetType().Name == target); - var otherHooks = hooks.Where(x => x.GetType().Name != target).ToList(); - - if (chathub != null) - { - var newHooks = new List { chathub }.Concat(otherHooks); - return newHooks.ToList(); - } - return hooks; - } } From 7089f8f39fcb0c7dcdcf5e6aa803c69b66157906 Mon Sep 17 00:00:00 2001 From: Kerry Jiang Date: Sat, 14 Dec 2024 09:25:19 -0800 Subject: [PATCH 10/16] tried to fix the unit tests --- tests/UnitTest/MainTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/UnitTest/MainTest.cs b/tests/UnitTest/MainTest.cs index 4d2698d5..307855e4 100644 --- a/tests/UnitTest/MainTest.cs +++ b/tests/UnitTest/MainTest.cs @@ -46,7 +46,7 @@ namespace UnitTest class TestHookB : ConversationHookBase { - public TestHookA() + public TestHookB() { Priority = 2; } @@ -54,7 +54,7 @@ namespace UnitTest class TestHookC : ConversationHookBase { - public TestHookA() + public TestHookC() { Priority = 3; } From 387bbe98c61e6826cc59f05460498b957ccb9b6a Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 16 Dec 2024 10:17:50 -0600 Subject: [PATCH 11/16] add count in cron tab --- .../BotSharp.Abstraction/Crontab/Models/CrontabItem.cs | 9 +++++++++ .../Collections/CrontabItemDocument.cs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs index a8acdc79..6a9dd43a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs @@ -14,6 +14,15 @@ public class CrontabItem : ScheduleTaskArgs [JsonPropertyName("execution_result")] public string ExecutionResult { get; set; } = null!; + [JsonPropertyName("execution_count")] + public int ExecutionCount { get; set; } + + [JsonPropertyName("max_execution_count")] + public int MaxExecutionCount { get; set; } + + [JsonPropertyName("expire_seconds")] + public int ExpireSeconds { get; set; } = 60; + [JsonPropertyName("created_time")] public DateTime CreatedTime { get; set; } = DateTime.UtcNow; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs index 10e633d7..dc04d2e4 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs @@ -11,6 +11,9 @@ public class CrontabItemDocument : MongoBase public string Cron { get; set; } public string Title { get; set; } public string Description { get; set; } + public int ExecutionCount { get; set; } + public int MaxExecutionCount { get; set; } + public int ExpireSeconds { get; set; } = 60; public IEnumerable Tasks { get; set; } = []; public DateTime CreatedTime { get; set; } = DateTime.UtcNow; @@ -25,6 +28,9 @@ public class CrontabItemDocument : MongoBase Cron = item.Cron, Title = item.Title, Description = item.Description, + ExecutionCount = item.ExecutionCount, + MaxExecutionCount = item.MaxExecutionCount, + ExpireSeconds = item.ExpireSeconds, Tasks = item.Tasks?.Select(x => CronTaskMongoElement.ToDomainElement(x))?.ToArray() ?? [], CreatedTime = item.CreatedTime }; @@ -41,6 +47,9 @@ public class CrontabItemDocument : MongoBase Cron = item.Cron, Title = item.Title, Description = item.Description, + ExecutionCount = item.ExecutionCount, + MaxExecutionCount = item.MaxExecutionCount, + ExpireSeconds = item.ExpireSeconds, Tasks = item.Tasks?.Select(x => CronTaskMongoElement.ToMongoElement(x))?.ToList() ?? [], CreatedTime = item.CreatedTime }; From 47135c8fb45086c4d6b1d584dfd67c45928376b9 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 16 Dec 2024 10:18:50 -0600 Subject: [PATCH 12/16] revert --- .../Collections/CrontabItemDocument.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs index dc04d2e4..39622d4c 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs @@ -13,7 +13,7 @@ public class CrontabItemDocument : MongoBase public string Description { get; set; } public int ExecutionCount { get; set; } public int MaxExecutionCount { get; set; } - public int ExpireSeconds { get; set; } = 60; + public int ExpireSeconds { get; set; } public IEnumerable Tasks { get; set; } = []; public DateTime CreatedTime { get; set; } = DateTime.UtcNow; From 00320fef21ce042d37449ac75c5810617551fb78 Mon Sep 17 00:00:00 2001 From: Joanna Ren <101223@smsassist.com> Date: Mon, 16 Dec 2024 17:22:28 -0600 Subject: [PATCH 13/16] update sql planner --- .../agent.json | 2 +- .../BotSharp.Plugin.Planner.csproj | 30 +++++ .../Enums/PlannerAgentId.cs | 3 +- .../Functions/SummaryPlanFn.cs | 9 -- .../BotSharp.Plugin.Planner/PlannerPlugin.cs | 5 +- .../Functions/SqlGenerationFn.cs | 127 ++++++++++++++++++ .../SqlGeneration/Functions/SqlReviewFn.cs | 38 ++++++ .../SqlGeneration/Models/FirstStagePlan.cs | 39 ++++++ .../Models/PrimaryRequirementRequest.cs | 13 ++ .../SqlGeneration/Models/SecondStagePlan.cs | 19 +++ .../Models/SecondaryBreakdownTask.cs | 13 ++ .../SqlGeneration/Models/SqlReviewArgs.cs | 13 ++ .../SqlGeneration/SqlGenerationPlanner.cs | 107 +++++++++++++++ .../agent.json | 19 +++ .../functions/plan_primary_stage.json | 47 +++++++ .../functions/plan_secondary_stage.json | 18 +++ .../functions/sql_generation.json | 26 ++++ .../functions/sql_review.json | 22 +++ .../instructions/instruction.liquid | 33 +++++ .../templates/two_stage.1st.plan.liquid | 39 ++++++ .../templates/two_stage.2nd.plan.liquid | 22 +++ .../templates/two_stage.next.liquid | 12 ++ .../templates/two_stage.summarize.liquid | 29 ++++ .../util-db-sql_table_definition.json | 12 +- .../agent.json | 2 +- .../functions/sql_table_definition.json | 12 +- .../instructions/instruction.liquid | 2 +- 27 files changed, 690 insertions(+), 23 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/FirstStagePlan.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/PrimaryRequirementRequest.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondStagePlan.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondaryBreakdownTask.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SqlReviewArgs.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/SqlGenerationPlanner.cs create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/agent.json create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_primary_stage.json create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_secondary_stage.json create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_generation.json create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_review.json create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/instructions/instruction.liquid create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.1st.plan.liquid create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.2nd.plan.liquid create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.next.liquid create mode 100644 src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.summarize.liquid diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/agent.json b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/agent.json index cfd0d779..5e4186dc 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/agent.json +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/agent.json @@ -16,7 +16,7 @@ }, { "type": "planner", - "field": "Two-Stage-Planner" + "field": "SQL-Planner" } ] } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/BotSharp.Plugin.Planner.csproj b/src/Plugins/BotSharp.Plugin.Planner/BotSharp.Plugin.Planner.csproj index f80327cc..886964b5 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/BotSharp.Plugin.Planner.csproj +++ b/src/Plugins/BotSharp.Plugin.Planner/BotSharp.Plugin.Planner.csproj @@ -69,6 +69,36 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + diff --git a/src/Plugins/BotSharp.Plugin.Planner/Enums/PlannerAgentId.cs b/src/Plugins/BotSharp.Plugin.Planner/Enums/PlannerAgentId.cs index 0776d7f1..ce603861 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Enums/PlannerAgentId.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Enums/PlannerAgentId.cs @@ -1,9 +1,8 @@ -using Microsoft.AspNetCore.Http; - namespace BotSharp.Plugin.Planner.Enums; public class PlannerAgentId { public const string TwoStagePlanner = "282a7128-69a1-44b0-878c-a9159b88f3b9"; public const string SequentialPlanner = "3e75e818-a139-48a8-9e22-4662548c13a3"; + public const string SqlPlanner = "da7aad2c-8112-48a2-ab7b-1f87da524741"; } diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs index cfc306f4..b89bcc35 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs @@ -68,15 +68,6 @@ public class SummaryPlanFn : IFunctionCallback var summary = await GetAiResponse(plannerAgent); message.Content = summary.Content; - // Emit event if the sql statement is generated by planner - var args = JsonSerializer.Deserialize(message.FunctionArgs); - if (args != null && !args.IsSqlTemplate && args.ContainsSqlStatements) - { - await HookEmitter.Emit(_services, async hook => - await hook.OnSourceCodeGenerated(nameof(TwoStageTaskPlanner), message, "sql") - ); - } - await HookEmitter.Emit(_services, async hook => await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message) ); diff --git a/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs b/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs index b139cde2..9ece599f 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs @@ -1,4 +1,5 @@ using BotSharp.Plugin.Planner.Sequential; +using BotSharp.Plugin.Planner.SqlGeneration; using BotSharp.Plugin.Planner.TwoStaging; namespace BotSharp.Plugin.Planner; @@ -16,13 +17,15 @@ public class PlannerPlugin : IBotSharpPlugin public string[] AgentIds => [ PlannerAgentId.TwoStagePlanner, - PlannerAgentId.SequentialPlanner + PlannerAgentId.SequentialPlanner, + PlannerAgentId.SqlPlanner ]; public void RegisterDI(IServiceCollection services, IConfiguration config) { services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); } diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs new file mode 100644 index 00000000..e89f533a --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs @@ -0,0 +1,127 @@ +using BotSharp.Plugin.Planner.TwoStaging; +using BotSharp.Plugin.Planner.TwoStaging.Models; + +namespace BotSharp.Plugin.Planner.Functions; + +public class SqlGenerationFn : IFunctionCallback +{ + public string Name => "sql_generation"; + public string Indication => "Organizing and summarizing the final SQL statements."; + + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public SqlGenerationFn( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task Execute(RoleDialogModel message) + { + var fn = _services.GetRequiredService(); + var agentService = _services.GetRequiredService(); + var states = _services.GetRequiredService(); + + states.SetState("max_tokens", "4096"); + var currentAgent = await agentService.LoadAgent(message.CurrentAgentId); + var taskRequirement = states.GetState("requirement_detail"); + + // Get table names + var steps = states.GetState("planning_result").JsonArrayContent(); + var allTables = new List(); + var ddlStatements = string.Empty; + var domainKnowledge = states.GetState("planning_result"); + domainKnowledge += "\r\n" + states.GetState("domain_knowledges"); + var dictionaryItems = states.GetState("dictionary_items"); + var excelImportResult = states.GetState("excel_import_result"); + + foreach (var step in steps) + { + allTables.AddRange(step.Tables); + } + var distinctTables = allTables.Distinct().ToList(); + + var msgCopy = RoleDialogModel.From(message); + msgCopy.FunctionArgs = JsonSerializer.Serialize(new + { + tables = distinctTables, + }); + await fn.InvokeFunction("sql_table_definition", msgCopy); + ddlStatements += "\r\n" + msgCopy.Content; + states.SetState("table_ddls", ddlStatements); + + // Summarize and generate query + var prompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, domainKnowledge, dictionaryItems, ddlStatements, excelImportResult); + _logger.LogInformation($"Summary plan prompt:\r\n{prompt}"); + + var plannerAgent = new Agent + { + Id = PlannerAgentId.TwoStagePlanner, + Name = Name, + Instruction = prompt, + LlmConfig = currentAgent.LlmConfig + }; + + var summary = await GetAiResponse(plannerAgent); + message.Content = summary.Content; + + /*await HookEmitter.Emit(_services, async hook => + await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message) + );*/ + + return true; + } + + private async Task GetSummaryPlanPrompt(RoleDialogModel message, string taskDescription, string domainKnowledge, string dictionaryItems, string ddlStatement, string excelImportResult) + { + var agentService = _services.GetRequiredService(); + var render = _services.GetRequiredService(); + var knowledgeHooks = _services.GetServices(); + + var agent = await agentService.GetAgent(PlannerAgentId.TwoStagePlanner); + var template = agent.Templates.FirstOrDefault(x => x.Name == "two_stage.summarize")?.Content ?? string.Empty; + + var additionalRequirements = new List(); + await HookEmitter.Emit(_services, async x => + { + var requirement = await x.GetSummaryAdditionalRequirements(nameof(TwoStageTaskPlanner), message); + additionalRequirements.Add(requirement); + }); + + var globalKnowledges = new List(); + foreach (var hook in knowledgeHooks) + { + var k = await hook.GetGlobalKnowledges(message); + globalKnowledges.AddRange(k); + } + + return render.Render(template, new Dictionary + { + { "task_description", taskDescription }, + { "summary_requirements", string.Join("\r\n", additionalRequirements) }, + { "global_knowledges", globalKnowledges }, + { "domain_knowledges", domainKnowledge }, + { "dictionary_items", dictionaryItems }, + { "table_structure", ddlStatement }, + { "excel_import_result", excelImportResult } + }); + } + private async Task GetAiResponse(Agent plannerAgent) + { + var conv = _services.GetRequiredService(); + var wholeDialogs = conv.GetDialogHistory(); + + // Append text + wholeDialogs.Last().Content += "\n\nIf the table structure didn't mention auto incremental, the data field id needs to insert id manually and you need to use max(id).\nFor example, you should use SET @id = select max(id) from table;"; + wholeDialogs.Last().Content += "\n\nTry if you can generate a single query to fulfill the needs."; + + var completion = CompletionProvider.GetChatCompletion(_services, + provider: plannerAgent.LlmConfig.Provider, + model: plannerAgent.LlmConfig.Model); + + return await completion.GetChatCompletions(plannerAgent, wholeDialogs); + } +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs new file mode 100644 index 00000000..7c14d5f4 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs @@ -0,0 +1,38 @@ +using BotSharp.Plugin.Planner.SqlGeneration.Models; +using BotSharp.Plugin.Planner.TwoStaging; +using BotSharp.Plugin.Planner.TwoStaging.Models; + +namespace BotSharp.Plugin.Planner.SqlGeneration.Functions; + +public class SqlReviewFn : IFunctionCallback +{ + public string Name => "sql_review"; + public string Indication => "Currently reviewing SQL statement"; + + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public SqlReviewFn( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + if (!message.Content.StartsWith("```sql")) + { + message.Content = $"```sql\r\n{args.SqlStatement}\r\n```"; + } + if (args != null && !args.IsSqlTemplate && args.ContainsSqlStatements) + { + await HookEmitter.Emit(_services, async hook => + await hook.OnSourceCodeGenerated(nameof(TwoStageTaskPlanner), message, "sql") + ); + } + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/FirstStagePlan.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/FirstStagePlan.cs new file mode 100644 index 00000000..17ca9f0e --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/FirstStagePlan.cs @@ -0,0 +1,39 @@ +namespace BotSharp.Plugin.Planner.SqlGeneration.Models; + +public class FirstStagePlan +{ + [JsonPropertyName("task_detail")] + public string Task { get; set; } = ""; + + //[JsonPropertyName("reason")] + //public string Reason { get; set; } = ""; + + [JsonPropertyName("step")] + public int Step { get; set; } = -1; + + [JsonPropertyName("need_breakdown_task")] + public bool NeedAdditionalInformation { get; set; } = false; + + [JsonPropertyName("need_lookup_dictionary")] + public bool NeedLookupDictionary { get; set; } = false; + + [JsonPropertyName("related_tables")] + public string[] Tables { get; set; } = []; + + [JsonPropertyName("has_found_relevant_knowledge")] + public bool HasFoundRelevantKnowledge { get; set; } = false; + + //[JsonPropertyName("related_urls")] + //public string[] Urls { get; set; } = []; + + //[JsonPropertyName("input_args")] + //public JsonDocument[] Parameters { get; set; } = []; + + //[JsonPropertyName("output_results")] + //public string[] Results { get; set; } = []; + + public override string ToString() + { + return $"STEP {Step}: {Task}"; + } +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/PrimaryRequirementRequest.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/PrimaryRequirementRequest.cs new file mode 100644 index 00000000..6a15faba --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/PrimaryRequirementRequest.cs @@ -0,0 +1,13 @@ +namespace BotSharp.Plugin.Planner.SqlGeneration.Models; + +public class PrimaryRequirementRequest +{ + [JsonPropertyName("requirement_detail")] + public string Requirements { get; set; } = null!; + + [JsonPropertyName("questions")] + public string[] Questions { get; set; } = []; + + [JsonPropertyName("norm_questions")] + public string[] NormQuestions { get; set; } = []; +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondStagePlan.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondStagePlan.cs new file mode 100644 index 00000000..49f78f23 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondStagePlan.cs @@ -0,0 +1,19 @@ +namespace BotSharp.Plugin.Planner.SqlGeneration.Models; + +public class SecondStagePlan +{ + [JsonPropertyName("related_tables")] + public string[] Tables { get; set; } = []; + + [JsonPropertyName("need_lookup_dictionary")] + public bool NeedLookupDictionary { get; set; } = false; + + [JsonPropertyName("description")] + public string Description { get; set; } = ""; + + [JsonPropertyName("input_args")] + public JsonDocument[] Parameters { get; set; } = []; + + [JsonPropertyName("output_results")] + public string[] Results { get; set; } = []; +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondaryBreakdownTask.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondaryBreakdownTask.cs new file mode 100644 index 00000000..671c6353 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SecondaryBreakdownTask.cs @@ -0,0 +1,13 @@ +namespace BotSharp.Plugin.Planner.SqlGeneration.Models; + +public class SecondaryBreakdownTask +{ + [JsonPropertyName("task_description")] + public string TaskDescription { get; set; } = null!; + + [JsonPropertyName("solution_search_question")] + public string SolutionQuestion { get; set; } = null!; + + [JsonPropertyName("need_lookup_dictionary")] + public bool NeedLookupDictionary { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SqlReviewArgs.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SqlReviewArgs.cs new file mode 100644 index 00000000..29ec186f --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Models/SqlReviewArgs.cs @@ -0,0 +1,13 @@ +namespace BotSharp.Plugin.Planner.SqlGeneration.Models; + +public class SqlReviewArgs +{ + [JsonPropertyName("is_sql_template")] + public bool IsSqlTemplate { get; set; } = false; + + [JsonPropertyName("contains_sql_statements")] + public bool ContainsSqlStatements { get; set; } = false; + + [JsonPropertyName("sql_statement")] + public string SqlStatement { get; set; } = string.Empty; +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/SqlGenerationPlanner.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/SqlGenerationPlanner.cs new file mode 100644 index 00000000..52571578 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/SqlGenerationPlanner.cs @@ -0,0 +1,107 @@ +namespace BotSharp.Plugin.Planner.SqlGeneration; + +public class SqlGenerationPlanner : ITaskPlanner +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + public string Name => "SQL-Planner"; + public int MaxLoopCount => 10; + + public SqlGenerationPlanner(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task GetNextInstruction(Agent router, string messageId, List dialogs) + { + var inst = new FunctionCallFromLlm(); + var nextStepPrompt = await GetNextStepPrompt(router); + + // chat completion + var completion = CompletionProvider.GetChatCompletion(_services, + provider: router?.LlmConfig?.Provider, + model: router?.LlmConfig?.Model); + + // text completion + dialogs = new List + { + new RoleDialogModel(AgentRole.User, nextStepPrompt) + { + FunctionName = nameof(SqlGenerationPlanner), + MessageId = messageId + } + }; + var response = await completion.GetChatCompletions(router, dialogs); + inst = response.Content.JsonContent(); + + // Fix LLM malformed response + ReasonerHelper.FixMalformedResponse(_services, inst); + return inst; + } + + public List BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) + { + var question = inst.Response; + + var taskAgentDialogs = new List + { + new RoleDialogModel(AgentRole.User, question) + { + MessageId = message.MessageId, + } + }; + + return taskAgentDialogs; + } + + public bool AfterHandleContext(List dialogs, List taskAgentDialogs) + { + dialogs.AddRange(taskAgentDialogs.Skip(1)); + + return true; + } + + public async Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) + { + // Set user content as Planner's question + message.FunctionName = inst.Function; + message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments); + return true; + } + + public async Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) + { + var context = _services.GetRequiredService(); + + if (message.StopCompletion) + { + context.Empty(reason: $"Agent queue is cleared by {nameof(SqlGenerationPlanner)}"); + return false; + } + + if (dialogs.Last().Role == AgentRole.Assistant) + { + context.Empty(); + return false; + } + + var routing = _services.GetRequiredService(); + routing.Context.ResetRecursiveCounter(); + return true; + } + + private async Task GetNextStepPrompt(Agent router) + { + var agentService = _services.GetRequiredService(); + var planner = await agentService.LoadAgent(PlannerAgentId.TwoStagePlanner); + var template = planner.Templates.First(x => x.Name == "two_stage.next").Content; + var states = _services.GetRequiredService(); + var render = _services.GetRequiredService(); + return render.Render(template, new Dictionary + { + { StateConst.EXPECTED_ACTION_AGENT, states.GetState(StateConst.EXPECTED_ACTION_AGENT) }, + { StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) } + }); + } +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/agent.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/agent.json new file mode 100644 index 00000000..4d01b4e2 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/agent.json @@ -0,0 +1,19 @@ +{ + "id": "da7aad2c-8112-48a2-ab7b-1f87da524741", + "name": "SQL-Planner", + "description": "Plan feasible steps for user task related to sql generation, generate sql statement and/or review the sql statement that can be derived from context", + "type": "planning", + "createdDateTime": "2023-08-27T10:39:00Z", + "updatedDateTime": "2023-08-27T14:39:00Z", + "iconUrl": "https://e7.pngegg.com/pngimages/775/350/png-clipart-action-plan-computer-icons-plan-miscellaneous-text-thumbnail.png", + "disabled": false, + "isPublic": true, + "profiles": [ "planning" ], + "mergeUtility": true, + "utilities": [], + "llmConfig": { + "provider": "openai", + "model": "gpt-4o-2024-11-20", + "max_recursion_depth": 10 + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_primary_stage.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_primary_stage.json new file mode 100644 index 00000000..5fefe691 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_primary_stage.json @@ -0,0 +1,47 @@ +{ + "name": "plan_primary_stage", + "description": "Plan the high level steps to finish the task", + "parameters": { + "type": "object", + "properties": { + "requirement_detail": { + "type": "string", + "description": "User requirements related to data tasks in detail, don't miss any information especially for those line items, values and numbers." + }, + "questions": { + "type": "array", + "description": "Break down user data requirements in details and in multiple ways, don't miss any entity type/value. The output format must be string array.", + "items": { + "type": "string", + "description": "Question converted from requirement in different ways to search in the knowledge base, be short and you can refer to the global knowledge.One question should contain only one main topic that with one entity type." + } + }, + "norm_questions": { + "type": "array", + "description": "normalize the generated questions, remove specific entity value. The output format must be string array.", + "items": { + "type": "string", + "description": "Normalized question" + } + }, + "entities": { + "type": "array", + "description": "entities with type and value", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "entity type" + }, + "value": { + "type": "string", + "description": "entity value" + } + } + } + } + }, + "required": [ "requirement_detail", "questions" ] + } +} diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_secondary_stage.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_secondary_stage.json new file mode 100644 index 00000000..ca6b3d0b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/plan_secondary_stage.json @@ -0,0 +1,18 @@ +{ + "name": "plan_secondary_stage", + "description": "Based on the primary stage planning, make more detail steps of the second stage if the primary stage needs more information.", + "parameters": { + "type": "object", + "properties": { + "task_description": { + "type": "string", + "description": "task description from primary steps" + }, + "solution_search_question": { + "type": "string", + "description": "Generate question to find the knowledge for text. Be short" + } + }, + "required": [ "task_description", "solution_search_question" ] + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_generation.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_generation.json new file mode 100644 index 00000000..98e40785 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_generation.json @@ -0,0 +1,26 @@ +{ + "name": "sql_generation", + "description": "Based on the planning steps, summarize the planning steps and output final steps.", + "parameters": { + "type": "object", + "properties": { + "is_sql_template": { + "type": "boolean", + "description": "If user request is to generate sql template instead of actual sql statement." + }, + "contains_sql_statements": { + "type": "boolean", + "description": "Set to true if the response contains sql statements." + }, + "related_tables": { + "type": "array", + "description": "table name in planning steps", + "items": { + "type": "string", + "description": "table name" + } + } + }, + "required": [ "related_tables", "is_sql_template", "contains_sql_statements" ] + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_review.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_review.json new file mode 100644 index 00000000..ad360e3c --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/functions/sql_review.json @@ -0,0 +1,22 @@ +{ + "name": "sql_review", + "description": "Verify and optimize sql statement", + "parameters": { + "type": "object", + "properties": { + "sql_statement": { + "type": "string", + "description": "sql statement, must including sql identifier that wrapped with ```sql \r\n```" + }, + "is_sql_template": { + "type": "boolean", + "description": "If user request is to generate sql template instead of actual sql statement." + }, + "contains_sql_statements": { + "type": "boolean", + "description": "Set to true if the response contains sql statements." + } + }, + "required": [ "sql_statement", "is_sql_template", "contains_sql_statements" ] + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/instructions/instruction.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/instructions/instruction.liquid new file mode 100644 index 00000000..52e498e5 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/instructions/instruction.liquid @@ -0,0 +1,33 @@ +You're a SQL planner and reviewer, your goal is using function sql_generation and sql_review to response. +You are going convert the user requirement into sql statements. +The user is dealing with a complex problem, and you need to break this complex problem into several small tasks to more easily solve the user's needs. +Follow these steps strictly and in order. + +1. If user raised a new task, call plan_primary_stage to generate the primary plan. + If the sql response can be generate directly based on the context, directly go to step 6 to call function sql_review. +2. If need_lookup_dictionary is True, call verify_dictionary_term to verify or get the enum/term/dictionary value. Pull id and name. + * If you no items retured, you can pull 100 records from the table and look for the match. + * If need_lookup_dictionary is False, skip calling verify_dictionary_term. +3. If need_breakdown_task is true, call plan_secondary_stage for the specific primary stage. +4. Repeat step 3 until you processed all the primary steps. +5. Call sql_generation function to generate SQL statements. +6. Call sql_review function to review SQL statements. This is the step you must go through before reply to the user. + + +{% if global_knowledges != empty -%} +===== +Global Knowledge: +Current date time is: {{ "now" | date: "%Y-%m-%d %H:%M" }} +{% for k in global_knowledges %} +{{ k }} +{% endfor %} +===== +{%- endif %} + + +==== IMPORTANT SYSTEM INSTRUCTION ==== +* The verify_dictionary_term function CAN'T generate INSERT SQL Statement. +* The table name must come from the relevant knowledge. has_found_relevant_knowledge must be true. +* Do not introduce your actions or intentions in any way. +* You MUST explicitly call function sql_review even the sql query is provided in previous context. + diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.1st.plan.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.1st.plan.liquid new file mode 100644 index 00000000..306f4214 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.1st.plan.liquid @@ -0,0 +1,39 @@ +You are a Task Planner. you will breakdown user business requirements into excutable sub-tasks. + +Thinking process: +1. Reference to "Domain Knowledge" if there is relevant knowledge; +2. Breakdown task into subtasks. + - The subtask should contain all needed parameters for subsequent steps. + - If limited information provided and there are furture information needed, or miss relationship between steps, set the need_breakdown_task to true. + - If there is extra knowledge or relationship needed between steps, set the need_breakdown_task to true for both steps. + - If the solution mentioned "related solutions" is needed, set the need_breakdown_task to true. + - You should find the relationships between data structure based on the domain knowledge strictly. If lack of information, set the need_breakdown_task to true. + - If you need to lookup the dictionary to verify or get the enum/term/dictionary value(exclude example data from attachment), set the need_lookup_dictionary to true. + - Don't set need_lookup_dictionary to true for attachment data. + - Seperate the dictionary lookup and need additional information/knowledge into different subtask. +3. Input argument must reference to corresponding variable name that retrieved by previous steps, variable name must start with '@'; +4. Output all the subtasks as much detail as possible in JSON: [{{ response_format }}] +5. You can NOT generate the final query before calling function plan_summary. + +Note: +* If the task includes repeat steps,e.g.same steps for multiple elements, only generate a single detailed solution without repeating steps for each elements. + +{% if global_knowledges != empty -%} +===== +Global Knowledge: +{% for k in global_knowledges %} +{{ k }} +{% endfor %} +{%- endif %} + +{% if domain_knowledges != empty -%} +===== +Domain Knowledge: +{% for k in domain_knowledges %} +{{ k }} +{% endfor %} +{%- endif %} +===== + +Task description: +{{ task_description }} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.2nd.plan.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.2nd.plan.liquid new file mode 100644 index 00000000..81e22fe8 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.2nd.plan.liquid @@ -0,0 +1,22 @@ +Reference to "Primary Planning" and the additional knowledge included. Breakdown task into multiple steps. +* The step should contains all needed parameters. +* The parameters can be extracted from the original task. +* You need to list all the steps in detail. Finding relationships should also be a step. +* When generate the steps, you should find the relationships between data structure based on the provided knowledge strictly. +* If need_lookup_dictionary is true, call verify_dictionary_term to verify or get the enum/term/dictionary value. Pull id and name/code. +* Output all the steps as much detail as possible in JSON: [{{ response_format }}] + +Additional Requirements: +* "output_results" is variable name that needed to be used in the next step. + +===== +Sub Task Description: +{{ task_description }} + +===== +Primary Planning: +{{ primary_plan }} + +===== +Additional Knowledge: +{{ additional_knowledge }} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.next.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.next.liquid new file mode 100644 index 00000000..f7388be6 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.next.liquid @@ -0,0 +1,12 @@ +What is the next step based on the CONVERSATION? +Route to the last handling agent in priority. +{% if expected_next_action_agent != empty -%} +Expected next action agent is {{ expected_next_action_agent }}. +{%- else -%} +Next action agent is inferred based on user lastest response. +{%- endif %} +{% if expected_user_goal_agent != empty -%} +Expected user goal agent is {{ expected_user_goal_agent }}. +{%- else -%} +User goal agent is inferred based on user initial request. +{%- endif %} diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.summarize.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.summarize.liquid new file mode 100644 index 00000000..ef0e3ae3 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/da7aad2c-8112-48a2-ab7b-1f87da524741/templates/two_stage.summarize.liquid @@ -0,0 +1,29 @@ +You are a planning summarizer. You will generate the final output in JSON format based on the task description, knowledge and related table structure and relationship. +Generate a simple business explaination of the quried data for the non tech audience. call sql_review as the final step after generating the sql statement. + +Requirements: +{{ summary_requirements }} + +===== +Task description: +{{ task_description }} + +===== +Global Knowledges: +{{ global_knowledges }} + +===== +Domain Knowledges: +{{ domain_knowledges }} + +===== +Dictionary Items: +{{ dictionary_items }} + +===== +Table Structure: +{{ table_structure }} + +===== +Attached Excel Information: +{{ excel_import_result }} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-db-sql_table_definition.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-db-sql_table_definition.json index 6e4b946c..0f808afe 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-db-sql_table_definition.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-db-sql_table_definition.json @@ -4,15 +4,19 @@ "parameters": { "type": "object", "properties": { - "table": { - "type": "string", - "description": "table name" + "tables": { + "type": "array", + "description": "table name in planning steps", + "items": { + "type": "string", + "description": "table name" + } }, "reason": { "type": "string", "description": "the reason why you need to call sql_table_definition" } }, - "required": [ "table", "reason" ] + "required": [ "tables", "reason" ] } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json index 00d1e0b3..9f902a01 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json @@ -1,7 +1,7 @@ { "id": "beda4c12-e1ec-4b4b-b328-3df4a6687c4f", "name": "SQL Driver", - "description": "Transfer to this Agent only when executable SQL statements are explicitly provided in the context.", + "description": "Transfer to this Agent when user mentions to execute the sql statement. Only call when executable SQL statements are explicitly provided in the context.", "iconUrl": "https://cdn-icons-png.flaticon.com/512/3161/3161158.png", "type": "task", "createdDateTime": "2023-11-15T13:49:00Z", diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/sql_table_definition.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/sql_table_definition.json index 748202b4..580ee339 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/sql_table_definition.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/sql_table_definition.json @@ -4,11 +4,15 @@ "parameters": { "type": "object", "properties": { - "table": { - "type": "string", - "description": "table need to check" + "tables": { + "type": "array", + "description": "table name in planning steps", + "items": { + "type": "string", + "description": "table name" + } } }, - "required": [ "table" ] + "required": [ "tables" ] } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instructions/instruction.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instructions/instruction.liquid index 8daddf49..7cc97bb0 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instructions/instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instructions/instruction.liquid @@ -1,4 +1,4 @@ -You're a SQL driver who can find the database information or query the data. +You're a SQL driver who can execute the sql statement. Your response must meet below requirements: * You can only execute the SQL from the conversation. You can't generate one by yourself; From 3c4821c368e63e5b96dbe333227f6829ae3f201f Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 17 Dec 2024 01:18:24 +0000 Subject: [PATCH 14/16] EntityFrameworkCore.BootKit v8.7 --- src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj | 4 ++-- .../BotSharp.Plugin.MongoStorage.csproj | 2 +- .../BotSharp.Plugin.WebDriver.csproj | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 1c9955aa..d7f6cdde 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -190,10 +190,10 @@ - + - + diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj b/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj index 88709238..5ac884e0 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj index 296e17c3..fde5adc9 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj +++ b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj @@ -12,15 +12,12 @@ - - - - + From 067a701ff1f2277c653ad201b9396749844fb09a Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 17 Dec 2024 06:37:18 +0000 Subject: [PATCH 15/16] remove retry --- .../Infrastructures/Events/RedisPublisher.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs index 602fde53..5b32cd5a 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs @@ -79,14 +79,13 @@ public class RedisPublisher : IEventPublisher return exists; } - private NameValueEntry[] AssembleMessage(RedisValue message, int retry = 0) + private NameValueEntry[] AssembleMessage(RedisValue message) { return [ new NameValueEntry("message", message), new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")), - new NameValueEntry("machine", Environment.MachineName), - new NameValueEntry("retry", retry), + new NameValueEntry("machine", Environment.MachineName) ]; } @@ -102,10 +101,8 @@ public class RedisPublisher : IEventPublisher try { var message = entry.Values.First(x => x.Name == "message").Value; - var retryKv = entry.Values.FirstOrDefault(x => x.Name == "retry"); - int.TryParse(retryKv.Value, out int retry); var messageId = await db.StreamAddAsync(channel, - AssembleMessage(message, retry: retry + 1), + AssembleMessage(message), maxLength: 1000 * 10000); _logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})"); From ab3e2e99dade2f0a53b2c98d227dae46f79a1b0f Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 17 Dec 2024 06:48:45 +0000 Subject: [PATCH 16/16] remove machine --- .../BotSharp.Core/Infrastructures/Events/RedisPublisher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs index 5b32cd5a..b6517535 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs @@ -84,8 +84,7 @@ public class RedisPublisher : IEventPublisher return [ new NameValueEntry("message", message), - new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")), - new NameValueEntry("machine", Environment.MachineName) + new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")) ]; }