Skip to content

refine state key search #928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public interface IAgentService
Task<Agent> CreateAgent(Agent agent);
Task<string> RefreshAgents();
Task<PagedItems<Agent>> GetAgents(AgentFilter filter);
Task<List<IdName>> GetAgentOptions();
Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null);

/// <summary>
/// Load agent configurations and trigger hooks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public static class MessageTypeName
public const string Notification = "notification";
public const string FunctionCall = "function";
public const string Audio = "audio";
public const string Error = "error";
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,7 @@ Task<bool> SendMessage(string agentId,

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>> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false);
Task<List<string>> GetConversationStateSearhKeys(ConversationStateKeysFilter filter);

Task<bool> MigrateLatestStates(int batchSize = 100, int errorLimit = 10);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Repositories.Filters;

namespace BotSharp.Abstraction.Loggers.Services;

Expand All @@ -12,5 +12,6 @@ public interface ILoggerService

#region Instruction
Task<PagedItems<InstructionLogModel>> GetInstructionLogs(InstructLogFilter filter);
Task<List<string>> GetInstructionLogSearchKeys(InstructLogKeysFilter filter);
#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public class ConversationFilter
/// <summary>
/// Check whether each key in the list is in the conversation states and its value equals to target value if not empty
/// </summary>
public IEnumerable<KeyValue>? States { get; set; } = [];
public List<KeyValue>? States { get; set; }

public IEnumerable<string>? Tags { get; set; } = [];
public List<string>? Tags { get; set; }

public static ConversationFilter Empty()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace BotSharp.Abstraction.Repositories.Filters;

public class ConversationStateKeysFilter
{
public string? Query { get; set; }
public int KeyLimit { get; set; } = 10;
public int ConvLimit { get; set; } = 100;
public bool PreLoad { get; set; }
public List<string>? AgentIds { get; set; }
public List<string>? UserIds { get; set; }

public ConversationStateKeysFilter()
{

}

public static ConversationStateKeysFilter Empty()
{
return new();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace BotSharp.Abstraction.Instructs.Models;
namespace BotSharp.Abstraction.Repositories.Filters;

public class InstructLogFilter : Pagination
{
Expand All @@ -11,6 +11,6 @@ public class InstructLogFilter : Pagination

public static InstructLogFilter Empty()
{
return new InstructLogFilter();
return new();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace BotSharp.Abstraction.Repositories.Filters;

public class InstructLogKeysFilter
{
public string? Query { get; set; }
public int KeyLimit { get; set; } = 10;
public int LogLimit { get; set; } = 100;
public bool PreLoad { get; set; }
public List<string>? AgentIds { get; set; }
public List<string>? UserIds { get; set; }

public InstructLogKeysFilter()
{

}

public static InstructLogKeysFilter Empty()
{
return new();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories.Filters;
Expand Down Expand Up @@ -44,7 +43,7 @@
List<User> GetUsersByAffiliateId(string affiliateId) => throw new NotImplementedException();
User? GetUserByUserName(string userName) => throw new NotImplementedException();
void UpdateUserName(string userId, string userName) => throw new NotImplementedException();
Dashboard? GetDashboard(string id = null) => throw new NotImplementedException();

Check warning on line 46 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 46 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 46 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 46 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 46 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 46 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Cannot convert null literal to non-nullable reference type.
void CreateUser(User user) => throw new NotImplementedException();
void UpdateExistUser(string userId, User user) => throw new NotImplementedException();
void UpdateUserVerified(string userId) => throw new NotImplementedException();
Expand Down Expand Up @@ -149,7 +148,7 @@
=> throw new NotImplementedException();
List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
=> throw new NotImplementedException();
List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100)
List<string> GetConversationStateSearchKeys(ConversationStateKeysFilter filter)
=> throw new NotImplementedException();
List<string> GetConversationsToMigrate(int batchSize = 100)
=> throw new NotImplementedException();
Expand Down Expand Up @@ -182,6 +181,9 @@

PagedItems<InstructionLogModel> GetInstructionLogs(InstructLogFilter filter)
=> throw new NotImplementedException();

List<string> GetInstructionLogSearchKeys(InstructLogKeysFilter filter)
=> throw new NotImplementedException();
#endregion

#region Statistics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface IUserService
Task<PagedItems<User>> GetUsers(UserFilter filter);
Task<List<User>> SearchLoginUsers(User filter);
Task<User?> GetUserDetails(string userId, bool includeAgent = false);
Task<bool> IsAdminUser(string userId);
Task<(bool, User?)> IsAdminUser(string userId);
Task<UserAuthorization> GetUserAuthorizations(IEnumerable<string>? agentIds = null);
Task<bool> UpdateUser(User user, bool isUpdateUserAgents = false);
Task<User> CreateUser(User user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ public async Task<PagedItems<Agent>> GetAgents(AgentFilter filter)
#if !DEBUG
[SharpCache(10, perInstanceCache: true)]
#endif
public async Task<List<IdName>> GetAgentOptions()
public async Task<List<IdName>> GetAgentOptions(List<string>? agentIds)
{
var agents = _db.GetAgents(AgentFilter.Empty());
var agents = _db.GetAgents(new AgentFilter
{
AgentIds = !agentIds.IsNullOrEmpty() ? agentIds : null
});
return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? [];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async Task<string> RefreshAgents()

var userIdentity = _services.GetRequiredService<IUserIdentity>();
var userService = _services.GetRequiredService<IUserService>();
var isValid = await userService.IsAdminUser(userIdentity.Id);
var (isValid, _) = await userService.IsAdminUser(userIdentity.Id);
if (!isValid)
{
return "Unauthorized user.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,26 @@ public void SaveStates()
_state.Save();
}

public async Task<List<string>> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false)
public async Task<List<string>> GetConversationStateSearhKeys(ConversationStateKeysFilter filter)
{
if (filter == null)
{
filter = ConversationStateKeysFilter.Empty();
}

var keys = new List<string>();
if (!preload && string.IsNullOrWhiteSpace(query))
if (!filter.PreLoad && string.IsNullOrWhiteSpace(filter.Query))
{
return keys;
}

var userService = _services.GetRequiredService<IUserService>();
var db = _services.GetRequiredService<IBotSharpRepository>();
keys = db.GetConversationStateSearchKeys(convUpperLimit: convLimit);
keys = preload ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList();
return keys.OrderBy(x => x).Take(keyLimit).ToList();

var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : [];
keys = db.GetConversationStateSearchKeys(filter);
keys = filter.PreLoad ? keys : keys.Where(x => x.Contains(filter.Query ?? string.Empty, StringComparison.OrdinalIgnoreCase)).ToList();
return keys.OrderBy(x => x).Take(filter.KeyLimit).ToList();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;

namespace BotSharp.Core.Loggers.Services;
Expand All @@ -15,11 +13,10 @@ public async Task<PagedItems<InstructionLogModel>> GetInstructionLogs(InstructLo
}

var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
var isAdmin = UserConstant.AdminRoles.Contains(user?.Role);
var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
if (!isAdmin && user?.Id == null) return new();

filter.UserIds = isAdmin ? [] : user?.Id != null ? [user.Id] : [];
filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : null;

var agents = new List<Agent>();
var users = new List<User>();
Expand Down Expand Up @@ -61,4 +58,27 @@ public async Task<PagedItems<InstructionLogModel>> GetInstructionLogs(InstructLo
Count = logs.Count
};
}

public async Task<List<string>> GetInstructionLogSearchKeys(InstructLogKeysFilter filter)
{
if (filter == null)
{
filter = InstructLogKeysFilter.Empty();
}

var keys = new List<string>();
if (!filter.PreLoad && string.IsNullOrWhiteSpace(filter.Query))
{
return keys;
}

var userService = _services.GetRequiredService<IUserService>();
var db = _services.GetRequiredService<IBotSharpRepository>();

var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : null;
keys = db.GetInstructionLogSearchKeys(filter);
keys = filter.PreLoad ? keys : keys.Where(x => x.Contains(filter.Query ?? string.Empty, StringComparison.OrdinalIgnoreCase)).ToList();
return keys.OrderBy(x => x).Take(filter.KeyLimit).ToList();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Users.Models;
using System.IO;

namespace BotSharp.Core.Repository;
Expand Down Expand Up @@ -647,7 +648,7 @@ public List<string> TruncateConversation(string conversationId, string messageId
#if !DEBUG
[SharpCache(10)]
#endif
public List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100)
public List<string> GetConversationStateSearchKeys(ConversationStateKeysFilter filter)
{
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
if (!Directory.Exists(dir)) return [];
Expand All @@ -658,26 +659,29 @@ public List<string> GetConversationStateSearchKeys(int messageLowerLimit = 2, in
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))
var latestStateFile = Path.Combine(d, CONV_LATEST_STATE_FILE);
if (!File.Exists(convFile) || !File.Exists(latestStateFile))
{
continue;
}

var convJson = File.ReadAllText(convFile);
var stateJson = File.ReadAllText(stateFile);
var stateJson = File.ReadAllText(latestStateFile);
var conv = JsonSerializer.Deserialize<Conversation>(convJson, _options);
var states = JsonSerializer.Deserialize<List<StateKeyValue>>(stateJson, _options);
if (conv == null || conv.DialogCount < messageLowerLimit)
var states = JsonSerializer.Deserialize<Dictionary<string, JsonDocument>>(stateJson, _options);
if (conv == null
|| states.IsNullOrEmpty()
|| (!filter.AgentIds.IsNullOrEmpty() && !filter.AgentIds.Contains(conv.AgentId))
|| (!filter.UserIds.IsNullOrEmpty() && !filter.UserIds.Contains(conv.UserId)))
{
continue;
}

var stateKeys = states?.Select(x => x.Key)?.Distinct()?.ToList() ?? [];
var stateKeys = states?.Select(x => x.Key)?.ToList() ?? [];
keys.AddRange(stateKeys);
count++;

if (count >= convUpperLimit)
if (count >= filter.ConvLimit)
{
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using System.IO;

Expand Down Expand Up @@ -271,6 +270,44 @@ public PagedItems<InstructionLogModel> GetInstructionLogs(InstructLogFilter filt
Count = logs.Count()
};
}

public List<string> GetInstructionLogSearchKeys(InstructLogKeysFilter filter)
{
var keys = new List<string>();
var baseDir = Path.Combine(_dbSettings.FileRepository, INSTRUCTION_LOG_FOLDER);
if (!Directory.Exists(baseDir))
{
return keys;
}

var count = 0;
var files = Directory.GetFiles(baseDir);
foreach (var file in files)
{
var json = File.ReadAllText(file);
var log = JsonSerializer.Deserialize<InstructionLogModel>(json, _options);
if (log == null) continue;

if (log == null
|| log.InnerStates.IsNullOrEmpty()
|| (!filter.UserIds.IsNullOrEmpty() && !filter.UserIds.Contains(log.UserId))
|| (!filter.AgentIds.IsNullOrEmpty() && !filter.AgentIds.Contains(log.AgentId)))
{
continue;
}

var stateKeys = log.InnerStates.Select(x => x.Key)?.ToList() ?? [];
keys.AddRange(stateKeys);
count++;

if (count >= filter.LogLimit)
{
break;
}
}

return keys.Distinct().ToList();
}
#endregion

#region Private methods
Expand Down
11 changes: 6 additions & 5 deletions src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,23 +418,24 @@ public async Task<PagedItems<User>> GetUsers(UserFilter filter)
return users;
}

public async Task<bool> IsAdminUser(string userId)
[SharpCache(10, perInstanceCache: true)]
public async Task<(bool, User?)> IsAdminUser(string userId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var user = db.GetUserById(userId);
return user != null && UserConstant.AdminRoles.Contains(user.Role);
var isAdmin = user != null && UserConstant.AdminRoles.Contains(user.Role);
return (isAdmin, user);
}

public async Task<UserAuthorization> GetUserAuthorizations(IEnumerable<string>? agentIds = null)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var user = db.GetUserById(_user.Id);
var (isAdmin, user) = await IsAdminUser(_user.Id);
var auth = new UserAuthorization();

if (user == null) return auth;

auth.IsAdmin = UserConstant.AdminRoles.Contains(user.Role);

auth.IsAdmin = isAdmin;
var role = db.GetRoles(new RoleFilter { Names = [user.Role] }).FirstOrDefault();
var permissions = user.Permissions?.Any() == true ? user.Permissions : role?.Permissions ?? [];
auth.Permissions = permissions;
Expand Down
Loading
Loading