Merge pull request #301 from iceljc/features/save-conversation-log
add content log and state log
This commit is contained in:
commit
5f80b5e802
|
|
@ -1,3 +1,4 @@
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
using BotSharp.Abstraction.Repositories.Filters;
|
using BotSharp.Abstraction.Repositories.Filters;
|
||||||
|
|
||||||
namespace BotSharp.Abstraction.Conversations;
|
namespace BotSharp.Abstraction.Conversations;
|
||||||
|
|
@ -14,6 +15,8 @@ public interface IConversationService
|
||||||
Task<List<Conversation>> GetLastConversations();
|
Task<List<Conversation>> GetLastConversations();
|
||||||
Task<bool> DeleteConversation(string id);
|
Task<bool> DeleteConversation(string id);
|
||||||
Task<bool> TruncateConversation(string conversationId, string messageId);
|
Task<bool> TruncateConversation(string conversationId, string messageId);
|
||||||
|
Task<List<ConversationContentLogModel>> GetConversationContentLogs(string conversationId);
|
||||||
|
Task<List<ConversationStateLogModel>> GetConversationStateLogs(string conversationId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send message to LLM
|
/// Send message to LLM
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
namespace BotSharp.Abstraction.Conversations.Models;
|
|
||||||
|
|
||||||
public class ConversationStateLogModel
|
|
||||||
{
|
|
||||||
[JsonPropertyName("conversation_id")]
|
|
||||||
public string ConvsersationId { get; set; }
|
|
||||||
[JsonPropertyName("states")]
|
|
||||||
public string States { get; set; }
|
|
||||||
[JsonPropertyName("created_at")]
|
|
||||||
public DateTime CreateTime { get; set; }
|
|
||||||
}
|
|
||||||
|
|
@ -9,4 +9,6 @@ public class ConversationSetting
|
||||||
public int MaxRecursiveDepth { get; set; } = 3;
|
public int MaxRecursiveDepth { get; set; } = 3;
|
||||||
public bool EnableLlmCompletionLog { get; set; }
|
public bool EnableLlmCompletionLog { get; set; }
|
||||||
public bool EnableExecutionLog { get; set; }
|
public bool EnableExecutionLog { get; set; }
|
||||||
|
public bool EnableContentLog { get; set; }
|
||||||
|
public bool EnableStateLog { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
namespace BotSharp.Abstraction.Loggers.Models;
|
namespace BotSharp.Abstraction.Loggers.Models;
|
||||||
|
|
||||||
public class StreamingLogModel
|
public class ConversationContentLogModel
|
||||||
{
|
{
|
||||||
[JsonPropertyName("conversation_id")]
|
[JsonPropertyName("conversation_id")]
|
||||||
public string ConversationId { get; set; }
|
public string ConversationId { get; set; }
|
||||||
|
[JsonPropertyName("message_id")]
|
||||||
|
public string MessageId { get; set; }
|
||||||
[JsonPropertyName("name")]
|
[JsonPropertyName("name")]
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
[JsonPropertyName("role")]
|
[JsonPropertyName("role")]
|
||||||
|
|
@ -13,5 +15,5 @@ public class StreamingLogModel
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("created_at")]
|
[JsonPropertyName("created_at")]
|
||||||
public DateTime CreateTime { get; set; }
|
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace BotSharp.Abstraction.Loggers.Models;
|
||||||
|
|
||||||
|
public class ConversationStateLogModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("conversation_id")]
|
||||||
|
public string ConversationId { get; set; }
|
||||||
|
[JsonPropertyName("message_id")]
|
||||||
|
public string MessageId { get; set; }
|
||||||
|
[JsonPropertyName("states")]
|
||||||
|
public Dictionary<string, string> States { get; set; }
|
||||||
|
[JsonPropertyName("created_at")]
|
||||||
|
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BotSharp.Abstraction.Conversations.Models;
|
namespace BotSharp.Abstraction.Loggers.Models;
|
||||||
|
|
||||||
public class LlmCompletionLog
|
public class LlmCompletionLog
|
||||||
{
|
{
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
using BotSharp.Abstraction.Plugins.Models;
|
using BotSharp.Abstraction.Plugins.Models;
|
||||||
using BotSharp.Abstraction.Repositories.Filters;
|
using BotSharp.Abstraction.Repositories.Filters;
|
||||||
using BotSharp.Abstraction.Repositories.Models;
|
using BotSharp.Abstraction.Repositories.Models;
|
||||||
|
|
@ -70,6 +71,16 @@ public interface IBotSharpRepository
|
||||||
void SaveLlmCompletionLog(LlmCompletionLog log);
|
void SaveLlmCompletionLog(LlmCompletionLog log);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation Content Log
|
||||||
|
void SaveConversationContentLog(ConversationContentLogModel log);
|
||||||
|
List<ConversationContentLogModel> GetConversationContentLogs(string conversationId);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation State Log
|
||||||
|
void SaveConversationStateLog(ConversationStateLogModel log);
|
||||||
|
List<ConversationStateLogModel> GetConversationStateLogs(string conversationId);
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Statistics
|
#region Statistics
|
||||||
void IncrementConversationCount();
|
void IncrementConversationCount();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
|
using BotSharp.Abstraction.Repositories;
|
||||||
|
|
||||||
|
namespace BotSharp.Core.Conversations.Services;
|
||||||
|
|
||||||
|
public partial class ConversationService
|
||||||
|
{
|
||||||
|
public async Task<List<ConversationContentLogModel>> GetConversationContentLogs(string conversationId)
|
||||||
|
{
|
||||||
|
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||||
|
var logs = db.GetConversationContentLogs(conversationId);
|
||||||
|
return await Task.FromResult(logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<List<ConversationStateLogModel>> GetConversationStateLogs(string conversationId)
|
||||||
|
{
|
||||||
|
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||||
|
var logs = db.GetConversationStateLogs(conversationId);
|
||||||
|
return await Task.FromResult(logs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using BotSharp.Abstraction.Agents.Models;
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
using BotSharp.Abstraction.Plugins.Models;
|
using BotSharp.Abstraction.Plugins.Models;
|
||||||
using BotSharp.Abstraction.Repositories;
|
using BotSharp.Abstraction.Repositories;
|
||||||
using BotSharp.Abstraction.Repositories.Filters;
|
using BotSharp.Abstraction.Repositories.Filters;
|
||||||
|
|
@ -255,6 +256,30 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation Content Log
|
||||||
|
public void SaveConversationContentLog(ConversationContentLogModel log)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConversationContentLogModel> GetConversationContentLogs(string conversationId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation State Log
|
||||||
|
public void SaveConversationStateLog(ConversationStateLogModel log)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConversationStateLogModel> GetConversationStateLogs(string conversationId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Stats
|
#region Stats
|
||||||
public void IncrementConversationCount()
|
public void IncrementConversationCount()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
|
using Serilog;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace BotSharp.Core.Repository
|
namespace BotSharp.Core.Repository
|
||||||
|
|
@ -54,14 +56,112 @@ namespace BotSharp.Core.Repository
|
||||||
Directory.CreateDirectory(logDir);
|
Directory.CreateDirectory(logDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = GetNextLlmCompletionLogIndex(logDir, log.MessageId);
|
var index = GetNextLogIndex(logDir, log.MessageId);
|
||||||
var file = Path.Combine(logDir, $"{log.MessageId}.{index}.log");
|
var file = Path.Combine(logDir, $"{log.MessageId}.{index}.log");
|
||||||
File.WriteAllText(file, JsonSerializer.Serialize(log, _options));
|
File.WriteAllText(file, JsonSerializer.Serialize(log, _options));
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation Content Log
|
||||||
|
public void SaveConversationContentLog(ConversationContentLogModel log)
|
||||||
|
{
|
||||||
|
if (log == null) return;
|
||||||
|
|
||||||
|
log.ConversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
var convDir = FindConversationDirectory(log.ConversationId);
|
||||||
|
if (string.IsNullOrEmpty(convDir))
|
||||||
|
{
|
||||||
|
convDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, log.ConversationId);
|
||||||
|
Directory.CreateDirectory(convDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logDir = Path.Combine(convDir, "content_log");
|
||||||
|
if (!Directory.Exists(logDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(logDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = GetNextLogIndex(logDir, log.MessageId);
|
||||||
|
var file = Path.Combine(logDir, $"{log.MessageId}.{index}.log");
|
||||||
|
File.WriteAllText(file, JsonSerializer.Serialize(log, _options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConversationContentLogModel> GetConversationContentLogs(string conversationId)
|
||||||
|
{
|
||||||
|
var logs = new List<ConversationContentLogModel>();
|
||||||
|
if (string.IsNullOrEmpty(conversationId)) return logs;
|
||||||
|
|
||||||
|
var convDir = FindConversationDirectory(conversationId);
|
||||||
|
if (string.IsNullOrEmpty(convDir)) return logs;
|
||||||
|
|
||||||
|
var logDir = Path.Combine(convDir, "content_log");
|
||||||
|
if (!Directory.Exists(logDir)) return logs;
|
||||||
|
|
||||||
|
foreach (var file in Directory.GetFiles(logDir))
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText(file);
|
||||||
|
var log = JsonSerializer.Deserialize<ConversationContentLogModel>(text);
|
||||||
|
if (log == null) continue;
|
||||||
|
|
||||||
|
logs.Add(log);
|
||||||
|
}
|
||||||
|
return logs.OrderBy(x => x.CreateTime).ToList();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation State Log
|
||||||
|
public void SaveConversationStateLog(ConversationStateLogModel log)
|
||||||
|
{
|
||||||
|
if (log == null) return;
|
||||||
|
|
||||||
|
log.ConversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
var convDir = FindConversationDirectory(log.ConversationId);
|
||||||
|
if (string.IsNullOrEmpty(convDir))
|
||||||
|
{
|
||||||
|
convDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, log.ConversationId);
|
||||||
|
Directory.CreateDirectory(convDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logDir = Path.Combine(convDir, "state_log");
|
||||||
|
if (!Directory.Exists(logDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(logDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = GetNextLogIndex(logDir, log.MessageId);
|
||||||
|
var file = Path.Combine(logDir, $"{log.MessageId}.{index}.log");
|
||||||
|
File.WriteAllText(file, JsonSerializer.Serialize(log, _options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConversationStateLogModel> GetConversationStateLogs(string conversationId)
|
||||||
|
{
|
||||||
|
var logs = new List<ConversationStateLogModel>();
|
||||||
|
if (string.IsNullOrEmpty(conversationId)) return logs;
|
||||||
|
|
||||||
|
var convDir = FindConversationDirectory(conversationId);
|
||||||
|
if (string.IsNullOrEmpty(convDir)) return logs;
|
||||||
|
|
||||||
|
var logDir = Path.Combine(convDir, "state_log");
|
||||||
|
if (!Directory.Exists(logDir)) return logs;
|
||||||
|
|
||||||
|
foreach (var file in Directory.GetFiles(logDir))
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText(file);
|
||||||
|
var log = JsonSerializer.Deserialize<ConversationStateLogModel>(text);
|
||||||
|
if (log == null) continue;
|
||||||
|
|
||||||
|
logs.Add(log);
|
||||||
|
}
|
||||||
|
return logs.OrderBy(x => x.CreateTime).ToList();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Private methods
|
#region Private methods
|
||||||
private int GetNextLlmCompletionLogIndex(string logDir, string id)
|
private int GetNextLogIndex(string logDir, string id)
|
||||||
{
|
{
|
||||||
var files = Directory.GetFiles(logDir);
|
var files = Directory.GetFiles(logDir);
|
||||||
if (files.IsNullOrEmpty())
|
if (files.IsNullOrEmpty())
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
|
|
||||||
public class CommonContentGeneratingHook : IContentGeneratingHook
|
public class CommonContentGeneratingHook : IContentGeneratingHook
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using SharpCompress.Compressors.Xz;
|
using SharpCompress.Compressors.Xz;
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -34,4 +35,18 @@ public class LoggerController : ControllerBase
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("/logger/conversation/{conversationId}/content-log")]
|
||||||
|
public async Task<List<ConversationContentLogModel>> GetConversationContentLogs([FromRoute] string conversationId)
|
||||||
|
{
|
||||||
|
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||||
|
return await conversationService.GetConversationContentLogs(conversationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/logger/conversation/{conversationId}/state-log")]
|
||||||
|
public async Task<List<ConversationStateLogModel>> GetConversationStateLogs([FromRoute] string conversationId)
|
||||||
|
{
|
||||||
|
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||||
|
return await conversationService.GetConversationStateLogs(conversationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
using BotSharp.Abstraction.Messaging;
|
using BotSharp.Abstraction.Messaging;
|
||||||
using BotSharp.Abstraction.Messaging.JsonConverters;
|
using BotSharp.Abstraction.Messaging.JsonConverters;
|
||||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||||
|
using BotSharp.Abstraction.Repositories;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace BotSharp.Plugin.ChatHub.Hooks;
|
namespace BotSharp.Plugin.ChatHub.Hooks;
|
||||||
|
|
@ -117,20 +119,28 @@ public class ChatHubConversationHook : ConversationHookBase
|
||||||
}
|
}
|
||||||
}, _serializerOptions);
|
}, _serializerOptions);
|
||||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", json);
|
await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", json);
|
||||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversateStatesGenerated", BuildConversationStates(conv.ConversationId, state.GetStates()));
|
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversateStatesGenerated", BuildConversationStates(conv.ConversationId, state.GetStates(), message));
|
||||||
|
|
||||||
await base.OnResponseGenerated(message);
|
await base.OnResponseGenerated(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildConversationStates(string conversationId, Dictionary<string, string> states)
|
private string BuildConversationStates(string conversationId, Dictionary<string, string> states, RoleDialogModel message)
|
||||||
{
|
{
|
||||||
var model = new ConversationStateLogModel
|
var log = new ConversationStateLogModel
|
||||||
{
|
{
|
||||||
ConvsersationId = conversationId,
|
ConversationId = conversationId,
|
||||||
States = JsonSerializer.Serialize(states, _serializerOptions),
|
MessageId = message.MessageId,
|
||||||
|
States = states,
|
||||||
CreateTime = DateTime.UtcNow
|
CreateTime = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
return JsonSerializer.Serialize(model, _serializerOptions);
|
var convSettings = _services.GetRequiredService<ConversationSetting>();
|
||||||
|
if (convSettings.EnableStateLog)
|
||||||
|
{
|
||||||
|
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||||
|
db.SaveConversationStateLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerializer.Serialize(log, _serializerOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using BotSharp.Abstraction.Agents.Models;
|
using BotSharp.Abstraction.Agents.Models;
|
||||||
using BotSharp.Abstraction.Loggers;
|
using BotSharp.Abstraction.Loggers;
|
||||||
using BotSharp.Abstraction.Loggers.Models;
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
|
using BotSharp.Abstraction.Repositories;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace BotSharp.Plugin.ChatHub.Hooks;
|
namespace BotSharp.Plugin.ChatHub.Hooks;
|
||||||
|
|
@ -37,7 +38,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
||||||
{
|
{
|
||||||
var conversationId = _state.GetConversationId();
|
var conversationId = _state.GetConversationId();
|
||||||
var log = $"MessageId: {message.MessageId} ==>\r\n{message.Role}: {message.Content}";
|
var log = $"MessageId: {message.MessageId} ==>\r\n{message.Role}: {message.Content}";
|
||||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, _user.UserName, message.Role, log));
|
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, _user.UserName, log, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
|
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
|
||||||
|
|
@ -64,25 +65,34 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
||||||
var conversationId = _state.GetConversationId();
|
var conversationId = _state.GetConversationId();
|
||||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||||
|
|
||||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, message.Role, tokenStats.Prompt));
|
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, tokenStats.Prompt, message));
|
||||||
|
|
||||||
var log = message.Role == AgentRole.Function ?
|
var log = message.Role == AgentRole.Function ?
|
||||||
$"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs})" :
|
$"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs})" :
|
||||||
$"[{agent?.Name}]: {message.Content}";
|
$"[{agent?.Name}]: {message.Content}";
|
||||||
log += $"\r\n<== MessageId: {message.MessageId}";
|
log += $"\r\n<== MessageId: {message.MessageId}";
|
||||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, message.Role, log));
|
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, log, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildLog(string conversationId, string? name, string role, string content)
|
private string BuildLog(string conversationId, string? name, string content, RoleDialogModel message)
|
||||||
{
|
{
|
||||||
var log = new StreamingLogModel
|
var log = new ConversationContentLogModel
|
||||||
{
|
{
|
||||||
ConversationId = conversationId,
|
ConversationId = conversationId,
|
||||||
|
MessageId = message.MessageId,
|
||||||
Name = name,
|
Name = name,
|
||||||
Role = role,
|
Role = message.Role,
|
||||||
Content = content,
|
Content = content,
|
||||||
CreateTime = DateTime.UtcNow
|
CreateTime = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var convSettings = _services.GetRequiredService<ConversationSetting>();
|
||||||
|
if (convSettings.EnableContentLog)
|
||||||
|
{
|
||||||
|
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||||
|
db.SaveConversationContentLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
return JsonSerializer.Serialize(log, _serializerOptions);
|
return JsonSerializer.Serialize(log, _serializerOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace BotSharp.Plugin.MongoStorage.Collections;
|
||||||
|
|
||||||
|
public class ConversationContentLogDocument : MongoBase
|
||||||
|
{
|
||||||
|
public string ConversationId { get; set; }
|
||||||
|
public string MessageId { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string Role { get; set; }
|
||||||
|
public string Content { get; set; }
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace BotSharp.Plugin.MongoStorage.Collections;
|
||||||
|
|
||||||
|
public class ConversationStateLogDocument : MongoBase
|
||||||
|
{
|
||||||
|
public string ConversationId { get; set; }
|
||||||
|
public string MessageId { get; set; }
|
||||||
|
public Dictionary<string, string> States { get; set; }
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ public class MongoDbContext
|
||||||
_mongoClient = new MongoClient(mongoDbConnectionString);
|
_mongoClient = new MongoClient(mongoDbConnectionString);
|
||||||
_mongoDbDatabaseName = GetDatabaseName(mongoDbConnectionString);
|
_mongoDbDatabaseName = GetDatabaseName(mongoDbConnectionString);
|
||||||
_collectionPrefix = dbSettings.TablePrefix.IfNullOrEmptyAs("BotSharp");
|
_collectionPrefix = dbSettings.TablePrefix.IfNullOrEmptyAs("BotSharp");
|
||||||
//CreateIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDatabaseName(string mongoDbConnectionString)
|
private string GetDatabaseName(string mongoDbConnectionString)
|
||||||
|
|
@ -29,6 +28,7 @@ public class MongoDbContext
|
||||||
|
|
||||||
private IMongoDatabase Database { get { return _mongoClient.GetDatabase(_mongoDbDatabaseName); } }
|
private IMongoDatabase Database { get { return _mongoClient.GetDatabase(_mongoDbDatabaseName); } }
|
||||||
|
|
||||||
|
#region Indexes
|
||||||
private IMongoCollection<ConversationDocument> CreateConversationIndex()
|
private IMongoCollection<ConversationDocument> CreateConversationIndex()
|
||||||
{
|
{
|
||||||
var collection = Database.GetCollection<ConversationDocument>($"{_collectionPrefix}_Conversations");
|
var collection = Database.GetCollection<ConversationDocument>($"{_collectionPrefix}_Conversations");
|
||||||
|
|
@ -39,7 +39,6 @@ public class MongoDbContext
|
||||||
var indexDef = Builders<ConversationDocument>.IndexKeys.Descending(x => x.CreatedTime);
|
var indexDef = Builders<ConversationDocument>.IndexKeys.Descending(x => x.CreatedTime);
|
||||||
collection.Indexes.CreateOne(new CreateIndexModel<ConversationDocument>(indexDef));
|
collection.Indexes.CreateOne(new CreateIndexModel<ConversationDocument>(indexDef));
|
||||||
}
|
}
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,28 +52,44 @@ public class MongoDbContext
|
||||||
var indexDef = Builders<AgentTaskDocument>.IndexKeys.Descending(x => x.CreatedTime);
|
var indexDef = Builders<AgentTaskDocument>.IndexKeys.Descending(x => x.CreatedTime);
|
||||||
collection.Indexes.CreateOne(new CreateIndexModel<AgentTaskDocument>(indexDef));
|
collection.Indexes.CreateOne(new CreateIndexModel<AgentTaskDocument>(indexDef));
|
||||||
}
|
}
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IMongoCollection<ConversationContentLogDocument> CreateContentLogIndex()
|
||||||
|
{
|
||||||
|
var collection = Database.GetCollection<ConversationContentLogDocument>($"{_collectionPrefix}_ConversationContentLogs");
|
||||||
|
var indexes = collection.Indexes.List().ToList();
|
||||||
|
var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreateTime"));
|
||||||
|
if (createTimeIndex == null)
|
||||||
|
{
|
||||||
|
var indexDef = Builders<ConversationContentLogDocument>.IndexKeys.Ascending(x => x.CreateTime);
|
||||||
|
collection.Indexes.CreateOne(new CreateIndexModel<ConversationContentLogDocument>(indexDef));
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IMongoCollection<ConversationStateLogDocument> CreateStateLogIndex()
|
||||||
|
{
|
||||||
|
var collection = Database.GetCollection<ConversationStateLogDocument>($"{_collectionPrefix}_ConversationStateLogs");
|
||||||
|
var indexes = collection.Indexes.List().ToList();
|
||||||
|
var createTimeIndex = indexes.FirstOrDefault(x => x.GetElement("name").ToString().StartsWith("CreateTime"));
|
||||||
|
if (createTimeIndex == null)
|
||||||
|
{
|
||||||
|
var indexDef = Builders<ConversationStateLogDocument>.IndexKeys.Ascending(x => x.CreateTime);
|
||||||
|
collection.Indexes.CreateOne(new CreateIndexModel<ConversationStateLogDocument>(indexDef));
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
public IMongoCollection<AgentDocument> Agents
|
public IMongoCollection<AgentDocument> Agents
|
||||||
=> Database.GetCollection<AgentDocument>($"{_collectionPrefix}_Agents");
|
=> Database.GetCollection<AgentDocument>($"{_collectionPrefix}_Agents");
|
||||||
|
|
||||||
public IMongoCollection<AgentTaskDocument> AgentTasks
|
public IMongoCollection<AgentTaskDocument> AgentTasks
|
||||||
{
|
=> CreateAgentTaskIndex();
|
||||||
get
|
|
||||||
{
|
|
||||||
return CreateAgentTaskIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IMongoCollection<ConversationDocument> Conversations
|
public IMongoCollection<ConversationDocument> Conversations
|
||||||
{
|
=> CreateConversationIndex();
|
||||||
get
|
|
||||||
{
|
|
||||||
return CreateConversationIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IMongoCollection<ConversationDialogDocument> ConversationDialogs
|
public IMongoCollection<ConversationDialogDocument> ConversationDialogs
|
||||||
=> Database.GetCollection<ConversationDialogDocument>($"{_collectionPrefix}_ConversationDialogs");
|
=> Database.GetCollection<ConversationDialogDocument>($"{_collectionPrefix}_ConversationDialogs");
|
||||||
|
|
@ -85,15 +100,21 @@ public class MongoDbContext
|
||||||
public IMongoCollection<ExecutionLogDocument> ExectionLogs
|
public IMongoCollection<ExecutionLogDocument> ExectionLogs
|
||||||
=> Database.GetCollection<ExecutionLogDocument>($"{_collectionPrefix}_ExecutionLogs");
|
=> Database.GetCollection<ExecutionLogDocument>($"{_collectionPrefix}_ExecutionLogs");
|
||||||
|
|
||||||
|
public IMongoCollection<LlmCompletionLogDocument> LlmCompletionLogs
|
||||||
|
=> Database.GetCollection<LlmCompletionLogDocument>($"{_collectionPrefix}_LlmCompletionLogs");
|
||||||
|
|
||||||
|
public IMongoCollection<ConversationContentLogDocument> ContentLogs
|
||||||
|
=> CreateContentLogIndex();
|
||||||
|
|
||||||
|
public IMongoCollection<ConversationStateLogDocument> StateLogs
|
||||||
|
=> CreateStateLogIndex();
|
||||||
|
|
||||||
public IMongoCollection<UserDocument> Users
|
public IMongoCollection<UserDocument> Users
|
||||||
=> Database.GetCollection<UserDocument>($"{_collectionPrefix}_Users");
|
=> Database.GetCollection<UserDocument>($"{_collectionPrefix}_Users");
|
||||||
|
|
||||||
public IMongoCollection<UserAgentDocument> UserAgents
|
public IMongoCollection<UserAgentDocument> UserAgents
|
||||||
=> Database.GetCollection<UserAgentDocument>($"{_collectionPrefix}_UserAgents");
|
=> Database.GetCollection<UserAgentDocument>($"{_collectionPrefix}_UserAgents");
|
||||||
|
|
||||||
public IMongoCollection<LlmCompletionLogDocument> LlmCompletionLogs
|
|
||||||
=> Database.GetCollection<LlmCompletionLogDocument>($"{_collectionPrefix}_Llm_Completion_Logs");
|
|
||||||
|
|
||||||
public IMongoCollection<PluginDocument> Plugins
|
public IMongoCollection<PluginDocument> Plugins
|
||||||
=> Database.GetCollection<PluginDocument>($"{_collectionPrefix}_Plugins");
|
=> Database.GetCollection<PluginDocument>($"{_collectionPrefix}_Plugins");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,20 @@ public partial class MongoRepository
|
||||||
var filterSates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
var filterSates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||||
var filterExeLog = Builders<ExecutionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
var filterExeLog = Builders<ExecutionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||||
var filterPromptLog = Builders<LlmCompletionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
var filterPromptLog = Builders<LlmCompletionLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||||
|
var filterContentLog = Builders<ConversationContentLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||||
|
var filterStateLog = Builders<ConversationStateLogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||||
|
|
||||||
var exeLogDeleted = _dc.ExectionLogs.DeleteMany(filterExeLog);
|
var exeLogDeleted = _dc.ExectionLogs.DeleteMany(filterExeLog);
|
||||||
var promptLogDeleted = _dc.LlmCompletionLogs.DeleteMany(filterPromptLog);
|
var promptLogDeleted = _dc.LlmCompletionLogs.DeleteMany(filterPromptLog);
|
||||||
|
var contentLogDeleted = _dc.ContentLogs.DeleteMany(filterContentLog);
|
||||||
|
var stateLogDeleted = _dc.StateLogs.DeleteMany(filterStateLog);
|
||||||
var statesDeleted = _dc.ConversationStates.DeleteMany(filterSates);
|
var statesDeleted = _dc.ConversationStates.DeleteMany(filterSates);
|
||||||
var dialogDeleted = _dc.ConversationDialogs.DeleteMany(filterDialog);
|
var dialogDeleted = _dc.ConversationDialogs.DeleteMany(filterDialog);
|
||||||
var convDeleted = _dc.Conversations.DeleteMany(filterConv);
|
var convDeleted = _dc.Conversations.DeleteMany(filterConv);
|
||||||
|
|
||||||
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0
|
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0
|
||||||
|| exeLogDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0;
|
|| exeLogDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0
|
||||||
|
|| contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<DialogElement> GetConversationDialogs(string conversationId)
|
public List<DialogElement> GetConversationDialogs(string conversationId)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using BotSharp.Abstraction.Conversations.Models;
|
using BotSharp.Abstraction.Loggers.Models;
|
||||||
using BotSharp.Plugin.MongoStorage.Collections;
|
using BotSharp.Plugin.MongoStorage.Collections;
|
||||||
using BotSharp.Plugin.MongoStorage.Models;
|
using BotSharp.Plugin.MongoStorage.Models;
|
||||||
|
|
||||||
|
|
@ -58,4 +58,82 @@ public partial class MongoRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation Content Log
|
||||||
|
public void SaveConversationContentLog(ConversationContentLogModel log)
|
||||||
|
{
|
||||||
|
if (log == null) return;
|
||||||
|
|
||||||
|
var conversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
var messageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
var logDoc = new ConversationContentLogDocument
|
||||||
|
{
|
||||||
|
ConversationId = conversationId,
|
||||||
|
MessageId = messageId,
|
||||||
|
Name = log.Name,
|
||||||
|
Role = log.Role,
|
||||||
|
Content = log.Content,
|
||||||
|
CreateTime = log.CreateTime
|
||||||
|
};
|
||||||
|
|
||||||
|
_dc.ContentLogs.InsertOne(logDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConversationContentLogModel> GetConversationContentLogs(string conversationId)
|
||||||
|
{
|
||||||
|
var logs = _dc.ContentLogs
|
||||||
|
.AsQueryable()
|
||||||
|
.Where(x => x.ConversationId == conversationId)
|
||||||
|
.Select(x => new ConversationContentLogModel
|
||||||
|
{
|
||||||
|
ConversationId = x.ConversationId,
|
||||||
|
MessageId = x.MessageId,
|
||||||
|
Name = x.Name,
|
||||||
|
Role = x.Role,
|
||||||
|
Content = x.Content,
|
||||||
|
CreateTime = x.CreateTime
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.CreateTime)
|
||||||
|
.ToList();
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversation State Log
|
||||||
|
public void SaveConversationStateLog(ConversationStateLogModel log)
|
||||||
|
{
|
||||||
|
if (log == null) return;
|
||||||
|
|
||||||
|
var conversationId = log.ConversationId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
var messageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
var logDoc = new ConversationStateLogDocument
|
||||||
|
{
|
||||||
|
ConversationId = conversationId,
|
||||||
|
MessageId = messageId,
|
||||||
|
States = log.States,
|
||||||
|
CreateTime = log.CreateTime
|
||||||
|
};
|
||||||
|
|
||||||
|
_dc.StateLogs.InsertOne(logDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConversationStateLogModel> GetConversationStateLogs(string conversationId)
|
||||||
|
{
|
||||||
|
var logs = _dc.StateLogs
|
||||||
|
.AsQueryable()
|
||||||
|
.Where(x => x.ConversationId == conversationId)
|
||||||
|
.Select(x => new ConversationStateLogModel
|
||||||
|
{
|
||||||
|
ConversationId = x.ConversationId,
|
||||||
|
MessageId = x.MessageId,
|
||||||
|
States = x.States,
|
||||||
|
CreateTime = x.CreateTime
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.CreateTime)
|
||||||
|
.ToList();
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,9 @@
|
||||||
"DataDir": "conversations",
|
"DataDir": "conversations",
|
||||||
"ShowVerboseLog": false,
|
"ShowVerboseLog": false,
|
||||||
"EnableLlmCompletionLog": false,
|
"EnableLlmCompletionLog": false,
|
||||||
"EnableExecutionLog": true
|
"EnableExecutionLog": true,
|
||||||
|
"EnableContentLog": true,
|
||||||
|
"EnableStateLog": true
|
||||||
},
|
},
|
||||||
|
|
||||||
"Statistics": {
|
"Statistics": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue