ICrontabSource

This commit is contained in:
Haiping Chen 2025-01-16 15:57:50 -06:00
parent 3d1a4fc2ef
commit bda1351a6c
12 changed files with 111 additions and 30 deletions

View file

@ -1,6 +0,0 @@
namespace BotSharp.Abstraction.Agents;
public interface IAgentRuleHook
{
void AddRules(List<AgentRule> rules);
}

View file

@ -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";
}

View file

@ -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;

View file

@ -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;
}

View file

@ -0,0 +1,9 @@
namespace BotSharp.Core.Crontab.Abstraction;
/// <summary>
/// Provide a cron source for the crontab service.
/// </summary>
public interface ICrontabSource
{
CrontabItem GetCrontabItem();
}

View file

@ -39,7 +39,17 @@ public class CrontabService : ICrontabService
{
var repo = _services.GetRequiredService<IBotSharpRepository>();
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<ICrontabSource>();
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<ICrontabHook>(_services, async hook =>
await hook.OnCronTriggered(item)
);
await Task.Delay(1000 * 10);
{
await hook.OnTaskExecuting(item);
await hook.OnCronTriggered(item);
await hook.OnTaskExecuted(item);
});
}
}

View file

@ -24,9 +24,9 @@ public class CrontabWatcher : BackgroundService
{
var locker = scope.ServiceProvider.GetRequiredService<IDistributedLocker>();
/*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;
}
}

View file

@ -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<MessageState>
{
new("channel", ConversationChannel.Database),
new("channel", trigger.Channel),
new("channel_id", trigger.EntityId)
};
convService.SetConversationId(conv.Id, states);

View file

@ -0,0 +1,5 @@
namespace BotSharp.Core.Rules.Triggers;
public interface IRuleConfig
{
}

View file

@ -47,6 +47,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BotSharp.Core.Rules\BotSharp.Core.Rules.csproj" />
<ProjectReference Include="..\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>

View file

@ -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<AgentRule> GetAgentRuleOptions()
{
var rules = new List<AgentRule>();
var hooks = _services.GetServices<IAgentRuleHook>();
foreach (var hook in hooks)
{
hook.AddRules(rules);
}
return rules.Where(x => !string.IsNullOrWhiteSpace(x.TriggerName)).OrderBy(x => x.TriggerName).ToList();
}
}

View file

@ -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<AgentRule> GetRuleTriggers()
{
var triggers = _services.GetServices<IRuleTrigger>();
return triggers.Select(x => new AgentRule
{
TriggerName = x.GetType().Name
}).OrderBy(x => x.TriggerName).ToList();
}
[HttpGet("/rule/formalization")]
public async Task<string> GetFormalizedRuleDefinition([FromBody] AgentRule rule)
{
return "{}";
}
}