Merge branch 'SciSharp:master' into master

This commit is contained in:
Haiping 2024-10-07 17:15:06 -05:00 committed by GitHub
commit 7033ec2484
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 168 additions and 56 deletions

View file

@ -79,4 +79,7 @@ public abstract class ConversationHookBase : IConversationHook
public virtual Task OnBreakpointUpdated(string conversationId, bool resetStates) public virtual Task OnBreakpointUpdated(string conversationId, bool resetStates)
=> Task.CompletedTask; => Task.CompletedTask;
public virtual Task OnNotificationGenerated(RoleDialogModel message)
=> Task.CompletedTask;
} }

View file

@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.Conversations.Enums;
public static class MessageTypeName
{
public const string Plain = "plain";
public const string Notification = "notification";
}

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Conversations; namespace BotSharp.Abstraction.Conversations;
public interface IConversationHook public interface IConversationHook
@ -107,4 +105,11 @@ public interface IConversationHook
/// <param name="conversationId"></param> /// <param name="conversationId"></param>
/// <returns></returns> /// <returns></returns>
Task OnBreakpointUpdated(string conversationId, bool resetStates); Task OnBreakpointUpdated(string conversationId, bool resetStates);
/// <summary>
/// Generate a notification
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
Task OnNotificationGenerated(RoleDialogModel message);
} }

View file

@ -8,7 +8,7 @@ public interface IConversationService
IConversationStateService States { get; } IConversationStateService States { get; }
string ConversationId { get; } string ConversationId { get; }
Task<Conversation> NewConversation(Conversation conversation); Task<Conversation> NewConversation(Conversation conversation);
void SetConversationId(string conversationId, List<MessageState> states); void SetConversationId(string conversationId, List<MessageState> states, bool isReadOnly = false);
Task<Conversation> GetConversation(string id); Task<Conversation> GetConversation(string id);
Task<PagedItems<Conversation>> GetConversations(ConversationFilter filter); Task<PagedItems<Conversation>> GetConversations(ConversationFilter filter);
Task<Conversation> UpdateConversationTitle(string id, string title); Task<Conversation> UpdateConversationTitle(string id, string title);
@ -41,7 +41,7 @@ public interface IConversationService
PostbackMessageModel? replyMessage, PostbackMessageModel? replyMessage,
Func<RoleDialogModel, Task> onResponseReceived); Func<RoleDialogModel, Task> onResponseReceived);
List<RoleDialogModel> GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true); List<RoleDialogModel> GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true, IEnumerable<string>? includeMessageTypes = null);
Task CleanHistory(string agentId); Task CleanHistory(string agentId);
/// <summary> /// <summary>

View file

@ -2,7 +2,6 @@ namespace BotSharp.Abstraction.Conversations;
public interface IConversationStorage public interface IConversationStorage
{ {
void InitStorage(string conversationId);
void Append(string conversationId, RoleDialogModel dialog); void Append(string conversationId, RoleDialogModel dialog);
List<RoleDialogModel> GetDialogs(string conversationId); List<RoleDialogModel> GetDialogs(string conversationId);
} }

View file

@ -83,6 +83,9 @@ public class DialogMetaData
[JsonPropertyName("message_id")] [JsonPropertyName("message_id")]
public string MessageId { get; set; } public string MessageId { get; set; }
[JsonPropertyName("message_type")]
public string MessageType { get; set; }
[JsonPropertyName("function_name")] [JsonPropertyName("function_name")]
public string? FunctionName { get; set; } public string? FunctionName { get; set; }

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Messaging.Models.RichContent;
@ -11,6 +12,11 @@ public class RoleDialogModel : ITrackableMessage
/// </summary> /// </summary>
public string MessageId { get; set; } public string MessageId { get; set; }
/// <summary>
/// The message type
/// </summary>
public string MessageType { get; set; } = MessageTypeName.Plain;
/// <summary> /// <summary>
/// user, system, assistant, function /// user, system, assistant, function
/// </summary> /// </summary>
@ -101,6 +107,7 @@ public class RoleDialogModel : ITrackableMessage
public List<ImageGeneration> GeneratedImages { get; set; } = new List<ImageGeneration>(); public List<ImageGeneration> GeneratedImages { get; set; } = new List<ImageGeneration>();
private RoleDialogModel() private RoleDialogModel()
{ {
} }
@ -110,6 +117,7 @@ public class RoleDialogModel : ITrackableMessage
Role = role; Role = role;
Content = text; Content = text;
MessageId = Guid.NewGuid().ToString(); MessageId = Guid.NewGuid().ToString();
MessageType = MessageTypeName.Plain;
} }
public override string ToString() public override string ToString()
@ -132,6 +140,7 @@ public class RoleDialogModel : ITrackableMessage
{ {
CurrentAgentId = source.CurrentAgentId, CurrentAgentId = source.CurrentAgentId,
MessageId = source.MessageId, MessageId = source.MessageId,
MessageType = source.MessageType,
FunctionArgs = source.FunctionArgs, FunctionArgs = source.FunctionArgs,
FunctionName = source.FunctionName, FunctionName = source.FunctionName,
ToolCallId = source.ToolCallId, ToolCallId = source.ToolCallId,

View file

@ -7,4 +7,5 @@ public static class ContentLogSource
public const string FunctionCall = "function call"; public const string FunctionCall = "function call";
public const string AgentResponse = "agent response"; public const string AgentResponse = "agent response";
public const string HardRule = "hard rule"; public const string HardRule = "hard rule";
public const string Notification = "notification";
} }

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Templating; using BotSharp.Abstraction.Templating;
@ -21,6 +22,7 @@ public partial class ConversationService
if (dialogs.IsNullOrEmpty()) continue; if (dialogs.IsNullOrEmpty()) continue;
dialogs = dialogs.Where(x => x.MessageType != MessageTypeName.Notification).ToList();
var content = GetConversationContent(dialogs); var content = GetConversationContent(dialogs);
if (string.IsNullOrWhiteSpace(content)) continue; if (string.IsNullOrWhiteSpace(content)) continue;

View file

@ -106,7 +106,7 @@ public partial class ConversationService : IConversationService
throw new NotImplementedException(); throw new NotImplementedException();
} }
public List<RoleDialogModel> GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true) public List<RoleDialogModel> GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true, IEnumerable<string>? includeMessageTypes = null)
{ {
if (string.IsNullOrEmpty(_conversationId)) if (string.IsNullOrEmpty(_conversationId))
{ {
@ -115,6 +115,15 @@ public partial class ConversationService : IConversationService
var dialogs = _storage.GetDialogs(_conversationId); 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) if (fromBreakpoint)
{ {
var db = _services.GetRequiredService<IBotSharpRepository>(); var db = _services.GetRequiredService<IBotSharpRepository>();
@ -134,7 +143,7 @@ public partial class ConversationService : IConversationService
.ToList(); .ToList();
} }
public void SetConversationId(string conversationId, List<MessageState> states) public void SetConversationId(string conversationId, List<MessageState> states, bool isReadOnly = false)
{ {
_conversationId = conversationId; _conversationId = conversationId;
_state.Load(_conversationId); _state.Load(_conversationId);

View file

@ -41,6 +41,7 @@ public class ConversationStorage : IConversationStorage
Role = dialog.Role, Role = dialog.Role,
AgentId = agentId, AgentId = agentId,
MessageId = dialog.MessageId, MessageId = dialog.MessageId,
MessageType = dialog.MessageType,
FunctionName = dialog.FunctionName, FunctionName = dialog.FunctionName,
CreateTime = dialog.CreatedAt CreateTime = dialog.CreatedAt
}; };
@ -65,6 +66,7 @@ public class ConversationStorage : IConversationStorage
Role = dialog.Role, Role = dialog.Role,
AgentId = agentId, AgentId = agentId,
MessageId = dialog.MessageId, MessageId = dialog.MessageId,
MessageType = dialog.MessageType,
SenderId = dialog.SenderId, SenderId = dialog.SenderId,
FunctionName = dialog.FunctionName, FunctionName = dialog.FunctionName,
CreateTime = dialog.CreatedAt CreateTime = dialog.CreatedAt
@ -108,6 +110,7 @@ public class ConversationStorage : IConversationStorage
var role = meta.Role; var role = meta.Role;
var currentAgentId = meta.AgentId; var currentAgentId = meta.AgentId;
var messageId = meta.MessageId; var messageId = meta.MessageId;
var messageType = meta.MessageType;
var function = meta.FunctionName; var function = meta.FunctionName;
var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId; var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId;
var createdAt = meta.CreateTime; var createdAt = meta.CreateTime;
@ -120,6 +123,7 @@ public class ConversationStorage : IConversationStorage
{ {
CurrentAgentId = currentAgentId, CurrentAgentId = currentAgentId,
MessageId = messageId, MessageId = messageId,
MessageType = messageType,
CreatedAt = createdAt, CreatedAt = createdAt,
SenderId = senderId, SenderId = senderId,
FunctionName = function, FunctionName = function,
@ -143,23 +147,4 @@ public class ConversationStorage : IConversationStorage
return results; 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");
}
} }

View file

@ -1,8 +1,10 @@
using Azure;
using BotSharp.Abstraction.Files.Constants; using BotSharp.Abstraction.Files.Constants;
using BotSharp.Abstraction.Files.Enums; using BotSharp.Abstraction.Files.Enums;
using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Enums;
using BotSharp.Core.Infrastructures;
namespace BotSharp.OpenAPI.Controllers; namespace BotSharp.OpenAPI.Controllers;
@ -251,6 +253,44 @@ public class ConversationController : ControllerBase
return isSuccess ? newMessageId : string.Empty; return isSuccess ? newMessageId : string.Empty;
} }
#region Send notification
[HttpPost("/conversation/{conversationId}/notification")]
public async Task<ChatResponseModel> SendNotification([FromRoute] string conversationId, [FromBody] NewMessageModel input)
{
var conv = _services.GetRequiredService<IConversationService>();
var routing = _services.GetRequiredService<IRoutingService>();
var userService = _services.GetRequiredService<IUserService>();
conv.SetConversationId(conversationId, new List<MessageState>(), 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<IConversationHook>(_services, async hook =>
await hook.OnNotificationGenerated(inputMsg)
);
return response;
}
#endregion
#region Send message #region Send message
[HttpPost("/conversation/{agentId}/{conversationId}")] [HttpPost("/conversation/{agentId}/{conversationId}")]
public async Task<ChatResponseModel> SendMessage([FromRoute] string agentId, public async Task<ChatResponseModel> SendMessage([FromRoute] string agentId,

View file

@ -15,6 +15,7 @@ public class ChatHubConversationHook : ConversationHookBase
private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant"; private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant";
private const string GENERATE_SENDER_ACTION = "OnSenderActionGenerated"; private const string GENERATE_SENDER_ACTION = "OnSenderActionGenerated";
private const string DELETE_MESSAGE = "OnMessageDeleted"; private const string DELETE_MESSAGE = "OnMessageDeleted";
private const string GENERATE_NOTIFICATION = "OnNotificationGenerated";
#endregion #endregion
public ChatHubConversationHook( public ChatHubConversationHook(
@ -117,6 +118,31 @@ public class ChatHubConversationHook : ConversationHookBase
await base.OnResponseGenerated(message); await base.OnResponseGenerated(message);
} }
public override async Task OnNotificationGenerated(RoleDialogModel message)
{
var conv = _services.GetRequiredService<IConversationService>();
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) public override async Task OnMessageDeleted(string conversationId, string messageId)
{ {
var model = new ChatResponseModel var model = new ChatResponseModel
@ -153,5 +179,10 @@ public class ChatHubConversationHook : ConversationHookBase
{ {
await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model); 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 #endregion
} }

View file

@ -60,7 +60,20 @@ public partial class KnowledgeService
} }
// Save to vector db // Save to vector db
var dataIds = await SaveToVectorDb(collectionName, fileId, file.FileName, contents, file.FileSource); var payload = new Dictionary<string, object>()
{
{ 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()) if (!dataIds.IsNullOrEmpty())
{ {
db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData
@ -119,7 +132,20 @@ public partial class KnowledgeService
var fileId = Guid.NewGuid(); var fileId = Guid.NewGuid();
var contentType = FileUtility.GetFileContentType(fileName); var contentType = FileUtility.GetFileContentType(fileName);
var dataIds = await SaveToVectorDb(collectionName, fileId, fileName, contents, fileSource, fileUrl: refData?.Url); var payload = new Dictionary<string, object>()
{
{ 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 db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData
{ {
Collection = collectionName, Collection = collectionName,
@ -386,8 +412,7 @@ public partial class KnowledgeService
} }
private async Task<IEnumerable<string>> SaveToVectorDb( private async Task<IEnumerable<string>> SaveToVectorDb(
string collectionName, Guid fileId, string fileName, IEnumerable<string> contents, string collectionName, Guid fileId, string fileName, IEnumerable<string> contents, Dictionary<string, object>? payload = null)
string fileSource = KnowledgeDocSource.Api, string vectorDataSource = VectorDataSource.File, string? fileUrl = null)
{ {
if (contents.IsNullOrEmpty()) if (contents.IsNullOrEmpty())
{ {
@ -398,25 +423,12 @@ public partial class KnowledgeService
var vectorDb = GetVectorDb(); var vectorDb = GetVectorDb();
var textEmbedding = GetTextEmbedding(collectionName); var textEmbedding = GetTextEmbedding(collectionName);
var payload = new Dictionary<string, object>
{
{ 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++) for (int i = 0; i < contents.Count(); i++)
{ {
var content = contents.ElementAt(i); var content = contents.ElementAt(i);
var vector = await textEmbedding.GetVectorAsync(content); var vector = await textEmbedding.GetVectorAsync(content);
var dataId = Guid.NewGuid(); 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<string, object>());
if (!saved) continue; if (!saved) continue;

View file

@ -48,6 +48,7 @@ public class DialogMetaDataMongoElement
public string Role { get; set; } public string Role { get; set; }
public string AgentId { get; set; } public string AgentId { get; set; }
public string MessageId { get; set; } public string MessageId { get; set; }
public string MessageType { get; set; }
public string? FunctionName { get; set; } public string? FunctionName { get; set; }
public string? SenderId { get; set; } public string? SenderId { get; set; }
public DateTime CreateTime { get; set; } public DateTime CreateTime { get; set; }
@ -64,6 +65,7 @@ public class DialogMetaDataMongoElement
Role = meta.Role, Role = meta.Role,
AgentId = meta.AgentId, AgentId = meta.AgentId,
MessageId = meta.MessageId, MessageId = meta.MessageId,
MessageType = meta.MessageType,
FunctionName = meta.FunctionName, FunctionName = meta.FunctionName,
SenderId = meta.SenderId, SenderId = meta.SenderId,
CreateTime = meta.CreateTime, CreateTime = meta.CreateTime,
@ -77,6 +79,7 @@ public class DialogMetaDataMongoElement
Role = meta.Role, Role = meta.Role,
AgentId = meta.AgentId, AgentId = meta.AgentId,
MessageId = meta.MessageId, MessageId = meta.MessageId,
MessageType = meta.MessageType,
FunctionName = meta.FunctionName, FunctionName = meta.FunctionName,
SenderId = meta.SenderId, SenderId = meta.SenderId,
CreateTime = meta.CreateTime, CreateTime = meta.CreateTime,

View file

@ -212,42 +212,45 @@ public class QdrantDb : IVectorDb
{ {
foreach (var item in payload) foreach (var item in payload)
{ {
if (item.Value is string str) var value = item.Value?.ToString();
{ if (value == null) continue;
point.Payload[item.Key] = str;
} if (bool.TryParse(value, out var b))
else if (item.Value is bool b)
{ {
point.Payload[item.Key] = 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; 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; 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; 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; 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; 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; 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"); point.Payload[item.Key] = dt.ToUniversalTime().ToString("o");
} }
else
{
point.Payload[item.Key] = value;
}
} }
} }