diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
index 06a97174..9239b865 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
@@ -79,4 +79,7 @@ public abstract class ConversationHookBase : IConversationHook
public virtual Task OnBreakpointUpdated(string conversationId, bool resetStates)
=> Task.CompletedTask;
+
+ public virtual Task OnNotificationGenerated(RoleDialogModel message)
+ => Task.CompletedTask;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs
new file mode 100644
index 00000000..c4e73d69
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Conversations.Enums;
+
+public static class MessageTypeName
+{
+ public const string Plain = "plain";
+ public const string Notification = "notification";
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
index 9ed47c61..34371ba5 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Functions.Models;
-
namespace BotSharp.Abstraction.Conversations;
public interface IConversationHook
@@ -107,4 +105,11 @@ public interface IConversationHook
///
///
Task OnBreakpointUpdated(string conversationId, bool resetStates);
+
+ ///
+ /// Generate a notification
+ ///
+ ///
+ ///
+ Task OnNotificationGenerated(RoleDialogModel message);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
index bddc5426..7e7e372e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
@@ -8,7 +8,7 @@ public interface IConversationService
IConversationStateService States { get; }
string ConversationId { get; }
Task NewConversation(Conversation conversation);
- void SetConversationId(string conversationId, List states);
+ void SetConversationId(string conversationId, List states, bool isReadOnly = false);
Task GetConversation(string id);
Task> GetConversations(ConversationFilter filter);
Task UpdateConversationTitle(string id, string title);
@@ -41,7 +41,7 @@ public interface IConversationService
PostbackMessageModel? replyMessage,
Func onResponseReceived);
- List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true);
+ List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true, IEnumerable? includeMessageTypes = null);
Task CleanHistory(string agentId);
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs
index 5f7066d2..a586e217 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs
@@ -2,7 +2,6 @@ namespace BotSharp.Abstraction.Conversations;
public interface IConversationStorage
{
- void InitStorage(string conversationId);
void Append(string conversationId, RoleDialogModel dialog);
List GetDialogs(string conversationId);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
index ad7ffd04..890f3211 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
@@ -83,6 +83,9 @@ public class DialogMetaData
[JsonPropertyName("message_id")]
public string MessageId { get; set; }
+ [JsonPropertyName("message_type")]
+ public string MessageType { get; set; }
+
[JsonPropertyName("function_name")]
public string? FunctionName { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
index 5b8f7017..679527a3 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Messaging.Models.RichContent;
@@ -11,6 +12,11 @@ public class RoleDialogModel : ITrackableMessage
///
public string MessageId { get; set; }
+ ///
+ /// The message type
+ ///
+ public string MessageType { get; set; } = MessageTypeName.Plain;
+
///
/// user, system, assistant, function
///
@@ -101,6 +107,7 @@ public class RoleDialogModel : ITrackableMessage
public List GeneratedImages { get; set; } = new List();
+
private RoleDialogModel()
{
}
@@ -110,6 +117,7 @@ public class RoleDialogModel : ITrackableMessage
Role = role;
Content = text;
MessageId = Guid.NewGuid().ToString();
+ MessageType = MessageTypeName.Plain;
}
public override string ToString()
@@ -132,6 +140,7 @@ public class RoleDialogModel : ITrackableMessage
{
CurrentAgentId = source.CurrentAgentId,
MessageId = source.MessageId,
+ MessageType = source.MessageType,
FunctionArgs = source.FunctionArgs,
FunctionName = source.FunctionName,
ToolCallId = source.ToolCallId,
diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs
index 45d74a51..78ab19ad 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs
@@ -7,4 +7,5 @@ public static class ContentLogSource
public const string FunctionCall = "function call";
public const string AgentResponse = "agent response";
public const string HardRule = "hard rule";
+ public const string Notification = "notification";
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs
index 0983e2ed..88d3a52d 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Templating;
@@ -21,6 +22,7 @@ public partial class ConversationService
if (dialogs.IsNullOrEmpty()) continue;
+ dialogs = dialogs.Where(x => x.MessageType != MessageTypeName.Notification).ToList();
var content = GetConversationContent(dialogs);
if (string.IsNullOrWhiteSpace(content)) continue;
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
index 6e0f97d1..8511b873 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
@@ -106,7 +106,7 @@ public partial class ConversationService : IConversationService
throw new NotImplementedException();
}
- public List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true)
+ public List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true, IEnumerable? includeMessageTypes = null)
{
if (string.IsNullOrEmpty(_conversationId))
{
@@ -115,6 +115,15 @@ public partial class ConversationService : IConversationService
var dialogs = _storage.GetDialogs(_conversationId);
+ if (!includeMessageTypes.IsNullOrEmpty())
+ {
+ dialogs = dialogs.Where(x => string.IsNullOrEmpty(x.MessageType) || includeMessageTypes.Contains(x.MessageType)).ToList();
+ }
+ else
+ {
+ dialogs = dialogs.Where(x => string.IsNullOrEmpty(x.MessageType) || x.MessageType.IsEqualTo(MessageTypeName.Plain)).ToList();
+ }
+
if (fromBreakpoint)
{
var db = _services.GetRequiredService();
@@ -134,7 +143,7 @@ public partial class ConversationService : IConversationService
.ToList();
}
- public void SetConversationId(string conversationId, List states)
+ public void SetConversationId(string conversationId, List states, bool isReadOnly = false)
{
_conversationId = conversationId;
_state.Load(_conversationId);
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
index 5abc4fc5..ea145ac0 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
@@ -41,6 +41,7 @@ public class ConversationStorage : IConversationStorage
Role = dialog.Role,
AgentId = agentId,
MessageId = dialog.MessageId,
+ MessageType = dialog.MessageType,
FunctionName = dialog.FunctionName,
CreateTime = dialog.CreatedAt
};
@@ -65,6 +66,7 @@ public class ConversationStorage : IConversationStorage
Role = dialog.Role,
AgentId = agentId,
MessageId = dialog.MessageId,
+ MessageType = dialog.MessageType,
SenderId = dialog.SenderId,
FunctionName = dialog.FunctionName,
CreateTime = dialog.CreatedAt
@@ -108,6 +110,7 @@ public class ConversationStorage : IConversationStorage
var role = meta.Role;
var currentAgentId = meta.AgentId;
var messageId = meta.MessageId;
+ var messageType = meta.MessageType;
var function = meta.FunctionName;
var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId;
var createdAt = meta.CreateTime;
@@ -120,6 +123,7 @@ public class ConversationStorage : IConversationStorage
{
CurrentAgentId = currentAgentId,
MessageId = messageId,
+ MessageType = messageType,
CreatedAt = createdAt,
SenderId = senderId,
FunctionName = function,
@@ -143,23 +147,4 @@ public class ConversationStorage : IConversationStorage
return results;
}
-
- public void InitStorage(string conversationId)
- {
- var file = GetStorageFile(conversationId);
- if (!File.Exists(file))
- {
- File.WriteAllLines(file, new string[0]);
- }
- }
-
- private string GetStorageFile(string conversationId)
- {
- var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId);
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
- return Path.Combine(dir, "dialogs.txt");
- }
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
index 5521b5e6..a020a490 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
@@ -1,8 +1,10 @@
+using Azure;
using BotSharp.Abstraction.Files.Constants;
using BotSharp.Abstraction.Files.Enums;
using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Users.Enums;
+using BotSharp.Core.Infrastructures;
namespace BotSharp.OpenAPI.Controllers;
@@ -251,6 +253,44 @@ public class ConversationController : ControllerBase
return isSuccess ? newMessageId : string.Empty;
}
+ #region Send notification
+ [HttpPost("/conversation/{conversationId}/notification")]
+ public async Task SendNotification([FromRoute] string conversationId, [FromBody] NewMessageModel input)
+ {
+ var conv = _services.GetRequiredService();
+ var routing = _services.GetRequiredService();
+ var userService = _services.GetRequiredService();
+
+ conv.SetConversationId(conversationId, new List(), isReadOnly: true);
+
+ var inputMsg = new RoleDialogModel(AgentRole.User, input.Text)
+ {
+ MessageId = Guid.NewGuid().ToString(),
+ CreatedAt = DateTime.UtcNow
+ };
+
+ var user = await userService.GetUser(_user.Id);
+ var response = new ChatResponseModel()
+ {
+ ConversationId = conversationId,
+ MessageId = inputMsg.MessageId,
+ Sender = new UserViewModel
+ {
+ Id = user?.Id ?? string.Empty,
+ FirstName = user?.FirstName ?? string.Empty,
+ LastName = user?.LastName ?? string.Empty
+ },
+ CreatedAt = DateTime.UtcNow
+ };
+
+ await HookEmitter.Emit(_services, async hook =>
+ await hook.OnNotificationGenerated(inputMsg)
+ );
+
+ return response;
+ }
+ #endregion
+
#region Send message
[HttpPost("/conversation/{agentId}/{conversationId}")]
public async Task SendMessage([FromRoute] string agentId,
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
index bf2292ea..80ee2299 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs
@@ -15,6 +15,7 @@ public class ChatHubConversationHook : ConversationHookBase
private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant";
private const string GENERATE_SENDER_ACTION = "OnSenderActionGenerated";
private const string DELETE_MESSAGE = "OnMessageDeleted";
+ private const string GENERATE_NOTIFICATION = "OnNotificationGenerated";
#endregion
public ChatHubConversationHook(
@@ -117,6 +118,31 @@ public class ChatHubConversationHook : ConversationHookBase
await base.OnResponseGenerated(message);
}
+
+ public override async Task OnNotificationGenerated(RoleDialogModel message)
+ {
+ var conv = _services.GetRequiredService();
+ var json = JsonSerializer.Serialize(new ChatResponseModel()
+ {
+ ConversationId = conv.ConversationId,
+ MessageId = message.MessageId,
+ Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
+ Function = message.FunctionName,
+ RichContent = message.SecondaryRichContent ?? message.RichContent,
+ Data = message.Data,
+ Sender = new UserViewModel()
+ {
+ FirstName = "AI",
+ LastName = "Assistant",
+ Role = AgentRole.Assistant
+ }
+ }, _options.JsonSerializerOptions);
+
+ await GenerateNotification(json);
+ await base.OnNotificationGenerated(message);
+ }
+
+
public override async Task OnMessageDeleted(string conversationId, string messageId)
{
var model = new ChatResponseModel
@@ -153,5 +179,10 @@ public class ChatHubConversationHook : ConversationHookBase
{
await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model);
}
+
+ private async Task GenerateNotification(string? json)
+ {
+ await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json);
+ }
#endregion
}
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs
index 74469177..3959f45d 100644
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs
@@ -60,7 +60,20 @@ public partial class KnowledgeService
}
// Save to vector db
- var dataIds = await SaveToVectorDb(collectionName, fileId, file.FileName, contents, file.FileSource);
+ var payload = new Dictionary()
+ {
+ { KnowledgePayloadName.DataSource, VectorDataSource.File },
+ { KnowledgePayloadName.FileId, fileId.ToString() },
+ { KnowledgePayloadName.FileName, file.FileName },
+ { KnowledgePayloadName.FileSource, file.FileSource }
+ };
+
+ if (!string.IsNullOrWhiteSpace(file.FileUrl))
+ {
+ payload[KnowledgePayloadName.FileUrl] = file.FileUrl;
+ }
+
+ var dataIds = await SaveToVectorDb(collectionName, fileId, file.FileName, contents, payload);
if (!dataIds.IsNullOrEmpty())
{
db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData
@@ -119,7 +132,20 @@ public partial class KnowledgeService
var fileId = Guid.NewGuid();
var contentType = FileUtility.GetFileContentType(fileName);
- var dataIds = await SaveToVectorDb(collectionName, fileId, fileName, contents, fileSource, fileUrl: refData?.Url);
+ var payload = new Dictionary()
+ {
+ { KnowledgePayloadName.DataSource, VectorDataSource.File },
+ { KnowledgePayloadName.FileId, fileId.ToString() },
+ { KnowledgePayloadName.FileName, fileName },
+ { KnowledgePayloadName.FileSource, fileSource }
+ };
+
+ if (!string.IsNullOrWhiteSpace(refData?.Url))
+ {
+ payload[KnowledgePayloadName.FileUrl] = refData.Url;
+ }
+
+ var dataIds = await SaveToVectorDb(collectionName, fileId, fileName, contents, payload);
db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData
{
Collection = collectionName,
@@ -386,8 +412,7 @@ public partial class KnowledgeService
}
private async Task> SaveToVectorDb(
- string collectionName, Guid fileId, string fileName, IEnumerable contents,
- string fileSource = KnowledgeDocSource.Api, string vectorDataSource = VectorDataSource.File, string? fileUrl = null)
+ string collectionName, Guid fileId, string fileName, IEnumerable contents, Dictionary? payload = null)
{
if (contents.IsNullOrEmpty())
{
@@ -398,25 +423,12 @@ public partial class KnowledgeService
var vectorDb = GetVectorDb();
var textEmbedding = GetTextEmbedding(collectionName);
- var payload = new Dictionary
- {
- { KnowledgePayloadName.DataSource, vectorDataSource },
- { KnowledgePayloadName.FileId, fileId.ToString() },
- { KnowledgePayloadName.FileName, fileName },
- { KnowledgePayloadName.FileSource, fileSource }
- };
-
- if (!string.IsNullOrWhiteSpace(fileUrl))
- {
- payload[KnowledgePayloadName.FileUrl] = fileUrl;
- }
-
for (int i = 0; i < contents.Count(); i++)
{
var content = contents.ElementAt(i);
var vector = await textEmbedding.GetVectorAsync(content);
var dataId = Guid.NewGuid();
- var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload);
+ var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload ?? new Dictionary());
if (!saved) continue;
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs
index 5c2a1698..1179d3cd 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs
@@ -48,6 +48,7 @@ public class DialogMetaDataMongoElement
public string Role { get; set; }
public string AgentId { get; set; }
public string MessageId { get; set; }
+ public string MessageType { get; set; }
public string? FunctionName { get; set; }
public string? SenderId { get; set; }
public DateTime CreateTime { get; set; }
@@ -64,6 +65,7 @@ public class DialogMetaDataMongoElement
Role = meta.Role,
AgentId = meta.AgentId,
MessageId = meta.MessageId,
+ MessageType = meta.MessageType,
FunctionName = meta.FunctionName,
SenderId = meta.SenderId,
CreateTime = meta.CreateTime,
@@ -77,6 +79,7 @@ public class DialogMetaDataMongoElement
Role = meta.Role,
AgentId = meta.AgentId,
MessageId = meta.MessageId,
+ MessageType = meta.MessageType,
FunctionName = meta.FunctionName,
SenderId = meta.SenderId,
CreateTime = meta.CreateTime,
diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs
index 9dde5b35..ef9faa93 100644
--- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs
+++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs
@@ -212,42 +212,45 @@ public class QdrantDb : IVectorDb
{
foreach (var item in payload)
{
- if (item.Value is string str)
- {
- point.Payload[item.Key] = str;
- }
- else if (item.Value is bool b)
+ var value = item.Value?.ToString();
+ if (value == null) continue;
+
+ if (bool.TryParse(value, out var b))
{
point.Payload[item.Key] = b;
}
- else if (item.Value is byte int8)
+ else if (byte.TryParse(value, out var int8))
{
point.Payload[item.Key] = int8;
}
- else if (item.Value is short int16)
+ else if (short.TryParse(value, out var int16))
{
point.Payload[item.Key] = int16;
}
- else if (item.Value is int int32)
+ else if (int.TryParse(value, out var int32))
{
point.Payload[item.Key] = int32;
}
- else if (item.Value is long int64)
+ else if (long.TryParse(value, out var int64))
{
point.Payload[item.Key] = int64;
}
- else if (item.Value is float f32)
+ else if (float.TryParse(value, out var f32))
{
point.Payload[item.Key] = f32;
}
- else if (item.Value is double f64)
+ else if (double.TryParse(value, out var f64))
{
point.Payload[item.Key] = f64;
}
- else if (item.Value is DateTime dt)
+ else if (DateTime.TryParse(value, out var dt))
{
point.Payload[item.Key] = dt.ToUniversalTime().ToString("o");
}
+ else
+ {
+ point.Payload[item.Key] = value;
+ }
}
}