Merge branch 'master' of https://github.com/SciSharp/BotSharp into features/refine-model-settings
This commit is contained in:
commit
fc1bb151fc
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,13 @@ public class RoleDialogModel : ITrackableMessage
|
|||
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
|
||||
public bool IsStreaming { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional messages that can be sent sequentially and save to db
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
|
||||
public ChatMessageWrapper? AdditionalMessageWrapper { get; set; }
|
||||
|
||||
|
||||
public RoleDialogModel()
|
||||
{
|
||||
}
|
||||
|
|
@ -171,7 +178,26 @@ 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Messages sending interval in milliseconds
|
||||
/// </summary>
|
||||
public int SendingInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the Messages are saved to db
|
||||
/// </summary>
|
||||
public bool SaveToDb { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Messages to send or save
|
||||
/// </summary>
|
||||
public List<RoleDialogModel>? Messages { get; set; }
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Hooks;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
|
|
|
|||
|
|
@ -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<RoleDialogModel> { 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ public class PlotChartFn : IFunctionCallback
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<PlotChartFn> _logger;
|
||||
private readonly ChartHandlerSettings _settings;
|
||||
|
||||
public string Name => "util-chart-plot_chart";
|
||||
public string Indication => "Plotting chart";
|
||||
|
|
@ -13,10 +14,12 @@ public class PlotChartFn : IFunctionCallback
|
|||
|
||||
public PlotChartFn(
|
||||
IServiceProvider services,
|
||||
ILogger<PlotChartFn> logger)
|
||||
ILogger<PlotChartFn> logger,
|
||||
ChartHandlerSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
|
|
@ -35,7 +38,7 @@ public class PlotChartFn : IFunctionCallback
|
|||
Instruction = inst,
|
||||
LlmConfig = new AgentLlmConfig
|
||||
{
|
||||
MaxOutputTokens = 8192
|
||||
MaxOutputTokens = _settings?.ChartPlot?.MaxOutputTokens ?? 8192
|
||||
},
|
||||
TemplateDict = new Dictionary<string, object>
|
||||
{
|
||||
|
|
@ -44,7 +47,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,
|
||||
|
|
@ -63,6 +67,29 @@ public class PlotChartFn : IFunctionCallback
|
|||
Language = "javascript"
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(obj?.ReportSummary))
|
||||
{
|
||||
message.AdditionalMessageWrapper = new()
|
||||
{
|
||||
SendingInterval = 1500,
|
||||
SaveToDb = true,
|
||||
Messages = new List<RoleDialogModel>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
@ -111,12 +138,11 @@ public class PlotChartFn : IFunctionCallback
|
|||
var model = "gpt-5";
|
||||
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var settings = _services.GetRequiredService<ChartHandlerSettings>();
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,42 @@
|
|||
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 <script>...</script> 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.",
|
||||
"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."
|
||||
}
|
||||
|
|
@ -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,49 @@ 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);
|
||||
|
||||
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 wrapper.Messages)
|
||||
{
|
||||
await Task.Delay(wrapper.SendingInterval);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue