From 832891cdb57663e5b6f608bf9cf3ff1fff672ee9 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Sun, 19 Oct 2025 23:30:32 -0500 Subject: [PATCH 1/3] refine --- .../Controllers/ImageGenerationController.cs | 4 +- .../Functions/ComposeImageFn.cs | 58 ++++++------------- .../Functions/EditImageFn.cs | 5 +- .../Providers/Image/ImageClientExtensions.cs | 29 +++++----- 4 files changed, 36 insertions(+), 60 deletions(-) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ImageGenerationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ImageGenerationController.cs index 6dd03f641..9d30faa79 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ImageGenerationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ImageGenerationController.cs @@ -27,7 +27,7 @@ public async Task ComposeImages([FromBody] ImageCompos try { - if (request.Files.Length < 1) + if (request.Files.IsNullOrEmpty()) { return new ImageGenerationViewModel { Message = "No image found" }; } @@ -46,7 +46,7 @@ public async Task ComposeImages([FromBody] ImageCompos } catch (Exception ex) { - var error = $"Error in image edit. {ex.Message}"; + var error = $"Error in image composition. {ex.Message}"; _logger.LogError(ex, error); imageViewModel.Message = error; return imageViewModel; diff --git a/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/ComposeImageFn.cs b/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/ComposeImageFn.cs index 4c1544e09..177ab3c5b 100644 --- a/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/ComposeImageFn.cs +++ b/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/ComposeImageFn.cs @@ -11,7 +11,6 @@ public class ComposeImageFn : IFunctionCallback private readonly ILogger _logger; private readonly ImageHandlerSettings _settings; - private Agent _agent; private string _conversationId; private string _messageId; @@ -29,22 +28,23 @@ public async Task Execute(RoleDialogModel message) { var args = JsonSerializer.Deserialize(message.FunctionArgs); var descrpition = args?.UserRequest ?? string.Empty; - await Init(message); + + var agentService = _services.GetRequiredService(); + var agent = await agentService.GetAgent(message.CurrentAgentId); + + Init(message); SetImageOptions(); var image = await SelectImage(descrpition); - var response = await GetImageEditGeneration(message, descrpition, image); + var response = await GetImageEditGeneration(agent, message, descrpition, image); message.Content = response; message.StopCompletion = true; return true; } - private async Task Init(RoleDialogModel message) + private void Init(RoleDialogModel message) { - var agentService = _services.GetRequiredService(); var convService = _services.GetRequiredService(); - - _agent = await agentService.GetAgent(message.CurrentAgentId); _conversationId = convService.ConversationId; _messageId = message.MessageId; } @@ -74,7 +74,7 @@ private void SetImageOptions() return selecteds?.FirstOrDefault(); } - private async Task GetImageEditGeneration(RoleDialogModel message, string description, MessageFileModel? image) + private async Task GetImageEditGeneration(Agent agent, RoleDialogModel message, string description, MessageFileModel? image) { if (image == null) { @@ -83,15 +83,10 @@ private async Task GetImageEditGeneration(RoleDialogModel message, strin try { - var (provider, model) = GetLlmProviderModel(); + var (provider, model) = GetLlmProviderModel(agent); var completion = CompletionProvider.GetImageCompletion(_services, provider: provider, model: model); var text = !string.IsNullOrWhiteSpace(description) ? description : message.Content; var dialog = RoleDialogModel.From(message, AgentRole.User, text); - var agent = new Agent - { - Id = _agent?.Id ?? BuiltInAgentId.UtilityAssistant, - Name = _agent?.Name ?? "Utility Assistant" - }; var fileStorage = _services.GetRequiredService(); var fileBinary = fileStorage.GetFileBytes(image.FileStorageUrl); @@ -110,7 +105,7 @@ private async Task GetImageEditGeneration(RoleDialogModel message, strin return response.Content; } - return await GetImageEditResponse(description, defaultContent: null); + return await GetImageEditResponse(agent, description); } catch (Exception ex) { @@ -120,43 +115,24 @@ private async Task GetImageEditGeneration(RoleDialogModel message, strin } } - private async Task GetImageEditResponse(string description, string? defaultContent) + private async Task GetImageEditResponse(Agent agent, string description) { - if (defaultContent != null) - { - return defaultContent; - } - - var llmConfig = _agent.LlmConfig; - var agent = new Agent - { - Id = _agent?.Id ?? BuiltInAgentId.UtilityAssistant, - Name = _agent?.Name ?? "Utility Assistant", - LlmConfig = new AgentLlmConfig - { - Provider = llmConfig?.Provider ?? "openai", - Model = llmConfig?.Model ?? "gpt-5-mini", - MaxOutputTokens = llmConfig?.MaxOutputTokens, - ReasoningEffortLevel = llmConfig?.ReasoningEffortLevel - } - }; - return await AiResponseHelper.GetImageGenerationResponse(_services, agent, description); } - private (string, string) GetLlmProviderModel() + private (string, string) GetLlmProviderModel(Agent agent) { - var state = _services.GetRequiredService(); - var llmProviderService = _services.GetRequiredService(); - - var provider = state.GetState("image_edit_llm_provider"); - var model = state.GetState("image_edit_llm_provider"); + var provider = agent?.LlmConfig?.ImageEdit?.Provider; + var model = agent?.LlmConfig?.ImageEdit?.Model; if (!string.IsNullOrEmpty(provider) && !string.IsNullOrEmpty(model)) { return (provider, model); } + provider = _settings?.Edit?.Provider; + model = _settings?.Edit?.Model; + if (!string.IsNullOrEmpty(provider) && !string.IsNullOrEmpty(model)) { return (provider, model); diff --git a/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/EditImageFn.cs b/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/EditImageFn.cs index 8e73d639c..c171b93f2 100644 --- a/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/EditImageFn.cs +++ b/src/Plugins/BotSharp.Plugin.ImageHandler/Functions/EditImageFn.cs @@ -32,7 +32,7 @@ public async Task Execute(RoleDialogModel message) var agentService = _services.GetRequiredService(); var agent = await agentService.GetAgent(message.CurrentAgentId); - await Init(message); + Init(message); SetImageOptions(); var image = await SelectImage(descrpition); @@ -42,10 +42,9 @@ public async Task Execute(RoleDialogModel message) return true; } - private async Task Init(RoleDialogModel message) + private void Init(RoleDialogModel message) { var convService = _services.GetRequiredService(); - _conversationId = convService.ConversationId; _messageId = message.MessageId; } diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs index 2fb14e9ef..d90e5330d 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs @@ -30,16 +30,24 @@ public static ClientResult GenerateImageEdits( ImageEditOptions options = null) { if (client == null) + { throw new ArgumentNullException(nameof(client)); + } - if (images == null || images.Length == 0) + if (images.IsNullOrEmpty()) + { throw new ArgumentException("At least one image is required", nameof(images)); + } if (imageFileNames == null || imageFileNames.Length != images.Length) + { throw new ArgumentException("Image file names array must match images array length", nameof(imageFileNames)); + } if (string.IsNullOrWhiteSpace(prompt)) + { throw new ArgumentException("Prompt cannot be null or empty", nameof(prompt)); + } // Get the pipeline from the client var pipeline = client.Pipeline; @@ -155,23 +163,16 @@ private static void WriteFormField(MemoryStream stream, string boundary, string stream.Write(newLine, 0, newLine.Length); } - #region Helper Methods - - private static string GetEndpoint(PipelineMessage message) - { - // Try to get the endpoint from the request URI if already set - return message.Request.Uri?.GetLeftPart(UriPartial.Authority); - } - + #region Private Methods private static GeneratedImageCollection ParseResponse(PipelineResponse response, GeneratedImageFormat? format) { try { // Try to use ModelReaderWriter to deserialize the response - var modelReaderWriter = ModelReaderWriter.Read(response.Content); - if (modelReaderWriter != null) + var result = ModelReaderWriter.Read(response.Content); + if (result != null) { - return modelReaderWriter; + return result; } } catch (Exception ex) @@ -196,7 +197,7 @@ private static GeneratedImageCollection ParseResponse(PipelineResponse response, var result = fromResponseMethod.Invoke(null, new object[] { response }); if (result != null) { - return (GeneratedImageCollection)result; + return result as GeneratedImageCollection; } } @@ -211,7 +212,7 @@ private static GeneratedImageCollection ParseResponse(PipelineResponse response, var result = deserializeMethod.Invoke(null, new object[] { jsonDocument.RootElement }); if (result != null) { - return (GeneratedImageCollection)result; + return result as GeneratedImageCollection; } } } From cd11f703001e0e62b751e30baa59a5b9bdc7d390 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Mon, 20 Oct 2025 01:06:50 -0500 Subject: [PATCH 2/3] refine settings --- .../MLTasks/Settings/LlmModelSetting.cs | 1 + .../Providers/Image/ImageClientExtensions.cs | 127 ++++++++---------- .../Image/ImageCompletionProvider.Compose.cs | 8 +- .../Image/ImageCompletionProvider.Edit.cs | 6 + 4 files changed, 67 insertions(+), 75 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs index 1bc7ef743..3249b9eb4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs @@ -122,6 +122,7 @@ public class ImageGenerationSetting public class ImageEditSetting { public ModelSettingBase? Size { get; set; } + public ModelSettingBase? Quality { get; set; } public ModelSettingBase? ResponseFormat { get; set; } public ModelSettingBase? Background { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs index d90e5330d..7ce780357 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageClientExtensions.cs @@ -2,6 +2,8 @@ using OpenAI.Images; using System.ClientModel; using System.ClientModel.Primitives; +using System.Net.Http; +using System.Net.Http.Headers; using System.Reflection; namespace BotSharp.Plugin.OpenAI.Providers.Image; @@ -23,11 +25,12 @@ public static class ImageClientExtensions /// ClientResult containing the generated image collection public static ClientResult GenerateImageEdits( this ImageClient client, + string model, Stream[] images, string[] imageFileNames, string prompt, int? imageCount = null, - ImageEditOptions options = null) + ImageEditOptions? options = null) { if (client == null) { @@ -54,29 +57,30 @@ public static ClientResult GenerateImageEdits( using var message = pipeline.CreateMessage(); // Build the request - BuildMultipartRequest(message, images, imageFileNames, prompt, imageCount, options); + BuildMultipartRequest(message, model, images, imageFileNames, prompt, imageCount, options); // Send the request pipeline.Send(message); - if (message.Response.IsError) + if (message.Response == null || message.Response.IsError) { - throw new InvalidOperationException($"API request failed with status {message.Response.Status}: {message.Response.ReasonPhrase} \r\n{message.Response.Content}"); + throw new InvalidOperationException($"API request failed with status {message.Response?.Status}: {message.Response?.ReasonPhrase} \r\n{message.Response?.Content}"); } // Parse the response - var generatedImages = ParseResponse(message.Response, options?.ResponseFormat); + var generatedImages = ParseResponse(message.Response); return ClientResult.FromValue(generatedImages, message.Response); } private static void BuildMultipartRequest( PipelineMessage message, + string model, Stream[] images, string[] imageFileNames, string prompt, int? imageCount, - ImageEditOptions options) + ImageEditOptions? options) { message.Request.Method = "POST"; @@ -84,87 +88,74 @@ private static void BuildMultipartRequest( var endpoint = "https://api.openai.com"; message.Request.Uri = new Uri($"{endpoint.TrimEnd('/')}/v1/images/edits"); - // Create multipart form data var boundary = $"----WebKitFormBoundary{Guid.NewGuid():N}"; - var contentBuilder = new MemoryStream(); - - // Add prompt - WriteFormField(contentBuilder, boundary, "prompt", prompt); - - WriteFormField(contentBuilder, boundary, "model", "gpt-image-1-mini"); - - // Add image count - WriteFormField(contentBuilder, boundary, "n", imageCount.Value.ToString() ?? "1"); - - for (var i = 0; i < images.Length; i++) + using var form = new MultipartFormDataContent(boundary) { - WriteFormField(contentBuilder, boundary, "image[]", imageFileNames[i], images[i], "image/png"); - } + { new StringContent(prompt), "prompt" }, + { new StringContent(model ?? "gpt-image-1-mini"), "model" } + }; - // Add optional parameters supported by OpenAI image edits API - if (options.Quality.HasValue) + if (imageCount.HasValue) { - WriteFormField(contentBuilder, boundary, "quality", options.Quality.ToString() ?? "auto"); + form.Add(new StringContent(imageCount.Value.ToString()), "n"); } - - if (options.Size.HasValue) + else { - WriteFormField(contentBuilder, boundary, "size", ConvertImageSizeToString(options.Size.Value)); + form.Add(new StringContent("1"), "n"); } - if (options.Background.HasValue) + if (options != null) { - WriteFormField(contentBuilder, boundary, "background", options.Background.ToString() ?? "auto"); - } - - WriteFormField(contentBuilder, boundary, "output_format", "png"); - - if (!string.IsNullOrEmpty(options.EndUserId)) - { - WriteFormField(contentBuilder, boundary, "user", options.EndUserId); - } - - WriteFormField(contentBuilder, boundary, "moderation", "auto"); - - // Write closing boundary - var closingBoundary = Encoding.UTF8.GetBytes($"--{boundary}--\r\n"); - contentBuilder.Write(closingBoundary, 0, closingBoundary.Length); - - // Set the content - contentBuilder.Position = 0; - message.Request.Content = BinaryContent.Create(BinaryData.FromStream(contentBuilder)); + if (options.Quality.HasValue) + { + form.Add(new StringContent(options.Quality.ToString()), "quality"); + } - // Set content type header - message.Request.Headers.Set("Content-Type", $"multipart/form-data; boundary={boundary}"); - } + if (options.Size.HasValue) + { + form.Add(new StringContent(ConvertImageSizeToString(options.Size.Value)), "size"); + } - private static void WriteFormField(MemoryStream stream, string boundary, string name, string value) - { - var header = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\n"; - var body = $"{header}\r\n{value}\r\n"; - var bytes = Encoding.UTF8.GetBytes(body); - stream.Write(bytes, 0, bytes.Length); - } + if (options.Background.HasValue) + { + form.Add(new StringContent(options.Background.ToString()), "background"); + } - private static void WriteFormField(MemoryStream stream, string boundary, string name, string fileName, Stream fileStream, string contentType) - { - var header = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"; filename=\"{fileName}\"\r\nContent-Type: {contentType}\r\n\r\n"; - var headerBytes = Encoding.UTF8.GetBytes(header); - stream.Write(headerBytes, 0, headerBytes.Length); + if (options.ResponseFormat.HasValue) + { + form.Add(new StringContent(options.ResponseFormat.ToString()), "response_format"); + } + } - // Copy file stream - if (fileStream.CanSeek) + for (var i = 0; i < images.Length; i++) { - fileStream.Position = 0; + if (images[i].CanSeek) + { + images[i].Position = 0; + } + var fileContent = new StreamContent(images[i]); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + + if (images.Length > 1) + { + form.Add(fileContent, name: "image[]", fileName: imageFileNames[i]); + } + else + { + form.Add(fileContent, name: "image", fileName: imageFileNames[i]); + } } - fileStream.CopyTo(stream); - var newLine = Encoding.UTF8.GetBytes("\r\n"); - stream.Write(newLine, 0, newLine.Length); + using var ms = new MemoryStream(); + form.CopyTo(ms, null, CancellationToken.None); + ms.Position = 0; + + message.Request.Headers.Set("Content-Type", form.Headers.ContentType.ToString()); + message.Request.Content = BinaryContent.Create(BinaryData.FromStream(ms)); } #region Private Methods - private static GeneratedImageCollection ParseResponse(PipelineResponse response, GeneratedImageFormat? format) + private static GeneratedImageCollection ParseResponse(PipelineResponse response) { try { diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Compose.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Compose.cs index d07c4b7b0..6215ba1af 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Compose.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Compose.cs @@ -1,6 +1,4 @@ #pragma warning disable OPENAI001 -using OpenAI.Images; - namespace BotSharp.Plugin.OpenAI.Providers.Image; public partial class ImageCompletionProvider @@ -20,11 +18,7 @@ public async Task GetImageComposition(Agent agent, RoleDialogMo var imageClient = client.GetImageClient(_model); // Use the new extension method to support multiple images - options.ResponseFormat = "b64_json"; - options.Quality = "medium"; - options.Background = "auto"; - options.Size = GeneratedImageSize.Auto; - var response = imageClient.GenerateImageEdits(images, imageFileNames, prompt, imageCount, options); + var response = imageClient.GenerateImageEdits(_model, images, imageFileNames, prompt, imageCount, options); var generatedImageCollection = response.Value; var generatedImages = GetImageGenerations(generatedImageCollection, options.ResponseFormat); diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Edit.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Edit.cs index 732cc7a96..7a8d44725 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Edit.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Image/ImageCompletionProvider.Edit.cs @@ -56,12 +56,14 @@ public async Task GetImageEdits(Agent agent, RoleDialogModel me var state = _services.GetRequiredService(); var size = state.GetState("image_size"); + var quality = state.GetState("image_quality"); var responseFormat = state.GetState("image_response_format"); var background = state.GetState("image_background"); var settings = settingsService.GetSetting(Provider, _model)?.Image?.Edit; size = settings?.Size != null ? LlmUtility.VerifyModelParameter(size, settings.Size.Default, settings.Size.Options) : null; + quality = settings?.Quality != null ? LlmUtility.VerifyModelParameter(quality, settings.Quality.Default, settings.Quality.Options) : null; responseFormat = settings?.ResponseFormat != null ? LlmUtility.VerifyModelParameter(responseFormat, settings.ResponseFormat.Default, settings.ResponseFormat.Options) : null; background = settings?.Background != null ? LlmUtility.VerifyModelParameter(background, settings.Background.Default, settings.Background.Options) : null; @@ -70,6 +72,10 @@ public async Task GetImageEdits(Agent agent, RoleDialogModel me { options.Size = GetImageSize(size); } + if (!string.IsNullOrEmpty(quality)) + { + options.Quality = GetImageQuality(quality); + } if (!string.IsNullOrEmpty(responseFormat)) { options.ResponseFormat = GetImageResponseFormat(responseFormat); From e60e864c70a753757c1f7abf97ffb3d6f6536d58 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 20 Oct 2025 15:01:45 -0500 Subject: [PATCH 3/3] refine template dict --- .../Agents/IAgentService.cs | 15 +++-- .../Options/BotSharpOptions.cs | 4 +- .../Templating/ITemplateRender.cs | 2 +- .../Utilities/ObjectExtensions.cs | 66 ++++++++----------- .../Services/AgentService.CreateAgent.cs | 2 +- .../Agents/Services/AgentService.GetAgents.cs | 2 +- .../Agents/Services/AgentService.LoadAgent.cs | 55 +++++++++++----- .../Services/AgentService.RefreshAgents.cs | 2 +- .../Agents/Services/AgentService.Rendering.cs | 66 +++++++++++++------ .../Agents/Services/AgentService.cs | 20 +++--- .../Infrastructures/HookEmitter.cs | 1 - .../Services/InstructService.Execute.cs | 2 +- .../MCP/Hooks/MCPToolAgentHook.cs | 2 +- .../BotSharp.Core/Plugins/PluginLoader.cs | 6 +- .../Routing/Hooks/RoutingAgentHook.cs | 2 +- .../Routing/Reasoning/ReasonerHelper.cs | 2 +- .../BotSharp.Core/Routing/RoutingContext.cs | 8 +-- .../Templating/TemplateRender.cs | 2 +- .../BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 5 -- .../Hooks/ChatHubCrontabHook.cs | 62 ----------------- .../Modules/Embeddings.cs | 4 +- .../BotSharp.Plugin.MetaGLM/Modules/Images.cs | 4 +- .../Hooks/SqlPlannerAgentHook.cs | 2 +- .../Services/TwilioMessageQueueService.cs | 2 +- .../PlaywrightDriver/PlaywrightWebDriver.cs | 2 +- .../Core/TestAgentService.cs | 15 +++-- 26 files changed, 162 insertions(+), 193 deletions(-) delete mode 100644 src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index e7b054d79..b6b106928 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -2,6 +2,7 @@ using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; +using System.Collections.Concurrent; namespace BotSharp.Abstraction.Agents; @@ -30,18 +31,18 @@ public interface IAgentService /// Task InheritAgent(Agent agent); - string RenderInstruction(Agent agent, Dictionary? renderData = null); + string RenderInstruction(Agent agent, IDictionary? renderData = null); - string RenderTemplate(Agent agent, string templateName, Dictionary? renderData = null); + string RenderTemplate(Agent agent, string templateName, IDictionary? renderData = null); - bool RenderFunction(Agent agent, FunctionDef def, Dictionary? renderData = null); + bool RenderFunction(Agent agent, FunctionDef def, IDictionary? renderData = null); - FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def, Dictionary? renderData = null); + FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def, IDictionary? renderData = null); - (string, IEnumerable) PrepareInstructionAndFunctions(Agent agent, Dictionary? renderData = null, StringComparer? comparer = null); + (string, IEnumerable) PrepareInstructionAndFunctions(Agent agent, IDictionary? renderData = null, StringComparer? comparer = null); - bool RenderVisibility(string? visibilityExpression, Dictionary dict); - Dictionary CollectRenderData(Agent agent); + bool RenderVisibility(string? visibilityExpression, IDictionary dict); + ConcurrentDictionary CollectRenderData(Agent agent); /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs index b8609422d..63469bcd8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Options/BotSharpOptions.cs @@ -9,9 +9,11 @@ public class BotSharpOptions PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, AllowTrailingCommas = true, - WriteIndented = true + WriteIndented = true, + ReferenceHandler = ReferenceHandler.IgnoreCycles }; + private JsonSerializerOptions _jsonSerializerOptions; public JsonSerializerOptions JsonSerializerOptions diff --git a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs index fecc36c45..9a555f9c7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs @@ -2,6 +2,6 @@ namespace BotSharp.Abstraction.Templating; public interface ITemplateRender { - string Render(string template, Dictionary dict); + string Render(string template, IDictionary dict); void RegisterType(Type type); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs index 7b3bca61b..bed3b71ab 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/ObjectExtensions.cs @@ -1,55 +1,41 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using BotSharp.Abstraction.Options; using System.Text.Json; -using System.Threading.Tasks; using JsonSerializer = System.Text.Json.JsonSerializer; -namespace BotSharp.Abstraction.Utilities +namespace BotSharp.Abstraction.Utilities; + +public static class ObjectExtensions { - public static class ObjectExtensions + private static readonly JsonSerializerOptions DefaultJsonOptions = new() { - private static readonly JsonSerializerOptions DefaultOptions = new() - { - ReferenceHandler = ReferenceHandler.IgnoreCycles - }; - private static JsonSerializerSettings DefaultSettings = new() - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore - }; + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + AllowTrailingCommas = true, + ReferenceHandler = ReferenceHandler.IgnoreCycles + }; - public static T DeepClone(this T obj) where T : class + public static T? DeepClone(this T? obj, Action? modifier = null, BotSharpOptions? options = null) where T : class + { + if (obj == null) { - if (obj == null) return null; - - try - { - var json = JsonSerializer.Serialize(obj, DefaultOptions); - return JsonSerializer.Deserialize(json, DefaultOptions); - } - catch(Exception ex) - { - Console.WriteLine($"DeepClone Error:{ex}"); - return DeepCloneWithNewtonsoft(obj); - } + return null; } - private static T DeepCloneWithNewtonsoft(this T obj) where T : class + try { - if (obj == null) return null; - - try + var json = JsonSerializer.Serialize(obj, options?.JsonSerializerOptions ?? DefaultJsonOptions); + var newObj = JsonSerializer.Deserialize(json, options?.JsonSerializerOptions ?? DefaultJsonOptions); + if (modifier != null && newObj != null) { - var json = JsonConvert.SerializeObject(obj, DefaultSettings); - return JsonConvert.DeserializeObject(json, DefaultSettings); + modifier(newObj); } - catch(Exception ex) - { - Console.WriteLine($"DeepClone Error:{ex}"); - return obj; - } + + return newObj; } + catch (Exception ex) + { + Console.WriteLine($"DeepClone Error in {nameof(DeepClone)}: {ex}"); + return null; + } } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index d61f0f3dd..5404c0d44 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -127,7 +127,7 @@ private List GetFunctionsFromFile(string fileDir) if (extension != "json") continue; var json = File.ReadAllText(file); - var function = JsonSerializer.Deserialize(json, _options); + var function = JsonSerializer.Deserialize(json, _options.JsonSerializerOptions); functions.Add(function); } catch diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 5fae2184e..7c46427f7 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -41,7 +41,7 @@ public async Task> GetAgentOptions(List? agentIdsOrNames, b return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? []; } - [SharpCache(10)] + [SharpCache(10, perInstanceCache: true)] public async Task GetAgent(string id) { if (string.IsNullOrWhiteSpace(id) || id == Guid.Empty.ToString()) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 2dcd15b77..53d9a05bc 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -10,12 +10,29 @@ public partial class AgentService // [SharpCache(10, perInstanceCache: true)] public async Task LoadAgent(string id, bool loadUtility = true) { - if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) return null; + if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) + { + return null; + } HookEmitter.Emit(_services, hook => hook.OnAgentLoading(ref id), id); - var agent = (await GetAgent(id)).DeepClone(); - if (agent == null) return null; + var originalAgent = await GetAgent(id); + var agent = originalAgent.DeepClone(modifier: agt => + { + agt.ChannelInstructions = originalAgent.ChannelInstructions.DeepClone() ?? new(); + agt.Functions = originalAgent.Functions.DeepClone() ?? new(); + agt.Templates = originalAgent.Templates.DeepClone() ?? new(); + agt.Samples = originalAgent.Samples.DeepClone() ?? new(); + agt.Responses = originalAgent.Responses.DeepClone() ?? new(); + agt.LlmConfig = originalAgent.LlmConfig.DeepClone() ?? new(); + agt.Plugin = originalAgent.Plugin.DeepClone() ?? new(); + }); + + if (agent == null) + { + return null; + } agent.TemplateDict = []; agent.SecondaryInstructions = []; @@ -25,8 +42,8 @@ public async Task LoadAgent(string id, bool loadUtility = true) OverrideInstructionByChannel(agent); AddOrUpdateParameters(agent); - // Populate state into dictionary - PopulateState(agent.TemplateDict); + // Populate state + PopulateState(agent); // After agent is loaded HookEmitter.Emit(_services, hook => { @@ -34,7 +51,9 @@ public async Task LoadAgent(string id, bool loadUtility = true) if (!string.IsNullOrEmpty(agent.Instruction)) { - hook.OnInstructionLoaded(agent.Instruction, agent.TemplateDict); + var dict = new Dictionary(agent.TemplateDict); + hook.OnInstructionLoaded(agent.Instruction, dict); + agent.TemplateDict = new Dictionary(dict); } if (agent.Functions != null) @@ -69,7 +88,10 @@ public async Task LoadAgent(string id, bool loadUtility = true) private void OverrideInstructionByChannel(Agent agent) { var instructions = agent.ChannelInstructions; - if (instructions.IsNullOrEmpty()) return; + if (instructions.IsNullOrEmpty()) + { + return; + } var state = _services.GetRequiredService(); var channel = state.GetState("channel"); @@ -79,19 +101,19 @@ private void OverrideInstructionByChannel(Agent agent) agent.Instruction = !string.IsNullOrWhiteSpace(found?.Instruction) ? found.Instruction : defaultInstruction?.Instruction; } - private void PopulateState(Dictionary dict) + private void PopulateState(Agent agent) { - var conv = _services.GetRequiredService(); - foreach (var t in conv.States.GetStates()) - { - dict[t.Key] = t.Value; - } + var dict = CollectRenderData(agent); + agent.TemplateDict = new Dictionary(dict); } private void AddOrUpdateParameters(Agent agent) { var agentId = agent.Id ?? agent.Name; - if (AgentParameterTypes.ContainsKey(agentId)) return; + if (AgentParameterTypes.ContainsKey(agentId)) + { + return; + } AddOrUpdateRoutesParameters(agentId, agent.RoutingRules); AddOrUpdateFunctionsParameters(agentId, agent.Functions); @@ -103,7 +125,10 @@ private void AddOrUpdateRoutesParameters(string agentId, List routi foreach (var rule in routingRules.Where(x => x.Required)) { - if (string.IsNullOrEmpty(rule.FieldType)) continue; + if (string.IsNullOrEmpty(rule.FieldType)) + { + continue; + } parameterTypes[rule.Field] = rule.FieldType; } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs index cc38a151a..045c71cf5 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs @@ -39,7 +39,7 @@ public async Task RefreshAgents(IEnumerable? agentIds = null) try { var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json")); - var agent = JsonSerializer.Deserialize(agentJson, _options); + var agent = JsonSerializer.Deserialize(agentJson, _options.JsonSerializerOptions); if (agent == null) { diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs index 00f50a1db..5ab883864 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs @@ -1,13 +1,14 @@ using BotSharp.Abstraction.Loggers; using BotSharp.Abstraction.Templating; using Newtonsoft.Json.Linq; +using System.Collections.Concurrent; using System.Text.RegularExpressions; namespace BotSharp.Core.Agents.Services; public partial class AgentService { - public string RenderInstruction(Agent agent, Dictionary? renderData = null) + public string RenderInstruction(Agent agent, IDictionary? renderData = null) { var render = _services.GetRequiredService(); var conv = _services.GetRequiredService(); @@ -18,16 +19,18 @@ public string RenderInstruction(Agent agent, Dictionary? renderD instructions.AddRange(secondaryInstructions); // update states - var renderDict = renderData != null ? new Dictionary(renderData ?? []) : CollectRenderData(agent); - renderDict[TemplateRenderConstant.RENDER_AGENT] = agent; + var renderDict = renderData != null + ? new ConcurrentDictionary(renderData) + : CollectRenderData(agent); + renderDict.AddOrUpdate(TemplateRenderConstant.RENDER_AGENT, agent, (key, curValue) => agent); var res = render.Render(string.Join("\r\n", instructions), renderDict); return res; } - public bool RenderFunction(Agent agent, FunctionDef def, Dictionary? renderData = null) + public bool RenderFunction(Agent agent, FunctionDef def, IDictionary? renderData = null) { - var renderDict = renderData ?? agent.TemplateDict; + var renderDict = new Dictionary(renderData ?? agent.TemplateDict ?? []); var isRender = true; var channels = def.Channels; @@ -41,7 +44,10 @@ public bool RenderFunction(Agent agent, FunctionDef def, Dictionary? renderData = null) + public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def, IDictionary? renderData = null) { - var parameterDef = def?.Parameters; + var parameterDef = def?.Parameters?.DeepClone(options: _options); var propertyDef = parameterDef?.Properties; - if (propertyDef == null) return null; + if (propertyDef == null) + { + return null; + } - var renderDict = renderData ?? agent.TemplateDict; + var renderDict = new Dictionary(renderData ?? agent.TemplateDict ?? []); var visibleExpress = "visibility_expression"; var root = propertyDef.RootElement; var iterator = root.EnumerateObject(); @@ -105,7 +114,7 @@ public bool RenderFunction(Agent agent, FunctionDef def, Dictionary) PrepareInstructionAndFunctions(Agent agent, Dictionary? renderData = null, StringComparer ? comparer = null) + public (string, IEnumerable) PrepareInstructionAndFunctions(Agent agent, IDictionary? renderData = null, StringComparer ? comparer = null) { var text = string.Empty; if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty()) @@ -117,7 +126,7 @@ public bool RenderFunction(Agent agent, FunctionDef def, Dictionary? renderData = null) + public string RenderTemplate(Agent agent, string templateName, IDictionary? renderData = null) { var conv = _services.GetRequiredService(); var render = _services.GetRequiredService(); @@ -125,8 +134,10 @@ public string RenderTemplate(Agent agent, string templateName, Dictionary x.Name == templateName)?.Content ?? string.Empty; // update states - var renderDict = renderData != null ? new Dictionary(renderData ?? []) : CollectRenderData(agent); - renderDict[TemplateRenderConstant.RENDER_AGENT] = agent; + var renderDict = renderData != null + ? new ConcurrentDictionary(renderData) + : CollectRenderData(agent); + renderDict.AddOrUpdate(TemplateRenderConstant.RENDER_AGENT, agent, (key, curValue) => agent); // render liquid template var content = render.Render(template, renderDict); @@ -137,7 +148,7 @@ public string RenderTemplate(Agent agent, string templateName, Dictionary dict) + public bool RenderVisibility(string? visibilityExpression, IDictionary dict) { if (string.IsNullOrWhiteSpace(visibilityExpression)) { @@ -145,25 +156,38 @@ public bool RenderVisibility(string? visibilityExpression, Dictionary(); + var copy = new Dictionary(dict); var result = render.Render(visibilityExpression, new Dictionary { - { "states", dict ?? [] } + { "states", copy } }); return result.IsEqualTo("visible"); } - public Dictionary CollectRenderData(Agent agent) + public ConcurrentDictionary CollectRenderData(Agent agent) { var state = _services.GetRequiredService(); - var renderDict = new Dictionary(agent?.TemplateDict ?? []); - foreach (var t in state.GetStates()) + var innerDict = new ConcurrentDictionary(); + if (agent?.TemplateDict != null) + { + var dict = new ConcurrentDictionary(agent.TemplateDict); + if (dict != null) + { + foreach (var p in dict) + { + innerDict.AddOrUpdate(p.Key, p.Value, (key, curValue) => p.Value); + } + } + } + + foreach (var p in state.GetStates()) { - renderDict[t.Key] = t.Value; + innerDict.AddOrUpdate(p.Key, p.Value, (key, curValue) => p.Value); } - return renderDict; + return innerDict; } #region Private methods diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index ca025447c..d06cc3764 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -1,6 +1,6 @@ +using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Repositories.Settings; using System.IO; -using System.Reflection; namespace BotSharp.Core.Agents.Services; @@ -10,24 +10,19 @@ public partial class AgentService : IAgentService private readonly IBotSharpRepository _db; private readonly ILogger _logger; private readonly AgentSettings _agentSettings; - private readonly JsonSerializerOptions _options; + private readonly BotSharpOptions _options; public AgentService(IServiceProvider services, IBotSharpRepository db, ILogger logger, - AgentSettings agentSettings) + AgentSettings agentSettings, + BotSharpOptions options) { _services = services; _db = db; _logger = logger; _agentSettings = agentSettings; - _options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true, - AllowTrailingCommas = true - }; + _options = options; } public string GetDataDir() @@ -49,7 +44,10 @@ public string GetAgentDataDir(string agentId) public async Task> GetUserAgents(string userId) { - if (string.IsNullOrEmpty(userId)) return []; + if (string.IsNullOrEmpty(userId)) + { + return []; + } var userAgents = _db.GetUserAgents(userId); return userAgents; diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs index 3615faf52..66ce90446 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Infrastructures; namespace BotSharp.Core.Infrastructures; diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 3556cabc6..43accacce 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -23,7 +23,7 @@ public async Task Execute( { var agentService = _services.GetRequiredService(); var state = _services.GetRequiredService(); - Agent agent = await agentService.LoadAgent(agentId); + var agent = await agentService.LoadAgent(agentId); var response = new InstructResult { diff --git a/src/Infrastructure/BotSharp.Core/MCP/Hooks/MCPToolAgentHook.cs b/src/Infrastructure/BotSharp.Core/MCP/Hooks/MCPToolAgentHook.cs index 743bf4c06..b117555fd 100644 --- a/src/Infrastructure/BotSharp.Core/MCP/Hooks/MCPToolAgentHook.cs +++ b/src/Infrastructure/BotSharp.Core/MCP/Hooks/MCPToolAgentHook.cs @@ -27,7 +27,7 @@ public override void OnAgentMcpToolLoaded(Agent agent) agent.SecondaryFunctions ??= []; - var functions = GetMcpContent(agent).Result; + var functions = GetMcpContent(agent).ConfigureAwait(false).GetAwaiter().GetResult(); agent.SecondaryFunctions = agent.SecondaryFunctions.Concat(functions).DistinctBy(x => x.Name, StringComparer.OrdinalIgnoreCase).ToList(); } diff --git a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs index 083ff85aa..96c703121 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs @@ -159,13 +159,13 @@ public PluginDef UpdatePluginStatus(IServiceProvider services, string id, bool e var agentService = services.GetRequiredService(); foreach (var agentId in dependentAgentIds) { - var agent = agentService.LoadAgent(agentId).Result; + var agent = agentService.LoadAgent(agentId).ConfigureAwait(false).GetAwaiter().GetResult(); agent.Disabled = false; agentService.UpdateAgent(agent, AgentField.Disabled); if (agent.InheritAgentId != null) { - agent = agentService.LoadAgent(agent.InheritAgentId).Result; + agent = agentService.LoadAgent(agent.InheritAgentId).ConfigureAwait(false).GetAwaiter().GetResult(); agent.Disabled = false; agentService.UpdateAgent(agent, AgentField.Disabled); } @@ -183,7 +183,7 @@ public PluginDef UpdatePluginStatus(IServiceProvider services, string id, bool e var agentService = services.GetRequiredService(); foreach (var agentId in plugin.AgentIds) { - var agent = agentService.LoadAgent(agentId).Result; + var agent = agentService.LoadAgent(agentId).ConfigureAwait(false).GetAwaiter().GetResult(); if (agent != null) { agent.Disabled = true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs index 53a54669e..3b1231be0 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs @@ -74,7 +74,7 @@ public override bool OnFunctionsLoaded(List functions) if (rule != null) { var agentService = _services.GetRequiredService(); - var redirectAgent = agentService.GetAgent(rule.RedirectTo).Result; + var redirectAgent = agentService.GetAgent(rule.RedirectTo).ConfigureAwait(false).GetAwaiter().GetResult(); var json = JsonSerializer.Serialize(new { diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs index a37e24077..278fd155b 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs @@ -12,7 +12,7 @@ public static void FixMalformedResponse(IServiceProvider services, FunctionCallF var agents = agentService.GetAgents(new AgentFilter { Types = [AgentType.Task] - }).Result.Items.ToList(); + }).ConfigureAwait(false).GetAwaiter().GetResult().Items.ToList(); var malformed = false; // Sometimes it populate malformed Function in Agent name diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index 4212c08c5..167866ddb 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -46,7 +46,7 @@ public string OriginAgentId { Types = [AgentType.Routing], Pager = new Pagination { Size = 100 } - }).Result.Items.Select(x => x.Id).ToArray(); + }).ConfigureAwait(false).GetAwaiter().GetResult().Items.Select(x => x.Id).ToArray(); } return _stack.Where(x => !_routerAgentIds.Contains(x)).LastOrDefault() ?? string.Empty; @@ -87,7 +87,7 @@ public void Push(string agentId, string? reason = null, bool updateLazyRouting = if (!Guid.TryParse(agentId, out _)) { var agentService = _services.GetRequiredService(); - var agents = agentService.GetAgentOptions([agentId], byName: true).Result; + var agents = agentService.GetAgentOptions([agentId], byName: true).ConfigureAwait(false).GetAwaiter().GetResult(); if (agents.Count > 0) { @@ -129,8 +129,8 @@ public void Pop(string? reason = null, bool updateLazyRouting = true) } // Run the routing rule - var agency = _services.GetRequiredService(); - var agent = agency.GetAgent(currentAgentId).Result; + var agentService = _services.GetRequiredService(); + var agent = agentService.GetAgent(currentAgentId).ConfigureAwait(false).GetAwaiter().GetResult(); var message = new RoleDialogModel(AgentRole.User, $"Try to route to agent {agent.Name}") { diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs index 2124da16e..797b76fc2 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -46,7 +46,7 @@ public TemplateRender(IServiceProvider services, ILogger logger) }); } - public string Render(string template, Dictionary dict) + public string Render(string template, IDictionary dict) { if (_parser.TryParse(template, out var t, out var error)) { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index 514666926..4ba2e30c0 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -1,11 +1,7 @@ -using BotSharp.Abstraction.Crontab; using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Abstraction.MessageHub.Observers; -using BotSharp.Core.MessageHub; -using BotSharp.Core.MessageHub.Observers; using BotSharp.Plugin.ChatHub.Hooks; using BotSharp.Plugin.ChatHub.Observers; -using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; namespace BotSharp.Plugin.ChatHub; @@ -34,6 +30,5 @@ public void RegisterDI(IServiceCollection services, IConfiguration config) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs deleted file mode 100644 index dd584f172..000000000 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs +++ /dev/null @@ -1,62 +0,0 @@ -using BotSharp.Abstraction.Conversations.Dtos; -using BotSharp.Abstraction.Conversations.Enums; -using BotSharp.Abstraction.Crontab; -using BotSharp.Abstraction.Crontab.Models; -using Microsoft.AspNetCore.SignalR; - -namespace BotSharp.Plugin.ChatHub.Hooks; - -public class ChatHubCrontabHook : ICrontabHook -{ - private readonly IServiceProvider _services; - private readonly IHubContext _chatHub; - private readonly ILogger _logger; - private readonly IUserIdentity _user; - private readonly BotSharpOptions _options; - private readonly ChatHubSettings _settings; - - public ChatHubCrontabHook( - IServiceProvider services, - IHubContext chatHub, - ILogger logger, - IUserIdentity user, - BotSharpOptions options, - ChatHubSettings settings) - { - _services = services; - _chatHub = chatHub; - _logger = logger; - _user = user; - _options = options; - _settings = settings; - } - - public async Task OnCronTriggered(CrontabItem item) - { - var data = new ChatResponseDto() - { - ConversationId = item.ConversationId, - MessageId = Guid.NewGuid().ToString(), - Text = item.ExecutionResult, - Function = "", - Sender = new() - { - FirstName = "Crontab", - LastName = "AI", - Role = AgentRole.Assistant - } - }; - - await SendEvent(item, data); - } - - private async Task SendEvent(CrontabItem item, ChatResponseDto data) - { - try - { - var json = JsonSerializer.Serialize(data, _options.JsonSerializerOptions); - await _chatHub.Clients.User(item.UserId).SendAsync(ChatEvent.OnNotificationGenerated, json); - } - catch { } - } -} diff --git a/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Embeddings.cs b/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Embeddings.cs index 9d7f357fe..5522e6191 100644 --- a/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Embeddings.cs +++ b/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Embeddings.cs @@ -35,8 +35,8 @@ private IEnumerable ProcessBase(EmbeddingRequestBase requestBody) }; - var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result; - var stream = response.Content.ReadAsStreamAsync().Result; + var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false).GetAwaiter().GetResult(); + var stream = response.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult(); byte[] buffer = new byte[8192]; int bytesRead; diff --git a/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Images.cs b/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Images.cs index 22fd81ad2..ee0a1b76e 100644 --- a/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Images.cs +++ b/src/Plugins/BotSharp.Plugin.MetaGLM/Modules/Images.cs @@ -31,8 +31,8 @@ private IEnumerable GenerateBase(ImageRequestBase requestBody) }; - var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result; - var stream = response.Content.ReadAsStreamAsync().Result; + var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false).GetAwaiter().GetResult(); + var stream = response.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult(); byte[] buffer = new byte[8192]; int bytesRead; diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Hooks/SqlPlannerAgentHook.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Hooks/SqlPlannerAgentHook.cs index 64645eac8..f2a8aca96 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Hooks/SqlPlannerAgentHook.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Hooks/SqlPlannerAgentHook.cs @@ -20,7 +20,7 @@ public override bool OnInstructionLoaded(string template, Dictionary GetReplySpeechFileName(string conversationId, private static string GetHints(string agentId, AssistantMessage reply, IServiceProvider sp) { var agentService = sp.GetRequiredService(); - var agent = agentService.GetAgent(agentId).Result; + var agent = agentService.GetAgent(agentId).ConfigureAwait(false).GetAwaiter().GetResult(); var extraWords = new List(); HookEmitter.Emit(sp, hook => extraWords.AddRange(hook.OnModelTranscriptPrompt(agent)), agentId); diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs index 3a4d62f2b..37a0a0a21 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.cs @@ -44,7 +44,7 @@ public void SetAgent(Agent agent) _ => AriaRole.Generic }; element = _instance.GetPage(contextId).Locator($"[name='{context.ElementName}']"); - var count = element.CountAsync().Result; + var count = element.CountAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (count == 0) { _logger.LogError($"Can't locate element {role} {context.ElementName}"); diff --git a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs index 45d6dc6d5..506858cc3 100644 --- a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs +++ b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs @@ -6,6 +6,7 @@ using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Utilities; +using System.Collections.Concurrent; namespace BotSharp.Plugin.Google.Core { @@ -41,32 +42,32 @@ public Task InheritAgent(Agent agent) return Task.CompletedTask; } - public string RenderInstruction(Agent agent, Dictionary? renderData = null) + public string RenderInstruction(Agent agent, IDictionary? renderData = null) { return "Fake Instruction"; } - public string RenderTemplate(Agent agent, string templateName, Dictionary? renderData = null) + public string RenderTemplate(Agent agent, string templateName, IDictionary? renderData = null) { return $"Rendered template for {templateName}"; } - public bool RenderFunction(Agent agent, FunctionDef def, Dictionary? renderData = null) + public bool RenderFunction(Agent agent, FunctionDef def, IDictionary? renderData = null) { return true; } - public (string, IEnumerable) PrepareInstructionAndFunctions(Agent agent, Dictionary? renderData = null, StringComparer? comparer = null) + public (string, IEnumerable) PrepareInstructionAndFunctions(Agent agent, IDictionary? renderData = null, StringComparer? comparer = null) { return (string.Empty, []); } - public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def, Dictionary? renderData = null) + public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def, IDictionary? renderData = null) { return def.Parameters; } - public bool RenderVisibility(string? visibilityExpression, Dictionary dict) + public bool RenderVisibility(string? visibilityExpression, IDictionary dict) { return true; } @@ -121,7 +122,7 @@ public Task> GetAgentUtilityOptions() return Task.FromResult(Enumerable.Empty()); } - public Dictionary CollectRenderData(Agent agent) + public ConcurrentDictionary CollectRenderData(Agent agent) { return []; }