From d445e0ba4a68a5433ac1d48923cd65459a5f32d8 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 17 Nov 2025 11:52:53 -0600 Subject: [PATCH] add run crontab endpoint --- .../Services/CrontabService.cs | 10 +- .../Services/InstructService.Execute.cs | 232 ++++++++++-------- .../Controllers/Crontab/CrontabController.cs | 45 ++++ .../Models/SpeechToTextRequest.cs | 5 - .../{ => Text}/TextCompletionRequest.cs | 2 +- .../{ => Text}/TextCompletionResponse.cs | 2 +- .../Realtime/RealTimeCompletionProvider.cs | 2 - src/Plugins/BotSharp.Plugin.OpenAI/Using.cs | 5 +- 8 files changed, 183 insertions(+), 120 deletions(-) create mode 100644 src/Infrastructure/BotSharp.OpenAPI/Controllers/Crontab/CrontabController.cs delete mode 100644 src/Plugins/BotSharp.Plugin.OpenAI/Models/SpeechToTextRequest.cs rename src/Plugins/BotSharp.Plugin.OpenAI/Models/{ => Text}/TextCompletionRequest.cs (89%) rename src/Plugins/BotSharp.Plugin.OpenAI/Models/{ => Text}/TextCompletionResponse.cs (94%) diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs index ce72b23b2..c8838b334 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs @@ -47,19 +47,19 @@ public async Task> GetCrontable() var crontable = await repo.GetCrontabItems(CrontabItemFilter.Empty()); // Add fixed crontab items from cronsources - var fixedCrantabItems = crontable.Items.ToList(); - var cronsources = _services.GetServices(); - foreach (var source in cronsources) + var fixedCrontabItems = crontable.Items.ToList(); + var cronSources = _services.GetServices(); + foreach (var source in cronSources) { if (source.IsRealTime) { continue; } var item = source.GetCrontabItem(); - fixedCrantabItems.Add(item); + fixedCrontabItems.Add(item); } - return fixedCrantabItems; + return fixedCrontabItems; } public async Task> GetTasks() diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index a0b7dc8cd..08c2ff7f5 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -1,6 +1,6 @@ using BotSharp.Abstraction.Coding; -using BotSharp.Abstraction.Coding.Enums; using BotSharp.Abstraction.Coding.Contexts; +using BotSharp.Abstraction.Coding.Enums; using BotSharp.Abstraction.Files.Options; using BotSharp.Abstraction.Files.Proccessors; using BotSharp.Abstraction.Instructs; @@ -23,7 +23,6 @@ public async Task Execute( FileInstructOptions? fileOptions = null) { var agentService = _services.GetRequiredService(); - var state = _services.GetRequiredService(); var agent = await agentService.LoadAgent(agentId); var response = new InstructResult @@ -32,7 +31,6 @@ public async Task Execute( Template = templateName }; - if (agent == null) { response.Text = $"Agent (id: {agentId}) does not exist!"; @@ -46,112 +44,14 @@ public async Task Execute( return response; } - // Run code template var codeResponse = await RunCode(agent, message, templateName, codeOptions); - if (!string.IsNullOrWhiteSpace(codeResponse?.Text)) + if (codeResponse != null) { return codeResponse; } - - // Before completion hooks - var hooks = _services.GetHooks(agentId); - foreach (var hook in hooks) - { - await hook.BeforeCompletion(agent, message); - - // Interrupted by hook - if (message.StopCompletion) - { - return new InstructResult - { - MessageId = message.MessageId, - Text = message.Content - }; - } - } - - - var provider = string.Empty; - var model = string.Empty; - var result = string.Empty; - - // Render prompt - var prompt = string.IsNullOrEmpty(templateName) ? - agentService.RenderInstruction(agent) : - agentService.RenderTemplate(agent, templateName); - - var completer = CompletionProvider.GetCompletion(_services, - agentConfig: agent.LlmConfig); - - if (completer is ITextCompletion textCompleter) - { - instruction = null; - provider = textCompleter.Provider; - model = textCompleter.Model; - - result = await GetTextCompletion(textCompleter, agent, prompt, message.MessageId); - response.Text = result; - } - else if (completer is IChatCompletion chatCompleter) - { - provider = chatCompleter.Provider; - model = chatCompleter.Model; - - - if (instruction == "#TEMPLATE#") - { - instruction = prompt; - prompt = message.Content; - } - - IFileProcessor? fileProcessor = null; - if (!files.IsNullOrEmpty() && fileOptions != null) - { - fileProcessor = _services.GetServices() - .FirstOrDefault(x => x.Provider.IsEqualTo(fileOptions.Processor)); - } - - if (fileProcessor != null) - { - var fileResponse = await fileProcessor.HandleFilesAsync(agent, prompt, files, new FileHandleOptions - { - Provider = provider, - Model = model, - Instruction = instruction, - UserMessage = message.Content, - TemplateName = templateName, - InvokeFrom = $"{nameof(InstructService)}.{nameof(Execute)}", - Data = state.GetStates().ToDictionary(x => x.Key, x => (object)x.Value) - }); - result = fileResponse.Result.IfNullOrEmptyAs(string.Empty); - } - else - { - result = await GetChatCompletion(chatCompleter, agent, instruction, prompt, message.MessageId, files); - } - response.Text = result; - } - - response.LogId = Guid.NewGuid().ToString(); - // After completion hooks - foreach (var hook in hooks) - { - await hook.AfterCompletion(agent, response); - await hook.OnResponseGenerated(new InstructResponseModel - { - LogId = response.LogId, - AgentId = agentId, - Provider = provider, - Model = model, - TemplateName = templateName, - UserMessage = prompt, - SystemInstruction = instruction, - CompletionText = response.Text - }); - } - + response = await RunLlm(agent, message, instruction, templateName, files, fileOptions); return response; } @@ -166,7 +66,7 @@ await hook.OnResponseGenerated(new InstructResponseModel private async Task RunCode( Agent agent, RoleDialogModel message, - string templateName, + string? templateName, CodeInstructOptions? codeOptions) { InstructResult? instructResult = null; @@ -266,11 +166,16 @@ await hook.OnResponseGenerated(new InstructResponseModel UseProcess = useProcess }, cancellationToken: cts.Token); + if (codeResponse == null || !codeResponse.Success) + { + return instructResult; + } + instructResult = new InstructResult { MessageId = message.MessageId, Template = context.CodeScript?.Name, - Text = codeResponse?.Result ?? string.Empty + Text = codeResponse.Result }; var codeExecution = new CodeExecutionResponseModel @@ -292,6 +197,123 @@ await hook.OnResponseGenerated(new InstructResponseModel return instructResult; } + + private async Task RunLlm( + Agent agent, + RoleDialogModel message, + string? instruction, + string? templateName, + IEnumerable? files = null, + FileInstructOptions? fileOptions = null) + { + var agentService = _services.GetRequiredService(); + var state = _services.GetRequiredService(); + + var response = new InstructResult + { + MessageId = message.MessageId, + Template = templateName + }; + + // Before completion hooks + var hooks = _services.GetHooks(agent.Id); + foreach (var hook in hooks) + { + await hook.BeforeCompletion(agent, message); + + // Interrupted by hook + if (message.StopCompletion) + { + return new InstructResult + { + MessageId = message.MessageId, + Text = message.Content + }; + } + } + + var provider = string.Empty; + var model = string.Empty; + var result = string.Empty; + + // Render prompt + var prompt = string.IsNullOrEmpty(templateName) ? + agentService.RenderInstruction(agent) : + agentService.RenderTemplate(agent, templateName); + + var completer = CompletionProvider.GetCompletion(_services, + agentConfig: agent.LlmConfig); + + if (completer is ITextCompletion textCompleter) + { + instruction = null; + provider = textCompleter.Provider; + model = textCompleter.Model; + + result = await GetTextCompletion(textCompleter, agent, prompt, message.MessageId); + response.Text = result; + } + else if (completer is IChatCompletion chatCompleter) + { + provider = chatCompleter.Provider; + model = chatCompleter.Model; + + if (instruction == "#TEMPLATE#") + { + instruction = prompt; + prompt = message.Content; + } + + IFileProcessor? fileProcessor = null; + if (!files.IsNullOrEmpty() && fileOptions != null) + { + fileProcessor = _services.GetServices() + .FirstOrDefault(x => x.Provider.IsEqualTo(fileOptions.Processor)); + } + + if (fileProcessor != null) + { + var fileResponse = await fileProcessor.HandleFilesAsync(agent, prompt, files, new FileHandleOptions + { + Provider = provider, + Model = model, + Instruction = instruction, + UserMessage = message.Content, + TemplateName = templateName, + InvokeFrom = $"{nameof(InstructService)}.{nameof(Execute)}", + Data = state.GetStates().ToDictionary(x => x.Key, x => (object)x.Value) + }); + result = fileResponse.Result.IfNullOrEmptyAs(string.Empty); + } + else + { + result = await GetChatCompletion(chatCompleter, agent, instruction, prompt, message.MessageId, files); + } + response.Text = result; + } + + response.LogId = Guid.NewGuid().ToString(); + // After completion hooks + foreach (var hook in hooks) + { + await hook.AfterCompletion(agent, response); + await hook.OnResponseGenerated(new InstructResponseModel + { + LogId = response.LogId, + AgentId = agent.Id, + Provider = provider, + Model = model, + TemplateName = templateName, + UserMessage = prompt, + SystemInstruction = instruction, + CompletionText = response.Text + }); + } + + return response; + } + + private async Task GetTextCompletion( ITextCompletion textCompleter, Agent agent, diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/Crontab/CrontabController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Crontab/CrontabController.cs new file mode 100644 index 000000000..3f3127789 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/Crontab/CrontabController.cs @@ -0,0 +1,45 @@ +using BotSharp.Abstraction.Crontab; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public class CrontabController : ControllerBase +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public CrontabController( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + [HttpPost("/crontab/{name}")] + public async Task RunCrontab(string name) + { + var cron = _services.GetRequiredService(); + var crons = await cron.GetCrontable(); + var found = crons.FirstOrDefault(x => x.Title.IsEqualTo(name)); + if (found == null) + { + _logger.LogWarning($"Cannnot find crontab {name}"); + return false; + } + + try + { + _logger.LogWarning($"Start running crontab {name}"); + await cron.ScheduledTimeArrived(found); + _logger.LogWarning($"Complete running crontab {name}"); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when running crontab {name}"); + return false; + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Models/SpeechToTextRequest.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Models/SpeechToTextRequest.cs deleted file mode 100644 index 4708f14f5..000000000 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Models/SpeechToTextRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace BotSharp.Plugin.OpenAI.Models; - -public class SpeechToTextRequest -{ -} diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Models/TextCompletionRequest.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Models/Text/TextCompletionRequest.cs similarity index 89% rename from src/Plugins/BotSharp.Plugin.OpenAI/Models/TextCompletionRequest.cs rename to src/Plugins/BotSharp.Plugin.OpenAI/Models/Text/TextCompletionRequest.cs index 1ba0da2c9..07319a179 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Models/TextCompletionRequest.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Models/Text/TextCompletionRequest.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Plugin.OpenAI.Models; +namespace BotSharp.Plugin.OpenAI.Models.Text; public class TextCompletionRequest { diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Models/TextCompletionResponse.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Models/Text/TextCompletionResponse.cs similarity index 94% rename from src/Plugins/BotSharp.Plugin.OpenAI/Models/TextCompletionResponse.cs rename to src/Plugins/BotSharp.Plugin.OpenAI/Models/Text/TextCompletionResponse.cs index fcbde06a1..aa5a0dadb 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Models/TextCompletionResponse.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Models/Text/TextCompletionResponse.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Plugin.OpenAI.Models; +namespace BotSharp.Plugin.OpenAI.Models.Text; public class TextCompletionResponse { diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index fe2e1bbb9..b023f94d5 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -1,8 +1,6 @@ using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Realtime.Options; -using BotSharp.Abstraction.Realtime.Sessions; using BotSharp.Abstraction.Realtime.Settings; -using BotSharp.Plugin.OpenAI.Models.Realtime; using OpenAI.Chat; namespace BotSharp.Plugin.OpenAI.Providers.Realtime; diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Using.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Using.cs index a1f2392d0..7a111d90a 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Using.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Using.cs @@ -31,7 +31,10 @@ global using BotSharp.Abstraction.Realtime; global using BotSharp.Abstraction.Realtime.Models; global using BotSharp.Abstraction.Realtime.Sessions; + global using BotSharp.Core.Infrastructures; global using BotSharp.Core.Session; -global using BotSharp.Plugin.OpenAI.Models; + +global using BotSharp.Plugin.OpenAI.Models.Text; +global using BotSharp.Plugin.OpenAI.Models.Realtime; global using BotSharp.Plugin.OpenAI.Settings; \ No newline at end of file