Merge branch 'master' of https://github.com/SciSharp/BotSharp into features/refine-agent-call-stats
This commit is contained in:
commit
25502b3dcb
44
.github/workflows/build.yml
vendored
Normal file
44
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macos-latest
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Set env
|
||||
run: |
|
||||
echo "DOTNET_CLI_TELEMETRY_OPTOUT=1" >> $GITHUB_ENV
|
||||
echo "DOTNET_hostBuilder:reloadConfigOnChange=false" >> $GITHUB_ENV
|
||||
- name: Install required workloads
|
||||
run: |
|
||||
dotnet workload install aspire --source https://aka.ms/dotnet8/nuget/index.json --source https://api.nuget.org/v3/index.json
|
||||
- name: Clean
|
||||
run: |
|
||||
dotnet clean ./BotSharp.sln --configuration Release
|
||||
dotnet nuget locals all --clear
|
||||
- name: Build
|
||||
run: dotnet build ./BotSharp.sln -c Release
|
||||
- name: Test
|
||||
run: |
|
||||
cd ./tests/UnitTest
|
||||
dotnet test --logger "console;verbosity=detailed"
|
||||
cd ../BotSharp.Plugin.SemanticKernel.UnitTests
|
||||
dotnet test --logger "console;verbosity=detailed"
|
||||
|
|
@ -71,5 +71,5 @@ public interface IConversationService
|
|||
/// <param name="convLimit">conversation limit</param>
|
||||
/// <param name="preLoad">if pre-loading, then keys are not filter by the search query</param>
|
||||
/// <returns></returns>
|
||||
Task<List<string>> GetConversationStateSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false);
|
||||
Task<List<string>> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,13 +58,17 @@ public class DialogElement
|
|||
[JsonPropertyName("payload")]
|
||||
public string? Payload { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("data")]
|
||||
public object? Data { get; set; }
|
||||
|
||||
public DialogElement()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DialogElement(DialogMetaData meta, string content, string? richContent = null,
|
||||
string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null)
|
||||
string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null, object? data = null)
|
||||
{
|
||||
MetaData = meta;
|
||||
Content = content;
|
||||
|
|
@ -72,6 +76,7 @@ public class DialogElement
|
|||
SecondaryContent = secondaryContent;
|
||||
SecondaryRichContent = secondaryRichContent;
|
||||
Payload = payload;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
=> throw new NotImplementedException();
|
||||
List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
|
||||
=> throw new NotImplementedException();
|
||||
List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100)
|
||||
List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100)
|
||||
=> throw new NotImplementedException();
|
||||
#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -222,17 +222,17 @@ public partial class ConversationService : IConversationService
|
|||
_state.Save();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetConversationStateSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false)
|
||||
public async Task<List<string>> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false)
|
||||
{
|
||||
var keys = new List<string>();
|
||||
if (!preLoad && string.IsNullOrWhiteSpace(query))
|
||||
if (!preload && string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return keys;
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
keys = db.GetConversationStateSearchKeys(convUpperlimit: convlimit);
|
||||
keys = preLoad ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
keys = db.GetConversationStateSearchKeys(convUpperLimit: convLimit);
|
||||
keys = preload ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
return keys.OrderBy(x => x).Take(keyLimit).ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.SideCar;
|
||||
|
||||
namespace BotSharp.Core.Conversations.Services;
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
private readonly ILogger _logger;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IBotSharpRepository _db;
|
||||
private readonly IConversationSideCar _sidecar;
|
||||
private string _conversationId;
|
||||
/// <summary>
|
||||
/// States in the current round of conversation
|
||||
|
|
@ -39,10 +41,12 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
public ConversationStateService(
|
||||
IServiceProvider services,
|
||||
IBotSharpRepository db,
|
||||
IConversationSideCar sidecar,
|
||||
ILogger<ConversationStateService> logger)
|
||||
{
|
||||
_services = services;
|
||||
_db = db;
|
||||
_sidecar = sidecar;
|
||||
_logger = logger;
|
||||
_curStates = new ConversationState();
|
||||
_historyStates = new ConversationState();
|
||||
|
|
@ -138,15 +142,20 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
_conversationId = !isReadOnly ? conversationId : null;
|
||||
Reset();
|
||||
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var curMsgId = routingCtx.MessageId;
|
||||
var endNodes = new Dictionary<string, string>();
|
||||
if (_sidecar?.IsEnabled() == true)
|
||||
{
|
||||
return endNodes;
|
||||
}
|
||||
|
||||
_historyStates = _db.GetConversationStates(conversationId);
|
||||
if (_historyStates.IsNullOrEmpty())
|
||||
{
|
||||
return endNodes;
|
||||
}
|
||||
|
||||
var endNodes = new Dictionary<string, string>();
|
||||
|
||||
if (_historyStates.IsNullOrEmpty()) return endNodes;
|
||||
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var curMsgId = routingCtx.MessageId;
|
||||
var dialogs = _db.GetConversationDialogs(conversationId);
|
||||
var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User)
|
||||
.GroupBy(x => x.MetaData?.MessageId)
|
||||
|
|
@ -210,7 +219,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
|
||||
public void Save()
|
||||
{
|
||||
if (_conversationId == null)
|
||||
if (_conversationId == null || _sidecar?.IsEnabled() == true)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ public class ConversationStorage : IConversationStorage
|
|||
MetaData = meta,
|
||||
Content = dialog.Content,
|
||||
SecondaryContent = dialog.SecondaryContent,
|
||||
Payload = dialog.Payload
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
@ -83,7 +84,8 @@ public class ConversationStorage : IConversationStorage
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = richContent,
|
||||
SecondaryRichContent = secondaryRichContent,
|
||||
Payload = dialog.Payload
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +123,8 @@ public class ConversationStorage : IConversationStorage
|
|||
MetaData = meta,
|
||||
Content = dialog.Content,
|
||||
SecondaryContent = dialog.SecondaryContent,
|
||||
Payload = dialog.Payload
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
@ -152,7 +155,8 @@ public class ConversationStorage : IConversationStorage
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = richContent,
|
||||
SecondaryRichContent = secondaryRichContent,
|
||||
Payload = dialog.Payload
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -196,7 +200,8 @@ public class ConversationStorage : IConversationStorage
|
|||
RichContent = richContent,
|
||||
SecondaryContent = secondaryContent,
|
||||
SecondaryRichContent = secondaryRichContent,
|
||||
Payload = payload
|
||||
Payload = payload,
|
||||
Data = dialog.Data
|
||||
};
|
||||
results.Add(record);
|
||||
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ public partial class FileRepository
|
|||
#if !DEBUG
|
||||
[SharpCache(10)]
|
||||
#endif
|
||||
public List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100)
|
||||
public List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100)
|
||||
{
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
|
||||
if (!Directory.Exists(dir)) return [];
|
||||
|
|
@ -635,7 +635,7 @@ public partial class FileRepository
|
|||
keys.AddRange(stateKeys);
|
||||
count++;
|
||||
|
||||
if (count >= convUpperlimit)
|
||||
if (count >= convUpperLimit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,9 +79,8 @@ public class ConversationController : ControllerBase
|
|||
[HttpGet("/conversation/{conversationId}/dialogs")]
|
||||
public async Task<IEnumerable<ChatResponseModel>> GetDialogs([FromRoute] string conversationId)
|
||||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
conv.SetConversationId(conversationId, new List<MessageState>(), isReadOnly: true);
|
||||
var history = conv.GetDialogHistory(fromBreakpoint: false);
|
||||
var storage = _services.GetRequiredService<IConversationStorage>();
|
||||
var history = storage.GetDialogs(conversationId);
|
||||
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
|
@ -555,10 +554,10 @@ public class ConversationController : ControllerBase
|
|||
|
||||
#region Search state keys
|
||||
[HttpGet("/conversation/state/keys")]
|
||||
public async Task<List<string>> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] bool preLoad = false)
|
||||
public async Task<List<string>> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] int convLimit = 100, [FromQuery] bool preload = false)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var keys = await convService.GetConversationStateSearhKeys(query, keyLimit: keyLimit, preLoad: preLoad);
|
||||
var keys = await convService.GetConversationStateSearhKeys(query, keyLimit: keyLimit, convLimit: convLimit, preload: preload);
|
||||
return keys;
|
||||
}
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using BotSharp.Abstraction.Loggers;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Interpreters.Settings;
|
||||
using BotSharp.Core.Crontab.Abstraction;
|
||||
using BotSharp.Plugin.ChatHub.Hooks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
@ -18,6 +17,10 @@ public class ChatHubPlugin : IBotSharpPlugin
|
|||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
var settings = new ChatHubSettings();
|
||||
config.Bind("ChatHub", settings);
|
||||
services.AddSingleton(x => settings);
|
||||
|
||||
// Register hooks
|
||||
services.AddScoped<IConversationHook, ChatHubConversationHook>();
|
||||
services.AddScoped<IConversationHook, StreamingLogHook>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Plugin.ChatHub.Enums;
|
||||
|
||||
public static class EventDispatchType
|
||||
{
|
||||
public const string Group = "group";
|
||||
public const string User = "user";
|
||||
}
|
||||
|
|
@ -9,8 +9,9 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
private readonly IHubContext<SignalRHub> _chatHub;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly ChatHubSettings _settings;
|
||||
|
||||
#region Event
|
||||
#region Events
|
||||
private const string INIT_CLIENT_CONVERSATION = "OnConversationInitFromClient";
|
||||
private const string RECEIVE_CLIENT_MESSAGE = "OnMessageReceivedFromClient";
|
||||
private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant";
|
||||
|
|
@ -23,12 +24,14 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
IServiceProvider services,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
BotSharpOptions options,
|
||||
ChatHubSettings settings,
|
||||
IUserIdentity user)
|
||||
{
|
||||
_services = services;
|
||||
_chatHub = chatHub;
|
||||
_user = user;
|
||||
_options = options;
|
||||
_settings = settings;
|
||||
Priority = -1; // Make sure this hook is the top one.
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +45,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
var user = await userService.GetUser(conv.User.Id);
|
||||
conv.User = UserViewModel.FromUser(user);
|
||||
|
||||
await InitClientConversation(conv);
|
||||
await InitClientConversation(conv.Id, conv);
|
||||
await base.OnConversationInitialized(conversation);
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +66,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
|
||||
Sender = UserViewModel.FromUser(sender)
|
||||
};
|
||||
await ReceiveClientMessage(model);
|
||||
await ReceiveClientMessage(conv.ConversationId, model);
|
||||
|
||||
// Send typing-on to client
|
||||
var action = new ConversationSenderActionModel
|
||||
|
|
@ -71,7 +74,8 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
ConversationId = conv.ConversationId,
|
||||
SenderAction = SenderActionEnum.TypingOn
|
||||
};
|
||||
await GenerateSenderAction(action);
|
||||
|
||||
await GenerateSenderAction(conv.ConversationId, action);
|
||||
await base.OnMessageReceived(message);
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +88,8 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
SenderAction = SenderActionEnum.TypingOn,
|
||||
Indication = message.Indication
|
||||
};
|
||||
await GenerateSenderAction(action);
|
||||
|
||||
await GenerateSenderAction(conv.ConversationId, action);
|
||||
await base.OnFunctionExecuting(message);
|
||||
}
|
||||
|
||||
|
|
@ -121,8 +126,8 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
SenderAction = SenderActionEnum.TypingOff
|
||||
};
|
||||
|
||||
await GenerateSenderAction(action);
|
||||
await ReceiveAssistantMessage(json);
|
||||
await GenerateSenderAction(conv.ConversationId, action);
|
||||
await ReceiveAssistantMessage(conv.ConversationId, json);
|
||||
await base.OnResponseGenerated(message);
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +151,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
}
|
||||
}, _options.JsonSerializerOptions);
|
||||
|
||||
await GenerateNotification(json);
|
||||
await GenerateNotification(conv.ConversationId, json);
|
||||
await base.OnNotificationGenerated(message);
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +163,8 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
ConversationId = conversationId,
|
||||
MessageId = messageId
|
||||
};
|
||||
await DeleteMessage(model);
|
||||
|
||||
await DeleteMessage(conversationId, model);
|
||||
await base.OnMessageDeleted(conversationId, messageId);
|
||||
}
|
||||
|
||||
|
|
@ -169,34 +175,77 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
return sidecar == null || !sidecar.IsEnabled();
|
||||
}
|
||||
|
||||
private async Task InitClientConversation(ConversationViewModel conversation)
|
||||
private async Task InitClientConversation(string conversationId, ConversationViewModel conversation)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveClientMessage(ChatResponseModel model)
|
||||
private async Task ReceiveClientMessage(string conversationId, ChatResponseModel model)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_CLIENT_MESSAGE, model);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_CLIENT_MESSAGE, model);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_CLIENT_MESSAGE, model);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveAssistantMessage(string? json)
|
||||
private async Task ReceiveAssistantMessage(string conversationId, string? json)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task GenerateSenderAction(ConversationSenderActionModel action)
|
||||
private async Task GenerateSenderAction(string conversationId, ConversationSenderActionModel action)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_SENDER_ACTION, action);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_SENDER_ACTION, action);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_SENDER_ACTION, action);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteMessage(ChatResponseModel model)
|
||||
private async Task DeleteMessage(string conversationId, ChatResponseModel model)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(DELETE_MESSAGE, model);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateNotification(string? json)
|
||||
private async Task GenerateNotification(string conversationId, string? json)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,18 +11,25 @@ public class ChatHubCrontabHook : ICrontabHook
|
|||
private readonly IUserIdentity _user;
|
||||
private readonly IConversationStorage _storage;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly ChatHubSettings _settings;
|
||||
|
||||
#region Events
|
||||
private const string GENERATE_NOTIFICATION = "OnNotificationGenerated";
|
||||
#endregion
|
||||
|
||||
public ChatHubCrontabHook(IServiceProvider services,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
IUserIdentity user,
|
||||
IConversationStorage storage,
|
||||
BotSharpOptions options)
|
||||
BotSharpOptions options,
|
||||
ChatHubSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_chatHub = chatHub;
|
||||
_user = user;
|
||||
_storage = storage;
|
||||
_options = options;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task OnCronTriggered(CrontabItem item)
|
||||
|
|
@ -41,6 +48,13 @@ public class ChatHubCrontabHook : ICrontabHook
|
|||
}
|
||||
}, _options.JsonSerializerOptions);
|
||||
|
||||
await _chatHub.Clients.User(item.UserId).SendAsync("OnNotificationGenerated", json);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(item.ConversationId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(item.UserId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
private readonly ConversationSetting _convSettings;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly JsonSerializerOptions _localJsonOptions;
|
||||
private readonly ChatHubSettings _settings;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IHubContext<SignalRHub> _chatHub;
|
||||
private readonly IConversationStateService _state;
|
||||
|
|
@ -16,7 +17,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
private readonly IAgentService _agentService;
|
||||
private readonly IRoutingContext _routingCtx;
|
||||
|
||||
#region Event
|
||||
#region Events
|
||||
private const string CONTENT_LOG_GENERATED = "OnConversationContentLogGenerated";
|
||||
private const string STATE_LOG_GENERATED = "OnConversateStateLogGenerated";
|
||||
private const string AGENT_QUEUE_CHANGED = "OnAgentQueueChanged";
|
||||
|
|
@ -26,6 +27,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
public StreamingLogHook(
|
||||
ConversationSetting convSettings,
|
||||
BotSharpOptions options,
|
||||
ChatHubSettings settings,
|
||||
IServiceProvider serivces,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
IConversationStateService state,
|
||||
|
|
@ -35,6 +37,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
{
|
||||
_convSettings = convSettings;
|
||||
_options = options;
|
||||
_settings = settings;
|
||||
_services = serivces;
|
||||
_chatHub = chatHub;
|
||||
_state = state;
|
||||
|
|
@ -58,7 +61,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.UserInput,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg)
|
||||
|
|
@ -76,7 +79,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.UserInput,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnRenderingTemplate(Agent agent, string name, string content)
|
||||
|
|
@ -98,7 +101,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.HardRule,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
|
||||
|
|
@ -126,7 +129,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.FunctionCall,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public override async Task OnFunctionExecuted(RoleDialogModel message)
|
||||
|
|
@ -147,7 +150,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.FunctionCall,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -174,7 +177,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.Prompt,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -208,7 +211,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.AgentResponse,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +229,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.FunctionCall,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public override async Task OnConversationEnding(RoleDialogModel message)
|
||||
|
|
@ -243,7 +246,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.FunctionCall,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public override async Task OnBreakpointUpdated(string conversationId, bool resetStates)
|
||||
|
|
@ -271,7 +274,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
},
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public override async Task OnStateChanged(StateChangeModel stateChange)
|
||||
|
|
@ -281,7 +284,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
|
||||
if (stateChange == null) return;
|
||||
|
||||
await SendStateChange(stateChange);
|
||||
await SendStateChange(conversationId, stateChange);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
|
@ -310,7 +313,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.HardRule,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnAgentDequeued(string agentId, string currentAgentId, string? reason = null)
|
||||
|
|
@ -338,7 +341,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.HardRule,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnAgentReplaced(string fromAgentId, string toAgentId, string? reason = null)
|
||||
|
|
@ -366,7 +369,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.HardRule,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnAgentQueueEmptied(string agentId, string? reason = null)
|
||||
|
|
@ -391,7 +394,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.HardRule,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message)
|
||||
|
|
@ -410,7 +413,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.AgentResponse,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnRoutingInstructionRevised(FunctionCallFromLlm instruct, RoleDialogModel message)
|
||||
|
|
@ -428,32 +431,62 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
Source = ContentLogSource.HardRule,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(input);
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Private methods
|
||||
private async Task SendContentLog(ContentLogInputModel input)
|
||||
private async Task SendContentLog(string conversationId, ContentLogInputModel input)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendStateLog(string conversationId, string agentId, Dictionary<string, string> states, RoleDialogModel message)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAgentQueueLog(string conversationId, string log)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendStateChange(StateChangeModel stateChange)
|
||||
private async Task SendStateChange(string conversationId, StateChangeModel stateChange)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private string BuildContentLog(ContentLogInputModel input)
|
||||
{
|
||||
var output = new ContentLogOutputModel
|
||||
|
|
|
|||
|
|
@ -9,18 +9,25 @@ public class WelcomeHook : ConversationHookBase
|
|||
private readonly IUserIdentity _user;
|
||||
private readonly IConversationStorage _storage;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly ChatHubSettings _settings;
|
||||
|
||||
#region Events
|
||||
private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant";
|
||||
#endregion
|
||||
|
||||
public WelcomeHook(IServiceProvider services,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
IUserIdentity user,
|
||||
IConversationStorage storage,
|
||||
BotSharpOptions options)
|
||||
BotSharpOptions options,
|
||||
ChatHubSettings settings)
|
||||
{
|
||||
_services = services;
|
||||
_chatHub = chatHub;
|
||||
_user = user;
|
||||
_storage = storage;
|
||||
_options = options;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public override async Task OnUserAgentConnectedInitially(Conversation conversation)
|
||||
|
|
@ -71,7 +78,14 @@ public class WelcomeHook : ConversationHookBase
|
|||
|
||||
_storage.Append(conversation.Id, dialog);
|
||||
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", json);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversation.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Plugin.ChatHub.Settings;
|
||||
|
||||
public class ChatHubSettings
|
||||
{
|
||||
public string EventDispatchBy { get; set; } = EventDispatchType.Group;
|
||||
}
|
||||
|
|
@ -33,6 +33,13 @@ public class SignalRHub : Hub
|
|||
if (!string.IsNullOrEmpty(conversationId))
|
||||
{
|
||||
_logger.LogInformation($"Connection {Context.ConnectionId} is with conversation {conversationId}");
|
||||
|
||||
var settings = _services.GetRequiredService<ChatHubSettings>();
|
||||
if (settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, conversationId);
|
||||
}
|
||||
|
||||
var conv = await convService.GetConversation(conversationId);
|
||||
if (conv != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,4 +31,6 @@ global using BotSharp.Abstraction.Routing;
|
|||
global using BotSharp.Abstraction.Messaging;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
global using BotSharp.Abstraction.Templating;
|
||||
global using BotSharp.Abstraction.Templating;
|
||||
global using BotSharp.Plugin.ChatHub.Settings;
|
||||
global using BotSharp.Plugin.ChatHub.Enums;
|
||||
|
|
@ -11,6 +11,7 @@ public class DialogMongoElement
|
|||
public string? RichContent { get; set; }
|
||||
public string? SecondaryRichContent { get; set; }
|
||||
public string? Payload { get; set; }
|
||||
public object? Data { get; set; }
|
||||
|
||||
public DialogMongoElement()
|
||||
{
|
||||
|
|
@ -26,7 +27,8 @@ public class DialogMongoElement
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = dialog.RichContent,
|
||||
SecondaryRichContent = dialog.SecondaryRichContent,
|
||||
Payload = dialog.Payload
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +41,8 @@ public class DialogMongoElement
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = dialog.RichContent,
|
||||
SecondaryRichContent = dialog.SecondaryRichContent,
|
||||
Payload = dialog.Payload
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -614,20 +614,20 @@ public partial class MongoRepository
|
|||
#if !DEBUG
|
||||
[SharpCache(10)]
|
||||
#endif
|
||||
public List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100)
|
||||
public List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100)
|
||||
{
|
||||
var convFilter = Builders<ConversationDocument>.Filter.Gte(x => x.DialogCount, messageLowerLimit);
|
||||
var conversations = _dc.Conversations.Find(convFilter)
|
||||
.SortByDescending(x => x.UpdatedTime)
|
||||
.Limit(convUpperlimit)
|
||||
.ToList();
|
||||
var stateBuilder = Builders<ConversationStateDocument>.Filter;
|
||||
var sortDef = Builders<ConversationStateDocument>.Sort.Descending(x => x.UpdatedTime);
|
||||
var stateFilters = new List<FilterDefinition<ConversationStateDocument>>()
|
||||
{
|
||||
stateBuilder.Exists(x => x.States),
|
||||
stateBuilder.Ne(x => x.States, [])
|
||||
};
|
||||
|
||||
if (conversations.IsNullOrEmpty()) return [];
|
||||
|
||||
var convIds = conversations.Select(x => x.Id).ToList();
|
||||
var stateFilter = Builders<ConversationStateDocument>.Filter.In(x => x.ConversationId, convIds);
|
||||
|
||||
var states = _dc.ConversationStates.Find(stateFilter).ToList();
|
||||
var states = _dc.ConversationStates.Find(stateBuilder.And(stateFilters))
|
||||
.Sort(sortDef)
|
||||
.Limit(convUpperLimit)
|
||||
.ToList();
|
||||
var keys = states.SelectMany(x => x.States.Select(x => x.Key)).Distinct().ToList();
|
||||
return keys;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,6 +222,10 @@
|
|||
"Enabled": false
|
||||
},
|
||||
|
||||
"ChatHub": {
|
||||
"EventDispatchBy": "group"
|
||||
},
|
||||
|
||||
"SharpCache": {
|
||||
"Enabled": true,
|
||||
"CacheType": 1,
|
||||
|
|
|
|||
Loading…
Reference in a new issue