From fee89ffb967e9fc1221e313e11b004610609a94d Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sun, 24 Mar 2024 12:33:41 -0500 Subject: [PATCH] Conversation rate limit. --- .../Conversations/Models/Conversation.cs | 5 ++ .../Settings/ConversationSetting.cs | 1 + .../Settings/RateLimitSetting.cs | 8 +++ .../Routing/IRoutingService.cs | 2 +- .../ConversationService.SendMessage.cs | 5 +- .../Routing/Functions/FallbackToRouterFn.cs | 12 ++-- .../BotSharp.Core/Routing/RoutingService.cs | 5 +- .../BotSharpLoggerExtensions.cs | 1 + .../Hooks/RateLimitConversationHook.cs | 65 +++++++++++++++++++ src/WebStarter/appsettings.json | 5 ++ 10 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs create mode 100644 src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs index cf5e3fd3..4ef8792b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs @@ -51,6 +51,11 @@ public class DialogElement Content = content; RichContent = richContent; } + + public override string ToString() + { + return $"{MetaData.Role}: {Content} [{MetaData.CreateTime}]"; + } } public class DialogMetaData diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs index 8f9e7a0f..76726ab6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs @@ -12,6 +12,7 @@ public class ConversationSetting public bool EnableContentLog { get; set; } public bool EnableStateLog { get; set; } public CleanConversationSetting CleanSetting { get; set; } + public RateLimitSetting RateLimit { get; set; } } public class CleanConversationSetting diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs new file mode 100644 index 00000000..0c9a7436 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Abstraction.Conversations.Settings; + +public class RateLimitSetting +{ + public int MaxConversationPerDay { get; set; } = 100; + public int MaxInputLengthPerRequest { get; set; } = 256; + public int MinTimeSecondsBetweenMessages { get; set; } = 2; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 48daad61..da84ffa3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -32,7 +32,7 @@ public interface IRoutingService void ResetRecursiveCounter(); Task InvokeAgent(string agentId, List dialogs); Task InvokeFunction(string name, RoleDialogModel message); - Task InstructLoop(RoleDialogModel message); + Task InstructLoop(RoleDialogModel message, List dialogs); /// /// Talk to a specific Agent directly, bypassing the Router diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 16f93e32..e18f5337 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -34,6 +34,9 @@ public partial class ConversationService _storage.Append(_conversationId, message); + var conv = _services.GetRequiredService(); + var dialogs = conv.GetDialogHistory(); + var statistics = _services.GetRequiredService(); var hooks = _services.GetServices().ToList(); @@ -75,7 +78,7 @@ public partial class ConversationService var settings = _services.GetRequiredService(); response = agent.Type == AgentType.Routing ? - await routing.InstructLoop(message) : + await routing.InstructLoop(message, dialogs) : await routing.InstructDirect(agent, message); routing.ResetRecursiveCounter(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs index 4d51113f..59685719 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs @@ -1,7 +1,5 @@ using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Routing; namespace BotSharp.Core.Routing.Functions; @@ -29,12 +27,14 @@ public class FallbackToRouterFn : IFunctionCallback return false; } - var routing = _services.GetRequiredService(); - routing.Replace(targetAgent.Id); + var conv = _services.GetRequiredService(); + var dialogs = conv.GetDialogHistory(); - var router = _services.GetRequiredService(); + var routing = _services.GetRequiredService(); + routing.Context.Replace(targetAgent.Id); message.CurrentAgentId = targetAgent.Id; - var response = await router.InstructLoop(message); + + var response = await routing.InstructLoop(message, dialogs); message.Content = response.Content; message.StopCompletion = true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 622926e7..4a17f67a 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -69,7 +69,7 @@ public partial class RoutingService : IRoutingService return response; } - public async Task InstructLoop(RoleDialogModel message) + public async Task InstructLoop(RoleDialogModel message, List dialogs) { var agentService = _services.GetRequiredService(); _router = await agentService.LoadAgent(message.CurrentAgentId); @@ -77,9 +77,6 @@ public partial class RoutingService : IRoutingService RoleDialogModel response = default; var states = _services.GetRequiredService(); - var conv = _services.GetRequiredService(); - var dialogs = conv.GetDialogHistory(); - var executor = _services.GetRequiredService(); var planner = GetPlanner(_router); diff --git a/src/Infrastructure/BotSharp.Logger/BotSharpLoggerExtensions.cs b/src/Infrastructure/BotSharp.Logger/BotSharpLoggerExtensions.cs index c57afb5f..dfc9c27d 100644 --- a/src/Infrastructure/BotSharp.Logger/BotSharpLoggerExtensions.cs +++ b/src/Infrastructure/BotSharp.Logger/BotSharpLoggerExtensions.cs @@ -13,6 +13,7 @@ public static class BotSharpLoggerExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } } diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs new file mode 100644 index 00000000..c745f5d4 --- /dev/null +++ b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs @@ -0,0 +1,65 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Users; + +namespace BotSharp.Logger.Hooks; + +/// +/// To prevent users from overusing, if the character limit is exceeded or the sending frequency is too fast, +/// a prompt message will be returned. +/// +public class RateLimitConversationHook : ConversationHookBase +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + public RateLimitConversationHook(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public override async Task OnMessageReceived(RoleDialogModel message) + { + var settings = _services.GetRequiredService(); + var rateLimit = settings.RateLimit; + + // Check max input length + var charCount = message.Content.Length; + if (charCount > rateLimit.MaxInputLengthPerRequest) + { + message.Content = $"The number of characters in your message exceeds the system maximum of {rateLimit.MaxInputLengthPerRequest}"; + message.StopCompletion = true; + return; + } + + // Check message sending frequency + var userSents = _dialogs.Where(x => x.Role == AgentRole.User) + .TakeLast(2).ToList(); + + if (userSents.Count > 1) + { + var seconds = (DateTime.UtcNow - userSents.First().CreatedAt).TotalSeconds; + if (seconds < rateLimit.MinTimeSecondsBetweenMessages) + { + message.Content = "Your message sending frequency exceeds the frequency specified by the system. Please try again later."; + message.StopCompletion = true; + return; + } + } + + // Check the number of conversations + var user = _services.GetRequiredService(); + var convService = _services.GetRequiredService(); + var results = await convService.GetConversations(new ConversationFilter + { + UserId = user.Id + }); + + if (results.Count > rateLimit.MaxConversationPerDay) + { + message.Content = $"The number of conversations you have exceeds the system maximum of {rateLimit.MaxConversationPerDay}"; + message.StopCompletion = true; + return; + } + } +} diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 356279b3..0db89cbd 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -125,6 +125,11 @@ "BatchSize": 50, "MessageLimit": 2, "BufferHours": 12 + }, + "RateLimit": { + "MaxConversationPerDay": 100, + "MaxInputLengthPerRequest": 256, + "MinTimeSecondsBetweenMessages": 2 } },