From 3da73d6fce84f63718ec8b7e1d96ce33eab63170 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 30 Jun 2025 23:07:58 -0500 Subject: [PATCH 01/51] init --- BotSharp.sln | 11 +++ .../Messaging/BotSharpMessageParser.cs | 4 + .../Messaging/Enums/RichTypeEnum.cs | 2 + .../Template/JsCodeTemplateMessage.cs | 10 +++ .../BotSharp.Plugin.ChartHandler.csproj | 29 +++++++ .../ChartHandlerPlugin.cs | 18 +++++ .../Enums/UtilityName.cs | 6 ++ .../Functions/GenerateChartFn.cs | 80 +++++++++++++++++++ .../Hooks/ChartHandlerUtilityHook.cs | 24 ++++++ .../LlmContext/LlmContextIn.cs | 10 +++ .../BotSharp.Plugin.ChartHandler/Using.cs | 32 ++++++++ .../functions/util-chart-generate_chart.json | 14 ++++ .../util-chart-generate_chart.fn.liquid | 1 + .../util-chart-plot-instruction.liquid | 9 +++ .../BotSharp.Plugin.FileHandler.csproj | 6 +- .../LlmContexts/LlmContextIn.cs | 4 - src/WebStarter/WebStarter.csproj | 3 +- src/WebStarter/appsettings.json | 1 + 18 files changed, 254 insertions(+), 10 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/ChartHandlerPlugin.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/Functions/GenerateChartFn.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextIn.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/Using.cs create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-generate_chart.json create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid diff --git a/BotSharp.sln b/BotSharp.sln index 972dc35f..4aa709de 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -143,6 +143,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.LLM.Tests", "tests EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Test.RealtimeVoice", "tests\BotSharp.Test.RealtimeVoice\BotSharp.Test.RealtimeVoice.csproj", "{B067B126-88CD-4282-BEEF-7369B64423EF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.ChartHandler", "src\Plugins\BotSharp.Plugin.ChartHandler\BotSharp.Plugin.ChartHandler.csproj", "{0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -599,6 +601,14 @@ Global {B067B126-88CD-4282-BEEF-7369B64423EF}.Release|Any CPU.Build.0 = Release|Any CPU {B067B126-88CD-4282-BEEF-7369B64423EF}.Release|x64.ActiveCfg = Release|Any CPU {B067B126-88CD-4282-BEEF-7369B64423EF}.Release|x64.Build.0 = Release|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Debug|x64.ActiveCfg = Debug|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Debug|x64.Build.0 = Debug|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Release|Any CPU.Build.0 = Release|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Release|x64.ActiveCfg = Release|Any CPU + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -668,6 +678,7 @@ Global {7D0DB012-9798-4BB9-B15B-A5B0B7B3B094} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} {7C0C7D13-D161-4AB0-9C29-83A0F1FF990E} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} {B067B126-88CD-4282-BEEF-7369B64423EF} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} + {0428DEAA-E4FE-4259-A6D8-6EDD1A9D0702} = {51AFE054-AE99-497D-A593-69BAEFB5106F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs index 1f495a44..57db70d7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs @@ -38,6 +38,10 @@ public static class BotSharpMessageParser { targetType = typeof(TextMessage); } + else if (richType == RichTypeEnum.JsCode) + { + targetType = typeof(JsCodeTemplateMessage); + } else if (richType == RichTypeEnum.GenericTemplate) { if (root.TryGetProperty("element_type", out element)) diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs index 7603f790..6227f522 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs @@ -9,4 +9,6 @@ public static class RichTypeEnum public const string QuickReply = "quick_reply"; public const string Text = "text"; public const string Attachment = "attachment"; + + public const string JsCode = "js_code"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs new file mode 100644 index 00000000..1d58546b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template; + +public class JsCodeTemplateMessage : IRichMessage +{ + [JsonPropertyName("rich_type")] + public string RichType => RichTypeEnum.JsCode; + + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj new file mode 100644 index 00000000..8051876c --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/ChartHandlerPlugin.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/ChartHandlerPlugin.cs new file mode 100644 index 00000000..1161f6e4 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/ChartHandlerPlugin.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.Configuration; + +namespace BotSharp.Plugin.ChartHandler; + +public class ChartHandlerPlugin : IBotSharpPlugin +{ + public string Id => "9dacac1d-2e29-4f01-9d66-b0201f05a9fa"; + public string Name => "Chart Plotter"; + public string Description => "AI plots chart"; + public string IconUrl => "https://cdn-icons-png.flaticon.com/512/423/423786.png"; + public string[] AgentIds => []; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddScoped(); + } + +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs new file mode 100644 index 00000000..a93a17e5 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Plugin.ChartHandler.Enums; + +public class UtilityName +{ + public const string ChartGenerator = "chart-generator"; +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/GenerateChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/GenerateChartFn.cs new file mode 100644 index 00000000..c7e66f1f --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/GenerateChartFn.cs @@ -0,0 +1,80 @@ +using BotSharp.Abstraction.Messaging.Models.RichContent.Template; + +namespace BotSharp.Plugin.ChartHandler.Functions; + +public class GenerateChartFn : IFunctionCallback +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public string Name => "util-chart-generate_chart"; + public string Indication => "Generating chart"; + + + public GenerateChartFn( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task Execute(RoleDialogModel message) + { + var db = _services.GetRequiredService(); + var agentService = _services.GetRequiredService(); + var convService = _services.GetRequiredService(); + + var args = JsonSerializer.Deserialize(message.FunctionArgs); + var agent = await agentService.GetAgent(message.CurrentAgentId); + var inst = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, "util-chart-plot-instruction"); + var innerAgent = new Agent + { + Id = agent.Id, + Name = agent.Name, + Instruction = inst, + TemplateDict = new Dictionary + { + { "plotting_requirement", args?.PlottingRequirement ?? string.Empty }, + { "chart_element_id", $"chart-{message.MessageId}" } + } + }; + + var response = await GetChatCompletion(innerAgent, [ + new RoleDialogModel(AgentRole.User, "Please follow the instruction to generate the javascript code.") + { + CurrentAgentId = message.CurrentAgentId, + MessageId = message.MessageId + } + ]); + + message.Content = response; + message.RichContent = new RichContent + { + Recipient = new Recipient { Id = convService.ConversationId }, + Message = new JsCodeTemplateMessage + { + Text = response + } + }; + message.StopCompletion = true; + return true; + } + + private async Task GetChatCompletion(Agent agent, List dialogs) + { + try + { + var llmProviderService = _services.GetRequiredService(); + var completion = CompletionProvider.GetChatCompletion(_services, provider: "openai", model: "gpt-4.1"); + var response = await completion.GetChatCompletions(agent, dialogs); + return response.Content; + } + catch (Exception ex) + { + var error = $"Error when plotting chart. {ex.Message}"; + _logger.LogWarning(ex, error); + return error; + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs new file mode 100644 index 00000000..51600147 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs @@ -0,0 +1,24 @@ +namespace BotSharp.Plugin.ChartHandler.Hooks; + +public class ChartHandlerUtilityHook : IAgentUtilityHook +{ + private const string GENERATE_CHART_FN = "util-chart-generate_chart"; + + public void AddUtilities(List utilities) + { + var item = new AgentUtility + { + Category = "chart", + Name = UtilityName.ChartGenerator, + Items = [ + new UtilityItem + { + FunctionName = GENERATE_CHART_FN, + TemplateName = $"{GENERATE_CHART_FN}.fn" + } + ] + }; + + utilities.Add(item); + } +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextIn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextIn.cs new file mode 100644 index 00000000..daaef271 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextIn.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.ChartHandler.LlmContext; + +public class LlmContextIn +{ + [JsonPropertyName("plotting_requirement")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? PlottingRequirement { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Using.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Using.cs new file mode 100644 index 00000000..fa94465c --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Using.cs @@ -0,0 +1,32 @@ +global using System; +global using System.Collections.Generic; +global using System.Text; +global using System.Linq; +global using System.Text.Json; +global using System.Net.Mime; +global using System.Threading.Tasks; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using BotSharp.Abstraction.Agents; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Plugins; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Functions; +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Files.Enums; +global using BotSharp.Abstraction.Files.Models; +global using BotSharp.Abstraction.Files; +global using BotSharp.Abstraction.MLTasks; +global using BotSharp.Abstraction.Utilities; +global using BotSharp.Abstraction.Agents.Settings; +global using BotSharp.Abstraction.Functions.Models; +global using BotSharp.Abstraction.Repositories; +global using BotSharp.Abstraction.Settings; +global using BotSharp.Abstraction.Messaging; +global using BotSharp.Abstraction.Messaging.Models.RichContent; +global using BotSharp.Abstraction.Options; +global using BotSharp.Core.Infrastructures; +global using BotSharp.Plugin.ChartHandler.Enums; +global using BotSharp.Plugin.ChartHandler.LlmContext; +global using BotSharp.Plugin.ChartHandler.Hooks; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-generate_chart.json b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-generate_chart.json new file mode 100644 index 00000000..937647f6 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-generate_chart.json @@ -0,0 +1,14 @@ +{ + "name": "util-chart-generate_chart", + "description": "If the user requests you plotting or generating chart, you can call this function to generate chart in any format that user requested.", + "parameters": { + "type": "object", + "properties": { + "plotting_requirement": { + "type": "string", + "description": "The requirement that user posted for plotting or generating chart." + } + }, + "required": [ "plotting_requirement" ] + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid new file mode 100644 index 00000000..c5fb2d00 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid @@ -0,0 +1 @@ +Please call function util-chart-generate_chart if user wants to generate a chart. \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid new file mode 100644 index 00000000..272f3a3d --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid @@ -0,0 +1,9 @@ +Please take a look at "Requirement" and generate a javascript code that can be used to render a pie chart on a canvas element with id {{ chart_element_id }}. + +=== Requirement === +{{ plotting_requirement }} + + +*** Important *** +** You must not create any new html element. +** You must output the js code only, and wrap it between the script tags. diff --git a/src/Plugins/BotSharp.Plugin.FileHandler/BotSharp.Plugin.FileHandler.csproj b/src/Plugins/BotSharp.Plugin.FileHandler/BotSharp.Plugin.FileHandler.csproj index 2147d178..2b343db0 100644 --- a/src/Plugins/BotSharp.Plugin.FileHandler/BotSharp.Plugin.FileHandler.csproj +++ b/src/Plugins/BotSharp.Plugin.FileHandler/BotSharp.Plugin.FileHandler.csproj @@ -1,4 +1,4 @@ - + $(TargetFramework) @@ -9,10 +9,6 @@ $(GenerateDocumentationFile) $(SolutionDir)packages - - - - diff --git a/src/Plugins/BotSharp.Plugin.FileHandler/LlmContexts/LlmContextIn.cs b/src/Plugins/BotSharp.Plugin.FileHandler/LlmContexts/LlmContextIn.cs index a35e83bd..9280bfd7 100644 --- a/src/Plugins/BotSharp.Plugin.FileHandler/LlmContexts/LlmContextIn.cs +++ b/src/Plugins/BotSharp.Plugin.FileHandler/LlmContexts/LlmContextIn.cs @@ -12,10 +12,6 @@ public class LlmContextIn [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ImageDescription { get; set; } - //[JsonPropertyName("image_url")] - //[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - //public string? ImageUrl { get; set; } - [JsonPropertyName("image_urls")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IEnumerable? ImageUrls { get; set; } diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 5db5199f..2610d4f1 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -36,11 +36,11 @@ - + @@ -75,6 +75,7 @@ + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 2b217318..4e7a4263 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -539,6 +539,7 @@ "BotSharp.Plugin.FileHandler", "BotSharp.Plugin.EmailHandler", "BotSharp.Plugin.AudioHandler", + "BotSharp.Plugin.ChartHandler", "BotSharp.Plugin.TencentCos" ], "ExcludedFunctions": [ From e4ad50cb7b9bba81bfab3c9962670276c513efca Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 1 Jul 2025 09:33:25 -0500 Subject: [PATCH 02/51] update instruction --- .../templates/util-chart-plot-instruction.liquid | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid index 272f3a3d..c396d793 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid @@ -1,9 +1,12 @@ -Please take a look at "Requirement" and generate a javascript code that can be used to render a pie chart on a canvas element with id {{ chart_element_id }}. +Please take a look at "Plotting Requirement" and generate a javascript code that can be used to render a chart on a div html element with id {{ chart_element_id }}. -=== Requirement === +=== Plotting Requirement === {{ plotting_requirement }} *** Important *** +** You must not generate multiple script tags, which means only one is allowed. ** You must not create any new html element. -** You must output the js code only, and wrap it between the script tags. +** You must output the javascript code only, and wrap it between the script tag. +** You need to import Plotly.js in the same script tag. The script source should be "https://cdn.plot.ly/plotly-3.0.1.min.js". +** You must not apply any styles on the div html element. \ No newline at end of file From ba485cef48b7ee67890d2f13cf0864e78d24f8f9 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 1 Jul 2025 12:01:09 -0500 Subject: [PATCH 03/51] rename and refine instructions --- .../BotSharp.Core/BotSharp.Core.csproj | 4 ++-- .../Instruct/FileInstructService.SelectFile.cs | 2 +- ... => util-file-select_file_instruction.liquid} | 0 .../BotSharp.Plugin.ChartHandler.csproj | 11 ++++++----- .../Enums/UtilityName.cs | 2 +- .../{GenerateChartFn.cs => PlotChartFn.cs} | 16 ++++++++-------- .../Hooks/ChartHandlerUtilityHook.cs | 16 ++++++++-------- ...ate_chart.json => util-chart-plot_chart.json} | 6 +++--- .../util-chart-generate_chart.fn.liquid | 1 - .../templates/util-chart-plot_chart.fn.liquid | 1 + ...liquid => util-chart-plot_instruction.liquid} | 1 + 11 files changed, 31 insertions(+), 29 deletions(-) rename src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/{select_file_prompt.liquid => util-file-select_file_instruction.liquid} (100%) rename src/Plugins/BotSharp.Plugin.ChartHandler/Functions/{GenerateChartFn.cs => PlotChartFn.cs} (87%) rename src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/{util-chart-generate_chart.json => util-chart-plot_chart.json} (66%) delete mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_chart.fn.liquid rename src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/{util-chart-plot-instruction.liquid => util-chart-plot_instruction.liquid} (91%) diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index ddcf68cb..635d35d7 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -86,7 +86,6 @@ - @@ -97,6 +96,7 @@ + @@ -188,7 +188,7 @@ PreserveNewest - + PreserveNewest diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs index 13525fb1..e00f9158 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.SelectFile.cs @@ -65,7 +65,7 @@ public partial class FileInstructService }).ToList(); var agentId = !string.IsNullOrWhiteSpace(options.AgentId) ? options.AgentId : BuiltInAgentId.UtilityAssistant; - var template = !string.IsNullOrWhiteSpace(options.Template) ? options.Template : "select_file_prompt"; + var template = !string.IsNullOrWhiteSpace(options.Template) ? options.Template : "util-file-select_file_instruction"; var foundAgent = db.GetAgent(agentId); var prompt = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, template); diff --git a/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_file_prompt.liquid b/src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-file-select_file_instruction.liquid similarity index 100% rename from src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/select_file_prompt.liquid rename to src/Infrastructure/BotSharp.Core/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-file-select_file_instruction.liquid diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj index 8051876c..4e7e8a21 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj @@ -7,18 +7,19 @@ - - + + + - + PreserveNewest - + PreserveNewest - + PreserveNewest diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs index a93a17e5..ee97a4a5 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Enums/UtilityName.cs @@ -2,5 +2,5 @@ namespace BotSharp.Plugin.ChartHandler.Enums; public class UtilityName { - public const string ChartGenerator = "chart-generator"; + public const string ChartPlotter = "chart-plotter"; } diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/GenerateChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs similarity index 87% rename from src/Plugins/BotSharp.Plugin.ChartHandler/Functions/GenerateChartFn.cs rename to src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs index c7e66f1f..afda35ed 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/GenerateChartFn.cs +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs @@ -2,18 +2,18 @@ using BotSharp.Abstraction.Messaging.Models.RichContent.Template; namespace BotSharp.Plugin.ChartHandler.Functions; -public class GenerateChartFn : IFunctionCallback +public class PlotChartFn : IFunctionCallback { private readonly IServiceProvider _services; - private readonly ILogger _logger; + private readonly ILogger _logger; - public string Name => "util-chart-generate_chart"; - public string Indication => "Generating chart"; + public string Name => "util-chart-plot_chart"; + public string Indication => "Plotting chart"; - public GenerateChartFn( + public PlotChartFn( IServiceProvider services, - ILogger logger) + ILogger logger) { _services = services; _logger = logger; @@ -27,7 +27,7 @@ public class GenerateChartFn : IFunctionCallback var args = JsonSerializer.Deserialize(message.FunctionArgs); var agent = await agentService.GetAgent(message.CurrentAgentId); - var inst = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, "util-chart-plot-instruction"); + var inst = db.GetAgentTemplate(BuiltInAgentId.UtilityAssistant, "util-chart-plot_instruction"); var innerAgent = new Agent { Id = agent.Id, @@ -48,7 +48,7 @@ public class GenerateChartFn : IFunctionCallback } ]); - message.Content = response; + message.Content = "Here is the chart you ask for:"; message.RichContent = new RichContent { Recipient = new Recipient { Id = convService.ConversationId }, diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs index 51600147..02793ffe 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Hooks/ChartHandlerUtilityHook.cs @@ -2,21 +2,21 @@ namespace BotSharp.Plugin.ChartHandler.Hooks; public class ChartHandlerUtilityHook : IAgentUtilityHook { - private const string GENERATE_CHART_FN = "util-chart-generate_chart"; + private const string PLOT_CHART_FN = "util-chart-plot_chart"; public void AddUtilities(List utilities) { var item = new AgentUtility { Category = "chart", - Name = UtilityName.ChartGenerator, + Name = UtilityName.ChartPlotter, Items = [ - new UtilityItem - { - FunctionName = GENERATE_CHART_FN, - TemplateName = $"{GENERATE_CHART_FN}.fn" - } - ] + new UtilityItem + { + FunctionName = PLOT_CHART_FN, + TemplateName = $"{PLOT_CHART_FN}.fn" + } + ] }; utilities.Add(item); diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-generate_chart.json b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-plot_chart.json similarity index 66% rename from src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-generate_chart.json rename to src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-plot_chart.json index 937647f6..da950832 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-generate_chart.json +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-chart-plot_chart.json @@ -1,12 +1,12 @@ { - "name": "util-chart-generate_chart", - "description": "If the user requests you plotting or generating chart, you can call this function to generate chart in any format that user requested.", + "name": "util-chart-plot_chart", + "description": "If the user requests you plotting or generating charts, you can call this function to generate charts in any format that user requested.", "parameters": { "type": "object", "properties": { "plotting_requirement": { "type": "string", - "description": "The requirement that user posted for plotting or generating chart." + "description": "The requirement that user posted for plotting or generating charts." } }, "required": [ "plotting_requirement" ] diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid deleted file mode 100644 index c5fb2d00..00000000 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-generate_chart.fn.liquid +++ /dev/null @@ -1 +0,0 @@ -Please call function util-chart-generate_chart if user wants to generate a chart. \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_chart.fn.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_chart.fn.liquid new file mode 100644 index 00000000..e54b3c4a --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_chart.fn.liquid @@ -0,0 +1 @@ +Please call function util-chart-plot_chart if user wants to plot or generate charts. \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid similarity index 91% rename from src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid rename to src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid index c396d793..50a27eff 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot-instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid @@ -9,4 +9,5 @@ Please take a look at "Plotting Requirement" and generate a javascript code that ** You must not create any new html element. ** You must output the javascript code only, and wrap it between the script tag. ** You need to import Plotly.js in the same script tag. The script source should be "https://cdn.plot.ly/plotly-3.0.1.min.js". +** You need to add the MODE bar for each chart you plot. ** You must not apply any styles on the div html element. \ No newline at end of file From fdfaffb4789691af2364ec879d096ed833a3960d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 1 Jul 2025 12:02:21 -0500 Subject: [PATCH 04/51] minor change --- .../templates/util-chart-plot_instruction.liquid | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid index 50a27eff..fd7a5e7a 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid @@ -5,9 +5,9 @@ Please take a look at "Plotting Requirement" and generate a javascript code that *** Important *** -** You must not generate multiple script tags, which means only one is allowed. +** You must not generate multiple script tag pairs, which means only one is allowed. ** You must not create any new html element. -** You must output the javascript code only, and wrap it between the script tag. +** You must output the javascript code only, and wrap it between one pair of script tags. ** You need to import Plotly.js in the same script tag. The script source should be "https://cdn.plot.ly/plotly-3.0.1.min.js". ** You need to add the MODE bar for each chart you plot. ** You must not apply any styles on the div html element. \ No newline at end of file From 0b54cfa4fafffeca759e7f30bf44ac9b5484ace0 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 1 Jul 2025 15:47:18 -0500 Subject: [PATCH 05/51] refine response format --- .../Functions/PlotChartFn.cs | 5 +++-- .../LlmContext/LlmContextOut.cs | 14 ++++++++++++++ .../templates/util-chart-plot_instruction.liquid | 9 ++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs index afda35ed..02ea76b6 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs @@ -48,13 +48,14 @@ public class PlotChartFn : IFunctionCallback } ]); - message.Content = "Here is the chart you ask for:"; + var obj = response.JsonContent(); + message.Content = obj?.GreetingMessage ?? "Here is the chart you ask for:"; message.RichContent = new RichContent { Recipient = new Recipient { Id = convService.ConversationId }, Message = new JsCodeTemplateMessage { - Text = response + Text = obj?.JsCode ?? string.Empty } }; message.StopCompletion = true; diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs new file mode 100644 index 00000000..7109f72d --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.ChartHandler.LlmContext; + +public class LlmContextOut +{ + [JsonPropertyName("greeting_message")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? GreetingMessage { get; set; } + + [JsonPropertyName("js_code")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? JsCode { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid index fd7a5e7a..9e8f8034 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid @@ -10,4 +10,11 @@ Please take a look at "Plotting Requirement" and generate a javascript code that ** You must output the javascript code only, and wrap it between one pair of script tags. ** You need to import Plotly.js in the same script tag. The script source should be "https://cdn.plot.ly/plotly-3.0.1.min.js". ** You need to add the MODE bar for each chart you plot. -** You must not apply any styles on the div html element. \ No newline at end of file +** You must not apply any styles on the div html element. + +*** Response Format *** +You must output the response in the following JSON format: +{ + "greeting_message": "A short polite message that informs user that the charts have been generated.", + "js_code": "The javascript code wrapped in a pair of script tags" +} \ No newline at end of file From a9b804d08e70a7b36d513d47a59ef950037a76c5 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 2 Jul 2025 11:54:41 -0500 Subject: [PATCH 06/51] refine plot instruction --- .../util-chart-plot_instruction.liquid | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid index 9e8f8034..6f5d241c 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid @@ -1,20 +1,20 @@ -Please take a look at "Plotting Requirement" and generate a javascript code that can be used to render a chart on a div html element with id {{ chart_element_id }}. - +Please take a look at "Plotting Requirement" and generate a javascript code that can be used to render the charts on an html element. === Plotting Requirement === {{ plotting_requirement }} -*** Important *** -** You must not generate multiple script tag pairs, which means only one is allowed. -** You must not create any new html element. -** You must output the javascript code only, and wrap it between one pair of script tags. -** You need to import Plotly.js in the same script tag. The script source should be "https://cdn.plot.ly/plotly-3.0.1.min.js". +***** Important ***** +** Your output must be a single block with everything needed inside. +** You need to import Plotly.js to plot the charts. The script source should be "https://cdn.plot.ly/plotly-3.0.1.min.js". ** You need to add the MODE bar for each chart you plot. -** You must not apply any styles on the div html element. +** You must render the charts on the div html element with id {{ chart_element_id }}. +** You must not create any new html element. +** You must not apply any styles on any html element. +** You must generate as less token as possible. *** Response Format *** You must output the response in the following JSON format: { "greeting_message": "A short polite message that informs user that the charts have been generated.", - "js_code": "The javascript code wrapped in a pair of script tags" + "js_code": "The javascript code that can generate the charts as requested." } \ No newline at end of file From eb8eb43ea372ecf55f39fb288ea4610495f8366a Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 2 Jul 2025 13:12:04 -0500 Subject: [PATCH 07/51] handle null string --- .../BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs index 02ea76b6..43c917d2 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs @@ -49,7 +49,7 @@ public class PlotChartFn : IFunctionCallback ]); var obj = response.JsonContent(); - message.Content = obj?.GreetingMessage ?? "Here is the chart you ask for:"; + message.Content = obj?.GreetingMessage.IfNullOrEmptyAs("Here is the chart you ask for:"); message.RichContent = new RichContent { Recipient = new Recipient { Id = convService.ConversationId }, From 894237c416fa6e1e94351b779abf8484c5197f15 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 8 Jul 2025 17:45:54 -0500 Subject: [PATCH 08/51] add program code template --- .../Messaging/BotSharpMessageParser.cs | 12 ++++++------ .../Messaging/Enums/RichTypeEnum.cs | 3 +-- .../Messaging/Enums/TemplateTypeEnum.cs | 2 +- .../Template/JsCodeTemplateMessage.cs | 10 ---------- .../Template/ProductTemplateMessage.cs | 19 ------------------- .../Template/ProgramCodeTemplateMessage.cs | 16 ++++++++++++++++ .../Functions/PlotChartFn.cs | 7 ++++--- 7 files changed, 28 insertions(+), 41 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProgramCodeTemplateMessage.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs index 57db70d7..25a97b2b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs @@ -1,7 +1,7 @@ -using BotSharp.Abstraction.Messaging.Models.RichContent.Template; -using BotSharp.Abstraction.Messaging.Models.RichContent; using System.Text.Json; using System.Reflection; +using BotSharp.Abstraction.Messaging.Models.RichContent.Template; +using BotSharp.Abstraction.Messaging.Models.RichContent; namespace BotSharp.Abstraction.Messaging; @@ -38,9 +38,9 @@ public static class BotSharpMessageParser { targetType = typeof(TextMessage); } - else if (richType == RichTypeEnum.JsCode) + else if (richType == RichTypeEnum.ProgramCode) { - targetType = typeof(JsCodeTemplateMessage); + targetType = typeof(ProgramCodeTemplateMessage); } else if (richType == RichTypeEnum.GenericTemplate) { @@ -88,9 +88,9 @@ public static class BotSharpMessageParser { targetType = typeof(CouponTemplateMessage); } - else if (templateType == TemplateTypeEnum.Product) + else if (templateType == TemplateTypeEnum.ProgramCode) { - targetType = typeof(ProductTemplateMessage); + targetType = typeof(ProgramCodeTemplateMessage); } else if (templateType == TemplateTypeEnum.Generic) { diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs index 6227f522..8d66904b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/RichTypeEnum.cs @@ -9,6 +9,5 @@ public static class RichTypeEnum public const string QuickReply = "quick_reply"; public const string Text = "text"; public const string Attachment = "attachment"; - - public const string JsCode = "js_code"; + public const string ProgramCode = "program_code"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/TemplateTypeEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/TemplateTypeEnum.cs index 74f88735..73a4e9d4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/TemplateTypeEnum.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/TemplateTypeEnum.cs @@ -6,5 +6,5 @@ public static class TemplateTypeEnum public const string Coupon = "coupon"; public const string Generic = "generic"; public const string MultiSelect = "multi-select"; - public const string Product = "product"; + public const string ProgramCode = "program_code"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs deleted file mode 100644 index 1d58546b..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/JsCodeTemplateMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template; - -public class JsCodeTemplateMessage : IRichMessage -{ - [JsonPropertyName("rich_type")] - public string RichType => RichTypeEnum.JsCode; - - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs deleted file mode 100644 index 05af097a..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template; - -public class ProductTemplateMessage : IRichMessage, ITemplateMessage -{ - [JsonPropertyName("rich_type")] - public string RichType => RichTypeEnum.GenericTemplate; - - [JsonPropertyName("text")] - [Translate] - public string Text { get; set; } = string.Empty; - - [JsonPropertyName("template_type")] - public string TemplateType => TemplateTypeEnum.Product; -} - -public class ProductElement -{ - public string Id { get; set; } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProgramCodeTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProgramCodeTemplateMessage.cs new file mode 100644 index 00000000..60f6464a --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProgramCodeTemplateMessage.cs @@ -0,0 +1,16 @@ +namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template; + +public class ProgramCodeTemplateMessage : IRichMessage, ITemplateMessage +{ + [JsonPropertyName("rich_type")] + public string RichType => RichTypeEnum.ProgramCode; + + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; + + [JsonPropertyName("template_type")] + public virtual string TemplateType { get; set; } = TemplateTypeEnum.ProgramCode; + + [JsonPropertyName("language")] + public string Language { get; set; } = string.Empty; +} diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs index 43c917d2..ad8de0d7 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs @@ -49,13 +49,14 @@ public class PlotChartFn : IFunctionCallback ]); var obj = response.JsonContent(); - message.Content = obj?.GreetingMessage.IfNullOrEmptyAs("Here is the chart you ask for:"); + message.Content = obj?.GreetingMessage ?? "Here is the chart you ask for:"; message.RichContent = new RichContent { Recipient = new Recipient { Id = convService.ConversationId }, - Message = new JsCodeTemplateMessage + Message = new ProgramCodeTemplateMessage { - Text = obj?.JsCode ?? string.Empty + Text = obj?.JsCode ?? string.Empty, + Language = "javascript" } }; message.StopCompletion = true; From 030e306a5597ff7d88eaa9a84dec4d717247a962 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 11 Jul 2025 14:28:09 -0500 Subject: [PATCH 09/51] minor change --- .../templates/util-chart-plot_instruction.liquid | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid index 6f5d241c..9bffa73f 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-chart-plot_instruction.liquid @@ -1,4 +1,6 @@ Please take a look at "Plotting Requirement" and generate a javascript code that can be used to render the charts on an html element. + + === Plotting Requirement === {{ plotting_requirement }} From 0a6066c5b276e9db2eface9f37f0ee5b3cd77cbe Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 28 Jul 2025 17:41:28 -0500 Subject: [PATCH 10/51] refine indication --- .../Conversations/Dtos/ChatResponseDto.cs | 4 + .../Conversations/Enums/ChatEvent.cs | 22 +++ .../Models/ConversationSenderActionModel.cs | 2 - .../Models/HubObserveData.cs | 2 +- .../Models/ObserveDataBase.cs | 2 +- .../Conversations/ConversationPlugin.cs | 4 +- .../Demo/Functions/GetWeatherFn.cs | 26 +++ .../Queues => MessageHub}/MessageHub.cs | 4 +- .../Routing/RoutingService.InvokeFunction.cs | 21 ++- src/Infrastructure/BotSharp.Core/Using.cs | 3 + .../Providers/Chat/ChatCompletionProvider.cs | 4 +- .../BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 4 +- .../Helpers/ChatHubHelper.cs | 38 ++++ .../Hooks/ChatHubConversationHook.cs | 170 +++-------------- .../Hooks/ChatHubCrontabHook.cs | 18 +- .../Hooks/StreamingLogHook.cs | 174 ++++++------------ .../Hooks/WelcomeHook.cs | 36 +--- .../Observers/ChatHubObserver.cs | 146 ++++++++------- src/Plugins/BotSharp.Plugin.ChatHub/Using.cs | 3 +- .../Providers/Chat/ChatCompletionProvider.cs | 4 +- .../Providers/ChatCompletionProvider.cs | 4 +- .../Providers/Chat/ChatCompletionProvider.cs | 4 +- .../Providers/ChatCompletionProvider.cs | 4 +- 23 files changed, 305 insertions(+), 394 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ChatEvent.cs rename src/Infrastructure/BotSharp.Abstraction/{Observables => MessageHub}/Models/HubObserveData.cs (75%) rename src/Infrastructure/BotSharp.Abstraction/{Observables => MessageHub}/Models/ObserveDataBase.cs (68%) rename src/Infrastructure/BotSharp.Core/{Observables/Queues => MessageHub}/MessageHub.cs (88%) create mode 100644 src/Plugins/BotSharp.Plugin.ChatHub/Helpers/ChatHubHelper.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs index 19b5e8b2..9e4fcb41 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs @@ -32,6 +32,10 @@ public class ChatResponseDto : InstructResult [JsonPropertyName("payload")] public string? Payload { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("indication")] + public string? Indication { get; set; } + [JsonPropertyName("has_message_files")] public bool HasMessageFiles { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ChatEvent.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ChatEvent.cs new file mode 100644 index 00000000..0d68fef7 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ChatEvent.cs @@ -0,0 +1,22 @@ +namespace BotSharp.Abstraction.Conversations.Enums; + +public static class ChatEvent +{ + public const string OnConversationInitFromClient = nameof(OnConversationInitFromClient); + public const string OnMessageReceivedFromClient = nameof(OnMessageReceivedFromClient); + public const string OnMessageReceivedFromAssistant = nameof(OnMessageReceivedFromAssistant); + + public const string OnMessageDeleted = nameof(OnMessageDeleted); + public const string OnNotificationGenerated = nameof(OnNotificationGenerated); + public const string OnIndicationReceived = nameof(OnIndicationReceived); + + public const string OnConversationContentLogGenerated = nameof(OnConversationContentLogGenerated); + public const string OnConversateStateLogGenerated = nameof(OnConversateStateLogGenerated); + public const string OnAgentQueueChanged = nameof(OnAgentQueueChanged); + public const string OnStateChangeGenerated = nameof(OnStateChangeGenerated); + + public const string BeforeReceiveLlmStreamMessage = nameof(BeforeReceiveLlmStreamMessage); + public const string OnReceiveLlmStreamMessage = nameof(OnReceiveLlmStreamMessage); + public const string AfterReceiveLlmStreamMessage = nameof(AfterReceiveLlmStreamMessage); + public const string OnSenderActionGenerated = nameof(OnSenderActionGenerated); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs index b49fc55d..502cca4c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Messaging.Enums; - namespace BotSharp.Abstraction.Conversations.Models; public class ConversationSenderActionModel diff --git a/src/Infrastructure/BotSharp.Abstraction/Observables/Models/HubObserveData.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs similarity index 75% rename from src/Infrastructure/BotSharp.Abstraction/Observables/Models/HubObserveData.cs rename to src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs index bf771cb4..403adbc6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Observables/Models/HubObserveData.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Abstraction.Observables.Models; +namespace BotSharp.Abstraction.MessageHub.Models; public class HubObserveData : ObserveDataBase { diff --git a/src/Infrastructure/BotSharp.Abstraction/Observables/Models/ObserveDataBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs similarity index 68% rename from src/Infrastructure/BotSharp.Abstraction/Observables/Models/ObserveDataBase.cs rename to src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs index 17773272..62c2950d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Observables/Models/ObserveDataBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Abstraction.Observables.Models; +namespace BotSharp.Abstraction.MessageHub.Models; public abstract class ObserveDataBase { diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index a559877f..2f0af290 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -10,9 +10,9 @@ using BotSharp.Core.Messaging; using BotSharp.Core.Routing.Reasoning; using BotSharp.Core.Templating; using BotSharp.Core.Translation; -using BotSharp.Core.Observables.Queues; using Microsoft.Extensions.Configuration; -using BotSharp.Abstraction.Observables.Models; +using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Core.MessageHub; namespace BotSharp.Core.Conversations; diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs index cb6bcd88..b8bb0f39 100644 --- a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Functions; +using BotSharp.Core.MessageHub; namespace BotSharp.Core.Demo.Functions; @@ -16,6 +17,31 @@ public class GetWeatherFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { + var conv = _services.GetRequiredService(); + var messageHub = _services.GetRequiredService>(); + + await Task.Delay(1000); + + message.Indication = "Start querying weather data"; + messageHub.Push(new() + { + EventName = ChatEvent.OnIndicationReceived, + Data = message, + ServiceProvider = _services + }); + + await Task.Delay(1500); + + message.Indication = "Still working on it"; + messageHub.Push(new() + { + EventName = ChatEvent.OnIndicationReceived, + Data = message, + ServiceProvider = _services + }); + + await Task.Delay(1500); + message.Content = $"It is a sunny day!"; message.StopCompletion = false; return true; diff --git a/src/Infrastructure/BotSharp.Core/Observables/Queues/MessageHub.cs b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs similarity index 88% rename from src/Infrastructure/BotSharp.Core/Observables/Queues/MessageHub.cs rename to src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs index affb142d..a104f4f6 100644 --- a/src/Infrastructure/BotSharp.Core/Observables/Queues/MessageHub.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs @@ -1,11 +1,11 @@ using System.Reactive.Subjects; -namespace BotSharp.Core.Observables.Queues; +namespace BotSharp.Core.MessageHub; public class MessageHub where T : class { private readonly ILogger> _logger; - private readonly ISubject _observable = new Subject(); + private readonly ISubject _observable = Subject.Synchronize(new Subject()); public IObservable Events => _observable; public MessageHub(ILogger> logger) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 1001dede..0922dcdb 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -1,4 +1,4 @@ -using BotSharp.Abstraction.Hooks; +using BotSharp.Core.MessageHub; using BotSharp.Core.Routing.Executor; namespace BotSharp.Core.Routing; @@ -23,15 +23,22 @@ public partial class RoutingService // Clone message var clonedMessage = RoleDialogModel.From(message); clonedMessage.FunctionName = name; - - var progressService = _services.GetService(); clonedMessage.Indication = await funcExecutor.GetIndicatorAsync(message); - if (progressService?.OnFunctionExecuting != null) + //var progressService = _services.GetService(); + //if (progressService?.OnFunctionExecuting != null) + //{ + // await progressService.OnFunctionExecuting(clonedMessage); + //} + + var messageHub = _services.GetRequiredService>(); + messageHub.Push(new() { - await progressService.OnFunctionExecuting(clonedMessage); - } - + EventName = ChatEvent.OnIndicationReceived, + Data = clonedMessage, + ServiceProvider = _services + }); + var hooks = _services.GetHooksOrderByPriority(clonedMessage.CurrentAgentId); foreach (var hook in hooks) { diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 0e8e2762..f67baef0 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -36,6 +36,9 @@ global using BotSharp.Abstraction.Loggers.Services; global using BotSharp.Abstraction.Infrastructures.Events; global using BotSharp.Abstraction.Templating.Constants; global using BotSharp.Abstraction.Realtime.Models.Session; +global using BotSharp.Abstraction.Conversations.Enums; +global using BotSharp.Abstraction.Hooks; +global using BotSharp.Abstraction.MessageHub.Models; global using BotSharp.Core.Agents.Services; global using BotSharp.Core.Conversations.Services; global using BotSharp.Core.Infrastructures; diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs index 605bceb8..d3c6e14a 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,9 +1,9 @@ using Azure; using BotSharp.Abstraction.Files.Utilities; using BotSharp.Abstraction.Hooks; -using BotSharp.Abstraction.Observables.Models; +using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; -using BotSharp.Core.Observables.Queues; +using BotSharp.Core.MessageHub; using OpenAI.Chat; using System.ClientModel; diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index d3c3f2ac..d771d0a2 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -1,6 +1,6 @@ using BotSharp.Abstraction.Crontab; -using BotSharp.Abstraction.Observables.Models; -using BotSharp.Core.Observables.Queues; +using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Core.MessageHub; using BotSharp.Plugin.ChatHub.Hooks; using BotSharp.Plugin.ChatHub.Observers; using Microsoft.AspNetCore.Builder; diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/ChatHubHelper.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/ChatHubHelper.cs new file mode 100644 index 00000000..94bbc90d --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/ChatHubHelper.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.SignalR; +using System.Runtime.CompilerServices; + +namespace BotSharp.Plugin.ChatHub.Helpers; + +public class ChatHubHelper +{ + public static async Task SendChatEvent( + IServiceProvider services, + ILogger logger, + string @event, + string conversationId, + string userId, + T data, + string callerClass = "", + [CallerMemberName] string callerMethod = "", + LogLevel logLevel = LogLevel.Warning) + { + try + { + var settings = services.GetRequiredService(); + var chatHub = services.GetRequiredService>(); + + if (settings.EventDispatchBy == EventDispatchType.Group) + { + await chatHub.Clients.Group(conversationId).SendAsync(@event, data); + } + else + { + await chatHub.Clients.User(userId).SendAsync(@event, data); + } + } + catch (Exception ex) + { + logger.Log(logLevel, ex, $"Failed to send event '{@event}' in ({callerClass}-{callerMethod}) (conversation id: {conversationId})"); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 1d641554..5cbc117e 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -1,8 +1,10 @@ using BotSharp.Abstraction.Conversations.Dtos; +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Routing.Enums; using BotSharp.Abstraction.SideCar; using BotSharp.Abstraction.Users.Dtos; using Microsoft.AspNetCore.SignalR; +using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Hooks; @@ -15,15 +17,6 @@ public class ChatHubConversationHook : ConversationHookBase private readonly BotSharpOptions _options; private readonly ChatHubSettings _settings; - #region Events - private const string INIT_CLIENT_CONVERSATION = "OnConversationInitFromClient"; - private const string RECEIVE_CLIENT_MESSAGE = "OnMessageReceivedFromClient"; - private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant"; - private const string GENERATE_SENDER_ACTION = "OnSenderActionGenerated"; - private const string DELETE_MESSAGE = "OnMessageDeleted"; - private const string GENERATE_NOTIFICATION = "OnNotificationGenerated"; - #endregion - public ChatHubConversationHook( IServiceProvider services, IHubContext chatHub, @@ -51,7 +44,8 @@ public class ChatHubConversationHook : ConversationHookBase var user = await userService.GetUser(conv.User.Id); conv.User = UserDto.FromUser(user); - await InitClientConversation(conv.Id, conv); + //await InitClientConversation(conv.Id, conv); + await SendEvent(ChatEvent.OnConversationInitFromClient, conv.Id, conv); await base.OnConversationInitialized(conversation); } @@ -72,7 +66,7 @@ public class ChatHubConversationHook : ConversationHookBase Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content, Sender = UserDto.FromUser(sender) }; - await ReceiveClientMessage(conv.ConversationId, model); + await SendEvent(ChatEvent.OnMessageReceivedFromClient, conv.ConversationId, model); // Send typing-on to client var action = new ConversationSenderActionModel @@ -80,22 +74,12 @@ public class ChatHubConversationHook : ConversationHookBase ConversationId = conv.ConversationId, SenderAction = SenderActionEnum.TypingOn }; - - await GenerateSenderAction(conv.ConversationId, action); + await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action); await base.OnMessageReceived(message); } public override async Task OnFunctionExecuting(RoleDialogModel message, string from = InvokeSource.Manual) { - var conv = _services.GetRequiredService(); - var action = new ConversationSenderActionModel - { - ConversationId = conv.ConversationId, - SenderAction = SenderActionEnum.TypingOn, - Indication = message.Indication - }; - - await GenerateSenderAction(conv.ConversationId, action); await base.OnFunctionExecuting(message, from: from); } @@ -110,7 +94,7 @@ public class ChatHubConversationHook : ConversationHookBase var conv = _services.GetRequiredService(); var state = _services.GetRequiredService(); - var json = JsonSerializer.Serialize(new ChatResponseDto() + var data = new ChatResponseDto() { ConversationId = conv.ConversationId, MessageId = message.MessageId, @@ -126,21 +110,20 @@ public class ChatHubConversationHook : ConversationHookBase LastName = "Assistant", Role = AgentRole.Assistant } - }, _options.JsonSerializerOptions); - - // Send typing-off to client - var action = new ConversationSenderActionModel - { - ConversationId = conv.ConversationId, - SenderAction = SenderActionEnum.TypingOff }; + // Send typing-off to client if (!message.IsStreaming) { - await GenerateSenderAction(conv.ConversationId, action); + var action = new ConversationSenderActionModel + { + ConversationId = conv.ConversationId, + SenderAction = SenderActionEnum.TypingOff + }; + await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action); } - await ReceiveAssistantMessage(conv.ConversationId, json); + await SendEvent(ChatEvent.OnMessageReceivedFromAssistant, conv.ConversationId, data); await base.OnResponseGenerated(message); } @@ -148,7 +131,7 @@ public class ChatHubConversationHook : ConversationHookBase public override async Task OnNotificationGenerated(RoleDialogModel message) { var conv = _services.GetRequiredService(); - var json = JsonSerializer.Serialize(new ChatResponseDto() + var data = new ChatResponseDto() { ConversationId = conv.ConversationId, MessageId = message.MessageId, @@ -162,9 +145,9 @@ public class ChatHubConversationHook : ConversationHookBase LastName = "Assistant", Role = AgentRole.Assistant } - }, _options.JsonSerializerOptions); + }; - await GenerateNotification(conv.ConversationId, json); + await SendEvent(ChatEvent.OnNotificationGenerated, conv.ConversationId, data); await base.OnNotificationGenerated(message); } @@ -177,7 +160,7 @@ public class ChatHubConversationHook : ConversationHookBase MessageId = messageId }; - await DeleteMessage(conversationId, model); + await SendEvent(ChatEvent.OnMessageDeleted, conversationId, model); await base.OnMessageDeleted(conversationId, messageId); } @@ -188,119 +171,10 @@ public class ChatHubConversationHook : ConversationHookBase return sidecar == null || !sidecar.IsEnabled; } - private async Task InitClientConversation(string conversationId, ConversationDto conversation) + private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(INIT_CLIENT_CONVERSATION, conversation); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to init client conversation in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } - } - - private async Task ReceiveClientMessage(string conversationId, ChatResponseDto model) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_CLIENT_MESSAGE, model); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_CLIENT_MESSAGE, model); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to receive assistant message in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } - } - - private async Task ReceiveAssistantMessage(string conversationId, string? json) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to receive assistant message in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } - - } - - private async Task GenerateSenderAction(string conversationId, ConversationSenderActionModel action) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_SENDER_ACTION, action); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_SENDER_ACTION, action); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to generate sender action in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } - } - - private async Task DeleteMessage(string conversationId, ChatResponseDto model) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(DELETE_MESSAGE, model); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to delete message in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } - } - - private async Task GenerateNotification(string conversationId, string? json) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_NOTIFICATION, json); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to generate notification in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } + var user = _services.GetRequiredService(); + await ChatHubHelper.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(ChatHubConversationHook), callerName); } #endregion } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs index ef956670..f965d28e 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Conversations.Dtos; +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Crontab; using BotSharp.Abstraction.Crontab.Models; using Microsoft.AspNetCore.SignalR; @@ -14,11 +15,8 @@ public class ChatHubCrontabHook : ICrontabHook private readonly BotSharpOptions _options; private readonly ChatHubSettings _settings; - #region Events - private const string GENERATE_NOTIFICATION = "OnNotificationGenerated"; - #endregion - - public ChatHubCrontabHook(IServiceProvider services, + public ChatHubCrontabHook( + IServiceProvider services, IHubContext chatHub, ILogger logger, IUserIdentity user, @@ -35,7 +33,7 @@ public class ChatHubCrontabHook : ICrontabHook public async Task OnCronTriggered(CrontabItem item) { - var json = JsonSerializer.Serialize(new ChatResponseDto() + var data = new ChatResponseDto() { ConversationId = item.ConversationId, MessageId = Guid.NewGuid().ToString(), @@ -47,16 +45,16 @@ public class ChatHubCrontabHook : ICrontabHook LastName = "AI", Role = AgentRole.Assistant } - }, _options.JsonSerializerOptions); + }; - await SendEvent(item, json); + await SendEvent(item, data); } - private async Task SendEvent(CrontabItem item, string json) + private async Task SendEvent(CrontabItem item, ChatResponseDto data) { try { - await _chatHub.Clients.User(item.UserId).SendAsync(GENERATE_NOTIFICATION, json); + await _chatHub.Clients.User(item.UserId).SendAsync(ChatEvent.OnNotificationGenerated, data); } catch { } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 5dd89a2b..f036deab 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -1,5 +1,7 @@ +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Routing.Enums; using Microsoft.AspNetCore.SignalR; +using System.Runtime.CompilerServices; using System.Text.Encodings.Web; using System.Text.Unicode; @@ -19,13 +21,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR private readonly IAgentService _agentService; private readonly IRoutingContext _routingCtx; - #region Events - private const string CONTENT_LOG_GENERATED = "OnConversationContentLogGenerated"; - private const string STATE_LOG_GENERATED = "OnConversateStateLogGenerated"; - private const string AGENT_QUEUE_CHANGED = "OnAgentQueueChanged"; - private const string STATE_CHANGED = "OnStateChangeGenerated"; - #endregion - public StreamingLogHook( ConversationSetting convSettings, BotSharpOptions options, @@ -65,7 +60,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.UserInput, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg) @@ -83,7 +79,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.UserInput, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task OnSessionUpdated(Agent agent, string instruction, FunctionDef[] functions, bool isInit = false) @@ -112,7 +109,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.Prompt, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task OnRenderingTemplate(Agent agent, string name, string content) @@ -134,7 +132,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task BeforeGenerating(Agent agent, List conversations) @@ -162,7 +161,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public override async Task OnFunctionExecuted(RoleDialogModel message, string from = InvokeSource.Manual) @@ -183,7 +183,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } /// @@ -210,7 +211,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.Prompt, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } /// @@ -225,7 +227,9 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conv = _services.GetRequiredService(); var routingCtx = _services.GetRequiredService(); - await SendStateLog(conv.ConversationId, routingCtx.EntryAgentId, _state.GetStates(), message); + var stateLog = BuildStateLog(conv.ConversationId, routingCtx.EntryAgentId, _state.GetStates(), message); + //await SendStateLog(conv.ConversationId, routingCtx.EntryAgentId, _state.GetStates(), message); + await SendEvent(ChatEvent.OnConversateStateLogGenerated, conv.ConversationId, stateLog); if (message.Role == AgentRole.Assistant) { @@ -244,7 +248,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.AgentResponse, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } } @@ -262,7 +267,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public override async Task OnConversationEnding(RoleDialogModel message) @@ -279,7 +285,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public override async Task OnBreakpointUpdated(string conversationId, bool resetStates) @@ -307,7 +314,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR }, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public override async Task OnStateChanged(StateChangeModel stateChange) @@ -317,7 +325,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (stateChange == null) return; - await SendStateChange(conversationId, stateChange); + //await SendStateChange(conversationId, stateChange); + await SendEvent(ChatEvent.OnStateChangeGenerated, conversationId, BuildStateChangeLog(stateChange)); } #endregion @@ -331,7 +340,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"{agent.Name} is enqueued"; - await SendAgentQueueLog(conversationId, log); + //await SendAgentQueueLog(conversationId, log); + await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log log = $"{agent.Name} is enqueued{(reason != null ? $" ({reason})" : "")}"; @@ -346,7 +356,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task OnAgentDequeued(string agentId, string currentAgentId, string? reason = null) @@ -359,7 +370,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"{agent.Name} is dequeued"; - await SendAgentQueueLog(conversationId, log); + //await SendAgentQueueLog(conversationId, log); + await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log log = $"{agent.Name} is dequeued{(reason != null ? $" ({reason})" : "")}, current agent is {currentAgent?.Name}"; @@ -374,7 +386,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task OnAgentReplaced(string fromAgentId, string toAgentId, string? reason = null) @@ -387,7 +400,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"Agent queue is replaced from {fromAgent.Name} to {toAgent.Name}"; - await SendAgentQueueLog(conversationId, log); + //await SendAgentQueueLog(conversationId, log); + await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log log = $"{fromAgent.Name} is replaced to {toAgent.Name}{(reason != null ? $" ({reason})" : "")}"; @@ -402,7 +416,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task OnAgentQueueEmptied(string agentId, string? reason = null) @@ -412,7 +427,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"Agent queue is empty"; - await SendAgentQueueLog(conversationId, log); + //await SendAgentQueueLog(conversationId, log); + await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log log = reason ?? "Agent queue is cleared"; @@ -427,7 +443,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message) @@ -446,7 +463,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.AgentResponse, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } public async Task OnRoutingInstructionRevised(FunctionCallFromLlm instruct, RoleDialogModel message) @@ -464,90 +482,20 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(conversationId, input); + //await SendContentLog(conversationId, input); + await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } #endregion #region Private methods - private async Task SendContentLog(string conversationId, ContentLogInputModel input) + private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input)); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input)); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to send content log in {nameof(StreamingLogHook)} (conversation id: {conversationId})."); - } + var user = _services.GetRequiredService(); + await ChatHubHelper.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(StreamingLogHook), callerName); } - private async Task SendStateLog(string conversationId, string agentId, Dictionary states, RoleDialogModel message) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message)); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message)); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to send state log in {nameof(StreamingLogHook)} (conversation id: {conversationId})."); - } - } - - private async Task SendAgentQueueLog(string conversationId, string log) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log)); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log)); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to send agent queue log in {nameof(StreamingLogHook)} (conversation id: {conversationId})."); - } - } - - private async Task SendStateChange(string conversationId, StateChangeModel stateChange) - { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange)); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange)); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to send state change in {nameof(StreamingLogHook)} (conversation id: {conversationId})."); - } - } - - - private string BuildContentLog(ContentLogInputModel input) + private ContentLogOutputModel BuildContentLog(ContentLogInputModel input) { var output = new ContentLogOutputModel { @@ -561,8 +509,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR CreatedTime = DateTime.UtcNow }; - var json = JsonSerializer.Serialize(output, _options.JsonSerializerOptions); - var convSettings = _services.GetRequiredService(); if (convSettings.EnableContentLog) { @@ -570,10 +516,10 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR db.SaveConversationContentLog(output); } - return json; + return output; } - private string BuildStateLog(string conversationId, string agentId, Dictionary states, RoleDialogModel message) + private ConversationStateLogModel BuildStateLog(string conversationId, string agentId, Dictionary states, RoleDialogModel message) { var log = new ConversationStateLogModel { @@ -591,10 +537,10 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR db.SaveConversationStateLog(log); } - return JsonSerializer.Serialize(log, _options.JsonSerializerOptions); + return log; } - private string BuildStateChangeLog(StateChangeModel stateChange) + private StateChangeOutputModel BuildStateChangeLog(StateChangeModel stateChange) { var log = new StateChangeOutputModel { @@ -611,10 +557,10 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR CreateTime = DateTime.UtcNow }; - return JsonSerializer.Serialize(log, _options.JsonSerializerOptions); + return log; } - private string BuildAgentQueueChangedLog(string conversationId, string log) + private AgentQueueChangedLogModel BuildAgentQueueChangedLog(string conversationId, string log) { var model = new AgentQueueChangedLogModel { @@ -623,7 +569,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR CreatedTime = DateTime.UtcNow }; - return JsonSerializer.Serialize(model, _options.JsonSerializerOptions); + return model; } private string GetMessageContent(RoleDialogModel message) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs index 3ae8b313..3db8017b 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs @@ -1,5 +1,7 @@ using BotSharp.Abstraction.Conversations.Dtos; +using BotSharp.Abstraction.Conversations.Enums; using Microsoft.AspNetCore.SignalR; +using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Hooks; @@ -13,11 +15,8 @@ public class WelcomeHook : ConversationHookBase private readonly BotSharpOptions _options; private readonly ChatHubSettings _settings; - #region Events - private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant"; - #endregion - - public WelcomeHook(IServiceProvider services, + public WelcomeHook( + IServiceProvider services, IHubContext chatHub, ILogger logger, IUserIdentity user, @@ -64,7 +63,7 @@ public class WelcomeHook : ConversationHookBase RichContent = richContent }; - var json = JsonSerializer.Serialize(new ChatResponseDto() + var data = new ChatResponseDto() { ConversationId = conversation.Id, MessageId = dialog.MessageId, @@ -76,35 +75,20 @@ public class WelcomeHook : ConversationHookBase LastName = "", Role = AgentRole.Assistant } - }, _options.JsonSerializerOptions); + }; await Task.Delay(300); - _storage.Append(conversation.Id, dialog); - - await SendEvent(conversation.Id, json); + await SendEvent(ChatEvent.OnMessageReceivedFromAssistant, conversation.Id, data); } } await base.OnUserAgentConnectedInitially(conversation); } - private async Task SendEvent(string conversationId, string json) + private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { - try - { - if (_settings.EventDispatchBy == EventDispatchType.Group) - { - await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); - } - else - { - await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to send event in {nameof(WelcomeHook)} (conversation id: {conversationId})."); - } + var user = _services.GetRequiredService(); + await ChatHubHelper.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(WelcomeHook), callerName); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index c15419ed..71b07b9d 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Conversations.Dtos; -using BotSharp.Abstraction.Observables.Models; +using BotSharp.Abstraction.Conversations.Enums; +using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Abstraction.SideCar; using BotSharp.Plugin.ChatHub.Hooks; using Microsoft.AspNetCore.SignalR; @@ -11,11 +12,6 @@ public class ChatHubObserver : IObserver private readonly ILogger _logger; private IServiceProvider _services; - private const string BEFORE_RECEIVE_LLM_STREAM_MESSAGE = "BeforeReceiveLlmStreamMessage"; - private const string ON_RECEIVE_LLM_STREAM_MESSAGE = "OnReceiveLlmStreamMessage"; - private const string AFTER_RECEIVE_LLM_STREAM_MESSAGE = "AfterReceiveLlmStreamMessage"; - private const string GENERATE_SENDER_ACTION = "OnSenderActionGenerated"; - public ChatHubObserver(ILogger logger) { _logger = logger; @@ -39,72 +35,86 @@ public class ChatHubObserver : IObserver var message = value.Data; var model = new ChatResponseDto(); - if (value.EventName == BEFORE_RECEIVE_LLM_STREAM_MESSAGE) + var action = new ConversationSenderActionModel(); + var conv = _services.GetRequiredService(); + + switch (value.EventName) { - var conv = _services.GetRequiredService(); - model = new ChatResponseDto() - { - ConversationId = conv.ConversationId, - MessageId = message.MessageId, - Text = string.Empty, - Sender = new() + case ChatEvent.BeforeReceiveLlmStreamMessage: + model = new ChatResponseDto() { - FirstName = "AI", - LastName = "Assistant", - Role = AgentRole.Assistant - } - }; + ConversationId = conv.ConversationId, + MessageId = message.MessageId, + Text = string.Empty, + Sender = new() + { + FirstName = "AI", + LastName = "Assistant", + Role = AgentRole.Assistant + } + }; - var action = new ConversationSenderActionModel - { - ConversationId = conv.ConversationId, - SenderAction = SenderActionEnum.TypingOn - }; - - GenerateSenderAction(conv.ConversationId, action); - } - else if (value.EventName == AFTER_RECEIVE_LLM_STREAM_MESSAGE && message.IsStreaming) - { - var conv = _services.GetRequiredService(); - model = new ChatResponseDto() - { - ConversationId = conv.ConversationId, - MessageId = message.MessageId, - Text = message.Content, - Sender = new() + action = new ConversationSenderActionModel { - FirstName = "AI", - LastName = "Assistant", - Role = AgentRole.Assistant - } - }; + ConversationId = conv.ConversationId, + SenderAction = SenderActionEnum.TypingOn + }; - var action = new ConversationSenderActionModel - { - ConversationId = conv.ConversationId, - SenderAction = SenderActionEnum.TypingOff - }; - - GenerateSenderAction(conv.ConversationId, action); - } - else if (value.EventName == ON_RECEIVE_LLM_STREAM_MESSAGE) - { - var conv = _services.GetRequiredService(); - model = new ChatResponseDto() - { - ConversationId = conv.ConversationId, - MessageId = message.MessageId, - Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content, - Function = message.FunctionName, - RichContent = message.SecondaryRichContent ?? message.RichContent, - Data = message.Data, - Sender = new() + GenerateSenderAction(conv.ConversationId, action); + break; + case ChatEvent.OnReceiveLlmStreamMessage: + model = new ChatResponseDto() { - FirstName = "AI", - LastName = "Assistant", - Role = AgentRole.Assistant - } - }; + ConversationId = conv.ConversationId, + MessageId = message.MessageId, + Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content, + Function = message.FunctionName, + RichContent = message.SecondaryRichContent ?? message.RichContent, + Data = message.Data, + Sender = new() + { + FirstName = "AI", + LastName = "Assistant", + Role = AgentRole.Assistant + } + }; + break; + case ChatEvent.AfterReceiveLlmStreamMessage: + model = new ChatResponseDto() + { + ConversationId = conv.ConversationId, + MessageId = message.MessageId, + Text = message.Content, + Sender = new() + { + FirstName = "AI", + LastName = "Assistant", + Role = AgentRole.Assistant + } + }; + + action = new ConversationSenderActionModel + { + ConversationId = conv.ConversationId, + SenderAction = SenderActionEnum.TypingOff + }; + + GenerateSenderAction(conv.ConversationId, action); + break; + case ChatEvent.OnIndicationReceived: + model = new ChatResponseDto + { + ConversationId = conv.ConversationId, + MessageId = message.MessageId, + Indication = message.Indication, + Sender = new() + { + FirstName = "AI", + LastName = "Assistant", + Role = AgentRole.Assistant + } + }; + break; } OnReceiveAssistantMessage(value.EventName, model.ConversationId, model); @@ -147,12 +157,12 @@ public class ChatHubObserver : IObserver var chatHub = _services.GetRequiredService>(); if (settings.EventDispatchBy == EventDispatchType.Group) { - chatHub.Clients.Group(conversationId).SendAsync(GENERATE_SENDER_ACTION, action).ConfigureAwait(false).GetAwaiter().GetResult(); + chatHub.Clients.Group(conversationId).SendAsync(ChatEvent.OnSenderActionGenerated, action).ConfigureAwait(false).GetAwaiter().GetResult(); } else { var user = _services.GetRequiredService(); - chatHub.Clients.User(user.Id).SendAsync(GENERATE_SENDER_ACTION, action).ConfigureAwait(false).GetAwaiter().GetResult(); + chatHub.Clients.User(user.Id).SendAsync(ChatEvent.OnSenderActionGenerated, action).ConfigureAwait(false).GetAwaiter().GetResult(); } } catch (Exception ex) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs index 4710509c..3473bb6c 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs @@ -34,4 +34,5 @@ global using BotSharp.Abstraction.Realtime; global using BotSharp.Abstraction.Realtime.Models; global using BotSharp.Plugin.ChatHub.Settings; global using BotSharp.Plugin.ChatHub.Enums; -global using BotSharp.Plugin.ChatHub.Models.Stream; \ No newline at end of file +global using BotSharp.Plugin.ChatHub.Models.Stream; +global using BotSharp.Plugin.ChatHub.Helpers; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs index 1335d4dc..36a5126e 100644 --- a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,8 +1,8 @@ using BotSharp.Abstraction.Files; using BotSharp.Abstraction.Hooks; -using BotSharp.Abstraction.Observables.Models; +using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; -using BotSharp.Core.Observables.Queues; +using BotSharp.Core.MessageHub; using BotSharp.Plugin.DeepSeek.Providers; using Microsoft.Extensions.Logging; using OpenAI.Chat; diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs index 321b9aee..edeb7f58 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs @@ -1,9 +1,9 @@ using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Loggers; -using BotSharp.Abstraction.Observables.Models; +using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; -using BotSharp.Core.Observables.Queues; +using BotSharp.Core.MessageHub; using Microsoft.AspNetCore.SignalR; using static LLama.Common.ChatHistory; using static System.Net.Mime.MediaTypeNames; diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 57b44252..0e39cb34 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,8 +1,8 @@ using Azure; using BotSharp.Abstraction.Hooks; -using BotSharp.Abstraction.Observables.Models; +using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; -using BotSharp.Core.Observables.Queues; +using BotSharp.Core.MessageHub; using BotSharp.Plugin.OpenAI.Models.Realtime; using Fluid; using OpenAI.Chat; diff --git a/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs index 035e03e0..6e4a58f2 100644 --- a/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs @@ -1,9 +1,9 @@ using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Loggers; -using BotSharp.Abstraction.Observables.Models; +using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; -using BotSharp.Core.Observables.Queues; +using BotSharp.Core.MessageHub; using Microsoft.AspNetCore.SignalR; namespace BotSharp.Plugin.SparkDesk.Providers; From 9a966c151cd4434b68913094097a431a918d3d23 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Mon, 28 Jul 2025 22:08:28 -0500 Subject: [PATCH 11/51] fix --- tests/BotSharp.LLM.Tests/Core/TestAgentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs index 50d34701..5d4b07d0 100644 --- a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs +++ b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs @@ -16,7 +16,7 @@ namespace BotSharp.Plugin.Google.Core return Task.FromResult(new Agent()); } - public Task RefreshAgents() + public Task RefreshAgents(IEnumerable? agentIds = null) { return Task.FromResult("Refreshed successfully"); } From dd024661d5cdab162edcbd052ffabe9186289e11 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 29 Jul 2025 10:32:55 -0500 Subject: [PATCH 12/51] rename --- .../{ChatHubHelper.cs => EventEmitter.cs} | 17 +++--- .../Hooks/ChatHubConversationHook.cs | 2 +- .../Hooks/StreamingLogHook.cs | 18 +----- .../Hooks/WelcomeHook.cs | 2 +- .../Observers/ChatHubObserver.cs | 55 ++++--------------- .../Core/TestAgentService.cs | 2 +- 6 files changed, 23 insertions(+), 73 deletions(-) rename src/Plugins/BotSharp.Plugin.ChatHub/Helpers/{ChatHubHelper.cs => EventEmitter.cs} (61%) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/ChatHubHelper.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs similarity index 61% rename from src/Plugins/BotSharp.Plugin.ChatHub/Helpers/ChatHubHelper.cs rename to src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs index 94bbc90d..821b173e 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/ChatHubHelper.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs @@ -3,9 +3,9 @@ using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Helpers; -public class ChatHubHelper +internal class EventEmitter { - public static async Task SendChatEvent( + internal static async Task SendChatEvent( IServiceProvider services, ILogger logger, string @event, @@ -21,13 +21,14 @@ public class ChatHubHelper var settings = services.GetRequiredService(); var chatHub = services.GetRequiredService>(); - if (settings.EventDispatchBy == EventDispatchType.Group) + switch (settings.EventDispatchBy) { - await chatHub.Clients.Group(conversationId).SendAsync(@event, data); - } - else - { - await chatHub.Clients.User(userId).SendAsync(@event, data); + case EventDispatchType.Group when !string.IsNullOrEmpty(conversationId): + await chatHub.Clients.Group(conversationId).SendAsync(@event, data); + break; + case EventDispatchType.User when !string.IsNullOrEmpty(userId): + await chatHub.Clients.User(userId).SendAsync(@event, data); + break; } } catch (Exception ex) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 5cbc117e..52dd4f7c 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -174,7 +174,7 @@ public class ChatHubConversationHook : ConversationHookBase private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { var user = _services.GetRequiredService(); - await ChatHubHelper.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(ChatHubConversationHook), callerName); + await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(ChatHubConversationHook), callerName); } #endregion } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index f036deab..73365115 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -228,7 +228,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conv = _services.GetRequiredService(); var routingCtx = _services.GetRequiredService(); var stateLog = BuildStateLog(conv.ConversationId, routingCtx.EntryAgentId, _state.GetStates(), message); - //await SendStateLog(conv.ConversationId, routingCtx.EntryAgentId, _state.GetStates(), message); await SendEvent(ChatEvent.OnConversateStateLogGenerated, conv.ConversationId, stateLog); if (message.Role == AgentRole.Assistant) @@ -248,7 +247,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.AgentResponse, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } } @@ -267,7 +265,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -285,7 +282,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -314,7 +310,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR }, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -325,7 +320,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (stateChange == null) return; - //await SendStateChange(conversationId, stateChange); await SendEvent(ChatEvent.OnStateChangeGenerated, conversationId, BuildStateChangeLog(stateChange)); } #endregion @@ -340,7 +334,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"{agent.Name} is enqueued"; - //await SendAgentQueueLog(conversationId, log); await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log @@ -356,7 +349,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -370,7 +362,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"{agent.Name} is dequeued"; - //await SendAgentQueueLog(conversationId, log); await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log @@ -386,7 +377,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -400,7 +390,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"Agent queue is replaced from {fromAgent.Name} to {toAgent.Name}"; - //await SendAgentQueueLog(conversationId, log); await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log @@ -416,7 +405,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -427,7 +415,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR // Agent queue log var log = $"Agent queue is empty"; - //await SendAgentQueueLog(conversationId, log); await SendEvent(ChatEvent.OnAgentQueueChanged, conversationId, BuildAgentQueueChangedLog(conversationId, log)); // Content log @@ -443,7 +430,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -463,7 +449,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.AgentResponse, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -482,7 +467,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } #endregion @@ -492,7 +476,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { var user = _services.GetRequiredService(); - await ChatHubHelper.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(StreamingLogHook), callerName); + await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(StreamingLogHook), callerName); } private ContentLogOutputModel BuildContentLog(ContentLogInputModel input) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs index 3db8017b..8a184568 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs @@ -89,6 +89,6 @@ public class WelcomeHook : ConversationHookBase private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { var user = _services.GetRequiredService(); - await ChatHubHelper.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(WelcomeHook), callerName); + await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(WelcomeHook), callerName); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index 71b07b9d..3c1799ea 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -4,6 +4,7 @@ using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Abstraction.SideCar; using BotSharp.Plugin.ChatHub.Hooks; using Microsoft.AspNetCore.SignalR; +using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Observers; @@ -60,7 +61,7 @@ public class ChatHubObserver : IObserver SenderAction = SenderActionEnum.TypingOn }; - GenerateSenderAction(conv.ConversationId, action); + SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action); break; case ChatEvent.OnReceiveLlmStreamMessage: model = new ChatResponseDto() @@ -99,7 +100,7 @@ public class ChatHubObserver : IObserver SenderAction = SenderActionEnum.TypingOff }; - GenerateSenderAction(conv.ConversationId, action); + SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action); break; case ChatEvent.OnIndicationReceived: model = new ChatResponseDto @@ -117,7 +118,7 @@ public class ChatHubObserver : IObserver break; } - OnReceiveAssistantMessage(value.EventName, model.ConversationId, model); + SendEvent(value.EventName, model.ConversationId, model); } private bool AllowSendingMessage() @@ -126,48 +127,12 @@ public class ChatHubObserver : IObserver return sidecar == null || !sidecar.IsEnabled; } - private void OnReceiveAssistantMessage(string @event, string conversationId, ChatResponseDto model) + #region Private methods + private void SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { - try - { - var settings = _services.GetRequiredService(); - var chatHub = _services.GetRequiredService>(); - - if (settings.EventDispatchBy == EventDispatchType.Group) - { - chatHub.Clients.Group(conversationId).SendAsync(@event, model).ConfigureAwait(false).GetAwaiter().GetResult(); - } - else - { - var user = _services.GetRequiredService(); - chatHub.Clients.User(user.Id).SendAsync(@event, model).ConfigureAwait(false).GetAwaiter().GetResult(); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to receive assistant message in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } - } - - private void GenerateSenderAction(string conversationId, ConversationSenderActionModel action) - { - try - { - var settings = _services.GetRequiredService(); - var chatHub = _services.GetRequiredService>(); - if (settings.EventDispatchBy == EventDispatchType.Group) - { - chatHub.Clients.Group(conversationId).SendAsync(ChatEvent.OnSenderActionGenerated, action).ConfigureAwait(false).GetAwaiter().GetResult(); - } - else - { - var user = _services.GetRequiredService(); - chatHub.Clients.User(user.Id).SendAsync(ChatEvent.OnSenderActionGenerated, action).ConfigureAwait(false).GetAwaiter().GetResult(); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Failed to generate sender action in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})"); - } + var user = _services.GetRequiredService(); + EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(ChatHubObserver), callerName) + .ConfigureAwait(false).GetAwaiter().GetResult(); } + #endregion } diff --git a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs index 50d34701..5d4b07d0 100644 --- a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs +++ b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs @@ -16,7 +16,7 @@ namespace BotSharp.Plugin.Google.Core return Task.FromResult(new Agent()); } - public Task RefreshAgents() + public Task RefreshAgents(IEnumerable? agentIds = null) { return Task.FromResult("Refreshed successfully"); } From d1b5ad592dfe2cdbaba45bb285764b3546c51db9 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 29 Jul 2025 11:17:33 -0500 Subject: [PATCH 13/51] add conversation observer --- .../Conversations/ConversationPlugin.cs | 15 ++++++-- .../Observers/ConversationObserver.cs | 34 +++++++++++++++++++ .../Routing/RoutingService.InvokeFunction.cs | 6 ---- .../Observers/ChatHubObserver.cs | 10 +++--- .../Providers/Chat/ChatCompletionProvider.cs | 9 ++--- 5 files changed, 55 insertions(+), 19 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index 2f0af290..cef32cad 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -6,17 +6,18 @@ using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Settings; using BotSharp.Abstraction.Templating; using BotSharp.Core.Instructs; +using BotSharp.Core.MessageHub; +using BotSharp.Core.MessageHub.Observers; using BotSharp.Core.Messaging; using BotSharp.Core.Routing.Reasoning; using BotSharp.Core.Templating; using BotSharp.Core.Translation; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; -using BotSharp.Abstraction.MessageHub.Models; -using BotSharp.Core.MessageHub; namespace BotSharp.Core.Conversations; -public class ConversationPlugin : IBotSharpPlugin +public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin { public string Id => "99e9b971-a9f1-4273-84da-876d2873d192"; public string Name => "Conversation"; @@ -67,4 +68,12 @@ public class ConversationPlugin : IBotSharpPlugin menu.Add(new PluginMenuDef("Conversation", link: "page/conversation", icon: "bx bx-conversation", weight: section.Weight + 5)); return true; } + + public void Configure(IApplicationBuilder app) + { + var services = app.ApplicationServices; + var queue = services.GetRequiredService>(); + var logger = services.GetRequiredService>>(); + queue.Events.Subscribe(new ConversationObserver(logger)); + } } diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs new file mode 100644 index 00000000..dd4b528c --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs @@ -0,0 +1,34 @@ +namespace BotSharp.Core.MessageHub.Observers; + +public class ConversationObserver : IObserver +{ + private readonly ILogger _logger; + private IServiceProvider _services; + + public ConversationObserver(ILogger logger) + { + _logger = logger; + } + + public void OnCompleted() + { + _logger.LogWarning($"{nameof(ConversationObserver)} receives complete notification."); + } + + public void OnError(Exception error) + { + _logger.LogError(error, $"{nameof(ConversationObserver)} receives error notification: {error.Message}"); + } + + public void OnNext(HubObserveData value) + { + _services = value.ServiceProvider; + + var progress = _services.GetRequiredService(); + if (value.EventName == ChatEvent.OnIndicationReceived + && progress.OnFunctionExecuting != null) + { + progress.OnFunctionExecuting(value.Data).ConfigureAwait(false).GetAwaiter().GetResult(); + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 0922dcdb..3660f61f 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -25,12 +25,6 @@ public partial class RoutingService clonedMessage.FunctionName = name; clonedMessage.Indication = await funcExecutor.GetIndicatorAsync(message); - //var progressService = _services.GetService(); - //if (progressService?.OnFunctionExecuting != null) - //{ - // await progressService.OnFunctionExecuting(clonedMessage); - //} - var messageHub = _services.GetRequiredService>(); messageHub.Push(new() { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index 3c1799ea..8f467908 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -2,8 +2,6 @@ using BotSharp.Abstraction.Conversations.Dtos; using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Abstraction.SideCar; -using BotSharp.Plugin.ChatHub.Hooks; -using Microsoft.AspNetCore.SignalR; using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Observers; @@ -32,8 +30,6 @@ public class ChatHubObserver : IObserver { _services = value.ServiceProvider; - if (!AllowSendingMessage()) return; - var message = value.Data; var model = new ChatResponseDto(); var action = new ConversationSenderActionModel(); @@ -42,6 +38,8 @@ public class ChatHubObserver : IObserver switch (value.EventName) { case ChatEvent.BeforeReceiveLlmStreamMessage: + if (!AllowSendingMessage()) return; + model = new ChatResponseDto() { ConversationId = conv.ConversationId, @@ -64,6 +62,8 @@ public class ChatHubObserver : IObserver SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action); break; case ChatEvent.OnReceiveLlmStreamMessage: + if (!AllowSendingMessage()) return; + model = new ChatResponseDto() { ConversationId = conv.ConversationId, @@ -81,6 +81,8 @@ public class ChatHubObserver : IObserver }; break; case ChatEvent.AfterReceiveLlmStreamMessage: + if (!AllowSendingMessage()) return; + model = new ChatResponseDto() { ConversationId = conv.ConversationId, diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 0e39cb34..e4567cfb 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,10 +1,7 @@ -using Azure; using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; using BotSharp.Core.MessageHub; -using BotSharp.Plugin.OpenAI.Models.Realtime; -using Fluid; using OpenAI.Chat; namespace BotSharp.Plugin.OpenAI.Providers.Chat; @@ -204,7 +201,7 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { ServiceProvider = _services, - EventName = "BeforeReceiveLlmStreamMessage", + EventName = ChatEvent.BeforeReceiveLlmStreamMessage, Data = new RoleDialogModel(AgentRole.Assistant, string.Empty) { CurrentAgentId = agent.Id, @@ -248,7 +245,7 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { ServiceProvider = _services, - EventName = "OnReceiveLlmStreamMessage", + EventName = ChatEvent.OnReceiveLlmStreamMessage, Data = content }); } @@ -291,7 +288,7 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { ServiceProvider = _services, - EventName = "AfterReceiveLlmStreamMessage", + EventName = ChatEvent.AfterReceiveLlmStreamMessage, Data = responseMessage }); From 7d3579c1ec36bc38cd3c67906b9ab7cacc6ef699 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 29 Jul 2025 13:55:53 -0500 Subject: [PATCH 14/51] refine message hub --- .../Conversations/Models/RoleDialogModel.cs | 2 +- .../MessageHub/Models/HubObserveData.cs | 4 ++-- .../BotSharp.Core/Conversations/ConversationPlugin.cs | 6 +++--- .../BotSharp.Core/Demo/Functions/GetWeatherFn.cs | 2 +- src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs | 2 +- .../MessageHub/Observers/ConversationObserver.cs | 6 +++--- .../BotSharp.Core/Routing/RoutingService.InvokeFunction.cs | 2 +- .../Providers/Chat/ChatCompletionProvider.cs | 2 +- src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 4 ++-- .../BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs | 4 ++-- .../Providers/Chat/ChatCompletionProvider.cs | 2 +- .../Providers/ChatCompletionProvider.cs | 2 +- .../Providers/Chat/ChatCompletionProvider.cs | 2 +- .../Providers/ChatCompletionProvider.cs | 2 +- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 6a8af2d6..6bc82f81 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -120,7 +120,7 @@ public class RoleDialogModel : ITrackableMessage [JsonIgnore(Condition = JsonIgnoreCondition.Always)] public bool IsStreaming { get; set; } - private RoleDialogModel() + public RoleDialogModel() { } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs index 403adbc6..277893ad 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs @@ -1,7 +1,7 @@ namespace BotSharp.Abstraction.MessageHub.Models; -public class HubObserveData : ObserveDataBase +public class HubObserveData : ObserveDataBase where TData : class, new() { public string EventName { get; set; } = null!; - public RoleDialogModel Data { get; set; } = null!; + public TData Data { get; set; } = null!; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index cef32cad..1a36875c 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -44,7 +44,7 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin return settingService.Bind("GoogleApi"); }); - services.AddSingleton>(); + services.AddSingleton>>(); services.AddScoped(); services.AddScoped(); @@ -72,8 +72,8 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { var services = app.ApplicationServices; - var queue = services.GetRequiredService>(); - var logger = services.GetRequiredService>>(); + var queue = services.GetRequiredService>>(); + var logger = services.GetRequiredService>>>(); queue.Events.Subscribe(new ConversationObserver(logger)); } } diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs index b8bb0f39..a22f5cec 100644 --- a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs @@ -18,7 +18,7 @@ public class GetWeatherFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { var conv = _services.GetRequiredService(); - var messageHub = _services.GetRequiredService>(); + var messageHub = _services.GetRequiredService>>(); await Task.Delay(1000); diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs index a104f4f6..8e99f898 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs @@ -2,7 +2,7 @@ using System.Reactive.Subjects; namespace BotSharp.Core.MessageHub; -public class MessageHub where T : class +public class MessageHub where T : class, new() { private readonly ILogger> _logger; private readonly ISubject _observable = Subject.Synchronize(new Subject()); diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs index dd4b528c..cad778b4 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs @@ -1,6 +1,6 @@ namespace BotSharp.Core.MessageHub.Observers; -public class ConversationObserver : IObserver +public class ConversationObserver : IObserver> { private readonly ILogger _logger; private IServiceProvider _services; @@ -20,11 +20,11 @@ public class ConversationObserver : IObserver _logger.LogError(error, $"{nameof(ConversationObserver)} receives error notification: {error.Message}"); } - public void OnNext(HubObserveData value) + public void OnNext(HubObserveData value) { _services = value.ServiceProvider; - var progress = _services.GetRequiredService(); + if (value.EventName == ChatEvent.OnIndicationReceived && progress.OnFunctionExecuting != null) { diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 3660f61f..f693f4dd 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -25,7 +25,7 @@ public partial class RoutingService clonedMessage.FunctionName = name; clonedMessage.Indication = await funcExecutor.GetIndicatorAsync(message); - var messageHub = _services.GetRequiredService>(); + var messageHub = _services.GetRequiredService>>(); messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs index d3c6e14a..b19bdaef 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -212,7 +212,7 @@ public class ChatCompletionProvider : IChatCompletion var chatClient = client.GetChatClient(_model); var (prompt, messages, options) = PrepareOptions(agent, conversations); - var hub = _services.GetRequiredService>(); + var hub = _services.GetRequiredService>>(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; var contentHooks = _services.GetHooks(agent.Id); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index d771d0a2..38bd936c 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -36,8 +36,8 @@ public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { var services = app.ApplicationServices; - var queue = services.GetRequiredService>(); - var logger = services.GetRequiredService>>(); + var queue = services.GetRequiredService>>(); + var logger = services.GetRequiredService>>>(); queue.Events.Subscribe(new ChatHubObserver(logger)); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index 8f467908..75aa0cd3 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Observers; -public class ChatHubObserver : IObserver +public class ChatHubObserver : IObserver> { private readonly ILogger _logger; private IServiceProvider _services; @@ -26,7 +26,7 @@ public class ChatHubObserver : IObserver _logger.LogError(error, $"{nameof(ChatHubObserver)} receives error notification: {error.Message}"); } - public void OnNext(HubObserveData value) + public void OnNext(HubObserveData value) { _services = value.ServiceProvider; diff --git a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs index 36a5126e..a404aef1 100644 --- a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs @@ -179,7 +179,7 @@ public class ChatCompletionProvider : IChatCompletion var chatClient = client.GetChatClient(_model); var (prompt, messages, options) = PrepareOptions(agent, conversations); - var hub = _services.GetRequiredService>(); + var hub = _services.GetRequiredService>>(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; var contentHooks = _services.GetHooks(agent.Id); diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs index edeb7f58..a4f18d31 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs @@ -183,7 +183,7 @@ public class ChatCompletionProvider : IChatCompletion _logger.LogInformation(agent.Instruction); } - var hub = _services.GetRequiredService>(); + var hub = _services.GetRequiredService>>(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; hub.Push(new() diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index e4567cfb..86447dca 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -188,7 +188,7 @@ public class ChatCompletionProvider : IChatCompletion var chatClient = client.GetChatClient(_model); var (prompt, messages, options) = PrepareOptions(agent, conversations); - var hub = _services.GetRequiredService>(); + var hub = _services.GetRequiredService>>(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; var contentHooks = _services.GetHooks(agent.Id); diff --git a/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs index 6e4a58f2..c2e10def 100644 --- a/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs @@ -152,7 +152,7 @@ public class ChatCompletionProvider : IChatCompletion var client = new SparkDeskClient(appId: _settings.AppId, apiKey: _settings.ApiKey, apiSecret: _settings.ApiSecret); var (prompt, messages, funcall) = PrepareOptions(agent, conversations); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; - var hub = _services.GetRequiredService>(); + var hub = _services.GetRequiredService>>(); hub.Push(new() { From 6419ca3f8fb470a798c797982acfebf43976ff64 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 29 Jul 2025 13:58:27 -0500 Subject: [PATCH 15/51] add logging --- .../BotSharp.Core/MessageHub/Observers/ConversationObserver.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs index cad778b4..15b44157 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs @@ -28,6 +28,9 @@ public class ConversationObserver : IObserver> if (value.EventName == ChatEvent.OnIndicationReceived && progress.OnFunctionExecuting != null) { +#if DEBUG + _logger.LogCritical($"Receiving {value.EventName} in {nameof(ConversationObserver)}"); +#endif progress.OnFunctionExecuting(value.Data).ConfigureAwait(false).GetAwaiter().GetResult(); } } From d523798d41e0b488dcb8304efea770ede6292c3d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 31 Jul 2025 17:48:51 -0500 Subject: [PATCH 16/51] refine observers --- .../MessageHub/Models/ObserveDataBase.cs | 3 +- .../MessageHub/Models/ObserverSubscription.cs | 22 +++++++++ .../Observers/BotSharpObserverBase.cs | 9 ++++ .../MessageHub/Observers/IBotSharpObserver.cs | 9 ++++ .../MessageHub/Services/IObserverService.cs | 8 +++ .../Services/ObserverSubscriptionContainer.cs | 49 +++++++++++++++++++ .../Conversations/ConversationPlugin.cs | 13 +++-- .../Demo/Functions/GetWeatherFn.cs | 14 +++++- .../BotSharp.Core/MessageHub/MessageHub.cs | 2 +- .../Observers/ConversationObserver.cs | 33 +++++++++---- .../MessageHub/Services/ObserverService.cs | 43 ++++++++++++++++ .../Routing/RoutingService.InvokeFunction.cs | 2 + .../Controllers/ConversationController.cs | 6 +++ .../BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 11 +++-- .../Observers/ChatHubObserver.cs | 22 ++++++++- 15 files changed, 223 insertions(+), 23 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs create mode 100644 src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs index 62c2950d..c881b49d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs @@ -1,6 +1,7 @@ namespace BotSharp.Abstraction.MessageHub.Models; -public abstract class ObserveDataBase +public class ObserveDataBase { public IServiceProvider ServiceProvider { get; set; } = null!; + public string RefId { get; set; } = null!; } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs new file mode 100644 index 00000000..ee8345cd --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs @@ -0,0 +1,22 @@ +using BotSharp.Abstraction.MessageHub.Observers; + +namespace BotSharp.Abstraction.MessageHub.Models; + +public class ObserverSubscription +{ + public IBotSharpObserver Observer { get; set; } + public IDisposable Subscription { get; set; } + + public ObserverSubscription() + { + + } + + public ObserverSubscription( + IBotSharpObserver observer, + IDisposable subscription) + { + Observer = observer; + Subscription = subscription; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs new file mode 100644 index 00000000..7b824075 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.MessageHub.Observers; + +public abstract class BotSharpObserverBase +{ + protected BotSharpObserverBase() + { + + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs new file mode 100644 index 00000000..edbb4df2 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.MessageHub.Observers; + +public interface IBotSharpObserver : IObserver +{ + //string Name { get; } + bool IsActive { get; } + void Activate(); + void Deactivate(); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs new file mode 100644 index 00000000..337f2bbf --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs @@ -0,0 +1,8 @@ +using BotSharp.Abstraction.MessageHub.Models; + +namespace BotSharp.Abstraction.MessageHub.Services; + +public interface IObserverService +{ + ObserverSubscriptionContainer RegisterObservers(string refId) where T : ObserveDataBase; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs new file mode 100644 index 00000000..860da06d --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs @@ -0,0 +1,49 @@ +using BotSharp.Abstraction.MessageHub.Models; + +namespace BotSharp.Abstraction.MessageHub.Services; + +public class ObserverSubscriptionContainer : IDisposable +{ + private IList> _subscriptions = []; + private bool _disposed = false; + + public ObserverSubscriptionContainer() + { + + } + + public ObserverSubscriptionContainer( + IList> subscriptions) + { + _subscriptions = subscriptions; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { +#if DEBUG + Console.WriteLine($"Start disposing subscriptions..."); +#endif + // Unregister all observers + foreach (var item in _subscriptions) + { + item.Observer.Deactivate(); + item.Subscription.Dispose(); + } + _subscriptions.Clear(); +#if DEBUG + Console.WriteLine($"End disposing subscriptions..."); +#endif + } + _disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index 1a36875c..d3797466 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -1,5 +1,8 @@ using BotSharp.Abstraction.Google.Settings; using BotSharp.Abstraction.Instructs; +using BotSharp.Abstraction.MessageHub; +using BotSharp.Abstraction.MessageHub.Observers; +using BotSharp.Abstraction.MessageHub.Services; using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Plugins.Models; @@ -8,6 +11,7 @@ using BotSharp.Abstraction.Templating; using BotSharp.Core.Instructs; using BotSharp.Core.MessageHub; using BotSharp.Core.MessageHub.Observers; +using BotSharp.Core.MessageHub.Services; using BotSharp.Core.Messaging; using BotSharp.Core.Routing.Reasoning; using BotSharp.Core.Templating; @@ -45,6 +49,8 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin }); services.AddSingleton>>(); + //services.AddScoped>, ConversationObserver>(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -71,9 +77,8 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { - var services = app.ApplicationServices; - var queue = services.GetRequiredService>>(); - var logger = services.GetRequiredService>>>(); - queue.Events.Subscribe(new ConversationObserver(logger)); + //var services = app.ApplicationServices; + //var queue = services.GetRequiredService>>(); + //var logger = services.GetRequiredService>>>(); } } diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs index a22f5cec..41b96399 100644 --- a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Functions; using BotSharp.Core.MessageHub; +using System.Text.Json.Serialization; namespace BotSharp.Core.Demo.Functions; @@ -17,26 +18,29 @@ public class GetWeatherFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { + var args = JsonSerializer.Deserialize(message.FunctionArgs); var conv = _services.GetRequiredService(); var messageHub = _services.GetRequiredService>>(); await Task.Delay(1000); - message.Indication = "Start querying weather data"; + message.Indication = $"Start querying weather data in {args?.City}"; messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, Data = message, + RefId = conv.ConversationId, ServiceProvider = _services }); await Task.Delay(1500); - message.Indication = "Still working on it"; + message.Indication = $"Still working on it, {args?.City}"; messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, Data = message, + RefId = conv.ConversationId, ServiceProvider = _services }); @@ -46,4 +50,10 @@ public class GetWeatherFn : IFunctionCallback message.StopCompletion = false; return true; } +} + +class WeatherLocation +{ + [JsonPropertyName("city")] + public string City { get; set; } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs index 8e99f898..8f01612d 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/MessageHub.cs @@ -2,7 +2,7 @@ using System.Reactive.Subjects; namespace BotSharp.Core.MessageHub; -public class MessageHub where T : class, new() +public class MessageHub where T : ObserveDataBase { private readonly ILogger> _logger; private readonly ISubject _observable = Subject.Synchronize(new Subject()); diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs index 15b44157..6c22f753 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs @@ -1,15 +1,31 @@ +using BotSharp.Abstraction.MessageHub.Observers; + namespace BotSharp.Core.MessageHub.Observers; -public class ConversationObserver : IObserver> +public class ConversationObserver : IBotSharpObserver> { - private readonly ILogger _logger; + private readonly ILogger _logger; private IServiceProvider _services; + private bool _isActive; - public ConversationObserver(ILogger logger) + public ConversationObserver( + ILogger logger) { _logger = logger; } + public bool IsActive => _isActive; + + public void Activate() + { + _isActive = true; + } + + public void Deactivate() + { + _isActive = false; + } + public void OnCompleted() { _logger.LogWarning($"{nameof(ConversationObserver)} receives complete notification."); @@ -23,15 +39,14 @@ public class ConversationObserver : IObserver> public void OnNext(HubObserveData value) { _services = value.ServiceProvider; - var progress = _services.GetRequiredService(); + //var progress = _services.GetRequiredService(); - if (value.EventName == ChatEvent.OnIndicationReceived - && progress.OnFunctionExecuting != null) + if (value.EventName == ChatEvent.OnIndicationReceived) { -#if DEBUG - _logger.LogCritical($"Receiving {value.EventName} in {nameof(ConversationObserver)}"); +#if !DEBUG + _logger.LogCritical($"Receiving {value.EventName} ({value.Data.Indication}) in {nameof(ConversationObserver)}"); #endif - progress.OnFunctionExecuting(value.Data).ConfigureAwait(false).GetAwaiter().GetResult(); + //progress.OnFunctionExecuting(value.Data).ConfigureAwait(false).GetAwaiter().GetResult(); } } } diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs new file mode 100644 index 00000000..c2165fc6 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs @@ -0,0 +1,43 @@ +using BotSharp.Abstraction.MessageHub.Observers; +using BotSharp.Abstraction.MessageHub.Services; +using System.Reactive.Linq; + +namespace BotSharp.Core.MessageHub.Services; + +public class ObserverService : IObserverService +{ + private readonly IServiceProvider _services; + + public ObserverService( + IServiceProvider services) + { + _services = services; + } + + public ObserverSubscriptionContainer RegisterObservers(string refId) where T : ObserveDataBase + { + var subscriptions = new List>(); + var observers = _services.GetServices>() + .Where(x => !x.IsActive) + .ToList(); + + if (observers.IsNullOrEmpty()) + { + return new(); + } + + var messageHub = _services.GetRequiredService>(); + foreach (var observer in observers) + { + observer.Activate(); + var sub = messageHub.Events.Where(x => x.RefId == refId).Subscribe(observer); + subscriptions.Add(new() + { + Observer = observer, + Subscription = sub + }); + } + + return new(subscriptions); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index f693f4dd..a009b88f 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -25,11 +25,13 @@ public partial class RoutingService clonedMessage.FunctionName = name; clonedMessage.Indication = await funcExecutor.GetIndicatorAsync(message); + var conv = _services.GetRequiredService(); var messageHub = _services.GetRequiredService>>(); messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, Data = clonedMessage, + RefId = conv.ConversationId, ServiceProvider = _services }); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index bd131ca1..345b926d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -1,10 +1,13 @@ using BotSharp.Abstraction.Files.Constants; using BotSharp.Abstraction.Files.Enums; using BotSharp.Abstraction.Files.Utilities; +using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Abstraction.MessageHub.Services; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Users.Dtos; using BotSharp.Core.Infrastructures; +using System.ComponentModel; namespace BotSharp.OpenAPI.Controllers; @@ -343,6 +346,9 @@ public class ConversationController : ControllerBase [FromRoute] string conversationId, [FromBody] NewMessageModel input) { + var observerService = _services.GetRequiredService(); + using var container = observerService.RegisterObservers>(conversationId); + var conv = _services.GetRequiredService(); var inputMsg = new RoleDialogModel(AgentRole.User, input.Text) { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index 38bd936c..4c48ef74 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -1,6 +1,8 @@ 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; @@ -24,6 +26,8 @@ public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin config.Bind("ChatHub", settings); services.AddSingleton(x => settings); + services.AddScoped>, ChatHubObserver>(); + // Register hooks services.AddScoped(); services.AddScoped(); @@ -35,9 +39,8 @@ public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { - var services = app.ApplicationServices; - var queue = services.GetRequiredService>>(); - var logger = services.GetRequiredService>>>(); - queue.Events.Subscribe(new ChatHubObserver(logger)); + //var services = app.ApplicationServices; + //var queue = services.GetRequiredService>>(); + //var logger = services.GetRequiredService>>>(); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index 75aa0cd3..bf2b1ed8 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -1,21 +1,37 @@ using BotSharp.Abstraction.Conversations.Dtos; using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Abstraction.MessageHub.Observers; using BotSharp.Abstraction.SideCar; +using BotSharp.Core.MessageHub.Observers; using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Observers; -public class ChatHubObserver : IObserver> +public class ChatHubObserver : IBotSharpObserver> { private readonly ILogger _logger; private IServiceProvider _services; + private bool _isActive = false; - public ChatHubObserver(ILogger logger) + public ChatHubObserver( + ILogger logger) { _logger = logger; } + public bool IsActive => _isActive; + + public void Activate() + { + _isActive = true; + } + + public void Deactivate() + { + _isActive = false; + } + public void OnCompleted() { _logger.LogWarning($"{nameof(ChatHubObserver)} receives complete notification."); @@ -117,6 +133,8 @@ public class ChatHubObserver : IObserver> Role = AgentRole.Assistant } }; + + _logger.LogCritical($"Receiving {value.EventName} ({value.Data.Indication}) in {nameof(ChatHubObserver)} - {conv.ConversationId}"); break; } From e32d82044051c228760e9edf7a902af4959b5c1b Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 1 Aug 2025 13:23:46 -0500 Subject: [PATCH 17/51] refine observer --- .../MessageHub/Models/HubObserveData.cs | 1 - .../MessageHub/Models/ObserveDataBase.cs | 2 +- .../MessageHub/Models/ObserverSubscription.cs | 6 ++ .../Observers/BotSharpObserverBase.cs | 34 +++++++++- .../MessageHub/Observers/IBotSharpObserver.cs | 4 +- .../MessageHub/Services/IObserverService.cs | 4 +- .../Services/ObserverSubscriptionContainer.cs | 49 -------------- .../Conversations/ConversationPlugin.cs | 7 +- .../Demo/Functions/GetWeatherFn.cs | 8 +-- .../Observers/ConversationObserver.cs | 34 ++++------ .../MessageHub/Services/ObserverService.cs | 41 ++++++++++-- .../Services/ObserverSubscriptionContainer.cs | 64 +++++++++++++++++++ .../Routing/RoutingService.InvokeFunction.cs | 7 +- .../Controllers/ConversationController.cs | 7 +- .../Providers/Chat/ChatCompletionProvider.cs | 15 +++-- .../BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 4 +- .../Observers/ChatHubObserver.cs | 30 +++------ .../Providers/Chat/ChatCompletionProvider.cs | 14 ++-- .../Providers/ChatCompletionProvider.cs | 14 ++-- .../Providers/Chat/ChatCompletionProvider.cs | 7 +- .../Providers/ChatCompletionProvider.cs | 16 +++-- 21 files changed, 215 insertions(+), 153 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs create mode 100644 src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverSubscriptionContainer.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs index 277893ad..d345ab86 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/HubObserveData.cs @@ -2,6 +2,5 @@ namespace BotSharp.Abstraction.MessageHub.Models; public class HubObserveData : ObserveDataBase where TData : class, new() { - public string EventName { get; set; } = null!; public TData Data { get; set; } = null!; } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs index c881b49d..c7d5c0f6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserveDataBase.cs @@ -2,6 +2,6 @@ namespace BotSharp.Abstraction.MessageHub.Models; public class ObserveDataBase { - public IServiceProvider ServiceProvider { get; set; } = null!; + public string EventName { get; set; } = null!; public string RefId { get; set; } = null!; } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs index ee8345cd..0ba75531 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Models/ObserverSubscription.cs @@ -19,4 +19,10 @@ public class ObserverSubscription Observer = observer; Subscription = subscription; } + + public void UnSubscribe() + { + Observer.Deactivate(); + Subscription.Dispose(); + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs index 7b824075..5afe9915 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs @@ -1,9 +1,41 @@ + namespace BotSharp.Abstraction.MessageHub.Observers; -public abstract class BotSharpObserverBase +public abstract class BotSharpObserverBase : IBotSharpObserver { + protected bool _active = false; + protected BotSharpObserverBase() { } + + public virtual string Name => string.Empty; + + public virtual bool Active => _active; + + public virtual void Activate() + { + _active = true; + } + + public virtual void Deactivate() + { + _active = false; + } + + public virtual void OnCompleted() + { + + } + + public virtual void OnError(Exception error) + { + + } + + public virtual void OnNext(T value) + { + + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs index edbb4df2..1c160765 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs @@ -2,8 +2,8 @@ namespace BotSharp.Abstraction.MessageHub.Observers; public interface IBotSharpObserver : IObserver { - //string Name { get; } - bool IsActive { get; } + string Name { get; } + bool Active { get; } void Activate(); void Deactivate(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs index 337f2bbf..bc4ad3bb 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs @@ -4,5 +4,7 @@ namespace BotSharp.Abstraction.MessageHub.Services; public interface IObserverService { - ObserverSubscriptionContainer RegisterObservers(string refId) where T : ObserveDataBase; + IDisposable SubscribeObservers(string refId, IEnumerable? names = null) where T : ObserveDataBase; + + void UnSubscribeObservers(IEnumerable? names = null) where T : ObserveDataBase; } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs deleted file mode 100644 index 860da06d..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/ObserverSubscriptionContainer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using BotSharp.Abstraction.MessageHub.Models; - -namespace BotSharp.Abstraction.MessageHub.Services; - -public class ObserverSubscriptionContainer : IDisposable -{ - private IList> _subscriptions = []; - private bool _disposed = false; - - public ObserverSubscriptionContainer() - { - - } - - public ObserverSubscriptionContainer( - IList> subscriptions) - { - _subscriptions = subscriptions; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { -#if DEBUG - Console.WriteLine($"Start disposing subscriptions..."); -#endif - // Unregister all observers - foreach (var item in _subscriptions) - { - item.Observer.Deactivate(); - item.Subscription.Dispose(); - } - _subscriptions.Clear(); -#if DEBUG - Console.WriteLine($"End disposing subscriptions..."); -#endif - } - _disposed = true; - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index d3797466..0759f104 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -49,7 +49,8 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin }); services.AddSingleton>>(); - //services.AddScoped>, ConversationObserver>(); + services.AddScoped>>(); + services.AddScoped>, ConversationObserver>(); services.AddScoped(); services.AddScoped(); @@ -77,8 +78,6 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { - //var services = app.ApplicationServices; - //var queue = services.GetRequiredService>>(); - //var logger = services.GetRequiredService>>>(); + } } diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs index 41b96399..f39571d9 100644 --- a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs @@ -29,19 +29,17 @@ public class GetWeatherFn : IFunctionCallback { EventName = ChatEvent.OnIndicationReceived, Data = message, - RefId = conv.ConversationId, - ServiceProvider = _services + RefId = conv.ConversationId }); await Task.Delay(1500); - message.Indication = $"Still working on it, {args?.City}"; + message.Indication = $"Still working on it... Hold on, {args?.City}"; messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, Data = message, - RefId = conv.ConversationId, - ServiceProvider = _services + RefId = conv.ConversationId }); await Task.Delay(1500); diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs index 6c22f753..f0d564d7 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs @@ -2,49 +2,39 @@ using BotSharp.Abstraction.MessageHub.Observers; namespace BotSharp.Core.MessageHub.Observers; -public class ConversationObserver : IBotSharpObserver> +public class ConversationObserver : BotSharpObserverBase> { private readonly ILogger _logger; - private IServiceProvider _services; - private bool _isActive; + private readonly IServiceProvider _services; public ConversationObserver( - ILogger logger) + IServiceProvider services, + ILogger logger) : base() { + _services = services; _logger = logger; } - public bool IsActive => _isActive; + public override string Name => nameof(ConversationObserver); - public void Activate() - { - _isActive = true; - } - - public void Deactivate() - { - _isActive = false; - } - - public void OnCompleted() + public override void OnCompleted() { _logger.LogWarning($"{nameof(ConversationObserver)} receives complete notification."); } - public void OnError(Exception error) + public override void OnError(Exception error) { _logger.LogError(error, $"{nameof(ConversationObserver)} receives error notification: {error.Message}"); } - public void OnNext(HubObserveData value) + public override void OnNext(HubObserveData value) { - _services = value.ServiceProvider; - //var progress = _services.GetRequiredService(); + var conv = _services.GetRequiredService(); if (value.EventName == ChatEvent.OnIndicationReceived) { -#if !DEBUG - _logger.LogCritical($"Receiving {value.EventName} ({value.Data.Indication}) in {nameof(ConversationObserver)}"); +#if DEBUG + _logger.LogCritical($"Receiving {value.EventName} ({value.Data.Indication}) in {nameof(ConversationObserver)} - {conv.ConversationId}"); #endif //progress.OnFunctionExecuting(value.Data).ConfigureAwait(false).GetAwaiter().GetResult(); } diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs index c2165fc6..60f1320a 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs @@ -7,25 +7,38 @@ namespace BotSharp.Core.MessageHub.Services; public class ObserverService : IObserverService { private readonly IServiceProvider _services; + private readonly ILogger _logger; public ObserverService( - IServiceProvider services) + IServiceProvider services, + ILogger logger) { _services = services; + _logger = logger; } - public ObserverSubscriptionContainer RegisterObservers(string refId) where T : ObserveDataBase + public IDisposable SubscribeObservers(string refId, IEnumerable? names = null) where T : ObserveDataBase { - var subscriptions = new List>(); + var container = _services.GetRequiredService>(); var observers = _services.GetServices>() - .Where(x => !x.IsActive) + .Where(x => !x.Active) .ToList(); + if (!names.IsNullOrEmpty()) + { + observers = observers.Where(x => names.Contains(x.Name)).ToList(); + } + if (observers.IsNullOrEmpty()) { - return new(); + return container; } +#if DEBUG + _logger.LogCritical($"Subscribe observers: {string.Join(",", observers.Select(x => x.Name))}"); +#endif + + var subscriptions = new List>(); var messageHub = _services.GetRequiredService>(); foreach (var observer in observers) { @@ -38,6 +51,22 @@ public class ObserverService : IObserverService }); } - return new(subscriptions); + container.Append(subscriptions); + return container; + } + + public void UnSubscribeObservers(IEnumerable? names = null) where T : ObserveDataBase + { + var container = _services.GetRequiredService>(); + var subscriptions = container.GetSubscriptions(names); + +#if DEBUG + _logger.LogCritical($"UnSubscribe observers: {string.Join(",", subscriptions.Select(x => x.Observer.Name))}"); +#endif + + foreach (var sub in subscriptions) + { + sub.UnSubscribe(); + } } } diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverSubscriptionContainer.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverSubscriptionContainer.cs new file mode 100644 index 00000000..59e33e2e --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverSubscriptionContainer.cs @@ -0,0 +1,64 @@ +namespace BotSharp.Core.MessageHub.Services; + +public class ObserverSubscriptionContainer : IDisposable +{ + private readonly ILogger> _logger; + + private List> _subscriptions = []; + private bool _disposed = false; + + public ObserverSubscriptionContainer( + ILogger> logger) + { + _logger = logger; + } + + public List> GetSubscriptions(IEnumerable? names = null) + { + if (!names.IsNullOrEmpty()) + { + return _subscriptions.Where(x => names.Contains(x.Observer.Name)).ToList(); + } + return _subscriptions; + } + + public void Append(List> subscriptions) + { + _subscriptions = _subscriptions.Concat(subscriptions).DistinctBy(x => x.Observer.Name).ToList(); + } + + public void Remove(IEnumerable? names = null) + { + if (!names.IsNullOrEmpty()) + { + _subscriptions = _subscriptions.Where(x => !names.Contains(x.Observer.Name)).ToList(); + return; + } + _subscriptions.Clear(); + } + + public void Clear() + { + _subscriptions.Clear(); + } + + public void Dispose() + { + if (!_disposed) + { +#if DEBUG + _logger.LogCritical($"Start disposing subscriptions..."); +#endif + // UnSubscribe all observers + foreach (var sub in _subscriptions) + { + sub.UnSubscribe(); + } + _subscriptions.Clear(); +#if DEBUG + _logger.LogCritical($"End disposing subscriptions..."); +#endif + _disposed = true; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 69909ac9..694ab2d3 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -31,8 +31,7 @@ public partial class RoutingService { EventName = ChatEvent.OnIndicationReceived, Data = clonedMessage, - RefId = conv.ConversationId, - ServiceProvider = _services + RefId = conv.ConversationId }); var hooks = _services.GetHooksOrderByPriority(clonedMessage.CurrentAgentId); @@ -67,7 +66,7 @@ public partial class RoutingService } catch (JsonException ex) { - _logger.LogError($"The input does not contain any JSON tokens:\r\n{message.Content}\r\n{ex.Message}"); + _logger.LogError(ex, $"The input does not contain any JSON tokens:\r\n{message.Content}\r\n{ex.Message}"); message.StopCompletion = true; message.Content = ex.Message; } @@ -75,7 +74,7 @@ public partial class RoutingService { message.StopCompletion = true; message.Content = ex.Message; - _logger.LogError(ex.ToString()); + _logger.LogError(ex, ex.ToString()); } // Make sure content has been populated diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 345b926d..3d80b0d5 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -7,7 +7,7 @@ using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Users.Dtos; using BotSharp.Core.Infrastructures; -using System.ComponentModel; +using BotSharp.Core.MessageHub.Observers; namespace BotSharp.OpenAPI.Controllers; @@ -346,8 +346,8 @@ public class ConversationController : ControllerBase [FromRoute] string conversationId, [FromBody] NewMessageModel input) { - var observerService = _services.GetRequiredService(); - using var container = observerService.RegisterObservers>(conversationId); + var observer = _services.GetRequiredService(); + using var container = observer.SubscribeObservers>(conversationId); var conv = _services.GetRequiredService(); var inputMsg = new RoleDialogModel(AgentRole.User, input.Text) @@ -363,7 +363,6 @@ public class ConversationController : ControllerBase SetStates(conv, input); var response = new ChatResponseModel(); - await conv.SendMessage(agentId, inputMsg, replyMessage: input.Postback, async msg => diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs index b19bdaef..ca2a283e 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,4 +1,4 @@ -using Azure; +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Files.Utilities; using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.MessageHub.Models; @@ -213,6 +213,7 @@ public class ChatCompletionProvider : IChatCompletion var (prompt, messages, options) = PrepareOptions(agent, conversations); var hub = _services.GetRequiredService>>(); + var conv = _services.GetRequiredService(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; var contentHooks = _services.GetHooks(agent.Id); @@ -224,8 +225,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, - EventName = "BeforeReceiveLlmStreamMessage", + EventName = ChatEvent.BeforeReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = new RoleDialogModel(AgentRole.Assistant, string.Empty) { CurrentAgentId = agent.Id, @@ -268,8 +269,8 @@ public class ChatCompletionProvider : IChatCompletion }; hub.Push(new() { - ServiceProvider = _services, - EventName = "OnReceiveLlmStreamMessage", + EventName = ChatEvent.OnReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = content }); } @@ -311,8 +312,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, - EventName = "AfterReceiveLlmStreamMessage", + EventName = ChatEvent.AfterReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = responseMessage }); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index 4c48ef74..14b7ca01 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -39,8 +39,6 @@ public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin public void Configure(IApplicationBuilder app) { - //var services = app.ApplicationServices; - //var queue = services.GetRequiredService>>(); - //var logger = services.GetRequiredService>>>(); + } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index bf2b1ed8..d9c4dcc2 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -3,49 +3,37 @@ using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Abstraction.MessageHub.Observers; using BotSharp.Abstraction.SideCar; -using BotSharp.Core.MessageHub.Observers; using System.Runtime.CompilerServices; namespace BotSharp.Plugin.ChatHub.Observers; -public class ChatHubObserver : IBotSharpObserver> +public class ChatHubObserver : BotSharpObserverBase> { private readonly ILogger _logger; - private IServiceProvider _services; - private bool _isActive = false; + private readonly IServiceProvider _services; public ChatHubObserver( - ILogger logger) + IServiceProvider services, + ILogger logger) : base() { + _services = services; _logger = logger; } - public bool IsActive => _isActive; + public override string Name => nameof(ChatHubObserver); - public void Activate() - { - _isActive = true; - } - - public void Deactivate() - { - _isActive = false; - } - - public void OnCompleted() + public override void OnCompleted() { _logger.LogWarning($"{nameof(ChatHubObserver)} receives complete notification."); } - public void OnError(Exception error) + public override void OnError(Exception error) { _logger.LogError(error, $"{nameof(ChatHubObserver)} receives error notification: {error.Message}"); } - public void OnNext(HubObserveData value) + public override void OnNext(HubObserveData value) { - _services = value.ServiceProvider; - var message = value.Data; var model = new ChatResponseDto(); var action = new ConversationSenderActionModel(); diff --git a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs index a404aef1..f29dd77a 100644 --- a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Files; using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.MessageHub.Models; @@ -180,6 +181,7 @@ public class ChatCompletionProvider : IChatCompletion var (prompt, messages, options) = PrepareOptions(agent, conversations); var hub = _services.GetRequiredService>>(); + var conv = _services.GetRequiredService(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; var contentHooks = _services.GetHooks(agent.Id); @@ -191,8 +193,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, - EventName = "BeforeReceiveLlmStreamMessage", + EventName = ChatEvent.BeforeReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = new RoleDialogModel(AgentRole.Assistant, string.Empty) { CurrentAgentId = agent.Id, @@ -235,8 +237,8 @@ public class ChatCompletionProvider : IChatCompletion }; hub.Push(new() { - ServiceProvider = _services, - EventName = "OnReceiveLlmStreamMessage", + EventName = ChatEvent.OnReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = content }); } @@ -278,8 +280,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, - EventName = "AfterReceiveLlmStreamMessage", + EventName = ChatEvent.AfterReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = responseMessage }); diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs index a4f18d31..08b6c676 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/ChatCompletionProvider.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Loggers; using BotSharp.Abstraction.MessageHub.Models; @@ -184,12 +185,13 @@ public class ChatCompletionProvider : IChatCompletion } var hub = _services.GetRequiredService>>(); + var conv = _services.GetRequiredService(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; hub.Push(new() { - ServiceProvider = _services, - EventName = "BeforeReceiveLlmStreamMessage", + EventName = ChatEvent.BeforeReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = new RoleDialogModel(AgentRole.Assistant, string.Empty) { CurrentAgentId = agent.Id, @@ -216,8 +218,8 @@ public class ChatCompletionProvider : IChatCompletion }; hub.Push(new() { - ServiceProvider = _services, - EventName = "OnReceiveLlmStreamMessage", + EventName = ChatEvent.OnReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = content }); } @@ -231,8 +233,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, - EventName = "AfterReceiveLlmStreamMessage", + EventName = ChatEvent.AfterReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = responseMessage }); diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 86447dca..af7e7e0a 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -189,6 +189,7 @@ public class ChatCompletionProvider : IChatCompletion var (prompt, messages, options) = PrepareOptions(agent, conversations); var hub = _services.GetRequiredService>>(); + var conv = _services.GetRequiredService(); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; var contentHooks = _services.GetHooks(agent.Id); @@ -200,8 +201,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, EventName = ChatEvent.BeforeReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = new RoleDialogModel(AgentRole.Assistant, string.Empty) { CurrentAgentId = agent.Id, @@ -244,8 +245,8 @@ public class ChatCompletionProvider : IChatCompletion }; hub.Push(new() { - ServiceProvider = _services, EventName = ChatEvent.OnReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = content }); } @@ -287,8 +288,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, EventName = ChatEvent.AfterReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = responseMessage }); diff --git a/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs index c2e10def..a1d45dcc 100644 --- a/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.SparkDesk/Providers/ChatCompletionProvider.cs @@ -1,10 +1,11 @@ using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Loggers; using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; using BotSharp.Core.MessageHub; -using Microsoft.AspNetCore.SignalR; namespace BotSharp.Plugin.SparkDesk.Providers; @@ -153,11 +154,12 @@ public class ChatCompletionProvider : IChatCompletion var (prompt, messages, funcall) = PrepareOptions(agent, conversations); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; var hub = _services.GetRequiredService>>(); + var conv = _services.GetRequiredService(); hub.Push(new() { - ServiceProvider = _services, - EventName = "BeforeReceiveLlmStreamMessage", + EventName = ChatEvent.BeforeReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = new RoleDialogModel(AgentRole.Assistant, string.Empty) { CurrentAgentId = agent.Id, @@ -197,8 +199,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, - EventName = "OnReceiveLlmStreamMessage", + EventName = ChatEvent.OnReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = responseMessage }); } @@ -212,8 +214,8 @@ public class ChatCompletionProvider : IChatCompletion hub.Push(new() { - ServiceProvider = _services, - EventName = "AfterReceiveLlmStreamMessage", + EventName = ChatEvent.AfterReceiveLlmStreamMessage, + RefId = conv.ConversationId, Data = responseMessage }); From 20de3f75658be2bfea0fd67609750947db9dbff9 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 1 Aug 2025 14:41:17 -0500 Subject: [PATCH 18/51] refine chat event --- .../BotSharp.Core/BotSharp.Core.csproj | 5 +- .../Conversations/ConversationPlugin.cs | 8 +-- .../Demo/Functions/GetFunEventsFn.cs | 51 +++++++++++++++++++ .../Demo/Functions/GetWeatherFn.cs | 18 +++++++ .../MessageHub/Services/ObserverService.cs | 3 ++ .../functions/get_fun_events.json | 15 ++++++ .../functions/get_weather.json | 1 + .../BotSharp.Plugin.ChatHub/ChatHubPlugin.cs | 7 +-- .../Hooks/ChatHubConversationHook.cs | 15 +++--- .../Observers/ChatHubObserver.cs | 10 +--- 10 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs create mode 100644 src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_fun_events.json diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index ddcf68cb..ec10cb45 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -98,6 +98,7 @@ + @@ -211,6 +212,9 @@ PreserveNewest + + PreserveNewest + @@ -242,5 +246,4 @@ true - diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index 0759f104..511a27f7 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -21,7 +21,7 @@ using Microsoft.Extensions.Configuration; namespace BotSharp.Core.Conversations; -public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin +public class ConversationPlugin : IBotSharpPlugin { public string Id => "99e9b971-a9f1-4273-84da-876d2873d192"; public string Name => "Conversation"; @@ -48,6 +48,7 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin return settingService.Bind("GoogleApi"); }); + // Observer and observable services.AddSingleton>>(); services.AddScoped>>(); services.AddScoped>, ConversationObserver>(); @@ -75,9 +76,4 @@ public class ConversationPlugin : IBotSharpPlugin, IBotSharpAppPlugin menu.Add(new PluginMenuDef("Conversation", link: "page/conversation", icon: "bx bx-conversation", weight: section.Weight + 5)); return true; } - - public void Configure(IApplicationBuilder app) - { - - } } diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs new file mode 100644 index 00000000..beae84c3 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs @@ -0,0 +1,51 @@ +using BotSharp.Abstraction.Functions; +using BotSharp.Core.MessageHub; +using System.Text.Json.Serialization; + +namespace BotSharp.Core.Demo.Functions; + +public class GetFunEventsFn : IFunctionCallback +{ + private readonly IServiceProvider _services; + + public GetFunEventsFn(IServiceProvider services) + { + _services = services; + } + + public string Name => "get_fun_events"; + public string Indication => "Searching fun events"; + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + var conv = _services.GetRequiredService(); + var messageHub = _services.GetRequiredService>>(); + + await Task.Delay(1000); + + message.Indication = $"Start querying event data in {args?.City}"; + messageHub.Push(new() + { + EventName = ChatEvent.OnIndicationReceived, + Data = message, + RefId = conv.ConversationId + }); + + await Task.Delay(1500); + + message.Indication = $"Still searching in {args?.City}"; + messageHub.Push(new() + { + EventName = ChatEvent.OnIndicationReceived, + Data = message, + RefId = conv.ConversationId + }); + + await Task.Delay(1500); + + message.Content = $"Here in {args?.City}, there are a lot of fun events in summer."; + message.StopCompletion = true; + return true; + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs index f39571d9..6465b88e 100644 --- a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs @@ -1,4 +1,6 @@ using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Models; +using BotSharp.Abstraction.SideCar; using BotSharp.Core.MessageHub; using System.Text.Json.Serialization; @@ -46,6 +48,22 @@ public class GetWeatherFn : IFunctionCallback message.Content = $"It is a sunny day!"; message.StopCompletion = false; + +#if DEBUG + var sidecar = _services.GetService(); + if (sidecar != null) + { + var text = $"I want to know fun events in {args?.City}"; + var states = new List + { + new() { Key = "channel", Value = "email" } + }; + + var msg = await sidecar.SendMessage(message.CurrentAgentId, text, states: states); + message.Content = $"{message.Content} {msg.Content}"; + } +#endif + return true; } } diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs index 60f1320a..19a5d651 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Services/ObserverService.cs @@ -66,7 +66,10 @@ public class ObserverService : IObserverService foreach (var sub in subscriptions) { + if (!sub.Observer.Active) continue; + sub.UnSubscribe(); } + container.Remove(names); } } diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_fun_events.json b/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_fun_events.json new file mode 100644 index 00000000..94b6abbf --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_fun_events.json @@ -0,0 +1,15 @@ +{ + "name": "get_fun_events", + "description": "Get fun events information for user.", + "visibility_expression": "{% if states.channel == 'email' %}visible{% endif %}", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "The city where user wants to find fun events." + } + }, + "required": [ "city" ] + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_weather.json b/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_weather.json index 0fd0a459..ab73340f 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_weather.json +++ b/src/Infrastructure/BotSharp.Core/data/agents/01e2fc5c-2c89-4ec7-8470-7688608b496c/functions/get_weather.json @@ -1,6 +1,7 @@ { "name": "get_weather", "description": "Get weather information for user.", + "visibility_expression": "{% if states.channel != 'email' %}visible{% endif %}", "parameters": { "type": "object", "properties": { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index 14b7ca01..51466692 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -13,7 +13,7 @@ namespace BotSharp.Plugin.ChatHub; /// /// The dialogue channel connects users, AI assistants and customer service representatives. /// -public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin +public class ChatHubPlugin : IBotSharpPlugin { public string Id => "6e52d42d-1e23-406b-8599-36af36c83209"; public string Name => "Chat Hub"; @@ -36,9 +36,4 @@ public class ChatHubPlugin : IBotSharpPlugin, IBotSharpAppPlugin services.AddScoped(); services.AddScoped(); } - - public void Configure(IApplicationBuilder app) - { - - } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 52dd4f7c..bd590276 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -113,16 +113,13 @@ public class ChatHubConversationHook : ConversationHookBase }; // Send typing-off to client - if (!message.IsStreaming) + var action = new ConversationSenderActionModel { - var action = new ConversationSenderActionModel - { - ConversationId = conv.ConversationId, - SenderAction = SenderActionEnum.TypingOff - }; - await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action); - } - + ConversationId = conv.ConversationId, + SenderAction = SenderActionEnum.TypingOff + }; + await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action); + await SendEvent(ChatEvent.OnMessageReceivedFromAssistant, conv.ConversationId, data); await base.OnResponseGenerated(message); } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index d9c4dcc2..9b5a97e5 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -99,14 +99,6 @@ public class ChatHubObserver : BotSharpObserverBase Date: Fri, 1 Aug 2025 15:47:42 -0500 Subject: [PATCH 19/51] replace conversation progress service --- .../IConversationProgressService.cs | 10 ------ .../Observers/BotSharpObserverBase.cs | 9 +++++- .../MessageHub/Observers/IBotSharpObserver.cs | 2 ++ .../MessageHub/Services/IObserverService.cs | 5 ++- .../Conversations/ConversationPlugin.cs | 1 - .../Services/ConversationProgressService.cs | 11 ------- .../Demo/Functions/GetFunEventsFn.cs | 2 +- .../Observers/ConversationObserver.cs | 5 ++- .../MessageHub/Services/ObserverService.cs | 13 +++++++- .../Controllers/ConversationController.cs | 31 +++++++++---------- .../Services/TwilioMessageQueueService.cs | 22 +++++++------ 11 files changed, 58 insertions(+), 53 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationProgressService.cs delete mode 100644 src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationProgressService.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationProgressService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationProgressService.cs deleted file mode 100644 index 8958cab5..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationProgressService.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace BotSharp.Abstraction.Conversations; - -public delegate Task FunctionExecuting(RoleDialogModel msg); -public delegate Task FunctionExecuted(RoleDialogModel msg); - -public interface IConversationProgressService -{ - FunctionExecuted OnFunctionExecuted { get; set; } - FunctionExecuting OnFunctionExecuting { get; set; } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs index 5afe9915..1381ea03 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/BotSharpObserverBase.cs @@ -3,7 +3,8 @@ namespace BotSharp.Abstraction.MessageHub.Observers; public abstract class BotSharpObserverBase : IBotSharpObserver { - protected bool _active = false; + private bool _active = false; + protected Dictionary> _listeners = []; protected BotSharpObserverBase() { @@ -22,6 +23,12 @@ public abstract class BotSharpObserverBase : IBotSharpObserver public virtual void Deactivate() { _active = false; + _listeners = []; + } + + public virtual void SetEventListeners(Dictionary> listeners) + { + _listeners = listeners; } public virtual void OnCompleted() diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs index 1c160765..158ef4a6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Observers/IBotSharpObserver.cs @@ -4,6 +4,8 @@ public interface IBotSharpObserver : IObserver { string Name { get; } bool Active { get; } + + void SetEventListeners(Dictionary> listeners); void Activate(); void Deactivate(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs index bc4ad3bb..582849aa 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MessageHub/Services/IObserverService.cs @@ -4,7 +4,10 @@ namespace BotSharp.Abstraction.MessageHub.Services; public interface IObserverService { - IDisposable SubscribeObservers(string refId, IEnumerable? names = null) where T : ObserveDataBase; + IDisposable SubscribeObservers( + string refId, + IEnumerable? names = null, + Dictionary>? listeners = null) where T : ObserveDataBase; void UnSubscribeObservers(IEnumerable? names = null) where T : ObserveDataBase; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index 511a27f7..61c23d1b 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -56,7 +56,6 @@ public class ConversationPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationProgressService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationProgressService.cs deleted file mode 100644 index d7e6440c..00000000 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationProgressService.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace BotSharp.Core.Conversations.Services -{ - public class ConversationProgressService : IConversationProgressService - { - - public FunctionExecuting OnFunctionExecuting { get; set; } - - - public FunctionExecuted OnFunctionExecuted { get; set; } - } -} diff --git a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs index beae84c3..87ddb1ee 100644 --- a/src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs +++ b/src/Infrastructure/BotSharp.Core/Demo/Functions/GetFunEventsFn.cs @@ -34,7 +34,7 @@ public class GetFunEventsFn : IFunctionCallback await Task.Delay(1500); - message.Indication = $"Still searching in {args?.City}"; + message.Indication = $"Still searching events in {args?.City}"; messageHub.Push(new() { EventName = ChatEvent.OnIndicationReceived, diff --git a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs index f0d564d7..e2588c6c 100644 --- a/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs +++ b/src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs @@ -36,7 +36,10 @@ public class ConversationObserver : BotSharpObserverBase(string refId, IEnumerable? names = null) where T : ObserveDataBase + public IDisposable SubscribeObservers( + string refId, + IEnumerable? names = null, + Dictionary>? listeners = null) where T : ObserveDataBase { var container = _services.GetRequiredService>(); var observers = _services.GetServices>() @@ -34,6 +37,14 @@ public class ObserverService : IObserverService return container; } + if (!listeners.IsNullOrEmpty()) + { + foreach (var observer in observers) + { + observer.SetEventListeners(listeners ?? []); + } + } + #if DEBUG _logger.LogCritical($"Subscribe observers: {string.Join(",", observers.Select(x => x.Name))}"); #endif diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 3d80b0d5..ec7dead5 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -7,7 +7,6 @@ using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Users.Dtos; using BotSharp.Core.Infrastructures; -using BotSharp.Core.MessageHub.Observers; namespace BotSharp.OpenAPI.Controllers; @@ -386,6 +385,12 @@ public class ConversationController : ControllerBase [HttpPost("/conversation/{agentId}/{conversationId}/sse")] public async Task SendMessageSse([FromRoute] string agentId, [FromRoute] string conversationId, [FromBody] NewMessageModel input) { + var observer = _services.GetRequiredService(); + using var container = observer.SubscribeObservers>(conversationId, listeners: new() + { + { ChatEvent.OnIndicationReceived, async data => await OnReceiveToolCallIndication(conversationId, data.Data) } + }); + var conv = _services.GetRequiredService(); var inputMsg = new RoleDialogModel(AgentRole.User, input.Text) { @@ -411,7 +416,6 @@ public class ConversationController : ControllerBase Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream"); Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache"); Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive"); - InitProgressService(conversationId); await conv.SendMessage(agentId, inputMsg, replyMessage: input.Postback, @@ -435,23 +439,18 @@ public class ConversationController : ControllerBase // await OnEventCompleted(Response); } - private void InitProgressService(string conversationId) + private async Task OnReceiveToolCallIndication(string conversationId, RoleDialogModel msg) { - var progressService = _services.GetService(); - progressService.OnFunctionExecuting = async msg => + var indicator = new ChatResponseModel { - var indicator = new ChatResponseModel - { - ConversationId = conversationId, - MessageId = msg.MessageId, - Text = msg.Indication, - Function = "indicating", - Instruction = msg.Instruction, - States = new Dictionary() - }; - await OnChunkReceived(Response, indicator); + ConversationId = conversationId, + MessageId = msg.MessageId, + Text = msg.Indication, + Function = "indicating", + Instruction = msg.Instruction, + States = new Dictionary() }; - progressService.OnFunctionExecuted = async msg => { }; + await OnChunkReceived(Response, indicator); } #endregion diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index 84f88b5b..28360518 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -1,4 +1,6 @@ using BotSharp.Abstraction.Files; +using BotSharp.Abstraction.MessageHub.Models; +using BotSharp.Abstraction.MessageHub.Services; using BotSharp.Abstraction.Realtime; using BotSharp.Abstraction.Routing; using BotSharp.Core.Infrastructures; @@ -82,8 +84,12 @@ public class TwilioMessageQueueService : BackgroundService var routing = sp.GetRequiredService(); var config = sp.GetRequiredService(); var sessionManager = sp.GetRequiredService(); - var progressService = sp.GetRequiredService(); - InitProgressService(message, sessionManager, progressService); + var observer = sp.GetRequiredService(); + + using var container = observer.SubscribeObservers>(message.ConversationId, listeners: new() + { + { ChatEvent.OnIndicationReceived, async data => await OnReceiveToolCallIndication(data.Data, message, sessionManager) } + }); InitConversation(message, inputMsg, conv, routing); // Need to consider Inbound and Outbound call @@ -185,15 +191,11 @@ public class TwilioMessageQueueService : BackgroundService return string.Join(", ", hints.Select(x => x.ToLower()).Distinct().Reverse()); } - private static void InitProgressService(CallerMessage message, ITwilioSessionManager sessionManager, IConversationProgressService progressService) + private static async Task OnReceiveToolCallIndication(RoleDialogModel msg, CallerMessage message, ITwilioSessionManager sessionManager) { - progressService.OnFunctionExecuting = async msg => + if (!string.IsNullOrEmpty(msg.Indication)) { - if (!string.IsNullOrEmpty(msg.Indication)) - { - await sessionManager.SetReplyIndicationAsync(message.ConversationId, message.SeqNumber, msg.Indication); - } - }; - progressService.OnFunctionExecuted = async msg => { }; + await sessionManager.SetReplyIndicationAsync(message.ConversationId, message.SeqNumber, msg.Indication); + } } } From ab287469c76e1cba6a59c84af61e93374a529821 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 4 Aug 2025 10:56:36 -0500 Subject: [PATCH 20/51] fix missing params --- tests/BotSharp.LLM.Tests/Core/TestAgentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs index 50d34701..5d4b07d0 100644 --- a/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs +++ b/tests/BotSharp.LLM.Tests/Core/TestAgentService.cs @@ -16,7 +16,7 @@ namespace BotSharp.Plugin.Google.Core return Task.FromResult(new Agent()); } - public Task RefreshAgents() + public Task RefreshAgents(IEnumerable? agentIds = null) { return Task.FromResult("Refreshed successfully"); } From 6308bdea5fa712f12f56e09d0d51343a25835d8a Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 4 Aug 2025 14:21:11 -0500 Subject: [PATCH 21/51] change to options --- .../Conversations/ConversationHookBase.cs | 4 +-- .../Conversations/IConversationHook.cs | 4 +-- .../Routing/IRoutingService.cs | 4 +-- .../Routing/Models/InvokeOptions.cs | 31 +++++++++++++++++++ .../Hooks/RealtimeConversationHook.cs | 6 ++-- .../Services/RealtimeHub.cs | 2 +- .../Evaluations/EvaluationConversationHook.cs | 5 +-- .../Routing/Reasoning/InstructExecutor.cs | 14 +++++---- .../Routing/RoutingService.InvokeAgent.cs | 17 +++++----- .../Routing/RoutingService.InvokeFunction.cs | 8 +++-- .../BotSharp.Core/Routing/RoutingService.cs | 11 ++++--- .../Controllers/RealtimeController.cs | 2 +- .../Hooks/ChatHubConversationHook.cs | 5 +-- .../Hooks/StreamingLogHook.cs | 5 +-- .../Hooks/TwilioConversationHook.cs | 3 +- 15 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Routing/Models/InvokeOptions.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs index 0a5244f8..855bd79e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs @@ -49,10 +49,10 @@ public abstract class ConversationHookBase : IConversationHook public virtual Task OnHumanInterventionNeeded(RoleDialogModel message) => Task.CompletedTask; - public virtual Task OnFunctionExecuting(RoleDialogModel message, string from = InvokeSource.Manual) + public virtual Task OnFunctionExecuting(RoleDialogModel message, InvokeFunctionOptions? options = null) => Task.CompletedTask; - public virtual Task OnFunctionExecuted(RoleDialogModel message, string from = InvokeSource.Manual) + public virtual Task OnFunctionExecuted(RoleDialogModel message, InvokeFunctionOptions? options = null) => Task.CompletedTask; public virtual Task OnMessageReceived(RoleDialogModel message) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs index d1b6f83c..5a1d8aa2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs @@ -61,7 +61,7 @@ public interface IConversationHook : IHookBase /// /// /// - Task OnFunctionExecuting(RoleDialogModel message, string from = InvokeSource.Manual); + Task OnFunctionExecuting(RoleDialogModel message, InvokeFunctionOptions? options = null); /// /// Triggered when the function calling completed. @@ -69,7 +69,7 @@ public interface IConversationHook : IHookBase /// /// /// - Task OnFunctionExecuted(RoleDialogModel message, string from = InvokeSource.Manual); + Task OnFunctionExecuted(RoleDialogModel message, InvokeFunctionOptions? options = null); Task OnResponseGenerated(RoleDialogModel message); diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 56c98a82..d387fab1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -26,8 +26,8 @@ public interface IRoutingService /// RoutingRule[] GetRulesByAgentId(string id); - Task InvokeAgent(string agentId, List dialogs, string from = InvokeSource.Manual, bool useStream = false); - Task InvokeFunction(string name, RoleDialogModel messages, string from = InvokeSource.Manual); + Task InvokeAgent(string agentId, List dialogs, InvokeAgentOptions? options = null); + Task InvokeFunction(string name, RoleDialogModel messages, InvokeFunctionOptions? options = null); Task InstructLoop(Agent agent, RoleDialogModel message, List dialogs); /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/InvokeOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/InvokeOptions.cs new file mode 100644 index 00000000..5e031787 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/InvokeOptions.cs @@ -0,0 +1,31 @@ +namespace BotSharp.Abstraction.Routing.Models; + +public abstract class InvokeOptions +{ + public string From { get; set; } +} + +public class InvokeAgentOptions : InvokeOptions +{ + public bool UseStream { get; set; } + + public static InvokeAgentOptions Default() + { + return new() + { + From = InvokeSource.Manual, + UseStream = false + }; + } +} + +public class InvokeFunctionOptions : InvokeOptions +{ + public static InvokeFunctionOptions Default() + { + return new() + { + From = InvokeSource.Manual + }; + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs b/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs index 095ff2b8..07b8646c 100644 --- a/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs +++ b/src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs @@ -11,7 +11,7 @@ public class RealtimeConversationHook : ConversationHookBase, IConversationHook _services = services; } - public async Task OnFunctionExecuting(RoleDialogModel message, string from = InvokeSource.Manual) + public async Task OnFunctionExecuting(RoleDialogModel message, InvokeFunctionOptions? options = null) { var hub = _services.GetRequiredService(); if (hub.HubConn == null) @@ -32,10 +32,10 @@ public class RealtimeConversationHook : ConversationHookBase, IConversationHook } } - public async Task OnFunctionExecuted(RoleDialogModel message, string from = InvokeSource.Manual) + public async Task OnFunctionExecuted(RoleDialogModel message, InvokeFunctionOptions? options = null) { var hub = _services.GetRequiredService(); - if (from != InvokeSource.Llm || hub.HubConn == null) + if (options?.From != InvokeSource.Llm || hub.HubConn == null) { return; } diff --git a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs index fe544b4d..0ff601e1 100644 --- a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs +++ b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs @@ -99,7 +99,7 @@ public class RealtimeHub : IRealtimeHub agent.Id); } - await routing.InvokeFunction(message.FunctionName, message, from: InvokeSource.Llm); + await routing.InvokeFunction(message.FunctionName, message, options: new() { From = InvokeSource.Llm }); } else { diff --git a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs index b1b4541e..80757219 100644 --- a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs +++ b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Evaluations; +using BotSharp.Abstraction.Routing.Models; namespace BotSharp.Core.Evaluations; @@ -22,13 +23,13 @@ public class EvaluationConversationHook : ConversationHookBase return base.OnMessageReceived(message); } - public override Task OnFunctionExecuted(RoleDialogModel message, string from = InvokeSource.Manual) + public override Task OnFunctionExecuted(RoleDialogModel message, InvokeFunctionOptions? options = null) { if (Conversation != null && _convSettings.EnableExecutionLog) { _logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.FunctionName}({message.FunctionArgs}) => {message.Content}"); } - return base.OnFunctionExecuted(message, from: from); + return base.OnFunctionExecuted(message, options); } public override Task OnResponseGenerated(RoleDialogModel message) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs index e0ac69a2..9dd19c09 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Infrastructures.Enums; using BotSharp.Abstraction.Planning; +using BotSharp.Abstraction.Routing.Models; namespace BotSharp.Core.Routing.Reasoning; @@ -33,7 +34,7 @@ public class InstructExecutor : IExecutor if (message.FunctionName != null) { var msg = RoleDialogModel.From(message, role: AgentRole.Function); - await routing.InvokeFunction(message.FunctionName, msg, from: InvokeSource.Llm); + await routing.InvokeFunction(message.FunctionName, msg, options: new() { From = InvokeSource.Routing }); } var agentId = routing.Context.GetCurrentAgentId(); @@ -59,11 +60,12 @@ public class InstructExecutor : IExecutor { var state = _services.GetRequiredService(); var useStreamMsg = state.GetState("use_stream_message"); - var ret = await routing.InvokeAgent( - agentId, - dialogs, - from: InvokeSource.Routing, - useStream: bool.TryParse(useStreamMsg, out var useStream) && useStream); + var options = new InvokeAgentOptions() + { + From = InvokeSource.Routing, + UseStream = bool.TryParse(useStreamMsg, out var useStream) && useStream + }; + var ret = await routing.InvokeAgent(agentId, dialogs, options); } var response = dialogs.Last(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs index 6d2813d9..6acb58b2 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Templating; namespace BotSharp.Core.Routing; @@ -7,9 +8,9 @@ public partial class RoutingService public async Task InvokeAgent( string agentId, List dialogs, - string from = InvokeSource.Manual, - bool useStream = false) + InvokeAgentOptions? options = null) { + options ??= InvokeAgentOptions.Default(); var agentService = _services.GetRequiredService(); var agent = await agentService.LoadAgent(agentId); @@ -36,7 +37,7 @@ public partial class RoutingService RoleDialogModel response; var message = dialogs.Last(); - if (useStream) + if (options?.UseStream == true) { response = await chatCompletion.GetChatCompletionsStreamingAsync(agent, dialogs); } @@ -59,7 +60,7 @@ public partial class RoutingService message.CurrentAgentId = agent.Id; message.IsStreaming = response.IsStreaming; - await InvokeFunction(message, dialogs, from: from, useStream: useStream); + await InvokeFunction(message, dialogs, options); } else { @@ -83,8 +84,7 @@ public partial class RoutingService private async Task InvokeFunction( RoleDialogModel message, List dialogs, - string from, - bool useStream) + InvokeAgentOptions? options = null) { // execute function // Save states @@ -93,7 +93,8 @@ public partial class RoutingService var routing = _services.GetRequiredService(); // Call functions - await routing.InvokeFunction(message.FunctionName, message, from: from); + var funcOptions = options != null ? new InvokeFunctionOptions() { From = options.From } : null; + await routing.InvokeFunction(message.FunctionName, message, options: funcOptions); // Pass execution result to LLM to get response if (!message.StopCompletion) @@ -120,7 +121,7 @@ public partial class RoutingService // Send to Next LLM var curAgentId = routing.Context.GetCurrentAgentId(); - await InvokeAgent(curAgentId, dialogs, from, useStream); + await InvokeAgent(curAgentId, dialogs, options); } } else diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 694ab2d3..dea0947d 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Routing.Models; using BotSharp.Core.MessageHub; using BotSharp.Core.Routing.Executor; @@ -5,8 +6,9 @@ namespace BotSharp.Core.Routing; public partial class RoutingService { - public async Task InvokeFunction(string name, RoleDialogModel message, string from = InvokeSource.Manual) + public async Task InvokeFunction(string name, RoleDialogModel message, InvokeFunctionOptions? options = null) { + options ??= InvokeFunctionOptions.Default(); var currentAgentId = message.CurrentAgentId; var agentService = _services.GetRequiredService(); var agent = await agentService.GetAgent(currentAgentId); @@ -38,7 +40,7 @@ public partial class RoutingService foreach (var hook in hooks) { hook.SetAgent(agent); - await hook.OnFunctionExecuting(clonedMessage, from: from); + await hook.OnFunctionExecuting(clonedMessage, options); } bool result = false; @@ -50,7 +52,7 @@ public partial class RoutingService // After functions have been executed foreach (var hook in hooks) { - await hook.OnFunctionExecuted(clonedMessage, from: from); + await hook.OnFunctionExecuted(clonedMessage, options); } // Set result to original message diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index c66ef4b2..4e43cbd5 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -53,11 +53,12 @@ public partial class RoutingService : IRoutingService { var state = _services.GetRequiredService(); var useStreamMsg = state.GetState("use_stream_message"); - var ret = await routing.InvokeAgent( - agentId, - dialogs, - from: InvokeSource.Routing, - useStream: bool.TryParse(useStreamMsg, out var useStream) && useStream); + var options = new InvokeAgentOptions() + { + From = InvokeSource.Routing, + UseStream = bool.TryParse(useStreamMsg, out var useStream) && useStream + }; + var ret = await routing.InvokeAgent(agentId, dialogs, options); } var response = dialogs.Last(); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RealtimeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RealtimeController.cs index 61ae77a7..ff3aca46 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RealtimeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RealtimeController.cs @@ -26,7 +26,7 @@ public class RealtimeController : ControllerBase FunctionName = functionName, FunctionArgs = JsonSerializer.Serialize(args) }; - await routing.InvokeFunction(functionName, message, from: InvokeSource.Llm); + await routing.InvokeFunction(functionName, message, options: new() { From = InvokeSource.Llm }); return message.Content; } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index bd590276..e07f94d4 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Conversations.Dtos; using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Routing.Enums; +using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.SideCar; using BotSharp.Abstraction.Users.Dtos; using Microsoft.AspNetCore.SignalR; @@ -78,9 +79,9 @@ public class ChatHubConversationHook : ConversationHookBase await base.OnMessageReceived(message); } - public override async Task OnFunctionExecuting(RoleDialogModel message, string from = InvokeSource.Manual) + public override async Task OnFunctionExecuting(RoleDialogModel message, InvokeFunctionOptions? options = null) { - await base.OnFunctionExecuting(message, from: from); + await base.OnFunctionExecuting(message, options); } public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 73365115..86adaf2c 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Routing.Enums; +using BotSharp.Abstraction.Routing.Models; using Microsoft.AspNetCore.SignalR; using System.Runtime.CompilerServices; using System.Text.Encodings.Web; @@ -141,7 +142,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (!_convSettings.ShowVerboseLog) return; } - public override async Task OnFunctionExecuting(RoleDialogModel message, string from = InvokeSource.Manual) + public override async Task OnFunctionExecuting(RoleDialogModel message, InvokeFunctionOptions? options = null) { var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; @@ -165,7 +166,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } - public override async Task OnFunctionExecuted(RoleDialogModel message, string from = InvokeSource.Manual) + public override async Task OnFunctionExecuted(RoleDialogModel message, InvokeFunctionOptions? options = null) { var conversationId = _state.GetConversationId(); if (string.IsNullOrEmpty(conversationId)) return; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs index 11bd8d36..bf856ee4 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Enums; +using BotSharp.Abstraction.Routing.Models; using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; using Twilio.Rest.Api.V2010.Account; @@ -23,7 +24,7 @@ public class TwilioConversationHook : ConversationHookBase, IConversationHook _logger = logger; } - public override async Task OnFunctionExecuted(RoleDialogModel message, string from = InvokeSource.Manual) + public override async Task OnFunctionExecuted(RoleDialogModel message, InvokeFunctionOptions? options = null) { var hooks = _services.GetHooks(message.CurrentAgentId); From 9c14caede95446ca0030d8a0e48528def8fb35ae Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 5 Aug 2025 11:09:15 -0500 Subject: [PATCH 22/51] refine --- .../SideCar/Attributes/SideCarAttribute.cs | 7 ++- .../SideCar/Models/SideCarOptions.cs | 8 +++- .../Services/BotSharpConversationSideCar.cs | 7 ++- .../FileRepository.Conversation.cs | 9 +--- .../FileRepository.KnowledgeBase.cs | 10 ++--- .../Services/KnowledgeService.Vector.cs | 45 +++++++++---------- 6 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs index 4426d71f..1d93dcba 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs @@ -23,7 +23,12 @@ public class SideCarAttribute : AsyncMoAttribute var instance = context.Target; var retType = context.ReturnType; - var serviceProvider = ((IHaveServiceProvider)instance).ServiceProvider; + var serviceProvider = (instance as IHaveServiceProvider)?.ServiceProvider; + if (serviceProvider == null) + { + return; + } + var (sidecar, sidecarMethod) = GetSideCarMethod(serviceProvider, methodName, retType, methodArgs); if (sidecar == null || sidecarMethod == null) { diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs index f221cab2..7fb734dd 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs @@ -4,18 +4,22 @@ public class SideCarOptions { public bool IsInheritStates { get; set; } public IEnumerable? InheritStateKeys { get; set; } + public IEnumerable? ExcludedStateKeys { get; set; } public static SideCarOptions Empty() { return new(); } - public static SideCarOptions InheritStates(IEnumerable? targetStates = null) + public static SideCarOptions InheritStates( + IEnumerable? includedStates = null, + IEnumerable? excludedStates = null) { return new() { IsInheritStates = true, - InheritStateKeys = targetStates + InheritStateKeys = includedStates, + ExcludedStateKeys = excludedStates }; } } diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index d636fa71..d598fdac 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -194,14 +194,17 @@ public class BotSharpConversationSideCar : IConversationSideCar if (_sideCarOptions?.IsInheritStates == true) { + var hasIncludedStates = _sideCarOptions?.InheritStateKeys?.Any() == true; + var hasExcludedStates = _sideCarOptions?.ExcludedStateKeys?.Any() == true; var curStates = state.GetCurrentState(); + foreach (var pair in curStates) { var endNode = pair.Value.Values.LastOrDefault(); if (endNode == null) continue; - if (_sideCarOptions?.InheritStateKeys?.Any() == true - && !_sideCarOptions.InheritStateKeys.Contains(pair.Key)) + if ((hasIncludedStates && !_sideCarOptions.InheritStateKeys.Contains(pair.Key)) + || (hasExcludedStates && _sideCarOptions.ExcludedStateKeys.Contains(pair.Key))) { continue; } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index f772acb2..81da4047 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -241,10 +241,9 @@ public partial class FileRepository if (!string.IsNullOrEmpty(convDir)) { var breakpointFile = Path.Combine(convDir, BREAKPOINT_FILE); - if (!File.Exists(breakpointFile)) { - File.Create(breakpointFile); + File.WriteAllText(breakpointFile, "[]"); } var content = File.ReadAllText(breakpointFile); @@ -285,7 +284,7 @@ public partial class FileRepository var breakpointFile = Path.Combine(convDir, BREAKPOINT_FILE); if (!File.Exists(breakpointFile)) { - File.Create(breakpointFile); + File.WriteAllText(breakpointFile, "[]"); } var content = File.ReadAllText(breakpointFile); @@ -920,7 +919,6 @@ public partial class FileRepository private bool SaveTruncatedDialogs(string dialogDir, List dialogs) { if (string.IsNullOrEmpty(dialogDir) || dialogs == null) return false; - if (!File.Exists(dialogDir)) File.Create(dialogDir); var texts = ParseDialogElements(dialogs); File.WriteAllText(dialogDir, texts); @@ -930,7 +928,6 @@ public partial class FileRepository private bool SaveTruncatedStates(string stateDir, List states) { if (string.IsNullOrEmpty(stateDir) || states == null) return false; - if (!File.Exists(stateDir)) File.Create(stateDir); var stateStr = JsonSerializer.Serialize(states, _options); File.WriteAllText(stateDir, stateStr); @@ -940,7 +937,6 @@ public partial class FileRepository private bool SaveTruncatedLatestStates(string latestStateDir, List states) { if (string.IsNullOrEmpty(latestStateDir) || states == null) return false; - if (!File.Exists(latestStateDir)) File.Create(latestStateDir); var latestStates = BuildLatestStates(states); var stateStr = JsonSerializer.Serialize(latestStates, _options); @@ -951,7 +947,6 @@ public partial class FileRepository private bool SaveTruncatedBreakpoints(string breakpointDir, List breakpoints) { if (string.IsNullOrEmpty(breakpointDir) || breakpoints == null) return false; - if (!File.Exists(breakpointDir)) File.Create(breakpointDir); var breakpointStr = JsonSerializer.Serialize(breakpoints, _options); File.WriteAllText(breakpointDir, breakpointStr); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs index b0a95027..b7fa26ab 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs @@ -15,17 +15,17 @@ public partial class FileRepository } var configFile = Path.Combine(vectorDir, COLLECTION_CONFIG_FILE); + if (!File.Exists(configFile)) + { + File.WriteAllText(configFile, "[]"); + } + if (reset) { File.WriteAllText(configFile, JsonSerializer.Serialize(configs ?? new(), _options)); return true; } - if (!File.Exists(configFile)) - { - File.Create(configFile); - } - var str = File.ReadAllText(configFile); var savedConfigs = JsonSerializer.Deserialize>(str, _options) ?? new(); diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs index c230078d..ce5fa43c 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs @@ -33,31 +33,30 @@ public partial class KnowledgeService return false; } - var vectorDb = GetVectorDb(); - var created = await vectorDb.CreateCollection(collectionName, dimension); + var db = _services.GetRequiredService(); + var created = db.AddKnowledgeCollectionConfigs(new List + { + new VectorCollectionConfig + { + Name = collectionName, + Type = collectionType, + VectorStore = new VectorStoreConfig + { + Provider = _settings.VectorDb.Provider + }, + TextEmbedding = new KnowledgeEmbeddingConfig + { + Provider = provider, + Model = model, + Dimension = dimension + } + } + }); + if (created) { - var db = _services.GetRequiredService(); - var userId = await GetUserId(); - - db.AddKnowledgeCollectionConfigs(new List - { - new VectorCollectionConfig - { - Name = collectionName, - Type = collectionType, - VectorStore = new VectorStoreConfig - { - Provider = _settings.VectorDb.Provider - }, - TextEmbedding = new KnowledgeEmbeddingConfig - { - Provider = provider, - Model = model, - Dimension = dimension - } - } - }); + var vectorDb = GetVectorDb(); + created = await vectorDb.CreateCollection(collectionName, dimension); } return created; From 3dee1406384d13fdce352be03c8fada31aacc3ba Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 5 Aug 2025 14:15:23 -0500 Subject: [PATCH 23/51] refine side car accumulate llm stats --- .../SideCar/Models/SideCarOptions.cs | 8 +-- .../Services/BotSharpConversationSideCar.cs | 54 ++++++++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs index 7fb734dd..bc6609c0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs @@ -3,8 +3,8 @@ namespace BotSharp.Abstraction.SideCar.Models; public class SideCarOptions { public bool IsInheritStates { get; set; } - public IEnumerable? InheritStateKeys { get; set; } - public IEnumerable? ExcludedStateKeys { get; set; } + public HashSet? InheritStateKeys { get; set; } + public HashSet? ExcludedStateKeys { get; set; } public static SideCarOptions Empty() { @@ -12,8 +12,8 @@ public class SideCarOptions } public static SideCarOptions InheritStates( - IEnumerable? includedStates = null, - IEnumerable? excludedStates = null) + HashSet? includedStates = null, + HashSet? excludedStates = null) { return new() { diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index d598fdac..3def01ef 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -15,6 +15,9 @@ ******************************************************************************/ using BotSharp.Core.Infrastructures; +using NetTopologySuite.Index.KdTree; +using Newtonsoft.Json.Linq; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace BotSharp.Core.SideCar.Services; @@ -189,7 +192,9 @@ public class BotSharpConversationSideCar : IConversationSideCar private void RestoreStates(ConversationState prevStates) { - var innerStates = prevStates; + var preValues = prevStates.Values.ToList(); + var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(preValues)); + var innerStates = new ConversationState(copy ?? []); var state = _services.GetRequiredService(); if (_sideCarOptions?.IsInheritStates == true) @@ -209,7 +214,7 @@ public class BotSharpConversationSideCar : IConversationSideCar continue; } - if (innerStates.ContainsKey(pair.Key)) + if (innerStates.ContainsKey(pair.Key) && innerStates[pair.Key].Versioning) { innerStates[pair.Key].Values.Add(endNode); } @@ -226,6 +231,51 @@ public class BotSharpConversationSideCar : IConversationSideCar } } + AccumulateLlmStats(state, prevStates, innerStates); state.SetCurrentState(innerStates); } + + private void AccumulateLlmStats(IConversationStateService state, ConversationState prevState, ConversationState curState) + { + var dict = new Dictionary + { + { "prompt_total", typeof(int) }, + { "completion_total", typeof(int) }, + { "llm_total_cost", typeof(float) } + }; + + foreach (var pair in dict) + { + var preVal = prevState.GetValueOrDefault(pair.Key)?.Values?.LastOrDefault()?.Data; + var curVal = state.GetState(pair.Key); + + object data = pair.Value switch + { + Type t when t == typeof(int) => ParseNumber(preVal) + ParseNumber(curVal), + Type t when t == typeof(float) => ParseNumber(preVal) + ParseNumber(curVal), + _ => default + }; + + var cur = curState.GetValueOrDefault(pair.Key); + if (cur?.Values?.LastOrDefault() != null) + { + cur.Values.Last().Data = $"{data}"; + } + } + } + + private T ParseNumber(string? data) where T : struct + { + if (string.IsNullOrEmpty(data)) + { + return default; + } + + return typeof(T) switch + { + Type t when t == typeof(int) => (T)(object)(int.TryParse(data, out var i) ? i : 0), + Type t when t == typeof(float) => (T)(object)(float.TryParse(data, out var f) ? f : 0), + _ => default + }; + } } \ No newline at end of file From de3b36368c92abdb184b83922255bf11c6cbf769 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 5 Aug 2025 15:46:07 -0500 Subject: [PATCH 24/51] temp save --- .../VectorStorage/IVectorDb.cs | 3 +- .../VectorStorage/Models/VectorFilter.cs | 4 +- .../Models/VectorSearchOptions.cs | 15 +++++- .../Controllers/KnowledgeBaseController.cs | 13 +++++ .../Request/SearchVectorKnowledgeRequest.cs | 3 ++ .../MemVecDb/MemoryVectorDb.cs | 9 ++-- .../Services/KnowledgeService.Vector.cs | 2 +- .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 54 +++++++++++++++---- .../SemanticKernelMemoryStoreProvider.cs | 8 +-- 9 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs index cb830bcc..39d80360 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs @@ -23,8 +23,7 @@ public interface IVectorDb => throw new NotImplementedException(); Task Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary? payload = null) => throw new NotImplementedException(); - Task> Search(string collectionName, float[] vector, IEnumerable? fields, - int limit = 5, float confidence = 0.5f, bool withVector = false) + Task> Search(string collectionName, float[] vector, VectorSearchOptions? options = null) => throw new NotImplementedException(); Task DeleteCollectionData(string collectionName, List ids) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs index 584e8653..e4d4f018 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs @@ -8,8 +8,8 @@ public class VectorFilter : StringIdPagination /// /// For keyword search /// - [JsonPropertyName("search_pairs")] - public IEnumerable? SearchPairs { get; set; } + [JsonPropertyName("filters")] + public IEnumerable? Filters { get; set; } /// diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs index 64943dd1..33e9b05e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs @@ -4,8 +4,21 @@ namespace BotSharp.Abstraction.VectorStorage.Models; public class VectorSearchOptions { - public IEnumerable? Fields { get; set; } = new List { KnowledgePayloadName.Text, KnowledgePayloadName.Answer }; + public IEnumerable? Fields { get; set; } = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer]; + public IEnumerable? Filters { get; set; } public int? Limit { get; set; } = 5; public float? Confidence { get; set; } = 0.5f; public bool WithVector { get; set; } + + public static VectorSearchOptions Default() + { + return new() + { + Fields = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer], + Filters = null, + Limit = 5, + Confidence = 0.5f, + WithVector = false + }; + } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs index 0f7b440c..07994e85 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs @@ -59,6 +59,7 @@ public class KnowledgeBaseController : ControllerBase var options = new VectorSearchOptions { Fields = request.Fields, + Filters = request.Filters, Limit = request.Limit ?? 5, Confidence = request.Confidence ?? 0.5f, WithVector = request.WithVector @@ -123,6 +124,18 @@ public class KnowledgeBaseController : ControllerBase { return await _knowledgeService.DeleteVectorCollectionAllData(collection); } + + [HttpPost("/knowledge/vector/{collection}/payload/index")] + public async Task CreateCollectionPayloadIndex() + { + return false; + } + + [HttpDelete("/knowledge/vector/{collection}/payload/index")] + public async Task DeleteCollectionPayloadIndex() + { + return false; + } #endregion diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs index 3f11a486..ccf13050 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs @@ -10,6 +10,9 @@ public class SearchVectorKnowledgeRequest [JsonPropertyName("fields")] public IEnumerable? Fields { get; set; } + [JsonPropertyName("filters")] + public IEnumerable? Filters { get; set; } + [JsonPropertyName("limit")] public int? Limit { get; set; } = 5; diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs index f766bb7e..a2327298 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs @@ -1,3 +1,4 @@ +using System.Linq; using Tensorflow.NumPy; namespace BotSharp.Plugin.KnowledgeBase.MemVecDb; @@ -44,24 +45,24 @@ public class MemoryVectorDb : IVectorDb throw new NotImplementedException(); } - public async Task> Search(string collectionName, float[] vector, - IEnumerable? fields, int limit = 5, float confidence = 0.5f, bool withVector = false) + public async Task> Search(string collectionName, float[] vector, VectorSearchOptions? options = null) { if (!_vectors.ContainsKey(collectionName)) { return new List(); } + options ??= VectorSearchOptions.Default(); var similarities = VectorHelper.CalCosineSimilarity(vector, _vectors[collectionName]); var results = np.argsort(similarities).ToArray() .Reverse() - .Take(limit) + .Take(options.Limit.GetValueOrDefault()) .Select(i => new VectorCollectionData { Data = new Dictionary { { "text", _vectors[collectionName][i].Text } }, Score = similarities[i], - Vector = withVector ? _vectors[collectionName][i].Vector : null, + Vector = options.WithVector ? _vectors[collectionName][i].Vector : null, }) .ToList(); diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs index ce5fa43c..102300f2 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs @@ -308,7 +308,7 @@ public partial class KnowledgeService // Vector search var db = GetVectorDb(); - var found = await db.Search(collectionName, vector, options.Fields, limit: options.Limit ?? 5, confidence: options.Confidence ?? 0.5f, withVector: options.WithVector); + var found = await db.Search(collectionName, vector, options); var results = found.Select(x => VectorSearchResult.CopyFrom(x)).ToList(); return results; diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 5c91301d..7d13620b 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Qdrant.Client; using Qdrant.Client.Grpc; +using System.Collections; using System.Net.Http; using System.Net.Mime; using System.Text.Json; @@ -142,9 +143,9 @@ public class QdrantDb : IVectorDb // Build query filter Filter? queryFilter = null; - if (!filter.SearchPairs.IsNullOrEmpty()) + if (!filter.Filters.IsNullOrEmpty()) { - var conditions = filter.SearchPairs.Select(x => new Condition + var conditions = filter.Filters.Select(x => new Condition { Field = new FieldCondition { @@ -308,8 +309,7 @@ public class QdrantDb : IVectorDb return result.Status == UpdateStatus.Completed; } - public async Task> Search(string collectionName, float[] vector, - IEnumerable? fields, int limit = 5, float confidence = 0.5f, bool withVector = false) + public async Task> Search(string collectionName, float[] vector, VectorSearchOptions? options = null) { var results = new List(); @@ -319,19 +319,42 @@ public class QdrantDb : IVectorDb return results; } + options ??= VectorSearchOptions.Default(); var payloadSelector = new WithPayloadSelector { Enable = true }; - if (fields != null) + if (!options.Fields.IsNullOrEmpty()) { - payloadSelector.Include = new PayloadIncludeSelector { Fields = { fields.ToArray() } }; + payloadSelector.Include = new PayloadIncludeSelector { Fields = { options.Fields.ToArray() } }; + } + + Filter? queryFilter = null; + if (!options.Filters.IsNullOrEmpty()) + { + var conditions = options.Filters.Select(x => new Condition + { + Field = new FieldCondition + { + Key = x.Key, + Match = new Match { Text = x.Value }, + } + }); + + queryFilter = new Filter + { + Should = + { + conditions + } + }; } var client = GetClient(); var points = await client.SearchAsync(collectionName, vector, - limit: (ulong)limit, - scoreThreshold: confidence, + limit: (ulong)options.Limit.GetValueOrDefault(), + scoreThreshold: options.Confidence, + filter: queryFilter, payloadSelector: payloadSelector, - vectorsSelector: withVector); + vectorsSelector: options.WithVector); results = points.Select(x => new VectorCollectionData { @@ -377,6 +400,19 @@ public class QdrantDb : IVectorDb var result = await client.DeleteAsync(collectionName, new Filter()); return result.Status == UpdateStatus.Completed; } + + + //public async Task CreateCollectionPayloadIndex(string collectionName) + //{ + // var exist = await DoesCollectionExist(collectionName); + // if (!exist) + // { + // return false; + // } + + // var client = GetClient(); + // var result = await client.CreatePayloadIndexAsync(collectionName, "text", PayloadSchemaType.Keyword); + //} #endregion #region Snapshots diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs index 194d1a58..2189e8a2 100644 --- a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs @@ -64,10 +64,10 @@ namespace BotSharp.Plugin.SemanticKernel return result; } - public async Task> Search(string collectionName, float[] vector, - IEnumerable? fields, int limit = 5, float confidence = 0.5f, bool withVector = false) + public async Task> Search(string collectionName, float[] vector, VectorSearchOptions? options = null) { - var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, limit); + options ??= VectorSearchOptions.Default(); + var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, options.Limit.GetValueOrDefault()); var resultTexts = new List(); await foreach (var (record, score) in results) @@ -76,7 +76,7 @@ namespace BotSharp.Plugin.SemanticKernel { Data = new Dictionary { { "text", record.Metadata.Text } }, Score = score, - Vector = withVector ? record.Embedding.ToArray() : null + Vector = options.WithVector ? record.Embedding.ToArray() : null }); } From 7e40dd9262de99dc99c790bd35c9ed6836360af8 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 10:14:22 -0500 Subject: [PATCH 25/51] add payload index --- .../Knowledges/IKnowledgeService.cs | 6 ++ .../Models/SuccessFailResponse.cs | 7 ++ .../VectorStorage/IVectorDb.cs | 5 ++ .../Models/VectorCollectionIndexOptions.cs | 18 +++++ .../Controllers/KnowledgeBaseController.cs | 13 ++-- .../Request/VectorCollectionIndexRequest.cs | 13 ++++ .../Services/KnowledgeService.Index.cs | 74 ++++++++++++++++++ .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 75 ++++++++++++++++--- 8 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Models/SuccessFailResponse.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorCollectionIndexOptions.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorCollectionIndexRequest.cs create mode 100644 src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs index 688e939e..52657646 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Graph.Models; +using BotSharp.Abstraction.Models; using BotSharp.Abstraction.VectorStorage.Models; namespace BotSharp.Abstraction.Knowledges; @@ -69,6 +70,11 @@ public interface IKnowledgeService Task DeleteVectorCollectionSnapshot(string collectionName, string snapshotName); #endregion + #region Index + Task> CreateVectorCollectionPayloadIndexes(string collectionName, IEnumerable options); + Task> DeleteVectorCollectionPayloadIndexes(string collectionName, IEnumerable options); + #endregion + #region Common Task RefreshVectorKnowledgeConfigs(VectorCollectionConfigsModel configs); #endregion diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/SuccessFailResponse.cs b/src/Infrastructure/BotSharp.Abstraction/Models/SuccessFailResponse.cs new file mode 100644 index 00000000..11985259 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Models/SuccessFailResponse.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Models; + +public class SuccessFailResponse +{ + public List Success { get; set; } = []; + public List Fail { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs index 39d80360..b078abb5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs @@ -39,4 +39,9 @@ public interface IVectorDb => throw new NotImplementedException(); Task DeleteCollectionShapshot(string collectionName, string snapshotName) => throw new NotImplementedException(); + + Task CreateCollectionPayloadIndex(string collectionName, CreateVectorCollectionIndexOptions options) + => throw new NotImplementedException(); + Task DeleteCollectionPayloadIndex(string collectionName, DeleteVectorCollectionIndexOptions options) + => throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorCollectionIndexOptions.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorCollectionIndexOptions.cs new file mode 100644 index 00000000..809bbf6b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorCollectionIndexOptions.cs @@ -0,0 +1,18 @@ +namespace BotSharp.Abstraction.VectorStorage.Models; + +public class VectorCollectionIndexOptions +{ + [JsonPropertyName("field_name")] + public string FieldName { get; set; } = null!; +} + +public class CreateVectorCollectionIndexOptions : VectorCollectionIndexOptions +{ + [JsonPropertyName("field_schema_type")] + public string FieldSchemaType { get; set; } = null!; +} + +public class DeleteVectorCollectionIndexOptions : VectorCollectionIndexOptions +{ + +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs index 07994e85..9752e02c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs @@ -2,6 +2,7 @@ using BotSharp.Abstraction.Files.Utilities; using BotSharp.Abstraction.Graph.Models; using BotSharp.Abstraction.VectorStorage.Models; using BotSharp.OpenAPI.ViewModels.Knowledges; +using BotSharp.OpenAPI.ViewModels.Knowledges.Request; namespace BotSharp.OpenAPI.Controllers; @@ -125,16 +126,16 @@ public class KnowledgeBaseController : ControllerBase return await _knowledgeService.DeleteVectorCollectionAllData(collection); } - [HttpPost("/knowledge/vector/{collection}/payload/index")] - public async Task CreateCollectionPayloadIndex() + [HttpPost("/knowledge/vector/{collection}/payload/indexes")] + public async Task> CreateCollectionPayloadIndexes([FromRoute] string collection, [FromBody] CreateVectorCollectionIndexRequest request) { - return false; + return await _knowledgeService.CreateVectorCollectionPayloadIndexes(collection, request.Options); } - [HttpDelete("/knowledge/vector/{collection}/payload/index")] - public async Task DeleteCollectionPayloadIndex() + [HttpDelete("/knowledge/vector/{collection}/payload/indexes")] + public async Task> DeleteCollectionPayloadIndexes([FromRoute] string collection, [FromBody] DeleteVectorCollectionIndexRequest request) { - return false; + return await _knowledgeService.DeleteVectorCollectionPayloadIndexes(collection, request.Options); } #endregion diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorCollectionIndexRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorCollectionIndexRequest.cs new file mode 100644 index 00000000..fada7032 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorCollectionIndexRequest.cs @@ -0,0 +1,13 @@ +using BotSharp.Abstraction.VectorStorage.Models; + +namespace BotSharp.OpenAPI.ViewModels.Knowledges.Request; + +public class CreateVectorCollectionIndexRequest +{ + public IEnumerable Options { get; set; } = []; +} + +public class DeleteVectorCollectionIndexRequest +{ + public IEnumerable Options { get; set; } = []; +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs new file mode 100644 index 00000000..96cd3f25 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs @@ -0,0 +1,74 @@ +using BotSharp.Abstraction.Models; + +namespace BotSharp.Plugin.KnowledgeBase.Services; + +public partial class KnowledgeService +{ + public async Task> CreateVectorCollectionPayloadIndexes(string collectionName, IEnumerable options) + { + try + { + if (string.IsNullOrWhiteSpace(collectionName) || options.IsNullOrEmpty()) + { + return new(); + } + + var response = new SuccessFailResponse(); + var vectorDb = GetVectorDb(); + foreach (var option in options) + { + var created = await vectorDb.CreateCollectionPayloadIndex(collectionName, option); + var field = $"{option.FieldName}-{option.FieldSchemaType}"; + if (created) + { + response.Success.Add(field); + } + else + { + _logger.LogError($"Failed to create vector collection payload index ({collectionName}-{field})."); + response.Fail.Add(field); + } + } + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when creating vector collection payload index ({collectionName})."); + return new(); + } + } + + public async Task> DeleteVectorCollectionPayloadIndexes(string collectionName, IEnumerable options) + { + try + { + if (string.IsNullOrWhiteSpace(collectionName) || options.IsNullOrEmpty()) + { + return new(); + } + + var response = new SuccessFailResponse(); + var vectorDb = GetVectorDb(); + foreach (var option in options) + { + var deleted = await vectorDb.DeleteCollectionPayloadIndex(collectionName, option); + var field = $"{option.FieldName}"; + if (deleted) + { + response.Success.Add(field); + } + else + { + _logger.LogError($"Failed to deleting vector collection payload index ({collectionName}-{field})."); + response.Fail.Add(field); + } + } + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when deleting vector collection payload index ({collectionName})."); + return new(); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 7d13620b..40cf6982 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -402,17 +402,32 @@ public class QdrantDb : IVectorDb } - //public async Task CreateCollectionPayloadIndex(string collectionName) - //{ - // var exist = await DoesCollectionExist(collectionName); - // if (!exist) - // { - // return false; - // } + public async Task CreateCollectionPayloadIndex(string collectionName, CreateVectorCollectionIndexOptions options) + { + var exist = await DoesCollectionExist(collectionName); + if (!exist) + { + return false; + } - // var client = GetClient(); - // var result = await client.CreatePayloadIndexAsync(collectionName, "text", PayloadSchemaType.Keyword); - //} + var client = GetClient(); + var schemaType = ConvertPayloadSchemaType(options.FieldSchemaType); + var result = await client.CreatePayloadIndexAsync(collectionName, options.FieldName, schemaType); + return result.Status == UpdateStatus.Completed; + } + + public async Task DeleteCollectionPayloadIndex(string collectionName, DeleteVectorCollectionIndexOptions options) + { + var exist = await DoesCollectionExist(collectionName); + if (!exist) + { + return false; + } + + var client = GetClient(); + var result = await client.DeletePayloadIndexAsync(collectionName, options.FieldName); + return result.Status == UpdateStatus.Completed; + } #endregion #region Snapshots @@ -559,4 +574,44 @@ public class QdrantDb : IVectorDb } } #endregion + + + #region Private methods + private PayloadSchemaType ConvertPayloadSchemaType(string schemaType) + { + PayloadSchemaType res; + switch (schemaType.ToLower()) + { + case "text": + res = PayloadSchemaType.Text; + break; + case "keyword": + res = PayloadSchemaType.Keyword; + break; + case "integer": + res = PayloadSchemaType.Integer; + break; + case "float": + res = PayloadSchemaType.Float; + break; + case "bool": + res = PayloadSchemaType.Bool; + break; + case "geo": + res = PayloadSchemaType.Geo; + break; + case "datetime": + res = PayloadSchemaType.Datetime; + break; + case "uuid": + res = PayloadSchemaType.Uuid; + break; + default: + res = PayloadSchemaType.UnknownType; + break; + } + + return res; + } + #endregion } From 4f427661f22b38187b1a5d6b1280811fc7cabcd4 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 11:27:26 -0500 Subject: [PATCH 26/51] sending json data --- .../Plugins/PluginLoaderSettings.cs | 2 -- .../BotSharp.Core/BotSharpCoreExtensions.cs | 4 +--- .../Hooks/ChatHubConversationHook.cs | 4 ++-- .../Hooks/ChatHubCrontabHook.cs | 3 ++- .../BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs | 10 ++-------- .../BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs | 3 ++- .../Observers/ChatHubObserver.cs | 8 ++++++-- src/WebStarter/appsettings.json | 3 --- 8 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs index 1a08fc5b..70564846 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs @@ -3,6 +3,4 @@ namespace BotSharp.Abstraction.Plugins; public class PluginSettings { public string[] Assemblies { get; set; } = new string[0]; - - public string[] ExcludedFunctions { get; set; } = new string[0]; } diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 5c00de0d..bfae45ba 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -125,7 +125,6 @@ public static class BotSharpCoreExtensions { var pluginSettings = new PluginSettings(); config.Bind("PluginLoader", pluginSettings); - var excludedFunctions = pluginSettings.ExcludedFunctions ?? []; services.AddScoped(provider => { @@ -143,8 +142,7 @@ public static class BotSharpCoreExtensions // Register function callback var functions = assembly.GetTypes() .Where(x => x.IsClass - && x.GetInterface(nameof(IFunctionCallback)) != null - && !excludedFunctions.Contains(x.Name)) + && x.GetInterface(nameof(IFunctionCallback)) != null) .ToArray(); foreach (var function in functions) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index e07f94d4..3831ecd7 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -45,7 +45,6 @@ public class ChatHubConversationHook : ConversationHookBase var user = await userService.GetUser(conv.User.Id); conv.User = UserDto.FromUser(user); - //await InitClientConversation(conv.Id, conv); await SendEvent(ChatEvent.OnConversationInitFromClient, conv.Id, conv); await base.OnConversationInitialized(conversation); } @@ -172,7 +171,8 @@ public class ChatHubConversationHook : ConversationHookBase private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { var user = _services.GetRequiredService(); - await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(ChatHubConversationHook), callerName); + var json = JsonSerializer.Serialize(data, _options.JsonSerializerOptions); + await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, json, nameof(ChatHubConversationHook), callerName); } #endregion } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs index f965d28e..dd584f17 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs @@ -54,7 +54,8 @@ public class ChatHubCrontabHook : ICrontabHook { try { - await _chatHub.Clients.User(item.UserId).SendAsync(ChatEvent.OnNotificationGenerated, data); + 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.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 86adaf2c..7156f29f 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -61,7 +61,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.UserInput, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -80,7 +79,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.UserInput, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -110,7 +108,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.Prompt, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -133,7 +130,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -162,7 +158,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -184,7 +179,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -212,7 +206,6 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.Prompt, Log = log }; - //await SendContentLog(conversationId, input); await SendEvent(ChatEvent.OnConversationContentLogGenerated, conversationId, BuildContentLog(input)); } @@ -477,7 +470,8 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { var user = _services.GetRequiredService(); - await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(StreamingLogHook), callerName); + var json = JsonSerializer.Serialize(data, _options.JsonSerializerOptions); + await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, json, nameof(StreamingLogHook), callerName); } private ContentLogOutputModel BuildContentLog(ContentLogInputModel input) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs index 8a184568..3fbd852e 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs @@ -89,6 +89,7 @@ public class WelcomeHook : ConversationHookBase private async Task SendEvent(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { var user = _services.GetRequiredService(); - await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(WelcomeHook), callerName); + var json = JsonSerializer.Serialize(data, _options.JsonSerializerOptions); + await EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, json, nameof(WelcomeHook), callerName); } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs index 9b5a97e5..03a56d47 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs @@ -9,14 +9,17 @@ namespace BotSharp.Plugin.ChatHub.Observers; public class ChatHubObserver : BotSharpObserverBase> { - private readonly ILogger _logger; private readonly IServiceProvider _services; + private readonly BotSharpOptions _options; + private readonly ILogger _logger; public ChatHubObserver( IServiceProvider services, + BotSharpOptions options, ILogger logger) : base() { _services = services; + _options = options; _logger = logger; } @@ -133,7 +136,8 @@ public class ChatHubObserver : BotSharpObserverBase(string @event, string conversationId, T data, [CallerMemberName] string callerName = "") { var user = _services.GetRequiredService(); - EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(ChatHubObserver), callerName) + var json = JsonSerializer.Serialize(data, _options.JsonSerializerOptions); + EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, json, nameof(ChatHubObserver), callerName) .ConfigureAwait(false).GetAwaiter().GetResult(); } #endregion diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 4e7a4263..1e0365ee 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -541,9 +541,6 @@ "BotSharp.Plugin.AudioHandler", "BotSharp.Plugin.ChartHandler", "BotSharp.Plugin.TencentCos" - ], - "ExcludedFunctions": [ - "McpToolAdapter" ] } } From d36c4c588c7ccfef32e7b9dd7b9fa41fd98c1976 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 13:52:24 -0500 Subject: [PATCH 27/51] temp save --- .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 137 ++++++++++-------- 1 file changed, 75 insertions(+), 62 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 40cf6982..4acad653 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Utilities; using BotSharp.Abstraction.VectorStorage.Models; @@ -141,41 +142,8 @@ public class QdrantDb : IVectorDb return new StringIdPagedItems(); } - // Build query filter - Filter? queryFilter = null; - if (!filter.Filters.IsNullOrEmpty()) - { - var conditions = filter.Filters.Select(x => new Condition - { - Field = new FieldCondition - { - Key = x.Key, - Match = new Match { Text = x.Value }, - } - }); - - queryFilter = new Filter - { - Should = - { - conditions - } - }; - } - - // Build payload selector - WithPayloadSelector? payloadSelector = null; - if (!filter.IncludedPayloads.IsNullOrEmpty()) - { - payloadSelector = new WithPayloadSelector - { - Enable = true, - Include = new PayloadIncludeSelector - { - Fields = { filter.IncludedPayloads.ToArray() } - } - }; - } + Filter? queryFilter = BuildQueryFilter(filter.Filters); + WithPayloadSelector? payloadSelector = BuildPayloadSelector(filter.IncludedPayloads); var client = GetClient(); var totalPointCount = await client.CountAsync(collectionName, filter: queryFilter); @@ -232,6 +200,7 @@ public class QdrantDb : IVectorDb Value.KindOneofCase.StringValue => p.Value.StringValue, Value.KindOneofCase.BoolValue => p.Value.BoolValue, Value.KindOneofCase.IntegerValue => p.Value.IntegerValue, + Value.KindOneofCase.DoubleValue => p.Value.DoubleValue, _ => new object() }) ?? new(), Vector = x.Vectors?.Vector?.Data?.ToArray() @@ -320,32 +289,8 @@ public class QdrantDb : IVectorDb } options ??= VectorSearchOptions.Default(); - var payloadSelector = new WithPayloadSelector { Enable = true }; - if (!options.Fields.IsNullOrEmpty()) - { - payloadSelector.Include = new PayloadIncludeSelector { Fields = { options.Fields.ToArray() } }; - } - - Filter? queryFilter = null; - if (!options.Filters.IsNullOrEmpty()) - { - var conditions = options.Filters.Select(x => new Condition - { - Field = new FieldCondition - { - Key = x.Key, - Match = new Match { Text = x.Value }, - } - }); - - queryFilter = new Filter - { - Should = - { - conditions - } - }; - } + Filter? queryFilter = BuildQueryFilter(options.Filters); + WithPayloadSelector? payloadSelector = BuildPayloadSelector(options.Fields, enable: true); var client = GetClient(); var points = await client.SearchAsync(collectionName, @@ -364,6 +309,7 @@ public class QdrantDb : IVectorDb Value.KindOneofCase.StringValue => p.Value.StringValue, Value.KindOneofCase.BoolValue => p.Value.BoolValue, Value.KindOneofCase.IntegerValue => p.Value.IntegerValue, + Value.KindOneofCase.DoubleValue => p.Value.DoubleValue, _ => new object() }), Score = x.Score, @@ -400,8 +346,9 @@ public class QdrantDb : IVectorDb var result = await client.DeleteAsync(collectionName, new Filter()); return result.Status == UpdateStatus.Completed; } + #endregion - + #region Payload index public async Task CreateCollectionPayloadIndex(string collectionName, CreateVectorCollectionIndexOptions options) { var exist = await DoesCollectionExist(collectionName); @@ -577,6 +524,72 @@ public class QdrantDb : IVectorDb #region Private methods + private Filter? BuildQueryFilter(IEnumerable? keyValues) + { + Filter? queryFilter = null; + if (!keyValues.IsNullOrEmpty()) + { + var conditions = keyValues.Select(x => + { + var field = new FieldCondition + { + Key = x.Key, + Match = new Match { Text = x.Value }, + }; + + if (bool.TryParse(x.Value, out var boolVal)) + { + field.Match = new Match { Boolean = boolVal }; + } + else if (long.TryParse(x.Value, out var intVal)) + { + field.Match = new Match { Integer = intVal }; + } + + return new Condition + { + Field = field + }; + }); + + queryFilter = new Filter + { + Should = + { + conditions + } + }; + } + + return queryFilter; + } + + private WithPayloadSelector? BuildPayloadSelector(IEnumerable? payloads, bool enable = false) + { + WithPayloadSelector? payloadSelector = null; + if (enable) + { + payloadSelector = new WithPayloadSelector + { + Enable = true + }; + } + + if (!payloads.IsNullOrEmpty()) + { + payloadSelector = new WithPayloadSelector + { + Enable = true, + Include = new PayloadIncludeSelector + { + Fields = { payloads.ToArray() } + } + }; + } + + return payloadSelector; + } + private PayloadSchemaType ConvertPayloadSchemaType(string schemaType) { PayloadSchemaType res; From 1ea378e4fb77c6036be68af3e97c27aa27d6980e Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 13:54:51 -0500 Subject: [PATCH 28/51] fix chart handler --- .../BotSharp.Plugin.ChartHandler.csproj | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj index 4e7e8a21..784b2405 100644 --- a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj +++ b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj @@ -1,9 +1,13 @@  - net8.0 - enable - enable + $(TargetFramework) + enable + $(LangVersion) + $(BotSharpVersion) + $(GeneratePackageOnBuild) + $(GenerateDocumentationFile) + $(SolutionDir)packages From 1c58638306ea4e259eaeb07b877fee9b720e5744 Mon Sep 17 00:00:00 2001 From: Leon Yu <105097@lessen.com> Date: Wed, 6 Aug 2025 15:40:50 -0500 Subject: [PATCH 29/51] add end time --- .../Repositories/Filters/ConversationFilter.cs | 1 + .../Repository/MongoRepository.Conversation.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index b94876c3..e274adb7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -16,6 +16,7 @@ public class ConversationFilter public string? ChannelId { get; set; } public string? UserId { get; set; } public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } /// /// Agent task id diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index d0da6fbf..fb424f02 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -393,6 +393,10 @@ public partial class MongoRepository { convFilters.Add(convBuilder.Gte(x => x.CreatedTime, filter.StartTime.Value)); } + if (filter?.EndTime != null) + { + convFilters.Add(convBuilder.Lte(x => x.CreatedTime, filter.EndTime.Value)); + } if (filter?.Tags != null && filter.Tags.Any()) { convFilters.Add(convBuilder.AnyIn(x => x.Tags, filter.Tags)); From bac6a624d1c6113fff0a6c7e5bfcca5287e12977 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 16:36:36 -0500 Subject: [PATCH 30/51] refine filter --- Directory.Packages.props | 2 +- .../VectorStorage/Models/VectorFilter.cs | 11 ++-- .../Models/VectorSearchOptions.cs | 2 + .../Controllers/KnowledgeBaseController.cs | 4 +- .../Request/SearchVectorKnowledgeRequest.cs | 3 ++ .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 51 +++++++++++-------- 6 files changed, 46 insertions(+), 27 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 03798e37..c840c156 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -74,7 +74,7 @@ - + diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs index e4d4f018..a5c81faf 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs @@ -11,10 +11,15 @@ public class VectorFilter : StringIdPagination [JsonPropertyName("filters")] public IEnumerable? Filters { get; set; } + /// + /// Filter operator + /// + [JsonPropertyName("filter_operator")] + public string FilterOperator { get; set; } = "or"; /// - /// Included payload keys + /// Included payload fields /// - [JsonPropertyName("included_payloads")] - public IEnumerable? IncludedPayloads { get; set; } + [JsonPropertyName("fields")] + public IEnumerable? Fields { get; set; } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs index 33e9b05e..edf36b8f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs @@ -6,6 +6,7 @@ public class VectorSearchOptions { public IEnumerable? Fields { get; set; } = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer]; public IEnumerable? Filters { get; set; } + public string FilterOperator { get; set; } = "or"; public int? Limit { get; set; } = 5; public float? Confidence { get; set; } = 0.5f; public bool WithVector { get; set; } @@ -16,6 +17,7 @@ public class VectorSearchOptions { Fields = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer], Filters = null, + FilterOperator = "or", Limit = 5, Confidence = 0.5f, WithVector = false diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs index 9752e02c..47067354 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs @@ -61,6 +61,7 @@ public class KnowledgeBaseController : ControllerBase { Fields = request.Fields, Filters = request.Filters, + FilterOperator = request.FilterOperator, Limit = request.Limit ?? 5, Confidence = request.Confidence ?? 0.5f, WithVector = request.WithVector @@ -74,8 +75,7 @@ public class KnowledgeBaseController : ControllerBase public async Task> GetPagedVectorCollectionData([FromRoute] string collection, [FromBody] VectorFilter filter) { var data = await _knowledgeService.GetPagedVectorCollectionData(collection, filter); - var items = data.Items?.Select(x => VectorKnowledgeViewModel.From(x))? - .ToList() ?? new List(); + var items = data.Items?.Select(x => VectorKnowledgeViewModel.From(x))?.ToList() ?? []; return new StringIdPagedItems { diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs index ccf13050..b33c7daa 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs @@ -13,6 +13,9 @@ public class SearchVectorKnowledgeRequest [JsonPropertyName("filters")] public IEnumerable? Filters { get; set; } + [JsonPropertyName("filter_operator")] + public string FilterOperator { get; set; } = "and"; + [JsonPropertyName("limit")] public int? Limit { get; set; } = 5; diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 4acad653..de582b7c 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -142,17 +142,26 @@ public class QdrantDb : IVectorDb return new StringIdPagedItems(); } - Filter? queryFilter = BuildQueryFilter(filter.Filters); - WithPayloadSelector? payloadSelector = BuildPayloadSelector(filter.IncludedPayloads); + Filter? queryFilter = BuildQueryFilter(filter.Filters, filter.FilterOperator); + WithPayloadSelector? payloadSelector = BuildPayloadSelector(filter.Fields); var client = GetClient(); - var totalPointCount = await client.CountAsync(collectionName, filter: queryFilter); - var response = await client.ScrollAsync(collectionName, limit: (uint)filter.Size, + var tasks = new List(); + + var totalCountTask = client.CountAsync(collectionName, filter: queryFilter); + var dataResponseTask = client.ScrollAsync( + collectionName, + limit: (uint)filter.Size, offset: !string.IsNullOrWhiteSpace(filter.StartId) ? new PointId { Uuid = filter.StartId } : null, filter: queryFilter, payloadSelector: payloadSelector, vectorsSelector: filter.WithVector); + await Task.WhenAll([totalCountTask, dataResponseTask]); + + var totalPointCount = totalCountTask.Result; + var response = dataResponseTask.Result; + var points = response?.Result?.Select(x => new VectorCollectionData { Id = x.Id?.Uuid ?? string.Empty, @@ -161,6 +170,7 @@ public class QdrantDb : IVectorDb Value.KindOneofCase.StringValue => p.Value.StringValue, Value.KindOneofCase.BoolValue => p.Value.BoolValue, Value.KindOneofCase.IntegerValue => p.Value.IntegerValue, + Value.KindOneofCase.DoubleValue => p.Value.DoubleValue, _ => new object() }), Vector = filter.WithVector ? x.Vectors?.Vector?.Data?.ToArray() : null @@ -289,8 +299,8 @@ public class QdrantDb : IVectorDb } options ??= VectorSearchOptions.Default(); - Filter? queryFilter = BuildQueryFilter(options.Filters); - WithPayloadSelector? payloadSelector = BuildPayloadSelector(options.Fields, enable: true); + Filter? queryFilter = BuildQueryFilter(options.Filters, options.FilterOperator); + WithPayloadSelector? payloadSelector = BuildPayloadSelector(options.Fields); var client = GetClient(); var points = await client.SearchAsync(collectionName, @@ -524,7 +534,7 @@ public class QdrantDb : IVectorDb #region Private methods - private Filter? BuildQueryFilter(IEnumerable? keyValues) + private Filter? BuildQueryFilter(IEnumerable? keyValues, string op) { Filter? queryFilter = null; if (!keyValues.IsNullOrEmpty()) @@ -552,29 +562,28 @@ public class QdrantDb : IVectorDb }; }); - queryFilter = new Filter + if (op.IsEqualTo("and")) { - Should = + queryFilter = new Filter { - conditions - } - }; + Must = { conditions } + }; + } + else + { + queryFilter = new Filter + { + Should = { conditions } + }; + } } return queryFilter; } - private WithPayloadSelector? BuildPayloadSelector(IEnumerable? payloads, bool enable = false) + private WithPayloadSelector? BuildPayloadSelector(IEnumerable? payloads) { WithPayloadSelector? payloadSelector = null; - if (enable) - { - payloadSelector = new WithPayloadSelector - { - Enable = true - }; - } - if (!payloads.IsNullOrEmpty()) { payloadSelector = new WithPayloadSelector From 97c26a167e04cadd814d3f4913e3e10f347c2657 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 17:40:47 -0500 Subject: [PATCH 31/51] add query vector point --- .../Knowledges/IKnowledgeService.cs | 1 + .../VectorStorage/IVectorDb.cs | 3 +- .../Models/VectorQueryOptions.cs | 12 +++++ .../Services/BotSharpConversationSideCar.cs | 2 +- .../Controllers/KnowledgeBaseController.cs | 17 +++++++ .../Request/QueryVectorDataRequest.cs | 8 ++++ .../View/VectorKnowledgeViewModel.cs | 10 ++++ .../MemVecDb/MemoryVectorDb.cs | 3 +- .../Services/KnowledgeService.Document.cs | 4 +- .../Services/KnowledgeService.Graph.cs | 2 +- .../Services/KnowledgeService.Index.cs | 4 +- .../Services/KnowledgeService.Vector.cs | 47 ++++++++++++++----- .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 5 +- .../SemanticKernelMemoryStoreProvider.cs | 3 +- 14 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorQueryOptions.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs index 52657646..d9d5eed4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -17,6 +17,7 @@ public interface IKnowledgeService Task DeleteVectorCollectionData(string collectionName, string id); Task DeleteVectorCollectionAllData(string collectionName); Task CreateVectorCollectionData(string collectionName, VectorCreateModel create); + Task> GetVectorCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null); Task UpdateVectorCollectionData(string collectionName, VectorUpdateModel update); Task UpsertVectorCollectionData(string collectionName, VectorUpdateModel update); #endregion diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs index b078abb5..95d6257c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs @@ -14,8 +14,7 @@ public interface IVectorDb => throw new NotImplementedException(); Task> GetPagedCollectionData(string collectionName, VectorFilter filter) => throw new NotImplementedException(); - Task> GetCollectionData(string collectionName, IEnumerable ids, - bool withPayload = false, bool withVector = false) + Task> GetCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null) => throw new NotImplementedException(); Task CreateCollection(string collectionName, int dimension) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorQueryOptions.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorQueryOptions.cs new file mode 100644 index 00000000..37bad608 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorQueryOptions.cs @@ -0,0 +1,12 @@ +namespace BotSharp.Abstraction.VectorStorage.Models; + +public class VectorQueryOptions +{ + public bool WithPayload { get; set; } + public bool WithVector { get; set; } + + public static VectorQueryOptions Default() + { + return new(); + } +} diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index 3def01ef..79711cb6 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -112,7 +112,7 @@ public class BotSharpConversationSideCar : IConversationSideCar var response = await InnerExecute(agentId, text, postback, states); AfterExecute(); - _logger.LogInformation($"Existing side car conversation..."); + _logger.LogInformation($"Exiting side car conversation..."); return response; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs index 47067354..48392968 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs @@ -99,6 +99,19 @@ public class KnowledgeBaseController : ControllerBase return created; } + [HttpGet("/knowledge/vector/{collection}/points")] + public async Task> GetVectorCollectionData([FromRoute] string collection, [FromQuery] QueryVectorDataRequest request) + { + var options = new VectorQueryOptions + { + WithPayload = request.WithPayload, + WithVector = request.WithVector + }; + + var points = await _knowledgeService.GetVectorCollectionData(collection, request.Ids, options); + return points.Select(x => VectorKnowledgeViewModel.From(x)); + } + [HttpPut("/knowledge/vector/{collection}/update")] public async Task UpdateVectorKnowledge([FromRoute] string collection, [FromBody] VectorKnowledgeUpdateRequest request) { @@ -125,7 +138,10 @@ public class KnowledgeBaseController : ControllerBase { return await _knowledgeService.DeleteVectorCollectionAllData(collection); } + #endregion + + #region Index [HttpPost("/knowledge/vector/{collection}/payload/indexes")] public async Task> CreateCollectionPayloadIndexes([FromRoute] string collection, [FromBody] CreateVectorCollectionIndexRequest request) { @@ -277,6 +293,7 @@ public class KnowledgeBaseController : ControllerBase } #endregion + #region Private methods private FileStreamResult BuildFileResult(string fileName, BinaryData fileData) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs new file mode 100644 index 00000000..9aa9566e --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs @@ -0,0 +1,8 @@ +namespace BotSharp.OpenAPI.ViewModels.Knowledges.Request; + +public class QueryVectorDataRequest +{ + public IEnumerable Ids { get; set; } = []; + public bool WithVector { get; set; } + public bool WithPayload { get; set; } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs index bbd7dc3a..61b96a97 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs @@ -30,4 +30,14 @@ public class VectorKnowledgeViewModel Vector = result.Vector }; } + + public static VectorKnowledgeViewModel From(VectorCollectionData data) + { + return new VectorKnowledgeViewModel + { + Id = data.Id, + Data = data.Data, + Vector = data.Vector + }; + } } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs index a2327298..27823202 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/MemVecDb/MemoryVectorDb.cs @@ -39,8 +39,7 @@ public class MemoryVectorDb : IVectorDb throw new NotImplementedException(); } - public Task> GetCollectionData(string collectionName, IEnumerable ids, - bool withPayload = false, bool withVector = false) + public Task> GetCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null) { throw new NotImplementedException(); } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs index 491261d8..d55d5a4a 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs @@ -170,7 +170,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when importing doc content to knowledgebase ({collectionName}-{fileName})"); + _logger.LogError(ex, $"Error when importing doc content to knowledgebase ({collectionName}-{fileName})"); return false; } } @@ -212,7 +212,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when deleting knowledge document " + + _logger.LogError(ex, $"Error when deleting knowledge document " + $"(Collection: {collectionName}, File id: {fileId})"); return false; } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Graph.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Graph.cs index dbbe8885..7207bace 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Graph.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Graph.cs @@ -15,7 +15,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when searching graph knowledge (Query: {query})."); + _logger.LogError(ex, $"Error when searching graph knowledge (Query: {query})."); return new GraphSearchResult(); } } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs index 96cd3f25..640464de 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Index.cs @@ -18,14 +18,14 @@ public partial class KnowledgeService foreach (var option in options) { var created = await vectorDb.CreateCollectionPayloadIndex(collectionName, option); - var field = $"{option.FieldName}-{option.FieldSchemaType}"; + var field = $"{option.FieldName} ({option.FieldSchemaType})"; if (created) { response.Success.Add(field); } else { - _logger.LogError($"Failed to create vector collection payload index ({collectionName}-{field})."); + _logger.LogError($"Failed to create vector collection payload index ({collectionName} => {field})."); response.Fail.Add(field); } } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs index 102300f2..f21fe1de 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs @@ -1,6 +1,5 @@ using BotSharp.Abstraction.Files; using BotSharp.Abstraction.VectorStorage.Enums; -using System; namespace BotSharp.Plugin.KnowledgeBase.Services; @@ -63,7 +62,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when creating a vector collection ({collectionName})."); + _logger.LogError(ex, $"Error when creating a vector collection ({collectionName})."); return false; } } @@ -85,7 +84,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when getting vector db collections."); + _logger.LogError(ex, $"Error when getting vector db collections."); return Enumerable.Empty(); } } @@ -112,7 +111,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when getting vector db collection details."); + _logger.LogError(ex, $"Error when getting vector db collection details."); return null; } } @@ -144,7 +143,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when deleting collection ({collectionName})."); + _logger.LogError(ex, $"Error when deleting collection ({collectionName})."); return false; } } @@ -172,11 +171,33 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when creating vector collection data."); + _logger.LogError(ex, $"Error when creating vector collection data."); return false; } } + public async Task> GetVectorCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null) + { + try + { + if (string.IsNullOrWhiteSpace(collectionName) || ids.IsNullOrEmpty()) + { + return []; + } + + var db = GetVectorDb(); + var pointIds = ids.Where(x => Guid.TryParse(x, out _)).Select(x => Guid.Parse(x)); + var points = await db.GetCollectionData(collectionName, pointIds, options); + return points; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when querying vector collection {collectionName} points."); + return []; + } + } + + public async Task UpdateVectorCollectionData(string collectionName, VectorUpdateModel update) { try @@ -204,7 +225,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when updating vector collection data."); + _logger.LogError(ex, $"Error when updating vector collection data."); return false; } } @@ -221,7 +242,7 @@ public partial class KnowledgeService } var db = GetVectorDb(); - var found = await db.GetCollectionData(collectionName, [guid], withVector: true, withPayload: true); + var found = await db.GetCollectionData(collectionName, [guid], options: new() { WithVector = true, WithPayload = true }); if (!found.IsNullOrEmpty()) { if (found.First().Data[KnowledgePayloadName.Text].ToString() == update.Text) @@ -240,7 +261,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when updating vector collection data."); + _logger.LogError(ex, $"Error when updating vector collection data."); return false; } } @@ -259,7 +280,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when deleting vector collection data ({collectionName}-{id})."); + _logger.LogError(ex, $"Error when deleting vector collection data ({collectionName}-{id})."); return false; } } @@ -274,7 +295,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when deleting vector collection data ({collectionName})."); + _logger.LogError(ex, $"Error when deleting vector collection data ({collectionName})."); return false; } } @@ -294,7 +315,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when getting vector knowledge collection data ({collectionName})."); + _logger.LogError(ex, $"Error when getting vector knowledge collection data ({collectionName})."); return new StringIdPagedItems(); } } @@ -315,7 +336,7 @@ public partial class KnowledgeService } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when searching vector knowledge ({collectionName})."); + _logger.LogError(ex, $"Error when searching vector knowledge ({collectionName})."); return Enumerable.Empty(); } } diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index de582b7c..4bca24a2 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -185,8 +185,7 @@ public class QdrantDb : IVectorDb } - public async Task> GetCollectionData(string collectionName, IEnumerable ids, - bool withPayload = false, bool withVector = false) + public async Task> GetCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null) { if (ids.IsNullOrEmpty()) { @@ -201,7 +200,7 @@ public class QdrantDb : IVectorDb var client = GetClient(); var pointIds = ids.Select(x => new PointId { Uuid = x.ToString() }).Distinct().ToList(); - var points = await client.RetrieveAsync(collectionName, pointIds, withPayload, withVector); + var points = await client.RetrieveAsync(collectionName, pointIds, options?.WithPayload ?? false, options?.WithVector ?? false); return points.Select(x => new VectorCollectionData { Id = x.Id?.Uuid ?? string.Empty, diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs index 2189e8a2..e87ea423 100644 --- a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs @@ -48,8 +48,7 @@ namespace BotSharp.Plugin.SemanticKernel throw new NotImplementedException(); } - public Task> GetCollectionData(string collectionName, IEnumerable ids, - bool withPayload = false, bool withVector = false) + public Task> GetCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null) { throw new NotImplementedException(); } From f82f36b9dd5fdbe250d977502581e62dbb63e5d3 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 22:53:12 -0500 Subject: [PATCH 32/51] minor change --- .../ViewModels/Knowledges/Request/QueryVectorDataRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs index 9aa9566e..b3a3841b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/QueryVectorDataRequest.cs @@ -2,7 +2,7 @@ namespace BotSharp.OpenAPI.ViewModels.Knowledges.Request; public class QueryVectorDataRequest { - public IEnumerable Ids { get; set; } = []; + public List Ids { get; set; } = []; public bool WithVector { get; set; } public bool WithPayload { get; set; } } From 6e5689b640643039fc588d0bd59aa4406e51f8e2 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 22:55:31 -0500 Subject: [PATCH 33/51] use for loop --- .../Services/KnowledgeService.Vector.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs index f21fe1de..7c8e695e 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs @@ -185,8 +185,16 @@ public partial class KnowledgeService return []; } + var pointIds = new List(); + foreach (var id in ids) + { + if (Guid.TryParse(id, out var guid)) + { + pointIds.Add(guid); + } + } + var db = GetVectorDb(); - var pointIds = ids.Where(x => Guid.TryParse(x, out _)).Select(x => Guid.Parse(x)); var points = await db.GetCollectionData(collectionName, pointIds, options); return points; } From 0c064cd8b45c4b72c69ea0805a5466ffdd0f744b Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 Aug 2025 23:14:39 -0500 Subject: [PATCH 34/51] refine code --- .../Knowledges/IKnowledgeService.cs | 2 +- .../Services/KnowledgeService.Vector.cs | 58 +++++++++---------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs index d9d5eed4..d7d9d528 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -14,10 +14,10 @@ public interface IKnowledgeService Task GetVectorCollectionDetails(string collectionName); Task> SearchVectorKnowledge(string query, string collectionName, VectorSearchOptions options); Task> GetPagedVectorCollectionData(string collectionName, VectorFilter filter); + Task> GetVectorCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null); Task DeleteVectorCollectionData(string collectionName, string id); Task DeleteVectorCollectionAllData(string collectionName); Task CreateVectorCollectionData(string collectionName, VectorCreateModel create); - Task> GetVectorCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null); Task UpdateVectorCollectionData(string collectionName, VectorUpdateModel update); Task UpsertVectorCollectionData(string collectionName, VectorUpdateModel update); #endregion diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs index 7c8e695e..8704f7d1 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs @@ -85,7 +85,7 @@ public partial class KnowledgeService catch (Exception ex) { _logger.LogError(ex, $"Error when getting vector db collections."); - return Enumerable.Empty(); + return []; } } @@ -176,35 +176,6 @@ public partial class KnowledgeService } } - public async Task> GetVectorCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null) - { - try - { - if (string.IsNullOrWhiteSpace(collectionName) || ids.IsNullOrEmpty()) - { - return []; - } - - var pointIds = new List(); - foreach (var id in ids) - { - if (Guid.TryParse(id, out var guid)) - { - pointIds.Add(guid); - } - } - - var db = GetVectorDb(); - var points = await db.GetCollectionData(collectionName, pointIds, options); - return points; - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error when querying vector collection {collectionName} points."); - return []; - } - } - public async Task UpdateVectorCollectionData(string collectionName, VectorUpdateModel update) { @@ -328,6 +299,31 @@ public partial class KnowledgeService } } + public async Task> GetVectorCollectionData(string collectionName, IEnumerable ids, VectorQueryOptions? options = null) + { + try + { + if (string.IsNullOrWhiteSpace(collectionName) || ids.IsNullOrEmpty()) + { + return []; + } + + var pointIds = ids.Select(x => new { Id = x, IsValid = Guid.TryParse(x, out var guid), ParseResult = guid }) + .Where(x => x.IsValid) + .Select(x => x.ParseResult) + .ToList(); + + var db = GetVectorDb(); + var points = await db.GetCollectionData(collectionName, pointIds, options); + return points; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error when querying vector collection {collectionName} points."); + return []; + } + } + public async Task> SearchVectorKnowledge(string query, string collectionName, VectorSearchOptions options) { try @@ -345,7 +341,7 @@ public partial class KnowledgeService catch (Exception ex) { _logger.LogError(ex, $"Error when searching vector knowledge ({collectionName})."); - return Enumerable.Empty(); + return []; } } #endregion From df0423d917966370c2de3ffbd1edbd37b62d8ff7 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Thu, 7 Aug 2025 15:38:16 -0500 Subject: [PATCH 35/51] Update IRealtimeHook --- .../Realtime/IRealtimeHook.cs | 15 ++++++++---- .../Services/RealtimeHub.cs | 24 +++++++++---------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs b/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs index 51d7383a..b4fed514 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs @@ -6,8 +6,15 @@ namespace BotSharp.Abstraction.Realtime; public interface IRealtimeHook : IHookBase { - Task OnModelReady(Agent agent, IRealTimeCompletion completer); - string[] OnModelTranscriptPrompt(Agent agent); - Task OnTranscribeCompleted(RoleDialogModel message, TranscriptionData data); - Task ShouldReconnect(RealtimeHubConnection conn) => Task.FromResult(false); + Task OnModelReady(Agent agent, IRealTimeCompletion completer) + => Task.CompletedTask; + + string[] OnModelTranscriptPrompt(Agent agent) + => []; + + Task OnTranscribeCompleted(RoleDialogModel message, TranscriptionData data) + => Task.CompletedTask; + + Task ShouldReconnect(RealtimeHubConnection conn, RoleDialogModel message) + => Task.FromResult(false); } diff --git a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs index 0ff601e1..8c057b95 100644 --- a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs +++ b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs @@ -100,6 +100,17 @@ public class RealtimeHub : IRealtimeHub } await routing.InvokeFunction(message.FunctionName, message, options: new() { From = InvokeSource.Llm }); + + var hooks = _services.GetHooks(_conn.CurrentAgentId); + foreach (var hook in hooks) + { + if (await hook.ShouldReconnect(_conn, message)) + { + await _completer.Reconnect(_conn); + _logger.LogWarning("Reconnecting to model due to function call: {FunctionName}", message.FunctionName); + break; + } + } } else { @@ -117,19 +128,6 @@ public class RealtimeHub : IRealtimeHub } } } - - var isReconnect = false; - var realtimeHooks = _services.GetHooks(_conn.CurrentAgentId); - foreach (var hook in realtimeHooks) - { - isReconnect = await hook.ShouldReconnect(_conn); - if (isReconnect) break; - } - - if (isReconnect) - { - await _completer.Reconnect(_conn); - } }, onConversationItemCreated: async response => { From 3455535dbdecc1c3509ac5aa1c5b835b98d50df7 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 7 Aug 2025 23:43:07 -0500 Subject: [PATCH 36/51] change model --- .../Knowledges/Request/SearchVectorKnowledgeRequest.cs | 2 +- .../Knowledges/View/VectorKnowledgeViewModel.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs index b33c7daa..5720861b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs @@ -14,7 +14,7 @@ public class SearchVectorKnowledgeRequest public IEnumerable? Filters { get; set; } [JsonPropertyName("filter_operator")] - public string FilterOperator { get; set; } = "and"; + public string FilterOperator { get; set; } = "or"; [JsonPropertyName("limit")] public int? Limit { get; set; } = 5; diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs index 61b96a97..fb1e6cbf 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/View/VectorKnowledgeViewModel.cs @@ -15,9 +15,9 @@ public class VectorKnowledgeViewModel [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public double? Score { get; set; } - [JsonPropertyName("vector")] + [JsonPropertyName("vector_dimension")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public float[]? Vector { get; set; } + public int? VectorDimension { get; set; } public static VectorKnowledgeViewModel From(VectorSearchResult result) @@ -27,7 +27,7 @@ public class VectorKnowledgeViewModel Id = result.Id, Data = result.Data, Score = result.Score, - Vector = result.Vector + VectorDimension = result.Vector?.Length }; } @@ -37,7 +37,7 @@ public class VectorKnowledgeViewModel { Id = data.Id, Data = data.Data, - Vector = data.Vector + VectorDimension = data.Vector?.Length }; } } From d30c17365b48f8024cfaa076bea38b2c64543bdf Mon Sep 17 00:00:00 2001 From: vguruparan Date: Fri, 8 Aug 2025 13:58:30 -0500 Subject: [PATCH 37/51] Add function to get browser url for Playwright --- .../BotSharp.Abstraction/Browsing/IWebBrowser.cs | 1 + .../PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs index 0e6b9ba3..327b2f99 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs @@ -30,4 +30,5 @@ public interface IWebBrowser Task SendHttpRequest(MessageInfo message, HttpRequestParams actionParams); Task GetAttributeValue(MessageInfo message, ElementLocatingArgs location); Task SetAttributeValue(MessageInfo message, ElementLocatingArgs location); + Task GetCurrentUrl(MessageInfo message); } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs index b716b7ba..9f5f4727 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs @@ -17,4 +17,12 @@ public partial class PlaywrightWebDriver return answer; } + public async Task GetCurrentUrl(MessageInfo message) + { + var page = _instance.GetPage(message.ContextId); + if (page == null) + return string.Empty; + return page.Url; + + } } From 4b6d3a0059232a31b9096511d0cb510f8d8afdcb Mon Sep 17 00:00:00 2001 From: vguruparan Date: Fri, 8 Aug 2025 13:59:22 -0500 Subject: [PATCH 38/51] clean code --- .../Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs index 9f5f4727..d793eb21 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs @@ -23,6 +23,5 @@ public partial class PlaywrightWebDriver if (page == null) return string.Empty; return page.Url; - } } From e9806537bd303a2c8b49994a8b74b5beb1aa59bd Mon Sep 17 00:00:00 2001 From: vguruparan Date: Fri, 8 Aug 2025 15:07:17 -0500 Subject: [PATCH 39/51] Update method to sync --- src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs | 2 +- .../Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs index 327b2f99..eebf7bfe 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs @@ -30,5 +30,5 @@ public interface IWebBrowser Task SendHttpRequest(MessageInfo message, HttpRequestParams actionParams); Task GetAttributeValue(MessageInfo message, ElementLocatingArgs location); Task SetAttributeValue(MessageInfo message, ElementLocatingArgs location); - Task GetCurrentUrl(MessageInfo message); + string GetCurrentUrl(MessageInfo message); } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs index d793eb21..ff3025bd 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ExtractData.cs @@ -17,7 +17,7 @@ public partial class PlaywrightWebDriver return answer; } - public async Task GetCurrentUrl(MessageInfo message) + public string GetCurrentUrl(MessageInfo message) { var page = _instance.GetPage(message.ContextId); if (page == null) From 0e2fb847a7f050a33f7d30073631291e73fa9e5d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 8 Aug 2025 15:27:38 -0500 Subject: [PATCH 40/51] refine vector filter --- .../VectorStorage/Models/VectorFilter.cs | 12 +--- .../VectorStorage/Models/VectorFilterGroup.cs | 10 ++++ .../Models/VectorSearchOptions.cs | 6 +- .../Controllers/KnowledgeBaseController.cs | 3 +- .../Request/SearchVectorKnowledgeRequest.cs | 8 +-- .../Request/VectorKnowledgeCreateRequest.cs | 2 +- .../Services/KnowledgeService.Vector.cs | 15 ++++- .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 60 ++++++++++++------- 8 files changed, 73 insertions(+), 43 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilterGroup.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs index a5c81faf..88a1aca4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs @@ -6,16 +6,10 @@ public class VectorFilter : StringIdPagination public bool WithVector { get; set; } /// - /// For keyword search + /// Filter group: each item contains a logical operator and a list of key-value pairs /// - [JsonPropertyName("filters")] - public IEnumerable? Filters { get; set; } - - /// - /// Filter operator - /// - [JsonPropertyName("filter_operator")] - public string FilterOperator { get; set; } = "or"; + [JsonPropertyName("filter_groups")] + public IEnumerable? FilterGroups { get; set; } /// /// Included payload fields diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilterGroup.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilterGroup.cs new file mode 100644 index 00000000..0b2eb6ac --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilterGroup.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.VectorStorage.Models; + +public class VectorFilterGroup +{ + [JsonPropertyName("filters")] + public IEnumerable? Filters { get; set; } + + [JsonPropertyName("filter_operator")] + public string FilterOperator { get; set; } = "or"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs index edf36b8f..ef888bb5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSearchOptions.cs @@ -5,8 +5,7 @@ namespace BotSharp.Abstraction.VectorStorage.Models; public class VectorSearchOptions { public IEnumerable? Fields { get; set; } = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer]; - public IEnumerable? Filters { get; set; } - public string FilterOperator { get; set; } = "or"; + public IEnumerable? FilterGroups { get; set; } public int? Limit { get; set; } = 5; public float? Confidence { get; set; } = 0.5f; public bool WithVector { get; set; } @@ -16,8 +15,7 @@ public class VectorSearchOptions return new() { Fields = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer], - Filters = null, - FilterOperator = "or", + FilterGroups = null, Limit = 5, Confidence = 0.5f, WithVector = false diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs index 48392968..f8bcacef 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeBaseController.cs @@ -60,8 +60,7 @@ public class KnowledgeBaseController : ControllerBase var options = new VectorSearchOptions { Fields = request.Fields, - Filters = request.Filters, - FilterOperator = request.FilterOperator, + FilterGroups = request.FilterGroups, Limit = request.Limit ?? 5, Confidence = request.Confidence ?? 0.5f, WithVector = request.WithVector diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs index 5720861b..50a2fc5c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/SearchVectorKnowledgeRequest.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.VectorStorage.Models; using System.Text.Json.Serialization; namespace BotSharp.OpenAPI.ViewModels.Knowledges; @@ -10,11 +11,8 @@ public class SearchVectorKnowledgeRequest [JsonPropertyName("fields")] public IEnumerable? Fields { get; set; } - [JsonPropertyName("filters")] - public IEnumerable? Filters { get; set; } - - [JsonPropertyName("filter_operator")] - public string FilterOperator { get; set; } = "or"; + [JsonPropertyName("filter_groups")] + public IEnumerable? FilterGroups { get; set; } [JsonPropertyName("limit")] public int? Limit { get; set; } = 5; diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeCreateRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeCreateRequest.cs index 9477d65d..ddcee3c0 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeCreateRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Knowledges/Request/VectorKnowledgeCreateRequest.cs @@ -8,7 +8,7 @@ public class VectorKnowledgeCreateRequest public string Text { get; set; } [JsonPropertyName("data_source")] - public string DataSource { get; set; } = VectorDataSource.Api; + public string DataSource { get; set; } = string.Empty; [JsonPropertyName("payload")] public Dictionary? Payload { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs index 8704f7d1..67d546e1 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Vector.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Files; using BotSharp.Abstraction.VectorStorage.Enums; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; namespace BotSharp.Plugin.KnowledgeBase.Services; @@ -165,7 +166,12 @@ public partial class KnowledgeService var db = GetVectorDb(); var guid = Guid.NewGuid(); var payload = create.Payload ?? new(); - payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(create.DataSource) ? create.DataSource : VectorDataSource.Api; + + if (!payload.TryGetValue(KnowledgePayloadName.DataSource, out _)) + { + payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(create.DataSource) ? + create.DataSource : VectorDataSource.Api; + } return await db.Upsert(collectionName, guid, vector, create.Text, payload); } @@ -198,7 +204,12 @@ public partial class KnowledgeService var textEmbedding = GetTextEmbedding(collectionName); var vector = await textEmbedding.GetVectorAsync(update.Text); var payload = update.Payload ?? new(); - payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ? update.DataSource : VectorDataSource.Api; + + if (!payload.TryGetValue(KnowledgePayloadName.DataSource, out _)) + { + payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ? + update.DataSource : VectorDataSource.Api; + } return await db.Upsert(collectionName, guid, vector, update.Text, payload); } diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 4bca24a2..f5465c32 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -142,7 +142,7 @@ public class QdrantDb : IVectorDb return new StringIdPagedItems(); } - Filter? queryFilter = BuildQueryFilter(filter.Filters, filter.FilterOperator); + Filter? queryFilter = BuildQueryFilter(filter.FilterGroups); WithPayloadSelector? payloadSelector = BuildPayloadSelector(filter.Fields); var client = GetClient(); @@ -237,7 +237,10 @@ public class QdrantDb : IVectorDb foreach (var item in payload) { var value = item.Value?.ToString(); - if (value == null) continue; + if (value == null || item.Key.IsEqualTo(KnowledgePayloadName.Text)) + { + continue; + } if (bool.TryParse(value, out var b)) { @@ -298,7 +301,7 @@ public class QdrantDb : IVectorDb } options ??= VectorSearchOptions.Default(); - Filter? queryFilter = BuildQueryFilter(options.Filters, options.FilterOperator); + Filter? queryFilter = BuildQueryFilter(options.FilterGroups); WithPayloadSelector? payloadSelector = BuildPayloadSelector(options.Fields); var client = GetClient(); @@ -533,49 +536,66 @@ public class QdrantDb : IVectorDb #region Private methods - private Filter? BuildQueryFilter(IEnumerable? keyValues, string op) + private Filter? BuildQueryFilter(IEnumerable? filterGroups) { Filter? queryFilter = null; - if (!keyValues.IsNullOrEmpty()) + + if (filterGroups.IsNullOrEmpty()) { - var conditions = keyValues.Select(x => + return queryFilter; + } + + var conditions = filterGroups.Where(x => !x.Filters.IsNullOrEmpty()).Select(x => + { + Filter filter; + var innerConditions = x.Filters.Select(f => { var field = new FieldCondition { - Key = x.Key, - Match = new Match { Text = x.Value }, + Key = f.Key, + Match = new Match { Text = f.Value }, }; - if (bool.TryParse(x.Value, out var boolVal)) + if (bool.TryParse(f.Value, out var boolVal)) { field.Match = new Match { Boolean = boolVal }; } - else if (long.TryParse(x.Value, out var intVal)) + else if (long.TryParse(f.Value, out var intVal)) { field.Match = new Match { Integer = intVal }; } - return new Condition - { - Field = field - }; + return new Condition { Field = field }; }); - if (op.IsEqualTo("and")) + if (x.FilterOperator.IsEqualTo("and")) { - queryFilter = new Filter + filter = new Filter { - Must = { conditions } + Must = { innerConditions } }; } else { - queryFilter = new Filter + filter = new Filter { - Should = { conditions } + Should = { innerConditions } }; } - } + + return new Condition + { + Filter = filter + }; + }); + + queryFilter = new Filter + { + Must = + { + conditions + } + }; return queryFilter; } From bf56fa9d4364dbac9c538b75fdc12d8d1ac18a64 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 8 Aug 2025 17:13:37 -0500 Subject: [PATCH 41/51] add reasoning effort level --- Directory.Packages.props | 2 +- .../Agents/Constants/LlmConstant.cs | 1 + .../Agents/Models/AgentLlmConfig.cs | 7 ++++ .../Controllers/AgentController.cs | 3 +- .../Models/AgentLlmConfigMongoElement.cs | 3 ++ .../Providers/Chat/ChatCompletionProvider.cs | 38 ++++++++++++++++++- 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c840c156..d5721a14 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,7 +46,7 @@ - + diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs index 3919ca57..e45c8b15 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs @@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Agents.Constants; public static class LlmConstant { public const int DEFAULT_MAX_OUTPUT_TOKEN = 1024; + public const string DEFAULT_REASONING_EFFORT_LEVEL = "low"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs index f1df3a01..75e3aa93 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs @@ -34,4 +34,11 @@ public class AgentLlmConfig [JsonPropertyName("max_output_tokens")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxOutputTokens { get; set; } + + /// + /// Reasoning effort level + /// + [JsonPropertyName("reasoning_effort_level")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? ReasoningEffortLevel { get; set; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 4543fe19..b6950e04 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -14,8 +14,7 @@ public class AgentController : ControllerBase public AgentController( IAgentService agentService, IUserIdentity user, - IServiceProvider services - ) + IServiceProvider services) { _agentService = agentService; _user = user; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs index b626d7da..63bc3b20 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs @@ -10,6 +10,7 @@ public class AgentLlmConfigMongoElement public bool IsInherit { get; set; } public int MaxRecursionDepth { get; set; } public int? MaxOutputTokens { get; set; } + public string? ReasoningEffortLevel { get; set; } public static AgentLlmConfigMongoElement? ToMongoElement(AgentLlmConfig? config) { @@ -22,6 +23,7 @@ public class AgentLlmConfigMongoElement IsInherit = config.IsInherit, MaxRecursionDepth = config.MaxRecursionDepth, MaxOutputTokens = config.MaxOutputTokens, + ReasoningEffortLevel = config.ReasoningEffortLevel }; } @@ -36,6 +38,7 @@ public class AgentLlmConfigMongoElement IsInherit = config.IsInherit, MaxRecursionDepth = config.MaxRecursionDepth, MaxOutputTokens = config.MaxOutputTokens, + ReasoningEffortLevel = config.ReasoningEffortLevel }; } } diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index af7e7e0a..c9318d79 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -17,7 +17,12 @@ public class ChatCompletionProvider : IChatCompletion private readonly Dictionary _defaultTemperature = new() { - { "o4-mini", 1.0f } + { "o3", 1.0f }, + { "o3-mini", 1.0f }, + { "o4-mini", 1.0f }, + { "gpt-5", 1.0f }, + { "gpt-5-mini", 1.0f }, + { "gpt-5-nano", 1.0f } }; public virtual string Provider => "openai"; @@ -499,13 +504,42 @@ public class ChatCompletionProvider : IChatCompletion ? tokens : agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN; + var level = state.GetState("reasoning_effort_level") + .IfNullOrEmptyAs(agent?.LlmConfig?.ReasoningEffortLevel ?? string.Empty) + .IfNullOrEmptyAs(LlmConstant.DEFAULT_REASONING_EFFORT_LEVEL); + var reasoningEffortLevel = ParseReasoningEffortLevel(level); + return new ChatCompletionOptions() { Temperature = temperature, - MaxOutputTokenCount = maxTokens + MaxOutputTokenCount = maxTokens, + ReasoningEffortLevel = reasoningEffortLevel }; } + private ChatReasoningEffortLevel? ParseReasoningEffortLevel(string? level) + { + if (string.IsNullOrWhiteSpace(level) || !_defaultTemperature.ContainsKey(_model)) + { + return null; + } + + var effortLevel = ChatReasoningEffortLevel.Low; + switch (level.ToLower()) + { + case "medium": + effortLevel = ChatReasoningEffortLevel.Medium; + break; + case "high": + effortLevel = ChatReasoningEffortLevel.High; + break; + default: + break; + } + + return effortLevel; + } + public void SetModelName(string model) { _model = model; From 81b2f299cd42d41ce37e365c9818e9b403cb479a Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Sun, 10 Aug 2025 22:42:10 -0500 Subject: [PATCH 42/51] revert reasoning effor level --- Directory.Packages.props | 2 +- .../Agents/Constants/LlmConstant.cs | 1 - .../Agents/Models/AgentLlmConfig.cs | 7 ----- .../Models/AgentLlmConfigMongoElement.cs | 7 ++--- .../Providers/Chat/ChatCompletionProvider.cs | 31 +------------------ 5 files changed, 4 insertions(+), 44 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d5721a14..4b450020 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,7 +46,7 @@ - + diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs index e45c8b15..3919ca57 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs @@ -3,5 +3,4 @@ namespace BotSharp.Abstraction.Agents.Constants; public static class LlmConstant { public const int DEFAULT_MAX_OUTPUT_TOKEN = 1024; - public const string DEFAULT_REASONING_EFFORT_LEVEL = "low"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs index 75e3aa93..f1df3a01 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs @@ -34,11 +34,4 @@ public class AgentLlmConfig [JsonPropertyName("max_output_tokens")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxOutputTokens { get; set; } - - /// - /// Reasoning effort level - /// - [JsonPropertyName("reasoning_effort_level")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? ReasoningEffortLevel { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs index 63bc3b20..21c99e34 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs @@ -10,7 +10,6 @@ public class AgentLlmConfigMongoElement public bool IsInherit { get; set; } public int MaxRecursionDepth { get; set; } public int? MaxOutputTokens { get; set; } - public string? ReasoningEffortLevel { get; set; } public static AgentLlmConfigMongoElement? ToMongoElement(AgentLlmConfig? config) { @@ -22,8 +21,7 @@ public class AgentLlmConfigMongoElement Model = config.Model, IsInherit = config.IsInherit, MaxRecursionDepth = config.MaxRecursionDepth, - MaxOutputTokens = config.MaxOutputTokens, - ReasoningEffortLevel = config.ReasoningEffortLevel + MaxOutputTokens = config.MaxOutputTokens }; } @@ -37,8 +35,7 @@ public class AgentLlmConfigMongoElement Model = config.Model, IsInherit = config.IsInherit, MaxRecursionDepth = config.MaxRecursionDepth, - MaxOutputTokens = config.MaxOutputTokens, - ReasoningEffortLevel = config.ReasoningEffortLevel + MaxOutputTokens = config.MaxOutputTokens }; } } diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index c9318d79..9eec0c1e 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -504,42 +504,13 @@ public class ChatCompletionProvider : IChatCompletion ? tokens : agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN; - var level = state.GetState("reasoning_effort_level") - .IfNullOrEmptyAs(agent?.LlmConfig?.ReasoningEffortLevel ?? string.Empty) - .IfNullOrEmptyAs(LlmConstant.DEFAULT_REASONING_EFFORT_LEVEL); - var reasoningEffortLevel = ParseReasoningEffortLevel(level); - return new ChatCompletionOptions() { Temperature = temperature, - MaxOutputTokenCount = maxTokens, - ReasoningEffortLevel = reasoningEffortLevel + MaxOutputTokenCount = maxTokens }; } - private ChatReasoningEffortLevel? ParseReasoningEffortLevel(string? level) - { - if (string.IsNullOrWhiteSpace(level) || !_defaultTemperature.ContainsKey(_model)) - { - return null; - } - - var effortLevel = ChatReasoningEffortLevel.Low; - switch (level.ToLower()) - { - case "medium": - effortLevel = ChatReasoningEffortLevel.Medium; - break; - case "high": - effortLevel = ChatReasoningEffortLevel.High; - break; - default: - break; - } - - return effortLevel; - } - public void SetModelName(string model) { _model = model; From 7f6f5685f2ffc27204d4b6f70e6df8a88b5ee40a Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Sun, 10 Aug 2025 22:53:53 -0500 Subject: [PATCH 43/51] add time range --- .../Repositories/Filters/InstructLogFilter.cs | 2 ++ .../FileRepository/FileRepository.Conversation.cs | 4 ++++ .../Repository/FileRepository/FileRepository.Log.cs | 8 ++++++++ .../Repository/MongoRepository.Log.cs | 8 ++++++++ 4 files changed, 22 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogFilter.cs index c1040f94..aeec7b18 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogFilter.cs @@ -8,6 +8,8 @@ public class InstructLogFilter : Pagination public List? TemplateNames { get; set; } public List? UserIds { get; set; } public List? States { get; set; } + public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } public static InstructLogFilter Empty() { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 81da4047..a42bc988 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -452,6 +452,10 @@ public partial class FileRepository { matched = matched && record.CreatedTime >= filter.StartTime.Value; } + if (filter?.EndTime != null) + { + matched = matched && record.CreatedTime <= filter.EndTime.Value; + } if (filter?.Tags != null && filter.Tags.Any()) { matched = matched && !record.Tags.IsNullOrEmpty() && record.Tags.Exists(t => filter.Tags.Contains(t)); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs index 3913a404..5b8d095f 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs @@ -202,6 +202,14 @@ namespace BotSharp.Core.Repository { matched = matched && filter.UserIds.Contains(log.UserId); } + if (filter.StartTime.HasValue) + { + matched = matched && log.CreatedTime >= filter.StartTime.Value; + } + if (filter.EndTime.HasValue) + { + matched = matched && log.CreatedTime <= filter.EndTime.Value; + } // Check states if (matched && filter != null && !filter.States.IsNullOrEmpty()) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs index 8cd6e158..196c6dfc 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs @@ -196,6 +196,14 @@ public partial class MongoRepository { logFilters.Add(logBuilder.In(x => x.TemplateName, filter.TemplateNames)); } + if (filter.StartTime.HasValue) + { + logFilters.Add(logBuilder.Gte(x => x.CreatedTime, filter.StartTime.Value)); + } + if (filter.EndTime.HasValue) + { + logFilters.Add(logBuilder.Lte(x => x.CreatedTime, filter.EndTime.Value)); + } // Filter states if (filter != null && !filter.States.IsNullOrEmpty()) From ed1576f7e02ed866330a7fc4d85bddfe9e9f429b Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 11 Aug 2025 15:46:35 -0500 Subject: [PATCH 44/51] refine list loading --- .../Filters/ConversationStateKeysFilter.cs | 2 + .../Filters/InstructLogKeysFilter.cs | 2 + .../Repositories/IBotSharpRepository.cs | 12 +- .../Users/IUserService.cs | 1 + .../Utilities/Pagination.cs | 2 +- .../Services/CrontabService.cs | 2 +- .../Services/ConversationService.cs | 2 +- .../Services/LoggerService.Instruction.cs | 12 +- .../Repository/BotSharpDbContext.cs | 196 ------------------ .../FileRepository.AgentTask.cs | 2 +- .../FileRepository.Conversation.cs | 8 +- .../FileRepository/FileRepository.Crontab.cs | 4 +- .../FileRepository.KnowledgeBase.cs | 2 +- .../FileRepository/FileRepository.Log.cs | 6 +- .../FileRepository/FileRepository.User.cs | 2 +- .../Tasks/Services/AgentTaskService.cs | 2 +- .../Users/Services/UserService.cs | 10 +- .../Controllers/ConversationController.cs | 4 +- .../Services/KnowledgeService.Document.cs | 6 +- .../Repository/MongoRepository.AgentTask.cs | 22 +- .../MongoRepository.Conversation.cs | 28 ++- .../Repository/MongoRepository.Crontab.cs | 19 +- .../MongoRepository.KnowledgeBase.cs | 18 +- .../Repository/MongoRepository.Log.cs | 27 ++- .../Repository/MongoRepository.User.cs | 19 +- 25 files changed, 149 insertions(+), 261 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationStateKeysFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationStateKeysFilter.cs index bff4b43c..201a031c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationStateKeysFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationStateKeysFilter.cs @@ -8,6 +8,8 @@ public class ConversationStateKeysFilter public bool PreLoad { get; set; } public List? AgentIds { get; set; } public List? UserIds { get; set; } + public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } public ConversationStateKeysFilter() { diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogKeysFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogKeysFilter.cs index c3eade89..ef04ede6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogKeysFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogKeysFilter.cs @@ -8,6 +8,8 @@ public class InstructLogKeysFilter public bool PreLoad { get; set; } public List? AgentIds { get; set; } public List? UserIds { get; set; } + public DateTime? StartTime { get; set; } + public DateTime? EndTime { get; set; } public InstructLogKeysFilter() { diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index d608d71f..8e0e2d55 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -56,7 +56,7 @@ public interface IBotSharpRepository : IHaveServiceProvider void UpdateUserPhone(string userId, string Iphone, string regionCode) => throw new NotImplementedException(); void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException(); void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException(); - PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException(); + ValueTask> GetUsers(UserFilter filter) => throw new NotImplementedException(); List SearchLoginUsers(User filter, string source = UserSource.Internal) =>throw new NotImplementedException(); User? GetUserDetails(string userId, bool includeAgent = false) => throw new NotImplementedException(); bool UpdateUser(User user, bool updateUserAgents = false) => throw new NotImplementedException(); @@ -93,7 +93,7 @@ public interface IBotSharpRepository : IHaveServiceProvider #endregion #region Agent Task - PagedItems GetAgentTasks(AgentTaskFilter filter) + ValueTask> GetAgentTasks(AgentTaskFilter filter) => throw new NotImplementedException(); AgentTask? GetAgentTask(string agentId, string taskId) => throw new NotImplementedException(); @@ -126,7 +126,7 @@ public interface IBotSharpRepository : IHaveServiceProvider => throw new NotImplementedException(); Conversation GetConversation(string conversationId, bool isLoadStates = false) => throw new NotImplementedException(); - PagedItems GetConversations(ConversationFilter filter) + ValueTask> GetConversations(ConversationFilter filter) => throw new NotImplementedException(); void UpdateConversationTitle(string conversationId, string title) => throw new NotImplementedException(); @@ -179,7 +179,7 @@ public interface IBotSharpRepository : IHaveServiceProvider bool SaveInstructionLogs(IEnumerable logs) => throw new NotImplementedException(); - PagedItems GetInstructionLogs(InstructLogFilter filter) + ValueTask> GetInstructionLogs(InstructLogFilter filter) => throw new NotImplementedException(); List GetInstructionLogSearchKeys(InstructLogKeysFilter filter) @@ -227,7 +227,7 @@ public interface IBotSharpRepository : IHaveServiceProvider /// bool DeleteKnolwedgeBaseFileMeta(string collectionName, string vectorStoreProvider, Guid? fileId = null) => throw new NotImplementedException(); - PagedItems GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter) + ValueTask> GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter) => throw new NotImplementedException(); #endregion @@ -236,7 +236,7 @@ public interface IBotSharpRepository : IHaveServiceProvider => throw new NotImplementedException(); bool DeleteCrontabItem(string conversationId) => throw new NotImplementedException(); - PagedItems GetCrontabItems(CrontabItemFilter filter) + ValueTask> GetCrontabItems(CrontabItemFilter filter) => throw new NotImplementedException(); #endregion } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 1499ea17..d03687ff 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -7,6 +7,7 @@ namespace BotSharp.Abstraction.Users; public interface IUserService { Task GetUser(string id); + Task> GetUsers(List ids); Task> GetUsers(UserFilter filter); Task> SearchLoginUsers(User filter); Task GetUserDetails(string userId, bool includeAgent = false); diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs index 4d21013b..c63b0056 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs @@ -50,6 +50,6 @@ public class Pagination : ICacheKey public class PagedItems { - public int Count { get; set; } + public long Count { get; set; } public IEnumerable Items { get; set; } = new List(); } diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs index 255faa71..b2101ee5 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs @@ -44,7 +44,7 @@ public class CrontabService : ICrontabService, ITaskFeeder public async Task> GetCrontable() { var repo = _services.GetRequiredService(); - var crontable = repo.GetCrontabItems(CrontabItemFilter.Empty()); + var crontable = await repo.GetCrontabItems(CrontabItemFilter.Empty()); // Add fixed crontab items from cronsources var fixedCrantabItems = crontable.Items.ToList(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 676ea362..9e410b49 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -87,7 +87,7 @@ public partial class ConversationService : IConversationService } var db = _services.GetRequiredService(); - var conversations = db.GetConversations(filter); + var conversations = await db.GetConversations(filter); return conversations; } diff --git a/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs b/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs index 42ad45ab..965212ca 100644 --- a/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs +++ b/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs @@ -18,22 +18,18 @@ public partial class LoggerService filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : null; - var agents = new List(); var users = new List(); var db = _services.GetRequiredService(); - var logs = db.GetInstructionLogs(filter); + var agentService = _services.GetRequiredService(); + var logs = await db.GetInstructionLogs(filter); var agentIds = logs.Items.Where(x => !string.IsNullOrEmpty(x.AgentId)).Select(x => x.AgentId).ToList(); var userIds = logs.Items.Where(x => !string.IsNullOrEmpty(x.UserId)).Select(x => x.UserId).ToList(); - agents = db.GetAgents(new AgentFilter - { - AgentIds = agentIds, - Pager = new Pagination { Size = filter.Size } - }); + var agents = await agentService.GetAgentOptions(agentIds); if (isAdmin) { - users = db.GetUserByIds(userIds); + users = await userService.GetUsers(userIds); } var items = logs.Items.Select(x => diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 492dcde3..c2dfc25f 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -1,202 +1,6 @@ -using BotSharp.Abstraction.Loggers.Models; -using BotSharp.Abstraction.Plugins.Models; -using BotSharp.Abstraction.Tasks.Models; -using BotSharp.Abstraction.Translation.Models; -using BotSharp.Abstraction.VectorStorage.Models; - namespace BotSharp.Core.Repository; public class BotSharpDbContext : Database, IBotSharpRepository { public IServiceProvider ServiceProvider => throw new NotImplementedException(); - - #region Plugin - public PluginConfig GetPluginConfig() => throw new NotImplementedException(); - public void SavePluginConfig(PluginConfig config) => throw new NotImplementedException(); - #endregion - - #region Agent - public Agent GetAgent(string agentId, bool basicsOnly = false) - => throw new NotImplementedException(); - - public List GetAgents(AgentFilter filter) - => throw new NotImplementedException(); - - public List GetUserAgents(string userId) - => throw new NotImplementedException(); - - public void UpdateAgent(Agent agent, AgentField field) - => throw new NotImplementedException(); - - public string GetAgentTemplate(string agentId, string templateName) - => throw new NotImplementedException(); - - public bool PatchAgentTemplate(string agentId, AgentTemplate template) - => throw new NotImplementedException(); - - public List GetAgentResponses(string agentId, string prefix, string intent) - => throw new NotImplementedException(); - - public void BulkInsertAgents(List agents) - => throw new NotImplementedException(); - - public void BulkInsertUserAgents(List userAgents) - => throw new NotImplementedException(); - - public bool DeleteAgents() - => throw new NotImplementedException(); - - public bool DeleteAgent(string agentId) - => throw new NotImplementedException(); - #endregion - - #region Agent Task - public PagedItems GetAgentTasks(AgentTaskFilter filter) - => throw new NotImplementedException(); - - public AgentTask? GetAgentTask(string agentId, string taskId) - => throw new NotImplementedException(); - - public void InsertAgentTask(AgentTask task) - => throw new NotImplementedException(); - - public void BulkInsertAgentTasks(List tasks) - => throw new NotImplementedException(); - - public void UpdateAgentTask(AgentTask task, AgentTaskField field) - => throw new NotImplementedException(); - - public bool DeleteAgentTask(string agentId, List taskIds) - => throw new NotImplementedException(); - - public bool DeleteAgentTasks() - => throw new NotImplementedException(); - #endregion - - #region Conversation - public void CreateNewConversation(Conversation conversation) - => throw new NotImplementedException(); - - public bool DeleteConversations(IEnumerable conversationIds) - => throw new NotImplementedException(); - - public Conversation GetConversation(string conversationId, bool isLoadStates = false) - => throw new NotImplementedException(); - - public PagedItems GetConversations(ConversationFilter filter) - => throw new NotImplementedException(); - - public List GetLastConversations() - => throw new NotImplementedException(); - - public List GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable excludeAgentIds) - => throw new NotImplementedException(); - - [SideCar] - public List GetConversationDialogs(string conversationId) - => throw new NotImplementedException(); - - public ConversationState GetConversationStates(string conversationId) - => throw new NotImplementedException(); - - [SideCar] - public void AppendConversationDialogs(string conversationId, List dialogs) - => throw new NotImplementedException(); - - public void UpdateConversationTitle(string conversationId, string title) - => throw new NotImplementedException(); - public void UpdateConversationTitleAlias(string conversationId, string titleAlias) - => throw new NotImplementedException(); - - public bool UpdateConversationTags(string conversationId, List tags) - => throw new NotImplementedException(); - - public bool AppendConversationTags(string conversationId, List tags) - => throw new NotImplementedException(); - - public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request) - => throw new NotImplementedException(); - - [SideCar] - public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) - => throw new NotImplementedException(); - - [SideCar] - public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) - => throw new NotImplementedException(); - - public void UpdateConversationStates(string conversationId, List states) - => throw new NotImplementedException(); - - public void UpdateConversationStatus(string conversationId, string status) - => throw new NotImplementedException(); - - public List TruncateConversation(string conversationId, string messageId, bool cleanLog = false) - => throw new NotImplementedException(); - #endregion - - #region LLM Completion Log - public void SaveLlmCompletionLog(LlmCompletionLog log) - { - throw new NotImplementedException(); - } - #endregion - - #region Conversation Content Log - public void SaveConversationContentLog(ContentLogOutputModel log) - { - throw new NotImplementedException(); - } - - public List GetConversationContentLogs(string conversationId) - { - throw new NotImplementedException(); - } - #endregion - - #region Conversation State Log - public void SaveConversationStateLog(ConversationStateLogModel log) - { - throw new NotImplementedException(); - } - - public List GetConversationStateLogs(string conversationId) - { - throw new NotImplementedException(); - } - #endregion - - #region Stats - public void IncrementConversationCount() - { - throw new NotImplementedException(); - } - #endregion - - #region Translation - public IEnumerable GetTranslationMemories(IEnumerable queries) - => throw new NotImplementedException(); - public bool SaveTranslationMemories(IEnumerable inputs) => - throw new NotImplementedException(); - #endregion - - #region KnowledgeBase - public bool AddKnowledgeCollectionConfigs(List configs, bool reset = false) => - throw new NotImplementedException(); - - public bool DeleteKnowledgeCollectionConfig(string collectionName) => - throw new NotImplementedException(); - - public IEnumerable GetKnowledgeCollectionConfigs(VectorCollectionConfigFilter filter) => - throw new NotImplementedException(); - - public bool SaveKnolwedgeBaseFileMeta(KnowledgeDocMetaData metaData) => - throw new NotImplementedException(); - - public bool DeleteKnolwedgeBaseFileMeta(string collectionName, string vectorStoreProvider, Guid? fileId = null) => - throw new NotImplementedException(); - - public PagedItems GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter) => - throw new NotImplementedException(); - #endregion } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs index 016e0c36..2b84f212 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs @@ -6,7 +6,7 @@ namespace BotSharp.Core.Repository; public partial class FileRepository { #region Task - public PagedItems GetAgentTasks(AgentTaskFilter filter) + public async ValueTask> GetAgentTasks(AgentTaskFilter filter) { if (filter == null) { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index a42bc988..68881ea3 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -379,7 +379,7 @@ public partial class FileRepository return record; } - public PagedItems GetConversations(ConversationFilter filter) + public async ValueTask> GetConversations(ConversationFilter filter) { if (filter == null) { @@ -546,7 +546,7 @@ public partial class FileRepository return new PagedItems { Items = records.OrderByDescending(x => x.CreatedTime).Skip(pager.Offset).Take(pager.Size), - Count = records.Count(), + Count = records.Count() }; } @@ -708,7 +708,9 @@ public partial class FileRepository if (conv == null || states.IsNullOrEmpty() || (!filter.AgentIds.IsNullOrEmpty() && !filter.AgentIds.Contains(conv.AgentId)) - || (!filter.UserIds.IsNullOrEmpty() && !filter.UserIds.Contains(conv.UserId))) + || (!filter.UserIds.IsNullOrEmpty() && !filter.UserIds.Contains(conv.UserId)) + || (filter.StartTime.HasValue && conv.CreatedTime < filter.StartTime.Value) + || (filter.EndTime.HasValue && conv.CreatedTime > filter.EndTime.Value)) { continue; } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs index 4a99027a..84f2794c 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs @@ -63,7 +63,7 @@ public partial class FileRepository } - public PagedItems GetCrontabItems(CrontabItemFilter filter) + public async ValueTask> GetCrontabItems(CrontabItemFilter filter) { if (filter == null) @@ -111,7 +111,7 @@ public partial class FileRepository return new PagedItems { Items = records.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size), - Count = records.Count(), + Count = records.Count() }; } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs index b7fa26ab..ae9d0405 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs @@ -155,7 +155,7 @@ public partial class FileRepository return true; } - public PagedItems GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter) + public async ValueTask> GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter) { if (string.IsNullOrWhiteSpace(collectionName) || string.IsNullOrWhiteSpace(vectorStoreProvider)) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs index 5b8d095f..c0851401 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs @@ -160,7 +160,7 @@ namespace BotSharp.Core.Repository return true; } - public PagedItems GetInstructionLogs(InstructLogFilter filter) + public async ValueTask> GetInstructionLogs(InstructLogFilter filter) { if (filter == null) { @@ -316,7 +316,9 @@ namespace BotSharp.Core.Repository if (log == null || log.InnerStates.IsNullOrEmpty() || (!filter.UserIds.IsNullOrEmpty() && !filter.UserIds.Contains(log.UserId)) - || (!filter.AgentIds.IsNullOrEmpty() && !filter.AgentIds.Contains(log.AgentId))) + || (!filter.AgentIds.IsNullOrEmpty() && !filter.AgentIds.Contains(log.AgentId)) + || (filter.StartTime.HasValue && log.CreatedTime < filter.StartTime.Value) + || (filter.EndTime.HasValue && log.CreatedTime > filter.EndTime.Value)) { continue; } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 7bd5a1a9..09212e20 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -105,7 +105,7 @@ public partial class FileRepository File.WriteAllText(path, JsonSerializer.Serialize(user, _options)); } - public PagedItems GetUsers(UserFilter filter) + public async ValueTask> GetUsers(UserFilter filter) { if (filter == null) { diff --git a/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs b/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs index ce5d72b8..5ae52a73 100644 --- a/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs +++ b/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs @@ -39,7 +39,7 @@ public class AgentTaskService : IAgentTaskService else { var db = _services.GetRequiredService(); - var pagedTasks = db.GetAgentTasks(filter); + var pagedTasks = await db.GetAgentTasks(filter); return await Task.FromResult(pagedTasks); } } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index e4ebe311..f7dd61c6 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -411,10 +411,18 @@ public class UserService : IUserService return user; } + [SharpCache(10)] + public async Task> GetUsers(List ids) + { + var db = _services.GetRequiredService(); + var users = db.GetUserByIds(ids); + return users; + } + public async Task> GetUsers(UserFilter filter) { var db = _services.GetRequiredService(); - var users = db.GetUsers(filter); + var users = await db.GetUsers(filter); return users; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index ec7dead5..45f53c0c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -66,11 +66,11 @@ public class ConversationController : ControllerBase var agents = await agentService.GetAgentOptions(agentIds); var userIds = list.Select(x => x.User.Id).ToList(); - var users = await userService.GetUsers(new UserFilter { UserIds = userIds, Size = filter.Pager.Size }); + var users = await userService.GetUsers(userIds); foreach (var item in list) { - user = users.Items.FirstOrDefault(x => x.Id == item.User.Id); + user = users.FirstOrDefault(x => x.Id == item.User.Id); item.User = UserViewModel.FromUser(user); var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); item.AgentName = agent?.Name ?? "Unkown"; diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs index d55d5a4a..3f4853e2 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs @@ -191,7 +191,7 @@ public partial class KnowledgeService var vectorStoreProvider = _settings.VectorDb.Provider; // Get doc meta data - var pageData = db.GetKnowledgeBaseFileMeta(collectionName, vectorStoreProvider, new KnowledgeFileFilter + var pageData = await db.GetKnowledgeBaseFileMeta(collectionName, vectorStoreProvider, new KnowledgeFileFilter { Size = 1, FileIds = [ fileId ] @@ -281,7 +281,7 @@ public partial class KnowledgeService var vectorStoreProvider = _settings.VectorDb.Provider; // Get doc meta data - var pagedData = db.GetKnowledgeBaseFileMeta(collectionName, vectorStoreProvider, filter); + var pagedData = await db.GetKnowledgeBaseFileMeta(collectionName, vectorStoreProvider, filter); var files = pagedData.Items?.Select(x => new KnowledgeFileModel { @@ -308,7 +308,7 @@ public partial class KnowledgeService var vectorStoreProvider = _settings.VectorDb.Provider; // Get doc binary data - var pageData = db.GetKnowledgeBaseFileMeta(collectionName, vectorStoreProvider, new KnowledgeFileFilter + var pageData = await db.GetKnowledgeBaseFileMeta(collectionName, vectorStoreProvider, new KnowledgeFileFilter { Size = 1, FileIds = [ fileId ] diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs index db3cdfc6..85f14215 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs @@ -6,7 +6,7 @@ namespace BotSharp.Plugin.MongoStorage.Repository; public partial class MongoRepository { #region Task - public PagedItems GetAgentTasks(AgentTaskFilter filter) + public async ValueTask> GetAgentTasks(AgentTaskFilter filter) { if (filter == null) { @@ -34,13 +34,23 @@ public partial class MongoRepository var filterDef = builder.And(filters); var sortDef = Builders.Sort.Descending(x => x.CreatedTime); - var totalTasks = _dc.AgentTasks.CountDocuments(filterDef); - var taskDocs = _dc.AgentTasks.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList(); - var agentIds = taskDocs.Select(x => x.AgentId).Distinct().ToList(); + var docsTask = _dc.AgentTasks.FindAsync(filterDef, options: new() + { + Sort = sortDef, + Skip = pager.Offset, + Limit = pager.Size + }); + var countTask = _dc.AgentTasks.CountDocumentsAsync(filterDef); + await Task.WhenAll([docsTask, countTask]); + + var docs = docsTask.Result.ToList(); + var count = countTask.Result; + + var agentIds = docs.Select(x => x.AgentId).Distinct().ToList(); var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); - var tasks = taskDocs.Select(x => + var tasks = docs.Select(x => { var task = AgentTaskDocument.ToDomainModel(x); task.Agent = agents.FirstOrDefault(a => a.Id == x.AgentId); @@ -50,7 +60,7 @@ public partial class MongoRepository return new PagedItems { Items = tasks, - Count = (int)totalTasks + Count = count }; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index fb424f02..6611c352 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -336,7 +336,7 @@ public partial class MongoRepository }; } - public PagedItems GetConversations(ConversationFilter filter) + public async ValueTask> GetConversations(ConversationFilter filter) { if (filter == null) { @@ -466,10 +466,19 @@ public partial class MongoRepository } } - var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList(); - var count = _dc.Conversations.CountDocuments(filterDef); + var docsTask = _dc.Conversations.FindAsync(filterDef, options: new() + { + Sort = sortDef, + Skip = pager.Offset, + Limit = pager.Size + }); + var countTask = _dc.Conversations.CountDocumentsAsync(filterDef); + await Task.WhenAll([docsTask, countTask]); - var conversations = conversationDocs.Select(x => + var docs = docsTask.Result.ToList(); + var count = countTask.Result; + + var conversations = docs.Select(x => { var states = new Dictionary(); if (filter.IsLoadLatestStates) @@ -502,7 +511,7 @@ public partial class MongoRepository return new PagedItems { Items = conversations, - Count = (int)count + Count = count }; } @@ -693,11 +702,18 @@ public partial class MongoRepository { filters.Add(builder.In(x => x.AgentId, filter.AgentIds)); } - if (!filter.UserIds.IsNullOrEmpty()) { filters.Add(builder.In(x => x.UserId, filter.UserIds)); } + if (filter.StartTime.HasValue) + { + filters.Add(builder.Gte(x => x.CreatedTime, filter.StartTime.Value)); + } + if (filter.EndTime.HasValue) + { + filters.Add(builder.Lte(x => x.CreatedTime, filter.EndTime.Value)); + } var convDocs = _dc.Conversations.Find(builder.And(filters)) .Sort(sortDef) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Crontab.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Crontab.cs index af10c373..4c307222 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Crontab.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Crontab.cs @@ -45,7 +45,7 @@ public partial class MongoRepository } - public PagedItems GetCrontabItems(CrontabItemFilter filter) + public async ValueTask> GetCrontabItems(CrontabItemFilter filter) { if (filter == null) { @@ -73,15 +73,24 @@ public partial class MongoRepository var filterDef = cronBuilder.And(cronFilters); var sortDef = Builders.Sort.Descending(x => x.CreatedTime); - var cronDocs = _dc.CrontabItems.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList(); - var count = _dc.CrontabItems.CountDocuments(filterDef); + var docsTask = _dc.CrontabItems.FindAsync(filterDef, options: new() + { + Sort = sortDef, + Skip = filter.Offset, + Limit = filter.Size + }); + var countTask = _dc.CrontabItems.CountDocumentsAsync(filterDef); + await Task.WhenAll([docsTask, countTask]); - var crontabItems = cronDocs.Select(x => CrontabItemDocument.ToDomainModel(x)).ToList(); + var docs = docsTask.Result.ToList(); + var count = countTask.Result; + + var crontabItems = docs.Select(x => CrontabItemDocument.ToDomainModel(x)).ToList(); return new PagedItems { Items = crontabItems, - Count = (int)count + Count = count }; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.KnowledgeBase.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.KnowledgeBase.cs index e47ed1c3..f6bcba89 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.KnowledgeBase.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.KnowledgeBase.cs @@ -168,7 +168,7 @@ public partial class MongoRepository return res.DeletedCount > 0; } - public PagedItems GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter) + public async ValueTask> GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter) { if (string.IsNullOrWhiteSpace(collectionName) || string.IsNullOrWhiteSpace(vectorStoreProvider)) @@ -209,8 +209,18 @@ public partial class MongoRepository var filterDef = builder.And(docFilters); var sortDef = Builders.Sort.Descending(x => x.CreatedDate); - var docs = _dc.KnowledgeCollectionFileMeta.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList(); - var count = _dc.KnowledgeCollectionFileMeta.CountDocuments(filterDef); + + var docsTask = _dc.KnowledgeCollectionFileMeta.FindAsync(filterDef, options: new() + { + Sort = sortDef, + Skip = filter.Offset, + Limit = filter.Size + }); + var countTask = _dc.KnowledgeCollectionFileMeta.CountDocumentsAsync(filterDef); + await Task.WhenAll([docsTask, countTask]); + + var docs = docsTask.Result.ToList(); + var count = countTask.Result; var files = docs?.Select(x => new KnowledgeDocMetaData { @@ -229,7 +239,7 @@ public partial class MongoRepository return new PagedItems { Items = files, - Count = (int)count + Count = count }; } #endregion diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs index 196c6dfc..9a236277 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs @@ -169,7 +169,7 @@ public partial class MongoRepository return true; } - public PagedItems GetInstructionLogs(InstructLogFilter filter) + public async ValueTask> GetInstructionLogs(InstructLogFilter filter) { if (filter == null) { @@ -251,8 +251,18 @@ public partial class MongoRepository var filterDef = logBuilder.And(logFilters); var sortDef = Builders.Sort.Descending(x => x.CreatedTime); - var docs = _dc.InstructionLogs.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList(); - var count = _dc.InstructionLogs.CountDocuments(filterDef); + + var docsTask = _dc.InstructionLogs.FindAsync(filterDef, options: new() + { + Sort = sortDef, + Skip = filter.Offset, + Limit = filter.Size + }); + var countTask = _dc.InstructionLogs.CountDocumentsAsync(filterDef); + await Task.WhenAll([docsTask, countTask]); + + var docs = docsTask.Result.ToList(); + var count = countTask.Result; var logs = docs.Select(x => { @@ -270,7 +280,7 @@ public partial class MongoRepository return new PagedItems { Items = logs, - Count = (int)count + Count = count }; } @@ -288,11 +298,18 @@ public partial class MongoRepository { filters.Add(builder.In(x => x.AgentId, filter.AgentIds)); } - if (!filter.UserIds.IsNullOrEmpty()) { filters.Add(builder.In(x => x.UserId, filter.UserIds)); } + if (filter.StartTime.HasValue) + { + filters.Add(builder.Gte(x => x.CreatedTime, filter.StartTime.Value)); + } + if (filter.EndTime.HasValue) + { + filters.Add(builder.Lte(x => x.CreatedTime, filter.EndTime.Value)); + } var convDocs = _dc.InstructionLogs.Find(builder.And(filters)) .Sort(sortDef) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index a6f316f2..f30e52fc 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -205,7 +205,7 @@ public partial class MongoRepository } } - public PagedItems GetUsers(UserFilter filter) + public async ValueTask> GetUsers(UserFilter filter) { if (filter == null) { @@ -246,14 +246,23 @@ public partial class MongoRepository var sortDef = Builders.Sort.Descending(x => x.CreatedTime); // Search - var userDocs = _dc.Users.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList(); - var count = _dc.Users.CountDocuments(filterDef); + var docsTask = _dc.Users.FindAsync(filterDef, options: new() + { + Sort = sortDef, + Skip = filter.Offset, + Limit = filter.Size + }); + var countTask = _dc.Users.CountDocumentsAsync(filterDef); + await Task.WhenAll([docsTask, countTask]); - var users = userDocs.Select(x => x.ToUser()).ToList(); + var docs = docsTask.Result.ToList(); + var count = countTask.Result; + + var users = docs.Select(x => x.ToUser()).ToList(); return new PagedItems { Items = users, - Count = (int)count + Count = count }; } From 0c0101408b08709f62bc5bce9fd399efe90efd27 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 11 Aug 2025 21:51:33 -0500 Subject: [PATCH 45/51] add order by --- .../VectorStorage/Models/VectorFilter.cs | 9 +++++++ .../VectorStorage/Models/VectorSort.cs | 10 ++++++++ .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 24 ++++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSort.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs index 88a1aca4..43ce6e04 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorFilter.cs @@ -9,11 +9,20 @@ public class VectorFilter : StringIdPagination /// Filter group: each item contains a logical operator and a list of key-value pairs /// [JsonPropertyName("filter_groups")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IEnumerable? FilterGroups { get; set; } + /// + /// Order by a specific field + /// + [JsonPropertyName("order_by")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public VectorSort? OrderBy { get; set; } + /// /// Included payload fields /// [JsonPropertyName("fields")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IEnumerable? Fields { get; set; } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSort.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSort.cs new file mode 100644 index 00000000..469018b0 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/Models/VectorSort.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.VectorStorage.Models; + +public class VectorSort +{ + [JsonPropertyName("field")] + public string? Field { get; set; } + + [JsonPropertyName("order")] + public string? Order { get; set; } = "desc"; +} diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index f5465c32..9841ddab 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -144,6 +144,7 @@ public class QdrantDb : IVectorDb Filter? queryFilter = BuildQueryFilter(filter.FilterGroups); WithPayloadSelector? payloadSelector = BuildPayloadSelector(filter.Fields); + OrderBy? orderBy = BuildOrderBy(filter.OrderBy); var client = GetClient(); var tasks = new List(); @@ -154,6 +155,7 @@ public class QdrantDb : IVectorDb limit: (uint)filter.Size, offset: !string.IsNullOrWhiteSpace(filter.StartId) ? new PointId { Uuid = filter.StartId } : null, filter: queryFilter, + orderBy: orderBy, payloadSelector: payloadSelector, vectorsSelector: filter.WithVector); @@ -371,7 +373,13 @@ public class QdrantDb : IVectorDb var client = GetClient(); var schemaType = ConvertPayloadSchemaType(options.FieldSchemaType); - var result = await client.CreatePayloadIndexAsync(collectionName, options.FieldName, schemaType); + var result = await client.CreatePayloadIndexAsync(collectionName, options.FieldName, schemaType, indexParams: new() + { + IntegerIndexParams = new() + { + Range = true + } + }); return result.Status == UpdateStatus.Completed; } @@ -618,6 +626,20 @@ public class QdrantDb : IVectorDb return payloadSelector; } + private OrderBy? BuildOrderBy(VectorSort? sort) + { + if (string.IsNullOrWhiteSpace(sort?.Field)) + { + return null; + } + + return new OrderBy + { + Key = sort.Field, + Direction = sort.Order == "asc" ? Direction.Asc : Direction.Desc + }; + } + private PayloadSchemaType ConvertPayloadSchemaType(string schemaType) { PayloadSchemaType res; From c1fd5f3d28b87939a1e2af907533d35fab963030 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Mon, 11 Aug 2025 22:32:58 -0500 Subject: [PATCH 46/51] clean code --- src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 9841ddab..e084d0c4 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -373,13 +373,7 @@ public class QdrantDb : IVectorDb var client = GetClient(); var schemaType = ConvertPayloadSchemaType(options.FieldSchemaType); - var result = await client.CreatePayloadIndexAsync(collectionName, options.FieldName, schemaType, indexParams: new() - { - IntegerIndexParams = new() - { - Range = true - } - }); + var result = await client.CreatePayloadIndexAsync(collectionName, options.FieldName, schemaType); return result.Status == UpdateStatus.Completed; } @@ -561,7 +555,7 @@ public class QdrantDb : IVectorDb var field = new FieldCondition { Key = f.Key, - Match = new Match { Text = f.Value }, + Match = new Match { Text = f.Value } }; if (bool.TryParse(f.Value, out var boolVal)) From 48931e2f325a89f8d2926835164e4bdc4c26c62a Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 12 Aug 2025 09:28:10 -0500 Subject: [PATCH 47/51] clean code --- src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index e084d0c4..660f2d97 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -147,7 +147,6 @@ public class QdrantDb : IVectorDb OrderBy? orderBy = BuildOrderBy(filter.OrderBy); var client = GetClient(); - var tasks = new List(); var totalCountTask = client.CountAsync(collectionName, filter: queryFilter); var dataResponseTask = client.ScrollAsync( From b556040d8fb64ed53e74f4915809d5a3c32565d8 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 12 Aug 2025 10:22:19 -0500 Subject: [PATCH 48/51] add reasoning effort level --- Directory.Packages.props | 2 +- .../Agents/Constants/LlmConstant.cs | 1 + .../Agents/Models/AgentLlmConfig.cs | 7 +++++ .../Models/AgentLlmConfigMongoElement.cs | 7 +++-- .../Providers/Chat/ChatCompletionProvider.cs | 31 ++++++++++++++++++- .../BotSharp.Plugin.Qdrant/QdrantDb.cs | 2 +- 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4b450020..d5721a14 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,7 +46,7 @@ - + diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs index 3919ca57..e45c8b15 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Constants/LlmConstant.cs @@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Agents.Constants; public static class LlmConstant { public const int DEFAULT_MAX_OUTPUT_TOKEN = 1024; + public const string DEFAULT_REASONING_EFFORT_LEVEL = "low"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs index f1df3a01..75e3aa93 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs @@ -34,4 +34,11 @@ public class AgentLlmConfig [JsonPropertyName("max_output_tokens")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxOutputTokens { get; set; } + + /// + /// Reasoning effort level + /// + [JsonPropertyName("reasoning_effort_level")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? ReasoningEffortLevel { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs index 21c99e34..63bc3b20 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentLlmConfigMongoElement.cs @@ -10,6 +10,7 @@ public class AgentLlmConfigMongoElement public bool IsInherit { get; set; } public int MaxRecursionDepth { get; set; } public int? MaxOutputTokens { get; set; } + public string? ReasoningEffortLevel { get; set; } public static AgentLlmConfigMongoElement? ToMongoElement(AgentLlmConfig? config) { @@ -21,7 +22,8 @@ public class AgentLlmConfigMongoElement Model = config.Model, IsInherit = config.IsInherit, MaxRecursionDepth = config.MaxRecursionDepth, - MaxOutputTokens = config.MaxOutputTokens + MaxOutputTokens = config.MaxOutputTokens, + ReasoningEffortLevel = config.ReasoningEffortLevel }; } @@ -35,7 +37,8 @@ public class AgentLlmConfigMongoElement Model = config.Model, IsInherit = config.IsInherit, MaxRecursionDepth = config.MaxRecursionDepth, - MaxOutputTokens = config.MaxOutputTokens + MaxOutputTokens = config.MaxOutputTokens, + ReasoningEffortLevel = config.ReasoningEffortLevel }; } } diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index 9eec0c1e..c9318d79 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -504,13 +504,42 @@ public class ChatCompletionProvider : IChatCompletion ? tokens : agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN; + var level = state.GetState("reasoning_effort_level") + .IfNullOrEmptyAs(agent?.LlmConfig?.ReasoningEffortLevel ?? string.Empty) + .IfNullOrEmptyAs(LlmConstant.DEFAULT_REASONING_EFFORT_LEVEL); + var reasoningEffortLevel = ParseReasoningEffortLevel(level); + return new ChatCompletionOptions() { Temperature = temperature, - MaxOutputTokenCount = maxTokens + MaxOutputTokenCount = maxTokens, + ReasoningEffortLevel = reasoningEffortLevel }; } + private ChatReasoningEffortLevel? ParseReasoningEffortLevel(string? level) + { + if (string.IsNullOrWhiteSpace(level) || !_defaultTemperature.ContainsKey(_model)) + { + return null; + } + + var effortLevel = ChatReasoningEffortLevel.Low; + switch (level.ToLower()) + { + case "medium": + effortLevel = ChatReasoningEffortLevel.Medium; + break; + case "high": + effortLevel = ChatReasoningEffortLevel.High; + break; + default: + break; + } + + return effortLevel; + } + public void SetModelName(string model) { _model = model; diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 660f2d97..eb83ca0e 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -629,7 +629,7 @@ public class QdrantDb : IVectorDb return new OrderBy { Key = sort.Field, - Direction = sort.Order == "asc" ? Direction.Asc : Direction.Desc + Direction = sort.Order.IsEqualTo("asc") ? Direction.Asc : Direction.Desc }; } From d19a4e1aaf6212efb0eef1aa10c9a2faf7621304 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 12 Aug 2025 11:33:51 -0500 Subject: [PATCH 49/51] add reasoning settings --- Directory.Packages.props | 2 +- .../MLTasks/Settings/LlmModelSetting.cs | 18 ++++++++-- .../Providers/Chat/ChatCompletionProvider.cs | 34 ++++++++----------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d5721a14..4b450020 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,7 +46,7 @@ - + diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs index e74a7c37..23e055d2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs @@ -47,7 +47,15 @@ public class LlmModelSetting /// public int Dimension { get; set; } - public LlmCost Cost { get; set; } = new(); + /// + /// Settings for reasoning model + /// + public ReasoningSetting? Reasoning { get; set; } + + /// + /// Settings for llm cost + /// + public LlmCostSetting Cost { get; set; } = new(); public override string ToString() { @@ -55,10 +63,16 @@ public class LlmModelSetting } } +public class ReasoningSetting +{ + public float Temperature { get; set; } = 1.0f; + public string? EffortLevel { get; set; } +} + /// /// Cost per 1K tokens /// -public class LlmCost +public class LlmCostSetting { // Input public float TextInputCost { get; set; } = 0f; diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index c9318d79..99de556f 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,3 +1,4 @@ +#pragma warning disable OPENAI001 using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.MessageHub.Models; using BotSharp.Core.Infrastructures.Streams; @@ -15,16 +16,6 @@ public class ChatCompletionProvider : IChatCompletion protected string _model; private List renderedInstructions = []; - private readonly Dictionary _defaultTemperature = new() - { - { "o3", 1.0f }, - { "o3-mini", 1.0f }, - { "o4-mini", 1.0f }, - { "gpt-5", 1.0f }, - { "gpt-5-mini", 1.0f }, - { "gpt-5-nano", 1.0f } - }; - public virtual string Provider => "openai"; public string Model => _model; @@ -493,22 +484,24 @@ public class ChatCompletionProvider : IChatCompletion private ChatCompletionOptions InitChatCompletionOption(Agent agent) { var state = _services.GetRequiredService(); + var settingsService = _services.GetRequiredService(); + var settings = settingsService.GetSetting(Provider, _model); + ChatReasoningEffortLevel? reasoningEffortLevel = null; var temperature = float.Parse(state.GetState("temperature", "0.0")); - if (_defaultTemperature.ContainsKey(_model)) + if (settings?.Reasoning != null) { - temperature = _defaultTemperature[_model]; + temperature = settings.Reasoning.Temperature; + var level = state.GetState("reasoning_effort_level") + .IfNullOrEmptyAs(agent?.LlmConfig?.ReasoningEffortLevel ?? string.Empty) + .IfNullOrEmptyAs(settings?.Reasoning?.EffortLevel ?? string.Empty); + reasoningEffortLevel = ParseReasoningEffortLevel(level); } var maxTokens = int.TryParse(state.GetState("max_tokens"), out var tokens) ? tokens : agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN; - var level = state.GetState("reasoning_effort_level") - .IfNullOrEmptyAs(agent?.LlmConfig?.ReasoningEffortLevel ?? string.Empty) - .IfNullOrEmptyAs(LlmConstant.DEFAULT_REASONING_EFFORT_LEVEL); - var reasoningEffortLevel = ParseReasoningEffortLevel(level); - return new ChatCompletionOptions() { Temperature = temperature, @@ -519,14 +512,17 @@ public class ChatCompletionProvider : IChatCompletion private ChatReasoningEffortLevel? ParseReasoningEffortLevel(string? level) { - if (string.IsNullOrWhiteSpace(level) || !_defaultTemperature.ContainsKey(_model)) + if (string.IsNullOrWhiteSpace(level)) { return null; } - var effortLevel = ChatReasoningEffortLevel.Low; + var effortLevel = new ChatReasoningEffortLevel("minimal"); switch (level.ToLower()) { + case "low": + effortLevel = ChatReasoningEffortLevel.Low; + break; case "medium": effortLevel = ChatReasoningEffortLevel.Medium; break; From 4325bfe2d9e6e174ffa2229d195cb20780a0ca2e Mon Sep 17 00:00:00 2001 From: Haiping Date: Thu, 14 Aug 2025 21:24:26 +0000 Subject: [PATCH 50/51] publish cron event only if settings.EventSubscriber.Enabled --- .../BotSharp.Core.Crontab/Services/CrontabWatcher.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs index 1546cb7c..f6ce7a26 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs @@ -45,6 +45,7 @@ public class CrontabWatcher : BackgroundService { var cron = services.GetRequiredService(); var crons = await cron.GetCrontable(); + var settings = services.GetRequiredService(); var publisher = services.GetService(); foreach (var item in crons) @@ -87,7 +88,7 @@ public class CrontabWatcher : BackgroundService { _logger.LogInformation($"The current time matches the cron expression {item}"); - if (publisher != null) + if (publisher != null && settings.EventSubscriber.Enabled) { await publisher.PublishAsync($"Crontab:{item.Title}", item.Cron); } From de032683d7acb08e0d885b6d45e0b1f4719b3760 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 15 Aug 2025 11:44:44 -0500 Subject: [PATCH 51/51] Add cursor_position for OsDriver. --- Directory.Packages.props | 2 +- .../BotSharp.Plugin.OsDriver/WinOSDriver.cs | 4 ++++ .../WindowsMouseController.cs | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4b450020..b51dfc1e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -38,7 +38,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.OsDriver/WinOSDriver.cs b/src/Plugins/BotSharp.Plugin.OsDriver/WinOSDriver.cs index 45a2572d..2ed39994 100644 --- a/src/Plugins/BotSharp.Plugin.OsDriver/WinOSDriver.cs +++ b/src/Plugins/BotSharp.Plugin.OsDriver/WinOSDriver.cs @@ -29,6 +29,10 @@ public class WinOSDriver : IComputerUse case "right_click": WindowsMouseController.RightClick(); break; + case "cursor_position": + var (x, y) = WindowsMouseController.GetCurrentCursorPosition(); + Console.WriteLine($"Current cursor position: ({x}, {y})"); + break; default: throw new ToolError($"Action {action} is not supported"); } diff --git a/src/Plugins/BotSharp.Plugin.OsDriver/WindowsMouseController.cs b/src/Plugins/BotSharp.Plugin.OsDriver/WindowsMouseController.cs index c604780f..8e54d8c1 100644 --- a/src/Plugins/BotSharp.Plugin.OsDriver/WindowsMouseController.cs +++ b/src/Plugins/BotSharp.Plugin.OsDriver/WindowsMouseController.cs @@ -13,6 +13,13 @@ public class WindowsMouseController public int Left, Top, Right, Bottom; } + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int X; + public int Y; + } + // Define MONITORINFO structure [StructLayout(LayoutKind.Sequential)] public struct MONITORINFO @@ -34,6 +41,10 @@ public class WindowsMouseController [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi); + [DllImport("user32.dll")] + private static extern bool GetCursorPos(out POINT lpPoint); + + // Class to hold monitor information public class MonitorInfo { @@ -41,6 +52,13 @@ public class WindowsMouseController public RECT MonitorArea; } + // Method to get current cursor position + public static (int x, int y) GetCurrentCursorPosition() + { + GetCursorPos(out POINT cursorPos); + return (cursorPos.X, cursorPos.Y); + } + // Method to get all connected monitors public static List GetMonitors() {