Merge branch 'master' of https://github.com/hchen2020/BotSharp
This commit is contained in:
commit
80995f54df
|
|
@ -0,0 +1,14 @@
|
|||
namespace BotSharp.Abstraction.Google.Models;
|
||||
|
||||
public class GoogleAddressResult
|
||||
{
|
||||
public IList<GoogleAddress> Results { get; set; } = new List<GoogleAddress>();
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class GoogleAddress
|
||||
{
|
||||
[JsonPropertyName("formatted_address")]
|
||||
public string FormatedAddress { get; set; }
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ public class GenericTemplateMessage<T> : IRichMessage, ITemplateMessage
|
|||
[JsonPropertyName("elements")]
|
||||
public List<T> Elements { get; set; } = new List<T>();
|
||||
|
||||
[JsonPropertyName("is_horizontal")]
|
||||
public bool IsHorizontal { get; set; }
|
||||
|
||||
[JsonPropertyName("element_type")]
|
||||
public string ElementType => typeof(T).Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,12 @@ public class MultiSelectTemplateMessage : IRichMessage, ITemplateMessage
|
|||
|
||||
[JsonPropertyName("template_type")]
|
||||
public string TemplateType => TemplateTypeEnum.MultiSelect;
|
||||
|
||||
[JsonPropertyName("options")]
|
||||
public List<OptionElement> Options { get; set; } = new List<OptionElement>();
|
||||
|
||||
[JsonPropertyName("is_horizontal")]
|
||||
public bool IsHorizontal { get; set; }
|
||||
}
|
||||
|
||||
public class OptionElement
|
||||
|
|
|
|||
|
|
@ -43,4 +43,6 @@ public interface IRoutingService
|
|||
Task<RoleDialogModel> InstructDirect(Agent agent, RoleDialogModel message);
|
||||
|
||||
Task<string> GetConversationContent(List<RoleDialogModel> dialogs, int maxDialogCount = 50);
|
||||
|
||||
bool HasMissingRequiredField(RoleDialogModel message, out string agentId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// Router calls this function to set the Active Agent according to the context
|
||||
/// </summary>
|
||||
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<IRoutingService>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the target agent needs some required fields but the
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool HasMissingRequiredField(RoleDialogModel message, out string agentId)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
|
||||
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<JsonElement>(message.FunctionArgs);
|
||||
var missingFields = new List<string>();
|
||||
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<IConversationStateService>();
|
||||
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<IBotSharpRepository>();
|
||||
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<ILogger<RouteToAgentFn>>();
|
||||
#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<string> values)
|
||||
{
|
||||
string fields = string.Join(",", values.Select(x => $"\"{x}\""));
|
||||
return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -105,6 +105,31 @@ public class RoutingContext : IRoutingContext
|
|||
HookEmitter.Emit<IRoutingHook>(_services, async hook =>
|
||||
await hook.OnAgentDequeued(agentId, currentAgentId, reason: reason)
|
||||
).Wait();
|
||||
|
||||
// Run the routing rule
|
||||
var agency = _services.GetRequiredService<IAgentService>();
|
||||
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<IRoutingService>();
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
public partial class RoutingService
|
||||
{
|
||||
/// <summary>
|
||||
/// If the target agent needs some required fields but the
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool HasMissingRequiredField(RoleDialogModel message, out string agentId)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
|
||||
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<JsonElement>(message.FunctionArgs);
|
||||
var missingFields = new List<string>();
|
||||
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<IConversationStateService>();
|
||||
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<IBotSharpRepository>();
|
||||
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<ILogger<RouteToAgentFn>>();
|
||||
#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<string> values)
|
||||
{
|
||||
string fields = string.Join(",", values.Select(x => $"\"{x}\""));
|
||||
return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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<IUserIdentity, UserIdentity>();
|
||||
services.AddHostedService<ConversationTimeoutService>();
|
||||
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
var settingService = provider.GetRequiredService<ISettingService>();
|
||||
return settingService.Bind<GoogleApiSettings>("GoogleApi");
|
||||
});
|
||||
|
||||
// Add bearer authentication
|
||||
var schema = "MIXED_SCHEME";
|
||||
var builder = services.AddAuthentication(options =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
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;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public AddressController(IServiceProvider services,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
BotSharpOptions options)
|
||||
{
|
||||
_services = services;
|
||||
_options = options;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
[HttpGet("/address/options")]
|
||||
public async Task<GoogleAddressResult> GetAddressOptions([FromQuery] string address)
|
||||
{
|
||||
var result = new GoogleAddressResult();
|
||||
|
||||
try
|
||||
{
|
||||
var settings = _services.GetRequiredService<GoogleApiSettings>();
|
||||
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<GoogleAddressResult>(responseStr, _options.JsonSerializerOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when calling google geocoding api... ${ex.Message}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue