From 513ad5814eb1c8185dfd60717e2e08538c56dd06 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sat, 28 Oct 2023 15:59:26 -0500 Subject: [PATCH] NaivePlanner --- .../Agents/IAgentService.cs | 4 + .../Agents/Models/Agent.cs | 6 + .../Functions/Models/ParameterPropertyDef.cs | 5 +- .../Instructs/IInstructService.cs | 2 +- .../Planning/IExecutor.cs | 13 ++ .../BotSharp.Abstraction/Planning/IPlaner.cs | 12 ++ .../Routing/IRoutingHandler.cs | 5 +- .../Routing/IRoutingService.cs | 3 - .../Routing/Models/RoutingHandlerDef.cs | 4 +- .../Routing/Models/RoutingItem.cs | 4 +- .../Routing/Settings/RoutingSettings.cs | 4 +- .../Agents/Services/AgentService.GetAgents.cs | 6 +- .../Agents/Services/AgentService.LoadAgent.cs | 26 ++- .../BotSharpServiceCollectionExtensions.cs | 14 ++ .../ConversationService.SendMessage.cs | 2 +- .../Instructs/InstructService.cs | 16 +- .../Planning/FeedbackReasoningPlanner.cs | 75 +++++++ .../Planning/InstructExecutor.cs | 45 +++++ .../BotSharp.Core/Planning/NaivePlanner.cs | 76 +++++++ .../ContinueExecuteTaskRoutingHandler.cs | 16 +- .../Handlers/ConversationEndRoutingHandler.cs | 8 +- .../HumanInterventionNeededHandler.cs | 7 +- .../InterruptTaskExecutionRoutingHandler.cs | 12 +- .../Handlers/ResponseToUserRoutingHandler.cs | 8 +- .../RetrieveDataFromAgentRoutingHandler.cs | 19 +- .../Handlers/RouteToAgentRoutingHandler.cs | 16 +- .../Routing/Handlers/TaskEndRoutingHandler.cs | 10 +- .../BotSharp.Core/Routing/RouterInstance.cs | 15 +- .../RoutingService.FixMalformedResponse.cs | 56 ++++++ .../RoutingService.GetConversationContent.cs | 24 +++ .../RoutingService.GetNextInstruction.cs | 187 ------------------ .../BotSharp.Core/Routing/RoutingService.cs | 48 +++-- .../Templating/TemplateRender.cs | 1 + .../Controllers/InstructModeController.cs | 19 +- .../Providers/ChatCompletionProvider.cs | 12 +- .../Providers/ProviderHelper.cs | 2 +- .../Providers/ChatCompletionProvider.cs | 5 +- .../Providers/ChatCompletionProvider.cs | 5 +- .../Providers/ChatCompletionProvider.cs | 6 +- src/WebStarter/appsettings.json | 3 +- .../instruction.liquid | 21 +- .../agent.json | 10 +- 42 files changed, 512 insertions(+), 320 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs create mode 100644 src/Infrastructure/BotSharp.Core/Planning/FeedbackReasoningPlanner.cs create mode 100644 src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs create mode 100644 src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs create mode 100644 src/Infrastructure/BotSharp.Core/Routing/RoutingService.FixMalformedResponse.cs create mode 100644 src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs delete mode 100644 src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 4bf197ea..e6599836 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -16,6 +16,10 @@ public interface IAgentService /// Task LoadAgent(string id); + string RenderedInstruction(Agent agent); + + string RenderedTemplate(Agent agent, string templateName); + /// /// Get agent detail without trigger any hook. /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index 42710520..7115ccf1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -65,6 +65,12 @@ public class Agent public List RoutingRules { get; set; } = new List(); + /// + /// For rendering deferral + /// + [JsonIgnore] + public Dictionary TemplateDict { get; set; } + public override string ToString() => $"{Name} {Id}"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs index 04159b95..fd927bbc 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Models; - namespace BotSharp.Abstraction.Functions.Models; public class ParameterPropertyDef : NameDesc @@ -10,6 +8,9 @@ public class ParameterPropertyDef : NameDesc Type = type; } + [JsonPropertyName("required")] + public bool Required { get; set; } + /// /// string, number, object /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs index 2162204d..476a9f01 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs @@ -4,5 +4,5 @@ namespace BotSharp.Abstraction.Instructs; public interface IInstructService { - Task Execute(Agent agent, RoleDialogModel message); + Task Execute(string agentId, RoleDialogModel message, string? templateName = null); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs new file mode 100644 index 00000000..54c674db --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs @@ -0,0 +1,13 @@ +using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Routing; + +namespace BotSharp.Abstraction.Planning; + +public interface IExecutor +{ + Task Execute(IRoutingService routing, + Agent router, + FunctionCallFromLlm inst, + List dialogs, + RoleDialogModel message); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs new file mode 100644 index 00000000..e42e99c5 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs @@ -0,0 +1,12 @@ +using BotSharp.Abstraction.Functions.Models; + +namespace BotSharp.Abstraction.Planning; + +/// +/// Task breakdown and execution plan +/// +public interface IPlaner +{ + Task GetNextInstruction(Agent router, string conversation); + Task AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs index 6c8ae94e..f57080d7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Planning; namespace BotSharp.Abstraction.Routing; @@ -10,9 +11,9 @@ public interface IRoutingHandler { string Name { get; } string Description { get; } - bool IsReasoning => false; + List Planers => null; bool Enabled => true; - List Parameters => new List(); + List Parameters => new List(); void SetRouter(Agent router) { } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 47744957..127c708e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Functions.Models; - namespace BotSharp.Abstraction.Routing; public interface IRoutingService @@ -7,7 +5,6 @@ public interface IRoutingService List Dialogs { get; } void ResetRecursiveCounter(); void RefreshDialogs(); - Task GetNextInstruction(); Task InvokeAgent(string agentId, RoleDialogModel message); Task InstructLoop(RoleDialogModel message); Task ExecuteOnce(Agent agent, RoleDialogModel message); diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs index 0591fa0c..f0bfbf5a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs @@ -1,4 +1,4 @@ -using BotSharp.Abstraction.Models; +using BotSharp.Abstraction.Functions.Models; namespace BotSharp.Abstraction.Routing.Models; @@ -6,7 +6,7 @@ public class RoutingHandlerDef { public string Name { get; set; } public string Description { get; set; } - public List Parameters { get; set; } + public List Parameters { get; set; } public override string ToString() => $"{Name}: {Description} ({Parameters.Count} Parameters)"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs index 8ea5269d..ca632ee1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs @@ -13,6 +13,6 @@ public class RoutingItem [JsonPropertyName("description")] public string Description { get; set; } = string.Empty; - [JsonPropertyName("required_fields")] - public List RequiredFields { get; set; } = new List(); + [JsonPropertyName("fields")] + public List Fields { get; set; } = new List(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs index 39338b13..62a6ae4a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs @@ -7,9 +7,7 @@ public class RoutingSettings /// public string RouterId { get; set; } = string.Empty; - public bool EnableReasoning { get; set; } = false; - public bool UseTextCompletion { get; set; } = false; - + public string Planner { get; set; } = string.Empty; public string Provider { get; set; } = string.Empty; public string Model { get; set; } = string.Empty; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 5912b1e0..45d3b76d 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -4,14 +4,16 @@ namespace BotSharp.Core.Agents.Services; public partial class AgentService { - [MemoryCache(10 * 60, PerInstanceCache = true)] + [MemoryCache(10 * 60)] public async Task> GetAgents(bool? allowRouting = null) { var agents = _db.GetAgents(allowRouting: allowRouting); return await Task.FromResult(agents); } - [MemoryCache(10 * 60, PerInstanceCache = true)] +#if !DEBUG + [MemoryCache(10 * 60)] +#endif public async Task GetAgent(string id) { var profile = _db.GetAgent(id); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 301da3b7..1b7a6e99 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -27,8 +27,10 @@ public partial class AgentService throw new Exception($"Can't load agent by id: {id}"); } - var templateDict = new Dictionary(); - PopulateState(templateDict); + agent.TemplateDict = new Dictionary(); + + // Populate state into dictionary + PopulateState(agent.TemplateDict); // After agent is loaded foreach (var hook in hooks) @@ -42,7 +44,7 @@ public partial class AgentService if (!string.IsNullOrEmpty(agent.Instruction)) { - hook.OnInstructionLoaded(agent.Instruction, templateDict); + hook.OnInstructionLoaded(agent.Instruction, agent.TemplateDict); } if (agent.Functions != null) @@ -58,15 +60,25 @@ public partial class AgentService hook.OnAgentLoaded(agent); } - // render liquid template - var render = _services.GetRequiredService(); - agent.Instruction = render.Render(agent.Instruction, templateDict); - _logger.LogInformation($"Loaded agent {agent}."); return agent; } + public string RenderedTemplate(Agent agent, string templateName) + { + // render liquid template + var render = _services.GetRequiredService(); + var template = agent.Templates.First(x => x.Name == templateName).Content; + return render.Render(template, agent.TemplateDict); + } + + public string RenderedInstruction(Agent agent) + { + var render = _services.GetRequiredService(); + return render.Render(agent.Instruction, agent.TemplateDict); + } + private void PopulateState(Dictionary dict) { var conv = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 9b64b127..757faab8 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -17,6 +17,8 @@ using BotSharp.Abstraction.Evaluations; using BotSharp.Core.Evaluatings; using BotSharp.Core.Evaluations; using BotSharp.Abstraction.MLTasks.Settings; +using BotSharp.Abstraction.Planning; +using BotSharp.Core.Planning; namespace BotSharp.Core; @@ -68,6 +70,18 @@ public static class BotSharpServiceCollectionExtensions config.Bind("Router", routingSettings); services.AddSingleton((IServiceProvider x) => routingSettings); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(provider => + { + if (routingSettings.Planner == "NaivePlanner") + return provider.GetRequiredService(); + else if (routingSettings.Planner == "FeedbackReasoningPlanner") + return provider.GetRequiredService(); + throw new NotImplementedException(); + }); + + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 387a18e7..63972742 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -20,7 +20,7 @@ public partial class ConversationService var content = $"Received [{agent.Name}] {message.Role}: {message.Content}"; #if DEBUG - Console.WriteLine(content, Color.OrangeRed); + Console.WriteLine(content, Color.GreenYellow); #else _logger.LogInformation(content); #endif diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs index 8ee49eac..e135afed 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs @@ -1,7 +1,6 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; - namespace BotSharp.Core.Instructs; public partial class InstructService : IInstructService @@ -15,13 +14,13 @@ public partial class InstructService : IInstructService _logger = logger; } - public async Task Execute(Agent agent, RoleDialogModel message) + public async Task Execute(string agentId, RoleDialogModel message, string? templateName = null) { // Trigger before completion hooks var hooks = _services.GetServices(); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agent.Id) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) { continue; } @@ -39,8 +38,15 @@ public partial class InstructService : IInstructService } } + // Render prompt + var agentService = _services.GetRequiredService(); + Agent agent = await agentService.LoadAgent(agentId); + var prompt = string.IsNullOrEmpty(templateName) ? + agentService.RenderedInstruction(agent) : + agentService.RenderedTemplate(agent, templateName); + var completer = CompletionProvider.GetTextCompletion(_services); - var result = await completer.GetCompletion(agent.Instruction); + var result = await completer.GetCompletion(prompt); var response = new InstructResult { MessageId = message.MessageId, @@ -49,7 +55,7 @@ public partial class InstructService : IInstructService foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agent.Id) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) { continue; } diff --git a/src/Infrastructure/BotSharp.Core/Planning/FeedbackReasoningPlanner.cs b/src/Infrastructure/BotSharp.Core/Planning/FeedbackReasoningPlanner.cs new file mode 100644 index 00000000..156cb590 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Planning/FeedbackReasoningPlanner.cs @@ -0,0 +1,75 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Planning; +using BotSharp.Abstraction.Templating; + +namespace BotSharp.Core.Planning; + +public class FeedbackReasoningPlanner : IPlaner +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public FeedbackReasoningPlanner(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task GetNextInstruction(Agent router, string conversation) + { + var next = GetNextStepPrompt(router); + + RoleDialogModel response = default; + var inst = new FunctionCallFromLlm(); + + var content = $"{conversation}\r\n###\r\n{next}"; + + var completion = CompletionProvider.GetChatCompletion(_services, + model: "llm-gpt4"); + + int retryCount = 0; + while (retryCount < 3) + { + try + { + response = completion.GetChatCompletions(router, new List + { + new RoleDialogModel(AgentRole.User, content) + }); + + inst = response.Content.JsonContent(); + break; + } + catch (Exception ex) + { + _logger.LogError($"{ex.Message}: {response.Content}"); + inst.Function = "response_to_user"; + inst.Response = ex.Message; + inst.AgentName = "Router"; + } + finally + { + retryCount++; + } + } + + return inst; + } + + public async Task AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message) + { + inst.AgentName = null; + return true; + } + + private string GetNextStepPrompt(Agent router) + { + var template = router.Templates.First(x => x.Name == "next_step_prompt").Content; + + var render = _services.GetRequiredService(); + return render.Render(template, new Dictionary + { + }); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs new file mode 100644 index 00000000..c6588ef8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs @@ -0,0 +1,45 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Planning; +using BotSharp.Abstraction.Routing; + +namespace BotSharp.Core.Planning; + +public class InstructExecutor : IExecutor +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public InstructExecutor(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task Execute(IRoutingService routing, + Agent router, + FunctionCallFromLlm inst, + List dialogs, + RoleDialogModel message) + { + // Set user content as Planner's question + inst.Question = message.Content; + message.Instruction = inst; + + var handlers = _services.GetServices(); + + var handler = handlers.FirstOrDefault(x => x.Name == inst.Function); + handler.SetRouter(router); + handler.SetDialogs(dialogs); + + message.FunctionName = inst.Function; + message.Role = AgentRole.Function; + message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments); + + var handled = await handler.Handle(routing, inst, message); + + inst.Response = message.Content; + + return handled; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs b/src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs new file mode 100644 index 00000000..c9aaf783 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs @@ -0,0 +1,76 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Planning; +using BotSharp.Abstraction.Templating; + +namespace BotSharp.Core.Planning; + +public class NaivePlanner : IPlaner +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public NaivePlanner(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task GetNextInstruction(Agent router, string conversation) + { + var next = GetNextStepPrompt(router); + + RoleDialogModel response = default; + var inst = new FunctionCallFromLlm(); + + var agentService = _services.GetRequiredService(); + var instruction = agentService.RenderedInstruction(router); + var content = $"{instruction}\r\n{conversation}\r\n###\r\n{next}"; + + // text completion + content = content + "\r\nResponse: "; + + var completion = CompletionProvider.GetTextCompletion(_services); + + int retryCount = 0; + while (retryCount < 3) + { + try + { + var text = await completion.GetCompletion(content); + response = new RoleDialogModel(AgentRole.Assistant, text); + inst = response.Content.JsonContent(); + break; + } + catch (Exception ex) + { + _logger.LogError($"{ex.Message}: {response.Content}"); + inst.Function = "response_to_user"; + inst.Response = ex.Message; + inst.AgentName = "Router"; + } + finally + { + retryCount++; + } + } + + return inst; + } + + public async Task AgentExecuted(FunctionCallFromLlm inst, RoleDialogModel message) + { + inst.AgentName = null; + return true; + } + + private string GetNextStepPrompt(Agent router) + { + var template = router.Templates.First(x => x.Name == "next_step_prompt").Content; + + var render = _services.GetRequiredService(); + return render.Render(template, new Dictionary + { + }); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs index 1c809ef4..10724208 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs @@ -12,14 +12,20 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan public string Description => "Continue to execute user's request without further information retrival."; - public List Parameters => new List + public List Parameters => new List { - new NameDesc("agent", "the name of the agent"), - new NameDesc("args", "required parameters extracted from question"), - new NameDesc("reason", "why continue to execute current task") + new ParameterPropertyDef("agent", "the name of the agent"), + new ParameterPropertyDef("reason", "why continue to execute current task"), + new ParameterPropertyDef("args", "required parameters extracted from question") + { + Type = "object" + } }; - public bool IsReasoning => true; + public List Planers => new List + { + "FeedbackReasoningPlanner" + }; public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) : base(services, logger, settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs index 97eafa46..754016d0 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs @@ -11,12 +11,10 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler public string Description => "User completed his task and wants to end the conversation."; - public bool IsReasoning => false; - - public List Parameters => new List + public List Parameters => new List { - new NameDesc("reason", "why end conversation"), - new NameDesc("response", "response content to user") + new ParameterPropertyDef("reason", "why end conversation"), + new ParameterPropertyDef("response", "response content to user") }; public ConversationEndRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs index 671dc644..612f6722 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; @@ -11,10 +10,10 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle public string Description => "Reach out to human being, customer service or customer representative."; - public List Parameters => new List + public List Parameters => new List { - new NameDesc("reason", "why need customer service"), - new NameDesc("response", "response content to user") + new ParameterPropertyDef("reason", "why need customer service"), + new ParameterPropertyDef("response", "response content to user") }; public HumanInterventionNeededHandler(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 76d18be3..9932777d 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; @@ -11,13 +10,16 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting public string Description => "Can't continue user's request becauase the requirements are not met."; - public List Parameters => new List + public List Parameters => new List { - new NameDesc("reason", "the reason why the request is interrupted"), - new NameDesc("answer", "the content response to user") + new ParameterPropertyDef("reason", "the reason why the request is interrupted"), + new ParameterPropertyDef("answer", "the content response to user") }; - public bool IsReasoning => true; + public List Planers => new List + { + "FeedbackReasoningPlanner" + }; public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) : base(services, logger, settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs index cb480e56..b4e399a7 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs @@ -11,12 +11,10 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler public string Description => "Response according to the context without asking specific agent."; - public bool IsReasoning => false; - - public List Parameters => new List + public List Parameters => new List { - new NameDesc("reason", "why response to user"), - new NameDesc("response", "response content") + new ParameterPropertyDef("reason", "why response to user"), + new ParameterPropertyDef("response", "response content") }; public ResponseToUserRoutingHandler(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 3b30c19d..aa2cbce3 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; @@ -12,15 +11,21 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH public string Description => "Retrieve data from appropriate agent."; - public List Parameters => new List + public List Parameters => new List { - new NameDesc("agent", "the name of the agent"), - new NameDesc("question", "the question you will ask the agent to get the necessary data"), - new NameDesc("reason", "why retrieve data"), - new NameDesc("args", "required parameters extracted from question and hand over to the next agent") + new ParameterPropertyDef("agent", "the name of the agent"), + new ParameterPropertyDef("question", "the question you will ask the agent to get the necessary data"), + new ParameterPropertyDef("reason", "why retrieve data"), + new ParameterPropertyDef("args", "required parameters extracted from question and hand over to the next agent") + { + Type = "object" + } }; - public bool IsReasoning => true; + public List Planers => new List + { + "FeedbackReasoningPlanner" + }; public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) : base(services, logger, settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index daa4e734..cd614e91 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -1,6 +1,5 @@ using BotSharp.Abstraction.Functions; using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Settings; @@ -13,16 +12,17 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler public string Description => "Route request to appropriate agent."; - public List Parameters => new List + public List Parameters => new List { - new NameDesc("reason", "why route to agent"), - new NameDesc("next_action_agent", "agent for next action based on user latest response"), - new NameDesc("user_goal_agent", "agent who can achieve user original goal"), - new NameDesc("args", "useful parameters of next action agent") + new ParameterPropertyDef("reason", "why route to agent"), + new ParameterPropertyDef("next_action_agent", "agent for next action based on user latest response"), + new ParameterPropertyDef("user_goal_agent", "agent who can achieve user original goal"), + new ParameterPropertyDef("args", "useful parameters of next action agent, format: { }") + { + Type = "object" + } }; - public bool IsReasoning => false; - public RouteToAgentRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) : base(services, logger, settings) { diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs index edb2cfc9..656302e5 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; @@ -11,12 +10,15 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler public string Description => "Call this function when current task is completed."; - public List Parameters => new List + public List Parameters => new List { - new NameDesc("abandoned_arguments", "the arguments next task can't reuse") + new ParameterPropertyDef("abandoned_arguments", "the arguments next task can't reuse") }; - public bool IsReasoning => true; + public List Planers => new List + { + "FeedbackReasoningPlanner" + }; public TaskEndRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) : base(services, logger, settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs b/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs index 2ee8e023..1c6f4e54 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs @@ -1,6 +1,6 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; +using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Models; @@ -36,8 +36,10 @@ public class RouterInstance : IRouterInstance public List GetHandlers() { + var planer = _services.GetRequiredService(); + return _services.GetServices() - .Where(x => x.IsReasoning == _settings.EnableReasoning) + .Where(x => x.Planers == null || x.Planers.Contains(planer.GetType().Name)) .Where(x => !string.IsNullOrEmpty(x.Description)) .Select((x, i) => new RoutingHandlerDef { @@ -90,10 +92,11 @@ public class RouterInstance : IRouterInstance AgentId = x.Id, Description = x.Description, Name = x.Name, - RequiredFields = x.RoutingRules - .Where(x => x.Required) - .Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type)) - .ToList() + Fields = x.RoutingRules + .Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type) + { + Required = p.Required + }).ToList() }).ToArray(); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.FixMalformedResponse.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.FixMalformedResponse.cs new file mode 100644 index 00000000..1818924c --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.FixMalformedResponse.cs @@ -0,0 +1,56 @@ +using BotSharp.Abstraction.Functions.Models; + +namespace BotSharp.Core.Routing; + +public partial class RoutingService +{ + /// + /// Sometimes LLM hallucinates and fails to set function names correctly. + /// + /// + private void FixMalformedResponse(FunctionCallFromLlm args) + { + var agentService = _services.GetRequiredService(); + var agents = agentService.GetAgents(allowRouting: true).Result; + var malformed = false; + + // Sometimes it populate malformed Function in Agent name + if (!string.IsNullOrEmpty(args.Function) && + args.Function == args.AgentName) + { + args.Function = "route_to_agent"; + malformed = true; + } + + // Another case of malformed response + if (string.IsNullOrEmpty(args.AgentName) && + agents.Select(x => x.Name).Contains(args.Function)) + { + args.AgentName = args.Function; + args.Function = "route_to_agent"; + malformed = true; + } + + // It should be Route to agent, but it is used as Response to user. + if (!string.IsNullOrEmpty(args.AgentName) && + agents.Select(x => x.Name).Contains(args.AgentName) && + args.Function != "route_to_agent") + { + args.Function = "route_to_agent"; + malformed = true; + } + + // Function name shouldn't contain dot symbol + if (!string.IsNullOrEmpty(args.Function) && + args.Function.Contains('.')) + { + args.Function = args.Function.Split('.').Last(); + malformed = true; + } + + if (malformed) + { + _logger.LogWarning($"Captured LLM malformed response"); + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs new file mode 100644 index 00000000..bd883314 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs @@ -0,0 +1,24 @@ +namespace BotSharp.Core.Routing; + +public partial class RoutingService +{ + public async Task GetConversationContent(List dialogs, int maxDialogCount = 50) + { + var agentService = _services.GetRequiredService(); + var conversation = ""; + + foreach (var dialog in dialogs.TakeLast(maxDialogCount)) + { + var role = dialog.Role; + if (role != AgentRole.User) + { + var agent = await agentService.GetAgent(dialog.CurrentAgentId); + role = agent.Name; + } + + conversation += $"{role}: {dialog.Content}\r\n"; + } + + return conversation; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs deleted file mode 100644 index e217dd89..00000000 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs +++ /dev/null @@ -1,187 +0,0 @@ -using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Templating; -using System.Drawing; -using System.Text.RegularExpressions; -namespace BotSharp.Core.Routing; - -public partial class RoutingService -{ - public async Task GetNextInstruction() - { - var content = GetNextStepPrompt(); - - RoleDialogModel response = default; - var args = new FunctionCallFromLlm(); - - if (_settings.UseTextCompletion) - { - var completion = CompletionProvider.GetTextCompletion(_services, - provider: _settings.Provider, - model: _settings.Model); - - content = _routerInstance.Router.Instruction + "\r\n\r\n" + content + "\r\nResponse: "; - - int retryCount = 0; - - while (retryCount < 3) - { - try - { - var text = await completion.GetCompletion(content); - response = new RoleDialogModel(AgentRole.Assistant, text); - - var pattern = @"\{(?:[^{}]|(?\{)|(?<-open>\}))+(?(open)(?!))\}"; - response.Content = Regex.Match(response.Content, pattern).Value; - args = JsonSerializer.Deserialize(response.Content); - break; - } - catch (Exception ex) - { - _logger.LogError($"{ex.Message}: {response.Content}"); - args.Function = "response_to_user"; - args.Response = ex.Message; - args.AgentName = "Router"; - content += "\r\nPlease response in JSON format."; - } - finally - { - retryCount++; - } - } - } - else - { - var completion = CompletionProvider.GetChatCompletion(_services, - provider: _settings.Provider, - model: _settings.Model); - - int retryCount = 0; - var agentService = _services.GetRequiredService(); - var dialogs = Dialogs; - - while (retryCount < 3) - { - try - { - var conversation = ""; - - foreach (var dialog in dialogs.TakeLast(50)) - { - var role = dialog.Role; - if (role != AgentRole.User) - { - var agent = await agentService.GetAgent(dialog.CurrentAgentId); - role = agent.Name; - } - - conversation += $"{role}: {dialog.Content}\r\n"; - } - content = $"{conversation}\r\n###\r\n{content}"; - - response = completion.GetChatCompletions(_routerInstance.Router, new List - { - new RoleDialogModel(AgentRole.User, content) - }); - - args = response.Content.JsonContent(); - break; - } - catch (Exception ex) - { - _logger.LogError($"{ex.Message}: {response.Content}"); - args.Function = "response_to_user"; - args.Response = ex.Message; - args.AgentName = "Router"; - content += "\r\nPlease response in JSON format."; - } - finally - { - retryCount++; - } - } - } - -#if DEBUG - Console.WriteLine(response.Content, Color.Green); -#else - _logger.LogInformation(response.Content); -#endif - - // Fix LLM malformed response - FixMalformedResponse(args); - - SaveStateByArgs(args.Arguments); - -#if DEBUG - Console.WriteLine($"*** Next Instruction *** {args}", Color.Green); -#else - _logger.LogInformation($"*** Next Instruction *** {args}"); -#endif - - return args; - } - - private string GetNextStepPrompt() - { - var template = _routerInstance.Router.Templates.First(x => x.Name == "next_step_prompt").Content; - - // If enabled reasoning - // JsonSerializer.Serialize(new FunctionCallFromLlm()); - - var render = _services.GetRequiredService(); - return render.Render(template, new Dictionary - { - { "enabled_reasoning", _settings.EnableReasoning } - }); - } - - /// - /// Sometimes LLM hallucinates and fails to set function names correctly. - /// - /// - private void FixMalformedResponse(FunctionCallFromLlm args) - { - var agentService = _services.GetRequiredService(); - var agents = agentService.GetAgents(allowRouting: true).Result; - var malformed = false; - - // Sometimes it populate malformed Function in Agent name - if (!string.IsNullOrEmpty(args.Function) && - args.Function == args.AgentName) - { - args.Function = "route_to_agent"; - malformed = true; - } - - // Another case of malformed response - if (string.IsNullOrEmpty(args.AgentName) && - agents.Select(x => x.Name).Contains(args.Function)) - { - args.AgentName = args.Function; - args.Function = "route_to_agent"; - malformed = true; - } - - // It should be Route to agent, but it is used as Response to user. - if (!string.IsNullOrEmpty(args.AgentName) && - agents.Select(x => x.Name).Contains(args.AgentName) && - args.Function != "route_to_agent") - { - args.Function = "route_to_agent"; - malformed = true; - } - - // Function name shouldn't contain dot symbol - if (!string.IsNullOrEmpty(args.Function) && - args.Function.Contains('.')) - { - args.Function = args.Function.Split('.').Last(); - malformed = true; - } - - if (malformed) - { - _logger.LogWarning($"Captured LLM malformed response"); - } - } -} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index b7073f04..25d4732b 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -1,7 +1,9 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; +using System.Drawing; namespace BotSharp.Core.Routing; @@ -69,7 +71,8 @@ public partial class RoutingService : IRoutingService _routerInstance.Load(); var router = _routerInstance.Router; - var handlers = _services.GetServices(); + var planner = _services.GetRequiredService(); + var executor = _services.GetRequiredService(); int loopCount = 0; var stop = false; @@ -77,28 +80,33 @@ public partial class RoutingService : IRoutingService { loopCount++; - var inst = await GetNextInstruction(); - message.Instruction = inst; - inst.Question = message.Content; + var conversation = await GetConversationContent(Dialogs); - var handler = handlers.FirstOrDefault(x => x.Name == inst.Function); - if (handler == null) + // Get instruction from Planner + var inst = await planner.GetNextInstruction(router, conversation); + + // Fix LLM malformed response + FixMalformedResponse(inst); + + // Save states + SaveStateByArgs(inst.Arguments); + +#if DEBUG + Console.WriteLine($"*** Next Instruction *** {inst}", Color.GreenYellow); +#else + _logger.LogInformation($"*** Next Instruction *** {inst}"); +#endif + + // Handle instruction by Executor + var executed = await executor.Execute(this, router, inst, Dialogs, message); + + await planner.AgentExecuted(inst, message); + + // There is no need for the agent to continue processing, indicating that the task has been completed. + if (inst.AgentName == null) { - handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction"); - continue; + break; } - handler.SetRouter(router); - handler.SetDialogs(Dialogs); - - message.FunctionName = inst.Function; - message.Role = AgentRole.Function; - message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments); - - await handler.Handle(this, inst, message); - - inst.Response = message.Content; - - stop = !_settings.EnableReasoning; } return true; diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs index 64a8608f..3aba223f 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -39,6 +39,7 @@ public class TemplateRender : ITemplateRender } else { + _logger.LogWarning(error); return template; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index c34bf42a..3af20b6c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -31,23 +31,10 @@ public class InstructModeController : ControllerBase, IApiAdapter .SetState("model", input.Model) .SetState("input_text", input.Text); - var agentService = _services.GetRequiredService(); - Agent agent = await agentService.LoadAgent(agentId); - - // switch to different instruction template - if (!string.IsNullOrEmpty(input.Template)) - { - var template = agent.Templates.First(x => x.Name == input.Template).Content; - var render = _services.GetRequiredService(); - var dict = new Dictionary(); - state.GetStates().Select(x => dict[x.Key] = x.Value).ToArray(); - var prompt = render.Render(template, dict); - agent.Instruction = prompt; - } - var instructor = _services.GetRequiredService(); - var result = await instructor.Execute(agent, - new RoleDialogModel(AgentRole.User, input.Text)); + var result = await instructor.Execute(agentId, + new RoleDialogModel(AgentRole.User, input.Text), + templateName: input.Template); result.States = state.GetStates(); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index bd2933f3..651c2906 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -1,5 +1,6 @@ using Azure; using Azure.AI.OpenAI; +using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Conversations; @@ -73,6 +74,12 @@ public class ChatCompletionProvider : IChatCompletion } } + var setting = _services.GetRequiredService(); + if (setting.ShowVerboseLog) + { + _logger.LogInformation(msg.Content); + } + // After chat completion hook Task.WaitAll(hooks.Select(hook => hook.AfterGenerated(msg, new TokenStatsModel @@ -193,11 +200,14 @@ public class ChatCompletionProvider : IChatCompletion protected ChatCompletionsOptions PrepareOptions(Agent agent, List conversations) { + var agentService = _services.GetRequiredService(); + var chatCompletionsOptions = new ChatCompletionsOptions(); if (!string.IsNullOrEmpty(agent.Instruction)) { - chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Instruction)); + var instruction = agentService.RenderedInstruction(agent); + chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, instruction)); } if (!string.IsNullOrEmpty(agent.Knowledges)) diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs index e3411fcc..caaa520f 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs @@ -11,7 +11,7 @@ public class ProviderHelper { public static OpenAIClient GetClient(string model, AzureOpenAiSettings settings) { - if (model == "gpt-4") + if (model == "gpt-4" || model == "llm-gpt4") { var client = new OpenAIClient(new Uri(settings.GPT4.Endpoint), new AzureKeyCredential(settings.GPT4.ApiKey)); return client; diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs index 0495799d..9733f39b 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/ChatCompletionProvider.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Conversations; using BotSharp.Plugin.GoogleAI.Settings; @@ -35,7 +36,9 @@ public class ChatCompletionProvider : IChatCompletion var messages = conversations.Select(c => new PalmChatMessage(c.Content, c.Role == AgentRole.User ? "user" : "AI")) .ToList(); - var response = client.ChatAsync(messages, agent.Instruction, null).Result; + var agentService = _services.GetRequiredService(); + var instruction = agentService.RenderedInstruction(agent); + var response = client.ChatAsync(messages, instruction, null).Result; var message = response.Candidates.First(); var msg = new RoleDialogModel(AgentRole.Assistant, message.Content) diff --git a/src/Plugins/BotSharp.Plugin.HuggingFace/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.HuggingFace/Providers/ChatCompletionProvider.cs index db6daf4f..8756c7b5 100644 --- a/src/Plugins/BotSharp.Plugin.HuggingFace/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.HuggingFace/Providers/ChatCompletionProvider.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Conversations.Settings; using BotSharp.Plugin.HuggingFace.Services; @@ -97,7 +98,9 @@ public class ChatCompletionProvider : IChatCompletion var content = string.Join("\r\n", conversations.Select(x => $"{AgentRole.System}: {x.Content}")).Trim(); content += $"\r\n{AgentRole.Assistant}: "; - var prompt = agent.Instruction + "\r\n" + content; + var agentService = _services.GetRequiredService(); + var instruction = agentService.RenderedInstruction(agent); + var prompt = instruction + "\r\n" + content; var convSetting = _services.GetRequiredService(); if (convSetting.ShowVerboseLog) diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs index adf452e0..9dc0d7e5 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Agents; + namespace BotSharp.Plugin.LLamaSharp.Providers; public class ChatCompletionProvider : IChatCompletion @@ -42,7 +44,9 @@ public class ChatCompletionProvider : IChatCompletion string totalResponse = ""; - var prompt = agent.Instruction + "\r\n" + content; + var agentService = _services.GetRequiredService(); + var instruction = agentService.RenderedInstruction(agent); + var prompt = instruction + "\r\n" + content; var convSetting = _services.GetRequiredService(); if (convSetting.ShowVerboseLog) diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 318c4298..ed51f673 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -15,8 +15,7 @@ "Router": { "RouterId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a", - "UseTextCompletion": false, - "EnableReasoning": false, + "Planner": "NaivePlanner", "Provider": "azure-openai", "Model": "gpt-3.5-turbo" }, diff --git a/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid index 67cd6d1a..7b4d552d 100644 --- a/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid +++ b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid @@ -9,23 +9,24 @@ You're {{router.name}} ({{router.description}}). Follow these steps to handle us {% for handler in routing_handlers %} # {{ handler.description}} {% if handler.parameters and handler.parameters != empty -%} -Response: { "function": "{{ handler.name }}", +Parameters: + - function: {{ handler.name }} {% for p in handler.parameters -%} - "{{ p.name }}": "{{ p.description }}"{{ ",\r\n " }} - {%- endfor %}} + - {{ p.name }}: {{ p.description }}{{ "\r\n " }} + {%- endfor %} {%- endif %} {% endfor %} [AGENTS] {% for agent in routing_agents %} -* Agent: {{ agent.name }} -{{ agent.description}} -{% if agent.required_fields and agent.required_fields != empty -%} -Required args: - {% for f in agent.required_fields -%} - - {{ f.name }} ({{ f.type }}): {{ f.description }}{{ "\r\n " }} +* {{ agent.description}} +Agent: {{ agent.name }} +{% if agent.fields and agent.fields != empty -%} +Arguments: + {% for f in agent.fields -%} + - {{ f.name }}: {{ f.description }} (type: {{ f.type }}, required: {{f.required}}){{ "\r\n " }} {%- endfor %} {%- endif %} {% endfor %} -[CONVERSATION] +[CONVERSATION] \ No newline at end of file diff --git a/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json b/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json index 14d27b26..6b2a2771 100644 --- a/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json +++ b/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json @@ -5,5 +5,13 @@ "updatedDateTime": "2023-08-18T14:39:32.2349686Z", "id": "b284db86-e9c2-4c25-a59e-4649797dd130", "allowRouting": true, - "isPublic": true + "isPublic": true, + "routingRules": [ + { + "field": "order_number", + "description": "order number", + "type": "string", + "redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd" + } + ] } \ No newline at end of file