From 3caa7a54992c0681cb511bb262ec0f3ff5154cca Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 30 Oct 2023 11:48:18 -0500 Subject: [PATCH] HFPlanner --- docs/architecture/hooks.md | 12 +++ .../Conversations/IConversationService.cs | 1 + .../Conversations/Models/RoleDialogModel.cs | 4 +- .../Conversations/Models/TokenStatsModel.cs | 1 + .../MLTasks/ITextCompletion.cs | 2 +- .../Planning/IExecutor.cs | 7 +- .../BotSharp.Abstraction/Planning/IPlaner.cs | 2 +- .../Routing/IRoutingHandler.cs | 5 +- .../Routing/IRoutingService.cs | 9 +- .../Routing/RoutingHandlerBase.cs | 6 -- .../Agents/Services/AgentService.GetAgents.cs | 2 + .../BotSharpServiceCollectionExtensions.cs | 6 +- .../ConversationService.SendMessage.cs | 27 +++--- .../Services/ConversationService.cs | 2 +- .../Evaluations/EvaluatingService.cs | 13 ++- .../Instructs/InstructService.cs | 2 +- .../{ReasoningPlanner.cs => HFPlanner.cs} | 33 ++++---- .../Planning/InstructExecutor.cs | 23 ++--- .../BotSharp.Core/Planning/NaivePlanner.cs | 18 ++-- .../ContinueExecuteTaskRoutingHandler.cs | 7 +- .../Handlers/ConversationEndRoutingHandler.cs | 14 +++- .../HumanInterventionNeededHandler.cs | 13 ++- .../InterruptTaskExecutionRoutingHandler.cs | 4 +- .../Handlers/ResponseToUserRoutingHandler.cs | 13 ++- .../RetrieveDataFromAgentRoutingHandler.cs | 21 ++++- .../Handlers/RouteToAgentRoutingHandler.cs | 6 +- .../Routing/Handlers/TaskEndRoutingHandler.cs | 9 +- .../Routing/RoutingService.InvokeAgent.cs | 36 ++++---- .../BotSharp.Core/Routing/RoutingService.cs | 71 +++++++--------- .../Controllers/ConversationController.cs | 11 ++- .../Controllers/InstructModeController.cs | 4 +- .../Conversations/MessageResponseModel.cs | 4 + .../Providers/ChatCompletionProvider.cs | 84 ++++++++++++------- .../Providers/TextCompletionProvider.cs | 28 +++++-- .../Providers/TextCompletionProvider.cs | 12 ++- .../Services/KnowledgeService.cs | 2 +- .../Providers/TextCompletionProvider.cs | 19 ++++- .../templates/task.place_pizza_order.liquid | 1 + .../Hooks/CommonAgentHook.cs | 21 +++++ 39 files changed, 340 insertions(+), 215 deletions(-) rename src/Infrastructure/BotSharp.Core/Planning/{ReasoningPlanner.cs => HFPlanner.cs} (76%) create mode 100644 tests/BotSharp.Plugin.PizzaBot/Hooks/CommonAgentHook.cs diff --git a/docs/architecture/hooks.md b/docs/architecture/hooks.md index 0fd951e1..6071a37d 100644 --- a/docs/architecture/hooks.md +++ b/docs/architecture/hooks.md @@ -47,4 +47,16 @@ More information about conversation hook please go to [Conversation Hook](../con ```csharp Task OnStateLoaded(ConversationState state); Task OnStateChanged(string name, string preValue, string currentValue); +``` + +### Content Generating Hook +`IContentGeneratingHook` + +Model content generating hook, it can be used for logging, metrics and tracing. +```csharp +// Before content generating. +Task BeforeGenerating(Agent agent, List conversations); + +// After content generated. +Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenStats); ``` \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index a6982088..4e8795ff 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -3,6 +3,7 @@ namespace BotSharp.Abstraction.Conversations; public interface IConversationService { IConversationStateService States { get; } + string ConversationId { get; } Task NewConversation(Conversation conversation); void SetConversationId(string conversationId, List states); Task GetConversation(string id); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 0aa0cd04..37a02428 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -1,10 +1,12 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; namespace BotSharp.Abstraction.Conversations.Models; public class RoleDialogModel : ITrackableMessage { + /// + /// If Role is Assistant, it is same as user's message id. + /// public string MessageId { get; set; } /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs index ac370931..cd54eb04 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/TokenStatsModel.cs @@ -3,6 +3,7 @@ namespace BotSharp.Abstraction.Conversations.Models; public class TokenStatsModel { public string Model { get; set; } + public string Prompt { get; set; } public int PromptCount { get; set; } public int CompletionCount { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs index 31fc95f7..63a15940 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs @@ -13,5 +13,5 @@ public interface ITextCompletion /// void SetModelName(string model); - Task GetCompletion(string text); + Task GetCompletion(string text, string agentId, string messageId); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs index 54c674db..187fcfcc 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs @@ -5,9 +5,8 @@ namespace BotSharp.Abstraction.Planning; public interface IExecutor { - Task Execute(IRoutingService routing, - Agent router, + Task Execute(IRoutingService routing, FunctionCallFromLlm inst, - List dialogs, - RoleDialogModel message); + RoleDialogModel message, + List dialogs); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs index d8de1167..c1f3a9d7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlaner.cs @@ -7,7 +7,7 @@ namespace BotSharp.Abstraction.Planning; /// public interface IPlaner { - Task GetNextInstruction(Agent router); + Task GetNextInstruction(Agent router, string messageId); Task AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message); 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 f57080d7..95cddf2e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Planning; namespace BotSharp.Abstraction.Routing; @@ -15,9 +14,7 @@ public interface IRoutingHandler bool Enabled => true; List Parameters => new List(); - void SetRouter(Agent router) { } - - void SetDialogs(List dialogs) { } + void SetDialogs(List dialogs); Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 127c708e..eee7c942 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -2,10 +2,9 @@ namespace BotSharp.Abstraction.Routing; public interface IRoutingService { - List Dialogs { get; } + Agent Router { get; } void ResetRecursiveCounter(); - void RefreshDialogs(); - Task InvokeAgent(string agentId, RoleDialogModel message); - Task InstructLoop(RoleDialogModel message); - Task ExecuteOnce(Agent agent, RoleDialogModel message); + Task InvokeAgent(string agentId, List dialogs); + Task InstructLoop(RoleDialogModel message); + Task ExecuteOnce(Agent agent, RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/RoutingHandlerBase.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/RoutingHandlerBase.cs index eceeca3a..cf394672 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/RoutingHandlerBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/RoutingHandlerBase.cs @@ -5,7 +5,6 @@ namespace BotSharp.Abstraction.Routing; public abstract class RoutingHandlerBase { - protected Agent _router; protected readonly IServiceProvider _services; protected readonly ILogger _logger; protected RoutingSettings _settings; @@ -20,11 +19,6 @@ public abstract class RoutingHandlerBase _settings = settings; } - public void SetRouter(Agent router) - { - _router = router; - } - public void SetDialogs(List dialogs) { _dialogs = dialogs; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 45d3b76d..6b749b3b 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -4,7 +4,9 @@ namespace BotSharp.Core.Agents.Services; public partial class AgentService { +#if !DEBUG [MemoryCache(10 * 60)] +#endif public async Task> GetAgents(bool? allowRouting = null) { var agents = _db.GetAgents(allowRouting: allowRouting); diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 4e6aeaad..e36e9a70 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -71,11 +71,11 @@ public static class BotSharpServiceCollectionExtensions services.AddSingleton((IServiceProvider x) => routingSettings); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(provider => { - if (routingSettings.Planner == nameof(ReasoningPlanner)) - return provider.GetRequiredService(); + if (routingSettings.Planner == nameof(HFPlanner)) + return provider.GetRequiredService(); else return provider.GetRequiredService(); }); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index fceccd48..19ddfee9 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -53,19 +53,18 @@ public partial class ConversationService var routing = _services.GetRequiredService(); var settings = _services.GetRequiredService(); - var ret = agentId == settings.RouterId ? + var response = agentId == settings.RouterId ? await routing.InstructLoop(message) : await routing.ExecuteOnce(agent, message); - await HandleAssistantMessage(message, onMessageReceived); + await HandleAssistantMessage(response, onMessageReceived); var statistics = _services.GetRequiredService(); statistics.PrintStatistics(); routing.ResetRecursiveCounter(); - routing.RefreshDialogs(); - return ret; + return true; } private async Task GetConversationRecord(string agentId) @@ -86,15 +85,15 @@ public partial class ConversationService return converation; } - private async Task HandleAssistantMessage(RoleDialogModel message, Func onMessageReceived) + private async Task HandleAssistantMessage(RoleDialogModel response, Func onMessageReceived) { var agentService = _services.GetRequiredService(); - var agent = await agentService.GetAgent(message.CurrentAgentId); + var agent = await agentService.GetAgent(response.CurrentAgentId); var agentName = agent.Name; - var text = message.Role == AgentRole.Function ? - $"Sending [{agentName}] {message.FunctionName}: {message.Content}" : - $"Sending [{agentName}] {message.Role}: {message.Content}"; + var text = response.Role == AgentRole.Function ? + $"Sending [{agentName}] {response.FunctionName}: {response.Content}" : + $"Sending [{agentName}] {response.Role}: {response.Content}"; #if DEBUG Console.WriteLine(text, Color.Yellow); #else @@ -103,21 +102,21 @@ public partial class ConversationService // Only read content from RichContent for UI rendering. When richContent is null, create a basic text message for richContent. var state = _services.GetRequiredService(); - message.RichContent = message.RichContent ?? new RichContent + response.RichContent = response.RichContent ?? new RichContent { Recipient = new Recipient { Id = state.GetConversationId() }, - Message = new TextMessage { Text = message.Content } + Message = new TextMessage { Text = response.Content } }; var hooks = _services.GetServices().ToList(); foreach (var hook in hooks) { - await hook.OnResponseGenerated(message); + await hook.OnResponseGenerated(response); } - await onMessageReceived(message); + await onMessageReceived(response); // Add to dialog history - _storage.Append(_conversationId, message); + _storage.Append(_conversationId, response); } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index db6fc90a..20bfb7cf 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Repositories; namespace BotSharp.Core.Conversations.Services; @@ -12,6 +11,7 @@ public partial class ConversationService : IConversationService private readonly IConversationStorage _storage; private readonly IConversationStateService _state; private string _conversationId; + public string ConversationId => _conversationId; public IConversationStateService States => _state; diff --git a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs index 119124e5..1cb826b7 100644 --- a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs +++ b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs @@ -43,14 +43,14 @@ public class EvaluatingService : IEvaluatingService }; var textCompletion = CompletionProvider.GetTextCompletion(_services); - RoleDialogModel response = default; + RoleDialogModel response = new RoleDialogModel(AgentRole.User, ""); var dialogs = new List(); int roundCount = 0; while (true) { // var text = string.Join("\r\n", dialogs.Select(x => $"{x.Role}: {x.Content}")); // text = instruction + $"\r\n###\r\n{text}\r\n{AgentRole.User}: "; - var question = await textCompletion.GetCompletion(prompt); + var question = await textCompletion.GetCompletion(prompt, request.AgentId, response.MessageId); dialogs.Add(new RoleDialogModel(AgentRole.User, question)); prompt += question.Trim(); @@ -61,9 +61,14 @@ public class EvaluatingService : IEvaluatingService roundCount++; + if (roundCount > 10) + { + Console.WriteLine($"Conversation ended due to execced max round count {roundCount}", Color.Red); + break; + } + if (response.FunctionName == "conversation_end" || - response.FunctionName == "human_intervention_needed" || - roundCount > 5) + response.FunctionName == "human_intervention_needed") { Console.WriteLine($"Conversation ended by function {response.FunctionName}", Color.Green); break; diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs index e135afed..b8f58cfe 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs @@ -46,7 +46,7 @@ public partial class InstructService : IInstructService agentService.RenderedTemplate(agent, templateName); var completer = CompletionProvider.GetTextCompletion(_services); - var result = await completer.GetCompletion(prompt); + var result = await completer.GetCompletion(prompt, agentId, message.MessageId); var response = new InstructResult { MessageId = message.MessageId, diff --git a/src/Infrastructure/BotSharp.Core/Planning/ReasoningPlanner.cs b/src/Infrastructure/BotSharp.Core/Planning/HFPlanner.cs similarity index 76% rename from src/Infrastructure/BotSharp.Core/Planning/ReasoningPlanner.cs rename to src/Infrastructure/BotSharp.Core/Planning/HFPlanner.cs index 2a4a5cac..c618ce4c 100644 --- a/src/Infrastructure/BotSharp.Core/Planning/ReasoningPlanner.cs +++ b/src/Infrastructure/BotSharp.Core/Planning/HFPlanner.cs @@ -3,30 +3,33 @@ using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing.Models; +using BotSharp.Abstraction.Routing.Settings; using BotSharp.Abstraction.Templating; namespace BotSharp.Core.Planning; -public class ReasoningPlanner : IPlaner +/// +/// Human feedback based planner +/// +public class HFPlanner : IPlaner { private readonly IServiceProvider _services; private readonly ILogger _logger; - public ReasoningPlanner(IServiceProvider services, ILogger logger) + public HFPlanner(IServiceProvider services, ILogger logger) { _services = services; _logger = logger; } - public async Task GetNextInstruction(Agent router) + public async Task GetNextInstruction(Agent router, string messageId) { var next = GetNextStepPrompt(router); RoleDialogModel response = default; var inst = new FunctionCallFromLlm(); - var completion = CompletionProvider.GetChatCompletion(_services, - model: "llm-gpt4"); + var completion = CompletionProvider.GetChatCompletion(_services); int retryCount = 0; while (retryCount < 3) @@ -36,6 +39,9 @@ public class ReasoningPlanner : IPlaner response = completion.GetChatCompletions(router, new List { new RoleDialogModel(AgentRole.User, next) + { + MessageId = messageId + } }); inst = response.Content.JsonContent(); @@ -59,14 +65,14 @@ public class ReasoningPlanner : IPlaner public async Task AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message) { - message.Content = inst.Question; - message.FunctionArgs = JsonSerializer.Serialize(inst.Arguments); - - var db = _services.GetRequiredService(); - var agent = db.GetAgents(inst.AgentName).FirstOrDefault(); + if (!string.IsNullOrEmpty(inst.AgentName)) + { + var db = _services.GetRequiredService(); + var agent = db.GetAgents(inst.AgentName).FirstOrDefault(); - var context = _services.GetRequiredService(); - context.Push(agent.Id); + var context = _services.GetRequiredService(); + context.Push(agent.Id); + } return true; } @@ -76,9 +82,6 @@ public class ReasoningPlanner : IPlaner var context = _services.GetRequiredService(); context.Pop(); - // push Router to continue - // Make decision according to last agent's response - return true; } diff --git a/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs index c6588ef8..0d02cd95 100644 --- a/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Routing; @@ -16,30 +15,24 @@ public class InstructExecutor : IExecutor _logger = logger; } - public async Task Execute(IRoutingService routing, - Agent router, + public async Task Execute(IRoutingService routing, FunctionCallFromLlm inst, - List dialogs, - RoleDialogModel message) + RoleDialogModel message, + List dialogs) { - // 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; + // For client display purpose + var response = dialogs.Last(); + response.MessageId = message.MessageId; + response.Instruction = inst; - return handled; + return response; } } diff --git a/src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs b/src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs index f1a8c74a..45737fc5 100644 --- a/src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs +++ b/src/Infrastructure/BotSharp.Core/Planning/NaivePlanner.cs @@ -3,6 +3,7 @@ using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Templating; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; namespace BotSharp.Core.Planning; @@ -17,11 +18,10 @@ public class NaivePlanner : IPlaner _logger = logger; } - public async Task GetNextInstruction(Agent router) + public async Task GetNextInstruction(Agent router, string messageId) { var next = GetNextStepPrompt(router); - RoleDialogModel response = default; var inst = new FunctionCallFromLlm(); var agentService = _services.GetRequiredService(); @@ -36,16 +36,20 @@ public class NaivePlanner : IPlaner int retryCount = 0; while (retryCount < 3) { + string text = string.Empty; try { - var text = await completion.GetCompletion(content); - response = new RoleDialogModel(AgentRole.Assistant, text); + text = await completion.GetCompletion(content, router.Id, messageId); + var response = new RoleDialogModel(AgentRole.Assistant, text) + { + MessageId = messageId + }; inst = response.Content.JsonContent(); break; } catch (Exception ex) { - _logger.LogError($"{ex.Message}: {response.Content}"); + _logger.LogError($"{ex.Message}: {text}"); inst.Function = "response_to_user"; inst.Response = ex.Message; inst.AgentName = "Router"; @@ -64,6 +68,10 @@ public class NaivePlanner : IPlaner public async Task AgentExecuting(FunctionCallFromLlm inst, RoleDialogModel message) { + // Set user content as Planner's question + message.FunctionName = inst.Function; + message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments); + return true; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs index 815f5bc5..681eaf07 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs @@ -7,7 +7,7 @@ using BotSharp.Core.Planning; namespace BotSharp.Core.Routing.Handlers; -public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHandler +public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase//, IRoutingHandler { public string Name => "continue_execute_task"; @@ -15,7 +15,8 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan public List Parameters => new List { - new ParameterPropertyDef("agent", "the name of the 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("reason", "why continue to execute current task"), new ParameterPropertyDef("args", "required parameters extracted from question") { @@ -25,7 +26,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan public List Planers => new List { - nameof(ReasoningPlanner) + nameof(HFPlanner) }; public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs index 754016d0..5a7b7c4b 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ConversationEndRoutingHandler.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; @@ -25,8 +24,15 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message) { - message.Content = inst.Response; - message.FunctionName = inst.Function; + var response = new RoleDialogModel(AgentRole.Assistant, inst.Response) + { + CurrentAgentId = message.CurrentAgentId, + MessageId = message.MessageId, + StopCompletion = true, + FunctionName = inst.Function + }; + + _dialogs.Add(response); var hooks = _services.GetServices() .OrderBy(x => x.Priority) @@ -34,7 +40,7 @@ public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler foreach (var hook in hooks) { - await hook.OnConversationEnding(message); + await hook.OnConversationEnding(response); } return true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs index 612f6722..b4a4b874 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/HumanInterventionNeededHandler.cs @@ -24,8 +24,15 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message) { - message.Role = AgentRole.Assistant; - message.Content = inst.Response; + var response = new RoleDialogModel(AgentRole.Assistant, inst.Response) + { + CurrentAgentId = message.CurrentAgentId, + MessageId = message.MessageId, + StopCompletion = true, + FunctionName = inst.Function + }; + + _dialogs.Add(response); var hooks = _services.GetServices() .OrderBy(x => x.Priority) @@ -33,7 +40,7 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle foreach (var hook in hooks) { - await hook.OnHumanInterventionNeeded(message); + await hook.OnHumanInterventionNeeded(response); } return true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs index d1aeed35..00068306 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs @@ -5,7 +5,7 @@ using BotSharp.Core.Planning; namespace BotSharp.Core.Routing.Handlers; -public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRoutingHandler +public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase//, IRoutingHandler { public string Name => "interrupt_task_execution"; @@ -19,7 +19,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting public List Planers => new List { - nameof(ReasoningPlanner) + nameof(HFPlanner) }; public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs index b4e399a7..1d12d4b5 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ResponseToUserRoutingHandler.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; @@ -24,9 +23,15 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message) { - message.Content = inst.Response; - message.StopCompletion = true; - message.Role = AgentRole.Assistant; + var response = new RoleDialogModel(AgentRole.Assistant, inst.Response) + { + CurrentAgentId = message.CurrentAgentId, + MessageId = message.MessageId, + StopCompletion = true + }; + + _dialogs.Add(response); + return true; } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs index cf86dec7..4af4f15f 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.Repositories; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Settings; @@ -21,6 +20,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH new ParameterPropertyDef("reason", "why retrieve data"), new ParameterPropertyDef("question", "the question you will ask the agent to get the necessary data"), new ParameterPropertyDef("next_action_agent", "agent that can handle the question"), + new ParameterPropertyDef("args", "required parameters extracted from question and hand over to the next agent") { Type = "object" @@ -29,7 +29,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH public List Planers => new List { - nameof(ReasoningPlanner) + nameof(HFPlanner) }; public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) @@ -40,7 +40,22 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message) { var context = _services.GetRequiredService(); - var ret = await routing.InvokeAgent(context.GetCurrentAgentId(), message); + var agentId = context.GetCurrentAgentId(); + var dialogs = new List + { + new RoleDialogModel(AgentRole.User, inst.Question) + { + CurrentAgentId = agentId, + MessageId = message.MessageId + } + }; + + var ret = await routing.InvokeAgent(agentId, dialogs); + var response = dialogs.Last(); + inst.Response = response.Content; + + // Add final response to parent dialog + _dialogs.Add(response); return ret; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index cd614e91..0f03955a 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -35,7 +35,11 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler message.FunctionArgs = JsonSerializer.Serialize(inst); var ret = await function.Execute(message); - ret = await routing.InvokeAgent(context.GetCurrentAgentId(), message); + var agentId = context.GetCurrentAgentId(); + ret = await routing.InvokeAgent(agentId, _dialogs); + + var response = _dialogs.Last(); + inst.Response = response.Content; return true; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs index 0dfdce94..0b299f25 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/TaskEndRoutingHandler.cs @@ -18,7 +18,7 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler public List Planers => new List { - nameof(ReasoningPlanner) + nameof(HFPlanner) }; public TaskEndRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings) @@ -32,10 +32,9 @@ public class TaskEndRoutingHandler : RoutingHandlerBase, IRoutingHandler .OrderBy(x => x.Priority) .ToList(); - foreach (var hook in hooks) - { - await hook.OnCurrentTaskEnding(message); - } + Task.WaitAll(hooks + .Select(h => h.OnCurrentTaskEnding(message)) + .ToArray()); return true; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs index 7946dca6..c6ced108 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs @@ -8,7 +8,7 @@ public partial class RoutingService { const int MAXIMUM_RECURSION_DEPTH = 3; private int _currentRecursionDepth = 0; - public async Task InvokeAgent(string agentId, RoleDialogModel message) + public async Task InvokeAgent(string agentId, List dialogs) { _currentRecursionDepth++; if (_currentRecursionDepth > MAXIMUM_RECURSION_DEPTH) @@ -22,25 +22,22 @@ public partial class RoutingService var settings = _services.GetRequiredService(); var chatCompletion = CompletionProvider.GetChatCompletion(_services, provider: settings.Provider, model: settings.Model); - RoleDialogModel response = chatCompletion.GetChatCompletions(agent, Dialogs); - message.Role = response.Role; + + RoleDialogModel response = chatCompletion.GetChatCompletions(agent, dialogs); if (response.Role == AgentRole.Function) { - message.FunctionName = response.FunctionName; - message.FunctionArgs = response.FunctionArgs; - - await InvokeFunction(agent, message); + await InvokeFunction(agent, response, dialogs); } else { - message.Content = response.Content; + dialogs.Add(response); } return true; } - private async Task InvokeFunction(Agent agent, RoleDialogModel message) + private async Task InvokeFunction(Agent agent, RoleDialogModel message, List dialogs) { // execute function // Save states @@ -50,8 +47,6 @@ public partial class RoutingService // Call functions await conversationService.CallFunctions(message); - Dialogs.Add(message); - // Pass execution result to LLM to get response if (!message.StopCompletion) { @@ -60,19 +55,24 @@ public partial class RoutingService var responseTemplate = await templateService.RenderFunctionResponse(agent.Id, message); if (!string.IsNullOrEmpty(responseTemplate)) { - message.Role = AgentRole.Assistant; message.Content = responseTemplate.Trim(); + message.Role = AgentRole.Assistant; + dialogs.Add(message); } else { - await InvokeAgent(agent.Id, message); + // Save to memory dialogs + dialogs.Add(new RoleDialogModel(AgentRole.Function, message.Content) + { + FunctionArgs = message.FunctionArgs, + FunctionName = message.FunctionName + }); + + // Send to LLM + await InvokeAgent(agent.Id, dialogs); } } - else - { - message.Role = AgentRole.Assistant; - } - return message; + return true; } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 98e4837a..ed316ec4 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -14,30 +14,14 @@ public partial class RoutingService : IRoutingService private readonly RoutingSettings _settings; private readonly IRouterInstance _routerInstance; private readonly ILogger _logger; - private List _dialogs; - public List Dialogs { - get - { - if (_dialogs == null) - { - var conv = _services.GetRequiredService(); - _dialogs = conv.GetDialogHistory(); - } - - return _dialogs; - } - } + private Agent _router; + public Agent Router => _router; public void ResetRecursiveCounter() { _currentRecursionDepth = 0; } - public void RefreshDialogs() - { - _dialogs = null; - } - public RoutingService(IServiceProvider services, RoutingSettings settings, ILogger logger, @@ -49,44 +33,57 @@ public partial class RoutingService : IRoutingService _routerInstance = routerInstance; } - public async Task ExecuteOnce(Agent agent, RoleDialogModel message) + public async Task ExecuteOnce(Agent agent, RoleDialogModel message) { var handlers = _services.GetServices(); var handler = handlers.FirstOrDefault(x => x.Name == "route_to_agent"); - handler.SetDialogs(Dialogs); + var dialogs = new List { message }; + handler.SetDialogs(dialogs); - var result = await handler.Handle(this, new FunctionCallFromLlm + var inst = new FunctionCallFromLlm { Function = "route_to_agent", Question = message.Content, Reason = message.Content, AgentName = agent.Name - }, message); + }; - return result; + var result = await handler.Handle(this, inst, message); + + var response = dialogs.Last(); + response.MessageId = message.MessageId; + response.Instruction = inst; + + return response; } - public async Task InstructLoop(RoleDialogModel message) + public async Task InstructLoop(RoleDialogModel message) { - _routerInstance.Load(); - var router = _routerInstance.Router; + _router = _routerInstance.Load() + .Router; + + RoleDialogModel response = default; + + var conv = _services.GetRequiredService(); + var dialogs = conv.GetDialogHistory(); var context = _services.GetRequiredService(); var planner = _services.GetRequiredService(); var executor = _services.GetRequiredService(); + context.Push(_router.Id); + int loopCount = 0; - var stop = false; - while (!stop && loopCount < 5) + while (loopCount < 5 && !context.IsEmpty) { loopCount++; - var conversation = await GetConversationContent(Dialogs); - router.TemplateDict["conversation"] = conversation; + var conversation = await GetConversationContent(dialogs); + _router.TemplateDict["conversation"] = conversation; // Get instruction from Planner - var inst = await planner.GetNextInstruction(router); + var inst = await planner.GetNextInstruction(_router, message.MessageId); // Save states SaveStateByArgs(inst.Arguments); @@ -99,18 +96,12 @@ public partial class RoutingService : IRoutingService await planner.AgentExecuting(inst, message); // Handle instruction by Executor - var executed = await executor.Execute(this, router, inst, Dialogs, message); + response = await executor.Execute(this, inst, message, dialogs); - await planner.AgentExecuted(inst, message); - - // There is no need for the agent to continue processing, indicating that the task has been completed. - if (context.IsEmpty || context.GetCurrentAgentId() == router.Id) - { - break; - } + await planner.AgentExecuted(inst, response); } - return true; + return response; } protected void SaveStateByArgs(JsonDocument args) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 80692bc9..7b3733a4 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -57,7 +57,11 @@ public class ConversationController : ControllerBase, IApiAdapter await conv.SendMessage(agentId, inputMsg, async msg => { - + response.Text = msg.Content; + response.Function = msg.FunctionName; + response.RichContent = msg.RichContent; + response.Instruction = msg.Instruction; + response.Data = msg.Data; }, async fnExecuting => { @@ -69,11 +73,6 @@ public class ConversationController : ControllerBase, IApiAdapter }); response.MessageId = inputMsg.MessageId; - response.Text = inputMsg.Content; - response.Data = inputMsg.Data; - response.Function = inputMsg.FunctionName; - response.Instruction = inputMsg.Instruction; - response.RichContent = inputMsg.RichContent; return response; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index 3af20b6c..40de5287 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -1,10 +1,8 @@ using BotSharp.Abstraction.Agents.Enums; -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.ApiAdapters; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; -using BotSharp.Abstraction.Templating; using BotSharp.Core.Infrastructures; using BotSharp.OpenAPI.ViewModels.Instructs; @@ -50,6 +48,6 @@ public class InstructModeController : ControllerBase, IApiAdapter .SetState("model", input.Model); var textCompletion = CompletionProvider.GetTextCompletion(_services); - return await textCompletion.GetCompletion(input.Text); + return await textCompletion.GetCompletion(input.Text, Guid.Empty.ToString(), Guid.Empty.ToString()); } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs index 4fe7781c..4b0c335d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/MessageResponseModel.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Models; +using System.Text.Json.Serialization; namespace BotSharp.OpenAPI.ViewModels.Conversations; @@ -8,7 +9,10 @@ public class MessageResponseModel : ITrackableMessage public string MessageId { get; set; } public string Text { get; set; } public string Function { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object Data { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public FunctionCallFromLlm Instruction { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object? RichContent { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index 1b0b2c6e..1a756c2e 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -8,6 +8,7 @@ using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Conversations.Settings; using BotSharp.Abstraction.MLTasks; using BotSharp.Plugin.AzureOpenAI.Settings; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; @@ -45,51 +46,54 @@ public class ChatCompletionProvider : IChatCompletion hook.BeforeGenerating(agent, conversations)).ToArray()); var client = ProviderHelper.GetClient(_model, _settings); - var chatCompletionsOptions = PrepareOptions(agent, conversations); + var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations); var response = client.GetChatCompletions(_model, chatCompletionsOptions); var choice = response.Value.Choices[0]; var message = choice.Message; - var msg = new RoleDialogModel(AgentRole.Assistant, message.Content) + var responseMessage = new RoleDialogModel(AgentRole.Assistant, message.Content) { - CurrentAgentId = agent.Id + CurrentAgentId = agent.Id, + MessageId = conversations.Last().MessageId }; if (choice.FinishReason == CompletionsFinishReason.FunctionCall) { - msg = new RoleDialogModel(AgentRole.Function, message.Content) + responseMessage = new RoleDialogModel(AgentRole.Function, message.Content) { CurrentAgentId = agent.Id, + MessageId = conversations.Last().MessageId, FunctionName = message.FunctionCall.Name, FunctionArgs = message.FunctionCall.Arguments }; // Somethings LLM will generate a function name with agent name. - if (!string.IsNullOrEmpty(msg.FunctionName)) + if (!string.IsNullOrEmpty(responseMessage.FunctionName)) { - msg.FunctionName = msg.FunctionName.Split('.').Last(); + responseMessage.FunctionName = responseMessage.FunctionName.Split('.').Last(); } } var setting = _services.GetRequiredService(); if (setting.ShowVerboseLog) { - _logger.LogInformation(msg.Role == AgentRole.Function ? - $"[{agent.Name}]: {msg.FunctionName}({msg.FunctionArgs})" : - $"[{agent.Name}]: {msg.Content}"); + _logger.LogInformation(responseMessage.Role == AgentRole.Function ? + $"[{agent.Name}]: {responseMessage.FunctionName}({responseMessage.FunctionArgs})" : + $"[{agent.Name}]: {responseMessage.Content}"); } // After chat completion hook Task.WaitAll(hooks.Select(hook => - hook.AfterGenerated(msg, new TokenStatsModel + hook.AfterGenerated(responseMessage, new TokenStatsModel { + Prompt = prompt, Model = _model, PromptCount = response.Value.Usage.PromptTokens, CompletionCount = response.Value.Usage.CompletionTokens })).ToArray()); - return msg; + return responseMessage; } public async Task GetChatCompletionsAsync(Agent agent, @@ -104,7 +108,7 @@ public class ChatCompletionProvider : IChatCompletion hook.BeforeGenerating(agent, conversations)).ToArray()); var client = ProviderHelper.GetClient(_model, _settings); - var chatCompletionsOptions = PrepareOptions(agent, conversations); + var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations); var response = await client.GetChatCompletionsAsync(_model, chatCompletionsOptions); var choice = response.Value.Choices[0]; @@ -119,6 +123,7 @@ public class ChatCompletionProvider : IChatCompletion Task.WaitAll(hooks.Select(hook => hook.AfterGenerated(msg, new TokenStatsModel { + Prompt = prompt, Model = _model, PromptCount = response.Value.Usage.PromptTokens, CompletionCount = response.Value.Usage.CompletionTokens @@ -156,7 +161,7 @@ public class ChatCompletionProvider : IChatCompletion public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) { var client = ProviderHelper.GetClient(_model, _settings); - var chatCompletionsOptions = PrepareOptions(agent, conversations); + var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations); var response = await client.GetChatCompletionsStreamingAsync(_model, chatCompletionsOptions); using StreamingChatCompletions streaming = response.Value; @@ -198,7 +203,7 @@ public class ChatCompletionProvider : IChatCompletion } - protected ChatCompletionsOptions PrepareOptions(Agent agent, List conversations) + protected (string, ChatCompletionsOptions) PrepareOptions(Agent agent, List conversations) { var agentService = _services.GetRequiredService(); @@ -255,33 +260,50 @@ public class ChatCompletionProvider : IChatCompletion // chatCompletionsOptions.FrequencyPenalty = 0; // chatCompletionsOptions.PresencePenalty = 0; + var prompt = GetPrompt(chatCompletionsOptions); var convSetting = _services.GetRequiredService(); if (convSetting.ShowVerboseLog) { - if (chatCompletionsOptions.Messages.Count > 0) - { - _logger.LogInformation("VERBOSE COMPLETION MESSAGES"); - var verbose = string.Join("\r\n", chatCompletionsOptions.Messages.Select(x => + _logger.LogInformation(prompt); + } + + return (prompt, chatCompletionsOptions); + } + + private string GetPrompt(ChatCompletionsOptions chatCompletionsOptions) + { + var prompt = string.Empty; + + if (chatCompletionsOptions.Messages.Count > 0) + { + // System instruction + var verbose = string.Join("\r\n", chatCompletionsOptions.Messages + .Where(x => x.Role == AgentRole.System).Select(x => + { + return $"{x.Role}: {x.Content}"; + })); + prompt += $"\r\n[INSTRUCTION]\r\n{verbose}\r\n"; + + verbose = string.Join("\r\n", chatCompletionsOptions.Messages + .Where(x => x.Role != AgentRole.System).Select(x => { return x.Role == ChatRole.Function ? $"{x.Role}: {x.Name} => {x.Content}" : $"{x.Role}: {x.Content}"; })); - _logger.LogInformation(verbose); - } - - if (chatCompletionsOptions.Functions.Count > 0) - { - _logger.LogInformation("VERBOSE FUNCTIONS"); - var verbose = string.Join("\r\n", chatCompletionsOptions.Functions.Select(x => - { - return $"{x.Name}: {x.Description}\r\n{x.Parameters}"; - })); - _logger.LogInformation(verbose); - } + prompt += $"\r\n[CONVERSATION]\r\n{verbose}\r\n"; } - return chatCompletionsOptions; + if (chatCompletionsOptions.Functions.Count > 0) + { + var functions = string.Join("\r\n", chatCompletionsOptions.Functions.Select(x => + { + return $"{x.Name}: {x.Description}\r\n{x.Parameters}"; + })); + prompt += $"\r\n[FUNCTIONS]\r\n{functions}\r\n"; + } + + return prompt; } public void SetModelName(string model) diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs index dc17ddc7..16707bed 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs @@ -32,16 +32,26 @@ public class TextCompletionProvider : ITextCompletion _logger = logger; } - public async Task GetCompletion(string text) + public async Task GetCompletion(string text, string agentId, string messageId) { var hooks = _services.GetServices().ToList(); // Before chat completion hook + var agent = new Agent() + { + Id = agentId, + }; + var message = new RoleDialogModel(AgentRole.User, text) + { + CurrentAgentId = agentId, + MessageId = messageId + }; + Task.WaitAll(hooks.Select(hook => - hook.BeforeGenerating(new Agent(), - new List - { - new RoleDialogModel(AgentRole.User, text) + hook.BeforeGenerating(agent, + new List + { + message })).ToArray()); var client = ProviderHelper.GetClient(_model, _settings); @@ -85,9 +95,15 @@ public class TextCompletionProvider : ITextCompletion } // After chat completion hook + var responseMessage = new RoleDialogModel(AgentRole.Assistant, completion) + { + CurrentAgentId = agentId, + MessageId = messageId + }; Task.WaitAll(hooks.Select(hook => - hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, completion), new TokenStatsModel + hook.AfterGenerated(responseMessage, new TokenStatsModel { + Prompt = text, Model = _model, PromptCount = response.Value.Usage.PromptTokens, CompletionCount = response.Value.Usage.CompletionTokens diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs index be73cd73..87c71e8f 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/TextCompletionProvider.cs @@ -26,13 +26,21 @@ public class TextCompletionProvider : ITextCompletion _tokenStatistics = tokenStatistics; } - public async Task GetCompletion(string text) + public async Task GetCompletion(string text, string agentId, string messageId) { var hooks = _services.GetServices().ToList(); // Before chat completion hook + var agent = new Agent() + { + Id = agentId + }; + var userMessage = new RoleDialogModel(AgentRole.User, text) + { + MessageId = messageId + }; Task.WaitAll(hooks.Select(hook => - hook.BeforeGenerating(new Agent(), new List { new RoleDialogModel(AgentRole.User, text) })).ToArray()); + hook.BeforeGenerating(agent, new List { userMessage })).ToArray()); var client = new GooglePalmClient(apiKey: _settings.PaLM.ApiKey); _tokenStatistics.StartTimer(); diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs index 15a38046..8a818534 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs @@ -67,7 +67,7 @@ public class KnowledgeService : IKnowledgeService sb.AppendLine("ANSWER: "); prompt = sb.ToString().Trim(); - var completion = await GetTextCompletion().GetCompletion(prompt); + var completion = await GetTextCompletion().GetCompletion(prompt, Guid.Empty.ToString(), Guid.Empty.ToString()); return JsonSerializer.Deserialize>(completion); } diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextCompletionProvider.cs index daba2cd5..9107d8a0 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextCompletionProvider.cs @@ -20,13 +20,21 @@ public class TextCompletionProvider : ITextCompletion _tokenStatistics = tokenStatistics; } - public async Task GetCompletion(string text) + public async Task GetCompletion(string text, string agentId, string messageId) { var hooks = _services.GetServices().ToList(); // Before chat completion hook + var agent = new Agent() + { + Id = agentId + }; + var userMessage = new RoleDialogModel(AgentRole.User, text) + { + MessageId = messageId + }; Task.WaitAll(hooks.Select(hook => - hook.BeforeGenerating(new Agent(), new List { new RoleDialogModel(AgentRole.User, text) })).ToArray()); + hook.BeforeGenerating(agent, new List { userMessage })).ToArray()); var llama = _services.GetRequiredService(); llama.LoadModel(_model); @@ -44,8 +52,13 @@ public class TextCompletionProvider : ITextCompletion _tokenStatistics.StopTimer(); // After chat completion hook + var responseMessage = new RoleDialogModel(AgentRole.Assistant, completion) + { + CurrentAgentId = agentId, + MessageId = messageId + }; Task.WaitAll(hooks.Select(hook => - hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, completion), new TokenStatsModel + hook.AfterGenerated(responseMessage, new TokenStatsModel { Model = _model })).ToArray()); diff --git a/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid b/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid index f47bd078..800bde83 100644 --- a/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid +++ b/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid @@ -5,6 +5,7 @@ Role: You're a customer who is going to buy a pizza. * Your phone number is +16308926431 Requirments: +* Greeting to clerk. * You want to know what kind of pizza do they have. * You want to buy three piece of pizza. * Say Bye if the order is placed and payment is completed. \ No newline at end of file diff --git a/tests/BotSharp.Plugin.PizzaBot/Hooks/CommonAgentHook.cs b/tests/BotSharp.Plugin.PizzaBot/Hooks/CommonAgentHook.cs new file mode 100644 index 00000000..95f9e6ef --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Hooks/CommonAgentHook.cs @@ -0,0 +1,21 @@ +using BotSharp.Abstraction.Agents; + +namespace BotSharp.Plugin.PizzaBot.Hooks; + +public class CommonAgentHook : AgentHookBase +{ + public override string SelfId => string.Empty; + + public CommonAgentHook(IServiceProvider services, AgentSettings settings) + : base(services, settings) + { + } + + public override bool OnInstructionLoaded(string template, Dictionary dict) + { + dict["current_date"] = DateTime.Now.ToString("MM/dd/yyyy"); + dict["current_time"] = DateTime.Now.ToString("hh:mm tt"); + dict["current_weekday"] = DateTime.Now.DayOfWeek; + return base.OnInstructionLoaded(template, dict); + } +}