From 8c4c0a0e6ed708dd896adb261825b8b4f9a24d6d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 19 Mar 2024 17:32:17 -0500 Subject: [PATCH 1/7] add google api --- .../Google/Models/GoogleAddressResult.cs | 14 +++++++ .../Google/Settings/GoogleApiSettings.cs | 9 +++++ .../BotSharp.Core/BotSharpCoreExtensions.cs | 3 -- .../BotSharpOpenApiExtensions.cs | 8 ++++ .../Controllers/AddressController.cs | 39 +++++++++++++++++++ src/WebStarter/appsettings.json | 7 ++++ 6 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs b/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs new file mode 100644 index 00000000..ec156261 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs @@ -0,0 +1,14 @@ +namespace BotSharp.Abstraction.Google.Models; + +public class GoogleAddressResult +{ + public IList Results { get; set; } + public string Status { get; set; } +} + + +public class GoogleAddress +{ + [JsonPropertyName("formatted_address")] + public string FormatedAddress { get; set; } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs new file mode 100644 index 00000000..d43ec58b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.Google.Settings; + +public class GoogleApiSettings +{ + public string ApiKey { get; set; } + public string Endpoint { get; set; } + public string Language { get; set; } + public string Components { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 44177b09..2b56281c 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -4,9 +4,6 @@ using Microsoft.Extensions.Configuration; using BotSharp.Core.Plugins; using BotSharp.Abstraction.Settings; using BotSharp.Abstraction.Options; -using BotSharp.Abstraction.Messaging; -using System.Text.Json.Serialization; -using Microsoft.Extensions.Options; using BotSharp.Abstraction.Messaging.JsonConverters; namespace BotSharp.Core; diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs index 0434976e..63a9bc92 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs @@ -11,6 +11,8 @@ using Microsoft.Net.Http.Headers; using Microsoft.OpenApi.Models; using Microsoft.IdentityModel.JsonWebTokens; using BotSharp.OpenAPI.BackgroundServices; +using BotSharp.Abstraction.Settings; +using BotSharp.Abstraction.Google.Settings; namespace BotSharp.OpenAPI; @@ -32,6 +34,12 @@ public static class BotSharpOpenApiExtensions services.AddScoped(); services.AddHostedService(); + services.AddScoped(provider => + { + var settingService = provider.GetRequiredService(); + return settingService.Bind("GoogleApi"); + }); + // Add bearer authentication var schema = "MIXED_SCHEME"; var builder = services.AddAuthentication(options => diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs new file mode 100644 index 00000000..5d674b21 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs @@ -0,0 +1,39 @@ +using BotSharp.Abstraction.Google.Models; +using BotSharp.Abstraction.Google.Settings; +using BotSharp.Abstraction.Options; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public class AddressController : ControllerBase +{ + private readonly IServiceProvider _services; + private readonly BotSharpOptions _options; + private readonly IHttpClientFactory _httpClientFactory; + + public AddressController(IServiceProvider services, + IHttpClientFactory httpClientFactory, + BotSharpOptions options) + { + _services = services; + _options = options; + _httpClientFactory = httpClientFactory; + } + + [HttpGet("/address/options")] + public async Task GetAddressOptions([FromQuery] string address) + { + var settings = _services.GetRequiredService(); + using var client = _httpClientFactory.CreateClient(); + var url = $"{settings.Endpoint}?key={settings.ApiKey}&" + + $"components={settings.Components}&" + + $"language={settings.Language}&" + + $"address={address}"; + + var response = await client.GetAsync(url); + var responseStr = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(responseStr, _options.JsonSerializerOptions); + return result; + } +} diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 46340bc3..ed24f1db 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -215,6 +215,13 @@ "ModelVersion": "V3_5" }, + "GoogleApi": { + "Endpoint": "https://maps.googleapis.com/maps/api/geocode/json", + "ApiKey": "", + "Components": "country=US|country=CA", + "Language": "en" + }, + "PluginLoader": { "Assemblies": [ "BotSharp.Core", From 7635886ff9b139b395ae2da4bc980ddf1a534d3e Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Wed, 20 Mar 2024 00:00:29 -0500 Subject: [PATCH 2/7] add try catch --- .../Google/Models/GoogleAddressResult.cs | 2 +- .../Controllers/AddressController.cs | 29 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs b/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs index ec156261..a327102d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleAddressResult.cs @@ -2,7 +2,7 @@ namespace BotSharp.Abstraction.Google.Models; public class GoogleAddressResult { - public IList Results { get; set; } + public IList Results { get; set; } = new List(); public string Status { get; set; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs index 5d674b21..755b8232 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AddressController.cs @@ -11,6 +11,7 @@ public class AddressController : ControllerBase private readonly IServiceProvider _services; private readonly BotSharpOptions _options; private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; public AddressController(IServiceProvider services, IHttpClientFactory httpClientFactory, @@ -24,16 +25,26 @@ public class AddressController : ControllerBase [HttpGet("/address/options")] public async Task GetAddressOptions([FromQuery] string address) { - var settings = _services.GetRequiredService(); - using var client = _httpClientFactory.CreateClient(); - var url = $"{settings.Endpoint}?key={settings.ApiKey}&" + - $"components={settings.Components}&" + - $"language={settings.Language}&" + - $"address={address}"; + var result = new GoogleAddressResult(); + + try + { + var settings = _services.GetRequiredService(); + using var client = _httpClientFactory.CreateClient(); + var url = $"{settings.Endpoint}?key={settings.ApiKey}&" + + $"components={settings.Components}&" + + $"language={settings.Language}&" + + $"address={address}"; + + var response = await client.GetAsync(url); + var responseStr = await response.Content.ReadAsStringAsync(); + result = JsonSerializer.Deserialize(responseStr, _options.JsonSerializerOptions); + } + catch (Exception ex) + { + _logger.LogError($"Error when calling google geocoding api... ${ex.Message}"); + } - var response = await client.GetAsync(url); - var responseStr = await response.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize(responseStr, _options.JsonSerializerOptions); return result; } } From 06090116504d009e4f77577f1771e8aac0b51ef2 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Wed, 20 Mar 2024 10:38:56 -0500 Subject: [PATCH 3/7] RouteToAgentRoutingHandler --- .../Routing/Handlers/RouteToAgentRoutingHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index 462ee076..1b8cb284 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -10,7 +10,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler public List Parameters => new List { - new ParameterPropertyDef("next_action_reason", "the reason why route to this agent, if user is replying last agent's question, you must route to this agent") + new ParameterPropertyDef("next_action_reason", "the reason why route to this agent") { Required = true }, @@ -22,7 +22,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler { Required = true }, - new ParameterPropertyDef("user_goal_agent", "agent who can achieve user original goal") + new ParameterPropertyDef("user_goal_agent", "user original goal") { Required = true }, From e6bcfafd17aaa60002db11414c8ae5f705c83395 Mon Sep 17 00:00:00 2001 From: "C. Oceania" Date: Wed, 20 Mar 2024 10:44:52 -0500 Subject: [PATCH 4/7] Update ChatCompletionProvider.cs --- .../Providers/ChatCompletionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index ba5a393a..cc19b7b1 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -267,7 +267,7 @@ public class ChatCompletionProvider : IChatCompletion var samplingFactor = float.Parse(state.GetState("sampling_factor", "0.0")); chatCompletionsOptions.Temperature = temperature; chatCompletionsOptions.NucleusSamplingFactor = samplingFactor; - chatCompletionsOptions.MaxTokens = int.Parse(state.GetState("max_tokens", "256")); + chatCompletionsOptions.MaxTokens = int.Parse(state.GetState("max_tokens", "1024")); // chatCompletionsOptions.FrequencyPenalty = 0; // chatCompletionsOptions.PresencePenalty = 0; From 94d2b6aaa602f550e936c73e3eb78afe0e682e2d Mon Sep 17 00:00:00 2001 From: "C. Oceania" Date: Wed, 20 Mar 2024 16:13:27 -0500 Subject: [PATCH 5/7] Update instruction.liquid --- .../01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid index 3a313023..990052cf 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid @@ -7,6 +7,11 @@ You're {{router.name}} ({{router.description}}). Follow these steps to handle us 6. Please do not make up any parameters when there is no exact information available, leave it blank. 7. Response must be in JSON format. +[REQUIREMENTS] +{% for requirement in routing_requirements %} +# {{ requirement }} +{% endfor %} + [FUNCTIONS] {% for handler in routing_handlers %} # {{ handler.description}} From 7b26a81a707326092a7015a612fc09dce2030ca3 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Wed, 20 Mar 2024 20:14:05 -0500 Subject: [PATCH 6/7] Add property is_horizontal to rich content. --- .../Models/RichContent/Template/ButtonTemplateMessage.cs | 6 ++++++ .../Models/RichContent/Template/GenericTemplateMessage.cs | 3 +++ .../RichContent/Template/MultiSelectTemplateMessage.cs | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs index fbc2f9d6..4c92a3fb 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs @@ -18,6 +18,9 @@ public class ButtonTemplateMessage : IRichMessage, ITemplateMessage [JsonPropertyName("buttons")] public ButtonElement[] Buttons { get; set; } = new ButtonElement[0]; + + [JsonPropertyName("is_horizontal")] + public bool IsHorizontal { get; set; } } public class ButtonElement @@ -34,4 +37,7 @@ public class ButtonElement public string? Payload { get; set; } public string Title { get; set; } = string.Empty; + + [JsonPropertyName("is_primary")] + public bool IsPrimary { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs index 32d6a4f6..db581f78 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs @@ -16,6 +16,9 @@ public class GenericTemplateMessage : IRichMessage, ITemplateMessage [JsonPropertyName("elements")] public List Elements { get; set; } = new List(); + [JsonPropertyName("is_horizontal")] + public bool IsHorizontal { get; set; } + [JsonPropertyName("element_type")] public string ElementType => typeof(T).Name; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs index b838ba03..5e72f1b1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs @@ -11,7 +11,12 @@ public class MultiSelectTemplateMessage : IRichMessage, ITemplateMessage [JsonPropertyName("template_type")] public string TemplateType => TemplateTypeEnum.MultiSelect; + + [JsonPropertyName("options")] public List Options { get; set; } = new List(); + + [JsonPropertyName("is_horizontal")] + public bool IsHorizontal { get; set; } } public class OptionElement From dccc345bcd828efc665f5a6a9c8c10fd94277269 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Thu, 21 Mar 2024 17:04:45 -0500 Subject: [PATCH 7/7] Check routing rule after poping agent. --- .../Routing/IRoutingService.cs | 2 + .../Routing/Functions/RouteToAgentFn.cs | 99 +---------------- .../BotSharp.Core/Routing/RoutingContext.cs | 27 ++++- .../RoutingService.HasMissingRequiredField.cs | 100 ++++++++++++++++++ .../Routing/RoutingService.InvokeFunction.cs | 6 ++ 5 files changed, 137 insertions(+), 97 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 74845e1f..48daad61 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -43,4 +43,6 @@ public interface IRoutingService Task InstructDirect(Agent agent, RoleDialogModel message); Task GetConversationContent(List dialogs, int maxDialogCount = 50); + + bool HasMissingRequiredField(RoleDialogModel message, out string agentId); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs index 476611d9..d2f478e8 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs @@ -1,13 +1,12 @@ using BotSharp.Abstraction.Functions; using BotSharp.Abstraction.Routing.Models; -using System.Drawing; namespace BotSharp.Core.Routing; /// /// Router calls this function to set the Active Agent according to the context /// -public class RouteToAgentFn : IFunctionCallback +public partial class RouteToAgentFn : IFunctionCallback { public string Name => "route_to_agent"; private readonly IServiceProvider _services; @@ -82,7 +81,8 @@ public class RouteToAgentFn : IFunctionCallback return false; } - var missingfield = HasMissingRequiredField(message, out var agentId); + var routing = _services.GetRequiredService(); + var missingfield = routing.HasMissingRequiredField(message, out var agentId); if (missingfield && message.CurrentAgentId != agentId) { // Stack redirection agent @@ -94,97 +94,4 @@ public class RouteToAgentFn : IFunctionCallback return true; } - - /// - /// If the target agent needs some required fields but the - /// - /// - private bool HasMissingRequiredField(RoleDialogModel message, out string agentId) - { - var args = JsonSerializer.Deserialize(message.FunctionArgs); - var routing = _services.GetRequiredService(); - - var routingRules = routing.GetRulesByAgentName(args.AgentName); - - if (routingRules == null || !routingRules.Any()) - { - agentId = message.CurrentAgentId; - return false; - } - - agentId = routingRules.First().AgentId; - // Add routed agent - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "route_to", agentId); - - // Check required fields - var root = JsonSerializer.Deserialize(message.FunctionArgs); - var missingFields = new List(); - foreach (var field in routingRules.Where(x => x.Required).Select(x => x.Field)) - { - if (!root.EnumerateObject().Any(x => x.Name == field)) - { - missingFields.Add(field); - } - else if (root.EnumerateObject().Any(x => x.Name == field) && - string.IsNullOrEmpty(root.EnumerateObject().FirstOrDefault(x => x.Name == field).Value.ToString())) - { - missingFields.Add(field); - } - } - - // Check if states contains the field according conversation context. - var states = _services.GetRequiredService(); - foreach (var field in missingFields.ToList()) - { - if (!string.IsNullOrEmpty(states.GetState(field))) - { - var value = states.GetState(field); - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, field, value); - missingFields.Remove(field); - } - } - - if (missingFields.Any()) - { - // Add field to args - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "missing_fields", missingFields); - message.Content = $"missing some information: {string.Join(", ", missingFields)}"; - - // Handle redirect - var routingRule = routingRules.FirstOrDefault(x => missingFields.Contains(x.Field)); - if (!string.IsNullOrEmpty(routingRule.RedirectTo)) - { - var db = _services.GetRequiredService(); - var record = db.GetAgent(routingRule.RedirectTo); - - // Add redirected agent - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "redirect_to", record.Name); - agentId = routingRule.RedirectTo; - var logger = _services.GetRequiredService>(); -#if DEBUG - Console.WriteLine($"*** Routing redirect to {record.Name.ToUpper()} ***", Color.Yellow); -#else - logger.LogInformation($"*** Routing redirect to {record.Name.ToUpper()} ***"); -#endif - } - else - { - // back to router - agentId = message.CurrentAgentId; - } - } - - return missingFields.Any(); - } - - private string AppendPropertyToArgs(string args, string key, string value) - { - return args.Substring(0, args.Length - 1) + $", \"{key}\": \"{value}\"" + "}"; - } - - private string AppendPropertyToArgs(string args, string key, IEnumerable values) - { - string fields = string.Join(",", values.Select(x => $"\"{x}\"")); - return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}"; - } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index 96cd95e6..e9abdc04 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -105,6 +105,31 @@ public class RoutingContext : IRoutingContext HookEmitter.Emit(_services, async hook => await hook.OnAgentDequeued(agentId, currentAgentId, reason: reason) ).Wait(); + + // Run the routing rule + var agency = _services.GetRequiredService(); + var agent = agency.LoadAgent(currentAgentId).Result; + + var message = new RoleDialogModel(AgentRole.User, $"Try to route to agent {agent.Name}") + { + FunctionName = "route_to_agent", + FunctionArgs = JsonSerializer.Serialize(new FunctionCallFromLlm + { + Function = "route_to_agent", + AgentName = agent.Name, + Reason = $"User manually route to agent {agent.Name}" + }) + }; + + var routing = _services.GetRequiredService(); + var missingfield = routing.HasMissingRequiredField(message, out agentId); + if (missingfield) + { + if (currentAgentId != agentId) + { + _stack.Push(agentId); + } + } } public string PreviousAgentId() @@ -115,7 +140,7 @@ public class RoutingContext : IRoutingContext } else if (_stack.Count > 1) { - return _stack.ToArray()[1]; + return _stack.ToArray()[_stack.Count - 2]; } return string.Empty; diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs new file mode 100644 index 00000000..20d75a95 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs @@ -0,0 +1,100 @@ +using BotSharp.Abstraction.Routing.Models; +using System.Drawing; + +namespace BotSharp.Core.Routing; + +public partial class RoutingService +{ + /// + /// If the target agent needs some required fields but the + /// + /// + public bool HasMissingRequiredField(RoleDialogModel message, out string agentId) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + var routing = _services.GetRequiredService(); + + var routingRules = routing.GetRulesByAgentName(args.AgentName); + + if (routingRules == null || !routingRules.Any()) + { + agentId = message.CurrentAgentId; + return false; + } + + agentId = routingRules.First().AgentId; + // Add routed agent + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "route_to", agentId); + + // Check required fields + var root = JsonSerializer.Deserialize(message.FunctionArgs); + var missingFields = new List(); + foreach (var field in routingRules.Where(x => x.Required).Select(x => x.Field)) + { + if (!root.EnumerateObject().Any(x => x.Name == field)) + { + missingFields.Add(field); + } + else if (root.EnumerateObject().Any(x => x.Name == field) && + string.IsNullOrEmpty(root.EnumerateObject().FirstOrDefault(x => x.Name == field).Value.ToString())) + { + missingFields.Add(field); + } + } + + // Check if states contains the field according conversation context. + var states = _services.GetRequiredService(); + foreach (var field in missingFields.ToList()) + { + if (!string.IsNullOrEmpty(states.GetState(field))) + { + var value = states.GetState(field); + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, field, value); + missingFields.Remove(field); + } + } + + if (missingFields.Any()) + { + // Add field to args + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "missing_fields", missingFields); + message.Content = $"missing some information: {string.Join(", ", missingFields)}"; + + // Handle redirect + var routingRule = routingRules.FirstOrDefault(x => missingFields.Contains(x.Field)); + if (!string.IsNullOrEmpty(routingRule.RedirectTo)) + { + var db = _services.GetRequiredService(); + var record = db.GetAgent(routingRule.RedirectTo); + + // Add redirected agent + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "redirect_to", record.Name); + agentId = routingRule.RedirectTo; + var logger = _services.GetRequiredService>(); +#if DEBUG + Console.WriteLine($"*** Routing redirect to {record.Name.ToUpper()} ***", Color.Yellow); +#else + logger.LogInformation($"*** Routing redirect to {record.Name.ToUpper()} ***"); +#endif + } + else + { + // back to router + agentId = message.CurrentAgentId; + } + } + + return missingFields.Any(); + } + + private string AppendPropertyToArgs(string args, string key, string value) + { + return args.Substring(0, args.Length - 1) + $", \"{key}\": \"{value}\"" + "}"; + } + + private string AppendPropertyToArgs(string args, string key, IEnumerable values) + { + string fields = string.Join(",", values.Select(x => $"\"{x}\"")); + return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}"; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 77659d69..690308eb 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -36,6 +36,12 @@ public partial class RoutingService { result = await function.Execute(message); } + catch (JsonException ex) + { + _logger.LogError($"The input does not contain any JSON tokens:\r\n{message.Content}"); + message.StopCompletion = true; + message.Content = ex.Message; + } catch (Exception ex) { message.StopCompletion = true;