From cbf0fe61205760f11b5b88961ea36a5df7bf1e7f Mon Sep 17 00:00:00 2001
From: Joanna Ren <101223@smsassist.com>
Date: Thu, 4 Sep 2025 17:59:29 -0500
Subject: [PATCH 01/20] add report_summary to PlotChart
---
.../BotSharp.Plugin.ChartHandler.csproj | 1 +
.../Functions/PlotChartFn.cs | 60 ++++++++++++++++++-
.../LlmContext/LlmContextOut.cs | 4 ++
.../util-chart-plot_instruction.liquid | 3 +-
.../Helpers/EventEmitter.cs | 4 +-
5 files changed, 68 insertions(+), 4 deletions(-)
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj
index 784b2405..a5a72560 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj
@@ -30,5 +30,6 @@
+
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 8e810683..64c44c56 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -1,4 +1,9 @@
+using BotSharp.Abstraction.Conversations.Dtos;
+using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
+using BotSharp.Abstraction.Users;
+using BotSharp.Plugin.ChatHub.Helpers;
+using System.Runtime.CompilerServices;
namespace BotSharp.Plugin.ChartHandler.Functions;
@@ -6,6 +11,7 @@ public class PlotChartFn : IFunctionCallback
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
+ private readonly BotSharpOptions _options;
public string Name => "util-chart-plot_chart";
public string Indication => "Plotting chart";
@@ -13,10 +19,12 @@ public class PlotChartFn : IFunctionCallback
public PlotChartFn(
IServiceProvider services,
- ILogger logger)
+ ILogger logger,
+ BotSharpOptions options)
{
_services = services;
_logger = logger;
+ _options = options;
}
public async Task Execute(RoleDialogModel message)
@@ -63,10 +71,60 @@ public class PlotChartFn : IFunctionCallback
Language = "javascript"
}
};
+
+ // Send report summary after 1.5 seconds if exists
+ if (!string.IsNullOrEmpty(obj?.ReportSummary))
+ {
+ _ = Task.Run(async () =>
+ {
+ var services = _services.CreateScope().ServiceProvider;
+ await Task.Delay(1500);
+ await SendDelayedMessage(services, obj.ReportSummary, convService.ConversationId, agent.Id, agent.Name);
+ });
+ }
+
message.StopCompletion = true;
return true;
}
+ private async Task SendDelayedMessage(IServiceProvider services, string text, string conversationId, string agentId, string agentName)
+ {
+ try
+ {
+ var messageId = Guid.NewGuid().ToString();
+ var messageData = new ChatResponseDto
+ {
+ ConversationId = conversationId,
+ MessageId = messageId,
+ Text = text,
+ Sender = new() { FirstName = agentName, LastName = "", Role = AgentRole.Assistant }
+ };
+
+ var dialogModel = new RoleDialogModel(AgentRole.Assistant, text)
+ {
+ MessageId = messageId,
+ CurrentAgentId = agentId,
+ CreatedAt = DateTime.UtcNow
+ };
+
+ var storage = services.GetService();
+ storage?.Append(conversationId, dialogModel);
+ await SendEvent(services, ChatEvent.OnMessageReceivedFromAssistant, conversationId, messageData);
+
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to send delayed message");
+ }
+ }
+
+ private async Task SendEvent(IServiceProvider services, string @event, string conversationId, T data, [CallerMemberName] string callerName = "")
+ {
+ var user = services.GetService();
+ var json = JsonSerializer.Serialize(data, _options.JsonSerializerOptions);
+ await EventEmitter.SendChatEvent(services, _logger, @event, conversationId, user?.Id, json, nameof(PlotChartFn), callerName);
+ }
+
private async Task GetChatCompletion(Agent agent, List dialogs)
{
try
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs
index 7109f72d..863ad44e 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/LlmContext/LlmContextOut.cs
@@ -8,6 +8,10 @@ public class LlmContextOut
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? GreetingMessage { get; set; }
+ [JsonPropertyName("report_summary")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? ReportSummary { 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 0d631b51..84a7b8ac 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
@@ -27,5 +27,6 @@ Please take a look at "Plotting Requirement" and generate a javascript code that
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 that can generate the charts as requested."
+ "js_code": "The javascript code that can generate the charts as requested.",
+ "report_summary": "An insight summary report based on the data, highlighting the key information in markdown format."
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs
index 821b173e..7c2b4077 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs
@@ -3,9 +3,9 @@ using System.Runtime.CompilerServices;
namespace BotSharp.Plugin.ChatHub.Helpers;
-internal class EventEmitter
+public class EventEmitter
{
- internal static async Task SendChatEvent(
+ public static async Task SendChatEvent(
IServiceProvider services,
ILogger logger,
string @event,
From 8778a5f8ed54b6cae8e5cd81044e30ef19c30ea7 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Fri, 5 Sep 2025 01:17:18 -0500
Subject: [PATCH 02/20] 1. refine chart handler instruction 2. add mechanism to
allow sending multiple messages in a single round
---
.../Conversations/Dtos/ChatResponseDto.cs | 3 +
.../Conversations/Models/RoleDialogModel.cs | 17 ++-
.../ConversationService.SendMessage.cs | 1 -
.../Services/ConversationStorage.cs | 126 ++++++++++--------
.../Routing/RoutingService.InvokeFunction.cs | 1 +
.../BotSharp.Plugin.ChartHandler.csproj | 1 -
.../Functions/PlotChartFn.cs | 79 ++++-------
.../Settings/ChartHandlerSettings.cs | 5 +-
.../util-chart-plot_instruction.liquid | 32 +++--
.../Helpers/EventEmitter.cs | 4 +-
.../Hooks/ChatHubConversationHook.cs | 38 +++++-
11 files changed, 181 insertions(+), 126 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs
index 9e4fcb41..030bae23 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs
@@ -42,6 +42,9 @@ public class ChatResponseDto : InstructResult
[JsonPropertyName("is_streaming")]
public bool IsStreaming { get; set; }
+ [JsonPropertyName("is_append")]
+ public bool IsAppend { get; set; }
+
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
index f4e75425..e518ae8b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
@@ -127,6 +127,13 @@ public class RoleDialogModel : ITrackableMessage
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public bool IsStreaming { get; set; }
+ ///
+ /// Additional messages that can be sent sequentially and save to db
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ public ChatMessageWrapper? AdditionalMessageWrapper { get; set; }
+
+
public RoleDialogModel()
{
}
@@ -171,7 +178,15 @@ public class RoleDialogModel : ITrackableMessage
Instruction = source.Instruction,
Data = source.Data,
IsStreaming = source.IsStreaming,
- Annotations = source.Annotations
+ Annotations = source.Annotations,
+ AdditionalMessageWrapper = source.AdditionalMessageWrapper
};
}
}
+
+public class ChatMessageWrapper
+{
+ public int IntervalMilliSeconds { get; set; } = 1000;
+ public bool SaveToDb { get; set; }
+ public List? Messages { get; set; }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index 1155dc38..9eb5d1f8 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -1,4 +1,3 @@
-using BotSharp.Abstraction.Hooks;
using BotSharp.Abstraction.Infrastructures.Enums;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Messaging.Models.RichContent;
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
index eeceeb09..5c4bff47 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Messaging.Models.RichContent;
using BotSharp.Abstraction.Options;
@@ -31,63 +32,21 @@ public class ConversationStorage : IConversationStorage
foreach ( var dialog in dialogs)
{
- if (dialog.Role == AgentRole.Function)
+ var innerList = new List { dialog };
+ if (dialog.AdditionalMessageWrapper != null
+ && dialog.AdditionalMessageWrapper.SaveToDb
+ && dialog.AdditionalMessageWrapper.Messages?.Count > 0)
{
- var meta = new DialogMetaData
- {
- Role = dialog.Role,
- AgentId = dialog.CurrentAgentId,
- MessageId = dialog.MessageId,
- MessageType = dialog.MessageType,
- FunctionName = dialog.FunctionName,
- FunctionArgs = dialog.FunctionArgs,
- ToolCallId = dialog.ToolCallId,
- CreatedTime = dialog.CreatedAt
- };
-
- var content = dialog.Content.RemoveNewLine();
- if (string.IsNullOrEmpty(content))
- {
- continue;
- }
- dialogElements.Add(new DialogElement
- {
- MetaData = meta,
- Content = dialog.Content,
- SecondaryContent = dialog.SecondaryContent,
- Payload = dialog.Payload
- });
+ innerList.AddRange(dialog.AdditionalMessageWrapper.Messages);
}
- else
+
+ foreach (var item in innerList)
{
- var meta = new DialogMetaData
+ var element = BuildDialogElement(item);
+ if (element != null)
{
- Role = dialog.Role,
- AgentId = dialog.CurrentAgentId,
- MessageId = dialog.MessageId,
- MessageType = dialog.MessageType,
- SenderId = dialog.SenderId,
- FunctionName = dialog.FunctionName,
- CreatedTime = dialog.CreatedAt
- };
-
- var content = dialog.Content.RemoveNewLine();
- if (string.IsNullOrEmpty(content))
- {
- continue;
+ dialogElements.Add(element);
}
-
- var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _options.JsonSerializerOptions) : null;
- var secondaryRichContent = dialog.SecondaryRichContent != null ? JsonSerializer.Serialize(dialog.SecondaryRichContent, _options.JsonSerializerOptions) : null;
- dialogElements.Add(new DialogElement
- {
- MetaData = meta,
- Content = dialog.Content,
- SecondaryContent = dialog.SecondaryContent,
- RichContent = richContent,
- SecondaryRichContent = secondaryRichContent,
- Payload = dialog.Payload
- });
}
}
@@ -148,4 +107,67 @@ public class ConversationStorage : IConversationStorage
return results;
}
+
+ private DialogElement? BuildDialogElement(RoleDialogModel dialog)
+ {
+ DialogElement? element = null;
+
+ if (dialog.Role == AgentRole.Function)
+ {
+ var meta = new DialogMetaData
+ {
+ Role = dialog.Role,
+ AgentId = dialog.CurrentAgentId,
+ MessageId = dialog.MessageId,
+ MessageType = dialog.MessageType,
+ FunctionName = dialog.FunctionName,
+ FunctionArgs = dialog.FunctionArgs,
+ ToolCallId = dialog.ToolCallId,
+ CreatedTime = dialog.CreatedAt
+ };
+
+ var content = dialog.Content.RemoveNewLine();
+ if (!string.IsNullOrEmpty(content))
+ {
+ element = new DialogElement
+ {
+ MetaData = meta,
+ Content = dialog.Content,
+ SecondaryContent = dialog.SecondaryContent,
+ Payload = dialog.Payload
+ };
+ }
+ }
+ else
+ {
+ var meta = new DialogMetaData
+ {
+ Role = dialog.Role,
+ AgentId = dialog.CurrentAgentId,
+ MessageId = dialog.MessageId,
+ MessageType = dialog.MessageType,
+ SenderId = dialog.SenderId,
+ FunctionName = dialog.FunctionName,
+ CreatedTime = dialog.CreatedAt
+ };
+
+ var content = dialog.Content.RemoveNewLine();
+ if (!string.IsNullOrEmpty(content))
+ {
+ var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _options.JsonSerializerOptions) : null;
+ var secondaryRichContent = dialog.SecondaryRichContent != null ? JsonSerializer.Serialize(dialog.SecondaryRichContent, _options.JsonSerializerOptions) : null;
+ element = new DialogElement
+ {
+ MetaData = meta,
+ Content = dialog.Content,
+ SecondaryContent = dialog.SecondaryContent,
+ RichContent = richContent,
+ SecondaryRichContent = secondaryRichContent,
+ Payload = dialog.Payload
+ };
+ }
+ }
+
+ return element;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs
index dea0947d..45b65c7e 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs
@@ -65,6 +65,7 @@ public partial class RoutingService
message.StopCompletion = clonedMessage.StopCompletion;
message.RichContent = clonedMessage.RichContent;
message.Data = clonedMessage.Data;
+ message.AdditionalMessageWrapper = clonedMessage.AdditionalMessageWrapper;
}
catch (JsonException ex)
{
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj
index a5a72560..784b2405 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/BotSharp.Plugin.ChartHandler.csproj
@@ -30,6 +30,5 @@
-
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 64c44c56..4a3a88c5 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -1,9 +1,4 @@
-using BotSharp.Abstraction.Conversations.Dtos;
-using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
-using BotSharp.Abstraction.Users;
-using BotSharp.Plugin.ChatHub.Helpers;
-using System.Runtime.CompilerServices;
namespace BotSharp.Plugin.ChartHandler.Functions;
@@ -11,6 +6,7 @@ public class PlotChartFn : IFunctionCallback
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
+ private readonly ChartHandlerSettings _settings;
private readonly BotSharpOptions _options;
public string Name => "util-chart-plot_chart";
@@ -20,10 +16,12 @@ public class PlotChartFn : IFunctionCallback
public PlotChartFn(
IServiceProvider services,
ILogger logger,
+ ChartHandlerSettings settings,
BotSharpOptions options)
{
_services = services;
_logger = logger;
+ _settings = settings;
_options = options;
}
@@ -43,7 +41,7 @@ public class PlotChartFn : IFunctionCallback
Instruction = inst,
LlmConfig = new AgentLlmConfig
{
- MaxOutputTokens = 8192
+ MaxOutputTokens = _settings?.ChartPlot?.MaxOutputTokens ?? 8192
},
TemplateDict = new Dictionary
{
@@ -52,7 +50,8 @@ public class PlotChartFn : IFunctionCallback
}
};
- var response = await GetChatCompletion(innerAgent, [
+ var response = await GetChatCompletion(innerAgent,
+ [
new RoleDialogModel(AgentRole.User, "Please follow the instruction to generate the javascript code.")
{
CurrentAgentId = message.CurrentAgentId,
@@ -72,59 +71,32 @@ public class PlotChartFn : IFunctionCallback
}
};
- // Send report summary after 1.5 seconds if exists
if (!string.IsNullOrEmpty(obj?.ReportSummary))
{
- _ = Task.Run(async () =>
+ message.AdditionalMessageWrapper = new()
{
- var services = _services.CreateScope().ServiceProvider;
- await Task.Delay(1500);
- await SendDelayedMessage(services, obj.ReportSummary, convService.ConversationId, agent.Id, agent.Name);
- });
+ IntervalMilliSeconds = 1500,
+ SaveToDb = true,
+ Messages = new List
+ {
+ new()
+ {
+ Role = AgentRole.Assistant,
+ MessageId = message.MessageId,
+ CurrentAgentId = message.CurrentAgentId,
+ Content = obj.ReportSummary,
+ FunctionName = message.FunctionName,
+ FunctionArgs = message.FunctionArgs,
+ CreatedAt = DateTime.UtcNow
+ }
+ }
+ };
}
message.StopCompletion = true;
return true;
}
- private async Task SendDelayedMessage(IServiceProvider services, string text, string conversationId, string agentId, string agentName)
- {
- try
- {
- var messageId = Guid.NewGuid().ToString();
- var messageData = new ChatResponseDto
- {
- ConversationId = conversationId,
- MessageId = messageId,
- Text = text,
- Sender = new() { FirstName = agentName, LastName = "", Role = AgentRole.Assistant }
- };
-
- var dialogModel = new RoleDialogModel(AgentRole.Assistant, text)
- {
- MessageId = messageId,
- CurrentAgentId = agentId,
- CreatedAt = DateTime.UtcNow
- };
-
- var storage = services.GetService();
- storage?.Append(conversationId, dialogModel);
- await SendEvent(services, ChatEvent.OnMessageReceivedFromAssistant, conversationId, messageData);
-
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Failed to send delayed message");
- }
- }
-
- private async Task SendEvent(IServiceProvider services, string @event, string conversationId, T data, [CallerMemberName] string callerName = "")
- {
- var user = services.GetService();
- var json = JsonSerializer.Serialize(data, _options.JsonSerializerOptions);
- await EventEmitter.SendChatEvent(services, _logger, @event, conversationId, user?.Id, json, nameof(PlotChartFn), callerName);
- }
-
private async Task GetChatCompletion(Agent agent, List dialogs)
{
try
@@ -169,12 +141,11 @@ public class PlotChartFn : IFunctionCallback
var model = "gpt-5";
var state = _services.GetRequiredService();
- var settings = _services.GetRequiredService();
provider = state.GetState("chart_plot_llm_provider")
- .IfNullOrEmptyAs(settings.ChartPlot?.LlmProvider)
+ .IfNullOrEmptyAs(_settings.ChartPlot?.LlmProvider)
.IfNullOrEmptyAs(provider);
model = state.GetState("chart_plot_llm_model")
- .IfNullOrEmptyAs(settings.ChartPlot?.LlmModel)
+ .IfNullOrEmptyAs(_settings.ChartPlot?.LlmModel)
.IfNullOrEmptyAs(model);
return (provider, model);
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
index 61b0a6be..4e031621 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
@@ -7,6 +7,7 @@ public class ChartHandlerSettings
public class ChartPlotSetting
{
- public string LlmProvider { get; set; }
- public string LlmModel { get; set; }
+ public string? LlmProvider { get; set; }
+ public string? LlmModel { get; set; }
+ public int? MaxOutputTokens { 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 84a7b8ac..7a37bab9 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,29 +1,39 @@
Please take a look at "Plotting Requirement" and generate a javascript code that can be used to render the charts on an html element.
+You must strictly follow the "Hard Requirements", "Render Requirements", "Code Requirements" and "Response Format" below.
=== Plotting Requirement ===
{{ plotting_requirement }}
+
***** Hard Requirements *****
** Your output javascript code must be wrapped in one or multiple blocks with everything needed inside.
-** You need to import ECharts.js to plot the charts. The script source is "https://cdnjs.cloudflare.com/ajax/libs/echarts/6.0.0/echarts.min.js".
-** You need to add the MODE bar for each chart you plot.
-** You must render the charts under the div html element with id {{ chart_element_id }}.
-** Add a custom mode bar button named "Fullscreen" that toggles the chart container in and out of fullscreen using the Fullscreen API. Requirements for this button:
- * Always call the Fullscreen API on the chart container div itself (document.getElementById("{{ chart_element_id }}")), not on the document or on Plotly’s SVG.
+** You need to import ECharts.js exactly once to plot the charts. The script source is "https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.1/echarts.min.js".
+** You must add an ECharts Toolbox bar at the top left corner of the chart.
+** ALWAYS add a "Full screen" button right next to the Toolbox bar.
+** The "Full screen" button can toggle the chart container in and out of fullscreen using the Fullscreen API. Requirements for this button:
+ * Always call the Fullscreen API on the chart container div itself (document.getElementById("{{ chart_element_id }}")), not on the document.
* Use el.requestFullscreen() with fallbacks to el.webkitRequestFullscreen || el.msRequestFullscreen.
* Exit fullscreen with document.exitFullscreen() and vendor fallbacks.
* Listen for fullscreenchange, webkitfullscreenchange, and msfullscreenchange to keep the button working across repeated clicks and ESC exits.
* Ensure the chart fully expands and scales to the entire screen when fullscreen is active.
- * Provide a simple inline SVG path icon for the button (no external assets).
- * Use Plotly.newPlot(container, data, layout, {displayModeBar:true, modeBarButtonsToAdd:[fullscreenBtn]});
- * fullscreenBtn must be a fully-formed object {name, title, icon, click}.
+ * fullscreenBtn must be a fully-formed object {show: true, name, title, icon: 'path://M3 3 H9 V5 H5 V9 H3 Z M15 3 H21 V9 H19 V5 H15 Z M3 15 H5 V19 H9 V21 H3 Z M19 15 H21 V21 H15 V19 H19 Z', onclick}.
+ * When using "chart.setOption" to define the fullscreen button, DO NOT use "graphic". Include the fullscreenBtn object in toolbox.feature with name 'myFullscreen'.
+
+
+***** Render Requirements *****
+** You must render the charts under the div html element with id {{ chart_element_id }}.
** You must not create any new html element.
+** You must ensure the generated charts have visible height (at least 500px) and width (at least 800px). DO NOT generate charts with zero height or zero width.
** You must not apply any styles on any html element.
-** Keep code compact (few tokens), but fix all errors before returning.
-** Do not generate charts with zero height and zero width.
+
+
+***** Code Requirements *****
+** You must strictly follow the valid javascript and ECharts.js syntax.
+** You must ensure no syntax/runtime error before returning the response.
** Please ensure the code can be executed and the charts are rendered correctly.
-*** Response Format ***
+
+***** 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.",
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.cs
index 7c2b4077..821b173e 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Helpers/EventEmitter.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 EventEmitter
+internal class EventEmitter
{
- public static async Task SendChatEvent(
+ internal static async Task SendChatEvent(
IServiceProvider services,
ILogger logger,
string @event,
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
index 3831ecd7..f0429dce 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
@@ -5,6 +5,7 @@ using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.SideCar;
using BotSharp.Abstraction.Users.Dtos;
using Microsoft.AspNetCore.SignalR;
+using System;
using System.Runtime.CompilerServices;
namespace BotSharp.Plugin.ChatHub.Hooks;
@@ -112,15 +113,48 @@ public class ChatHubConversationHook : ConversationHookBase
}
};
- // Send typing-off to client
+ // Send type-off to client
var action = new ConversationSenderActionModel
{
ConversationId = conv.ConversationId,
SenderAction = SenderActionEnum.TypingOff
};
await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action);
-
await SendEvent(ChatEvent.OnMessageReceivedFromAssistant, conv.ConversationId, data);
+
+ if (message.AdditionalMessageWrapper?.Messages?.Count > 0)
+ {
+ action.SenderAction = SenderActionEnum.TypingOn;
+ await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action);
+
+ foreach (var item in message.AdditionalMessageWrapper.Messages)
+ {
+ await Task.Delay(message.AdditionalMessageWrapper.IntervalMilliSeconds);
+
+ data = new ChatResponseDto
+ {
+ ConversationId = conv.ConversationId,
+ MessageId = item.MessageId,
+ Text = !string.IsNullOrEmpty(item.SecondaryContent) ? item.SecondaryContent : item.Content,
+ Function = item.FunctionName,
+ RichContent = item.SecondaryRichContent ?? item.RichContent,
+ Data = item.Data,
+ States = state.GetStates(),
+ IsAppend = true,
+ Sender = new()
+ {
+ FirstName = "AI",
+ LastName = "Assistant",
+ Role = AgentRole.Assistant
+ }
+ };
+ await SendEvent(ChatEvent.OnMessageReceivedFromAssistant, conv.ConversationId, data);
+ }
+
+ action.SenderAction = SenderActionEnum.TypingOff;
+ await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action);
+ }
+
await base.OnResponseGenerated(message);
}
From 462eaf03fb081a32b5a97b7bccdb64f4ff37bf02 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Fri, 5 Sep 2025 01:20:32 -0500
Subject: [PATCH 03/20] clean code
---
.../BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 4a3a88c5..29fef88e 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -7,7 +7,6 @@ public class PlotChartFn : IFunctionCallback
private readonly IServiceProvider _services;
private readonly ILogger _logger;
private readonly ChartHandlerSettings _settings;
- private readonly BotSharpOptions _options;
public string Name => "util-chart-plot_chart";
public string Indication => "Plotting chart";
@@ -16,13 +15,11 @@ public class PlotChartFn : IFunctionCallback
public PlotChartFn(
IServiceProvider services,
ILogger logger,
- ChartHandlerSettings settings,
- BotSharpOptions options)
+ ChartHandlerSettings settings)
{
_services = services;
_logger = logger;
_settings = settings;
- _options = options;
}
public async Task Execute(RoleDialogModel message)
From 4bade23f31cb964f251df0551b1e4fc1fa59f03f Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Fri, 5 Sep 2025 01:49:14 -0500
Subject: [PATCH 04/20] refine naming
---
.../Conversations/Models/RoleDialogModel.cs | 13 ++++++++++++-
.../Functions/PlotChartFn.cs | 2 +-
.../Hooks/ChatHubConversationHook.cs | 7 ++++---
3 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
index e518ae8b..5794e332 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
@@ -186,7 +186,18 @@ public class RoleDialogModel : ITrackableMessage
public class ChatMessageWrapper
{
- public int IntervalMilliSeconds { get; set; } = 1000;
+ ///
+ /// Messages sending interval in milliseconds
+ ///
+ public int SendingInterval { get; set; }
+
+ ///
+ /// Whether the Messages are saved to db
+ ///
public bool SaveToDb { get; set; }
+
+ ///
+ /// Messages to send or save
+ ///
public List? Messages { get; set; }
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 29fef88e..b51da11c 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -72,7 +72,7 @@ public class PlotChartFn : IFunctionCallback
{
message.AdditionalMessageWrapper = new()
{
- IntervalMilliSeconds = 1500,
+ SendingInterval = 1500,
SaveToDb = true,
Messages = new List
{
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
index f0429dce..b8188d0f 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
@@ -122,14 +122,15 @@ public class ChatHubConversationHook : ConversationHookBase
await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action);
await SendEvent(ChatEvent.OnMessageReceivedFromAssistant, conv.ConversationId, data);
- if (message.AdditionalMessageWrapper?.Messages?.Count > 0)
+ var wrapper = message.AdditionalMessageWrapper;
+ if (wrapper?.SendingInterval > 0 && wrapper?.Messages?.Count > 0)
{
action.SenderAction = SenderActionEnum.TypingOn;
await SendEvent(ChatEvent.OnSenderActionGenerated, conv.ConversationId, action);
- foreach (var item in message.AdditionalMessageWrapper.Messages)
+ foreach (var item in wrapper.Messages)
{
- await Task.Delay(message.AdditionalMessageWrapper.IntervalMilliSeconds);
+ await Task.Delay(wrapper.SendingInterval);
data = new ChatResponseDto
{
From 9b30898f06fb9fb3c4933cf5aa76f538b4ff73bb Mon Sep 17 00:00:00 2001
From: Jicheng Lu <103353@smsassist.com>
Date: Fri, 5 Sep 2025 14:51:26 -0500
Subject: [PATCH 05/20] add chart data and code endpoints
---
.../Chart/IBotSharpChartService.cs | 14 ++
.../Chart/Models/ChartCodeOptions.cs | 25 +++
.../Chart/Models/ChartCodeResult.cs | 7 +
.../Chart/Models/ChartDataOptions.cs | 9 +
.../Chart/Models/ChartDataResult.cs | 6 +
.../Controllers/ConversationController.cs | 30 ++++
src/Infrastructure/BotSharp.OpenAPI/Using.cs | 1 +
.../Request/ConversationChartDataRequest.cs | 17 ++
.../Response/ConversationChartDataResponse.cs | 40 +++++
.../util-chart-plot_instruction.liquid | 5 +-
.../BotSharp.Plugin.SqlDriver.csproj | 2 +-
.../LlmContext/ChartLlmContextOut.cs | 18 ++
.../Services/SqlChartService.cs | 155 ++++++++++++++++++
.../SqlDriverPlugin.cs | 1 +
.../BotSharp.Plugin.SqlDriver/Using.cs | 2 +
src/WebStarter/appsettings.json | 1 +
16 files changed, 330 insertions(+), 3 deletions(-)
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Chart/IBotSharpChartService.cs
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeOptions.cs
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeResult.cs
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataOptions.cs
create mode 100644 src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataResult.cs
create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Request/ConversationChartDataRequest.cs
create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs
create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/LlmContext/ChartLlmContextOut.cs
create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs
diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/IBotSharpChartService.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/IBotSharpChartService.cs
new file mode 100644
index 00000000..1a433e6f
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Chart/IBotSharpChartService.cs
@@ -0,0 +1,14 @@
+using BotSharp.Abstraction.Chart.Models;
+
+namespace BotSharp.Abstraction.Chart;
+
+public interface IBotSharpChartService
+{
+ public string Provider { get; }
+
+ Task GetConversationChartData(string conversationId, string messageId, ChartDataOptions options)
+ => throw new NotImplementedException();
+
+ Task GetConversationChartCode(string conversationId, string messageId, ChartCodeOptions options)
+ => throw new NotImplementedException();
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeOptions.cs
new file mode 100644
index 00000000..beac046d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeOptions.cs
@@ -0,0 +1,25 @@
+namespace BotSharp.Abstraction.Chart.Models;
+
+public class ChartCodeOptions
+{
+ public string? AgentId { get; set; }
+ public string? TemplateName { get; set; }
+ public string Text { get; set; } = string.Empty;
+
+ ///
+ /// Conversation state that can be used to fetch chart data
+ ///
+ public string? TargetStateName { get; set; }
+
+ public ChartLlmOptions? Llm { get; set; }
+ public List>? States { get; set; }
+
+}
+
+public class ChartLlmOptions
+{
+ public string? Provider { get; set; }
+ public string? Model { get; set; }
+ public int? MaxOutputTokens { get; set; }
+ public string? ReasoningEffortLevel { get; set; }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeResult.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeResult.cs
new file mode 100644
index 00000000..143ba218
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartCodeResult.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Chart.Models;
+
+public class ChartCodeResult
+{
+ public string Code { get; set; }
+ public string Language { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataOptions.cs
new file mode 100644
index 00000000..249ec080
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataOptions.cs
@@ -0,0 +1,9 @@
+namespace BotSharp.Abstraction.Chart.Models;
+
+public class ChartDataOptions
+{
+ ///
+ /// Conversation state that can be used to fetch chart data
+ ///
+ public string? TargetStateName { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataResult.cs b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataResult.cs
new file mode 100644
index 00000000..28292d5a
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Chart/Models/ChartDataResult.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Abstraction.Chart.Models;
+
+public class ChartDataResult
+{
+ public object Data { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
index 45f53c0c..2f127b10 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Chart;
using BotSharp.Abstraction.Files.Constants;
using BotSharp.Abstraction.Files.Enums;
using BotSharp.Abstraction.Files.Utilities;
@@ -530,6 +531,35 @@ public class ConversationController : ControllerBase
}
#endregion
+ #region Chart
+ [AllowAnonymous]
+ [HttpGet("/conversation/{conversationId}/message/{messageId}/user/chart/data")]
+ public async Task GetConversationChartData(
+ [FromRoute] string conversationId,
+ [FromRoute] string messageId,
+ [FromQuery] ConversationChartDataRequest request)
+ {
+ var chart = _services.GetServices().FirstOrDefault(x => x.Provider == request?.ChartProvider);
+ if (chart == null) return null;
+
+ var result = await chart.GetConversationChartData(conversationId, messageId, request);
+ return ConversationChartDataResponse.From(result);
+ }
+
+ [HttpPost("/conversation/{conversationId}/message/{messageId}/user/chart/code")]
+ public async Task GetConversationChartCode(
+ [FromRoute] string conversationId,
+ [FromRoute] string messageId,
+ [FromBody] ConversationChartCodeRequest request)
+ {
+ var chart = _services.GetServices().FirstOrDefault(x => x.Provider == request?.ChartProvider);
+ if (chart == null) return null;
+
+ var result = await chart.GetConversationChartCode(conversationId, messageId, request);
+ return ConversationChartCodeResponse.From(result);
+ }
+ #endregion
+
#region Dashboard
[HttpPut("/agent/{agentId}/conversation/{conversationId}/dashboard")]
public async Task PinConversationToDashboard([FromRoute] string agentId, [FromRoute] string conversationId)
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Using.cs b/src/Infrastructure/BotSharp.OpenAPI/Using.cs
index 602f4437..ea5f9f7c 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Using.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Using.cs
@@ -28,6 +28,7 @@ global using BotSharp.Abstraction.Files.Models;
global using BotSharp.Abstraction.Files;
global using BotSharp.Abstraction.VectorStorage.Enums;
global using BotSharp.Abstraction.Knowledges.Models;
+global using BotSharp.Abstraction.Chart.Models;
global using BotSharp.OpenAPI.ViewModels.Conversations;
global using BotSharp.OpenAPI.ViewModels.Users;
global using BotSharp.OpenAPI.ViewModels.Agents;
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Request/ConversationChartDataRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Request/ConversationChartDataRequest.cs
new file mode 100644
index 00000000..19fcd14e
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Request/ConversationChartDataRequest.cs
@@ -0,0 +1,17 @@
+namespace BotSharp.OpenAPI.ViewModels.Conversations;
+
+public class ConversationChartDataRequest : ChartDataOptions
+{
+ ///
+ /// Chart service provider
+ ///
+ public string ChartProvider { get; set; } = "Botsharp";
+}
+
+public class ConversationChartCodeRequest : ChartCodeOptions
+{
+ ///
+ /// Chart service provider
+ ///
+ public string ChartProvider { get; set; } = "Botsharp";
+}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs
new file mode 100644
index 00000000..197c1174
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ConversationChartDataResponse.cs
@@ -0,0 +1,40 @@
+namespace BotSharp.OpenAPI.ViewModels.Conversations;
+
+public class ConversationChartDataResponse
+{
+ public object Data { get; set; }
+
+ public static ConversationChartDataResponse? From(ChartDataResult? result)
+ {
+ if (result == null)
+ {
+ return null;
+ }
+
+ return new()
+ {
+ Data = result.Data
+ };
+ }
+}
+
+
+public class ConversationChartCodeResponse
+{
+ public string Code { get; set; }
+ public string Language { get; set; }
+
+ public static ConversationChartCodeResponse? From(ChartCodeResult? result)
+ {
+ if (result == null)
+ {
+ return null;
+ }
+
+ return new()
+ {
+ Code = result.Code,
+ Language = result.Language
+ };
+ }
+}
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 7a37bab9..7721493f 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
@@ -7,23 +7,24 @@ You must strictly follow the "Hard Requirements", "Render Requirements", "Code R
***** Hard Requirements *****
** Your output javascript code must be wrapped in one or multiple blocks with everything needed inside.
-** You need to import ECharts.js exactly once to plot the charts. The script source is "https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.1/echarts.min.js".
+** You need to import ECharts.js exactly once to plot the charts. The script source is "https://cdnjs.cloudflare.com/ajax/libs/echarts/6.0.0/echarts.min.js".
** You must add an ECharts Toolbox bar at the top left corner of the chart.
** ALWAYS add a "Full screen" button right next to the Toolbox bar.
** The "Full screen" button can toggle the chart container in and out of fullscreen using the Fullscreen API. Requirements for this button:
* Always call the Fullscreen API on the chart container div itself (document.getElementById("{{ chart_element_id }}")), not on the document.
+ * When entering fullscreen, the chart container must have widhth: 100vw and height: 100vh.
* Use el.requestFullscreen() with fallbacks to el.webkitRequestFullscreen || el.msRequestFullscreen.
* Exit fullscreen with document.exitFullscreen() and vendor fallbacks.
* Listen for fullscreenchange, webkitfullscreenchange, and msfullscreenchange to keep the button working across repeated clicks and ESC exits.
* Ensure the chart fully expands and scales to the entire screen when fullscreen is active.
* fullscreenBtn must be a fully-formed object {show: true, name, title, icon: 'path://M3 3 H9 V5 H5 V9 H3 Z M15 3 H21 V9 H19 V5 H15 Z M3 15 H5 V19 H9 V21 H3 Z M19 15 H21 V21 H15 V19 H19 Z', onclick}.
* When using "chart.setOption" to define the fullscreen button, DO NOT use "graphic". Include the fullscreenBtn object in toolbox.feature with name 'myFullscreen'.
+** Initialize the chart with explicit non-zero width (at least 800px) and non-zero height (at least 500px).
***** Render Requirements *****
** You must render the charts under the div html element with id {{ chart_element_id }}.
** You must not create any new html element.
-** You must ensure the generated charts have visible height (at least 500px) and width (at least 800px). DO NOT generate charts with zero height or zero width.
** You must not apply any styles on any html element.
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
index 2e7b802d..bda50ed0 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
@@ -1,4 +1,4 @@
-
+
$(TargetFramework)
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/LlmContext/ChartLlmContextOut.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/LlmContext/ChartLlmContextOut.cs
new file mode 100644
index 00000000..a1c73f05
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/LlmContext/ChartLlmContextOut.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace BotSharp.Plugin.SqlDriver.LlmContext;
+
+internal class ChartLlmContextOut
+{
+ [JsonPropertyName("greeting_message")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? GreetingMessage { get; set; }
+
+ [JsonPropertyName("report_summary")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? ReportSummary { get; set; }
+
+ [JsonPropertyName("js_code")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? JsCode { get; set; }
+}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs
new file mode 100644
index 00000000..7e091bc3
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlChartService.cs
@@ -0,0 +1,155 @@
+using BotSharp.Abstraction.Options;
+using BotSharp.Abstraction.Repositories;
+using BotSharp.Core.Infrastructures;
+using BotSharp.Plugin.SqlDriver.LlmContext;
+
+namespace BotSharp.Plugin.SqlDriver.Services;
+
+public class SqlChartService : IBotSharpChartService
+{
+ private readonly IServiceProvider _services;
+ private readonly ILogger _logger;
+ private readonly BotSharpOptions _botSharpOptions;
+
+ public SqlChartService(
+ IServiceProvider services,
+ ILogger logger,
+ BotSharpOptions botSharpOptions)
+ {
+ _services = services;
+ _logger = logger;
+ _botSharpOptions = botSharpOptions;
+ }
+
+ public string Provider => "sql_driver";
+
+ public async Task GetConversationChartData(string conversationId, string messageId, ChartDataOptions options)
+ {
+ if (string.IsNullOrWhiteSpace(conversationId))
+ {
+ return null;
+ }
+
+ if (!string.IsNullOrWhiteSpace(options?.TargetStateName))
+ {
+ var db = _services.GetRequiredService();
+ var states = db.GetConversationStates(conversationId);
+ var value = states?.GetValueOrDefault(options?.TargetStateName)?.Values?.LastOrDefault()?.Data;
+
+ // To do
+ //return new ChartDataResult();
+ }
+
+ // Dummy data for testing
+ var data = new
+ {
+ categories = new string[] { "A", "B", "C", "D", "E" },
+ values = new int[] { 42, 67, 29, 85, 53 }
+ };
+
+ return new ChartDataResult { Data = data };
+ }
+
+ public async Task GetConversationChartCode(string conversationId, string messageId, ChartCodeOptions options)
+ {
+ if (string.IsNullOrWhiteSpace(conversationId))
+ {
+ return null;
+ }
+
+ var agentService = _services.GetRequiredService();
+
+ var agentId = options.AgentId.IfNullOrEmptyAs(BuiltInAgentId.UtilityAssistant);
+ var templateName = options.TemplateName.IfNullOrEmptyAs("util-chart-plot_instruction");
+ var inst = GetChartCodeInstruction(agentId, templateName);
+
+ var agent = await agentService.GetAgent(agentId);
+ agent = new Agent
+ {
+ Id = agent.Id,
+ Name = agent.Name,
+ Instruction = inst,
+ LlmConfig = new AgentLlmConfig
+ {
+ MaxOutputTokens = options.Llm?.MaxOutputTokens ?? 8192,
+ ReasoningEffortLevel = options.Llm?.ReasoningEffortLevel
+ },
+ TemplateDict = BuildChartStates(options)
+ };
+
+ var dialogs = new List
+ {
+ new RoleDialogModel
+ {
+ Role = AgentRole.User,
+ MessageId = messageId,
+ Content = options.Text.IfNullOrEmptyAs("Please follow the instruction to generate response.")
+ }
+ };
+ var response = await GetChatCompletion(agent, dialogs, options);
+ var obj = response.JsonContent();
+
+ return new ChartCodeResult
+ {
+ Code = obj?.JsCode,
+ Language = "javascript"
+ };
+ }
+
+
+ private Dictionary BuildChartStates(ChartCodeOptions options)
+ {
+ var states = new Dictionary();
+
+ if (!options.States.IsNullOrEmpty())
+ {
+ foreach (var item in options.States)
+ {
+ if (item.Value == null)
+ {
+ continue;
+ }
+ states[item.Key] = item.Value;
+ }
+ }
+ return states;
+ }
+
+ private string GetChartCodeInstruction(string agentId, string templateName)
+ {
+ var db = _services.GetRequiredService();
+ var templateContent = db.GetAgentTemplate(agentId, templateName);
+ return templateContent;
+ }
+
+ private async Task GetChatCompletion(Agent agent, List dialogs, ChartCodeOptions options)
+ {
+ try
+ {
+ var (provider, model) = GetLlmProviderModel(options);
+ var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model);
+ var response = await completion.GetChatCompletions(agent, dialogs);
+ return response.Content;
+ }
+ catch (Exception ex)
+ {
+ var error = $"Error when generating chart code. {ex.Message}";
+ _logger.LogWarning(ex, error);
+ return error;
+ }
+ }
+
+ private (string, string) GetLlmProviderModel(ChartCodeOptions options)
+ {
+ var provider = "openai";
+ var model = "gpt-5";
+
+ if (options?.Llm != null)
+ {
+ provider = options.Llm.Provider.IfNullOrEmptyAs(provider);
+ model = options.Llm.Model.IfNullOrEmptyAs(model);
+ }
+
+ return (provider, model);
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs
index 57969771..1dd27ad7 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs
@@ -30,5 +30,6 @@ public class SqlDriverPlugin : IBotSharpPlugin
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
}
}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Using.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Using.cs
index f0bf5480..94e9d49c 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Using.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Using.cs
@@ -25,6 +25,8 @@ global using BotSharp.Abstraction.Agents.Enums;
global using BotSharp.Abstraction.Instructs;
global using BotSharp.Abstraction.Instructs.Models;
global using BotSharp.Abstraction.Routing;
+global using BotSharp.Abstraction.Chart;
+global using BotSharp.Abstraction.Chart.Models;
global using BotSharp.Plugin.SqlDriver.Models;
global using BotSharp.Plugin.SqlDriver.Hooks;
diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json
index 8124a4a8..8800c380 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -549,6 +549,7 @@
"BotSharp.Plugin.EmailHandler",
"BotSharp.Plugin.AudioHandler",
"BotSharp.Plugin.ChartHandler",
+ "BotSharp.Plugin.SqlDriver",
"BotSharp.Plugin.TencentCos"
]
}
From 4af97e9035f2ceb160f292f35caa71d8f3914da8 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Sat, 6 Sep 2025 12:16:32 -0500
Subject: [PATCH 06/20] allow sending indication
---
.../Functions/PlotChartFn.cs | 1 +
.../Hooks/ChatHubConversationHook.cs | 34 ++++++++++++-------
2 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index b51da11c..572deb08 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -82,6 +82,7 @@ public class PlotChartFn : IFunctionCallback
MessageId = message.MessageId,
CurrentAgentId = message.CurrentAgentId,
Content = obj.ReportSummary,
+ Indication = "Summarizing",
FunctionName = message.FunctionName,
FunctionArgs = message.FunctionArgs,
CreatedAt = DateTime.UtcNow
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
index b8188d0f..6ffa4964 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
@@ -95,6 +95,14 @@ public class ChatHubConversationHook : ConversationHookBase
var conv = _services.GetRequiredService();
var state = _services.GetRequiredService();
+
+ var sender = new UserDto
+ {
+ FirstName = "AI",
+ LastName = "Assistant",
+ Role = AgentRole.Assistant
+ };
+
var data = new ChatResponseDto()
{
ConversationId = conv.ConversationId,
@@ -105,12 +113,7 @@ public class ChatHubConversationHook : ConversationHookBase
Data = message.Data,
States = state.GetStates(),
IsStreaming = message.IsStreaming,
- Sender = new()
- {
- FirstName = "AI",
- LastName = "Assistant",
- Role = AgentRole.Assistant
- }
+ Sender = sender
};
// Send type-off to client
@@ -130,6 +133,18 @@ public class ChatHubConversationHook : ConversationHookBase
foreach (var item in wrapper.Messages)
{
+ if (!string.IsNullOrWhiteSpace(item.Indication))
+ {
+ data = new ChatResponseDto
+ {
+ ConversationId = conv.ConversationId,
+ MessageId = item.MessageId,
+ Indication = item.Indication,
+ Sender = sender
+ };
+ await SendEvent(ChatEvent.OnIndicationReceived, conv.ConversationId, data);
+ }
+
await Task.Delay(wrapper.SendingInterval);
data = new ChatResponseDto
@@ -142,12 +157,7 @@ public class ChatHubConversationHook : ConversationHookBase
Data = item.Data,
States = state.GetStates(),
IsAppend = true,
- Sender = new()
- {
- FirstName = "AI",
- LastName = "Assistant",
- Role = AgentRole.Assistant
- }
+ Sender = sender
};
await SendEvent(ChatEvent.OnMessageReceivedFromAssistant, conv.ConversationId, data);
}
From 2e524d5f28080dde5f1c37b4690db95b9aaab78c Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Sat, 6 Sep 2025 15:49:56 -0500
Subject: [PATCH 07/20] reset message wrapper and fix path error
---
.../Storage/LocalFileStorageService.Audio.cs | 6 ++
.../LocalFileStorageService.Conversation.cs | 91 ++++++++++++++++---
.../Routing/RoutingService.InvokeAgent.cs | 2 +
.../Functions/PlotChartFn.cs | 4 +-
4 files changed, 88 insertions(+), 15 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs
index 45841749..a62744a5 100644
--- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs
@@ -31,11 +31,17 @@ public partial class LocalFileStorageService
public BinaryData GetSpeechFile(string conversationId, string fileName)
{
+ if (string.IsNullOrWhiteSpace(conversationId) || string.IsNullOrWhiteSpace(fileName))
+ {
+ return BinaryData.Empty;
+ }
+
var path = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, TEXT_TO_SPEECH_FOLDER, fileName);
if (!File.Exists(path))
{
return BinaryData.Empty;
}
+
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
return BinaryData.FromStream(fs);
}
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
index 9aac4df3..137fb5a3 100644
--- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
@@ -19,16 +19,30 @@ public partial class LocalFileStorageService
foreach (var messageId in messageIds)
{
+ if (string.IsNullOrWhiteSpace(messageId))
+ {
+ continue;
+ }
+
var dir = Path.Combine(pathPrefix, messageId, FileSourceType.User);
- if (!ExistDirectory(dir)) continue;
+ if (!ExistDirectory(dir))
+ {
+ continue;
+ }
foreach (var subDir in Directory.GetDirectories(dir))
{
var file = Directory.GetFiles(subDir).FirstOrDefault();
- if (file == null) continue;
+ if (file == null)
+ {
+ continue;
+ }
var screenshots = await GetScreenshots(file, subDir, messageId, source);
- if (screenshots.IsNullOrEmpty()) continue;
+ if (screenshots.IsNullOrEmpty())
+ {
+ continue;
+ }
files.AddRange(screenshots);
}
@@ -41,10 +55,18 @@ public partial class LocalFileStorageService
string source, IEnumerable? contentTypes = null)
{
var files = new List();
- if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return files;
+ if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty())
+ {
+ return files;
+ }
foreach (var messageId in messageIds)
{
+ if (string.IsNullOrWhiteSpace(messageId))
+ {
+ continue;
+ }
+
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId, source);
if (!ExistDirectory(dir))
{
@@ -85,6 +107,14 @@ public partial class LocalFileStorageService
public string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName)
{
+ if (string.IsNullOrWhiteSpace(conversationId)
+ || string.IsNullOrWhiteSpace(messageId)
+ || string.IsNullOrWhiteSpace(source)
+ || string.IsNullOrWhiteSpace(index))
+ {
+ return string.Empty;
+ }
+
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId, source, index);
if (!ExistDirectory(dir))
{
@@ -98,10 +128,18 @@ public partial class LocalFileStorageService
public IEnumerable GetMessagesWithFile(string conversationId, IEnumerable messageIds)
{
var foundMsgs = new List();
- if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return foundMsgs;
+ if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty())
+ {
+ return foundMsgs;
+ }
foreach (var messageId in messageIds)
{
+ if (string.IsNullOrWhiteSpace(messageId))
+ {
+ continue;
+ }
+
var prefix = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId);
var userDir = Path.Combine(prefix, FileSourceType.User);
if (ExistDirectory(userDir))
@@ -121,7 +159,17 @@ public partial class LocalFileStorageService
public bool SaveMessageFiles(string conversationId, string messageId, string source, List files)
{
- if (files.IsNullOrEmpty()) return false;
+ if (files.IsNullOrEmpty())
+ {
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(conversationId)
+ || string.IsNullOrWhiteSpace(messageId)
+ || string.IsNullOrWhiteSpace(source))
+ {
+ return false;
+ }
var dir = GetConversationFileDirectory(conversationId, messageId, createNewDir: true);
if (!ExistDirectory(dir)) return false;
@@ -164,7 +212,10 @@ public partial class LocalFileStorageService
public bool DeleteMessageFiles(string conversationId, IEnumerable messageIds, string targetMessageId, string? newMessageId = null)
{
- if (string.IsNullOrEmpty(conversationId) || messageIds == null) return false;
+ if (string.IsNullOrEmpty(conversationId) || messageIds == null)
+ {
+ return false;
+ }
if (!string.IsNullOrEmpty(targetMessageId) && !string.IsNullOrEmpty(newMessageId))
{
@@ -192,7 +243,10 @@ public partial class LocalFileStorageService
foreach (var messageId in messageIds)
{
var dir = GetConversationFileDirectory(conversationId, messageId);
- if (!ExistDirectory(dir)) continue;
+ if (!ExistDirectory(dir))
+ {
+ continue;
+ }
DeleteDirectory(dir);
Thread.Sleep(100);
@@ -203,12 +257,18 @@ public partial class LocalFileStorageService
public bool DeleteConversationFiles(IEnumerable conversationIds)
{
- if (conversationIds.IsNullOrEmpty()) return false;
+ if (conversationIds.IsNullOrEmpty())
+ {
+ return false;
+ }
foreach (var conversationId in conversationIds)
{
var convDir = GetConversationDirectory(conversationId);
- if (!ExistDirectory(convDir)) continue;
+ if (!ExistDirectory(convDir))
+ {
+ continue;
+ }
DeleteDirectory(convDir);
}
@@ -241,7 +301,10 @@ public partial class LocalFileStorageService
private IEnumerable GetMessageIds(IEnumerable dialogs, int? offset = null)
{
- if (dialogs.IsNullOrEmpty()) return Enumerable.Empty();
+ if (dialogs.IsNullOrEmpty())
+ {
+ return Enumerable.Empty();
+ }
if (offset.HasValue && offset < 1)
{
@@ -264,13 +327,17 @@ public partial class LocalFileStorageService
private async Task> ConvertPdfToImages(string pdfLoc, string imageLoc)
{
var converters = _services.GetServices();
- if (converters.IsNullOrEmpty()) return Enumerable.Empty();
+ if (converters.IsNullOrEmpty())
+ {
+ return Enumerable.Empty();
+ }
var converter = GetPdf2ImageConverter();
if (converter == null)
{
return Enumerable.Empty();
}
+
return await converter.ConvertPdfToImages(pdfLoc, imageLoc);
}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs
index 6acb58b2..b1246b58 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs
@@ -59,6 +59,7 @@ public partial class RoutingService
message.Indication = response.Indication;
message.CurrentAgentId = agent.Id;
message.IsStreaming = response.IsStreaming;
+ message.AdditionalMessageWrapper = null;
await InvokeFunction(message, dialogs, options);
}
@@ -74,6 +75,7 @@ public partial class RoutingService
message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: response.Content);
message.CurrentAgentId = agent.Id;
message.IsStreaming = response.IsStreaming;
+ message.AdditionalMessageWrapper = null;
dialogs.Add(message);
Context.SetDialogs(dialogs);
}
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 572deb08..7db394e0 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -76,12 +76,10 @@ public class PlotChartFn : IFunctionCallback
SaveToDb = true,
Messages = new List
{
- new()
+ new(AgentRole.Assistant, obj.ReportSummary)
{
- Role = AgentRole.Assistant,
MessageId = message.MessageId,
CurrentAgentId = message.CurrentAgentId,
- Content = obj.ReportSummary,
Indication = "Summarizing",
FunctionName = message.FunctionName,
FunctionArgs = message.FunctionArgs,
From a5157a60526dd95125f2ffdb97155ba07131bf08 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Sat, 6 Sep 2025 15:52:14 -0500
Subject: [PATCH 08/20] minor change
---
.../Storage/LocalFileStorageService.Conversation.cs | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
index 137fb5a3..81e668c1 100644
--- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
+++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs
@@ -159,20 +159,19 @@ public partial class LocalFileStorageService
public bool SaveMessageFiles(string conversationId, string messageId, string source, List files)
{
- if (files.IsNullOrEmpty())
- {
- return false;
- }
-
if (string.IsNullOrWhiteSpace(conversationId)
|| string.IsNullOrWhiteSpace(messageId)
- || string.IsNullOrWhiteSpace(source))
+ || string.IsNullOrWhiteSpace(source)
+ || files.IsNullOrEmpty())
{
return false;
}
var dir = GetConversationFileDirectory(conversationId, messageId, createNewDir: true);
- if (!ExistDirectory(dir)) return false;
+ if (!ExistDirectory(dir))
+ {
+ return false;
+ }
for (int i = 0; i < files.Count; i++)
{
From 7ea8d84f74e6fa73f0f9c87585d0244a2be1aea7 Mon Sep 17 00:00:00 2001
From: Jicheng Lu <103353@smsassist.com>
Date: Mon, 8 Sep 2025 14:29:08 -0500
Subject: [PATCH 09/20] add message label
---
.../Conversations/Dtos/ChatResponseDto.cs | 4 ++++
.../Conversations/Models/Conversation.cs | 4 ++++
.../Conversations/Models/RoleDialogModel.cs | 6 +++++
.../Services/ConversationStorage.cs | 24 +++++++++----------
.../Routing/RoutingService.InvokeAgent.cs | 2 ++
.../Routing/RoutingService.InvokeFunction.cs | 1 +
.../Controllers/ConversationController.cs | 2 ++
.../Functions/PlotChartFn.cs | 3 ++-
.../Hooks/ChatHubConversationHook.cs | 2 ++
.../Models/DialogMongoElement.cs | 3 +++
10 files changed, 37 insertions(+), 14 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs
index 030bae23..e6873807 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Dtos/ChatResponseDto.cs
@@ -36,6 +36,10 @@ public class ChatResponseDto : InstructResult
[JsonPropertyName("indication")]
public string? Indication { get; set; }
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("message_label")]
+ public string? MessageLabel { get; set; }
+
[JsonPropertyName("has_message_files")]
public bool HasMessageFiles { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
index 5ffdf187..675c0885 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
@@ -98,6 +98,10 @@ public class DialogMetaData
[JsonPropertyName("message_type")]
public string MessageType { get; set; } = default!;
+ [JsonPropertyName("message_label")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? MessageLabel { get; set; }
+
[JsonPropertyName("function_name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? FunctionName { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
index 5794e332..ed9d68a5 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
@@ -19,6 +19,11 @@ public class RoleDialogModel : ITrackableMessage
///
public string MessageType { get; set; } = MessageTypeName.Plain;
+ ///
+ /// The message label
+ ///
+ public string? MessageLabel { get; set; }
+
///
/// user, system, assistant, function
///
@@ -167,6 +172,7 @@ public class RoleDialogModel : ITrackableMessage
CurrentAgentId = source.CurrentAgentId,
MessageId = source.MessageId,
MessageType = source.MessageType,
+ MessageLabel = source.MessageLabel,
FunctionArgs = source.FunctionArgs,
FunctionName = source.FunctionName,
ToolCallId = source.ToolCallId,
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
index 5c4bff47..26020da3 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
@@ -1,4 +1,3 @@
-using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Messaging.Models.RichContent;
using BotSharp.Abstraction.Options;
@@ -67,11 +66,7 @@ public class ConversationStorage : IConversationStorage
var secondaryContent = dialog.SecondaryContent;
var payload = string.IsNullOrEmpty(dialog.Payload) ? null : dialog.Payload;
var role = meta.Role;
- var currentAgentId = meta.AgentId;
- var messageId = meta.MessageId;
- var messageType = meta.MessageType;
- var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId;
- var createdAt = meta.CreatedTime;
+ var senderId = role == AgentRole.Function ? meta?.AgentId : meta?.SenderId;
var richContent = !string.IsNullOrEmpty(dialog.RichContent) ?
JsonSerializer.Deserialize>(dialog.RichContent, _options.JsonSerializerOptions) : null;
var secondaryRichContent = !string.IsNullOrEmpty(dialog.SecondaryRichContent) ?
@@ -79,14 +74,15 @@ public class ConversationStorage : IConversationStorage
var record = new RoleDialogModel(role, content)
{
- CurrentAgentId = currentAgentId,
- MessageId = messageId,
- MessageType = messageType,
- CreatedAt = createdAt,
+ CurrentAgentId = meta?.AgentId ?? string.Empty,
+ MessageId = meta?.MessageId ?? string.Empty,
+ MessageType = meta?.MessageType ?? string.Empty,
+ MessageLabel = meta?.MessageLabel,
+ CreatedAt = meta?.CreatedTime ?? default,
SenderId = senderId,
- FunctionName = meta.FunctionName,
- FunctionArgs = meta.FunctionArgs,
- ToolCallId = meta.ToolCallId,
+ FunctionName = meta?.FunctionName,
+ FunctionArgs = meta?.FunctionArgs,
+ ToolCallId = meta?.ToolCallId,
RichContent = richContent,
SecondaryContent = secondaryContent,
SecondaryRichContent = secondaryRichContent,
@@ -120,6 +116,7 @@ public class ConversationStorage : IConversationStorage
AgentId = dialog.CurrentAgentId,
MessageId = dialog.MessageId,
MessageType = dialog.MessageType,
+ MessageLabel = dialog.MessageLabel,
FunctionName = dialog.FunctionName,
FunctionArgs = dialog.FunctionArgs,
ToolCallId = dialog.ToolCallId,
@@ -146,6 +143,7 @@ public class ConversationStorage : IConversationStorage
AgentId = dialog.CurrentAgentId,
MessageId = dialog.MessageId,
MessageType = dialog.MessageType,
+ MessageLabel = dialog.MessageLabel,
SenderId = dialog.SenderId,
FunctionName = dialog.FunctionName,
CreatedTime = dialog.CreatedAt
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs
index b1246b58..5b378a24 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs
@@ -59,6 +59,7 @@ public partial class RoutingService
message.Indication = response.Indication;
message.CurrentAgentId = agent.Id;
message.IsStreaming = response.IsStreaming;
+ message.MessageLabel = response.MessageLabel;
message.AdditionalMessageWrapper = null;
await InvokeFunction(message, dialogs, options);
@@ -75,6 +76,7 @@ public partial class RoutingService
message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: response.Content);
message.CurrentAgentId = agent.Id;
message.IsStreaming = response.IsStreaming;
+ message.MessageLabel = response.MessageLabel;
message.AdditionalMessageWrapper = null;
dialogs.Add(message);
Context.SetDialogs(dialogs);
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs
index 45b65c7e..f23f150c 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs
@@ -65,6 +65,7 @@ public partial class RoutingService
message.StopCompletion = clonedMessage.StopCompletion;
message.RichContent = clonedMessage.RichContent;
message.Data = clonedMessage.Data;
+ message.MessageLabel = clonedMessage.MessageLabel;
message.AdditionalMessageWrapper = clonedMessage.AdditionalMessageWrapper;
}
catch (JsonException ex)
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
index 2f127b10..13fb5199 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
@@ -109,6 +109,7 @@ public class ConversationController : ControllerBase
{
ConversationId = conversationId,
MessageId = message.MessageId,
+ MessageLabel = message.MessageLabel,
CreatedAt = message.CreatedAt,
Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
Data = message.Data,
@@ -124,6 +125,7 @@ public class ConversationController : ControllerBase
{
ConversationId = conversationId,
MessageId = message.MessageId,
+ MessageLabel = message.MessageLabel,
CreatedAt = message.CreatedAt,
Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
Function = message.FunctionName,
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 7db394e0..870566e0 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -79,8 +79,9 @@ public class PlotChartFn : IFunctionCallback
new(AgentRole.Assistant, obj.ReportSummary)
{
MessageId = message.MessageId,
- CurrentAgentId = message.CurrentAgentId,
+ MessageLabel = "chart_report_summary",
Indication = "Summarizing",
+ CurrentAgentId = message.CurrentAgentId,
FunctionName = message.FunctionName,
FunctionArgs = message.FunctionArgs,
CreatedAt = DateTime.UtcNow
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
index 6ffa4964..e0034001 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
@@ -107,6 +107,7 @@ public class ChatHubConversationHook : ConversationHookBase
{
ConversationId = conv.ConversationId,
MessageId = message.MessageId,
+ MessageLabel = message.MessageLabel,
Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
Function = message.FunctionName,
RichContent = message.SecondaryRichContent ?? message.RichContent,
@@ -139,6 +140,7 @@ public class ChatHubConversationHook : ConversationHookBase
{
ConversationId = conv.ConversationId,
MessageId = item.MessageId,
+ MessageLabel = item.MessageLabel,
Indication = item.Indication,
Sender = sender
};
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs
index b67fc53c..7dd7ece7 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs
@@ -46,6 +46,7 @@ public class DialogMetaDataMongoElement
public string AgentId { get; set; } = default!;
public string MessageId { get; set; } = default!;
public string MessageType { get; set; } = default!;
+ public string? MessageLabel { get; set; }
public string? FunctionName { get; set; }
public string? FunctionArgs { get; set; }
public string? ToolCallId { get; set; }
@@ -60,6 +61,7 @@ public class DialogMetaDataMongoElement
AgentId = meta.AgentId,
MessageId = meta.MessageId,
MessageType = meta.MessageType,
+ MessageLabel = meta.MessageLabel,
FunctionName = meta.FunctionName,
FunctionArgs = meta.FunctionArgs,
ToolCallId = meta.ToolCallId,
@@ -76,6 +78,7 @@ public class DialogMetaDataMongoElement
AgentId = meta.AgentId,
MessageId = meta.MessageId,
MessageType = meta.MessageType,
+ MessageLabel = meta.MessageLabel,
FunctionName = meta.FunctionName,
FunctionArgs = meta.FunctionArgs,
ToolCallId = meta.ToolCallId,
From d3035c07a275ab905c337002d58c2aa10fc1f581 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Mon, 8 Sep 2025 23:07:51 -0500
Subject: [PATCH 10/20] add message wrapper to chat response
---
.../Controllers/ConversationController.cs | 4 ++
.../Response/ChatResponseModel.cs | 39 +++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
index 13fb5199..7f12eec2 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
@@ -371,9 +371,11 @@ public class ConversationController : ControllerBase
{
response.Text = !string.IsNullOrEmpty(msg.SecondaryContent) ? msg.SecondaryContent : msg.Content;
response.Function = msg.FunctionName;
+ response.MessageLabel = msg.MessageLabel;
response.RichContent = msg.SecondaryRichContent ?? msg.RichContent;
response.Instruction = msg.Instruction;
response.Data = msg.Data;
+ response.AdditionalMessageWrapper = ChatResponseWrapper.From(msg.AdditionalMessageWrapper, conversationId, inputMsg.MessageId);
});
var state = _services.GetRequiredService();
@@ -426,11 +428,13 @@ public class ConversationController : ControllerBase
async msg =>
{
response.Text = !string.IsNullOrEmpty(msg.SecondaryContent) ? msg.SecondaryContent : msg.Content;
+ response.MessageLabel = msg.MessageLabel;
response.Function = msg.FunctionName;
response.RichContent = msg.SecondaryRichContent ?? msg.RichContent;
response.Instruction = msg.Instruction;
response.Data = msg.Data;
response.States = state.GetStates();
+ response.AdditionalMessageWrapper = ChatResponseWrapper.From(msg.AdditionalMessageWrapper, conversationId, inputMsg.MessageId);
await OnChunkReceived(Response, response);
});
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ChatResponseModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ChatResponseModel.cs
index 4d149200..37db3084 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ChatResponseModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Response/ChatResponseModel.cs
@@ -1,7 +1,46 @@
using BotSharp.Abstraction.Conversations.Dtos;
+using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Conversations;
public class ChatResponseModel : ChatResponseDto
{
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("additional_message_wrapper")]
+ public ChatResponseWrapper? AdditionalMessageWrapper { get; set; }
}
+
+public class ChatResponseWrapper
+{
+ [JsonPropertyName("sending_interval")]
+ public int SendingInterval { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("messages")]
+ public List? Messages { get; set; }
+
+ public static ChatResponseWrapper? From(ChatMessageWrapper? wrapper, string conversationId, string? messageId = null)
+ {
+ if (wrapper == null)
+ {
+ return null;
+ }
+
+ return new ChatResponseWrapper
+ {
+ SendingInterval = wrapper.SendingInterval,
+ Messages = wrapper?.Messages?.Select(x => new ChatResponseModel
+ {
+ ConversationId = conversationId,
+ MessageId = messageId ?? x.MessageId,
+ Text = !string.IsNullOrEmpty(x.SecondaryContent) ? x.SecondaryContent : x.Content,
+ MessageLabel = x.MessageLabel,
+ Function = x.FunctionName,
+ RichContent = x.SecondaryRichContent ?? x.RichContent,
+ Instruction = x.Instruction,
+ Data = x.Data,
+ IsAppend = true
+ })?.ToList()
+ };
+ }
+}
\ No newline at end of file
From 8250336498e3379106925d5d8d1ab75a2927f3d3 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Tue, 9 Sep 2025 01:58:46 -0500
Subject: [PATCH 11/20] fix missing label
---
.../BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
index e0034001..9aa7e5b6 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
@@ -153,6 +153,7 @@ public class ChatHubConversationHook : ConversationHookBase
{
ConversationId = conv.ConversationId,
MessageId = item.MessageId,
+ MessageLabel = item.MessageLabel,
Text = !string.IsNullOrEmpty(item.SecondaryContent) ? item.SecondaryContent : item.Content,
Function = item.FunctionName,
RichContent = item.SecondaryRichContent ?? item.RichContent,
From e4c53a32b5cf5a825777abca7fae72ee9c6d30f2 Mon Sep 17 00:00:00 2001
From: vguruparan
Date: Tue, 9 Sep 2025 11:19:29 -0500
Subject: [PATCH 12/20] Update distributed lock for cronservice
---
.../Crontab/Settings/CrontabSettings.cs | 1 +
.../BotSharp.Core.Crontab/Services/CrontabWatcher.cs | 8 ++++++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
index 4bca1240..53e90959 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
@@ -4,6 +4,7 @@ public class CrontabSettings
{
public CrontabBaseSetting EventSubscriber { get; set; } = new();
public CrontabBaseSetting Watcher { get; set; } = new();
+ public string LockName { get; set; }
}
public class CrontabBaseSetting
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
index 7bb621f4..9497ddd4 100644
--- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
@@ -10,11 +10,15 @@ public class CrontabWatcher : BackgroundService
{
private readonly ILogger _logger;
private readonly IServiceProvider _services;
+ private readonly CrontabSettings _cronSettings;
+ private string DIST_KEY;
- public CrontabWatcher(IServiceProvider services, ILogger logger)
+ public CrontabWatcher(IServiceProvider services, ILogger logger, CrontabSettings cronSettings)
{
_logger = logger;
_services = services;
+ _cronSettings = cronSettings;
+ DIST_KEY = $"CrontabWatcher:locker-{_cronSettings.LockName ?? "default"}";
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@@ -29,7 +33,7 @@ public class CrontabWatcher : BackgroundService
{
var delay = Task.Delay(1000, stoppingToken);
- await locker.LockAsync("CrontabWatcher:locker", async () =>
+ await locker.LockAsync(DIST_KEY, async () =>
{
await RunCronChecker(scope.ServiceProvider);
});
From 4a1439574dc7cd59bfdbc3ffeac0c070292d3cef Mon Sep 17 00:00:00 2001
From: vguruparan
Date: Tue, 9 Sep 2025 11:45:04 -0500
Subject: [PATCH 13/20] Update lockname to default if not provided.
---
.../BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs | 2 +-
.../BotSharp.Core.Crontab/Services/CrontabWatcher.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
index 53e90959..31326687 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
@@ -4,7 +4,7 @@ public class CrontabSettings
{
public CrontabBaseSetting EventSubscriber { get; set; } = new();
public CrontabBaseSetting Watcher { get; set; } = new();
- public string LockName { get; set; }
+ public string LockName { get; set; } = "default";
}
public class CrontabBaseSetting
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
index 9497ddd4..dce21fa6 100644
--- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
@@ -18,7 +18,7 @@ public class CrontabWatcher : BackgroundService
_logger = logger;
_services = services;
_cronSettings = cronSettings;
- DIST_KEY = $"CrontabWatcher:locker-{_cronSettings.LockName ?? "default"}";
+ DIST_KEY = $"CrontabWatcher:locker-{_cronSettings.LockName}";
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
From 393430e9819bebbff37e976b89aa95418826f2e2 Mon Sep 17 00:00:00 2001
From: vguruparan
Date: Tue, 9 Sep 2025 13:13:53 -0500
Subject: [PATCH 14/20] Update crontab naming
---
.../BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs | 2 +-
.../BotSharp.Core.Crontab/Services/CrontabWatcher.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
index 31326687..ac74ac38 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
@@ -4,7 +4,7 @@ public class CrontabSettings
{
public CrontabBaseSetting EventSubscriber { get; set; } = new();
public CrontabBaseSetting Watcher { get; set; } = new();
- public string LockName { get; set; } = "default";
+ public string LockName { get; set; } = "CrontabWatcher:locker";
}
public class CrontabBaseSetting
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
index dce21fa6..f75f09ad 100644
--- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
@@ -18,7 +18,7 @@ public class CrontabWatcher : BackgroundService
_logger = logger;
_services = services;
_cronSettings = cronSettings;
- DIST_KEY = $"CrontabWatcher:locker-{_cronSettings.LockName}";
+ DIST_KEY = _cronSettings.LockName;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
From bdaf3cc6b3a7776d6b04163bf191a49bc2950668 Mon Sep 17 00:00:00 2001
From: "nick.yi"
Date: Wed, 10 Sep 2025 11:45:39 +0800
Subject: [PATCH 15/20] add crontab debug config
---
.../Crontab/Settings/CrontabSettings.cs | 6 ++++++
.../BotSharp.Core.Crontab/Services/CrontabWatcher.cs | 5 ++++-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
index 4bca1240..ac939a7d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Settings/CrontabSettings.cs
@@ -4,9 +4,15 @@ public class CrontabSettings
{
public CrontabBaseSetting EventSubscriber { get; set; } = new();
public CrontabBaseSetting Watcher { get; set; } = new();
+ public DebugSetting Debug { get; set; } = new();
}
public class CrontabBaseSetting
{
public bool Enabled { get; set; } = true;
}
+
+public class DebugSetting
+{
+ public string AllowRuleTrigger { get; set; } = "";
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
index 7bb621f4..dc5f8cd2 100644
--- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
@@ -89,7 +89,10 @@ public class CrontabWatcher : BackgroundService
_logger.LogInformation($"The current time matches the cron expression {item}");
#if DEBUG
- await HandleCrontabEvent(item);
+ if (item.Title == settings.Debug.AllowRuleTrigger)
+ {
+ await HandleCrontabEvent(item);
+ }
#else
if (publisher != null)
{
From ee758e02ece517a67847c25a88d5fb2341c1ba7a Mon Sep 17 00:00:00 2001
From: Jicheng Lu <103353@smsassist.com>
Date: Wed, 10 Sep 2025 14:31:03 -0500
Subject: [PATCH 16/20] add reasoning effort in chart plot
---
.../Functions/PlotChartFn.cs | 21 +++++++++++++++----
.../Settings/ChartHandlerSettings.cs | 1 +
.../util-chart-plot_instruction.liquid | 2 +-
src/WebStarter/appsettings.json | 4 +++-
4 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 870566e0..bd4feb0a 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -36,10 +36,7 @@ public class PlotChartFn : IFunctionCallback
Id = agent.Id,
Name = agent.Name,
Instruction = inst,
- LlmConfig = new AgentLlmConfig
- {
- MaxOutputTokens = _settings?.ChartPlot?.MaxOutputTokens ?? 8192
- },
+ LlmConfig = GetLlmConfig(),
TemplateDict = new Dictionary
{
{ "plotting_requirement", args?.PlottingRequirement ?? string.Empty },
@@ -147,4 +144,20 @@ public class PlotChartFn : IFunctionCallback
return (provider, model);
}
+
+ private AgentLlmConfig GetLlmConfig()
+ {
+ var maxOutputTokens = _settings?.ChartPlot?.MaxOutputTokens ?? 8192;
+ var reasoningEffortLevel = _settings?.ChartPlot?.ReasoningEffortLevel ?? "minimal";
+
+ var state = _services.GetRequiredService();
+ maxOutputTokens = int.TryParse(state.GetState("chart_plot_max_output_tokens"), out var tokens) ? tokens : maxOutputTokens;
+ reasoningEffortLevel = state.GetState("chart_plot_reasoning_effort_level").IfNullOrEmptyAs(reasoningEffortLevel);
+
+ return new AgentLlmConfig
+ {
+ MaxOutputTokens = maxOutputTokens,
+ ReasoningEffortLevel = reasoningEffortLevel
+ };
+ }
}
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
index 4e031621..ad13e0ef 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
@@ -10,4 +10,5 @@ public class ChartPlotSetting
public string? LlmProvider { get; set; }
public string? LlmModel { get; set; }
public int? MaxOutputTokens { get; set; }
+ public string? ReasoningEffortLevel { 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 7721493f..2c23b441 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
@@ -19,7 +19,7 @@ You must strictly follow the "Hard Requirements", "Render Requirements", "Code R
* Ensure the chart fully expands and scales to the entire screen when fullscreen is active.
* fullscreenBtn must be a fully-formed object {show: true, name, title, icon: 'path://M3 3 H9 V5 H5 V9 H3 Z M15 3 H21 V9 H19 V5 H15 Z M3 15 H5 V19 H9 V21 H3 Z M19 15 H21 V21 H15 V19 H19 Z', onclick}.
* When using "chart.setOption" to define the fullscreen button, DO NOT use "graphic". Include the fullscreenBtn object in toolbox.feature with name 'myFullscreen'.
-** Initialize the chart with explicit non-zero width (at least 800px) and non-zero height (at least 500px).
+** You must initialize the chart with explicit non-zero width (at least 800px) and non-zero height (at least 500px).
***** Render Requirements *****
diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json
index 8800c380..893e8fa2 100644
--- a/src/WebStarter/appsettings.json
+++ b/src/WebStarter/appsettings.json
@@ -320,7 +320,9 @@
"ChartHandler": {
"ChartPlot": {
"LlmProvider": "openai",
- "LlmModel": "gpt-5"
+ "LlmModel": "gpt-5",
+ "MaxOutputTokens": 8192,
+ "ReasoningEffortLevel": "minimal"
}
},
From a03ecc376f3623aa6bc1bc4eedcc0dadcfb2bae7 Mon Sep 17 00:00:00 2001
From: Joanna Ren <101223@smsassist.com>
Date: Wed, 10 Sep 2025 19:58:57 -0500
Subject: [PATCH 17/20] minor change
---
.../templates/util-chart-plot_instruction.liquid | 5 +++--
.../BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs | 3 ++-
2 files changed, 5 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 2c23b441..9043b5fb 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
@@ -20,7 +20,8 @@ You must strictly follow the "Hard Requirements", "Render Requirements", "Code R
* fullscreenBtn must be a fully-formed object {show: true, name, title, icon: 'path://M3 3 H9 V5 H5 V9 H3 Z M15 3 H21 V9 H19 V5 H15 Z M3 15 H5 V19 H9 V21 H3 Z M19 15 H21 V21 H15 V19 H19 Z', onclick}.
* When using "chart.setOption" to define the fullscreen button, DO NOT use "graphic". Include the fullscreenBtn object in toolbox.feature with name 'myFullscreen'.
** You must initialize the chart with explicit non-zero width (at least 800px) and non-zero height (at least 500px).
-
+** When generating code for multiple charts, arrange them in a clear layout and make sure they do not overlap.
+** Only using the dataset provided in the context, NEVER generate or assume any additional dataset on your own.
***** Render Requirements *****
** You must render the charts under the div html element with id {{ chart_element_id }}.
@@ -39,5 +40,5 @@ 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 that can generate the charts as requested.",
- "report_summary": "An insight summary report based on the data, highlighting the key information in markdown format."
+ "report_summary": "Generate an insightful summary report in markdown format based on the data. Bold all key findings and use level-4 headings or smaller (####, #####, etc.) to structure the content with clear titles."
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs
index 72bef238..1d96c7f3 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs
@@ -7,8 +7,9 @@ namespace BotSharp.Plugin.SqlDriver.UtilFunctions;
public class SqlSelect : IFunctionCallback
{
- public string Name => "util-db-sql_select";
private readonly IServiceProvider _services;
+ public string Name => "util-db-sql_select";
+ public string Indication => "Extracting data";
public SqlSelect(IServiceProvider services)
{
From c00391c18c5c7f280a31c6c35d09621f0d7475e7 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Wed, 10 Sep 2025 22:18:27 -0500
Subject: [PATCH 18/20] refine chart plot prompt
---
.../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 9043b5fb..e89ace07 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
@@ -32,6 +32,8 @@ You must strictly follow the "Hard Requirements", "Render Requirements", "Code R
***** Code Requirements *****
** You must strictly follow the valid javascript and ECharts.js syntax.
** You must ensure no syntax/runtime error before returning the response.
+** You must ensure the method is applied on variable with correct type, such as never applying "toFixed" to a non-number variable. Use explicit conversion if necessary.
+** You must ensure the math operations are valid, such as avoid dividing by zero.
** Please ensure the code can be executed and the charts are rendered correctly.
From c736186a9bfce073174eb128d3d0a6d7aa1e0001 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Wed, 10 Sep 2025 23:38:45 -0500
Subject: [PATCH 19/20] add chat context to chart
---
.../Functions/PlotChartFn.cs | 25 +++++++++++++------
.../Settings/ChartHandlerSettings.cs | 1 +
.../util-chart-plot_instruction.liquid | 2 +-
3 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index bd4feb0a..73059c0b 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -1,4 +1,6 @@
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
+using BotSharp.Abstraction.Routing;
+using System.Linq;
namespace BotSharp.Plugin.ChartHandler.Functions;
@@ -26,6 +28,7 @@ public class PlotChartFn : IFunctionCallback
{
var agentService = _services.GetRequiredService();
var convService = _services.GetRequiredService();
+ var routingCtx = _services.GetRequiredService();
var args = JsonSerializer.Deserialize(message.FunctionArgs);
@@ -44,15 +47,21 @@ public class PlotChartFn : IFunctionCallback
}
};
- var response = await GetChatCompletion(innerAgent,
- [
- new RoleDialogModel(AgentRole.User, "Please follow the instruction to generate the javascript code.")
- {
- CurrentAgentId = message.CurrentAgentId,
- MessageId = message.MessageId
- }
- ]);
+ var dialogs = routingCtx.GetDialogs();
+ if (dialogs.IsNullOrEmpty())
+ {
+ dialogs = convService.GetDialogHistory();
+ }
+ var messageLimit = _settings.ChartPlot?.MessageLimit > 0 ? _settings.ChartPlot.MessageLimit.Value : 50;
+ dialogs = dialogs.TakeLast(messageLimit).ToList();
+ dialogs.Add(new RoleDialogModel(AgentRole.User, "Please follow the instruction and chat context to generate valid javascript code.")
+ {
+ CurrentAgentId = message.CurrentAgentId,
+ MessageId = message.MessageId
+ });
+
+ var response = await GetChatCompletion(innerAgent, dialogs);
var obj = response.JsonContent();
message.Content = obj?.GreetingMessage ?? "Here is the chart you ask for:";
message.RichContent = new RichContent
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
index ad13e0ef..1218b397 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Settings/ChartHandlerSettings.cs
@@ -11,4 +11,5 @@ public class ChartPlotSetting
public string? LlmModel { get; set; }
public int? MaxOutputTokens { get; set; }
public string? ReasoningEffortLevel { get; set; }
+ public int? MessageLimit { 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 e89ace07..af0a04a9 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
@@ -42,5 +42,5 @@ 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 that can generate the charts as requested.",
- "report_summary": "Generate an insightful summary report in markdown format based on the data. Bold all key findings and use level-4 headings or smaller (####, #####, etc.) to structure the content with clear titles."
+ "report_summary": "Generate an insightful summary report in markdown format based on the data. You can summarize using one or multiple titles and list the key findings under each title. Bold all key findings and use level-4 headings or smaller (####, #####, etc.). DO NOT make everything in one line."
}
\ No newline at end of file
From e8c3c93b8fec8f23a8c7a2f496113dca7f7290d5 Mon Sep 17 00:00:00 2001
From: Jicheng Lu
Date: Wed, 10 Sep 2025 23:39:41 -0500
Subject: [PATCH 20/20] clean code
---
.../BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
index 73059c0b..d1fc5943 100644
--- a/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
+++ b/src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs
@@ -1,6 +1,5 @@
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
using BotSharp.Abstraction.Routing;
-using System.Linq;
namespace BotSharp.Plugin.ChartHandler.Functions;