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/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/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.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.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.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/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(); 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/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 "{}"; + } +} 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..8657b31b 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,15 +47,16 @@ 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) + 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) + FunctionArgs = JsonSerializer.Serialize(toolResult.Input), }; } else @@ -161,7 +161,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..9f1f692f 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -160,6 +160,7 @@ 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() }; 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.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 diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Chat/GeminiChatCompletionProvider.cs index b52013dd..5930a6b9 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,17 +126,19 @@ public class GeminiChatCompletionProvider : IChatCompletion if (!agentService.RenderFunction(agent, function)) continue; var def = agentService.RenderFunctionProperty(agent, function); + 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 = new() - { - Type = ParameterType.Object, - Properties = def.Properties, - Required = def.Required - } + Parameters = parameters }); funcPrompts.Add($"{function.Name}: {function.Description} {def}"); 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 }; 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..21756f5d 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -138,6 +138,7 @@ 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() diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index ef24298d..9d96bf3c 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; @@ -126,9 +127,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 +390,9 @@ public class TwilioVoiceController : TwilioController $"twilio/voice/speeches/{conversationId}/intial.mp3" } }; + string tag = $"twilio:{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); 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 c371bec2..b032d8ed 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 @@ -89,7 +92,9 @@ 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), + asyncAmd: "true", + 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; 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,