Merge branch 'master' into MetaGLM
This commit is contained in:
commit
49020edd5f
11
BotSharp.sln
11
BotSharp.sln
|
|
@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SparkDesk",
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MetaGLM", "src\Plugins\BotSharp.Plugin.MetaGLM\BotSharp.Plugin.MetaGLM.csproj", "{CCF745F2-0C95-4ED0-983B-507C528B39EA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AnthropicAI", "src\Plugins\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj", "{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -343,6 +345,14 @@ Global
|
|||
{CCF745F2-0C95-4ED0-983B-507C528B39EA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CCF745F2-0C95-4ED0-983B-507C528B39EA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CCF745F2-0C95-4ED0-983B-507C528B39EA}.Release|x64.Build.0 = Release|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -386,6 +396,7 @@ Global
|
|||
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||
{289E25C8-63F1-4D52-9909-207724DB40CB} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{CCF745F2-0C95-4ED0-983B-507C528B39EA} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<BotSharpVersion>1.2.1</BotSharpVersion>
|
||||
<BotSharpVersion>1.5.1</BotSharpVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace BotSharp.Abstraction.Agents;
|
|||
public interface IAgentService
|
||||
{
|
||||
Task<Agent> CreateAgent(Agent agent);
|
||||
Task RefreshAgents();
|
||||
Task<string> RefreshAgents();
|
||||
Task<PagedItems<Agent>> GetAgents(AgentFilter filter);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -26,6 +26,8 @@ public interface IAgentService
|
|||
|
||||
bool RenderFunction(Agent agent, FunctionDef def);
|
||||
|
||||
FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def);
|
||||
|
||||
/// <summary>
|
||||
/// Get agent detail without trigger any hook.
|
||||
/// </summary>
|
||||
|
|
@ -35,9 +37,18 @@ public interface IAgentService
|
|||
|
||||
Task<bool> DeleteAgent(string id);
|
||||
Task UpdateAgent(Agent agent, AgentField updateField);
|
||||
Task UpdateAgentFromFile(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Path existing templates of agent, cannot create new or delete templates
|
||||
/// </summary>
|
||||
/// <param name="agent"></param>
|
||||
/// <returns></returns>
|
||||
Task<string> PatchAgentTemplate(Agent agent);
|
||||
Task<string> UpdateAgentFromFile(string id);
|
||||
string GetDataDir();
|
||||
string GetAgentDataDir(string agentId);
|
||||
|
||||
List<Agent> GetAgentsByUser(string userId);
|
||||
|
||||
PluginDef GetPlugin(string agentId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ public class AgentSettings
|
|||
public string DataDir { get; set; } = string.Empty;
|
||||
public string TemplateFormat { get; set; } = "liquid";
|
||||
public string HostAgentId { get; set; } = string.Empty;
|
||||
public bool EnableTranslator { get; set; } = false;
|
||||
public bool EnableHttpHandler { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// This is the default LLM config for agent
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Browsing.Enums;
|
||||
|
||||
public enum BroswerActionEnum
|
||||
{
|
||||
Click = 1,
|
||||
InputText = 2,
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ namespace BotSharp.Abstraction.Browsing;
|
|||
|
||||
public interface IWebBrowser
|
||||
{
|
||||
Task<BrowserActionResult> LaunchBrowser(string conversationId, string? url);
|
||||
Task<BrowserActionResult> ScreenshotAsync(string conversationId, string path);
|
||||
Task<BrowserActionResult> LaunchBrowser(string contextId, string? url);
|
||||
Task<BrowserActionResult> ScreenshotAsync(string contextId, string path);
|
||||
Task<BrowserActionResult> ScrollPageAsync(BrowserActionParams actionParams);
|
||||
|
||||
Task<BrowserActionResult> ActionOnElement(MessageInfo message, ElementLocatingArgs location, ElementActionArgs action);
|
||||
|
|
@ -19,10 +19,11 @@ public interface IWebBrowser
|
|||
Task<BrowserActionResult> ChangeListValue(BrowserActionParams actionParams);
|
||||
Task<BrowserActionResult> CheckRadioButton(BrowserActionParams actionParams);
|
||||
Task<BrowserActionResult> ChangeCheckbox(BrowserActionParams actionParams);
|
||||
Task<BrowserActionResult> GoToPage(string conversationId, string url);
|
||||
Task<BrowserActionResult> GoToPage(string contextId, string url, bool openNewTab = false);
|
||||
Task<string> ExtractData(BrowserActionParams actionParams);
|
||||
Task<T> EvaluateScript<T>(string conversationId, string script);
|
||||
Task CloseBrowser(string conversationId);
|
||||
Task<BrowserActionResult> SendHttpRequest(BrowserActionParams actionParams);
|
||||
Task<string> GetAttributeValue(MessageInfo message, ElementLocatingArgs location, BrowserActionResult result);
|
||||
Task<T> EvaluateScript<T>(string contextId, string script);
|
||||
Task CloseBrowser(string contextId);
|
||||
Task CloseCurrentPage(string contextId);
|
||||
Task<BrowserActionResult> SendHttpRequest(MessageInfo message, HttpRequestParams actionParams);
|
||||
Task<BrowserActionResult> GetAttributeValue(MessageInfo message, ElementLocatingArgs location);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ public class BrowserActionParams
|
|||
{
|
||||
public Agent Agent { get; set; }
|
||||
public BrowsingContextIn Context { get; set; }
|
||||
public string ConversationId { get; set; }
|
||||
public string ContextId { get; set; }
|
||||
public string MessageId { get; set; }
|
||||
|
||||
public BrowserActionParams(Agent agent, BrowsingContextIn context, string conversationId, string messageId)
|
||||
public BrowserActionParams(Agent agent, BrowsingContextIn context, string contextId, string messageId)
|
||||
{
|
||||
Agent = agent;
|
||||
Context = context;
|
||||
ConversationId = conversationId;
|
||||
ContextId = contextId;
|
||||
MessageId = messageId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ namespace BotSharp.Abstraction.Browsing.Models;
|
|||
public class BrowserActionResult
|
||||
{
|
||||
public bool IsSuccess { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public string StackTrace { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public string? StackTrace { get; set; }
|
||||
public string Selector { get; set; }
|
||||
public string Body { get; set; }
|
||||
public bool IsHighlighted { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{IsSuccess} - {Selector}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,4 @@ public class BrowsingContextIn
|
|||
|
||||
[JsonPropertyName("direction")]
|
||||
public string? Direction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Http request payload
|
||||
/// </summary>
|
||||
[JsonPropertyName("payload")]
|
||||
public string? Payload { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,34 @@
|
|||
using BotSharp.Abstraction.Browsing.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Browsing.Models;
|
||||
|
||||
public class ElementActionArgs
|
||||
{
|
||||
private string _action;
|
||||
public string Action => _action;
|
||||
public BroswerActionEnum Action { get; set; }
|
||||
|
||||
public ElementActionArgs(string action)
|
||||
public string? Content { get; set; }
|
||||
|
||||
public ElementPosition? Position { get; set; }
|
||||
|
||||
public string? PressKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required for deserialization
|
||||
/// </summary>
|
||||
public ElementActionArgs()
|
||||
{
|
||||
_action = action;
|
||||
|
||||
}
|
||||
|
||||
public ElementActionArgs(BroswerActionEnum action, ElementPosition? position = null)
|
||||
{
|
||||
Action = action;
|
||||
Position = position;
|
||||
}
|
||||
|
||||
public ElementActionArgs(BroswerActionEnum action, string content)
|
||||
{
|
||||
Action = action;
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ public class ElementLocatingArgs
|
|||
[JsonPropertyName("match_rule")]
|
||||
public string MatchRule { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("tag")]
|
||||
public string? Tag { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public string? Text { get; set; }
|
||||
|
||||
|
|
@ -20,5 +23,12 @@ public class ElementLocatingArgs
|
|||
[JsonPropertyName("selector")]
|
||||
public string? Selector { get; set; }
|
||||
|
||||
public bool Parent { get; set; }
|
||||
|
||||
public bool FailIfMultiple { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Draw outline around the element
|
||||
/// </summary>
|
||||
public bool Highlight { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Browsing.Models;
|
||||
|
||||
public class ElementPosition
|
||||
{
|
||||
public float X { get; set; } = default!;
|
||||
|
||||
public float Y { get; set; } = default!;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using System.Net.Http;
|
||||
|
||||
namespace BotSharp.Abstraction.Browsing.Models;
|
||||
|
||||
public class HttpRequestParams
|
||||
{
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("method")]
|
||||
public HttpMethod Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Http request payload
|
||||
/// </summary>
|
||||
[JsonPropertyName("payload")]
|
||||
public string? Payload { get; set; }
|
||||
|
||||
public HttpRequestParams(string url, HttpMethod method, string? payload = null)
|
||||
{
|
||||
Method = method;
|
||||
Url = url;
|
||||
Payload = payload;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@ namespace BotSharp.Abstraction.Browsing.Models;
|
|||
public class MessageInfo
|
||||
{
|
||||
public string AgentId { get; set; }
|
||||
public string ConversationId { get; set; }
|
||||
public string ContextId { get; set; }
|
||||
public string MessageId { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Browsing.Settings;
|
||||
|
||||
public class WebBrowsingSettings
|
||||
{
|
||||
public string Driver { get; set; } = "Playwright";
|
||||
}
|
||||
|
|
@ -44,6 +44,9 @@ public abstract class ConversationHookBase : IConversationHook
|
|||
public virtual Task OnConversationEnding(RoleDialogModel message)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public virtual Task OnNewTaskDetected(RoleDialogModel message, string reason)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public virtual Task OnTaskCompleted(RoleDialogModel message)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Conversations.Enums;
|
||||
|
||||
public class StateDataType
|
||||
{
|
||||
public const string String = "string";
|
||||
public const string Boolean = "boolean";
|
||||
public const string Number = "number";
|
||||
public const string Currency = "currency";
|
||||
public const string Date = "date";
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Conversations.Enums;
|
||||
|
||||
public class StateSource
|
||||
{
|
||||
public const string External = "external";
|
||||
public const string Application = "application";
|
||||
public const string User = "user";
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace BotSharp.Abstraction.Conversations;
|
||||
|
||||
public interface IConversationAttachmentService
|
||||
{
|
||||
string GetDirectory(string conversationId);
|
||||
}
|
||||
|
|
@ -64,6 +64,13 @@ public interface IConversationHook
|
|||
|
||||
Task OnResponseGenerated(RoleDialogModel message);
|
||||
|
||||
/// <summary>
|
||||
/// LLM detected user requested a new task different from previous topic.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
Task OnNewTaskDetected(RoleDialogModel message, string reason);
|
||||
|
||||
/// <summary>
|
||||
/// LLM detected the current task is completed.
|
||||
/// It's useful for the situation of multiple tasks in the same conversation.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,15 @@ public interface IConversationService
|
|||
Task<List<Conversation>> GetLastConversations();
|
||||
Task<List<string>> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
|
||||
Task<bool> DeleteConversations(IEnumerable<string> ids);
|
||||
Task<bool> TruncateConversation(string conversationId, string messageId);
|
||||
|
||||
/// <summary>
|
||||
/// Truncate conversation
|
||||
/// </summary>
|
||||
/// <param name="conversationId">Target conversation id</param>
|
||||
/// <param name="messageId">Target message id to delete</param>
|
||||
/// <param name="newMessageId">If not null, delete messages while input a new message; otherwise delete messages only</param>
|
||||
/// <returns></returns>
|
||||
Task<bool> TruncateConversation(string conversationId, string messageId, string? newMessageId = null);
|
||||
Task<List<ContentLogOutputModel>> GetConversationContentLogs(string conversationId);
|
||||
Task<List<ConversationStateLogModel>> GetConversationStateLogs(string conversationId);
|
||||
|
||||
|
|
@ -42,6 +50,10 @@ public interface IConversationService
|
|||
/// Use this feature when you want to hide some context from LLM.
|
||||
/// </summary>
|
||||
/// <param name="resetStates">Whether to reset all states</param>
|
||||
/// <param name="reason">Append user init words</param>
|
||||
/// <param name="excludedStates"></param>
|
||||
/// <returns></returns>
|
||||
Task UpdateBreakpoint(bool resetStates = false);
|
||||
Task UpdateBreakpoint(bool resetStates = false, string? reason = null, params string[] excludedStates);
|
||||
|
||||
Task<string> GetConversationSummary(IEnumerable<string> conversationId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations;
|
||||
|
|
@ -8,12 +9,14 @@ namespace BotSharp.Abstraction.Conversations;
|
|||
public interface IConversationStateService
|
||||
{
|
||||
string GetConversationId();
|
||||
Dictionary<string, string> Load(string conversationId);
|
||||
Dictionary<string, string> Load(string conversationId, bool isReadOnly = false);
|
||||
string GetState(string name, string defaultValue = "");
|
||||
bool ContainsState(string name);
|
||||
Dictionary<string, string> GetStates();
|
||||
IConversationStateService SetState<T>(string name, T value, bool isNeedVersion = true, int activeRounds = -1);
|
||||
IConversationStateService SetState<T>(string name, T value, bool isNeedVersion = true,
|
||||
int activeRounds = -1, string valueType = StateDataType.String, string source = StateSource.User, bool readOnly = false);
|
||||
void SaveStateByArgs(JsonDocument args);
|
||||
void CleanStates();
|
||||
bool RemoveState(string name);
|
||||
void CleanStates(params string[] excludedStates);
|
||||
void Save();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,34 +32,63 @@ public class Conversation
|
|||
|
||||
public class DialogElement
|
||||
{
|
||||
[JsonPropertyName("meta_data")]
|
||||
public DialogMetaData MetaData { get; set; }
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; }
|
||||
|
||||
[JsonPropertyName("secondary_content")]
|
||||
public string? SecondaryContent { get; set; }
|
||||
|
||||
[JsonPropertyName("rich_content")]
|
||||
public string? RichContent { get; set; }
|
||||
|
||||
[JsonPropertyName("secondary_rich_content")]
|
||||
public string? SecondaryRichContent { get; set; }
|
||||
|
||||
[JsonPropertyName("payload")]
|
||||
public string? Payload { get; set; }
|
||||
|
||||
public DialogElement()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DialogElement(DialogMetaData meta, string content, string? richContent = null)
|
||||
public DialogElement(DialogMetaData meta, string content, string? richContent = null,
|
||||
string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null)
|
||||
{
|
||||
MetaData = meta;
|
||||
Content = content;
|
||||
RichContent = richContent;
|
||||
SecondaryContent = secondaryContent;
|
||||
SecondaryRichContent = secondaryRichContent;
|
||||
Payload = payload;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{MetaData.Role}: {Content} [{MetaData.CreateTime}]";
|
||||
return $"{MetaData.Role}: {Content} [{MetaData?.CreateTime}]";
|
||||
}
|
||||
}
|
||||
|
||||
public class DialogMetaData
|
||||
{
|
||||
[JsonPropertyName("role")]
|
||||
public string Role { get; set; }
|
||||
|
||||
[JsonPropertyName("agent_id")]
|
||||
public string AgentId { get; set; }
|
||||
|
||||
[JsonPropertyName("message_id")]
|
||||
public string MessageId { get; set; }
|
||||
|
||||
[JsonPropertyName("function_name")]
|
||||
public string? FunctionName { get; set; }
|
||||
|
||||
[JsonPropertyName("sender_id")]
|
||||
public string? SenderId { get; set; }
|
||||
|
||||
[JsonPropertyName("create_at")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,7 @@ public class ConversationBreakpoint
|
|||
|
||||
[JsonPropertyName("created_time")]
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ public class ConversationSenderActionModel
|
|||
{
|
||||
[JsonPropertyName("conversation_id")]
|
||||
public string ConversationId { get; set; }
|
||||
|
||||
[JsonPropertyName("sender_action")]
|
||||
public SenderActionEnum SenderAction { get; set; }
|
||||
|
||||
[JsonPropertyName("indication")]
|
||||
public string? Indication { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@ public class IncomingMessageModel : MessageConfig
|
|||
/// Postback message
|
||||
/// </summary>
|
||||
public PostbackMessageModel? Postback { get; set; }
|
||||
|
||||
public List<BotSharpFile> Files { get; set; } = new List<BotSharpFile>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,20 @@ public class RoleDialogModel : ITrackableMessage
|
|||
|
||||
public string Content { get; set; }
|
||||
|
||||
public string? SecondaryContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Postback content
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("payload")]
|
||||
public string? Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicator message used to provide UI feedback for function execution
|
||||
/// </summary>
|
||||
public string? Indication { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string CurrentAgentId { get; set; }
|
||||
|
||||
|
|
@ -35,6 +49,12 @@ public class RoleDialogModel : ITrackableMessage
|
|||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FunctionName { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? ToolCallId { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? PostbackFunctionName { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FunctionArgs { get; set; }
|
||||
|
||||
|
|
@ -54,6 +74,9 @@ public class RoleDialogModel : ITrackableMessage
|
|||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public RichContent<IRichMessage>? RichContent { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public RichContent<IRichMessage>? SecondaryRichContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stop conversation completion
|
||||
/// </summary>
|
||||
|
|
@ -62,6 +85,8 @@ public class RoleDialogModel : ITrackableMessage
|
|||
|
||||
public FunctionCallFromLlm Instruction { get; set; }
|
||||
|
||||
public List<BotSharpFile> Files { get; set; } = new List<BotSharpFile>();
|
||||
|
||||
private RoleDialogModel()
|
||||
{
|
||||
}
|
||||
|
|
@ -95,6 +120,8 @@ public class RoleDialogModel : ITrackableMessage
|
|||
MessageId = source.MessageId,
|
||||
FunctionArgs = source.FunctionArgs,
|
||||
FunctionName = source.FunctionName,
|
||||
ToolCallId = source.ToolCallId,
|
||||
PostbackFunctionName = source.PostbackFunctionName,
|
||||
RichContent = source.RichContent,
|
||||
StopCompletion = source.StopCompletion,
|
||||
Instruction = source.Instruction,
|
||||
|
|
|
|||
|
|
@ -12,14 +12,23 @@ public class StateChangeModel
|
|||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("before_value")]
|
||||
public string BeforeValue { get; set; }
|
||||
public string? BeforeValue { get; set; }
|
||||
|
||||
[JsonPropertyName("before_active_rounds")]
|
||||
public int? BeforeActiveRounds { get; set; }
|
||||
|
||||
[JsonPropertyName("after_value")]
|
||||
public string AfterValue { get; set; }
|
||||
public string? AfterValue { get; set; }
|
||||
|
||||
[JsonPropertyName("after_active_rounds")]
|
||||
public int? AfterActiveRounds { get; set; }
|
||||
|
||||
[JsonPropertyName("data_type")]
|
||||
public string DataType { get; set; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
[JsonPropertyName("readonly")]
|
||||
public bool Readonly { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations.Models;
|
||||
|
||||
public class StateKeyValue
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public bool Versioning { get; set; }
|
||||
public bool Readonly { get; set; }
|
||||
public List<StateValue> Values { get; set; } = new List<StateValue>();
|
||||
|
||||
public StateKeyValue()
|
||||
|
|
@ -16,6 +19,12 @@ public class StateKeyValue
|
|||
Key = key;
|
||||
Values = values;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var lastValue = Values.LastOrDefault();
|
||||
return $"{Key} => ({lastValue?.ToString()})";
|
||||
}
|
||||
}
|
||||
|
||||
public class StateValue
|
||||
|
|
@ -30,6 +39,12 @@ public class StateValue
|
|||
[JsonPropertyName("active_rounds")]
|
||||
public int ActiveRounds { get; set; }
|
||||
|
||||
[JsonPropertyName("data_type")]
|
||||
public string DataType { get; set; } = StateDataType.String;
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
[JsonPropertyName("update_time")]
|
||||
public DateTime UpdateTime { get; set; }
|
||||
|
||||
|
|
@ -37,4 +52,11 @@ public class StateValue
|
|||
{
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var isActive = Active ? "Yes" : "No";
|
||||
var activeRounds = ActiveRounds <= 0 ? "infinity" : ActiveRounds.ToString();
|
||||
return $"Data: {Data}, Active: {isActive}, Active rounds: {activeRounds}, Source: {Source}";
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@ namespace BotSharp.Abstraction.Conversations.Settings;
|
|||
public class RateLimitSetting
|
||||
{
|
||||
public int MaxConversationPerDay { get; set; } = 100;
|
||||
public int MaxInputLengthPerRequest { get; set; } = 256;
|
||||
public int MaxInputLengthPerRequest { get; set; } = 512;
|
||||
public int MinTimeSecondsBetweenMessages { get; set; } = 2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public interface IBotSharpFileService
|
||||
{
|
||||
string GetDirectory(string conversationId);
|
||||
IEnumerable<MessageFileModel> GetChatImages(string conversationId, List<RoleDialogModel> conversations, int offset = 2);
|
||||
IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, bool imageOnly = false);
|
||||
string GetMessageFile(string conversationId, string messageId, string fileName);
|
||||
bool SaveMessageFiles(string conversationId, string messageId, List<BotSharpFile> files);
|
||||
|
||||
string GetUserAvatar();
|
||||
bool SaveUserAvatar(BotSharpFile file);
|
||||
|
||||
/// <summary>
|
||||
/// Delete files under messages
|
||||
/// </summary>
|
||||
/// <param name="conversationId">Conversation Id</param>
|
||||
/// <param name="messageIds">Files in these messages will be deleted</param>
|
||||
/// <param name="targetMessageId">The starting message to delete</param>
|
||||
/// <param name="newMessageId">If not null, delete messages while input a new message; otherwise, delete messages only</param>
|
||||
/// <returns></returns>
|
||||
bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId, string? newMessageId = null);
|
||||
bool DeleteConversationFiles(IEnumerable<string> conversationIds);
|
||||
|
||||
/// <summary>
|
||||
/// Get file bytes and content type from data, e.g., "data:image/png;base64,aaaaaaaaa"
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
(string, byte[]) GetFileInfoFromData(string data);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
namespace BotSharp.Abstraction.Files.Models;
|
||||
|
||||
public class BotSharpFile
|
||||
{
|
||||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// File data, e.g., "data:image/png;base64,aaaaaaaa"
|
||||
/// </summary>
|
||||
[JsonPropertyName("file_data")]
|
||||
public string FileData { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("file_url")]
|
||||
public string FileUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
namespace BotSharp.Abstraction.Files.Models;
|
||||
|
||||
public class MessageFileModel
|
||||
{
|
||||
[JsonPropertyName("message_id")]
|
||||
public string MessageId { get; set; }
|
||||
|
||||
[JsonPropertyName("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("file_storage_url")]
|
||||
public string FileStorageUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonPropertyName("file_type")]
|
||||
public string FileType { get; set; }
|
||||
|
||||
[JsonPropertyName("content_type")]
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public MessageFileModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"File name: {FileName}, File type: {FileType}, Content type: {ContentType}";
|
||||
}
|
||||
}
|
||||
|
|
@ -3,5 +3,11 @@ namespace BotSharp.Abstraction.Functions;
|
|||
public interface IFunctionCallback
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicator message used to provide UI feedback for function execution
|
||||
/// </summary>
|
||||
string Indication => string.Empty;
|
||||
|
||||
Task<bool> Execute(RoleDialogModel message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Functions.Models;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public class FunctionCallingResponse
|
|||
[JsonPropertyName("content")]
|
||||
public string? Content { get; set; }
|
||||
|
||||
[JsonPropertyName("function_name")]
|
||||
[JsonPropertyName("function")]
|
||||
public string? FunctionName { get; set; }
|
||||
|
||||
[JsonPropertyName("args")]
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ namespace BotSharp.Abstraction.Functions.Models;
|
|||
|
||||
public class FunctionDef
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("visibility_expression")]
|
||||
public string? VisibilityExpression { get; set; }
|
||||
|
|
@ -11,6 +14,7 @@ public class FunctionDef
|
|||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Impact { get; set; }
|
||||
|
||||
[JsonPropertyName("parameters")]
|
||||
public FunctionParametersDef Parameters { get; set; } = new FunctionParametersDef();
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ public class FunctionParametersDef
|
|||
[JsonPropertyName("required")]
|
||||
public List<string> Required { get; set; } = new List<string>();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{\"type\":\"{Type}\", \"properties\":{JsonSerializer.Serialize(Properties)}, \"required\":[{string.Join(",", Required.Select(x => "\"" + x + "\""))}]}}";
|
||||
}
|
||||
|
||||
public FunctionParametersDef()
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ namespace BotSharp.Abstraction.Functions.Models;
|
|||
|
||||
public class ParameterPropertyDef : NameDesc
|
||||
{
|
||||
public ParameterPropertyDef(string name, string description, string type = "string")
|
||||
public ParameterPropertyDef(string name, string description, string type = "string", bool required = false)
|
||||
: base(name, description)
|
||||
{
|
||||
Type = type;
|
||||
Required = required;
|
||||
}
|
||||
|
||||
[JsonPropertyName("required")]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Google.Models;
|
|||
public class GoogleVideoResult
|
||||
{
|
||||
public string Kind { get; set; }
|
||||
public IList<VideoItem> Items { get; set; } = new List<VideoItem>();
|
||||
public List<VideoItem> Items { get; set; } = new List<VideoItem>();
|
||||
}
|
||||
|
||||
public class VideoItem
|
||||
|
|
@ -25,6 +25,7 @@ public class VideoSnippet
|
|||
{
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string ChannelId { get; set; }
|
||||
public string ChannelTitle { get; set; }
|
||||
public VideoThumbnails Thumbnails { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,5 @@ public class YoutubeSettings
|
|||
public string Endpoint { get; set; }
|
||||
public string Part { get; set; }
|
||||
public string RegionCode { get; set; }
|
||||
public IList<string> Channels { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Http.Settings;
|
||||
|
||||
public class HttpSettings
|
||||
{
|
||||
public string BaseAddress { get; set; } = string.Empty;
|
||||
public string Origin { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Infrastructures.Enums;
|
||||
|
||||
public class LanguageType
|
||||
{
|
||||
public const string UNKNOWN = "Unknown";
|
||||
public const string ENGLISH = "English";
|
||||
public const string SPANISH = "Spanish";
|
||||
public const string CHINESE = "Chinese";
|
||||
}
|
||||
|
|
@ -5,5 +5,8 @@ public class StateConst
|
|||
public const string EXPECTED_ACTION_AGENT = "expected_next_action_agent";
|
||||
public const string EXPECTED_GOAL_AGENT = "expected_user_goal_agent";
|
||||
public const string NEXT_ACTION_AGENT = "next_action_agent";
|
||||
public const string NEXT_ACTION_REASON = "next_action_reason";
|
||||
public const string USER_GOAL_AGENT = "user_goal_agent";
|
||||
|
||||
public const string LANGUAGE = "language";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ public interface ILlmProviderService
|
|||
{
|
||||
LlmModelSetting GetSetting(string provider, string model);
|
||||
List<string> GetProviders();
|
||||
LlmModelSetting GetProviderModel(string provider, string id);
|
||||
LlmModelSetting GetProviderModel(string provider, string id, bool? multiModal = null);
|
||||
List<LlmModelSetting> GetProviderModels(string provider);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ public class LlmModelSetting
|
|||
public string Endpoint { get; set; }
|
||||
public LlmModelType Type { get; set; } = LlmModelType.Chat;
|
||||
|
||||
/// <summary>
|
||||
/// If true, allow sending images/vidoes to this model
|
||||
/// </summary>
|
||||
public bool MultiModal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prompt cost per 1K token
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using System.Text.Json;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BotSharp.Core.Messaging;
|
||||
namespace BotSharp.Abstraction.Messaging;
|
||||
|
||||
public static class MessageParser
|
||||
public static class BotSharpMessageParser
|
||||
{
|
||||
|
||||
public static IRichMessage? ParseRichMessage(JsonElement root, JsonSerializerOptions options)
|
||||
{
|
||||
IRichMessage? res = null;
|
||||
Type? targetType = null;
|
||||
JsonElement element;
|
||||
var jsonText = root.GetRawText();
|
||||
|
||||
|
|
@ -20,47 +20,52 @@ public static class MessageParser
|
|||
var richType = element.GetString();
|
||||
if (richType == RichTypeEnum.ButtonTemplate)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<ButtonTemplateMessage>(jsonText, options);
|
||||
targetType = typeof(ButtonTemplateMessage);
|
||||
}
|
||||
else if (richType == RichTypeEnum.MultiSelectTemplate)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<MultiSelectTemplateMessage>(jsonText, options);
|
||||
targetType = typeof(MultiSelectTemplateMessage);
|
||||
}
|
||||
else if (richType == RichTypeEnum.QuickReply)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<QuickReplyMessage>(jsonText, options);
|
||||
targetType = typeof(QuickReplyMessage);
|
||||
}
|
||||
else if (richType == RichTypeEnum.CouponTemplate)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<CouponTemplateMessage>(jsonText, options);
|
||||
targetType = typeof(CouponTemplateMessage);
|
||||
}
|
||||
else if (richType == RichTypeEnum.Text)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<TextMessage>(jsonText, options);
|
||||
targetType = typeof(TextMessage);
|
||||
}
|
||||
else if (richType == RichTypeEnum.GenericTemplate)
|
||||
{
|
||||
if (root.TryGetProperty("element_type", out element))
|
||||
{
|
||||
var elementType = element.GetString();
|
||||
if (elementType == typeof(GenericElement).Name)
|
||||
var wrapperType = typeof(GenericTemplateMessage<>);
|
||||
var genericType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x => x.Name == elementType);
|
||||
|
||||
if (wrapperType != null && genericType != null)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
|
||||
}
|
||||
else if (elementType == typeof(ButtonElement).Name)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<ButtonElement>>(jsonText, options);
|
||||
targetType = wrapperType.MakeGenericType(genericType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType != null)
|
||||
{
|
||||
res = JsonSerializer.Deserialize(jsonText, targetType, options) as IRichMessage;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static ITemplateMessage? ParseTemplateMessage(JsonElement root, JsonSerializerOptions options)
|
||||
{
|
||||
ITemplateMessage? res = null;
|
||||
Type? targetType = null;
|
||||
JsonElement element;
|
||||
var jsonText = root.GetRawText();
|
||||
|
||||
|
|
@ -69,37 +74,41 @@ public static class MessageParser
|
|||
var templateType = element.GetString();
|
||||
if (templateType == TemplateTypeEnum.Button)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<ButtonTemplateMessage>(jsonText, options);
|
||||
targetType = typeof(ButtonTemplateMessage);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.MultiSelect)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<MultiSelectTemplateMessage>(jsonText, options);
|
||||
targetType = typeof(MultiSelectTemplateMessage);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Coupon)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<CouponTemplateMessage>(jsonText, options);
|
||||
targetType = typeof(CouponTemplateMessage);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Product)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<ProductTemplateMessage>(jsonText, options);
|
||||
targetType = typeof(ProductTemplateMessage);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Generic)
|
||||
{
|
||||
if (root.TryGetProperty("element_type", out element))
|
||||
{
|
||||
var elementType = element.GetString();
|
||||
if (elementType == typeof(GenericElement).Name)
|
||||
var wrapperType = typeof(GenericTemplateMessage<>);
|
||||
var genericType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x => x.Name == elementType);
|
||||
|
||||
if (wrapperType != null && genericType != null)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
|
||||
}
|
||||
else if (elementType == typeof(ButtonElement).Name)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<ButtonElement>>(jsonText, options);
|
||||
targetType = wrapperType.MakeGenericType(genericType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType != null)
|
||||
{
|
||||
res = JsonSerializer.Deserialize(jsonText, targetType, options) as ITemplateMessage;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ public static class EditorTypeEnum
|
|||
public const string DateTimePicker = "datetime-picker";
|
||||
public const string DateTimeRangePicker = "datetime-range-picker";
|
||||
public const string Email = "email";
|
||||
public const string File = "file";
|
||||
|
||||
/// <summary>
|
||||
/// Regex, set the expression in editor_attributes
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging;
|
||||
|
||||
public interface IRichMessage
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Core.Messaging;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
|
|
@ -9,8 +8,7 @@ public class RichContentJsonConverter : JsonConverter<IRichMessage>
|
|||
{
|
||||
using var jsonDoc = JsonDocument.ParseValue(ref reader);
|
||||
var root = jsonDoc.RootElement;
|
||||
var jsonText = root.GetRawText();
|
||||
var res = MessageParser.ParseRichMessage(root, options);
|
||||
var res = BotSharpMessageParser.ParseRichMessage(root, options);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Core.Messaging;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
|
|
@ -9,8 +8,7 @@ public class TemplateMessageJsonConverter : JsonConverter<ITemplateMessage>
|
|||
{
|
||||
using var jsonDoc = JsonDocument.ParseValue(ref reader);
|
||||
var root = jsonDoc.RootElement;
|
||||
var jsonText = root.GetRawText();
|
||||
var res = MessageParser.ParseTemplateMessage(root, options);
|
||||
var res = BotSharpMessageParser.ParseTemplateMessage(root, options);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,30 @@ namespace BotSharp.Abstraction.Messaging.Models.RichContent;
|
|||
/// </summary>
|
||||
public class ElementButton
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Type { get; set; } = "web_url";
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Url { get; set; }
|
||||
public string? Url { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
[Translate]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[Translate]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Payload { get; set; }
|
||||
public string? Payload { get; set; }
|
||||
|
||||
[JsonPropertyName("is_primary")]
|
||||
public bool IsPrimary { get; set; }
|
||||
|
||||
[JsonPropertyName("is_secondary")]
|
||||
public bool IsSecondary { get; set; }
|
||||
|
||||
[JsonPropertyName("post_action_disclaimer")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[Translate]
|
||||
public string? PostActionDisclaimer { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
|
||||
public class QuickReplyMessage : IRichMessage
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
|
||||
public class RichContent<T> where T : IRichMessage
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -11,33 +9,15 @@ public class ButtonTemplateMessage : IRichMessage, ITemplateMessage
|
|||
public string RichType => RichTypeEnum.ButtonTemplate;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
[Translate]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("template_type")]
|
||||
public string TemplateType => TemplateTypeEnum.Button;
|
||||
|
||||
[JsonPropertyName("buttons")]
|
||||
public ButtonElement[] Buttons { get; set; } = new ButtonElement[0];
|
||||
public ElementButton[] Buttons { get; set; } = new ElementButton[0];
|
||||
|
||||
[JsonPropertyName("is_horizontal")]
|
||||
public bool IsHorizontal { get; set; }
|
||||
}
|
||||
|
||||
public class ButtonElement
|
||||
{
|
||||
/// <summary>
|
||||
/// web_url, postback, phone_number
|
||||
/// </summary>
|
||||
public string Type { get; set; } = "web_url";
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Url { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Payload { get; set; }
|
||||
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("is_primary")]
|
||||
public bool IsPrimary { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -10,6 +8,7 @@ public class CouponTemplateMessage : IRichMessage, ITemplateMessage
|
|||
{
|
||||
[JsonPropertyName("rich_type")]
|
||||
public string RichType => RichTypeEnum.CouponTemplate;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
|
||||
public class GenericTemplateMessage<T> : IRichMessage, ITemplateMessage
|
||||
|
|
@ -8,6 +6,7 @@ public class GenericTemplateMessage<T> : IRichMessage, ITemplateMessage
|
|||
public string RichType => RichTypeEnum.GenericTemplate;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
[Translate]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("template_type")]
|
||||
|
|
@ -19,16 +18,24 @@ public class GenericTemplateMessage<T> : IRichMessage, ITemplateMessage
|
|||
[JsonPropertyName("is_horizontal")]
|
||||
public bool IsHorizontal { get; set; }
|
||||
|
||||
[JsonPropertyName("is_popup")]
|
||||
public bool IsPopup { get; set; }
|
||||
|
||||
[JsonPropertyName("element_type")]
|
||||
public string ElementType => typeof(T).Name;
|
||||
}
|
||||
|
||||
public class GenericElement
|
||||
{
|
||||
[Translate]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Translate]
|
||||
public string Subtitle { get; set; }
|
||||
|
||||
[JsonPropertyName("image_url")]
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("default_action")]
|
||||
public ElementAction DefaultAction { get; set; }
|
||||
public ElementButton[] Buttons { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
|
||||
public class MultiSelectTemplateMessage : IRichMessage, ITemplateMessage
|
||||
{
|
||||
[JsonPropertyName("rich_type")]
|
||||
public string RichType => RichTypeEnum.MultiSelectTemplate;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
[Translate]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("template_type")]
|
||||
|
|
@ -21,6 +21,7 @@ public class MultiSelectTemplateMessage : IRichMessage, ITemplateMessage
|
|||
|
||||
public class OptionElement
|
||||
{
|
||||
[Translate]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string? Payload { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
|
||||
public class ProductTemplateMessage : IRichMessage, ITemplateMessage
|
||||
|
|
@ -8,6 +6,7 @@ public class ProductTemplateMessage : IRichMessage, ITemplateMessage
|
|||
public string RichType => RichTypeEnum.GenericTemplate;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
[Translate]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("template_type")]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
|
||||
public class TextMessage : IRichMessage
|
||||
|
|
@ -7,6 +5,7 @@ public class TextMessage : IRichMessage
|
|||
[JsonPropertyName("rich_type")]
|
||||
public string RichType => RichTypeEnum.Text;
|
||||
|
||||
[Translate]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
public TextMessage(string text)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ public class PluginMenuDef
|
|||
[JsonIgnore]
|
||||
public int Weight { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string>? Roles { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public List<PluginMenuDef>? SubMenu { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public class BotSharpDatabaseSettings : DatabaseBasicSettings
|
|||
public string BotSharpMongoDb { get; set; }
|
||||
public string TablePrefix { get; set; }
|
||||
public DbConnectionSetting BotSharp { get; set; }
|
||||
public string Redis { get; set; }
|
||||
}
|
||||
|
||||
public class DatabaseBasicSettings
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Repositories.Enums;
|
||||
|
||||
public static class RepositoryEnum
|
||||
{
|
||||
public const string FileRepository = nameof(FileRepository);
|
||||
public const string MongoRepository = nameof(MongoRepository);
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Repositories.Models;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
|
||||
|
|
@ -18,10 +17,11 @@ public interface IBotSharpRepository
|
|||
#endregion
|
||||
|
||||
#region User
|
||||
User? GetUserByEmail(string email);
|
||||
User? GetUserById(string id);
|
||||
User? GetUserByUserName(string userName);
|
||||
void CreateUser(User user);
|
||||
User? GetUserByEmail(string email) => throw new NotImplementedException();
|
||||
User? GetUserById(string id) => throw new NotImplementedException();
|
||||
User? GetUserByUserName(string userName) => throw new NotImplementedException();
|
||||
void CreateUser(User user) => throw new NotImplementedException();
|
||||
void UpdateUserVerified(string userId) => throw new NotImplementedException();
|
||||
#endregion
|
||||
|
||||
#region Agent
|
||||
|
|
@ -32,8 +32,10 @@ public interface IBotSharpRepository
|
|||
void BulkInsertAgents(List<Agent> agents);
|
||||
void BulkInsertUserAgents(List<UserAgent> userAgents);
|
||||
bool DeleteAgents();
|
||||
bool DeleteAgent(string agentId);
|
||||
List<string> GetAgentResponses(string agentId, string prefix, string intent);
|
||||
string GetAgentTemplate(string agentId, string templateName);
|
||||
bool PatchAgentTemplate(string agentId, AgentTemplate template);
|
||||
#endregion
|
||||
|
||||
#region Agent Task
|
||||
|
|
@ -42,7 +44,7 @@ public interface IBotSharpRepository
|
|||
void InsertAgentTask(AgentTask task);
|
||||
void BulkInsertAgentTasks(List<AgentTask> tasks);
|
||||
void UpdateAgentTask(AgentTask task, AgentTaskField field);
|
||||
bool DeleteAgentTask(string agentId, string taskId);
|
||||
bool DeleteAgentTask(string agentId, List<string> taskIds);
|
||||
bool DeleteAgentTasks();
|
||||
#endregion
|
||||
|
||||
|
|
@ -50,7 +52,6 @@ public interface IBotSharpRepository
|
|||
void CreateNewConversation(Conversation conversation);
|
||||
bool DeleteConversations(IEnumerable<string> conversationIds);
|
||||
List<DialogElement> GetConversationDialogs(string conversationId);
|
||||
void UpdateConversationDialogElements(string conversationId, List<DialogContentUpdateModel> updateElements);
|
||||
void AppendConversationDialogs(string conversationId, List<DialogElement> dialogs);
|
||||
ConversationState GetConversationStates(string conversationId);
|
||||
void UpdateConversationStates(string conversationId, List<StateKeyValue> states);
|
||||
|
|
@ -58,11 +59,11 @@ public interface IBotSharpRepository
|
|||
Conversation GetConversation(string conversationId);
|
||||
PagedItems<Conversation> GetConversations(ConversationFilter filter);
|
||||
void UpdateConversationTitle(string conversationId, string title);
|
||||
void UpdateConversationBreakpoint(string conversationId, string messageId, DateTime breakpoint);
|
||||
DateTime GetConversationBreakpoint(string conversationId);
|
||||
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
|
||||
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
|
||||
List<Conversation> GetLastConversations();
|
||||
List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
|
||||
bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
|
||||
IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
|
||||
#endregion
|
||||
|
||||
#region Execution Log
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ namespace BotSharp.Abstraction.Routing;
|
|||
public interface IRoutingContext
|
||||
{
|
||||
string GetCurrentAgentId();
|
||||
string PreviousAgentId();
|
||||
string FirstGoalAgentId();
|
||||
bool ContainsAgentId(string agentId);
|
||||
string OriginAgentId { get; }
|
||||
string ConversationId { get; }
|
||||
string MessageId { get; }
|
||||
|
|
@ -13,6 +14,7 @@ public interface IRoutingContext
|
|||
int AgentCount { get; }
|
||||
void Push(string agentId, string? reason = null);
|
||||
void Pop(string? reason = null);
|
||||
void PopTo(string agentId, string reason);
|
||||
void Replace(string agentId, string? reason = null);
|
||||
void Empty(string? reason = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@ public interface IRoutingHandler
|
|||
|
||||
void SetDialogs(List<RoleDialogModel> dialogs);
|
||||
|
||||
Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message);
|
||||
Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message, Func<RoleDialogModel, Task> onFunctionExecuting);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Routing;
|
||||
|
||||
public interface IRoutingService
|
||||
|
|
@ -30,9 +28,9 @@ public interface IRoutingService
|
|||
|
||||
List<RoutingHandlerDef> GetHandlers(Agent router);
|
||||
void ResetRecursiveCounter();
|
||||
Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs);
|
||||
Task<bool> InvokeFunction(string name, RoleDialogModel message);
|
||||
Task<RoleDialogModel> InstructLoop(RoleDialogModel message, List<RoleDialogModel> dialogs);
|
||||
Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs, Func<RoleDialogModel, Task> onFunctionExecuting);
|
||||
Task<bool> InvokeFunction(string name, RoleDialogModel messages, Func<RoleDialogModel, Task>? onFunctionExecuting = null);
|
||||
Task<RoleDialogModel> InstructLoop(RoleDialogModel message, List<RoleDialogModel> dialogs, Func<RoleDialogModel, Task> onFunctionExecuting);
|
||||
|
||||
/// <summary>
|
||||
/// Talk to a specific Agent directly, bypassing the Router
|
||||
|
|
@ -44,5 +42,5 @@ public interface IRoutingService
|
|||
|
||||
Task<string> GetConversationContent(List<RoleDialogModel> dialogs, int maxDialogCount = 50);
|
||||
|
||||
bool HasMissingRequiredField(RoleDialogModel message, out string agentId);
|
||||
(bool, string) HasMissingRequiredField(RoleDialogModel message, out string agentId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Routing.Models;
|
|||
public class RoutingArgs
|
||||
{
|
||||
[JsonPropertyName("function")]
|
||||
public string Function { get; set; }
|
||||
public string Function { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The reason why you select this function or agent
|
||||
|
|
@ -19,30 +19,36 @@ public class RoutingArgs
|
|||
[JsonPropertyName("conversation_end")]
|
||||
public bool ConversationEnd { get; set; }
|
||||
|
||||
[JsonPropertyName("task_completed")]
|
||||
public bool TaskCompleted { get; set; }
|
||||
|
||||
[JsonPropertyName("is_new_task")]
|
||||
public bool IsNewTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The content of replying to user
|
||||
/// </summary>
|
||||
[JsonPropertyName("response")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Response { get; set; }
|
||||
public string Response { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Agent for next action based on user latest response
|
||||
/// </summary>
|
||||
[JsonPropertyName("next_action_agent")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string AgentName { get; set; }
|
||||
public string AgentName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Agent who can achieve user original goal
|
||||
/// </summary>
|
||||
[JsonPropertyName("user_goal_agent")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string OriginalAgent { get; set; }
|
||||
public string OriginalAgent { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("user_goal_description")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string UserGoal { get; set; }
|
||||
public string UserGoal { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ public interface IExecutor
|
|||
Task<RoleDialogModel> Execute(IRoutingService routing,
|
||||
FunctionCallFromLlm inst,
|
||||
RoleDialogModel message,
|
||||
List<RoleDialogModel> dialogs);
|
||||
List<RoleDialogModel> dialogs,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Translation.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)]
|
||||
public class TranslateAttribute : Attribute
|
||||
{
|
||||
public TranslateAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Translation;
|
||||
|
||||
public interface ITranslationService
|
||||
{
|
||||
Task<T> Translate<T>(Agent router, string messageId, T data, string language = "Spanish", bool clone = true) where T : class;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Translation.Models;
|
||||
|
||||
public class TranslationInput
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; } = -1;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = null!;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
namespace BotSharp.Abstraction.Translation.Models;
|
||||
|
||||
public class TranslationOutput
|
||||
{
|
||||
[JsonPropertyName("input_lang")]
|
||||
public string InputLanguage { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("output_lang")]
|
||||
public string OutputLanguage { get; set; } = LanguageType.ENGLISH;
|
||||
|
||||
[JsonPropertyName("texts")]
|
||||
public TranslationInput[] Texts { get; set; } = Array.Empty<TranslationInput>();
|
||||
}
|
||||
|
|
@ -8,4 +8,5 @@ public interface IAuthenticationHook
|
|||
Task<User> Authenticate(string id, string password);
|
||||
void AddClaims(List<Claim> claims);
|
||||
void BeforeSending(Token token);
|
||||
Task UserCreated(User user);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ public interface IUserIdentity
|
|||
string FirstName { get; }
|
||||
string LastName { get; }
|
||||
string FullName { get; }
|
||||
string? UserLanguage { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Users.Models;
|
||||
using BotSharp.OpenAPI.ViewModels.Users;
|
||||
|
||||
namespace BotSharp.Abstraction.Users;
|
||||
|
||||
|
|
@ -6,6 +7,9 @@ public interface IUserService
|
|||
{
|
||||
Task<User> GetUser(string id);
|
||||
Task<User> CreateUser(User user);
|
||||
Task<Token> GetToken(string authorization);
|
||||
Task<Token> ActiveUser(UserActivationModel model);
|
||||
Task<Token?> GetToken(string authorization);
|
||||
Task<User> GetMyProfile();
|
||||
Task<bool> VerifyUserNameExisting(string userName);
|
||||
Task<bool> VerifyEmailExisting(string email);
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ public class User
|
|||
public string Source { get; set; } = "internal";
|
||||
public string? ExternalId { get; set; }
|
||||
public string Role { get; set; } = UserRole.Client;
|
||||
public string? VerificationCode { get; set; }
|
||||
public bool Verified { get; set; }
|
||||
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.OpenAPI.ViewModels.Users;
|
||||
|
||||
public class UserActivationModel
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public string VerificationCode { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Users.Settings;
|
||||
|
||||
public class AccountSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to enable verification code to verify the authenticity of new users
|
||||
/// </summary>
|
||||
public bool NewUserVerification { get; set; }
|
||||
}
|
||||
|
|
@ -14,3 +14,6 @@ global using BotSharp.Abstraction.Models;
|
|||
global using BotSharp.Abstraction.Routing.Models;
|
||||
global using BotSharp.Abstraction.Routing.Planning;
|
||||
global using BotSharp.Abstraction.Templating;
|
||||
global using BotSharp.Abstraction.Translation.Attributes;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Abstraction.Files.Models;
|
||||
|
|
@ -13,19 +13,26 @@ public class Pagination
|
|||
|
||||
public int Size
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (_size <= 0) return 20;
|
||||
if (_size > 100) return 100;
|
||||
|
||||
return _size;
|
||||
}
|
||||
set
|
||||
return _size > 0 ? _size : 1;
|
||||
}
|
||||
set
|
||||
{
|
||||
_size = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort by field
|
||||
/// </summary>
|
||||
public string? Sort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sort order: asc or desc
|
||||
/// </summary>
|
||||
public string Order { get; set; } = "asc";
|
||||
|
||||
public int Offset
|
||||
{
|
||||
get { return (Page - 1) * Size; }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Core.Agents;
|
||||
|
|
@ -14,6 +15,13 @@ public class AgentPlugin : IBotSharpPlugin
|
|||
public SettingsMeta Settings =>
|
||||
new SettingsMeta("Agent");
|
||||
|
||||
public string[] AgentIds => new string[]
|
||||
{
|
||||
"01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
|
||||
"01e2fc5c-2c89-4ec7-8470-7688608b496c",
|
||||
"01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b"
|
||||
};
|
||||
|
||||
public object GetNewSettingsInstance() =>
|
||||
new AgentSettings();
|
||||
|
||||
|
|
@ -36,8 +44,8 @@ public class AgentPlugin : IBotSharpPlugin
|
|||
{
|
||||
SubMenu = new List<PluginMenuDef>
|
||||
{
|
||||
new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin"
|
||||
new PluginMenuDef("Evaluating", link: "page/agent/evaluator"), // icon: "bx bx-task"
|
||||
new PluginMenuDef("Routing", link: "page/agent/router") { Roles = new List<string> { UserRole.Admin } }, // icon: "bx bx-map-pin"
|
||||
new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List<string> { UserRole.Admin } }, // icon: "bx bx-task"
|
||||
new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot"
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
|
@ -26,32 +22,13 @@ public partial class AgentService
|
|||
|
||||
var dbSettings = _services.GetRequiredService<BotSharpDatabaseSettings>();
|
||||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir);
|
||||
var foundAgent = FetchAgentFileByName(agent.Name, filePath);
|
||||
|
||||
if (foundAgent != null)
|
||||
{
|
||||
agentRecord.SetId(foundAgent.Id)
|
||||
.SetName(foundAgent.Name)
|
||||
.SetDescription(foundAgent.Description)
|
||||
.SetIsPublic(foundAgent.IsPublic)
|
||||
.SetDisabled(foundAgent.Disabled)
|
||||
.SetAgentType(foundAgent.Type)
|
||||
.SetProfiles(foundAgent.Profiles)
|
||||
.SetRoutingRules(foundAgent.RoutingRules)
|
||||
.SetInstruction(foundAgent.Instruction)
|
||||
.SetTemplates(foundAgent.Templates)
|
||||
.SetFunctions(foundAgent.Functions)
|
||||
.SetResponses(foundAgent.Responses)
|
||||
.SetLlmConfig(foundAgent.LlmConfig);
|
||||
}
|
||||
|
||||
var user = _db.GetUserById(_user.Id);
|
||||
var userAgentRecord = new UserAgent
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
UserId = user.Id,
|
||||
AgentId = foundAgent?.Id ?? agentRecord.Id,
|
||||
AgentId = agentRecord.Id,
|
||||
Editable = false,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow
|
||||
|
|
@ -65,7 +42,7 @@ public partial class AgentService
|
|||
|
||||
Utilities.ClearCache();
|
||||
|
||||
return agentRecord;
|
||||
return await Task.FromResult(agentRecord);
|
||||
}
|
||||
|
||||
private Agent FetchAgentFileByName(string agentName, string filePath)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
using BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
||||
public partial class AgentService
|
||||
{
|
||||
public async Task<bool> DeleteAgent(string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var user = _db.GetUserById(_user.Id);
|
||||
var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id));
|
||||
|
||||
if (user?.Role != UserRole.Admin && agent == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var deleted = _db.DeleteAgent(id);
|
||||
return await Task.FromResult(deleted);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,89 @@
|
|||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
||||
public partial class AgentService
|
||||
{
|
||||
public async Task RefreshAgents()
|
||||
public async Task<string> RefreshAgents()
|
||||
{
|
||||
var isAgentDeleted = _db.DeleteAgents();
|
||||
var isTaskDeleted = _db.DeleteAgentTasks();
|
||||
if (!isAgentDeleted) return;
|
||||
|
||||
string refreshResult;
|
||||
var dbSettings = _services.GetRequiredService<BotSharpDatabaseSettings>();
|
||||
if (dbSettings.Default == RepositoryEnum.FileRepository)
|
||||
{
|
||||
refreshResult = $"Invalid database repository setting: {dbSettings.Default}";
|
||||
_logger.LogWarning(refreshResult);
|
||||
return refreshResult;
|
||||
}
|
||||
|
||||
var agentDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
|
||||
dbSettings.FileRepository,
|
||||
_agentSettings.DataDir);
|
||||
|
||||
if (!Directory.Exists(agentDir))
|
||||
{
|
||||
refreshResult = $"Cannot find the directory: {agentDir}";
|
||||
return refreshResult;
|
||||
}
|
||||
|
||||
var user = _db.GetUserById(_user.Id);
|
||||
var agents = new List<Agent>();
|
||||
var userAgents = new List<UserAgent>();
|
||||
var agentTasks = new List<AgentTask>();
|
||||
var refreshedAgents = new List<string>();
|
||||
|
||||
foreach (var dir in Directory.GetDirectories(agentDir))
|
||||
{
|
||||
var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
|
||||
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
|
||||
if (agent == null) continue;
|
||||
try
|
||||
{
|
||||
var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
|
||||
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
|
||||
|
||||
if (agent == null)
|
||||
{
|
||||
_logger.LogError($"Cannot find agent in file directory: {dir}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var functions = FetchFunctionsFromFile(dir);
|
||||
var instruction = FetchInstructionFromFile(dir);
|
||||
var responses = FetchResponsesFromFile(dir);
|
||||
var templates = FetchTemplatesFromFile(dir);
|
||||
var samples = FetchSamplesFromFile(dir);
|
||||
agent.SetInstruction(instruction)
|
||||
.SetTemplates(templates)
|
||||
.SetFunctions(functions)
|
||||
.SetResponses(responses)
|
||||
.SetSamples(samples);
|
||||
agents.Add(agent);
|
||||
var functions = FetchFunctionsFromFile(dir);
|
||||
var instruction = FetchInstructionFromFile(dir);
|
||||
var responses = FetchResponsesFromFile(dir);
|
||||
var templates = FetchTemplatesFromFile(dir);
|
||||
var samples = FetchSamplesFromFile(dir);
|
||||
agent.SetInstruction(instruction)
|
||||
.SetTemplates(templates)
|
||||
.SetFunctions(functions)
|
||||
.SetResponses(responses)
|
||||
.SetSamples(samples);
|
||||
|
||||
var userAgent = BuildUserAgent(agent.Id, user.Id);
|
||||
userAgents.Add(userAgent);
|
||||
var userAgent = BuildUserAgent(agent.Id, user.Id);
|
||||
var tasks = FetchTasksFromFile(dir);
|
||||
|
||||
var tasks = FetchTasksFromFile(dir);
|
||||
agentTasks.AddRange(tasks);
|
||||
var isAgentDeleted = _db.DeleteAgent(agent.Id);
|
||||
if (isAgentDeleted)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
_db.BulkInsertAgents(new List<Agent> { agent });
|
||||
_db.BulkInsertUserAgents(new List<UserAgent> { userAgent });
|
||||
_db.BulkInsertAgentTasks(tasks);
|
||||
refreshedAgents.Add(agent.Name);
|
||||
_logger.LogInformation($"Agent {agent.Name} has been migrated.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Failed to migrate agent in file directory: {dir}\r\nError: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
_db.BulkInsertAgents(agents);
|
||||
_db.BulkInsertUserAgents(userAgents);
|
||||
_db.BulkInsertAgentTasks(agentTasks);
|
||||
if (!refreshedAgents.IsNullOrEmpty())
|
||||
{
|
||||
Utilities.ClearCache();
|
||||
refreshResult = $"Agents are migrated!\r\n{string.Join("\r\n", refreshedAgents)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
refreshResult = "No agent gets refreshed!";
|
||||
}
|
||||
|
||||
Utilities.ClearCache();
|
||||
_logger.LogInformation(refreshResult);
|
||||
return refreshResult;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using BotSharp.Abstraction.Loggers;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
||||
|
|
@ -32,6 +33,64 @@ public partial class AgentService
|
|||
return true;
|
||||
}
|
||||
|
||||
public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def)
|
||||
{
|
||||
var parameterDef = def?.Parameters;
|
||||
var propertyDef = parameterDef?.Properties;
|
||||
if (propertyDef == null) return null;
|
||||
|
||||
var visibleExpress = "visibility_expression";
|
||||
var root = propertyDef.RootElement;
|
||||
var iterator = root.EnumerateObject();
|
||||
var visibleProps = new List<string>();
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
var prop = iterator.Current;
|
||||
var name = prop.Name;
|
||||
var node = prop.Value;
|
||||
var matched = true;
|
||||
if (node.TryGetProperty(visibleExpress, out var element))
|
||||
{
|
||||
var expression = element.GetString();
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var result = render.Render(expression, new Dictionary<string, object>
|
||||
{
|
||||
{ "states", agent.TemplateDict }
|
||||
});
|
||||
matched = result == "visible";
|
||||
}
|
||||
|
||||
if (matched)
|
||||
{
|
||||
visibleProps.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
var rootObject = JObject.Parse(root.GetRawText());
|
||||
var clonedRoot = rootObject.DeepClone() as JObject;
|
||||
var required = parameterDef?.Required ?? new List<string>();
|
||||
foreach (var property in rootObject.Properties())
|
||||
{
|
||||
if (visibleProps.Contains(property.Name))
|
||||
{
|
||||
var value = clonedRoot.GetValue(property.Name) as JObject;
|
||||
if (value != null && value.ContainsKey(visibleExpress))
|
||||
{
|
||||
value.Remove(visibleExpress);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clonedRoot.Remove(property.Name);
|
||||
required.Remove(property.Name);
|
||||
}
|
||||
}
|
||||
|
||||
parameterDef.Properties = JsonSerializer.Deserialize<JsonDocument>(clonedRoot.ToString());
|
||||
parameterDef.Required = required;
|
||||
return parameterDef; ;
|
||||
}
|
||||
|
||||
public string RenderedTemplate(Agent agent, string templateName)
|
||||
{
|
||||
// render liquid template
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
|
@ -10,6 +9,10 @@ public partial class AgentService
|
|||
{
|
||||
public async Task UpdateAgent(Agent agent, AgentField updateField)
|
||||
{
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
if (user?.Role != UserRole.Admin) return;
|
||||
|
||||
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
|
||||
|
||||
var record = _db.GetAgent(agent.Id);
|
||||
|
|
@ -39,21 +42,41 @@ public partial class AgentService
|
|||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task UpdateAgentFromFile(string id)
|
||||
public async Task<string> UpdateAgentFromFile(string id)
|
||||
{
|
||||
var agent = _db.GetAgent(id);
|
||||
|
||||
if (agent == null) return;
|
||||
|
||||
string updateResult;
|
||||
var dbSettings = _services.GetRequiredService<BotSharpDatabaseSettings>();
|
||||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
|
||||
if (dbSettings.Default == RepositoryEnum.FileRepository)
|
||||
{
|
||||
updateResult = $"Invalid database repository setting: {dbSettings.Default}";
|
||||
_logger.LogWarning(updateResult);
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
var agent = _db.GetAgent(id);
|
||||
if (agent == null)
|
||||
{
|
||||
updateResult = $"Cannot find agent ${id}";
|
||||
_logger.LogError(updateResult);
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
|
||||
dbSettings.FileRepository,
|
||||
agentSettings.DataDir);
|
||||
|
||||
var clonedAgent = Agent.Clone(agent);
|
||||
var foundAgent = FetchAgentFileById(agent.Id, filePath);
|
||||
if (foundAgent != null)
|
||||
if (foundAgent == null)
|
||||
{
|
||||
updateResult = $"Cannot find agent {agent.Name} in file directory: {filePath}";
|
||||
_logger.LogError(updateResult);
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
clonedAgent.SetId(foundAgent.Id)
|
||||
.SetName(foundAgent.Name)
|
||||
|
|
@ -71,15 +94,77 @@ public partial class AgentService
|
|||
.SetLlmConfig(foundAgent.LlmConfig);
|
||||
|
||||
_db.UpdateAgent(clonedAgent, AgentField.All);
|
||||
|
||||
Utilities.ClearCache();
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
updateResult = $"Agent {agent.Name} has been migrated!";
|
||||
_logger.LogInformation(updateResult);
|
||||
return updateResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
updateResult = $"Failed to migrate agent {agent.Name} in file directory {filePath}.\r\nError: {ex.Message}";
|
||||
_logger.LogError(updateResult);
|
||||
return updateResult;
|
||||
}
|
||||
}
|
||||
|
||||
private Agent FetchAgentFileById(string agentId, string filePath)
|
||||
|
||||
public async Task<string> PatchAgentTemplate(Agent agent)
|
||||
{
|
||||
var patchResult = string.Empty;
|
||||
if (agent == null || agent.Templates.IsNullOrEmpty())
|
||||
{
|
||||
patchResult = $"Null agent instance or empty input templates";
|
||||
_logger.LogWarning(patchResult);
|
||||
return patchResult;
|
||||
}
|
||||
|
||||
var record = _db.GetAgent(agent.Id);
|
||||
if (record == null)
|
||||
{
|
||||
patchResult = $"Cannot find agent {agent.Id}";
|
||||
_logger.LogWarning(patchResult);
|
||||
return patchResult;
|
||||
}
|
||||
|
||||
var successTemplates = new List<string>();
|
||||
var failTemplates = new List<string>();
|
||||
foreach (var template in agent.Templates)
|
||||
{
|
||||
if (template == null) continue;
|
||||
|
||||
var result = _db.PatchAgentTemplate(agent.Id, template);
|
||||
if (result)
|
||||
{
|
||||
successTemplates.Add(template.Name);
|
||||
_logger.LogInformation($"Template {template.Name} is updated successfully!");
|
||||
}
|
||||
else
|
||||
{
|
||||
failTemplates.Add(template.Name);
|
||||
_logger.LogWarning($"Template {template.Name} is failed to be updated!");
|
||||
}
|
||||
}
|
||||
|
||||
Utilities.ClearCache();
|
||||
|
||||
if (!successTemplates.IsNullOrEmpty())
|
||||
{
|
||||
patchResult += $"Success templates:\n{string.Join('\n', successTemplates)}\n\n";
|
||||
}
|
||||
|
||||
if (!failTemplates.IsNullOrEmpty())
|
||||
{
|
||||
patchResult += $"Failed templates:\n{string.Join('\n', failTemplates)}";
|
||||
}
|
||||
|
||||
return patchResult;
|
||||
}
|
||||
|
||||
private Agent? FetchAgentFileById(string agentId, string filePath)
|
||||
{
|
||||
if (!Directory.Exists(filePath)) return null;
|
||||
|
||||
foreach (var dir in Directory.GetDirectories(filePath))
|
||||
{
|
||||
var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
|
||||
|
|
|
|||
|
|
@ -47,4 +47,10 @@ public partial class AgentService : IAgentService
|
|||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public List<Agent> GetAgentsByUser(string userId)
|
||||
{
|
||||
var agents = _db.GetAgentsByUser(userId);
|
||||
return agents;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,11 +46,17 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\agent.json" />
|
||||
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instruction.liquid" />
|
||||
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json" />
|
||||
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\agent.json" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instruction.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\.welcome.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\conversation.summary.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.hf.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.naive.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.get_remaining_task.liquid" />
|
||||
|
|
@ -59,6 +65,7 @@
|
|||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.2nd.plan.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.2nd.task.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instruction.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid" />
|
||||
|
|
@ -67,6 +74,21 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
@ -106,6 +128,9 @@
|
|||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\.welcome.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
@ -118,6 +143,9 @@
|
|||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.reviewer.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\conversation.summary.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\plugins\config.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
@ -133,9 +161,11 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
|
||||
<PackageReference Include="Colorful.Console" Version="1.2.15" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="6.3.0" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.7.0" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.2.1" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.8.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
|
||||
<PackageReference Include="Nanoid" Version="3.0.0" />
|
||||
<PackageReference Include="RedLock.net" Version="2.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using BotSharp.Core.Plugins;
|
|||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
using BotSharp.Abstraction.Users.Settings;
|
||||
|
||||
namespace BotSharp.Core;
|
||||
|
||||
|
|
@ -14,9 +15,11 @@ public static class BotSharpCoreExtensions
|
|||
{
|
||||
services.AddScoped<ISettingService, SettingService>();
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
services.AddSingleton<DistributedLocker>();
|
||||
|
||||
RegisterPlugins(services, config);
|
||||
ConfigureBotSharpOptions(services, configOptions);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +85,10 @@ public static class BotSharpCoreExtensions
|
|||
return settingService.Bind<PluginSettings>("PluginLoader");
|
||||
});
|
||||
|
||||
var accountSettings = new AccountSetting();
|
||||
config.Bind("Account", accountSettings);
|
||||
services.AddScoped(x => accountSettings);
|
||||
|
||||
var loader = new PluginLoader(services, config, pluginSettings);
|
||||
loader.Load(assembly =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Google.Settings;
|
||||
using BotSharp.Abstraction.Instructs;
|
||||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Routing.Planning;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using BotSharp.Core.Files;
|
||||
using BotSharp.Core.Instructs;
|
||||
using BotSharp.Core.Messaging;
|
||||
using BotSharp.Core.Routing.Planning;
|
||||
using BotSharp.Core.Templating;
|
||||
using BotSharp.Core.Translation;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Core.Conversations;
|
||||
|
|
@ -31,10 +35,17 @@ public class ConversationPlugin : IBotSharpPlugin
|
|||
return settingService.Bind<ConversationSetting>("Conversation");
|
||||
});
|
||||
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
var settingService = provider.GetRequiredService<ISettingService>();
|
||||
return settingService.Bind<GoogleApiSettings>("GoogleApi");
|
||||
});
|
||||
|
||||
services.AddScoped<IConversationStorage, ConversationStorage>();
|
||||
services.AddScoped<IConversationService, ConversationService>();
|
||||
services.AddScoped<IConversationStateService, ConversationStateService>();
|
||||
services.AddScoped<IConversationAttachmentService, ConversationAttachmentService>();
|
||||
services.AddScoped<IBotSharpFileService, BotSharpFileService>();
|
||||
services.AddScoped<ITranslationService, TranslationService>();
|
||||
|
||||
// Rich content messaging
|
||||
services.AddScoped<IRichContentService, RichContentService>();
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
using BotSharp.Abstraction.Repositories;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
||||
public class ConversationAttachmentService : IConversationAttachmentService
|
||||
{
|
||||
private readonly BotSharpDatabaseSettings _dbSettings;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public ConversationAttachmentService(
|
||||
BotSharpDatabaseSettings dbSettings,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_dbSettings = dbSettings;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public string GetDirectory(string conversationId)
|
||||
{
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId, "attachments");
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,6 @@ public partial class ConversationService
|
|||
#endif
|
||||
|
||||
message.CurrentAgentId = agent.Id;
|
||||
message.CreatedAt = DateTime.UtcNow;
|
||||
if (string.IsNullOrEmpty(message.SenderId))
|
||||
{
|
||||
message.SenderId = _user.Id;
|
||||
|
|
@ -47,6 +46,17 @@ public partial class ConversationService
|
|||
routing.Context.SetMessageId(_conversationId, message.MessageId);
|
||||
routing.Context.Push(agent.Id);
|
||||
|
||||
// Save message files
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
fileService.SaveMessageFiles(_conversationId, message.MessageId, message.Files);
|
||||
message.Files?.Clear();
|
||||
|
||||
// Save payload
|
||||
if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
|
||||
{
|
||||
message.Payload = replyMessage.Payload;
|
||||
}
|
||||
|
||||
// Before chat completion hook
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
|
|
@ -71,19 +81,13 @@ public partial class ConversationService
|
|||
}
|
||||
}
|
||||
|
||||
// Persist to storage
|
||||
_storage.Append(_conversationId, message);
|
||||
|
||||
// Add to thread
|
||||
dialogs.Add(RoleDialogModel.From(message));
|
||||
|
||||
if (!stopCompletion)
|
||||
{
|
||||
// Routing with reasoning
|
||||
var settings = _services.GetRequiredService<RoutingSettings>();
|
||||
|
||||
response = agent.Type == AgentType.Routing ?
|
||||
await routing.InstructLoop(message, dialogs) :
|
||||
await routing.InstructLoop(message, dialogs, onFunctionExecuting) :
|
||||
await routing.InstructDirect(agent, message);
|
||||
|
||||
routing.ResetRecursiveCounter();
|
||||
|
|
@ -136,7 +140,7 @@ public partial class ConversationService
|
|||
response.RichContent is RichContent<IRichMessage> template &&
|
||||
string.IsNullOrEmpty(template.Message.Text))
|
||||
{
|
||||
template.Message.Text = response.Content;
|
||||
template.Message.Text = response.SecondaryContent ?? response.Content;
|
||||
}
|
||||
|
||||
// Only read content from RichContent for UI rendering. When richContent is null, create a basic text message for richContent.
|
||||
|
|
@ -144,30 +148,40 @@ public partial class ConversationService
|
|||
response.RichContent = response.RichContent ?? new RichContent<IRichMessage>
|
||||
{
|
||||
Recipient = new Recipient { Id = state.GetConversationId() },
|
||||
Message = new TextMessage(response.Content)
|
||||
Message = new TextMessage(response.SecondaryContent ?? response.Content)
|
||||
};
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
// Patch return function name
|
||||
if (response.PostbackFunctionName != null)
|
||||
{
|
||||
response.FunctionName = response.PostbackFunctionName;
|
||||
}
|
||||
|
||||
if (response.Instruction != null)
|
||||
{
|
||||
var conversation = _services.GetRequiredService<IConversationService>();
|
||||
var updatedConversation = await conversation.UpdateConversationTitle(_conversationId, response.Instruction.NextActionReason);
|
||||
|
||||
// Emit conversation task completed hook
|
||||
if (response.Instruction.TaskCompleted)
|
||||
{
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnTaskCompleted(response)
|
||||
);
|
||||
}
|
||||
|
||||
// Emit conversation ending hook
|
||||
if (response.Instruction.ConversationEnd)
|
||||
{
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnConversationEnding(response);
|
||||
}
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnConversationEnding(response)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnResponseGenerated(response);
|
||||
}
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnResponseGenerated(response)
|
||||
);
|
||||
|
||||
await onResponseReceived(response);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
||||
public partial class ConversationService
|
||||
{
|
||||
public async Task<string> GetConversationSummary(IEnumerable<string> conversationIds)
|
||||
{
|
||||
if (conversationIds.IsNullOrEmpty()) return string.Empty;
|
||||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
||||
var contents = new List<string>();
|
||||
foreach ( var conversationId in conversationIds)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId)) continue;
|
||||
|
||||
var dialogs = _storage.GetDialogs(conversationId);
|
||||
|
||||
if (dialogs.IsNullOrEmpty()) continue;
|
||||
|
||||
var content = GetConversationContent(dialogs);
|
||||
if (string.IsNullOrWhiteSpace(content)) continue;
|
||||
|
||||
contents.Add(content);
|
||||
}
|
||||
|
||||
if (contents.IsNullOrEmpty()) return string.Empty;
|
||||
|
||||
var router = await agentService.LoadAgent(AIAssistant);
|
||||
var prompt = GetPrompt(router, contents);
|
||||
var summary = await Summarize(router, prompt);
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
private string GetPrompt(Agent agent, List<string> contents)
|
||||
{
|
||||
var template = agent.Templates.First(x => x.Name == "conversation.summary").Content;
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
|
||||
var texts = new List<string>();
|
||||
for (int i = 0; i < contents.Count; i++)
|
||||
{
|
||||
texts.Add($"{contents[i]}");
|
||||
}
|
||||
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "texts", texts }
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<string> Summarize(Agent agent, string prompt)
|
||||
{
|
||||
var provider = "openai";
|
||||
string? model;
|
||||
|
||||
var providerService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var modelSettings = providerService.GetProviderModels(provider);
|
||||
var modelSetting = modelSettings.FirstOrDefault(x => x.Name.IsEqualTo("gpt4-turbo") || x.Name.IsEqualTo("gpt-4o"));
|
||||
|
||||
if (modelSetting != null)
|
||||
{
|
||||
model = modelSetting.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
provider = agent?.LlmConfig?.Provider;
|
||||
model = agent?.LlmConfig?.Model;
|
||||
if (provider == null || model == null)
|
||||
{
|
||||
var agentSettings = _services.GetRequiredService<AgentSettings>();
|
||||
provider = agentSettings.LlmConfig.Provider;
|
||||
model = agentSettings.LlmConfig.Model;
|
||||
}
|
||||
}
|
||||
|
||||
var chatCompletion = CompletionProvider.GetChatCompletion(_services, provider, model);
|
||||
var response = await chatCompletion.GetChatCompletions(new Agent
|
||||
{
|
||||
Id = agent.Id,
|
||||
Name = agent.Name,
|
||||
Instruction = prompt
|
||||
}, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, "Please summarize the conversations.")
|
||||
});
|
||||
|
||||
return response.Content;
|
||||
}
|
||||
|
||||
private string GetConversationContent(List<RoleDialogModel> dialogs, int maxDialogCount = 50)
|
||||
{
|
||||
var conversation = "";
|
||||
|
||||
foreach (var dialog in dialogs.TakeLast(maxDialogCount))
|
||||
{
|
||||
var role = dialog.Role;
|
||||
if (role == AgentRole.Function) continue;
|
||||
|
||||
if (role != AgentRole.User)
|
||||
{
|
||||
role = AgentRole.Assistant;
|
||||
}
|
||||
|
||||
conversation += $"{role}: {dialog.Payload ?? dialog.Content}\r\n";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(conversation))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return conversation + "\r\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,19 @@ namespace BotSharp.Core.Conversations.Services;
|
|||
|
||||
public partial class ConversationService : IConversationService
|
||||
{
|
||||
public async Task<bool> TruncateConversation(string conversationId, string messageId)
|
||||
public async Task<bool> TruncateConversation(string conversationId, string messageId, string? newMessageId = null)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var isSaved = db.TruncateConversation(conversationId, messageId, true);
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true);
|
||||
|
||||
fileService.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnMessageDeleted(conversationId, messageId);
|
||||
}
|
||||
return await Task.FromResult(isSaved);
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,34 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
||||
public partial class ConversationService : IConversationService
|
||||
{
|
||||
public async Task UpdateBreakpoint(bool resetStates = false)
|
||||
public async Task UpdateBreakpoint(bool resetStates = false, string? reason = null, params string[] excludedStates)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var messageId = routingCtx.MessageId;
|
||||
db.UpdateConversationBreakpoint(_conversationId, messageId, DateTime.UtcNow);
|
||||
|
||||
db.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint
|
||||
{
|
||||
MessageId = messageId,
|
||||
Breakpoint = DateTime.UtcNow,
|
||||
Reason = reason
|
||||
});
|
||||
|
||||
// Reset states
|
||||
if (resetStates)
|
||||
{
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
states.CleanStates();
|
||||
// keep language state
|
||||
if (excludedStates == null) excludedStates = new string[] { };
|
||||
if (!excludedStates.Contains(StateConst.LANGUAGE))
|
||||
{
|
||||
excludedStates = excludedStates.Append(StateConst.LANGUAGE).ToArray();
|
||||
}
|
||||
|
||||
states.CleanStates(excludedStates);
|
||||
}
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.Models;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
|
@ -11,6 +12,8 @@ public partial class ConversationService : IConversationService
|
|||
private readonly IConversationStorage _storage;
|
||||
private readonly IConversationStateService _state;
|
||||
private string _conversationId;
|
||||
private const string AIAssistant = "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a";
|
||||
|
||||
public string ConversationId => _conversationId;
|
||||
|
||||
public IConversationStateService States => _state;
|
||||
|
|
@ -34,7 +37,9 @@ public partial class ConversationService : IConversationService
|
|||
public async Task<bool> DeleteConversations(IEnumerable<string> ids)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var isDeleted = db.DeleteConversations(ids);
|
||||
fileService.DeleteConversationFiles(ids);
|
||||
return await Task.FromResult(isDeleted);
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +119,14 @@ public partial class ConversationService : IConversationService
|
|||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var breakpoint = db.GetConversationBreakpoint(_conversationId);
|
||||
dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint).ToList();
|
||||
if (breakpoint != null)
|
||||
{
|
||||
dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint.Breakpoint).ToList();
|
||||
if (!string.IsNullOrEmpty(breakpoint.Reason))
|
||||
{
|
||||
dialogs.Insert(0, new RoleDialogModel(AgentRole.User, breakpoint.Reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dialogs
|
||||
|
|
@ -126,6 +138,6 @@ public partial class ConversationService : IConversationService
|
|||
{
|
||||
_conversationId = conversationId;
|
||||
_state.Load(_conversationId);
|
||||
states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds));
|
||||
states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
|
@ -9,9 +10,16 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServiceProvider _services;
|
||||
private ConversationState _states;
|
||||
private string _conversationId;
|
||||
private readonly IBotSharpRepository _db;
|
||||
private string _conversationId;
|
||||
/// <summary>
|
||||
/// States in the current round of conversation
|
||||
/// </summary>
|
||||
private ConversationState _curStates;
|
||||
/// <summary>
|
||||
/// States in the previous rounds of conversation
|
||||
/// </summary>
|
||||
private ConversationState _historyStates;
|
||||
|
||||
public ConversationStateService(ILogger<ConversationStateService> logger,
|
||||
IServiceProvider services,
|
||||
|
|
@ -20,7 +28,8 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
_logger = logger;
|
||||
_services = services;
|
||||
_db = db;
|
||||
_states = new ConversationState();
|
||||
_curStates = new ConversationState();
|
||||
_historyStates = new ConversationState();
|
||||
}
|
||||
|
||||
public string GetConversationId() => _conversationId;
|
||||
|
|
@ -33,7 +42,8 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
/// <param name="value"></param>
|
||||
/// <param name="isNeedVersion">whether the state is related to message or not</param>
|
||||
/// <returns></returns>
|
||||
public IConversationStateService SetState<T>(string name, T value, bool isNeedVersion = true, int activeRounds = -1)
|
||||
public IConversationStateService SetState<T>(string name, T value, bool isNeedVersion = true,
|
||||
int activeRounds = -1, string valueType = StateDataType.String, string source = StateSource.User, bool readOnly = false)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
|
|
@ -46,34 +56,41 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
var curActiveRounds = activeRounds > 0 ? activeRounds : -1;
|
||||
int? preActiveRounds = null;
|
||||
|
||||
if (ContainsState(name) && _states.TryGetValue(name, out var pair))
|
||||
if (ContainsState(name) && _curStates.TryGetValue(name, out var pair))
|
||||
{
|
||||
var lastNode = pair?.Values?.LastOrDefault();
|
||||
preActiveRounds = lastNode?.ActiveRounds;
|
||||
preValue = lastNode?.Data ?? string.Empty;
|
||||
var leafNode = pair?.Values?.LastOrDefault();
|
||||
preActiveRounds = leafNode?.ActiveRounds;
|
||||
preValue = leafNode?.Data ?? string.Empty;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"[STATE] {name} = {value}");
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
|
||||
foreach (var hook in hooks)
|
||||
if (!ContainsState(name) || preValue != currentValue || preActiveRounds != curActiveRounds)
|
||||
{
|
||||
hook.OnStateChanged(new StateChangeModel
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
ConversationId = _conversationId,
|
||||
MessageId = routingCtx.MessageId,
|
||||
Name = name,
|
||||
BeforeValue = preValue,
|
||||
BeforeActiveRounds = preActiveRounds,
|
||||
AfterValue = currentValue,
|
||||
AfterActiveRounds = curActiveRounds
|
||||
}).Wait();
|
||||
hook.OnStateChanged(new StateChangeModel
|
||||
{
|
||||
ConversationId = _conversationId,
|
||||
MessageId = routingCtx.MessageId,
|
||||
Name = name,
|
||||
BeforeValue = preValue,
|
||||
BeforeActiveRounds = preActiveRounds,
|
||||
AfterValue = currentValue,
|
||||
AfterActiveRounds = curActiveRounds,
|
||||
DataType = valueType,
|
||||
Source = source,
|
||||
Readonly = readOnly
|
||||
}).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
var newPair = new StateKeyValue
|
||||
{
|
||||
Key = name,
|
||||
Versioning = isNeedVersion
|
||||
Versioning = isNeedVersion,
|
||||
Readonly = readOnly
|
||||
};
|
||||
|
||||
var newValue = new StateValue
|
||||
|
|
@ -82,76 +99,94 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
MessageId = routingCtx.MessageId,
|
||||
Active = true,
|
||||
ActiveRounds = curActiveRounds,
|
||||
DataType = valueType,
|
||||
Source = source,
|
||||
UpdateTime = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
if (!isNeedVersion || !_states.ContainsKey(name))
|
||||
if (!isNeedVersion || !_curStates.ContainsKey(name))
|
||||
{
|
||||
newPair.Values = new List<StateValue> { newValue };
|
||||
_states[name] = newPair;
|
||||
_curStates[name] = newPair;
|
||||
}
|
||||
else
|
||||
{
|
||||
_states[name].Values.Add(newValue);
|
||||
_curStates[name].Values.Add(newValue);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> Load(string conversationId)
|
||||
public Dictionary<string, string> Load(string conversationId, bool isReadOnly = false)
|
||||
{
|
||||
_conversationId = conversationId;
|
||||
_conversationId = !isReadOnly ? conversationId : null;
|
||||
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var curMsgId = routingCtx.MessageId;
|
||||
_states = _db.GetConversationStates(_conversationId);
|
||||
var dialogs = _db.GetConversationDialogs(_conversationId);
|
||||
|
||||
_historyStates = _db.GetConversationStates(conversationId);
|
||||
var dialogs = _db.GetConversationDialogs(conversationId);
|
||||
var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User || x.MetaData?.Role == UserRole.Client)
|
||||
.GroupBy(x => x.MetaData?.MessageId)
|
||||
.Select(g => g.First())
|
||||
.OrderBy(x => x.MetaData?.CreateTime)
|
||||
.ToList();
|
||||
|
||||
var curMsgIndex = userDialogs.FindIndex(x => !string.IsNullOrEmpty(curMsgId) && x.MetaData?.MessageId == curMsgId);
|
||||
curMsgIndex = curMsgIndex < 0 ? userDialogs.Count() : curMsgIndex;
|
||||
var curStates = new Dictionary<string, string>();
|
||||
|
||||
if (!_states.IsNullOrEmpty())
|
||||
var endNodes = new Dictionary<string, string>();
|
||||
if (_historyStates.IsNullOrEmpty()) return endNodes;
|
||||
|
||||
foreach (var state in _historyStates)
|
||||
{
|
||||
foreach (var state in _states)
|
||||
var key = state.Key;
|
||||
var value = state.Value;
|
||||
var leafNode = value?.Values?.LastOrDefault();
|
||||
if (leafNode == null) continue;
|
||||
|
||||
_curStates[key] = new StateKeyValue
|
||||
{
|
||||
var value = state.Value?.Values?.LastOrDefault();
|
||||
if (value == null || !value.Active) continue;
|
||||
Key = key,
|
||||
Versioning = value.Versioning,
|
||||
Readonly = value.Readonly,
|
||||
Values = new List<StateValue> { leafNode }
|
||||
};
|
||||
|
||||
if (value.ActiveRounds > 0)
|
||||
if (!leafNode.Active) continue;
|
||||
|
||||
// Handle state active rounds
|
||||
if (leafNode.ActiveRounds > 0)
|
||||
{
|
||||
var stateMsgIndex = userDialogs.FindIndex(x => !string.IsNullOrEmpty(x.MetaData?.MessageId) && x.MetaData.MessageId == leafNode.MessageId);
|
||||
if (stateMsgIndex >= 0 && curMsgIndex - stateMsgIndex >= leafNode.ActiveRounds)
|
||||
{
|
||||
var stateMsgIndex = userDialogs.FindIndex(x => !string.IsNullOrEmpty(x.MetaData?.MessageId) && x.MetaData.MessageId == value.MessageId);
|
||||
if (stateMsgIndex >= 0 && curMsgIndex - stateMsgIndex >= value.ActiveRounds)
|
||||
_curStates[key].Values.Add(new StateValue
|
||||
{
|
||||
state.Value.Values.Add(new StateValue
|
||||
{
|
||||
Data = value.Data,
|
||||
MessageId = curMsgId,
|
||||
Active = false,
|
||||
ActiveRounds = value.ActiveRounds,
|
||||
UpdateTime = DateTime.UtcNow
|
||||
});
|
||||
continue;
|
||||
}
|
||||
Data = leafNode.Data,
|
||||
MessageId = curMsgId,
|
||||
Active = false,
|
||||
ActiveRounds = leafNode.ActiveRounds,
|
||||
DataType = leafNode.DataType,
|
||||
Source = leafNode.Source,
|
||||
UpdateTime = DateTime.UtcNow
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var data = value.Data ?? string.Empty;
|
||||
curStates[state.Key] = data;
|
||||
_logger.LogInformation($"[STATE] {state.Key} : {data}");
|
||||
}
|
||||
|
||||
var data = leafNode.Data ?? string.Empty;
|
||||
endNodes[state.Key] = data;
|
||||
_logger.LogInformation($"[STATE] {key} : {data}");
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Loaded conversation states: {_conversationId}");
|
||||
_logger.LogInformation($"Loaded conversation states: {conversationId}");
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.OnStateLoaded(_states).Wait();
|
||||
hook.OnStateLoaded(_curStates).Wait();
|
||||
}
|
||||
|
||||
return curStates;
|
||||
return endNodes;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
|
|
@ -163,35 +198,106 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
|
||||
var states = new List<StateKeyValue>();
|
||||
|
||||
foreach (var dic in _states)
|
||||
foreach (var pair in _curStates)
|
||||
{
|
||||
states.Add(dic.Value);
|
||||
var key = pair.Key;
|
||||
var curValue = pair.Value;
|
||||
|
||||
if (!_historyStates.TryGetValue(key, out var historyValue)
|
||||
|| historyValue == null
|
||||
|| historyValue.Values.IsNullOrEmpty()
|
||||
|| !curValue.Versioning)
|
||||
{
|
||||
states.Add(curValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
var historyValues = historyValue.Values.Take(historyValue.Values.Count - 1).ToList();
|
||||
var newValues = historyValues.Concat(curValue.Values).ToList();
|
||||
var updatedNode = new StateKeyValue
|
||||
{
|
||||
Key = pair.Key,
|
||||
Versioning = curValue.Versioning,
|
||||
Readonly = curValue.Readonly,
|
||||
Values = newValues
|
||||
};
|
||||
states.Add(updatedNode);
|
||||
}
|
||||
}
|
||||
|
||||
_db.UpdateConversationStates(_conversationId, states);
|
||||
_logger.LogInformation($"Saved states of conversation {_conversationId}");
|
||||
}
|
||||
|
||||
public void CleanStates()
|
||||
public bool RemoveState(string name)
|
||||
{
|
||||
if (!ContainsState(name)) return false;
|
||||
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var value = _curStates[name];
|
||||
var leafNode = value?.Values?.LastOrDefault();
|
||||
if (value == null || !value.Versioning || leafNode == null) return false;
|
||||
|
||||
_curStates[name].Values.Add(new StateValue
|
||||
{
|
||||
Data = leafNode.Data,
|
||||
MessageId = routingCtx.MessageId,
|
||||
Active = false,
|
||||
ActiveRounds = leafNode.ActiveRounds,
|
||||
DataType = leafNode.DataType,
|
||||
Source = leafNode.Source,
|
||||
UpdateTime = DateTime.UtcNow
|
||||
});
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.OnStateChanged(new StateChangeModel
|
||||
{
|
||||
ConversationId = _conversationId,
|
||||
MessageId = routingCtx.MessageId,
|
||||
Name = name,
|
||||
BeforeValue = leafNode.Data,
|
||||
BeforeActiveRounds = leafNode.ActiveRounds,
|
||||
AfterValue = null,
|
||||
AfterActiveRounds = leafNode.ActiveRounds,
|
||||
DataType = leafNode.DataType,
|
||||
Source = leafNode.Source,
|
||||
Readonly = value.Readonly
|
||||
}).Wait();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CleanStates(params string[] excludedStates)
|
||||
{
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var curMsgId = routingCtx.MessageId;
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
foreach (var key in _states.Keys)
|
||||
foreach (var key in _curStates.Keys)
|
||||
{
|
||||
var value = _states[key];
|
||||
// skip state
|
||||
if (excludedStates.Contains(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = _curStates[key];
|
||||
if (value == null || !value.Versioning || value.Values.IsNullOrEmpty()) continue;
|
||||
|
||||
var lastValue = value.Values.LastOrDefault();
|
||||
if (lastValue == null || !lastValue.Active) continue;
|
||||
var leafNode = value.Values.LastOrDefault();
|
||||
if (leafNode == null || !leafNode.Active) continue;
|
||||
|
||||
value.Values.Add(new StateValue
|
||||
{
|
||||
Data = lastValue.Data,
|
||||
Data = leafNode.Data,
|
||||
MessageId = curMsgId,
|
||||
Active = false,
|
||||
ActiveRounds = lastValue.ActiveRounds,
|
||||
ActiveRounds = leafNode.ActiveRounds,
|
||||
DataType = leafNode.DataType,
|
||||
Source = leafNode.Source,
|
||||
UpdateTime = utcNow
|
||||
});
|
||||
}
|
||||
|
|
@ -199,25 +305,25 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
|
||||
public Dictionary<string, string> GetStates()
|
||||
{
|
||||
var curStates = new Dictionary<string, string>();
|
||||
foreach (var state in _states)
|
||||
var endNodes = new Dictionary<string, string>();
|
||||
foreach (var state in _curStates)
|
||||
{
|
||||
var value = state.Value?.Values?.LastOrDefault();
|
||||
if (value == null || !value.Active) continue;
|
||||
|
||||
curStates[state.Key] = value.Data ?? string.Empty;
|
||||
endNodes[state.Key] = value.Data ?? string.Empty;
|
||||
}
|
||||
return curStates;
|
||||
return endNodes;
|
||||
}
|
||||
|
||||
public string GetState(string name, string defaultValue = "")
|
||||
{
|
||||
if (!_states.ContainsKey(name) || _states[name].Values.IsNullOrEmpty() || !_states[name].Values.Last().Active)
|
||||
if (!_curStates.ContainsKey(name) || _curStates[name].Values.IsNullOrEmpty() || !_curStates[name].Values.Last().Active)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return _states[name].Values.Last().Data;
|
||||
return _curStates[name].Values.Last().Data;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -227,10 +333,10 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
|
||||
public bool ContainsState(string name)
|
||||
{
|
||||
return _states.ContainsKey(name)
|
||||
&& !_states[name].Values.IsNullOrEmpty()
|
||||
&& _states[name].Values.LastOrDefault()?.Active == true
|
||||
&& !string.IsNullOrEmpty(_states[name].Values.Last().Data);
|
||||
return _curStates.ContainsKey(name)
|
||||
&& !_curStates[name].Values.IsNullOrEmpty()
|
||||
&& _curStates[name].Values.LastOrDefault()?.Active == true
|
||||
&& !string.IsNullOrEmpty(_curStates[name].Values.Last().Data);
|
||||
}
|
||||
|
||||
public void SaveStateByArgs(JsonDocument args)
|
||||
|
|
@ -244,9 +350,17 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
{
|
||||
foreach (JsonProperty property in root.EnumerateObject())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(property.Value.ToString()))
|
||||
var propertyValue = property.Value;
|
||||
var stateValue = propertyValue.ToString();
|
||||
if (!string.IsNullOrEmpty(stateValue))
|
||||
{
|
||||
SetState(property.Name, property.Value);
|
||||
if (propertyValue.ValueKind == JsonValueKind.True ||
|
||||
propertyValue.ValueKind == JsonValueKind.False)
|
||||
{
|
||||
stateValue = stateValue?.ToLower();
|
||||
}
|
||||
|
||||
SetState(property.Name, stateValue, source: StateSource.Application);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ namespace BotSharp.Core.Conversations.Services;
|
|||
public class ConversationStorage : IConversationStorage
|
||||
{
|
||||
private readonly BotSharpDatabaseSettings _dbSettings;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public ConversationStorage(
|
||||
BotSharpDatabaseSettings dbSettings,
|
||||
|
|
@ -18,7 +18,7 @@ public class ConversationStorage : IConversationStorage
|
|||
{
|
||||
_dbSettings = dbSettings;
|
||||
_services = services;
|
||||
_jsonOptions = InitJsonSerilizerOptions(options);
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public void Append(string conversationId, RoleDialogModel dialog)
|
||||
|
|
@ -28,11 +28,11 @@ public class ConversationStorage : IConversationStorage
|
|||
var dialogElements = new List<DialogElement>();
|
||||
|
||||
// Prevent duplicate record to be inserted
|
||||
var dialogs = db.GetConversationDialogs(conversationId);
|
||||
/*var dialogs = db.GetConversationDialogs(conversationId);
|
||||
if (dialogs.Any(x => x.MetaData.MessageId == dialog.MessageId && x.Content == dialog.Content))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (dialog.Role == AgentRole.Function)
|
||||
{
|
||||
|
|
@ -50,7 +50,13 @@ public class ConversationStorage : IConversationStorage
|
|||
{
|
||||
return;
|
||||
}
|
||||
dialogElements.Add(new DialogElement(meta, content));
|
||||
dialogElements.Add(new DialogElement
|
||||
{
|
||||
MetaData = meta,
|
||||
Content = dialog.Content,
|
||||
SecondaryContent = dialog.SecondaryContent,
|
||||
Payload = dialog.Payload
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -70,8 +76,17 @@ public class ConversationStorage : IConversationStorage
|
|||
return;
|
||||
}
|
||||
|
||||
var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _jsonOptions) : null;
|
||||
dialogElements.Add(new DialogElement(meta, content, richContent));
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
db.AppendConversationDialogs(conversationId, dialogElements);
|
||||
|
|
@ -88,6 +103,8 @@ public class ConversationStorage : IConversationStorage
|
|||
{
|
||||
var meta = dialog.MetaData;
|
||||
var content = dialog.Content;
|
||||
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;
|
||||
|
|
@ -95,7 +112,9 @@ public class ConversationStorage : IConversationStorage
|
|||
var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId;
|
||||
var createdAt = meta.CreateTime;
|
||||
var richContent = !string.IsNullOrEmpty(dialog.RichContent) ?
|
||||
JsonSerializer.Deserialize<RichContent<IRichMessage>>(dialog.RichContent, _jsonOptions) : null;
|
||||
JsonSerializer.Deserialize<RichContent<IRichMessage>>(dialog.RichContent, _options.JsonSerializerOptions) : null;
|
||||
var secondaryRichContent = !string.IsNullOrEmpty(dialog.SecondaryRichContent) ?
|
||||
JsonSerializer.Deserialize<RichContent<IRichMessage>>(dialog.SecondaryRichContent, _options.JsonSerializerOptions) : null;
|
||||
|
||||
var record = new RoleDialogModel(role, content)
|
||||
{
|
||||
|
|
@ -104,7 +123,10 @@ public class ConversationStorage : IConversationStorage
|
|||
CreatedAt = createdAt,
|
||||
SenderId = senderId,
|
||||
FunctionName = function,
|
||||
RichContent = richContent
|
||||
RichContent = richContent,
|
||||
SecondaryContent = secondaryContent,
|
||||
SecondaryRichContent = secondaryRichContent,
|
||||
Payload = payload
|
||||
};
|
||||
results.Add(record);
|
||||
|
||||
|
|
@ -140,21 +162,4 @@ public class ConversationStorage : IConversationStorage
|
|||
}
|
||||
return Path.Combine(dir, "dialogs.txt");
|
||||
}
|
||||
|
||||
private JsonSerializerOptions InitJsonSerilizerOptions(BotSharpOptions botSharOptions)
|
||||
{
|
||||
var options = botSharOptions.JsonSerializerOptions;
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
|
||||
PropertyNamingPolicy = options.PropertyNamingPolicy ?? JsonNamingPolicy.CamelCase,
|
||||
AllowTrailingCommas = options.AllowTrailingCommas,
|
||||
};
|
||||
|
||||
foreach (var converter in options.Converters)
|
||||
{
|
||||
jsonOptions.Converters.Add(converter);
|
||||
}
|
||||
return jsonOptions;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue