From 6dd892ac2eb52e6acdeeebd9fee132efe687307e Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Thu, 9 Jan 2025 09:54:11 -0600 Subject: [PATCH 01/14] Remove MaxConversationPerDay for database channel --- .../BotSharp.Logger/Hooks/RateLimitConversationHook.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs index a9a9478f..9b71214d 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs @@ -52,7 +52,7 @@ public class RateLimitConversationHook : ConversationHookBase var channel = states.GetState("channel"); // Check the number of conversations - if (channel != ConversationChannel.Phone && channel != ConversationChannel.Email) + if (channel != ConversationChannel.Phone && channel != ConversationChannel.Email && channel != ConversationChannel.Database) { var user = _services.GetRequiredService(); var convService = _services.GetRequiredService(); From a6ed69c355f4005d943edf25ef0e74688a118fee Mon Sep 17 00:00:00 2001 From: Haiping Chen <101423@smsassist.com> Date: Thu, 9 Jan 2025 16:30:25 -0600 Subject: [PATCH 02/14] Generate Indication from LLM --- .../Conversations/Models/RoleDialogModel.cs | 1 + .../BotSharp.Abstraction/Functions/IFunctionCallback.cs | 2 +- .../Agents/Services/AgentService.LoadAgent.cs | 2 +- .../Agents/Services/AgentService.Rendering.cs | 2 +- .../BotSharp.Core/Routing/RoutingService.InvokeAgent.cs | 1 + src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs | 2 +- .../BotSharp.Plugin.AnthropicAI.csproj | 2 +- .../Providers/ChatCompletionProvider.cs | 7 ++++--- .../BotSharp.Plugin.AzureOpenAI.csproj | 2 +- .../Providers/Chat/ChatCompletionProvider.cs | 7 +++++-- .../BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs | 3 ++- .../Providers/Chat/GeminiChatCompletionProvider.cs | 6 ++++-- .../BotSharp.Plugin.OpenAI/BotSharp.Plugin.OpenAI.csproj | 2 +- .../Providers/Chat/ChatCompletionProvider.cs | 7 +++++-- 14 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 5a67216d..585112a2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -146,6 +146,7 @@ public class RoleDialogModel : ITrackableMessage FunctionArgs = source.FunctionArgs, FunctionName = source.FunctionName, ToolCallId = source.ToolCallId, + Indication = source.Indication, PostbackFunctionName = source.PostbackFunctionName, RichContent = source.RichContent, Payload = source.Payload, diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs index 40577799..b842b5b6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs @@ -9,7 +9,7 @@ public interface IFunctionCallback /// string Indication => string.Empty; - Task GetIndication(RoleDialogModel message) => Task.FromResult(Indication); + Task GetIndication(RoleDialogModel message) => Task.FromResult(message.Indication ?? Indication); Task Execute(RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 9e6ad8ee..6c57168b 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -7,7 +7,7 @@ public partial class AgentService { public static ConcurrentDictionary> AgentParameterTypes = new(); - [MemoryCache(10 * 60, perInstanceCache: true)] + // [MemoryCache(10 * 60, perInstanceCache: true)] public async Task LoadAgent(string id, bool loadUtility = true) { if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs index d217d2c1..87da12b2 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs @@ -111,7 +111,7 @@ public partial class AgentService parameterDef.Properties = JsonSerializer.Deserialize(clonedRoot.ToString()); parameterDef.Required = required; - return parameterDef; ; + return parameterDef; } public string RenderedTemplate(Agent agent, string templateName) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs index 39c8ed23..fbe8c055 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs @@ -43,6 +43,7 @@ public partial class RoutingService message.ToolCallId = response.ToolCallId; message.FunctionName = response.FunctionName; message.FunctionArgs = response.FunctionArgs; + message.Indication = response.Indication; message.CurrentAgentId = agent.Id; await InvokeFunction(message, dialogs); diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs index afb8dd01..9740bb20 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/VerboseLogHook.cs @@ -41,7 +41,7 @@ public class VerboseLogHook : IContentGeneratingHook var agent = await agentService.LoadAgent(message.CurrentAgentId); var log = message.Role == AgentRole.Function ? - $"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs})" : + $"[{agent?.Name}]: {message.Indication} {message.FunctionName}({message.FunctionArgs})" : $"[{agent?.Name}]: {message.Content}" + $" <== [msg_id: {message.MessageId}]"; _logger.LogInformation(tokenStats.Prompt); diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj index d053f4d6..0dc416df 100644 --- a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj +++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs index 45ef200e..6ad62852 100644 --- a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs @@ -1,7 +1,6 @@ using Anthropic.SDK.Common; using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.MLTasks.Settings; -using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -48,6 +47,7 @@ public class ChatCompletionProvider : IChatCompletion if (response.StopReason == "tool_use") { + var content = response.Content.OfType().FirstOrDefault(); var toolResult = response.Content.OfType().First(); responseMessage = new RoleDialogModel(AgentRole.Function, response.FirstMessage?.Text ?? string.Empty) @@ -56,7 +56,8 @@ public class ChatCompletionProvider : IChatCompletion MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, ToolCallId = toolResult.Id, FunctionName = toolResult.Name, - FunctionArgs = JsonSerializer.Serialize(toolResult.Input) + FunctionArgs = JsonSerializer.Serialize(toolResult.Input), + Indication = content.Text }; } else @@ -161,7 +162,7 @@ public class ChatCompletionProvider : IChatCompletion new ToolResultContent() { ToolUseId = conv.ToolCallId, - Content = conv.Content + Content = [new TextContent() { Text = conv.Content }] } } }); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj b/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj index 06fa6a3c..284e9b3c 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs index bfce9d98..fe23a465 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -58,7 +58,8 @@ public class ChatCompletionProvider : IChatCompletion CurrentAgentId = agent.Id, MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString() + FunctionArgs = toolCall?.FunctionArguments?.ToString(), + Indication = value.Content.FirstOrDefault()?.Text }; // Somethings LLM will generate a function name with agent name. @@ -160,8 +161,10 @@ public class ChatCompletionProvider : IChatCompletion var funcContextIn = new RoleDialogModel(AgentRole.Function, text) { CurrentAgentId = agent.Id, + MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString() + FunctionArgs = toolCall?.FunctionArguments?.ToString(), + Indication = value.Content.FirstOrDefault()?.Text }; // Somethings LLM will generate a function name with agent name. diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 5b699b51..aecce913 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -116,7 +116,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var agent = await _agentService.LoadAgent(message.CurrentAgentId); message.FunctionArgs = message.FunctionArgs ?? "{}"; var args = message.FunctionArgs.FormatJson(); - var log = $"{message.FunctionName} executing\r\n```json\r\n{args}\r\n```"; + var log = $"*{message.Indication.Replace("\r", string.Empty).Replace("\n", string.Empty)}* \r\n\r\n **{message.FunctionName}**()"; + log += args.Length > 5 ? $" \r\n```json\r\n{args}\r\n```" : string.Empty; var input = new ContentLogInputModel(conversationId, message) { diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs index b52013dd..11ea8f18 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Loggers; +using Google.Protobuf.WellKnownTypes; using Microsoft.Extensions.Logging; using Mscc.GenerativeAI; @@ -125,6 +126,7 @@ public class GeminiChatCompletionProvider : IChatCompletion if (!agentService.RenderFunction(agent, function)) continue; var def = agentService.RenderFunctionProperty(agent, function); + var str = JsonSerializer.Serialize(def.Properties); funcDeclarations.Add(new FunctionDeclaration { @@ -132,8 +134,8 @@ public class GeminiChatCompletionProvider : IChatCompletion Description = function.Description, Parameters = new() { - Type = ParameterType.Object, - Properties = def.Properties, + Type = str != "{}" ? ParameterType.Object : ParameterType.TypeUnspecified, + Properties = str != "{}" ? JsonSerializer.Deserialize(str) : null, Required = def.Required } }); diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/BotSharp.Plugin.OpenAI.csproj b/src/Plugins/BotSharp.Plugin.OpenAI/BotSharp.Plugin.OpenAI.csproj index e1138d92..4b7792d2 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/BotSharp.Plugin.OpenAI.csproj +++ b/src/Plugins/BotSharp.Plugin.OpenAI/BotSharp.Plugin.OpenAI.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 6f424c33..86de222c 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -55,7 +55,8 @@ public class ChatCompletionProvider : IChatCompletion MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, ToolCallId = toolCall?.Id, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString() + FunctionArgs = toolCall?.FunctionArguments?.ToString(), + Indication = value.Content.FirstOrDefault()?.Text }; // Somethings LLM will generate a function name with agent name. @@ -138,9 +139,11 @@ public class ChatCompletionProvider : IChatCompletion var funcContextIn = new RoleDialogModel(AgentRole.Function, text) { CurrentAgentId = agent.Id, + MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, ToolCallId = toolCall?.Id, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString() + FunctionArgs = toolCall?.FunctionArguments?.ToString(), + Indication = value.Content.FirstOrDefault()?.Text }; // Somethings LLM will generate a function name with agent name. From fb58ac3c4679443de12cef4468190d04ef216a18 Mon Sep 17 00:00:00 2001 From: Haiping Chen <101423@smsassist.com> Date: Thu, 9 Jan 2025 16:34:21 -0600 Subject: [PATCH 03/14] Undo LoadAgent cache --- .../BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 6c57168b..9e6ad8ee 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -7,7 +7,7 @@ public partial class AgentService { public static ConcurrentDictionary> AgentParameterTypes = new(); - // [MemoryCache(10 * 60, perInstanceCache: true)] + [MemoryCache(10 * 60, perInstanceCache: true)] public async Task LoadAgent(string id, bool loadUtility = true) { if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) From 48d2e43e3889fe045c2019708c008b40fad65bdb Mon Sep 17 00:00:00 2001 From: Haiping Chen <101423@smsassist.com> Date: Mon, 13 Jan 2025 11:45:40 -0600 Subject: [PATCH 04/14] undo indication set. --- .../Providers/ChatCompletionProvider.cs | 3 +-- .../Providers/Chat/ChatCompletionProvider.cs | 6 ++---- .../Providers/Chat/ChatCompletionProvider.cs | 6 ++---- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs index 6ad62852..8657b31b 100644 --- a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs @@ -50,14 +50,13 @@ public class ChatCompletionProvider : IChatCompletion var content = response.Content.OfType().FirstOrDefault(); var toolResult = response.Content.OfType().First(); - responseMessage = new RoleDialogModel(AgentRole.Function, response.FirstMessage?.Text ?? string.Empty) + responseMessage = new RoleDialogModel(AgentRole.Function, content?.Text ?? string.Empty) { CurrentAgentId = agent.Id, MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, ToolCallId = toolResult.Id, FunctionName = toolResult.Name, FunctionArgs = JsonSerializer.Serialize(toolResult.Input), - Indication = content.Text }; } else diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs index fe23a465..9f1f692f 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -58,8 +58,7 @@ public class ChatCompletionProvider : IChatCompletion CurrentAgentId = agent.Id, MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString(), - Indication = value.Content.FirstOrDefault()?.Text + FunctionArgs = toolCall?.FunctionArguments?.ToString() }; // Somethings LLM will generate a function name with agent name. @@ -163,8 +162,7 @@ public class ChatCompletionProvider : IChatCompletion CurrentAgentId = agent.Id, MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString(), - Indication = value.Content.FirstOrDefault()?.Text + FunctionArgs = toolCall?.FunctionArguments?.ToString() }; // Somethings LLM will generate a function name with agent name. diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 86de222c..21756f5d 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -55,8 +55,7 @@ public class ChatCompletionProvider : IChatCompletion MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, ToolCallId = toolCall?.Id, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString(), - Indication = value.Content.FirstOrDefault()?.Text + FunctionArgs = toolCall?.FunctionArguments?.ToString() }; // Somethings LLM will generate a function name with agent name. @@ -142,8 +141,7 @@ public class ChatCompletionProvider : IChatCompletion MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, ToolCallId = toolCall?.Id, FunctionName = toolCall?.FunctionName, - FunctionArgs = toolCall?.FunctionArguments?.ToString(), - Indication = value.Content.FirstOrDefault()?.Text + FunctionArgs = toolCall?.FunctionArguments?.ToString() }; // Somethings LLM will generate a function name with agent name. From 40b047c2d29f6c73453c3c350cb9813d87f5532b Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 13 Jan 2025 17:28:34 -0600 Subject: [PATCH 05/14] append list --- .../Functions/HandleOutboundPhoneCallFn.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs index c371bec2..c9a6c923 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs @@ -64,13 +64,16 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions Channel = ConversationChannel.Phone }); var conversationId = newConv.Id; - convStorage.Append(conversationId, new RoleDialogModel(AgentRole.User, "Hi, I'm calling to check my work order quote status, please help me locate my work order number and let me know what to do next.") + convStorage.Append(conversationId, new List { - CurrentAgentId = entryAgentId - }); - convStorage.Append(conversationId, new RoleDialogModel(AgentRole.Assistant, args.InitialMessage) - { - CurrentAgentId = entryAgentId + new RoleDialogModel(AgentRole.User, "Hi, I'm calling to check my work order quote status, please help me locate my work order number and let me know what to do next.") + { + CurrentAgentId = entryAgentId + }, + new RoleDialogModel(AgentRole.Assistant, args.InitialMessage) + { + CurrentAgentId = entryAgentId + } }); // Generate audio From c1d64314a148dff07e1fc3a91ecd42f8a4bee163 Mon Sep 17 00:00:00 2001 From: Bo Yin <103488@smsassist.com> Date: Tue, 14 Jan 2025 09:28:18 -0600 Subject: [PATCH 06/14] enable answering machine detection --- .../Controllers/TwilioVoiceController.cs | 5 ++++- src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs | 3 +++ .../Functions/HandleOutboundPhoneCallFn.cs | 3 ++- .../Services/TwilioMessageQueueService.cs | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index ef24298d..7b5f9365 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -126,9 +126,11 @@ public class TwilioVoiceController : TwilioController SeqNumber = request.SeqNum, Content = messageContent, Digits = request.Digits, - From = request.From, + From = string.Equals(request.Direction, "inbound") ? request.From : request.To, States = ParseStates(request.States) }; + callerMessage.RequestHeaders = new KeyValuePair[Request.Headers.Count]; + Request.Headers.CopyTo(callerMessage.RequestHeaders, 0); await messageQueue.EnqueueAsync(callerMessage); response = new VoiceResponse(); @@ -387,6 +389,7 @@ public class TwilioVoiceController : TwilioController $"twilio/voice/speeches/{conversationId}/intial.mp3" } }; + string tag = Request.Form["AnsweredBy"]; var twilio = _services.GetRequiredService(); var response = twilio.ReturnNoninterruptedInstructions(instruction); return TwiML(response); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs b/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs index c74addd0..4b9c6a84 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Primitives; + namespace BotSharp.Plugin.Twilio.Models { public class CallerMessage @@ -8,6 +10,7 @@ namespace BotSharp.Plugin.Twilio.Models public string Digits { get; set; } public string From { get; set; } public Dictionary States { get; set; } = new(); + public KeyValuePair[] RequestHeaders { get; set; } public override string ToString() { diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs index c9a6c923..bdc05a61 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs @@ -92,7 +92,8 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions var call = await CallResource.CreateAsync( url: new Uri($"{_twilioSetting.CallbackHost}/twilio/voice/init-call?conversationId={conversationId}"), to: new PhoneNumber(args.PhoneNumber), - from: new PhoneNumber(_twilioSetting.PhoneNumber)); + from: new PhoneNumber(_twilioSetting.PhoneNumber), + machineDetection: "DetectMessageEnd"); message.Content = $"The generated phone message: {args.InitialMessage}. \r\n[Conversation ID: {conversationId}]" ?? message.Content; message.StopCompletion = true; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index 0260320f..5d726095 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -65,6 +65,10 @@ namespace BotSharp.Plugin.Twilio.Services var httpContext = sp.GetRequiredService(); httpContext.HttpContext = new DefaultHttpContext(); httpContext.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); + foreach (var header in message.RequestHeaders) + { + httpContext.HttpContext.Request.Headers[header.Key] = header.Value; + } httpContext.HttpContext.Request.Headers["X-Twilio-BotSharp"] = "LOST"; AssistantMessage reply = null; From 065601ff2ee2634e7906646a02e902c10d9933e4 Mon Sep 17 00:00:00 2001 From: Bo Yin <103488@smsassist.com> Date: Tue, 14 Jan 2025 10:45:54 -0600 Subject: [PATCH 07/14] add answeredby tag --- .../Controllers/TwilioVoiceController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index 7b5f9365..199e1e34 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Files; using BotSharp.Abstraction.Infrastructures; +using BotSharp.Abstraction.Repositories; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; @@ -389,7 +390,9 @@ public class TwilioVoiceController : TwilioController $"twilio/voice/speeches/{conversationId}/intial.mp3" } }; - string tag = Request.Form["AnsweredBy"]; + string tag = $"AnsweredBy: {Request.Form["AnsweredBy"]}"; + var db = _services.GetRequiredService(); + db.AppendConversationTags(conversationId, new List { tag }); var twilio = _services.GetRequiredService(); var response = twilio.ReturnNoninterruptedInstructions(instruction); return TwiML(response); From 87945eff694d73e9c5033ee3e9f3ee2e55c9ba40 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 14 Jan 2025 11:11:42 -0600 Subject: [PATCH 08/14] Enhanced PhoneCall model --- .../Controllers/TwilioVoiceController.cs | 2 +- .../BotSharp.Plugin.Twilio/Services/TwilioService.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index 199e1e34..9d96bf3c 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -390,7 +390,7 @@ public class TwilioVoiceController : TwilioController $"twilio/voice/speeches/{conversationId}/intial.mp3" } }; - string tag = $"AnsweredBy: {Request.Form["AnsweredBy"]}"; + string tag = $"twilio:{Request.Form["AnsweredBy"]}"; var db = _services.GetRequiredService(); db.AppendConversationTags(conversationId, new List { tag }); var twilio = _services.GetRequiredService(); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index 3b29864f..5d407b23 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -59,7 +59,10 @@ public class TwilioService Gather.InputEnum.Speech, Gather.InputEnum.Dtmf }, - Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{twilioSetting.AgentId}") + Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{twilioSetting.AgentId}"), + Enhanced = true, + SpeechModel = Gather.SpeechModelEnum.PhoneCall, + SpeechTimeout = "auto" }; gather.Say(message); @@ -78,6 +81,7 @@ public class TwilioService Gather.InputEnum.Dtmf }, Action = new Uri($"{_settings.CallbackHost}/{conversationalVoiceResponse.CallbackPath}"), + Enhanced = true, SpeechModel = Gather.SpeechModelEnum.PhoneCall, SpeechTimeout = "auto", // timeout > 0 ? timeout.ToString() : "3", Timeout = conversationalVoiceResponse.Timeout > 0 ? conversationalVoiceResponse.Timeout : 3, @@ -115,6 +119,7 @@ public class TwilioService Gather.InputEnum.Dtmf }, Action = new Uri($"{_settings.CallbackHost}/{conversationalVoiceResponse.CallbackPath}"), + Enhanced = true, SpeechModel = Gather.SpeechModelEnum.PhoneCall, SpeechTimeout = "auto", // conversationalVoiceResponse.Timeout > 0 ? conversationalVoiceResponse.Timeout.ToString() : "3", Timeout = conversationalVoiceResponse.Timeout > 0 ? conversationalVoiceResponse.Timeout : 3, From f098ce1bc3247102e5a238a3f3935cdba4c5f442 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 14 Jan 2025 11:42:58 -0600 Subject: [PATCH 09/14] refine prompt --- .../functions/util-file-read_image.json | 4 ++-- .../templates/util-file-read_image.fn.liquid | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-file-read_image.json b/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-file-read_image.json index b8dda8b1..57a6d90d 100644 --- a/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-file-read_image.json +++ b/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-file-read_image.json @@ -1,6 +1,6 @@ { "name": "util-file-read_image", - "description": "If the user's request is related to analyzing images, you can call this function to analyze images.", + "description": "If the user's request is related to describing or analyzing images, you can call this function to analyze images.", "parameters": { "type": "object", "properties": { @@ -10,7 +10,7 @@ }, "image_urls": { "type": "array", - "description": "The image, photo or picture urls that user requests for analysis. They typically start with 'http' or 'https'. If user doesn't include any url, then leave this array empty. Please remove any duplicated urls", + "description": "The image, photo or picture urls that user requests for analysis. They typically start with 'http' or 'https'. If user doesn't include any url, then leave this array empty. Please remove any duplicated urls. Do not make up any urls.", "items": { "type": "string", "description": "The image, photo or picture url that user requests for analysis. It typically starts with http or https." diff --git a/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-file-read_image.fn.liquid b/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-file-read_image.fn.liquid index 76f75bd0..c245d21d 100644 --- a/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-file-read_image.fn.liquid +++ b/src/Plugins/BotSharp.Plugin.FileHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-file-read_image.fn.liquid @@ -1 +1,2 @@ -Please call function util-file-read_image if user wants to describe an image or images. \ No newline at end of file +Please call function util-file-read_image if user wants to describe an image or images. +You can also call function util-file-read_image to access the image or images that user uploaded. \ No newline at end of file From 48eb6e8fdae94a5a38d08006e85b1cacf996e4f4 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 15 Jan 2025 15:40:35 -0600 Subject: [PATCH 10/14] fix gemini function call --- .../Providers/Chat/GeminiChatCompletionProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs index 11ea8f18..877070cd 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs @@ -126,18 +126,18 @@ public class GeminiChatCompletionProvider : IChatCompletion if (!agentService.RenderFunction(agent, function)) continue; var def = agentService.RenderFunctionProperty(agent, function); - var str = JsonSerializer.Serialize(def.Properties); + var str = JsonSerializer.Serialize(def?.Properties); funcDeclarations.Add(new FunctionDeclaration { Name = function.Name, Description = function.Description, - Parameters = new() + Parameters = str != "{}" ? new() { - Type = str != "{}" ? ParameterType.Object : ParameterType.TypeUnspecified, - Properties = str != "{}" ? JsonSerializer.Deserialize(str) : null, - Required = def.Required - } + Type = ParameterType.Object, + Properties = JsonSerializer.Deserialize(str), + Required = def?.Required ?? [] + } : null }); funcPrompts.Add($"{function.Name}: {function.Description} {def}"); From 90c2bd75dfdf7aa0c8c01ca4519fa8f7ca56dcc8 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 15 Jan 2025 15:46:03 -0600 Subject: [PATCH 11/14] refine gemini function parameter --- .../Chat/GeminiChatCompletionProvider.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs index 877070cd..5930a6b9 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs @@ -126,18 +126,19 @@ public class GeminiChatCompletionProvider : IChatCompletion if (!agentService.RenderFunction(agent, function)) continue; var def = agentService.RenderFunctionProperty(agent, function); - var str = JsonSerializer.Serialize(def?.Properties); + var props = JsonSerializer.Serialize(def?.Properties); + var parameters = !string.IsNullOrWhiteSpace(props) && props != "{}" ? new Schema() + { + Type = ParameterType.Object, + Properties = JsonSerializer.Deserialize(props), + Required = def?.Required ?? [] + } : null; funcDeclarations.Add(new FunctionDeclaration { Name = function.Name, Description = function.Description, - Parameters = str != "{}" ? new() - { - Type = ParameterType.Object, - Properties = JsonSerializer.Deserialize(str), - Required = def?.Required ?? [] - } : null + Parameters = parameters }); funcPrompts.Add($"{function.Name}: {function.Description} {def}"); From 3d1a4fc2efbfff8daac701537b1476965c9c1de0 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Thu, 16 Jan 2025 09:44:44 -0600 Subject: [PATCH 12/14] outbound call in asyncAmd --- .../Functions/HandleOutboundPhoneCallFn.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs index bdc05a61..b032d8ed 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HandleOutboundPhoneCallFn.cs @@ -93,6 +93,7 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions url: new Uri($"{_twilioSetting.CallbackHost}/twilio/voice/init-call?conversationId={conversationId}"), to: new PhoneNumber(args.PhoneNumber), from: new PhoneNumber(_twilioSetting.PhoneNumber), + asyncAmd: "true", machineDetection: "DetectMessageEnd"); message.Content = $"The generated phone message: {args.InitialMessage}. \r\n[Conversation ID: {conversationId}]" ?? message.Content; From bda1351a6ca20b5b6e38f32326b4f8264e3ec6ba Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Thu, 16 Jan 2025 15:57:50 -0600 Subject: [PATCH 13/14] ICrontabSource --- .../Agents/IAgentRuleHook.cs | 6 ---- .../Enums/ConversationChannel.cs | 3 +- .../Crontab/Models/CrontabItem.cs | 3 ++ .../Abstraction/ICrontabHook.cs | 9 ++++- .../Abstraction/ICrontabSource.cs | 9 +++++ .../Services/CrontabService.cs | 20 ++++++++--- .../Services/CrontabWatcher.cs | 36 ++++++++++++++++--- .../BotSharp.Core.Rules/Engines/RuleEngine.cs | 4 ++- .../Triggers/IRuleConfig.cs | 5 +++ .../BotSharp.OpenAPI/BotSharp.OpenAPI.csproj | 1 + .../Controllers/AgentController.cs | 12 ------- .../Controllers/RulesController.cs | 33 +++++++++++++++++ 12 files changed, 111 insertions(+), 30 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRuleHook.cs create mode 100644 src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabSource.cs create mode 100644 src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleConfig.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/Controllers/RulesController.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRuleHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRuleHook.cs deleted file mode 100644 index 8a19a561..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRuleHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace BotSharp.Abstraction.Agents; - -public interface IAgentRuleHook -{ - void AddRules(List rules); -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs index f23ff4ba..1843a1bc 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs @@ -5,8 +5,9 @@ public class ConversationChannel public const string WebChat = "webchat"; public const string OpenAPI = "openapi"; public const string Phone = "phone"; + public const string SMS = "sms"; public const string Messenger = "messenger"; public const string Email = "email"; - public const string Cron = "cron"; + public const string Crontab = "crontab"; public const string Database = "database"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs index 6a9dd43a..3a531012 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs @@ -23,6 +23,9 @@ public class CrontabItem : ScheduleTaskArgs [JsonPropertyName("expire_seconds")] public int ExpireSeconds { get; set; } = 60; + [JsonPropertyName("last_execution_time")] + public DateTime? LastExecutionTime { get; set; } + [JsonPropertyName("created_time")] public DateTime CreatedTime { get; set; } = DateTime.UtcNow; diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs index 1738ef5e..bf4e5868 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs @@ -2,5 +2,12 @@ namespace BotSharp.Core.Crontab.Abstraction; public interface ICrontabHook { - Task OnCronTriggered(CrontabItem item); + Task OnCronTriggered(CrontabItem item) + => Task.CompletedTask; + + Task OnTaskExecuting(CrontabItem item) + => Task.CompletedTask; + + Task OnTaskExecuted(CrontabItem item) + => Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabSource.cs b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabSource.cs new file mode 100644 index 00000000..ee42c648 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabSource.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Core.Crontab.Abstraction; + +/// +/// Provide a cron source for the crontab service. +/// +public interface ICrontabSource +{ + CrontabItem GetCrontabItem(); +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs index 78b737a0..70d16a0b 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs @@ -39,7 +39,17 @@ public class CrontabService : ICrontabService { var repo = _services.GetRequiredService(); var crontable = repo.GetCrontabItems(CrontabItemFilter.Empty()); - return crontable.Items.ToList(); + + // Add fixed crontab items from cronsources + var fixedCrantabItems = crontable.Items.ToList(); + var cronsources = _services.GetServices(); + foreach (var source in cronsources) + { + var item = source.GetCrontabItem(); + fixedCrantabItems.Add(source.GetCrontabItem()); + } + + return fixedCrantabItems; } public async Task ScheduledTimeArrived(CrontabItem item) @@ -47,8 +57,10 @@ public class CrontabService : ICrontabService _logger.LogDebug($"ScheduledTimeArrived {item}"); await HookEmitter.Emit(_services, async hook => - await hook.OnCronTriggered(item) - ); - await Task.Delay(1000 * 10); + { + await hook.OnTaskExecuting(item); + await hook.OnCronTriggered(item); + await hook.OnTaskExecuted(item); + }); } } diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs index 4711a6aa..7f47c3f0 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs @@ -24,9 +24,9 @@ public class CrontabWatcher : BackgroundService { var locker = scope.ServiceProvider.GetRequiredService(); - /*while (!stoppingToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested) { - var delay = Task.Delay(1000, stoppingToken); + var delay = Task.Delay(1000 * 10, stoppingToken); await locker.LockAsync("CrontabWatcher", async () => { @@ -34,7 +34,7 @@ public class CrontabWatcher : BackgroundService }); await delay; - }*/ + } _logger.LogWarning("Crontab Watcher background service is stopped."); } @@ -58,10 +58,24 @@ public class CrontabWatcher : BackgroundService // Get the current time var currentTime = DateTime.UtcNow; + // Get the last occurrence from the schedule + var lastOccurrence = GetLastOccurrence(schedule); + // Get the next occurrence from the schedule var nextOccurrence = schedule.GetNextOccurrence(currentTime.AddSeconds(-1)); - // Check if the current time matches the schedule + // Get the previous occurrence from the execution log + var previousOccurrence = item.LastExecutionTime; + + // First check if this occurrence was already triggered + if (previousOccurrence.HasValue && + previousOccurrence.Value >= lastOccurrence && + previousOccurrence.Value < nextOccurrence.AddSeconds(1)) + { + continue; + } + + // Then check if the current time matches the schedule bool matches = currentTime >= nextOccurrence && currentTime < nextOccurrence.AddSeconds(1); if (matches) @@ -72,9 +86,21 @@ public class CrontabWatcher : BackgroundService } catch (Exception ex) { - _logger.LogWarning($"Error when running cron task ({item.ConversationId}, {item.Title}, {item.Cron}): {ex.Message}\r\n{ex.InnerException}"); + _logger.LogError($"Error when running cron task ({item.Title}, {item.Cron}): {ex.Message}"); continue; } } } + + private DateTime GetLastOccurrence(CrontabSchedule schedule) + { + var nextOccurrence = schedule.GetNextOccurrence(DateTime.UtcNow); + var afterNextOccurrence = schedule.GetNextOccurrence(nextOccurrence); + var interval = afterNextOccurrence - nextOccurrence; + if (interval.TotalMinutes < 10) + { + throw new ArgumentException("The minimum interval must be at least 10 minutes."); + } + return nextOccurrence - interval; + } } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 5841d08a..4fdf66ba 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -45,6 +45,8 @@ public class RuleEngine : IRuleEngine { var conv = await convService.NewConversation(new Conversation { + Channel = trigger.Channel, + Title = data, AgentId = agent.Id }); @@ -52,7 +54,7 @@ public class RuleEngine : IRuleEngine var states = new List { - new("channel", ConversationChannel.Database), + new("channel", trigger.Channel), new("channel_id", trigger.EntityId) }; convService.SetConversationId(conv.Id, states); diff --git a/src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleConfig.cs b/src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleConfig.cs new file mode 100644 index 00000000..e9d75733 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Rules/Triggers/IRuleConfig.cs @@ -0,0 +1,5 @@ +namespace BotSharp.Core.Rules.Triggers; + +public interface IRuleConfig +{ +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj index 28dde755..94a86637 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj @@ -47,6 +47,7 @@ + diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index c8e1e889..226783d3 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -160,16 +160,4 @@ public class AgentController : ControllerBase } return utilities.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList(); } - - [HttpGet("/agent/rule/options")] - public IEnumerable GetAgentRuleOptions() - { - var rules = new List(); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - hook.AddRules(rules); - } - return rules.Where(x => !string.IsNullOrWhiteSpace(x.TriggerName)).OrderBy(x => x.TriggerName).ToList(); - } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RulesController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RulesController.cs new file mode 100644 index 00000000..613f82c2 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RulesController.cs @@ -0,0 +1,33 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Core.Rules.Triggers; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public class RulesController +{ + private readonly IServiceProvider _services; + + public RulesController( + IServiceProvider services) + { + _services = services; + } + + [HttpGet("/rule/triggers")] + public IEnumerable GetRuleTriggers() + { + var triggers = _services.GetServices(); + return triggers.Select(x => new AgentRule + { + TriggerName = x.GetType().Name + }).OrderBy(x => x.TriggerName).ToList(); + } + + [HttpGet("/rule/formalization")] + public async Task GetFormalizedRuleDefinition([FromBody] AgentRule rule) + { + return "{}"; + } +} From a412d333c05382974cc2269302f6b9453fd393d1 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 16 Jan 2025 16:20:00 -0600 Subject: [PATCH 14/14] add data --- .../Collections/CrontabItemDocument.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs index 39622d4c..9c697310 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/CrontabItemDocument.cs @@ -14,6 +14,8 @@ public class CrontabItemDocument : MongoBase public int ExecutionCount { get; set; } public int MaxExecutionCount { get; set; } public int ExpireSeconds { get; set; } + public DateTime? LastExecutionTime { get; set; } + public bool LessThan60Seconds { get; set; } = false; public IEnumerable Tasks { get; set; } = []; public DateTime CreatedTime { get; set; } = DateTime.UtcNow; @@ -31,6 +33,8 @@ public class CrontabItemDocument : MongoBase ExecutionCount = item.ExecutionCount, MaxExecutionCount = item.MaxExecutionCount, ExpireSeconds = item.ExpireSeconds, + LastExecutionTime = item.LastExecutionTime, + LessThan60Seconds = item.LessThan60Seconds, Tasks = item.Tasks?.Select(x => CronTaskMongoElement.ToDomainElement(x))?.ToArray() ?? [], CreatedTime = item.CreatedTime }; @@ -50,6 +54,8 @@ public class CrontabItemDocument : MongoBase ExecutionCount = item.ExecutionCount, MaxExecutionCount = item.MaxExecutionCount, ExpireSeconds = item.ExpireSeconds, + LastExecutionTime = item.LastExecutionTime, + LessThan60Seconds = item.LessThan60Seconds, Tasks = item.Tasks?.Select(x => CronTaskMongoElement.ToMongoElement(x))?.ToList() ?? [], CreatedTime = item.CreatedTime };