refine state search

This commit is contained in:
Jicheng Lu 2025-02-11 14:27:53 -06:00
parent 9d219dc2f9
commit f423a40f53
10 changed files with 122 additions and 11 deletions

View file

@ -63,4 +63,13 @@ public interface IConversationService
bool IsConversationMode();
void SaveStates();
/// <summary>
/// Get conversation keys for searching
/// </summary>
/// <param name="query">search query</param>
/// <param name="convLimit">conversation limit</param>
/// <param name="preLoad">if pre-loading, then keys are not filter by the search query</param>
/// <returns></returns>
Task<List<string>> GetConversationSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false);
}

View file

@ -146,7 +146,9 @@ public interface IBotSharpRepository : IHaveServiceProvider
=> throw new NotImplementedException();
List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable<string> excludeAgentIds)
=> throw new NotImplementedException();
IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
=> throw new NotImplementedException();
List<string> GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100)
=> throw new NotImplementedException();
#endregion

View file

@ -221,4 +221,18 @@ public partial class ConversationService : IConversationService
{
_state.Save();
}
public async Task<List<string>> GetConversationSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false)
{
var keys = new List<string>();
if (!preLoad && string.IsNullOrWhiteSpace(query))
{
return keys;
}
var db = _services.GetRequiredService<IBotSharpRepository>();
keys = db.GetConversationSearchKeys(convlimit: convlimit);
keys = preLoad ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList();
return keys.Take(keyLimit).ToList();
}
}

View file

@ -131,7 +131,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public void UpdateConversationStatus(string conversationId, string status)
=> throw new NotImplementedException();
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
public List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
=> throw new NotImplementedException();
#endregion

View file

@ -547,7 +547,7 @@ namespace BotSharp.Core.Repository
}
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
public List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
{
var deletedMessageIds = new List<string>();
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
@ -603,6 +603,46 @@ namespace BotSharp.Core.Repository
}
public List<string> GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100)
{
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
if (!Directory.Exists(dir)) return [];
var count = 0;
var keys = new List<string>();
foreach (var d in Directory.GetDirectories(dir))
{
var convFile = Path.Combine(d, CONVERSATION_FILE);
var stateFile = Path.Combine(d, STATE_FILE);
if (!File.Exists(convFile) || !File.Exists(stateFile))
{
continue;
}
var convJson = File.ReadAllText(convFile);
var stateJson = File.ReadAllText(stateFile);
var conv = JsonSerializer.Deserialize<Conversation>(convJson, _options);
var states = JsonSerializer.Deserialize<List<StateKeyValue>>(stateJson, _options);
if (conv == null || conv.DialogCount < messageLimit)
{
continue;
}
var stateKeys = states?.Select(x => x.Key)?.Distinct()?.ToList() ?? [];
keys.AddRange(stateKeys);
count++;
if (count > convlimit)
{
break;
}
}
return keys.Distinct().ToList();
}
#region Private methods
private string? FindConversationDirectory(string conversationId)
{

View file

@ -553,6 +553,16 @@ public class ConversationController : ControllerBase
}
#endregion
#region Search state keys
[HttpGet("/conversation/state/keys")]
public async Task<List<string>> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] bool preLoad = false)
{
var convService = _services.GetRequiredService<IConversationService>();
var keys = await convService.GetConversationSearhKeys(query, keyLimit: keyLimit, preLoad: preLoad);
return keys;
}
#endregion
#region Private methods
private void SetStates(IConversationService conv, NewMessageModel input)
{

View file

@ -4,5 +4,6 @@ public class ConversationDialogDocument : MongoBase
{
public string ConversationId { get; set; }
public string AgentId { get; set; }
public DateTime UpdatedTime { get; set; }
public List<DialogMongoElement> Dialogs { get; set; }
}

View file

@ -4,6 +4,7 @@ public class ConversationStateDocument : MongoBase
{
public string ConversationId { get; set; }
public string AgentId { get; set; }
public DateTime UpdatedTime { get; set; }
public List<StateMongoElement> States { get; set; } = new List<StateMongoElement>();
public List<BreakpointMongoElement> Breakpoints { get; set; } = new List<BreakpointMongoElement>();
}

View file

@ -30,7 +30,8 @@ public partial class MongoRepository
Id = Guid.NewGuid().ToString(),
ConversationId = convDoc.Id,
AgentId = conversation.AgentId,
Dialogs = new List<DialogMongoElement>()
Dialogs = [],
UpdatedTime = utcNow
};
var stateDoc = new ConversationStateDocument
@ -38,8 +39,9 @@ public partial class MongoRepository
Id = Guid.NewGuid().ToString(),
ConversationId = convDoc.Id,
AgentId = conversation.AgentId,
States = new List<StateMongoElement>(),
Breakpoints = new List<BreakpointMongoElement>()
States = [],
Breakpoints = [],
UpdatedTime = utcNow
};
_dc.Conversations.InsertOne(convDoc);
@ -97,7 +99,8 @@ public partial class MongoRepository
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var dialogElements = dialogs.Select(x => DialogMongoElement.ToMongoElement(x)).ToList();
var updateDialog = Builders<ConversationDialogDocument>.Update.PushEach(x => x.Dialogs, dialogElements);
var updateDialog = Builders<ConversationDialogDocument>.Update.PushEach(x => x.Dialogs, dialogElements)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow)
.Inc(x => x.DialogCount, dialogs.Count);
@ -190,7 +193,8 @@ public partial class MongoRepository
found.SecondaryRichContent = request.Message.RichContent;
}
var update = Builders<ConversationDialogDocument>.Update.Set(x => x.Dialogs, dialogs);
var update = Builders<ConversationDialogDocument>.Update.Set(x => x.Dialogs, dialogs)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationDialogs.UpdateOne(filter, update);
return true;
}
@ -208,7 +212,8 @@ public partial class MongoRepository
Reason = breakpoint.Reason
};
var filterState = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var updateState = Builders<ConversationStateDocument>.Update.Push(x => x.Breakpoints, newBreakpoint);
var updateState = Builders<ConversationStateDocument>.Update.Push(x => x.Breakpoints, newBreakpoint)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationStates.UpdateOne(filterState, updateState);
}
@ -258,7 +263,8 @@ public partial class MongoRepository
var filterStates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var saveStates = states.Select(x => StateMongoElement.ToMongoElement(x)).ToList();
var updateStates = Builders<ConversationStateDocument>.Update.Set(x => x.States, saveStates);
var updateStates = Builders<ConversationStateDocument>.Update.Set(x => x.States, saveStates)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationStates.UpdateOne(filterStates, updateStates);
}
@ -500,7 +506,7 @@ public partial class MongoRepository
return conversationIds.Take(batchSize).ToList();
}
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
public List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
{
var deletedMessageIds = new List<string>();
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
@ -566,11 +572,13 @@ public partial class MongoRepository
}
// Update
foundStates.UpdatedTime = DateTime.UtcNow;
_dc.ConversationStates.ReplaceOne(stateFilter, foundStates);
}
// Save dialogs
foundDialog.Dialogs = truncatedDialogs;
foundDialog.UpdatedTime = DateTime.UtcNow;
_dc.ConversationDialogs.ReplaceOne(dialogFilter, foundDialog);
// Update conversation
@ -603,6 +611,27 @@ public partial class MongoRepository
return deletedMessageIds;
}
public List<string> GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100)
{
var convFilter = Builders<ConversationDocument>.Filter.Gte(x => x.DialogCount, messageLimit);
var conversations = _dc.Conversations.Find(convFilter)
.SortByDescending(x => x.UpdatedTime)
.Limit(convlimit)
.ToList();
if (conversations.IsNullOrEmpty()) return [];
var convIds = conversations.Select(x => x.Id).ToList();
var stateFilter = Builders<ConversationStateDocument>.Filter.In(x => x.ConversationId, convIds);
var states = _dc.ConversationStates.Find(stateFilter).ToList();
var keys = states.SelectMany(x => x.States.Select(x => x.Key)).Distinct().ToList();
return keys;
}
private string ConvertSnakeCaseToPascalCase(string snakeCase)
{
string[] words = snakeCase.Split('_');

View file

@ -429,6 +429,11 @@ public partial class MongoRepository
return true;
}
public Dashboard? GetDashboard(string userId = null)
{
return null;
}
public void AddDashboardConversation(string userId, string conversationId)
{
var user = _dc.Users.AsQueryable()