diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 72f4ac9e..907dd451 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -15,7 +15,7 @@ public interface IConversationService Task> GetLastConversations(); Task DeleteConversation(string id); Task TruncateConversation(string conversationId, string messageId); - Task> GetConversationContentLogs(string conversationId); + Task> GetConversationContentLogs(string conversationId); Task> GetConversationStateLogs(string conversationId); /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ContentLogInputModel.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ContentLogInputModel.cs new file mode 100644 index 00000000..a1c4538b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ContentLogInputModel.cs @@ -0,0 +1,22 @@ +namespace BotSharp.Abstraction.Loggers.Models; + +public class ContentLogInputModel +{ + public string ConversationId { get; set; } + public string? Name { get; set; } + public string? AgentId { get; set; } + public string Log { get; set; } + public string Source { get; set; } + public RoleDialogModel Message { get; set; } + + public ContentLogInputModel() + { + + } + + public ContentLogInputModel(string conversationId, RoleDialogModel message) + { + ConversationId = conversationId; + Message = message; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ConversationContentLogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ContentLogOutputModel.cs similarity index 84% rename from src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ConversationContentLogModel.cs rename to src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ContentLogOutputModel.cs index 82a1ca11..fc2ed794 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ConversationContentLogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Models/ContentLogOutputModel.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Loggers.Models; -public class ConversationContentLogModel +public class ContentLogOutputModel { [JsonPropertyName("conversation_id")] public string ConversationId { get; set; } @@ -11,6 +11,9 @@ public class ConversationContentLogModel [JsonPropertyName("name")] public string? Name { get; set; } + [JsonPropertyName("agent_id")] + public string? AgentId { get; set; } + [JsonPropertyName("role")] public string Role { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index a94e3c60..0c06f9fa 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -72,8 +72,8 @@ public interface IBotSharpRepository #endregion #region Conversation Content Log - void SaveConversationContentLog(ConversationContentLogModel log); - List GetConversationContentLogs(string conversationId); + void SaveConversationContentLog(ContentLogOutputModel log); + List GetConversationContentLogs(string conversationId); #endregion #region Conversation State Log diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Log.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Log.cs index 0d13bd66..7b583f3f 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Log.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Log.cs @@ -5,7 +5,7 @@ namespace BotSharp.Core.Conversations.Services; public partial class ConversationService { - public async Task> GetConversationContentLogs(string conversationId) + public async Task> GetConversationContentLogs(string conversationId) { var db = _services.GetRequiredService(); var logs = db.GetConversationContentLogs(conversationId); diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 232c8a71..054e9c15 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -257,12 +257,12 @@ public class BotSharpDbContext : Database, IBotSharpRepository #endregion #region Conversation Content Log - public void SaveConversationContentLog(ConversationContentLogModel log) + public void SaveConversationContentLog(ContentLogOutputModel log) { throw new NotImplementedException(); } - public List GetConversationContentLogs(string conversationId) + public List GetConversationContentLogs(string conversationId) { throw new NotImplementedException(); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 057580e5..a9a81949 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -409,7 +409,7 @@ namespace BotSharp.Core.Repository foreach (var file in Directory.GetFiles(contentLogDir)) { var text = File.ReadAllText(file); - var log = JsonSerializer.Deserialize(text); + var log = JsonSerializer.Deserialize(text); if (log == null) continue; if (log.CreateTime >= refTime) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs index 72b5d882..71bde9e1 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs @@ -63,7 +63,7 @@ namespace BotSharp.Core.Repository #endregion #region Conversation Content Log - public void SaveConversationContentLog(ConversationContentLogModel log) + public void SaveConversationContentLog(ContentLogOutputModel log) { if (log == null) return; @@ -88,9 +88,9 @@ namespace BotSharp.Core.Repository File.WriteAllText(file, JsonSerializer.Serialize(log, _options)); } - public List GetConversationContentLogs(string conversationId) + public List GetConversationContentLogs(string conversationId) { - var logs = new List(); + var logs = new List(); if (string.IsNullOrEmpty(conversationId)) return logs; var convDir = FindConversationDirectory(conversationId); @@ -102,7 +102,7 @@ namespace BotSharp.Core.Repository foreach (var file in Directory.GetFiles(logDir)) { var text = File.ReadAllText(file); - var log = JsonSerializer.Deserialize(text); + var log = JsonSerializer.Deserialize(text); if (log == null) continue; logs.Add(log); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs index 135a3625..0b914635 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs @@ -37,7 +37,7 @@ public class LoggerController : ControllerBase } [HttpGet("/logger/conversation/{conversationId}/content-log")] - public async Task> GetConversationContentLogs([FromRoute] string conversationId) + public async Task> GetConversationContentLogs([FromRoute] string conversationId) { var conversationService = _services.GetRequiredService(); return await conversationService.GetConversationContentLogs(conversationId); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 812f87e6..dc0ddbb0 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -49,19 +49,19 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR { var conversationId = _state.GetConversationId(); var log = $"{message.Content}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, _user.UserName, log, ContentLogSource.UserInput, message)); + + var input = new ContentLogInputModel(conversationId, message) + { + Name = _user.UserName, + Source = ContentLogSource.UserInput, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); } public async Task BeforeGenerating(Agent agent, List conversations) { if (!_convSettings.ShowVerboseLog) return; - - /*var _state = _services.GetRequiredService(); - var conversationId = _state.GetConversationId(); - var dialog = conversations.Last(); - var log = $"{dialog.Role}: {dialog.Content} [msg_id: {dialog.MessageId}] ==>"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, log));*/ } public override async Task OnFunctionExecuted(RoleDialogModel message) @@ -69,8 +69,15 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var conversationId = _state.GetConversationId(); var agent = await _agentService.LoadAgent(message.CurrentAgentId); var log = $"{message.FunctionName}({message.FunctionArgs})\r\n => {message.Content}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, agent?.Name, log, ContentLogSource.FunctionCall, message)); + + var input = new ContentLogInputModel(conversationId, message) + { + Name = agent?.Name, + AgentId = agent?.Id, + Source = ContentLogSource.FunctionCall, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); } /// @@ -87,8 +94,15 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var agent = await _agentService.LoadAgent(message.CurrentAgentId); var log = tokenStats.Prompt; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, agent?.Name, log, ContentLogSource.Prompt, message)); + + var input = new ContentLogInputModel(conversationId, message) + { + Name = agent?.Name, + AgentId = agent?.Id, + Source = ContentLogSource.Prompt, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); } /// @@ -111,31 +125,141 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR var richContent = JsonSerializer.Serialize(message.RichContent, _serializerOptions); log += $"\r\n{richContent}"; } - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conv.ConversationId, agent?.Name, log, ContentLogSource.AgentResponse, message)); + + var input = new ContentLogInputModel(conv.ConversationId, message) + { + Name = agent?.Name, + AgentId = agent?.Id, + Source = ContentLogSource.AgentResponse, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); } } - private string BuildContentLog(string conversationId, string? name, string logContent, string logSource, RoleDialogModel message) + #region IRoutingHook + public async Task OnAgentEnqueued(string agentId, string preAgentId, string? reason = null) { - var log = new ConversationContentLogModel + var conversationId = _state.GetConversationId(); + var agent = await _agentService.LoadAgent(agentId); + var preAgent = await _agentService.LoadAgent(preAgentId); + + var log = $"{agent.Name} is enqueued{(reason != null ? $" ({reason})" : "")}"; + var message = new RoleDialogModel(AgentRole.System, log) { - ConversationId = conversationId, - MessageId = message.MessageId, - Name = name, - Role = message.Role, - Content = logContent, - Source = logSource, + MessageId = _routingCtx.MessageId + }; + + var input = new ContentLogInputModel(conversationId, message) + { + Name = "Router", + Source = ContentLogSource.HardRule, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); + } + + public async Task OnAgentDequeued(string agentId, string currentAgentId, string? reason = null) + { + var conversationId = _state.GetConversationId(); + var agent = await _agentService.LoadAgent(agentId); + var currentAgent = await _agentService.LoadAgent(currentAgentId); + + var log = $"{agent.Name} is dequeued{(reason != null ? $" ({reason})" : "")}, current agent is {currentAgent?.Name}"; + var message = new RoleDialogModel(AgentRole.System, log) + { + MessageId = _routingCtx.MessageId + }; + + var input = new ContentLogInputModel(conversationId, message) + { + Name = "Router", + Source = ContentLogSource.HardRule, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); + } + + public async Task OnAgentReplaced(string fromAgentId, string toAgentId, string? reason = null) + { + var conversationId = _state.GetConversationId(); + var fromAgent = await _agentService.LoadAgent(fromAgentId); + var toAgent = await _agentService.LoadAgent(toAgentId); + + var log = $"{fromAgent.Name} is replaced to {toAgent.Name}{(reason != null ? $" ({reason})" : "")}"; + var message = new RoleDialogModel(AgentRole.System, log) + { + MessageId = _routingCtx.MessageId + }; + + var input = new ContentLogInputModel(conversationId, message) + { + Name = "Router", + Source = ContentLogSource.HardRule, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); + } + + public async Task OnAgentQueueEmptied(string agentId, string? reason = null) + { + var conversationId = _state.GetConversationId(); + var agent = await _agentService.LoadAgent(agentId); + + var log = reason ?? "Agent queue is cleared"; + var message = new RoleDialogModel(AgentRole.System, log) + { + MessageId = _routingCtx.MessageId + }; + + var input = new ContentLogInputModel(conversationId, message) + { + Name = "Router", + Source = ContentLogSource.HardRule, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); + } + + public async Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message) + { + var conversationId = _state.GetConversationId(); + var agent = await _agentService.LoadAgent(message.CurrentAgentId); + var log = JsonSerializer.Serialize(instruct, _serializerOptions); + + var input = new ContentLogInputModel(conversationId, message) + { + Name = agent?.Name, + AgentId = agent?.Id, + Source = ContentLogSource.AgentResponse, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); + } + #endregion + + + private string BuildContentLog(ContentLogInputModel input) + { + var output = new ContentLogOutputModel + { + ConversationId = input.ConversationId, + MessageId = input.Message.MessageId, + Name = input.Name, + AgentId = input.AgentId, + Role = input.Message.Role, + Content = input.Log, + Source = input.Source, CreateTime = DateTime.UtcNow }; - var json = JsonSerializer.Serialize(log, _serializerOptions); + var json = JsonSerializer.Serialize(output, _serializerOptions); var convSettings = _services.GetRequiredService(); if (convSettings.EnableContentLog) { var db = _services.GetRequiredService(); - db.SaveConversationContentLog(log); + db.SaveConversationContentLog(output); } return json; @@ -160,70 +284,4 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR return JsonSerializer.Serialize(log, _serializerOptions); } - - #region IRoutingHook - public async Task OnAgentEnqueued(string agentId, string preAgentId, string? reason = null) - { - var conversationId = _state.GetConversationId(); - var agent = await _agentService.LoadAgent(agentId); - var preAgent = await _agentService.LoadAgent(preAgentId); - - var log = $"{agent.Name} is enqueued{(reason != null ? $" ({reason})" : "")}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, "Router", log, ContentLogSource.HardRule, new RoleDialogModel(AgentRole.System, log) - { - MessageId = _routingCtx.MessageId - })); - } - - public async Task OnAgentDequeued(string agentId, string currentAgentId, string? reason = null) - { - var conversationId = _state.GetConversationId(); - var agent = await _agentService.LoadAgent(agentId); - var currentAgent = await _agentService.LoadAgent(currentAgentId); - - var log = $"{agent.Name} is dequeued{(reason != null ? $" ({reason})" : "")}, current agent is {currentAgent?.Name}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, "Router", log, ContentLogSource.HardRule, new RoleDialogModel(AgentRole.System, log) - { - MessageId = _routingCtx.MessageId - })); - } - - public async Task OnAgentReplaced(string fromAgentId, string toAgentId, string? reason = null) - { - var conversationId = _state.GetConversationId(); - var fromAgent = await _agentService.LoadAgent(fromAgentId); - var toAgent = await _agentService.LoadAgent(toAgentId); - - var log = $"{fromAgent.Name} is replaced to {toAgent.Name}{(reason != null ? $" ({reason})" : "")}"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, "Router", log, ContentLogSource.HardRule, new RoleDialogModel(AgentRole.System, log) - { - MessageId = _routingCtx.MessageId - })); - } - - public async Task OnAgentQueueEmptied(string agentId, string? reason = null) - { - var conversationId = _state.GetConversationId(); - var agent = await _agentService.LoadAgent(agentId); - - var log = reason ?? "Agent queue is cleared"; - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, "Router", log, ContentLogSource.HardRule, new RoleDialogModel(AgentRole.System, log) - { - MessageId = _routingCtx.MessageId - })); - } - - public async Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message) - { - var conversationId = _state.GetConversationId(); - var agent = await _agentService.LoadAgent(message.CurrentAgentId); - var log = JsonSerializer.Serialize(instruct, _serializerOptions); - await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", - BuildContentLog(conversationId, agent.Name, log, ContentLogSource.AgentResponse, message)); - } - #endregion -} +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs index 32d751fe..8626898a 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationContentLogDocument.cs @@ -5,6 +5,7 @@ public class ConversationContentLogDocument : MongoBase public string ConversationId { get; set; } public string MessageId { get; set; } public string? Name { get; set; } + public string? AgentId { get; set; } public string Role { get; set; } public string Source { get; set; } public string Content { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs index bc4d8e0e..861ee3e4 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs @@ -60,7 +60,7 @@ public partial class MongoRepository #endregion #region Conversation Content Log - public void SaveConversationContentLog(ConversationContentLogModel log) + public void SaveConversationContentLog(ContentLogOutputModel log) { if (log == null) return; @@ -72,6 +72,7 @@ public partial class MongoRepository ConversationId = conversationId, MessageId = messageId, Name = log.Name, + AgentId = log.AgentId, Role = log.Role, Source = log.Source, Content = log.Content, @@ -81,16 +82,17 @@ public partial class MongoRepository _dc.ContentLogs.InsertOne(logDoc); } - public List GetConversationContentLogs(string conversationId) + public List GetConversationContentLogs(string conversationId) { var logs = _dc.ContentLogs .AsQueryable() .Where(x => x.ConversationId == conversationId) - .Select(x => new ConversationContentLogModel + .Select(x => new ContentLogOutputModel { ConversationId = x.ConversationId, MessageId = x.MessageId, Name = x.Name, + AgentId = x.AgentId, Role = x.Role, Source = x.Source, Content = x.Content,