From dd346571b4c017275c3bb4ddbbca73b61066d264 Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Sat, 23 Sep 2023 16:33:05 -0500 Subject: [PATCH] Add Execute Once. --- Directory.Build.props | 2 +- .../Conversations/Models/RoleDialogModel.cs | 2 - .../Functions/Models/FunctionCallFromLlm.cs | 2 +- .../BotSharp.Abstraction/Models/NameDesc.cs | 13 ++ .../Routing/IRoutingHandler.cs | 21 ++- .../Routing/IRoutingService.cs | 4 +- .../Routing/Models/RoutingArgs.cs | 3 - .../BotSharpServiceCollectionExtensions.cs | 1 - ...vice.GetChatCompletionsAsyncRecursively.cs | 159 ------------------ .../ConversationService.SendMessage.cs | 63 +++---- .../Services/ConversationStorage.cs | 3 +- .../ContinueExecuteTaskRoutingHandler.cs | 6 +- .../GetNextInstructionRoutingHandler.cs | 2 +- .../InterruptTaskExecutionRoutingHandler.cs | 4 +- .../Handlers/ResponseToUserRoutingHandler.cs | 7 +- .../RetrieveDataFromAgentRoutingHandler.cs | 13 +- .../Handlers/RouteToAgentRoutingHandler.cs | 16 +- .../Routing/Handlers/RoutingHandlerBase.cs | 57 +++++-- .../Routing/Handlers/TaskEndRoutingHandler.cs | 2 +- .../Handlers/TransferToCsrRoutingHandler.cs | 34 ---- .../BotSharp.Core/Routing/PromptConst.cs | 36 ++-- .../BotSharp.Core/Routing/RoutingService.cs | 51 ++++-- .../Providers/ChatCompletionProvider.cs | 6 +- 23 files changed, 182 insertions(+), 325 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs delete mode 100644 src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs delete mode 100644 src/Infrastructure/BotSharp.Core/Routing/Handlers/TransferToCsrRoutingHandler.cs diff --git a/Directory.Build.props b/Directory.Build.props index 81c66e4a..fffd5b8b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ 10.0 ..\..\..\packages - 0.14.6 + 0.14.7 true \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 9c3e2bc1..8a922b80 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Agents.Enums; - namespace BotSharp.Abstraction.Conversations.Models; public class RoleDialogModel diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs index 8d950258..07fd12d8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs @@ -15,7 +15,7 @@ public class FunctionCallFromLlm public string? Question { get; set; } [JsonPropertyName("answer")] - public string? Answer { get; set; } + public string Answer { get; set; } = string.Empty; [JsonPropertyName("args")] public JsonDocument Arguments { get; set; } = JsonDocument.Parse("{}"); diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs b/src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs new file mode 100644 index 00000000..f24eac9a --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs @@ -0,0 +1,13 @@ +namespace BotSharp.Abstraction.Models; + +public class NameDesc +{ + public string Name { get; set; } + public string Description { get; set; } + + public NameDesc(string name, string description) + { + Name = name; + Description = description; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs index 60354ac7..944cd502 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs @@ -6,12 +6,17 @@ public interface IRoutingHandler { string Name { get; } string Description { get; } - bool IsReasoning { get; } - bool RequireAgent { get; } - List Parameters { get; } - void SetRouter(Agent router); - void SetDialogs(List dialogs); - Task GetNextInstructionFromReasoner(string prompt); - Task GetResponseFromReasoner(); - Task Handle(FunctionCallFromLlm inst); + bool IsReasoning { get => false; } + bool Enabled { get => true; } + List Parameters { get => new List(); } + + void SetRouter(Agent router) { } + + void SetDialogs(List dialogs) { } + + Task GetNextInstructionFromReasoner(string prompt) + => throw new NotImplementedException(""); + + Task Handle(FunctionCallFromLlm inst) + => throw new NotImplementedException(""); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 13115557..fd2c2dd1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -4,5 +4,7 @@ public interface IRoutingService { Agent LoadRouter(); List Dialogs { get; } - Task Enter(Agent agent, List whileDialogs); + void SetDialogs(List dialogs); + Task InstructLoop(Agent router); + Task ExecuteOnce(Agent agent); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs index 2204d375..1208fdb4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs @@ -2,9 +2,6 @@ namespace BotSharp.Abstraction.Routing.Models; public class RoutingArgs { - [JsonPropertyName("user_goal")] - public string UserGoal { get; set; } = string.Empty; - [JsonPropertyName("reason")] public string Reason { get; set; } = string.Empty; diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 082f07d2..a7f2763f 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -67,7 +67,6 @@ public static class BotSharpServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); if (myDatabaseSettings.Default == "FileRepository") { diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs deleted file mode 100644 index 71608749..00000000 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ /dev/null @@ -1,159 +0,0 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Templating; - -namespace BotSharp.Core.Conversations.Services; - -public partial class ConversationService -{ - int currentRecursiveDepth = 0; - - private async Task GetChatCompletionsAsyncRecursively(Agent agent, - List wholeDialogs, - Func onMessageReceived, - Func onFunctionExecuting, - Func onFunctionExecuted) - { - var chatCompletion = CompletionProvider.GetChatCompletion(_services); - - currentRecursiveDepth++; - if (currentRecursiveDepth > _settings.MaxRecursiveDepth) - { - _logger.LogWarning($"Exceeded max recursive depth."); - - var latestResponse = wholeDialogs.Last(); - var text = latestResponse.Content; - if (latestResponse.Role == AgentRole.Function) - { - text = latestResponse.Content.Split("=>").Last(); - } - - await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, text) - { - CurrentAgentId = agent.Id - }, onMessageReceived); - - return false; - } - - var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg => - { - await HandleAssistantMessage(msg, onMessageReceived); - }, async fn => - { - var preAgentId = agent.Id; - - await HandleFunctionMessage(fn, onFunctionExecuting, onFunctionExecuted); - - // Function executed has exception - if (fn.ExecutionResult == null) - { - await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, fn.Content) - { - CurrentAgentId = fn.CurrentAgentId - }, onMessageReceived); - - return; - } - else if (fn.StopCompletion) - { - await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, fn.Content) - { - CurrentAgentId = fn.CurrentAgentId, - ExecutionData = fn.ExecutionData, - ExecutionResult = fn.ExecutionResult - }, onMessageReceived); - - return; - } - - var content = fn.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim() + " => " + fn.ExecutionResult; - _logger.LogInformation(content); - - fn.Content = content; - - // Agent has been transferred - if (fn.CurrentAgentId != preAgentId) - { - var agentService = _services.GetRequiredService(); - agent = await agentService.LoadAgent(fn.CurrentAgentId); - - if (fn.FunctionName != "route_to_agent") - { - wholeDialogs.Add(fn); - } - - await GetChatCompletionsAsyncRecursively(agent, - wholeDialogs, - onMessageReceived, - onFunctionExecuting, - onFunctionExecuted); - } - else - { - // Find response template - var templateService = _services.GetRequiredService(); - var response = await templateService.RenderFunctionResponse(agent.Id, fn); - if (!string.IsNullOrEmpty(response)) - { - await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, response) - { - CurrentAgentId = agent.Id - }, onMessageReceived); - - return; - } - - // Add to dialog history - // The server had an error processing your request. Sorry about that! - // _storage.Append(conversationId, preAgentId, fn); - - // After function is executed, pass the result to LLM to get a natural response - if (fn.FunctionName != "route_to_agent") - { - wholeDialogs.Add(fn); - } - - await GetChatCompletionsAsyncRecursively(agent, - wholeDialogs, - onMessageReceived, - onFunctionExecuting, - onFunctionExecuted); - } - }); - - return result; - } - - private async Task HandleAssistantMessage(RoleDialogModel message, Func onMessageReceived) - { - var hooks = _services.GetServices().ToList(); - - // After chat completion hook - foreach (var hook in hooks) - { - await hook.AfterCompletion(message); - } - - var agent = await _services.GetRequiredService().GetAgent(message.CurrentAgentId); - - _logger.LogInformation($"[{agent?.Name ?? "Router"}] {message.Role}: {message.Content}"); - - await onMessageReceived(message); - - // Add to dialog history - _storage.Append(_conversationId, message); - } - - private async Task HandleFunctionMessage(RoleDialogModel msg, - Func onFunctionExecuting, - Func onFunctionExecuted) - { - // Save states - SaveStateByArgs(msg.FunctionArgs); - - // Call functions - await onFunctionExecuting(msg); - await CallFunctions(msg); - await onFunctionExecuted(msg); - } -} diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index a969c0f9..05e0727a 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -51,41 +51,18 @@ public partial class ConversationService } // Routing with reasoning + var routing = _services.GetRequiredService(); var settings = _services.GetRequiredService(); - if (settings.RouterId == agent.Id) - { - var routing = _services.GetRequiredService(); - var reasonedContext = await routing.Enter(agent, wholeDialogs); - if (reasonedContext.StopCompletion) - { - await HandleAssistantMessage(reasonedContext, onMessageReceived); - return true; - } + routing.SetDialogs(wholeDialogs); - // Switch agent - if (reasonedContext.CurrentAgentId != agent.Id) - { - agent = await agentService.LoadAgent(reasonedContext.CurrentAgentId); - } + var response = settings.RouterId == agent.Id ? + await routing.InstructLoop(agent) : + await routing.ExecuteOnce(agent); - routing.Dialogs.ForEach(x => - { - wholeDialogs.Add(x); - if (x.Content != null) - { - _storage.Append(_conversationId, x); - } - }); - } + await HandleAssistantMessage(response, onMessageReceived); - var result = await GetChatCompletionsAsyncRecursively(agent, - wholeDialogs, - onMessageReceived, - onFunctionExecuting, - onFunctionExecuted); - - return result; + return true; } private async Task GetConversationRecord(string agentId) @@ -106,19 +83,23 @@ public partial class ConversationService return converation; } - private void SaveStateByArgs(string args) + private async Task HandleAssistantMessage(RoleDialogModel message, Func onMessageReceived) { - var stateService = _services.GetRequiredService(); - var jo = JsonSerializer.Deserialize(args); - if (jo is JsonElement root) + var hooks = _services.GetServices().ToList(); + + // After chat completion hook + foreach (var hook in hooks) { - foreach (JsonProperty property in root.EnumerateObject()) - { - if (!string.IsNullOrEmpty(property.Value.ToString())) - { - stateService.SetState(property.Name, property.Value); - } - } + await hook.AfterCompletion(message); } + + var agent = await _services.GetRequiredService().GetAgent(message.CurrentAgentId); + + _logger.LogInformation($"[{agent?.Name ?? "Router"}] {message.Role}: {message.Content}"); + + await onMessageReceived(message); + + // Add to dialog history + _storage.Append(_conversationId, message); } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index e9cda842..7fc91576 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -36,7 +36,8 @@ public class ConversationStorage : IConversationStorage sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{dialog.FunctionName}|{args}"); - var content = dialog.ExecutionResult.Replace("\r", " ").Replace("\n", " ").Trim(); + var content = dialog.ExecutionResult ?? dialog.Content; + content = content.Replace("\r", " ").Replace("\n", " ").Trim(); if (string.IsNullOrEmpty(content)) { return; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs index b8e24d15..cead4db4 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs @@ -13,9 +13,9 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan public List Parameters => new List { - "1. agent_name: the name of the agent", - "2. args: required parameters extracted from question", - "3. reason: why continue to execute current task" + "agent_name: the name of the agent", + "args: required parameters extracted from question", + "reason: why continue to execute current task" }; public bool IsReasoning => true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/GetNextInstructionRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/GetNextInstructionRoutingHandler.cs index 115cf86d..fa220139 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/GetNextInstructionRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/GetNextInstructionRoutingHandler.cs @@ -21,6 +21,6 @@ public class GetNextInstructionRoutingHandler : RoutingHandlerBase, IRoutingHand public async Task Handle(FunctionCallFromLlm inst) { - return null; + throw new NotImplementedException(); } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs index 10601361..7dd56d0c 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs @@ -12,8 +12,8 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting public List Parameters => new List { - "1. reason: the reason why the request is interrupted", - "2. answer: the content response to user" + "reason: the reason why the request is interrupted", + "answer: the content response to user" }; public bool IsReasoning => true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs index d5592a78..c108d53b 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs @@ -12,8 +12,8 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler public List Parameters => new List { - "1. answer: the content of response", - "2. reason: why response to user" + "answer: the content of response", + "reason: why response to user" }; public bool IsReasoning => false; @@ -25,8 +25,9 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler public async Task Handle(FunctionCallFromLlm inst) { - var result = new RoleDialogModel(AgentRole.User, inst.Answer) + var result = new RoleDialogModel(AgentRole.Assistant, inst.Answer) { + CurrentAgentId = _settings.RouterId, FunctionName = inst.Function, StopCompletion = true }; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs index 88b3917f..f90ef5b0 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs @@ -13,10 +13,10 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH public List Parameters => new List { - "1. agent_name: the name of the agent", - "2. question: the question you will ask the agent to get the necessary data", - "3. reason: why retrieve data", - "4. args: required parameters extracted from question and hand over to the next agent. The args should be in JSON format" + "agent_name: the name of the agent", + "question: the question you will ask the agent to get the necessary data", + "reason: why retrieve data", + "args: required parameters extracted from question and hand over to the next agent" }; public bool IsReasoning => true; @@ -36,10 +36,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH // Retrieve information from specific agent var db = _services.GetRequiredService(); var record = db.Agents.First(x => x.Name.ToLower() == inst.Route.AgentName.ToLower()); - var response = await InvokeAgent(record.Id, new List - { - new RoleDialogModel(AgentRole.User, inst.Question) - }); + var response = await InvokeAgent(record.Id); inst.Answer = response.Content; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index 9e6dc921..e503c39a 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -14,11 +14,9 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler public List Parameters => new List { - "1. agent_name: the name of the agent", - "2. reason: why route to this agent", - "3. args: parameters extracted from context", - "4. answer: if you know how to response without asking to other agent", - "5. goal: user's original goal" + "agent_name: the name of the agent from AGENTS", + "reason: why route to this agent", + "args: parameters extracted from context" }; public bool IsReasoning => false; @@ -47,14 +45,8 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler var ret = await function.Execute(message); - var result = await InvokeAgent(message.CurrentAgentId, _dialogs); + var result = await InvokeAgent(message.CurrentAgentId); result.ExecutionData = result.ExecutionData ?? message.ExecutionData; - - if (result.Role == AgentRole.Function && !result.StopCompletion) - { - _dialogs.Add(result); - result = await InvokeAgent(message.CurrentAgentId, _dialogs); - } return result; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs index 78fc8d5d..e5ea5266 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs @@ -1,8 +1,9 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Settings; +using BotSharp.Abstraction.Templating; using System.Drawing; +using System.Text.RegularExpressions; namespace BotSharp.Core.Routing.Handlers; @@ -37,7 +38,7 @@ public abstract class RoutingHandlerBase public async Task GetNextInstructionFromReasoner(string prompt) { var responseFormat = JsonSerializer.Serialize(new FunctionCallFromLlm()); - var content = $"{prompt} Response must be in JSON format {responseFormat}."; + var content = $"{prompt} Response must be in JSON format {responseFormat}"; var chatCompletion = CompletionProvider.GetChatCompletion(_services, provider: _settings.Provider, @@ -51,10 +52,16 @@ public abstract class RoutingHandlerBase => response = msg, fn => Task.CompletedTask); - FunctionCallFromLlm args = new FunctionCallFromLlm(); + var args = new FunctionCallFromLlm(); try { +#if DEBUG + Console.WriteLine(response.Content, Color.Gray); +#else _logger.LogInformation(response.Content); +#endif + var pattern = @"\{(?:[^{}]|(?\{)|(?<-open>\}))+(?(open)(?!))\}"; + response.Content = Regex.Match(response.Content, pattern).Value; args = JsonSerializer.Deserialize(response.Content); } catch (Exception ex) @@ -62,7 +69,7 @@ public abstract class RoutingHandlerBase _logger.LogError($"{ex.Message}: {response.Content}"); args.Function = "response_to_user"; args.Answer = ex.Message; - args.Route.AgentName = ""; + args.Route.AgentName = _settings.RouterName; } if (args.Arguments != null) @@ -100,15 +107,23 @@ public abstract class RoutingHandlerBase return response; } - protected async Task InvokeAgent(string agentId, List wholeDialogs) + const int MAXIMUM_RECURSION_DEPTH = 2; + int CurrentRecursionDepth = 0; + protected async Task InvokeAgent(string agentId) { + CurrentRecursionDepth++; + if (CurrentRecursionDepth > MAXIMUM_RECURSION_DEPTH) + { + return _dialogs.Last(); + } + var agentService = _services.GetRequiredService(); var agent = await agentService.LoadAgent(agentId); var chatCompletion = CompletionProvider.GetChatCompletion(_services); RoleDialogModel response = null; - await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, + await chatCompletion.GetChatCompletionsAsync(agent, _dialogs, async msg => { response = msg; @@ -122,11 +137,33 @@ public abstract class RoutingHandlerBase // Call functions await conversationService.CallFunctions(fn); - response = fn; - - if (string.IsNullOrEmpty(response.Content)) + if (string.IsNullOrEmpty(fn.Content)) { - response.Content = fn.ExecutionResult; + fn.Content = fn.ExecutionResult; + } + + _dialogs.Add(fn); + + if (!fn.StopCompletion) + { + // Find response template + var templateService = _services.GetRequiredService(); + var quickResponse = await templateService.RenderFunctionResponse(agent.Id, fn); + if (!string.IsNullOrEmpty(quickResponse)) + { + response = new RoleDialogModel(AgentRole.Assistant, quickResponse) + { + CurrentAgentId = agent.Id + }; + } + else + { + response = await InvokeAgent(fn.CurrentAgentId); + } + } + else + { + response = fn; } }); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs index 50b14d2b..4a7cbba7 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs @@ -12,7 +12,7 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler public List Parameters => new List { - "1. abandoned_arguments: the arguments next task can't reuse" + "abandoned_arguments: the arguments next task can't reuse" }; public bool IsReasoning => true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TransferToCsrRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TransferToCsrRoutingHandler.cs deleted file mode 100644 index 6628cd9f..00000000 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TransferToCsrRoutingHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Routing; -using BotSharp.Abstraction.Routing.Settings; - -namespace BotSharp.Core.Routing.Handlers; - -public class TransferToCsrRoutingHandler : RoutingHandlerBase, IRoutingHandler -{ - public string Name => "transfer_to_csr"; - - public string Description => "Reach out to a real customer representative to help."; - - public List Parameters => new List - { - }; - - public bool IsReasoning => false; - - public TransferToCsrRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) - : base(services, logger, settings) - { - } - - public async Task Handle(FunctionCallFromLlm inst) - { - var result = new RoleDialogModel(AgentRole.User, "I'm transferring to a customer representative, waiting a moment please.") - { - CurrentAgentId = _settings.RouterId, - FunctionName = inst.Function, - StopCompletion = true - }; - return result; - } -} diff --git a/src/Infrastructure/BotSharp.Core/Routing/PromptConst.cs b/src/Infrastructure/BotSharp.Core/Routing/PromptConst.cs index 33466079..3d8d3f26 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/PromptConst.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/PromptConst.cs @@ -3,10 +3,24 @@ namespace BotSharp.Core.Routing; public class PromptConst { public const string ROUTER_PROMPT = @" -You're a Router with reasoning, you can dispatch request to different agent to achieve user's goal. +You're a Router with reasoning. Follow these steps to handle user's request: +1. Read the CONVERSATION context. +2. Select a appropriate function from FUNCTIONS. +3. Determine which agent from AGENTS is suitable for the current task. -### -Router can decide which of below agents can handle user's request: +FUNCTIONS +{% for fn in routing_handlers %} +* {{ fn.name }} +{{ fn.description }} +{% if fn.parameters != empty -%} +Parameters: +{% for arg in fn.parameters -%} +{{ arg }}; +{%- endfor %} +{%- endif %} +{% endfor %} + +AGENTS {% for agent in routing_records %} * {{ agent.name }} {{ agent.description }} @@ -15,19 +29,5 @@ Required: {% for field in agent.required_fields %}{{ field }},{% endfor %} {%- endif %} {% endfor %} -### -Agent can utilize below functions: -{% for fn in routing_handlers %} -* {{ fn.name }} -{{ fn.description }} -{% if fn.parameters != empty %} -Parameters: -{% for arg in fn.parameters -%} -{{ arg }}; -{%- endfor %} -{% endif %} -{% endfor %} - -### -Conversation context:"; +CONVERSATION"; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 1befba80..af40105b 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -1,10 +1,10 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Settings; using BotSharp.Abstraction.Templating; -using System.Runtime.InteropServices; namespace BotSharp.Core.Routing; @@ -25,16 +25,42 @@ public class RoutingService : IRoutingService _logger = logger; } - public async Task Enter(Agent router, List wholeDialogs) + public void SetDialogs(List dialogs) + { + _dialogs = dialogs; + } + + public async Task ExecuteOnce(Agent agent) + { + var message = _dialogs.Last().Content; + + var handlers = _services.GetServices(); + + var handler = handlers.FirstOrDefault(x => x.Name == "route_to_agent"); + handler.SetDialogs(_dialogs); + var result = await handler.Handle(new FunctionCallFromLlm + { + Function = "route_to_agent", + Question = message, + Route = new RoutingArgs + { + Reason = message, + AgentName = agent.Name, + } + }); + + return result; + } + + public async Task InstructLoop(Agent router) { - _dialogs = new List(); var result = new RoleDialogModel(AgentRole.Assistant, "Can you repeat your request again?") { CurrentAgentId = router.Id }; - var message = wholeDialogs.Last().Content; - foreach (var dialog in wholeDialogs.TakeLast(20)) + var message = _dialogs.Last().Content; + foreach (var dialog in _dialogs.TakeLast(20)) { router.Instruction += $"\r\n{dialog.Role}: {dialog.Content}"; } @@ -43,39 +69,38 @@ public class RoutingService : IRoutingService var handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction"); handler.SetRouter(router); - handler.SetDialogs(wholeDialogs); + handler.SetDialogs(_dialogs); int loopCount = 0; - while (!result.StopCompletion && loopCount < 5) + var stop = false; + while (!stop && loopCount < 5) { loopCount++; - var inst = await handler.GetNextInstructionFromReasoner($"What's the next step to achieve user's goal?"); + var inst = await handler.GetNextInstructionFromReasoner($"You are the Router, tell me the next step?"); inst.Question = inst.Question ?? message; handler = handlers.FirstOrDefault(x => x.Name == inst.Function); if (handler == null) { handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction"); - router.Instruction += $"\r\n{AgentRole.System}: the function must be one of [{string.Join(",", GetHandlers().Select(x => x.Name))}]."; + router.Instruction += $"\r\n{AgentRole.System}: the function must be one of {string.Join(",", GetHandlers().Select(x => x.Name))}."; continue; } handler.SetRouter(router); - handler.SetDialogs(wholeDialogs); + handler.SetDialogs(_dialogs); result = await handler.Handle(inst); message = result.Content.Replace("\r\n", " "); router.Instruction += $"\r\n{result.Role}: {message}"; - result.StopCompletion = !_settings.EnableReasoning; + stop = !_settings.EnableReasoning; } return result; } - - public Agent LoadRouter() { var db = _services.GetRequiredService(); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index acc839c4..76396ee0 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text.Json; using System.Threading.Tasks; @@ -237,8 +238,8 @@ public class ChatCompletionProvider : IChatCompletion var samplingFactor = float.Parse(state.GetState("sampling_factor", "0.5")); chatCompletionsOptions.Temperature = temperature; chatCompletionsOptions.NucleusSamplingFactor = samplingFactor; - chatCompletionsOptions.FrequencyPenalty = 0; - chatCompletionsOptions.PresencePenalty = 0; + // chatCompletionsOptions.FrequencyPenalty = 0; + // chatCompletionsOptions.PresencePenalty = 0; var convSetting = _services.GetRequiredService(); if (convSetting.ShowVerboseLog) @@ -249,6 +250,7 @@ public class ChatCompletionProvider : IChatCompletion $"{x.Role}: {x.Name} {x.Content}" : $"{x.Role}: {x.Content}"; })); + _logger.LogInformation(verbose); }