diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs index 4a39c144..26655e2b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs @@ -7,6 +7,7 @@ public interface IConversationStateService { ConversationState Load(string conversationId); string GetState(string name, string defaultValue = ""); + bool ContainsState(string name); ConversationState GetStates(); IConversationStateService SetState(string name, T value); void CleanState(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs index 07fd12d8..14ac7796 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs @@ -3,13 +3,13 @@ using System.Text.Json; namespace BotSharp.Abstraction.Functions.Models; -public class FunctionCallFromLlm +public class FunctionCallFromLlm : RoutingArgs { [JsonPropertyName("function")] public string Function { get; set; } = string.Empty; - [JsonPropertyName("route")] - public RoutingArgs Route { get; set; } = new RoutingArgs(); + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; [JsonPropertyName("question")] public string? Question { get; set; } @@ -22,13 +22,15 @@ public class FunctionCallFromLlm public override string ToString() { + var route = string.IsNullOrEmpty(AgentName) ? "" : $""; + if (string.IsNullOrEmpty(Answer)) { - return $"[{Function} {Route} {JsonSerializer.Serialize(Arguments)}]: {Question}"; + return $"[{Function} {route} {JsonSerializer.Serialize(Arguments)}]: {Question}"; } else { - return $"[{Function} {Route} {JsonSerializer.Serialize(Arguments)}]: {Question} => {Answer}"; + return $"[{Function} {route} {JsonSerializer.Serialize(Arguments)}]: {Question} => {Answer}"; } } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index fd2c2dd1..7ed8843d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -4,7 +4,6 @@ public interface IRoutingService { Agent LoadRouter(); List Dialogs { get; } - void SetDialogs(List dialogs); - Task InstructLoop(Agent router); + Task InstructLoop(); 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 473af579..a6c39df3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs @@ -2,14 +2,11 @@ namespace BotSharp.Abstraction.Routing.Models; public class RoutingArgs { - [JsonPropertyName("reason")] - public string Reason { get; set; } = string.Empty; - [JsonPropertyName("agent")] public string AgentName { get; set; } = string.Empty; public override string ToString() { - return string.IsNullOrEmpty(AgentName) ? "" : $""; + return AgentName; } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 4fde0e74..2eee1169 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -1,5 +1,7 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories; +using BotSharp.Abstraction.Routing; +using BotSharp.Abstraction.Routing.Settings; namespace BotSharp.Core.Agents.Services; @@ -20,6 +22,13 @@ public partial class AgentService #endif public async Task GetAgent(string id) { + var settings = _services.GetRequiredService(); + var routingService = _services.GetRequiredService(); + if (settings.RouterId == id) + { + return routingService.LoadRouter(); + } + var profile = _db.GetAgent(id); var instructionFile = profile?.Instruction; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 87e05734..59926c43 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -20,9 +20,7 @@ public partial class AgentService hook.OnAgentLoading(ref id); } - var settings = _services.GetRequiredService(); - var routingService = _services.GetRequiredService(); - var agent = settings.RouterId == id ? routingService.LoadRouter() : await GetAgent(id); + var agent = await GetAgent(id); var templateDict = new Dictionary(); PopulateState(templateDict); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 8806d8d1..cc233085 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -8,7 +8,7 @@ namespace BotSharp.Core.Conversations.Services; public partial class ConversationService { public async Task SendMessage(string agentId, - RoleDialogModel lastDialog, + RoleDialogModel incoming, Func onMessageReceived, Func onFunctionExecuting, Func onFunctionExecuted) @@ -18,14 +18,11 @@ public partial class ConversationService var agentService = _services.GetRequiredService(); Agent agent = await agentService.LoadAgent(agentId); - _logger.LogInformation($"[{agent.Name}] {lastDialog.Role}: {lastDialog.Content}"); + _logger.LogInformation($"[{agent.Name}] {incoming.Role}: {incoming.Content}"); - lastDialog.CurrentAgentId = agent.Id; - - var wholeDialogs = GetDialogHistory(); - wholeDialogs.Add(lastDialog); + incoming.CurrentAgentId = agent.Id; - _storage.Append(_conversationId, lastDialog); + _storage.Append(_conversationId, incoming); var hooks = _services.GetServices().ToList(); @@ -35,18 +32,13 @@ public partial class ConversationService hook.SetAgent(agent) .SetConversation(conversation); - await hook.OnDialogsLoaded(wholeDialogs); - await hook.BeforeCompletion(lastDialog); + await hook.BeforeCompletion(incoming); // Interrupted by hook - if (lastDialog.StopCompletion) + if (incoming.StopCompletion) { - var message = new RoleDialogModel(AgentRole.Assistant, lastDialog.Content) - { - CurrentAgentId = agent.Id - }; - await onMessageReceived(message); - _storage.Append(_conversationId, message); + await onMessageReceived(incoming); + _storage.Append(_conversationId, incoming); return true; } } @@ -55,10 +47,8 @@ public partial class ConversationService var routing = _services.GetRequiredService(); var settings = _services.GetRequiredService(); - routing.SetDialogs(wholeDialogs); - - var response = settings.RouterId == agent.Id ? - await routing.InstructLoop(agent) : + var response = agentId == settings.RouterId ? + await routing.InstructLoop() : await routing.ExecuteOnce(agent); await HandleAssistantMessage(response, onMessageReceived); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index 4f242846..a03e35ec 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -13,7 +13,6 @@ public class ConversationStateService : IConversationStateService, IDisposable private ConversationState _states; private BotSharpDatabaseSettings _dbSettings; private string _conversationId; - private string _file; private readonly IBotSharpRepository _db; private List _savedStates; @@ -100,22 +99,6 @@ public class ConversationStateService : IConversationStateService, IDisposable //File.Delete(_file); } - private string GetStorageFile(string conversationId) - { - var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId); - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - - var stateFile = Path.Combine(dir, "state.dict"); - if (!File.Exists(stateFile)) - { - File.WriteAllText(stateFile, ""); - } - return stateFile; - } - public ConversationState GetStates() => _states; @@ -138,4 +121,9 @@ public class ConversationStateService : IConversationStateService, IDisposable { Save(); } + + public bool ContainsState(string name) + { + return _states.ContainsKey(name) && !string.IsNullOrEmpty(_states[name]); + } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs index d0cec273..52d8ba88 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs @@ -30,7 +30,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase, IRoutingHan { var routing = _services.GetRequiredService(); var db = _services.GetRequiredService(); - var record = db.Agents.First(x => x.Name.ToLower() == inst.Route.AgentName.ToLower()); + var record = db.Agents.First(x => x.Name.ToLower() == inst.AgentName.ToLower()); var result = new RoleDialogModel(AgentRole.Function, inst.Question) { diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs index 7669982d..5a9dfd43 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs @@ -26,7 +26,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase, IRouting public async Task Handle(FunctionCallFromLlm inst) { - var result = new RoleDialogModel(AgentRole.User, inst.Route.Reason) + var result = new RoleDialogModel(AgentRole.User, inst.Reason) { 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 3a181252..97d9ac41 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs @@ -29,14 +29,14 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingH public async Task Handle(FunctionCallFromLlm inst) { - if (string.IsNullOrEmpty(inst.Route.AgentName)) + if (string.IsNullOrEmpty(inst.AgentName)) { inst = await GetNextInstructionFromReasoner($"What's the next step? your response must have agent name."); } // Retrieve information from specific agent var db = _services.GetRequiredService(); - var record = db.Agents.First(x => x.Name.ToLower() == inst.Route.AgentName.ToLower()); + var record = db.Agents.First(x => x.Name.ToLower() == inst.AgentName.ToLower()); 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 662b5f19..0acd7c3f 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -15,9 +15,9 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler public List Parameters => new List { - new NameDesc("agent", "the name of the agent from AGENTS"), + new NameDesc("agent", "the name of the agent"), new NameDesc("reason", "why route to this agent"), - new NameDesc("args", "parameters extracted from context") + new NameDesc("args", "the agent required parameters") }; public bool IsReasoning => false; @@ -29,18 +29,13 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler public async Task Handle(FunctionCallFromLlm inst) { - if (string.IsNullOrEmpty(inst.Route.AgentName)) - { - inst = await GetNextInstructionFromReasoner($"What's the next step? your response must have agent name."); - } - var function = _services.GetServices().FirstOrDefault(x => x.Name == inst.Function); var message = new RoleDialogModel(AgentRole.Function, inst.Question) { FunctionName = inst.Function, FunctionArgs = JsonSerializer.Serialize(new RoutingArgs { - AgentName = inst.Route.AgentName + AgentName = inst.AgentName }), }; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs index e5ea5266..639904be 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RoutingHandlerBase.cs @@ -63,13 +63,20 @@ public abstract class RoutingHandlerBase var pattern = @"\{(?:[^{}]|(?\{)|(?<-open>\}))+(?(open)(?!))\}"; response.Content = Regex.Match(response.Content, pattern).Value; args = JsonSerializer.Deserialize(response.Content); + + // Sometimes it populate malformed Function in Agent name + if (args.Function == args.AgentName) + { + args.Function = "route_to_agent"; + _logger.LogWarning($"Captured LLM response "); + } } catch (Exception ex) { _logger.LogError($"{ex.Message}: {response.Content}"); args.Function = "response_to_user"; args.Answer = ex.Message; - args.Route.AgentName = _settings.RouterName; + args.AgentName = _settings.RouterName; } if (args.Arguments != null) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index e01e63d5..82117a38 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -12,7 +12,18 @@ public class RoutingService : IRoutingService private readonly RoutingSettings _settings; private readonly ILogger _logger; private List _dialogs; - public List Dialogs => _dialogs; + public List Dialogs { + get + { + if (_dialogs == null) + { + var conv = _services.GetRequiredService(); + _dialogs = conv.GetDialogHistory(); + } + + return _dialogs; + } + } public RoutingService(IServiceProvider services, RoutingSettings settings, @@ -23,42 +34,36 @@ public class RoutingService : IRoutingService _logger = logger; } - public void SetDialogs(List dialogs) - { - _dialogs = dialogs; - } public async Task ExecuteOnce(Agent agent) { - var message = _dialogs.Last().Content; + var message = Dialogs.Last().Content; var handlers = _services.GetServices(); var handler = handlers.FirstOrDefault(x => x.Name == "route_to_agent"); - handler.SetDialogs(_dialogs); + handler.SetDialogs(Dialogs); var result = await handler.Handle(new FunctionCallFromLlm { - Function = "route_to_agent", - Question = message, - Route = new RoutingArgs - { - Reason = message, - AgentName = agent.Name, - } + Function = "route_to_agent", + Question = message, + Reason = message, + AgentName = agent.Name }); return result; } - public async Task InstructLoop(Agent router) + public async Task InstructLoop() { + var router = LoadRouter(); var result = new RoleDialogModel(AgentRole.Assistant, "Can you repeat your request again?") { CurrentAgentId = router.Id }; - var message = _dialogs.Last().Content; - foreach (var dialog in _dialogs.TakeLast(20)) + var message = Dialogs.Last().Content; + foreach (var dialog in Dialogs.TakeLast(20)) { router.Instruction += $"\r\n{dialog.Role}: {dialog.Content}"; } @@ -67,7 +72,7 @@ public class RoutingService : IRoutingService var handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction"); handler.SetRouter(router); - handler.SetDialogs(_dialogs); + handler.SetDialogs(Dialogs); int loopCount = 0; var stop = false; @@ -86,7 +91,7 @@ public class RoutingService : IRoutingService continue; } handler.SetRouter(router); - handler.SetDialogs(_dialogs); + handler.SetDialogs(Dialogs); result = await handler.Handle(inst); @@ -115,7 +120,7 @@ public class RoutingService : IRoutingService var prompt = @"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. +3. Determine which agent is suitable according to conversation context. 4. Re-think about selected function is from FUNCTIONS to handle the request."; // Append function @@ -133,7 +138,7 @@ public class RoutingService : IRoutingService prompt += "\r\nParameters:"; handler.Parameters.Select((p, i) => { - prompt += $"\r\n{i + 1}. {p.Name}: {p.Description}"; + prompt += $"\r\n - {p.Name}: {p.Description}"; return p; }).ToList(); }