diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs b/src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs index f24eac9a..4bbb3ca0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Models/NameDesc.cs @@ -2,7 +2,10 @@ namespace BotSharp.Abstraction.Models; public class NameDesc { + [JsonPropertyName("name")] public string Name { get; set; } + + [JsonPropertyName("description")] public string Description { get; set; } public NameDesc(string name, string description) @@ -10,4 +13,7 @@ public class NameDesc Name = name; Description = description; } + + public override string ToString() + => $"{Name}: {Description}"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRouterInstance.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRouterInstance.cs index bc204c98..e6734844 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRouterInstance.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRouterInstance.cs @@ -6,6 +6,7 @@ public interface IRouterInstance { string AgentId { get; } Agent Router { get; } + RoutingItem[] GetRoutingItems(); List GetHandlers(); IRouterInstance Load(); IRouterInstance WithDialogs(List dialogs); diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs index eb50835c..0591fa0c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingHandlerDef.cs @@ -7,4 +7,7 @@ public class RoutingHandlerDef public string Name { get; set; } public string Description { get; set; } public List Parameters { get; set; } + + public override string ToString() + => $"{Name}: {Description} ({Parameters.Count} Parameters)"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs index 38e62ced..39338b13 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs @@ -7,10 +7,6 @@ public class RoutingSettings /// public string RouterId { get; set; } = string.Empty; - public string RouterName { get; set; } = "Router"; - - public string Description { get; set; } = string.Empty; - public bool EnableReasoning { get; set; } = false; public bool UseTextCompletion { get; set; } = false; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 755e8213..3b70849f 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -25,13 +25,6 @@ public partial class AgentService #endif public async Task GetAgent(string id) { - var settings = _services.GetRequiredService(); - var routerInstance = _services.GetRequiredService(); - if (settings.RouterId == id) - { - return routerInstance.Load().Router; - } - var profile = _db.GetAgent(id); var instructionFile = profile?.Instruction; diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 1b789f1f..cc495f4a 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -63,4 +63,8 @@ + + + + diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index bef15ac1..d4736e7e 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -89,7 +89,7 @@ public partial class ConversationService var routingSetting = _services.GetRequiredService(); var agentName = routingSetting.RouterId == message.CurrentAgentId ? - routingSetting.RouterName : + "Router" : (await _services.GetRequiredService().GetAgent(message.CurrentAgentId)).Name; var text = message.Role == AgentRole.Function ? diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index 7fc91576..d22f1c0c 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -47,7 +47,7 @@ public class ConversationStorage : IConversationStorage else { var routingSetting = _services.GetRequiredService(); - var agentName = routingSetting.RouterId == agentId ? routingSetting.RouterName : db.Agents.First(x => x.Id == agentId).Name; + var agentName = routingSetting.RouterId == agentId ? "Router" : db.Agents.First(x => x.Id == agentId).Name; sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{agentName}|"); var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs index fd868ab4..8d0de63d 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs @@ -1,4 +1,8 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Models; +using BotSharp.Abstraction.Repositories; +using BotSharp.Abstraction.Routing; +using BotSharp.Abstraction.Routing.Models; namespace BotSharp.Core.Routing.Hooks; @@ -9,6 +13,17 @@ public class RoutingAgentHook : AgentHookBase { } + public override bool OnInstructionLoaded(string template, Dictionary dict) + { + dict["router"] = _agent; + + var router = _services.GetRequiredService(); + dict["routing_agents"] = router.GetRoutingItems(); + dict["routing_handlers"] = router.GetHandlers(); + + return base.OnInstructionLoaded(template, dict); + } + public override bool OnFunctionsLoaded(List functions) { /*functions.Add(new FunctionDef diff --git a/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs b/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs index 3782cf60..f1452dfb 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs @@ -28,80 +28,8 @@ public class RouterInstance : IRouterInstance public IRouterInstance Load() { - var db = _services.GetRequiredService(); - - _router = new Agent() - { - Id = _settings.RouterId, - Name = _settings.RouterName, - Description = _settings.Description - }; - var agents = db.Agents.Where(x => !x.Disabled && x.AllowRouting).ToArray(); - - // Assemble prompt - var prompt = @$"You're {_settings.RouterName} ({_settings.Description}). Follow these steps to handle user's request: -1. Read the CONVERSATION context. -2. Select a appropriate function from FUNCTIONS. -3. Determine which agent is suitable according to conversation context. -4. Re-think about selected function is from FUNCTIONS to handle the request. -5. Make sure agent is not in args."; - - // Append function - prompt += "\r\n"; - prompt += "\r\nFUNCTIONS"; - GetHandlers().Select((handler, i) => - { - prompt += "\r\n"; - prompt += $"\r\n{i + 1}. {handler.Name}"; - prompt += $"\r\n{handler.Description}"; - - // Append parameters - if (handler.Parameters.Any()) - { - prompt += "\r\nParameters:"; - handler.Parameters.Select((p, i) => - { - prompt += $"\r\n - {p.Name}: {p.Description}"; - return p; - }).ToList(); - } - - return handler; - }).ToList(); - - prompt += "\r\n"; - prompt += "\r\nAGENTS"; - agents.Select(x => new RoutingItem - { - AgentId = x.Id, - Description = x.Description, - Name = x.Name, - RequiredFields = x.RoutingRules.Where(x => x.Required) - .Select(x => new NameDesc(x.Field, x.Description)) - .ToList() - }).Select((agent, i) => - { - prompt += "\r\n"; - prompt += $"\r\n{i + 1}. {agent.Name}"; - prompt += $"\r\n{agent.Description}"; - - // Append parameters - if (agent.RequiredFields.Any()) - { - prompt += $"\r\nRequired:"; - agent.RequiredFields.Select((field, i) => - { - prompt += $"\r\n - {field.Name}: {field.Description}"; - return field; - }).ToList(); - } - return agent; - }).ToList(); - - prompt += "\r\n"; - prompt += "\r\nCONVERSATION"; - _router.Instruction = prompt; - + var agentService = _services.GetRequiredService(); + _router = agentService.LoadAgent(_settings.RouterId).Result; return this; } @@ -119,7 +47,7 @@ public class RouterInstance : IRouterInstance return _services.GetServices() .Where(x => x.IsReasoning == _settings.EnableReasoning) .Where(x => !string.IsNullOrEmpty(x.Description)) - .Select(x => new RoutingHandlerDef + .Select((x, i) => new RoutingHandlerDef { Name = x.Name, Description = x.Description, @@ -157,6 +85,25 @@ public class RouterInstance : IRouterInstance return records; } +#if !DEBUG + [MemoryCache(10 * 60)] +#endif + public RoutingItem[] GetRoutingItems() + { + var db = _services.GetRequiredService(); + + var agents = db.Agents.Where(x => !x.Disabled && x.AllowRouting).ToArray(); + return agents.Select(x => new RoutingItem + { + AgentId = x.Id, + Description = x.Description, + Name = x.Name, + RequiredFields = x.RoutingRules.Where(x => x.Required) + .Select(x => new NameDesc(x.Field, x.Description)) + .ToList() + }).ToArray(); + } + public RoutingRule[] GetRulesByName(string name) { return GetRoutingRecords() diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs index 4b0468cb..cd3c8de5 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs @@ -49,7 +49,7 @@ public partial class RoutingService _logger.LogError($"{ex.Message}: {response.Content}"); args.Function = "response_to_user"; args.Answer = ex.Message; - args.AgentName = _settings.RouterName; + args.AgentName = "Router"; content += "\r\nPlease response in JSON format."; } finally @@ -85,7 +85,7 @@ public partial class RoutingService _logger.LogError($"{ex.Message}: {response.Content}"); args.Function = "response_to_user"; args.Answer = ex.Message; - args.AgentName = _settings.RouterName; + args.AgentName = "Router"; content += "\r\nPlease response in JSON format."; } finally diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 8be7bfe0..ca4f11ea 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -77,6 +77,7 @@ public partial class RoutingService : IRoutingService loopCount++; var prompt = _settings.EnableReasoning ? "Tell me the next step?" : "Which agent is suitable to handle user's request based on the CONVERSATION?"; + prompt += " Or you can handle without asking specific agent."; var inst = await GetNextInstruction(prompt); inst.Question = inst.Question ?? message; diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs index 6f08efaf..f03df0a1 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Templating; using Fluid; @@ -17,6 +19,9 @@ public class TemplateRender : ITemplateRender _logger = logger; _options = new TemplateOptions(); _options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.SnakeCase; + + _options.MemberAccessStrategy.Register(); + _options.MemberAccessStrategy.Register(); _options.MemberAccessStrategy.Register(); _options.MemberAccessStrategy.Register(); } @@ -31,7 +36,6 @@ public class TemplateRender : ITemplateRender } else { - return template; } } diff --git a/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/agent.json b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/agent.json new file mode 100644 index 00000000..1951ab9b --- /dev/null +++ b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/agent.json @@ -0,0 +1,7 @@ +{ + "name": "PizzaBot", + "description": "Pizza restaurant AI Bot", + "createdDateTime": "2023-08-18T14:39:32.2349685Z", + "updatedDateTime": "2023-08-18T14:39:32.2349686Z", + "id": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a" +} \ No newline at end of file diff --git a/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid new file mode 100644 index 00000000..3dbcde84 --- /dev/null +++ b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid @@ -0,0 +1,26 @@ +You're {{router.name}} ({{router.description}}). Follow these steps to handle user's request: +1. Read the CONVERSATION context. +2. Select a appropriate function from FUNCTIONS. +3. Determine which agent is suitable according to conversation context. +4. Re-think about selected function is from FUNCTIONS to handle the request. +5. Make sure agent is not in args. + +FUNCTIONS +{% for handler in routing_handlers %} +# {{ handler.name }} +{{ handler.description}} +{% if handler.parameters -%} +Parameters: + {% for p in handler.parameters -%} + {{ p.name }}: {{ p.description }}{{ "\r\n " }} + {%- endfor %} +{%- endif %} +{% endfor %} + +AGENTS +{% for agent in routing_agents %} +* {{ agent.name }} +{{ agent.description}} +{% endfor %} + +CONVERSATION \ No newline at end of file