diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs similarity index 76% rename from src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs rename to src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs index c8bffe6c..3362f1a4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Routing; -namespace BotSharp.Abstraction.Routing.Planning; +namespace BotSharp.Abstraction.Planning; public interface IExecutor { diff --git a/src/Infrastructure/BotSharp.Abstraction/Planning/ITaskPlanner.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/ITaskPlanner.cs index a5668a52..67f30fb1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Planning/ITaskPlanner.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Planning/ITaskPlanner.cs @@ -1,9 +1,19 @@ +using BotSharp.Abstraction.Functions.Models; + namespace BotSharp.Abstraction.Planning; /// /// Planning process for Task Agent +/// https://www.promptingguide.ai/techniques/cot /// -public class ITaskPlanner +public interface ITaskPlanner { - + Task GetNextInstruction(Agent router, string messageId, List dialogs); + Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs); + Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs); + List BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) + => dialogs; + bool AfterHandleContext(List dialogs, List taskAgentDialogs) + => true; + int MaxLoopCount => 5; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index f3859a3a..83fe2533 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -32,9 +32,13 @@ public interface IBotSharpRepository : IHaveServiceProvider List GetUserByIds(List ids) => throw new NotImplementedException(); List GetUsersByAffiliateId(string affiliateId) => throw new NotImplementedException(); User? GetUserByUserName(string userName) => throw new NotImplementedException(); + Dashboard? GetDashboard(string id = null) => throw new NotImplementedException(); void CreateUser(User user) => throw new NotImplementedException(); void UpdateExistUser(string userId, User user) => throw new NotImplementedException(); void UpdateUserVerified(string userId) => throw new NotImplementedException(); + void AddDashboardConversation(string userId, string conversationId) => throw new NotImplementedException(); + void RemoveDashboardConversation(string userId, string conversationId) => throw new NotImplementedException(); + void UpdateDashboardConversation(string userId, DashboardConversation dashConv) => throw new NotImplementedException(); void UpdateUserVerificationCode(string userId, string verficationCode) => throw new NotImplementedException(); void UpdateUserPassword(string userId, string password) => throw new NotImplementedException(); void UpdateUserEmail(string userId, string email) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs index 1d1913dd..c595d59c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs @@ -12,6 +12,11 @@ public class RuleType /// public const string DataValidation = "data-validation"; + /// + /// The reasoning approach name for next step + /// + public const string Reasoner = "reasoner"; + /// /// The planning approach name for next step /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IRoutingPlaner.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IRoutingPlaner.cs deleted file mode 100644 index 7f3abde9..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IRoutingPlaner.cs +++ /dev/null @@ -1,19 +0,0 @@ -using BotSharp.Abstraction.Functions.Models; - -namespace BotSharp.Abstraction.Routing.Planning; - -/// -/// Task breakdown and execution plan -/// https://www.promptingguide.ai/techniques/cot -/// -public interface IRoutingPlaner -{ - Task GetNextInstruction(Agent router, string messageId, List dialogs); - Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs); - Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs); - List BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) - => dialogs; - bool AfterHandleContext(List dialogs, List taskAgentDialogs) - => true; - int MaxLoopCount => 5; -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Reasoning/IRoutingReasoner.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Reasoning/IRoutingReasoner.cs new file mode 100644 index 00000000..f7bb5eb4 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Reasoning/IRoutingReasoner.cs @@ -0,0 +1,30 @@ +using BotSharp.Abstraction.Functions.Models; + +namespace BotSharp.Abstraction.Routing.Reasoning; + +/// +/// Reasoning approaches for large language models (LLMs) help enhance their ability to solve complex problems, +/// handle tasks requiring logic, and provide accurate and contextually appropriate responses. +/// +public interface IRoutingReasoner +{ + string Name => "Unnamed Reasoner"; + string Description => "Each of these approaches leverages the capabilities of LLMs to reason more effectively, " + + "ensuring better performance and more coherent outputs across various types of complex tasks."; + + int MaxLoopCount => 5; + + Task GetNextInstruction(Agent router, string messageId, List dialogs); + + Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) + => Task.FromResult(true); + + Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) + => Task.FromResult(true); + + List BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) + => dialogs; + + bool AfterHandleContext(List dialogs, List taskAgentDialogs) + => true; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index cc67c1f7..134ca25b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -29,4 +29,8 @@ public interface IUserService Task UpdatePassword(string newPassword, string verificationCode); Task GetUserTokenExpires(); Task UpdateUsersIsDisable(List userIds, bool isDisable); + Task AddDashboardConversation(string userId, string conversationId); + Task RemoveDashboardConversation(string userId, string conversationId); + Task UpdateDashboardConversation(string userId, DashboardConversation dashConv); + Task GetDashboard(string userId); } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/Dashboard.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Dashboard.cs new file mode 100644 index 00000000..753cb390 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Dashboard.cs @@ -0,0 +1,20 @@ + +namespace BotSharp.Abstraction.Users.Models; + +public class Dashboard +{ + public IList ConversationList { get; set; } = []; +} + +public class DashboardComponent +{ + public required string Id { get; set; } + public string? Name { get; set; } +} + +public class DashboardConversation : DashboardComponent +{ + public string? ConversationId { get; set; } + public string? Instruction { get; set; } = ""; +} + diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index 825f7d8f..9f35c1c8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Using.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs @@ -12,7 +12,6 @@ global using BotSharp.Abstraction.Agents.Enums; global using BotSharp.Abstraction.Infrastructures.Enums; global using BotSharp.Abstraction.Models; global using BotSharp.Abstraction.Routing.Models; -global using BotSharp.Abstraction.Routing.Planning; global using BotSharp.Abstraction.Templating; global using BotSharp.Abstraction.Translation.Attributes; global using BotSharp.Abstraction.Messaging.Enums; diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 32987e69..1c82c84f 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -59,6 +59,11 @@ + + + + + @@ -73,10 +78,6 @@ - - - - @@ -120,16 +121,19 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest + + PreserveNewest diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index e2b11afb..09936dec 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -1,13 +1,13 @@ using BotSharp.Abstraction.Google.Settings; using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Messaging; +using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Plugins.Models; -using BotSharp.Abstraction.Routing.Planning; using BotSharp.Abstraction.Settings; using BotSharp.Abstraction.Templating; using BotSharp.Core.Instructs; using BotSharp.Core.Messaging; -using BotSharp.Core.Routing.Planning; +using BotSharp.Core.Routing.Reasoning; using BotSharp.Core.Templating; using BotSharp.Core.Translation; using Microsoft.Extensions.Configuration; diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 9600bbc8..5dd4b3d9 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -41,6 +41,11 @@ public partial class FileRepository return Users.FirstOrDefault(x => x.UserName == userName.ToLower()); } + public Dashboard? GetDashboard(string id = null) + { + return Dashboards.FirstOrDefault(); + } + public void CreateUser(User user) { var userId = Guid.NewGuid().ToString(); @@ -191,4 +196,68 @@ public partial class FileRepository _users = []; return true; } + + public void AddDashboardConversation(string userId, string conversationId) + { + var user = GetUserById(userId); + if (user == null) return; + + // one user only has one dashboard currently + var dash = Dashboards.FirstOrDefault(); + dash ??= new(); + var existingConv = dash.ConversationList.FirstOrDefault(x => string.Equals(x.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase)); + if (existingConv != null) return; + + var dashconv = new DashboardConversation + { + Id = Guid.NewGuid().ToString(), + ConversationId = conversationId + }; + + dash.ConversationList.Add(dashconv); + + var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, userId); + var path = Path.Combine(dir, DASHBOARD_FILE); + File.WriteAllText(path, JsonSerializer.Serialize(dash, _options)); + } + + public void RemoveDashboardConversation(string userId, string conversationId) + { + var user = GetUserById(userId); + if (user == null) return; + + // one user only has one dashboard currently + var dash = Dashboards.FirstOrDefault(); + if (dash == null) return; + + var dashconv = dash.ConversationList.FirstOrDefault( + c => string.Equals(c.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase)); + if (dashconv == null) return; + + dash.ConversationList.Remove(dashconv); + + var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, userId); + var path = Path.Combine(dir, DASHBOARD_FILE); + File.WriteAllText(path, JsonSerializer.Serialize(dash, _options)); + } + + public void UpdateDashboardConversation(string userId, DashboardConversation dashConv) + { + var user = GetUserById(userId); + if (user == null) return; + + // one user only has one dashboard currently + var dash = Dashboards.FirstOrDefault(); + if (dash == null) return; + + var curIdx = dash.ConversationList.ToList().FindIndex( + x => string.Equals(x.ConversationId, dashConv.ConversationId, StringComparison.OrdinalIgnoreCase)); + if (curIdx < 0) return; + + dash.ConversationList[curIdx] = dashConv; + + var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, userId); + var path = Path.Combine(dir, DASHBOARD_FILE); + File.WriteAllText(path, JsonSerializer.Serialize(dash, _options)); + } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs index 0edcb699..57fbcb91 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs @@ -22,6 +22,7 @@ public partial class FileRepository : IBotSharpRepository private const string AGENT_FILE = "agent.json"; private const string AGENT_INSTRUCTION_FILE = "instruction"; private const string AGENT_SAMPLES_FILE = "samples.txt"; + private const string DASHBOARD_FILE = "dashboard.json"; private const string AGENT_INSTRUCTIONS_FOLDER = "instructions"; private const string AGENT_FUNCTIONS_FOLDER = "functions"; private const string AGENT_TEMPLATES_FOLDER = "templates"; @@ -83,6 +84,7 @@ public partial class FileRepository : IBotSharpRepository private List _roles = new List(); private List _users = new List(); + private List _dashboards = []; private List _agents = new List(); private List _roleAgents = new List(); private List _userAgents = new List(); @@ -170,6 +172,36 @@ public partial class FileRepository : IBotSharpRepository } } + private IQueryable Dashboards + { + get + { + if (!_dashboards.IsNullOrEmpty()) + { + return _dashboards.AsQueryable(); + } + + var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER); + _dashboards = []; + if (Directory.Exists(dir)) + { + foreach (var d in Directory.GetDirectories(dir)) + { + var dashboardFile = Path.Combine(d, DASHBOARD_FILE); + if (!Directory.Exists(d) || !File.Exists(dashboardFile)) + continue; + + var json = File.ReadAllText(dashboardFile); + var dash = JsonSerializer.Deserialize(json, _options); + + if (dash == null) continue; + _dashboards.Add(dash); + } + } + return _dashboards.AsQueryable(); + } + } + private IQueryable Agents { get diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs index 646dd481..2ee6f9d7 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs @@ -4,7 +4,7 @@ using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; -using BotSharp.Core.Routing.Planning; +using BotSharp.Core.Routing.Reasoning; namespace BotSharp.Core.Routing.Handlers; @@ -27,7 +27,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase//, IRoutingH public List Planers => new List { - nameof(HFPlanner) + nameof(HFReasoner) }; public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs index da58b98b..0d11f296 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs @@ -1,7 +1,7 @@ using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; -using BotSharp.Core.Routing.Planning; +using BotSharp.Core.Routing.Reasoning; namespace BotSharp.Core.Routing.Handlers; @@ -19,7 +19,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase//, IRouti public List Planers => new List { - nameof(HFPlanner) + nameof(HFReasoner) }; public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs index 5b5b1025..038488eb 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs @@ -1,5 +1,5 @@ using BotSharp.Abstraction.Routing.Settings; -using BotSharp.Core.Routing.Planning; +using BotSharp.Core.Routing.Reasoning; namespace BotSharp.Core.Routing.Handlers; @@ -26,7 +26,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase//, IRoutin public List Planers => new List { - nameof(HFPlanner) + nameof(HFReasoner) }; public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/FirstStagePlanParameter.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/FirstStagePlanParameter.cs deleted file mode 100644 index 7bececc6..00000000 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/FirstStagePlanParameter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -public class FirstStagePlanParameter -{ - [JsonPropertyName("input_args")] - public JsonDocument[] Parameters { get; set; } = new JsonDocument[0]; - - [JsonPropertyName("output_results")] - public string[] Results { get; set; } = new string[0]; - - public override string ToString() - { - return $"INPUTS:\r\n{JsonSerializer.Serialize(Parameters)}\r\n\r\nOUTPUTS:\r\n{JsonSerializer.Serialize(Results)}"; - } -} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlan.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlan.cs deleted file mode 100644 index f180c043..00000000 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlan.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; - -namespace BotSharp.Core.Routing.Planning; - -public class SecondStagePlan -{ - [JsonPropertyName("related_tables")] - public string[] Tables { get; set; } = new string[0]; - - [JsonPropertyName("description")] - public string Description { get; set; } = ""; - - [JsonPropertyName("tool_name")] - public string Tool { get; set; } = ""; - - [JsonPropertyName("input_args")] - public JsonDocument[] Parameters { get; set; } = new JsonDocument[0]; - - [JsonPropertyName("output_results")] - public string[] Results { get; set; } = new string[0]; -} diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlanParameter.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlanParameter.cs deleted file mode 100644 index 1d043740..00000000 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlanParameter.cs +++ /dev/null @@ -1,4 +0,0 @@ -public class SecondStagePlanParameter : FirstStagePlanParameter -{ - -} diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/HFPlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/HFReasoner.cs similarity index 72% rename from src/Infrastructure/BotSharp.Core/Routing/Planning/HFPlanner.cs rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/HFReasoner.cs index 8984da51..0c825a05 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/HFPlanner.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/HFReasoner.cs @@ -1,17 +1,33 @@ -using BotSharp.Abstraction.Routing.Planning; +/***************************************************************************** + Copyright 2024 Written by Haiping Chen. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + +using BotSharp.Abstraction.Routing.Reasoning; using BotSharp.Abstraction.Templating; -namespace BotSharp.Core.Routing.Planning; +namespace BotSharp.Core.Routing.Reasoning; /// -/// Human feedback based planner +/// Human feedback based reasoner /// -public class HFPlanner : IRoutingPlaner +public class HFReasoner : IRoutingReasoner { private readonly IServiceProvider _services; private readonly ILogger _logger; - public HFPlanner(IServiceProvider services, ILogger logger) + public HFReasoner(IServiceProvider services, ILogger logger) { _services = services; _logger = logger; @@ -37,7 +53,7 @@ public class HFPlanner : IRoutingPlaner { new RoleDialogModel(AgentRole.User, next) { - FunctionName = nameof(HFPlanner), + FunctionName = nameof(HFReasoner), MessageId = messageId } }; @@ -60,7 +76,7 @@ public class HFPlanner : IRoutingPlaner } // Fix LLM malformed response - PlannerHelper.FixMalformedResponse(_services, inst); + ReasonerHelper.FixMalformedResponse(_services, inst); return inst; } @@ -87,13 +103,13 @@ public class HFPlanner : IRoutingPlaner public async Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs) { var context = _services.GetRequiredService(); - context.Empty(reason: $"Agent queue is cleared by {nameof(HFPlanner)}"); + context.Empty(reason: $"Agent queue is cleared by {nameof(HFReasoner)}"); return true; } private string GetNextStepPrompt(Agent router) { - var template = router.Templates.First(x => x.Name == "planner_prompt.hf").Content; + var template = router.Templates.First(x => x.Name == "reasoner.hf").Content; var render = _services.GetRequiredService(); // update states var conv = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs similarity index 91% rename from src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs index a1d413f0..6bb23e97 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs @@ -1,6 +1,6 @@ -using BotSharp.Abstraction.Routing.Planning; +using BotSharp.Abstraction.Planning; -namespace BotSharp.Core.Routing.Planning; +namespace BotSharp.Core.Routing.Reasoning; public class InstructExecutor : IExecutor { diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs similarity index 73% rename from src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs index 6ebc5012..4f7bba6c 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs @@ -1,16 +1,35 @@ +/***************************************************************************** + Copyright 2024 Written by Haiping Chen. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + using BotSharp.Abstraction.Infrastructures.Enums; using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Routing.Planning; +using BotSharp.Abstraction.Routing.Reasoning; using BotSharp.Abstraction.Templating; -namespace BotSharp.Core.Routing.Planning; +namespace BotSharp.Core.Routing.Reasoning; -public class NaivePlanner : IRoutingPlaner +/// +/// simple or unsophisticated methods used to decide which specialized model or module in a system to engage for a given task. +/// +public class NaiveReasoner : IRoutingReasoner { private readonly IServiceProvider _services; private readonly ILogger _logger; - public NaivePlanner(IServiceProvider services, ILogger logger) + public NaiveReasoner(IServiceProvider services, ILogger logger) { _services = services; _logger = logger; @@ -46,7 +65,7 @@ public class NaivePlanner : IRoutingPlaner { new RoleDialogModel(AgentRole.User, next) { - FunctionName = nameof(NaivePlanner), + FunctionName = nameof(NaiveReasoner), MessageId = messageId } }; @@ -69,7 +88,7 @@ public class NaivePlanner : IRoutingPlaner } // Fix LLM malformed response - PlannerHelper.FixMalformedResponse(_services, inst); + ReasonerHelper.FixMalformedResponse(_services, inst); return inst; } @@ -99,7 +118,7 @@ public class NaivePlanner : IRoutingPlaner } else { - context.Empty(reason: $"Agent queue is cleared by {nameof(NaivePlanner)}"); + context.Empty(reason: $"Agent queue is cleared by {nameof(NaiveReasoner)}"); // context.Push(inst.OriginalAgent, "Push user goal agent"); } return true; @@ -107,7 +126,7 @@ public class NaivePlanner : IRoutingPlaner private string GetNextStepPrompt(Agent router) { - var template = router.Templates.First(x => x.Name == "planner_prompt.naive").Content; + var template = router.Templates.First(x => x.Name == "reasoner.naive").Content; var states = _services.GetRequiredService(); var render = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/OneStepForwardReasoner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/OneStepForwardReasoner.cs new file mode 100644 index 00000000..97568751 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/OneStepForwardReasoner.cs @@ -0,0 +1,137 @@ +/***************************************************************************** + Copyright 2024 Written by Haiping Chen. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + +using BotSharp.Abstraction.Infrastructures.Enums; +using BotSharp.Abstraction.Routing.Models; +using BotSharp.Abstraction.Routing.Reasoning; +using BotSharp.Abstraction.Templating; + +namespace BotSharp.Core.Routing.Reasoning; + +/// +/// One-step forward reasoning is a straightforward reasoning approach where the model or agent evaluates its current state +/// and takes the next best logical step toward the solution without extensive lookahead or planning. +/// This type of reasoning involves making a decision based on the current situation and immediate context +/// rather than considering multiple future steps or possibilities. +/// +public class OneStepForwardReasoner : IRoutingReasoner +{ + public string Name => "one-step-forward"; + + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public OneStepForwardReasoner(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task GetNextInstruction(Agent router, string messageId, List dialogs) + { + var next = GetNextStepPrompt(router); + + var inst = new FunctionCallFromLlm(); + + // chat completion + var completion = CompletionProvider.GetChatCompletion(_services, + provider: router?.LlmConfig?.Provider, + model: router?.LlmConfig?.Model); + + int retryCount = 0; + while (retryCount < 3) + { + string text = string.Empty; + try + { + // text completion + // text = await completion.GetCompletion(content, router.Id, messageId); + dialogs = new List + { + new RoleDialogModel(AgentRole.User, next) + { + FunctionName = Name, + MessageId = messageId + } + }; + var response = await completion.GetChatCompletions(router, dialogs); + + inst = response.Content.JsonContent(); + break; + } + catch (Exception ex) + { + _logger.LogError($"{ex.Message}: {text}"); + inst.Function = "response_to_user"; + inst.Response = ex.Message; + inst.AgentName = "Router"; + } + finally + { + retryCount++; + } + } + + // Fix LLM malformed response + ReasonerHelper.FixMalformedResponse(_services, inst); + + return inst; + } + + 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 (inst.UnmatchedAgent) + { + var unmatchedAgentId = context.GetCurrentAgentId(); + + // Exclude the wrong routed agent + var agents = router.TemplateDict["routing_agents"] as RoutableAgent[]; + router.TemplateDict["routing_agents"] = agents.Where(x => x.AgentId != unmatchedAgentId).ToArray(); + + // Handover to Router; + context.Pop(); + } + else + { + context.Empty(reason: $"Agent queue is cleared by {nameof(NaiveReasoner)}"); + // context.Push(inst.OriginalAgent, "Push user goal agent"); + } + return true; + } + + private string GetNextStepPrompt(Agent router) + { + var template = router.Templates.First(x => x.Name == "reasoner.one-step-forward").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/Infrastructure/BotSharp.Core/Routing/Planning/PlannerHelper.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs similarity index 96% rename from src/Infrastructure/BotSharp.Core/Routing/Planning/PlannerHelper.cs rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs index 4500ca1d..7c8debf6 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/PlannerHelper.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs @@ -1,6 +1,6 @@ -namespace BotSharp.Core.Routing.Planning; +namespace BotSharp.Core.Routing.Reasoning; -public static class PlannerHelper +public static class ReasonerHelper { /// /// Sometimes LLM hallucinates and fails to set function names correctly. diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/SequentialPlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/SequentialReasoner.cs similarity index 81% rename from src/Infrastructure/BotSharp.Core/Routing/Planning/SequentialPlanner.cs rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/SequentialReasoner.cs index f6b05375..959747db 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/SequentialPlanner.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/SequentialReasoner.cs @@ -1,11 +1,30 @@ +/***************************************************************************** + Copyright 2024 Written by Haiping Chen. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Routing.Planning; +using BotSharp.Abstraction.Routing.Reasoning; using BotSharp.Abstraction.Templating; -namespace BotSharp.Core.Routing.Planning; +namespace BotSharp.Core.Routing.Reasoning; -public class SequentialPlanner : IRoutingPlaner +/// +/// Sequential tasks focused reasoning approach +/// +public class SequentialReasoner : IRoutingReasoner { private readonly IServiceProvider _services; private readonly ILogger _logger; @@ -14,7 +33,7 @@ public class SequentialPlanner : IRoutingPlaner public int MaxLoopCount => 100; private FunctionCallFromLlm _lastInst; - public SequentialPlanner(IServiceProvider services, ILogger logger) + public SequentialReasoner(IServiceProvider services, ILogger logger) { _services = services; _logger = logger; @@ -72,7 +91,7 @@ public class SequentialPlanner : IRoutingPlaner { new RoleDialogModel(AgentRole.User, next) { - FunctionName = nameof(SequentialPlanner), + FunctionName = nameof(SequentialReasoner), MessageId = messageId } }; @@ -139,7 +158,7 @@ public class SequentialPlanner : IRoutingPlaner if (message.StopCompletion) { - context.Empty(reason: $"Agent queue is cleared by {nameof(SequentialPlanner)}"); + context.Empty(reason: $"Agent queue is cleared by {nameof(SequentialReasoner)}"); return false; } @@ -154,7 +173,7 @@ public class SequentialPlanner : IRoutingPlaner private string GetNextStepPrompt(Agent router) { - var template = router.Templates.First(x => x.Name == "planner_prompt.sequential").Content; + var template = router.Templates.First(x => x.Name == "reasoner.sequential").Content; var render = _services.GetRequiredService(); return render.Render(template, new Dictionary @@ -169,11 +188,11 @@ public class SequentialPlanner : IRoutingPlaner var inst = new DecomposedStep(); var llmProviderService = _services.GetRequiredService(); - var model = llmProviderService.GetProviderModel("azure-openai", "gpt-4"); + var model = llmProviderService.GetProviderModel("openai", "gpt-4o"); // chat completion var completion = CompletionProvider.GetChatCompletion(_services, - provider: "azure-openai", + provider: "openai", model: model.Name); int retryCount = 0; @@ -185,7 +204,7 @@ public class SequentialPlanner : IRoutingPlaner var response = await completion.GetChatCompletions(new Agent { Id = router.Id, - Name = nameof(SequentialPlanner), + Name = nameof(SequentialReasoner), Instruction = systemPrompt }, dialogs); @@ -208,16 +227,11 @@ public class SequentialPlanner : IRoutingPlaner private string GetDecomposeTaskPrompt(Agent router) { - var template = router.Templates.First(x => x.Name == "planner_prompt.sequential.get_remaining_task").Content; + var template = router.Templates.First(x => x.Name == "reasoner.sequential.get_remaining_task").Content; var render = _services.GetRequiredService(); return render.Render(template, new Dictionary { }); } - - public Task GetNextInstruction(Agent router, string messageId) - { - throw new NotImplementedException(); - } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingPlugin.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingPlugin.cs index 7a751057..f5c2a9d6 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingPlugin.cs @@ -1,10 +1,8 @@ -using BotSharp.Abstraction.Routing; -using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Routing.Planning; +using BotSharp.Abstraction.Routing.Reasoning; using BotSharp.Abstraction.Routing.Settings; using BotSharp.Abstraction.Settings; using BotSharp.Core.Routing.Hooks; -using BotSharp.Core.Routing.Planning; +using BotSharp.Core.Routing.Reasoning; using Microsoft.Extensions.Configuration; namespace BotSharp.Core.Routing; @@ -35,8 +33,10 @@ public class RoutingPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetPlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetPlanner.cs deleted file mode 100644 index 0fcaa5a1..00000000 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetPlanner.cs +++ /dev/null @@ -1,24 +0,0 @@ -using BotSharp.Abstraction.Routing.Enums; -using BotSharp.Abstraction.Routing.Planning; -using BotSharp.Core.Routing.Planning; - -namespace BotSharp.Core.Routing; - -public partial class RoutingService -{ - public IRoutingPlaner GetPlanner(Agent router) - { - var rule = router.RoutingRules.FirstOrDefault(x => x.Type == RuleType.Planner); - - var planner = _services.GetServices(). - FirstOrDefault(x => x.GetType().Name.EndsWith(rule.Field)); - - if (planner == null) - { - _logger.LogError($"Can't find specific planner named {rule.Field}"); - return _services.GetRequiredService(); - } - - return planner; - } -} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs new file mode 100644 index 00000000..5b84122c --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs @@ -0,0 +1,112 @@ +using BotSharp.Abstraction.Infrastructures.Enums; +using BotSharp.Abstraction.Planning; +using BotSharp.Abstraction.Routing.Enums; +using BotSharp.Abstraction.Routing.Reasoning; +using BotSharp.Core.Routing.Reasoning; + +namespace BotSharp.Core.Routing; + +public partial class RoutingService +{ + public async Task InstructLoop(RoleDialogModel message, List dialogs) + { + RoleDialogModel response = default; + + var agentService = _services.GetRequiredService(); + var convService = _services.GetRequiredService(); + var storage = _services.GetRequiredService(); + + _router = await agentService.LoadAgent(message.CurrentAgentId); + + var states = _services.GetRequiredService(); + var executor = _services.GetRequiredService(); + + var planner = GetReasoner(_router); + + _context.Push(_router.Id); + + // Handle multi-language for input + var agentSettings = _services.GetRequiredService(); + if (agentSettings.EnableTranslator) + { + var translator = _services.GetRequiredService(); + + var language = states.GetState(StateConst.LANGUAGE, LanguageType.ENGLISH); + if (language != LanguageType.ENGLISH) + { + message.SecondaryContent = message.Content; + message.Content = await translator.Translate(_router, message.MessageId, message.Content, + language: LanguageType.ENGLISH, + clone: false); + } + } + + dialogs.Add(message); + storage.Append(convService.ConversationId, message); + + // Get first instruction + _router.TemplateDict["conversation"] = await GetConversationContent(dialogs); + var inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs); + + int loopCount = 1; + while (true) + { + await HookEmitter.Emit(_services, async hook => + await hook.OnRoutingInstructionReceived(inst, message) + ); + + // Save states + states.SaveStateByArgs(inst.Arguments); + +#if DEBUG + Console.WriteLine($"*** Next Instruction *** {inst}"); +#else + _logger.LogInformation($"*** Next Instruction *** {inst}"); +#endif + await planner.AgentExecuting(_router, inst, message, dialogs); + + // Handover to Task Agent + if (inst.HandleDialogsByPlanner) + { + var dialogWithoutContext = planner.BeforeHandleContext(inst, message, dialogs); + response = await executor.Execute(this, inst, message, dialogWithoutContext); + planner.AfterHandleContext(dialogs, dialogWithoutContext); + } + else + { + response = await executor.Execute(this, inst, message, dialogs); + } + + await planner.AgentExecuted(_router, inst, response, dialogs); + + if (loopCount >= planner.MaxLoopCount || _context.IsEmpty) + { + break; + } + + // Get next instruction from Planner + _router.TemplateDict["conversation"] = await GetConversationContent(dialogs); + inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs); + loopCount++; + } + + return response; + } + + public IRoutingReasoner GetReasoner(Agent router) + { + var rule = router.RoutingRules.FirstOrDefault(x => x.Type == RuleType.Reasoner); + + var reasoner = _services.GetServices(). + FirstOrDefault(x => x.GetType().Name.EndsWith(rule.Field)); + + if (reasoner == null) + { + _logger.LogError($"Can't find specific planner named {rule.Field}"); + // Default use NaiveReasoner + return _services.GetRequiredService(); + } + + return reasoner; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 4f57571f..d3eb2b1c 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -1,6 +1,4 @@ -using BotSharp.Abstraction.Infrastructures.Enums; using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Routing.Planning; using BotSharp.Abstraction.Routing.Settings; namespace BotSharp.Core.Routing; @@ -60,97 +58,12 @@ public partial class RoutingService : IRoutingService return response; } - public async Task InstructLoop(RoleDialogModel message, List dialogs) - { - RoleDialogModel response = default; - - var agentService = _services.GetRequiredService(); - var convService = _services.GetRequiredService(); - var storage = _services.GetRequiredService(); - - _router = await agentService.LoadAgent(message.CurrentAgentId); - - var states = _services.GetRequiredService(); - var executor = _services.GetRequiredService(); - - var planner = GetPlanner(_router); - - _context.Push(_router.Id); - - // Handle multi-language for input - var agentSettings = _services.GetRequiredService(); - if (agentSettings.EnableTranslator) - { - var translator = _services.GetRequiredService(); - - var language = states.GetState(StateConst.LANGUAGE, LanguageType.ENGLISH); - if (language != LanguageType.ENGLISH) - { - message.SecondaryContent = message.Content; - message.Content = await translator.Translate(_router, message.MessageId, message.Content, - language: LanguageType.ENGLISH, - clone: false); - } - } - - dialogs.Add(message); - storage.Append(convService.ConversationId, message); - - // Get first instruction - _router.TemplateDict["conversation"] = await GetConversationContent(dialogs); - var inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs); - - int loopCount = 1; - while (true) - { - await HookEmitter.Emit(_services, async hook => - await hook.OnRoutingInstructionReceived(inst, message) - ); - - // Save states - states.SaveStateByArgs(inst.Arguments); - -#if DEBUG - Console.WriteLine($"*** Next Instruction *** {inst}"); -#else - _logger.LogInformation($"*** Next Instruction *** {inst}"); -#endif - await planner.AgentExecuting(_router, inst, message, dialogs); - - // Handover to Task Agent - if (inst.HandleDialogsByPlanner) - { - var dialogWithoutContext = planner.BeforeHandleContext(inst, message, dialogs); - response = await executor.Execute(this, inst, message, dialogWithoutContext); - planner.AfterHandleContext(dialogs, dialogWithoutContext); - } - else - { - response = await executor.Execute(this, inst, message, dialogs); - } - - await planner.AgentExecuted(_router, inst, response, dialogs); - - if (loopCount >= planner.MaxLoopCount || _context.IsEmpty) - { - break; - } - - // Get next instruction from Planner - _router.TemplateDict["conversation"] = await GetConversationContent(dialogs); - inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs); - loopCount++; - } - - return response; - } - public List GetHandlers(Agent router) { - var planer = GetPlanner(router); + var reasoner = GetReasoner(router); return _services.GetServices() - .Where(x => x.Planers == null || x.Planers.Contains(planer.GetType().Name)) + .Where(x => x.Planers == null || x.Planers.Contains(reasoner.GetType().Name)) .Where(x => !string.IsNullOrEmpty(x.Description)) .Select((x, i) => new RoutingHandlerDef { diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 9e74d961..b3cb884e 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -736,4 +736,42 @@ public class UserService : IUserService } return true; } + + public async Task AddDashboardConversation(string userId, string conversationId) + { + var db = _services.GetRequiredService(); + db.AddDashboardConversation(userId, conversationId); + + await Task.CompletedTask; + return true; + } + + public async Task RemoveDashboardConversation(string userId, string conversationId) + { + var db = _services.GetRequiredService(); + db.RemoveDashboardConversation(userId, conversationId); + + await Task.CompletedTask; + return true; + } + + public async Task UpdateDashboardConversation(string userId, DashboardConversation newDashConv) + { + var db = _services.GetRequiredService(); + var dashConv = db.GetDashboard(userId)?.ConversationList.FirstOrDefault(x => string.Equals(x.ConversationId, newDashConv.ConversationId)); + if (dashConv == null) return; + dashConv.Name = newDashConv.Name ?? dashConv.Name; + dashConv.Instruction = newDashConv.Instruction ?? dashConv.Instruction; + db.UpdateDashboardConversation(userId, dashConv); + await Task.CompletedTask; + return; + } + + public async Task GetDashboard(string userId) + { + var db = _services.GetRequiredService(); + var dash = db.GetDashboard(); + await Task.CompletedTask; + return dash; + } } 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 3159bb08..992e04f3 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 @@ -11,8 +11,8 @@ "profiles": [ "tool" ], "routingRules": [ { - "type": "planner", - "field": "HFPlanner" + "type": "reasoner", + "field": "HFReasoner" } ] } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.hf.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.hf.liquid similarity index 100% rename from src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.hf.liquid rename to src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.hf.liquid diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.naive.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.naive.liquid similarity index 100% rename from src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.naive.liquid rename to src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.naive.liquid diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.one-step-forward.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.one-step-forward.liquid new file mode 100644 index 00000000..dd6fe616 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.one-step-forward.liquid @@ -0,0 +1,2 @@ +Analyze the user's problem. Which prerequisite task needs to be completed? Output the next step of routing instructions. +Check the job responsibilities of the routable Agent and do not transfer to an Agent that exceeds the scope of responsibility. \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.sequential.get_remaining_task.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.sequential.get_remaining_task.liquid similarity index 100% rename from src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.sequential.get_remaining_task.liquid rename to src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.sequential.get_remaining_task.liquid diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.sequential.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.sequential.liquid similarity index 100% rename from src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.sequential.liquid rename to src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/reasoner.sequential.liquid diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 2da3251b..389bf96f 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -511,6 +511,28 @@ public class ConversationController : ControllerBase } #endregion + #region miscellaneous + [HttpPut("/agent/{agentId}/conversation/{conversationId}/dashboard")] + public async Task PinConversationToDashboard([FromRoute] string agentId, [FromRoute] string conversationId) + { + var userService = _services.GetRequiredService(); + + var user = await userService.GetUser(_user.Id); + var pinned = await userService.AddDashboardConversation(user.Id, conversationId); + return pinned; + } + + [HttpDelete("/agent/{agentId}/conversation/{conversationId}/dashboard")] + public async Task UnpinConversationFromDashboard([FromRoute] string agentId, [FromRoute] string conversationId) + { + var userService = _services.GetRequiredService(); + + var user = await userService.GetUser(_user.Id); + var unpinned = await userService.RemoveDashboardConversation(user.Id, conversationId); + return unpinned; + } + #endregion + #region Private methods private void SetStates(IConversationService conv, NewMessageModel input) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/DashboardController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/DashboardController.cs new file mode 100644 index 00000000..f75467fe --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/DashboardController.cs @@ -0,0 +1,73 @@ +using BotSharp.Abstraction.Options; +using BotSharp.Abstraction.Users.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public class DashboardController : ControllerBase +{ + private readonly IServiceProvider _services; + private readonly IUserIdentity _user; + + public DashboardController(IServiceProvider services, + IUserIdentity user, + BotSharpOptions options) + { + _services = services; + _user = user; + + } + #region User Components + [HttpGet("/dashboard/components")] + public async Task GetComponents(string userId) + { + var userService = _services.GetRequiredService(); + var dashboardProfile = await userService.GetDashboard(userId); + if (dashboardProfile == null) return new UserDashboardModel(); + var result = new UserDashboardModel + { + ConversationList = dashboardProfile.ConversationList.Select( + x => new UserDashboardConversationModel + { + Name = x.Name, + ConversationId = x.ConversationId, + Instruction = x.Instruction + } + ).ToList() + }; + return result; + } + + [HttpPost("/dashboard/component/conversation")] + public async Task UpdateDashboardConversationInstruction(string userId, UserDashboardConversationModel dashConv) + { + if (string.IsNullOrEmpty(dashConv.Name) && string.IsNullOrEmpty(dashConv.Instruction)) + { + return; + } + var newDashConv = new DashboardConversation + { + Id = Guid.Empty.ToString(), + ConversationId = dashConv.ConversationId + }; + if (!string.IsNullOrEmpty(dashConv.Name)) + { + newDashConv.Name = dashConv.Name; + } + if (!string.IsNullOrEmpty(dashConv.Instruction)) + { + newDashConv.Instruction = dashConv.Instruction; + } + + var userService = _services.GetRequiredService(); + await userService.UpdateDashboardConversation(userId, newDashConv); + return; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs index 05f8cb89..90b4ff67 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs @@ -12,6 +12,7 @@ public class ConversationViewModel [JsonPropertyName("agent_name")] public string AgentName { get; set; } + [JsonPropertyName("title")] public string Title { get; set; } = string.Empty; public UserViewModel User { get; set; } = new UserViewModel(); diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserDashboardConversationModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserDashboardConversationModel.cs new file mode 100644 index 00000000..3a5f3491 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserDashboardConversationModel.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace BotSharp.OpenAPI.ViewModels.Users; +public class UserDashboardModel +{ + + [JsonPropertyName("conversation_list")] + public IList ConversationList { get; set; } = []; +} + +public class UserDashboardConversationModel +{ + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("conversation_id")] + public string? ConversationId { get; set; } + + [JsonPropertyName("instruction")] + public string? Instruction { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.CodeDriver/data/agents/c0ded7d9-3f9d-4ef6-b7ce-56a892dcef62/agent.json b/src/Plugins/BotSharp.Plugin.CodeDriver/data/agents/c0ded7d9-3f9d-4ef6-b7ce-56a892dcef62/agent.json index 4ed8765c..038c969e 100644 --- a/src/Plugins/BotSharp.Plugin.CodeDriver/data/agents/c0ded7d9-3f9d-4ef6-b7ce-56a892dcef62/agent.json +++ b/src/Plugins/BotSharp.Plugin.CodeDriver/data/agents/c0ded7d9-3f9d-4ef6-b7ce-56a892dcef62/agent.json @@ -8,7 +8,7 @@ "updatedDateTime": "2024-11-23T00:00:00Z", "disabled": false, "isPublic": true, - "profiles": [ "database" ], + "profiles": [ "coding" ], "llmConfig": { "provider": "openai", "model": "gpt-4o", diff --git a/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/BotSharp.Plugin.MicrosoftExtensionsAI.csproj b/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/BotSharp.Plugin.MicrosoftExtensionsAI.csproj index 9262828f..9ce69188 100644 --- a/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/BotSharp.Plugin.MicrosoftExtensionsAI.csproj +++ b/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/BotSharp.Plugin.MicrosoftExtensionsAI.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs index 4054d09d..aa131d37 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs @@ -26,6 +26,8 @@ public class UserDocument : MongoBase public DateTime CreatedTime { get; set; } public DateTime UpdatedTime { get; set; } + public Dashboard? Dashboard { get; set; } + public User ToUser() { return new User diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index e999e034..d5431b36 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -317,4 +317,51 @@ public partial class MongoRepository return true; } + + public void AddDashboardConversation(string userId, string conversationId) + { + var user = _dc.Users.AsQueryable() + .FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId)); + if (user == null) return; + var curDash = user.Dashboard ?? new Dashboard(); + curDash.ConversationList.Add(new DashboardConversation + { + Id = Guid.NewGuid().ToString(), + ConversationId = conversationId + }); + + var filter = Builders.Filter.Eq(x => x.Id, userId); + var update = Builders.Update.Set(x => x.Dashboard, curDash) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + } + + public void RemoveDashboardConversation(string userId, string conversationId) + { + var user = _dc.Users.AsQueryable() + .FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId)); + if (user == null || user.Dashboard == null || user.Dashboard.ConversationList.IsNullOrEmpty()) return; + var curDash = user.Dashboard; + var unpinConv = user.Dashboard.ConversationList.FirstOrDefault( + x => string.Equals(x.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase)); + if (unpinConv == null) return; + curDash.ConversationList.Remove(unpinConv); + + var filter = Builders.Filter.Eq(x => x.Id, userId); + var update = Builders.Update.Set(x => x.Dashboard, curDash) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + } + + public void UpdateDashboardConversation(string userId, DashboardConversation dashConv) + { + var user = _dc.Users.AsQueryable() + .FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId)); + if (user == null || user.Dashboard == null || user.Dashboard.ConversationList.IsNullOrEmpty()) return; + var curIdx = user.Dashboard.ConversationList.ToList().FindIndex( + x => string.Equals(x.ConversationId, dashConv.ConversationId, StringComparison.OrdinalIgnoreCase)); + if (curIdx < 0) return; + + var filter = Builders.Filter.Eq(x => x.Id, userId); + var update = Builders.Update.Set(x => x.Dashboard.ConversationList[curIdx], dashConv) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + } } diff --git a/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs b/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs index aba02745..17d84c1c 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/PlannerPlugin.cs @@ -1,4 +1,4 @@ -using BotSharp.Abstraction.Routing.Planning; +using BotSharp.Abstraction.Planning; using BotSharp.Plugin.Planner.TwoStaging; namespace BotSharp.Plugin.Planner; @@ -17,7 +17,7 @@ public class PlannerPlugin : IBotSharpPlugin public void RegisterDI(IServiceCollection services, IConfiguration config) { - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); } diff --git a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs index 565115a5..47b9c03a 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs @@ -1,10 +1,10 @@ using BotSharp.Abstraction.Infrastructures.Enums; -using BotSharp.Abstraction.Routing.Planning; -using BotSharp.Core.Routing.Planning; +using BotSharp.Abstraction.Planning; +using BotSharp.Core.Routing.Reasoning; namespace BotSharp.Plugin.Planner.TwoStaging; -public partial class TwoStageTaskPlanner : IRoutingPlaner +public partial class TwoStageTaskPlanner : ITaskPlanner { private readonly IServiceProvider _services; private readonly ILogger _logger; @@ -40,7 +40,7 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner inst = response.Content.JsonContent(); // Fix LLM malformed response - PlannerHelper.FixMalformedResponse(_services, inst); + ReasonerHelper.FixMalformedResponse(_services, inst); return inst; } diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid index c53abdab..d8f741a7 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.summarize.liquid @@ -1,4 +1,5 @@ -You are a planning summarizer. You will generate the final output in JSON format with short explanation based on the task description, knowledge and related table structure and relationship. +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. Requirements: {{ summary_requirements }} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs index f47e022d..b712d347 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs @@ -62,7 +62,7 @@ public class SqlValidateFn : IFunctionCallback { Provider = agent?.LlmConfig?.Provider ?? "openai", Model = agent?.LlmConfig?.Model ?? "gpt-4o", - Message = "Correct SQL Statement", + Message = "Correct SQL Statement and keep the comments/explanations", Data = new Dictionary { { "original_sql", message.Content }, 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 1d5aca1f..1ec4bfce 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": "Execute the sql query in database from the latest dialog.", + "description": "Transfer to this Agent is allowed only when executable SQL statements are provided in the context.", "iconUrl": "https://cdn-icons-png.flaticon.com/512/3161/3161158.png", "type": "task", "createdDateTime": "2023-11-15T13:49:00Z", @@ -18,7 +18,7 @@ "field": "sql_statement", "required": true, "field_type": "string", - "description": "SQL statement" + "description": "SQL statement provided in the context" } ] } \ No newline at end of file