diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs index 81aacb85d..190b6cb8c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs @@ -18,3 +18,12 @@ public enum AgentField Sample, LlmConfig } + +public enum AgentTaskField +{ + All = 1, + Name, + Description, + Enabled, + Content +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs index a7b16b34d..2d8ba477b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs @@ -4,4 +4,5 @@ public class AgentTaskFilter { public Pagination Pager { get; set; } = new Pagination(); public string? AgentId { get; set; } + public bool? Enabled { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index bf69bec39..709958db2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Repositories.Models; +using BotSharp.Abstraction.Tasks.Models; using BotSharp.Abstraction.Users.Models; namespace BotSharp.Abstraction.Repositories; @@ -34,6 +35,14 @@ public interface IBotSharpRepository string GetAgentTemplate(string agentId, string templateName); #endregion + #region Agent Task + PagedItems GetAgentTasks(AgentTaskFilter filter); + AgentTask? GetAgentTask(string agentId, string taskId); + void InsertAgentTask(AgentTask task); + void UpdateAgentTask(AgentTask task, AgentTaskField field); + bool DeleteAgentTask(string agentId, string taskId); + #endregion + #region Conversation void CreateNewConversation(Conversation conversation); bool DeleteConversation(string conversationId); @@ -49,9 +58,7 @@ public interface IBotSharpRepository List GetLastConversations(); bool TruncateConversation(string conversationId, string messageId); #endregion - #region Statistics - void IncrementConversationCount(); - #endregion + #region Execution Log void AddExecutionLogs(string conversationId, List logs); List GetExecutionLogs(string conversationId); @@ -60,4 +67,8 @@ public interface IBotSharpRepository #region LLM Completion Log void SaveLlmCompletionLog(LlmCompletionLog log); #endregion + + #region Statistics + void IncrementConversationCount(); + #endregion } diff --git a/src/Infrastructure/BotSharp.Abstraction/Tasks/IAgentTaskService.cs b/src/Infrastructure/BotSharp.Abstraction/Tasks/IAgentTaskService.cs index 73a9e272b..34199eccd 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Tasks/IAgentTaskService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Tasks/IAgentTaskService.cs @@ -6,4 +6,12 @@ namespace BotSharp.Abstraction.Tasks; public interface IAgentTaskService { Task> GetTasks(AgentTaskFilter filter); + + Task GetTask(string agentId, string taskId); + + Task CreateTask(AgentTask task); + + Task UpdateTask(AgentTask task, AgentTaskField field); + + Task DeleteTask(string agentId, string taskId); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Tasks/Models/AgentTask.cs b/src/Infrastructure/BotSharp.Abstraction/Tasks/Models/AgentTask.cs index 4505087ef..ebbbdf441 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Tasks/Models/AgentTask.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Tasks/Models/AgentTask.cs @@ -10,9 +10,17 @@ public class AgentTask public DateTime CreatedDateTime { get; set; } public DateTime UpdatedDateTime { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] + public string AgentId { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] public Agent Agent { get; set; } + public AgentTask() + { + + } + public AgentTask(string id, string name, string? description = null) { Id = id; diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs index e2dafa7f1..32db29b84 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs @@ -13,5 +13,5 @@ public class Pagination public class PagedItems { public int Count { get; set; } - public IEnumerable Items { get; set; } + public IEnumerable Items { get; set; } = new List(); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index fef3c6dca..de71e1d1d 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -3,6 +3,7 @@ using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Repositories.Models; +using BotSharp.Abstraction.Tasks.Models; using BotSharp.Abstraction.Users.Models; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -119,6 +120,32 @@ public bool DeleteAgents() } #endregion + #region Agent Task + public PagedItems GetAgentTasks(AgentTaskFilter filter) + { + throw new NotImplementedException(); + } + + public AgentTask? GetAgentTask(string agentId, string taskId) + { + throw new NotImplementedException(); + } + + public void InsertAgentTask(AgentTask task) + { + throw new NotImplementedException(); + } + + public void UpdateAgentTask(AgentTask task, AgentTaskField field) + { + throw new NotImplementedException(); + } + + public bool DeleteAgentTask(string agentId, string taskId) + { + throw new NotImplementedException(); + } + #endregion #region Conversation public void CreateNewConversation(Conversation conversation) @@ -184,13 +211,7 @@ public bool TruncateConversation(string conversationId, string messageId) throw new NotImplementedException(); } #endregion - #region Stats - public void IncrementConversationCount() - { - throw new NotImplementedException(); - } - #endregion - + #region User public User? GetUserByEmail(string email) => throw new NotImplementedException(); @@ -205,7 +226,6 @@ public void CreateUser(User user) => throw new NotImplementedException(); #endregion - #region Execution Log public void AddExecutionLogs(string conversationId, List logs) { @@ -224,4 +244,11 @@ public void SaveLlmCompletionLog(LlmCompletionLog log) throw new NotImplementedException(); } #endregion + + #region Stats + public void IncrementConversationCount() + { + throw new NotImplementedException(); + } + #endregion } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index ece042498..4bdb3edf9 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,9 +1,9 @@ using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Evaluations.Settings; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Routing.Settings; +using BotSharp.Abstraction.Tasks.Models; +using Microsoft.Extensions.Logging; using System.IO; namespace BotSharp.Core.Repository @@ -326,12 +326,11 @@ public List GetAgentResponses(string agentId, string prefix, string inte var functions = FetchFunctions(dir); var samples = FetchSamples(dir); var templates = FetchTemplates(dir); - var tasks = FetchTasks(dir); var responses = FetchResponses(dir); return record.SetInstruction(instruction) .SetFunctions(functions) - .SetSamples(samples) .SetTemplates(templates) + .SetSamples(samples) .SetResponses(responses); } @@ -406,6 +405,7 @@ public string GetAgentTemplate(string agentId, string templateName) return string.Empty; } + public void BulkInsertAgents(List agents) { } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs new file mode 100644 index 000000000..57d19270d --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs @@ -0,0 +1,228 @@ +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Tasks.Models; +using System.IO; +using System.Threading.Tasks; + +namespace BotSharp.Core.Repository; + +public partial class FileRepository +{ + #region Task + public PagedItems GetAgentTasks(AgentTaskFilter filter) + { + var tasks = new List(); + var pager = filter.Pager ?? new Pagination(); + var skipCount = 0; + var takeCount = 0; + var totalCount = 0; + var matched = true; + + var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir); + if (!Directory.Exists(dir)) return new PagedItems(); + + foreach (var agentDir in Directory.GetDirectories(dir)) + { + var taskDir = Path.Combine(agentDir, "tasks"); + if (!Directory.Exists(taskDir)) continue; + + var agentId = agentDir.Split(Path.DirectorySeparatorChar).Last(); + + matched = true; + if (filter?.AgentId != null) + { + matched = agentId == filter.AgentId; + } + + if (!matched) continue; + + var curTasks = new List(); + foreach (var taskFile in Directory.GetFiles(taskDir)) + { + var task = ParseAgentTask(taskFile); + if (task == null) continue; + + matched = true; + if (filter?.Enabled != null) + { + matched = matched && task.Enabled == filter.Enabled; + } + + if (!matched) continue; + + totalCount++; + if (takeCount >= pager.Size) continue; + + if (skipCount < pager.Offset) + { + skipCount++; + } + else + { + curTasks.Add(task); + takeCount++; + } + } + + if (curTasks.IsNullOrEmpty()) continue; + + var agent = ParseAgent(agentDir); + curTasks.ForEach(t => + { + t.AgentId = agentId; + t.Agent = agent; + }); + tasks.AddRange(curTasks); + } + + return new PagedItems + { + Items = tasks, + Count = totalCount + }; + } + + public AgentTask? GetAgentTask(string agentId, string taskId) + { + var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId); + if (!Directory.Exists(agentDir)) return null; + + var taskDir = Path.Combine(agentDir, "tasks"); + if (!Directory.Exists(taskDir)) return null; + + var taskFile = FindTaskFileById(taskDir, taskId); + if (taskFile == null) return null; + + var task = ParseAgentTask(taskFile); + if (task == null) return null; + + var agent = ParseAgent(agentDir); + task.AgentId = agentId; + task.Agent = agent; + return task; + } + + public void InsertAgentTask(AgentTask task) + { + if (task == null || string.IsNullOrEmpty(task.AgentId)) return; + + var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, task.AgentId); + if (!Directory.Exists(agentDir)) return; + + var taskDir = Path.Combine(agentDir, "tasks"); + if (!Directory.Exists(taskDir)) + { + Directory.CreateDirectory(taskDir); + } + + var fileName = $"{Guid.NewGuid()}.liquid"; + var taskFile = Path.Combine(taskDir, fileName); + + var model = new AgentTaskFileModel + { + Name = task.Name, + Description = task.Description, + Enabled = task.Enabled, + CreatedDateTime = DateTime.UtcNow, + UpdatedDateTime = DateTime.UtcNow + }; + + var fileContent = BuildAgentTaskFileContent(model, task.Content); + File.WriteAllText(taskFile, fileContent); + } + + public void UpdateAgentTask(AgentTask task, AgentTaskField field) + { + if (task == null || string.IsNullOrEmpty(task.Id)) return; + + var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, task.AgentId); + if (!Directory.Exists(agentDir)) return; + + var taskDir = Path.Combine(agentDir, "tasks"); + if (!Directory.Exists(taskDir)) return; + + var taskFile = FindTaskFileById(taskDir, task.Id); + if (string.IsNullOrEmpty(taskFile)) return; + + var parsedTask = ParseAgentTask(taskFile); + if (parsedTask == null) return; + + var model = new AgentTaskFileModel + { + Name = parsedTask.Name, + Description = parsedTask.Description, + Enabled = parsedTask.Enabled, + CreatedDateTime = parsedTask.CreatedDateTime, + UpdatedDateTime = DateTime.UtcNow + }; + var content = parsedTask.Content; + + switch (field) + { + case AgentTaskField.Name: + model.Name = task.Name; + break; + case AgentTaskField.Description: + model.Description = task.Description; + break; + case AgentTaskField.Enabled: + model.Enabled = task.Enabled; + break; + case AgentTaskField.Content: + content = task.Content; + break; + case AgentTaskField.All: + model.Name = task.Name; + model.Description = task.Description; + model.Enabled = task.Enabled; + content = task.Content; + break; + } + + var fileContent = BuildAgentTaskFileContent(model, content); + File.WriteAllText(taskFile, fileContent); + } + + public bool DeleteAgentTask(string agentId, string taskId) + { + var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId); + if (!Directory.Exists(agentDir)) return false; + + var taskDir = Path.Combine(agentDir, "tasks"); + if (!Directory.Exists(taskDir)) return false; + + var taskFile = FindTaskFileById(taskDir, taskId); + if (string.IsNullOrWhiteSpace(taskFile)) return false; + + File.Delete(taskFile); + return true; + } + + private string? FindTaskFileById(string taskDir, string taskId) + { + if (!Directory.Exists(taskDir) || string.IsNullOrEmpty(taskId)) return null; + + var taskFile = Directory.GetFiles(taskDir).FirstOrDefault(file => + { + var fileName = file.Split(Path.DirectorySeparatorChar).Last(); + var id = fileName.Split('.').First(); + return id.IsEqualTo(taskId); + }); + + return taskFile; + } + + private string BuildAgentTaskFileContent(AgentTaskFileModel fileModel, string taskContent) + { + return $"{AGENT_TASK_PREFIX}\n{JsonSerializer.Serialize(fileModel, _options)}\n{AGENT_TASK_SUFFIX}\n\n{taskContent}"; + } + #endregion +} + +internal class AgentTaskFileModel +{ + public string Name { get; set; } + public string? Description { get; set; } + public bool Enabled { get; set; } + public DateTime CreatedDateTime { get; set; } + public DateTime UpdatedDateTime { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 8131bdd2f..36eaa3edf 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -35,9 +35,9 @@ public void CreateNewConversation(Conversation conversation) { Key = x.Key, Values = new List - { - new StateValue { Data = x.Value, UpdateTime = DateTime.UtcNow } - } + { + new StateValue { Data = x.Value, UpdateTime = DateTime.UtcNow } + } }).ToList(); File.WriteAllText(stateFile, JsonSerializer.Serialize(initialStates, _options)); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs index 18876af48..13e19e54c 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs @@ -33,6 +33,8 @@ public partial class FileRepository : IBotSharpRepository private const string STATE_FILE = "state.json"; private const string EXECUTION_LOG_FILE = "execution.log"; private const string PLUGIN_CONFIG_FILE = "config.json"; + private const string AGENT_TASK_PREFIX = "#metadata"; + private const string AGENT_TASK_SUFFIX = "/metadata"; public FileRepository( IServiceProvider services, @@ -116,9 +118,8 @@ private IQueryable Agents if (agent != null) { agent = agent.SetInstruction(FetchInstruction(d)) - .SetTemplates(FetchTemplates(d)) - .SetTasks(FetchTasks(d)) .SetFunctions(FetchFunctions(d)) + .SetTemplates(FetchTemplates(d)) .SetResponses(FetchResponses(d)) .SetSamples(FetchSamples(d)); _agents.Add(agent); @@ -236,19 +237,10 @@ private List FetchTasks(string fileDir) foreach (var file in Directory.GetFiles(taskDir)) { - var fileName = file.Split(Path.DirectorySeparatorChar).Last(); - var id = fileName.Split('.').First(); - var data = File.ReadAllText(file); - var metadata = Regex.Match(data, @"#metadata.+/metadata", RegexOptions.Singleline); + var task = ParseAgentTask(file); + if (task == null) continue; - if (metadata.Success) - { - var task = metadata.Value.JsonContent(); - task.Id = id; - var content = Regex.Match(data, @"/metadata.+", RegexOptions.Singleline).Value; - task.Content = content.Substring(9).Trim(); - tasks.Add(task); - } + tasks.Add(task); } return tasks; @@ -273,6 +265,51 @@ private List FetchResponses(string fileDir) return responses; } + private Agent? ParseAgent(string agentDir) + { + if (string.IsNullOrEmpty(agentDir)) return null; + + var agentJson = File.ReadAllText(Path.Combine(agentDir, AGENT_FILE)); + if (string.IsNullOrEmpty(agentJson)) return null; + + var agent = JsonSerializer.Deserialize(agentJson, _options); + if (agent == null) return null; + + var instruction = FetchInstruction(agentDir); + var functions = FetchFunctions(agentDir); + var samples = FetchSamples(agentDir); + var templates = FetchTemplates(agentDir); + var responses = FetchResponses(agentDir); + + return agent.SetInstruction(instruction) + .SetFunctions(functions) + .SetTemplates(templates) + .SetSamples(samples) + .SetResponses(responses); + } + + private AgentTask? ParseAgentTask(string taskFile) + { + if (string.IsNullOrWhiteSpace(taskFile)) return null; + + var fileName = taskFile.Split(Path.DirectorySeparatorChar).Last(); + var id = fileName.Split('.').First(); + var data = File.ReadAllText(taskFile); + var pattern = $@"{AGENT_TASK_PREFIX}.+{AGENT_TASK_SUFFIX}"; + var metaData = Regex.Match(data, pattern, RegexOptions.Singleline); + + if (!metaData.Success) return null; + + var task = metaData.Value.JsonContent(); + if (task == null) return null; + + task.Id = id; + pattern = $@"{AGENT_TASK_SUFFIX}.+"; + var content = Regex.Match(data, pattern, RegexOptions.Singleline).Value; + task.Content = content.Substring(AGENT_TASK_SUFFIX.Length).Trim(); + return task; + } + private string[] ParseFileNameByPath(string path, string separator = ".") { var name = path.Split(Path.DirectorySeparatorChar).Last(); diff --git a/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs b/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs index aa5977baa..5a4c182f4 100644 --- a/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs +++ b/src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Tasks; using BotSharp.Abstraction.Tasks.Models; @@ -14,23 +15,36 @@ public AgentTaskService(IServiceProvider services) public async Task> GetTasks(AgentTaskFilter filter) { - var agentService = _services.GetRequiredService(); - var agents = await agentService.GetAgents(new AgentFilter()); - var tasks = new List(); - foreach (var agent in agents.Items) - { - if (filter.AgentId != null && filter.AgentId != agent.Id) - { - continue; - } - agent.Tasks.ForEach(x => x.Agent = agent); - tasks.AddRange(agent.Tasks); - } + var db = _services.GetRequiredService(); + var pagedTasks = db.GetAgentTasks(filter); + return await Task.FromResult(pagedTasks); + } + + public async Task GetTask(string agentId, string taskId) + { + var db = _services.GetRequiredService(); + var task = db.GetAgentTask(agentId, taskId); + return await Task.FromResult(task); + } - return new PagedItems - { - Items = tasks.Skip(filter.Pager.Offset).Take(filter.Pager.Size), - Count = tasks.Count, - }; + public async Task CreateTask(AgentTask task) + { + var db = _services.GetRequiredService(); + db.InsertAgentTask(task); + await Task.CompletedTask; + } + + public async Task UpdateTask(AgentTask task, AgentTaskField field) + { + var db = _services.GetRequiredService(); + db.UpdateAgentTask(task, field); + await Task.CompletedTask; + } + + public async Task DeleteTask(string agentId, string taskId) + { + var db = _services.GetRequiredService(); + var isDeleted = db.DeleteAgentTask(agentId, taskId); + return await Task.FromResult(isDeleted); } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentTaskController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentTaskController.cs index 57e0d8257..95ad902d1 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentTaskController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentTaskController.cs @@ -1,6 +1,4 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Tasks; -using BotSharp.Abstraction.Tasks.Models; namespace BotSharp.OpenAPI.Controllers; @@ -8,30 +6,64 @@ namespace BotSharp.OpenAPI.Controllers; [ApiController] public class AgentTaskController : ControllerBase { - private readonly IAgentService _agentService; + private readonly IAgentTaskService _agentTaskService; private readonly IServiceProvider _services; - public AgentTaskController(IAgentService agentService, IServiceProvider services) + public AgentTaskController(IAgentTaskService agentTaskService, IServiceProvider services) { - _agentService = agentService; + _agentTaskService = agentTaskService; _services = services; } - [HttpGet("/agent/task/{id}")] - public async Task GetAgentTask([FromRoute] string id) + [HttpGet("/agent/{agentId}/task/{taskId}")] + public async Task GetAgentTask([FromRoute] string agentId, [FromRoute] string taskId) { - throw new NotImplementedException(""); + var task = await _agentTaskService.GetTask(agentId, taskId); + if (task == null) return null; + + return AgentTaskViewModel.From(task); } [HttpGet("/agent/tasks")] public async Task> GetAgents([FromQuery] AgentTaskFilter filter) { - var taskService = _services.GetRequiredService(); - var tasks = await taskService.GetTasks(filter); + var tasks = await _agentTaskService.GetTasks(filter); return new PagedItems { Items = tasks.Items.Select(x => AgentTaskViewModel.From(x)), Count = tasks.Count }; } + + [HttpPost("/agent/{agentId}/task")] + public async Task CreateAgentTask([FromRoute] string agentId, [FromBody] AgentTaskCreateModel task) + { + var agentTask = task.ToAgentTask(); + agentTask.AgentId = agentId; + await _agentTaskService.CreateTask(agentTask); + } + + [HttpPut("/agent/{agentId}/task/{taskId}")] + public async Task UpdateAgentTask([FromRoute] string agentId, [FromRoute] string taskId, [FromBody] AgentTaskUpdateModel task) + { + var agentTask = task.ToAgentTask(); + agentTask.AgentId = agentId; + agentTask.Id = taskId; + await _agentTaskService.UpdateTask(agentTask, AgentTaskField.All); + } + + [HttpPatch("/agent/{agentId}/task/{taskId}/{field}")] + public async Task PatchAgentTaskByField([FromRoute] string agentId, [FromRoute] string taskId, [FromRoute] AgentTaskField field, [FromBody] AgentTaskUpdateModel task) + { + var agentTask = task.ToAgentTask(); + agentTask.AgentId = agentId; + agentTask.Id = taskId; + await _agentTaskService.UpdateTask(agentTask, field); + } + + [HttpDelete("/agent/{agentId}/task/{taskId}")] + public async Task DeleteAgentTask([FromRoute] string agentId, [FromRoute] string taskId) + { + return await _agentTaskService.DeleteTask(agentId, taskId); + } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskCreateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskCreateModel.cs new file mode 100644 index 000000000..89565e9fd --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskCreateModel.cs @@ -0,0 +1,22 @@ +using BotSharp.Abstraction.Tasks.Models; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +public class AgentTaskCreateModel +{ + public string Name { get; set; } + public string? Description { get; set; } + public string Content { get; set; } + public bool Enabled { get; set; } + + public AgentTask ToAgentTask() + { + return new AgentTask + { + Name = Name, + Description = Description, + Content = Content, + Enabled = Enabled + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskUpdateModel.cs new file mode 100644 index 000000000..143aee0d3 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskUpdateModel.cs @@ -0,0 +1,22 @@ +using BotSharp.Abstraction.Tasks.Models; + +namespace BotSharp.OpenAPI.ViewModels.Agents; + +public class AgentTaskUpdateModel +{ + public string? Name { get; set; } + public string? Description { get; set; } + public string? Content { get; set; } + public bool Enabled { get; set; } + + public AgentTask ToAgentTask() + { + return new AgentTask + { + Name = Name, + Description = Description, + Content = Content, + Enabled = Enabled + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskViewModel.cs index 0e69a25b9..2fc06dc3c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentTaskViewModel.cs @@ -28,8 +28,8 @@ public static AgentTaskViewModel From(AgentTask task) Description = task.Description, Content = task.Content, Enabled = task.Enabled, - AgentId = task.Agent.Id, - AgentName = task.Agent.Name, + AgentId = task.AgentId, + AgentName = task.Agent?.Name, CreatedDateTime = task.CreatedDateTime, UpdatedDateTime = task.UpdatedDateTime }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentTaskDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentTaskDocument.cs new file mode 100644 index 000000000..198b70c1f --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentTaskDocument.cs @@ -0,0 +1,12 @@ +namespace BotSharp.Plugin.MongoStorage.Collections; + +public class AgentTaskDocument : MongoBase +{ + public string Name { get; set; } + public string? Description { get; set; } + public string Content { get; set; } + public bool Enabled { get; set; } + public string AgentId { get; set; } + public DateTime CreatedTime { get; set; } + public DateTime UpdatedTime { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs index a3e994b49..0067f885c 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs @@ -31,6 +31,9 @@ private string GetDatabaseName(string mongoDbConnectionString) public IMongoCollection Agents => Database.GetCollection($"{_collectionPrefix}_Agents"); + public IMongoCollection AgentTasks + => Database.GetCollection($"{_collectionPrefix}_AgentTasks"); + public IMongoCollection Conversations => Database.GetCollection($"{_collectionPrefix}_Conversations"); diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 58fe44ff4..b974f6811 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -1,9 +1,11 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Evaluations.Settings; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Settings; +using BotSharp.Abstraction.Tasks.Models; using BotSharp.Plugin.MongoStorage.Collections; using BotSharp.Plugin.MongoStorage.Models; @@ -261,33 +263,7 @@ private void UpdateAgentAllFields(Agent agent) var agent = _dc.Agents.AsQueryable().FirstOrDefault(x => x.Id == agentId); if (agent == null) return null; - return new Agent - { - Id = agent.Id, - Name = agent.Name, - IconUrl = agent.IconUrl, - Description = agent.Description, - Instruction = agent.Instruction, - Templates = !agent.Templates.IsNullOrEmpty() ? agent.Templates - .Select(t => AgentTemplateMongoElement.ToDomainElement(t)) - .ToList() : new List(), - Functions = !agent.Functions.IsNullOrEmpty() ? agent.Functions - .Select(f => FunctionDefMongoElement.ToDomainElement(f)) - .ToList() : new List(), - Responses = !agent.Responses.IsNullOrEmpty() ? agent.Responses - .Select(r => AgentResponseMongoElement.ToDomainElement(r)) - .ToList() : new List(), - Samples = agent.Samples ?? new List(), - IsPublic = agent.IsPublic, - Disabled = agent.Disabled, - Type = agent.Type, - InheritAgentId = agent.InheritAgentId, - Profiles = agent.Profiles, - RoutingRules = !agent.RoutingRules.IsNullOrEmpty() ? agent.RoutingRules - .Select(r => RoutingRuleMongoElement.ToDomainElement(agent.Id, agent.Name, r)) - .ToList() : new List(), - LlmConfig = AgentLlmConfigMongoElement.ToDomainElement(agent.LlmConfig) - }; + return TransformAgentDocument(agent); } public List GetAgents(AgentFilter filter) @@ -323,33 +299,7 @@ public List GetAgents(AgentFilter filter) var agentDocs = _dc.Agents.Find(builder.And(filters)).ToList(); - return agentDocs.Select(x => new Agent - { - Id = x.Id, - Name = x.Name, - IconUrl = x.IconUrl, - Description = x.Description, - Instruction = x.Instruction, - Templates = !x.Templates.IsNullOrEmpty() ? x.Templates - .Select(t => AgentTemplateMongoElement.ToDomainElement(t)) - .ToList() : new List(), - Functions = !x.Functions.IsNullOrEmpty() ? x.Functions - .Select(f => FunctionDefMongoElement.ToDomainElement(f)) - .ToList() : new List(), - Responses = !x.Responses.IsNullOrEmpty() ? x.Responses - .Select(r => AgentResponseMongoElement.ToDomainElement(r)) - .ToList() : new List(), - Samples = x.Samples ?? new List(), - IsPublic = x.IsPublic, - Disabled = x.Disabled, - Type = x.Type, - InheritAgentId = x.InheritAgentId, - Profiles = x.Profiles, - RoutingRules = !x.RoutingRules.IsNullOrEmpty() ? x.RoutingRules - .Select(r => RoutingRuleMongoElement.ToDomainElement(x.Id, x.Name, r)) - .ToList() : new List(), - LlmConfig = AgentLlmConfigMongoElement.ToDomainElement(x.LlmConfig) - }).ToList(); + return agentDocs.Select(x => TransformAgentDocument(x)).ToList(); } public List GetAgentsByUser(string userId) @@ -453,4 +403,37 @@ public bool DeleteAgents() } } + + private Agent TransformAgentDocument(AgentDocument? agentDoc) + { + if (agentDoc == null) return new Agent(); + + return new Agent + { + Id = agentDoc.Id, + Name = agentDoc.Name, + IconUrl = agentDoc.IconUrl, + Description = agentDoc.Description, + Instruction = agentDoc.Instruction, + Templates = !agentDoc.Templates.IsNullOrEmpty() ? agentDoc.Templates + .Select(t => AgentTemplateMongoElement.ToDomainElement(t)) + .ToList() : new List(), + Functions = !agentDoc.Functions.IsNullOrEmpty() ? agentDoc.Functions + .Select(f => FunctionDefMongoElement.ToDomainElement(f)) + .ToList() : new List(), + Responses = !agentDoc.Responses.IsNullOrEmpty() ? agentDoc.Responses + .Select(r => AgentResponseMongoElement.ToDomainElement(r)) + .ToList() : new List(), + Samples = agentDoc.Samples ?? new List(), + IsPublic = agentDoc.IsPublic, + Disabled = agentDoc.Disabled, + Type = agentDoc.Type, + InheritAgentId = agentDoc.InheritAgentId, + Profiles = agentDoc.Profiles, + RoutingRules = !agentDoc.RoutingRules.IsNullOrEmpty() ? agentDoc.RoutingRules + .Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r)) + .ToList() : new List(), + LlmConfig = AgentLlmConfigMongoElement.ToDomainElement(agentDoc.LlmConfig) + }; + } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs new file mode 100644 index 000000000..ac3dd97e4 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs @@ -0,0 +1,140 @@ +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Tasks.Models; +using BotSharp.Plugin.MongoStorage.Collections; + +namespace BotSharp.Plugin.MongoStorage.Repository; + +public partial class MongoRepository +{ + #region Task + public PagedItems GetAgentTasks(AgentTaskFilter filter) + { + var pager = filter.Pager ?? new Pagination(); + var builder = Builders.Filter; + var filters = new List>() { builder.Empty }; + + if (!string.IsNullOrEmpty(filter.AgentId)) + { + filters.Add(builder.Eq(x => x.AgentId, filter.AgentId)); + } + + if (filter.Enabled.HasValue) + { + filters.Add(builder.Eq(x => x.Enabled, filter.Enabled.Value)); + } + + var filterDef = builder.And(filters); + var sortDef = Builders.Sort.Descending(x => x.CreatedTime); + var totalTasks = _dc.AgentTasks.CountDocuments(filterDef); + var taskDocs = _dc.AgentTasks.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList(); + + var agentIds = taskDocs.Select(x => x.AgentId).Distinct().ToList(); + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + + var tasks = taskDocs.Select(x => new AgentTask + { + Id = x.Id, + Name = x.Name, + Description = x.Description, + Enabled = x.Enabled, + AgentId = x.AgentId, + Content = x.Content, + CreatedDateTime = x.CreatedTime, + UpdatedDateTime = x.UpdatedTime, + Agent = agents.FirstOrDefault(a => a.Id == x.AgentId) + }).ToList(); + + return new PagedItems + { + Items = tasks, + Count = (int)totalTasks + }; + } + + public AgentTask? GetAgentTask(string agentId, string taskId) + { + if (string.IsNullOrEmpty(taskId)) return null; + + var taskDoc = _dc.AgentTasks.AsQueryable().FirstOrDefault(x => x.Id == taskId); + if (taskDoc == null) return null; + + var agentDoc = _dc.Agents.AsQueryable().FirstOrDefault(x => x.Id == taskDoc.AgentId); + var agent = TransformAgentDocument(agentDoc); + + var task = new AgentTask + { + Id = taskDoc.Id, + Name = taskDoc.Name, + Description = taskDoc.Description, + Enabled = taskDoc.Enabled, + AgentId = taskDoc.AgentId, + Content = taskDoc.Content, + CreatedDateTime = taskDoc.CreatedTime, + UpdatedDateTime = taskDoc.UpdatedTime, + Agent = agent + }; + + return task; + } + + public void InsertAgentTask(AgentTask task) + { + var taskDoc = new AgentTaskDocument + { + Id = Guid.NewGuid().ToString(), + Name = task.Name, + Description = task.Description, + Enabled = task.Enabled, + AgentId = task.AgentId, + Content = task.Content, + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + }; + + _dc.AgentTasks.InsertOne(taskDoc); + } + + public void UpdateAgentTask(AgentTask task, AgentTaskField field) + { + if (task == null || string.IsNullOrEmpty(task.Id)) return; + + var filter = Builders.Filter.Eq(x => x.Id, task.Id); + var taskDoc = _dc.AgentTasks.Find(filter).FirstOrDefault(); + if (taskDoc == null) return; + + switch (field) + { + case AgentTaskField.Name: + taskDoc.Name = task.Name; + break; + case AgentTaskField.Description: + taskDoc.Description = task.Description; + break; + case AgentTaskField.Enabled: + taskDoc.Enabled = task.Enabled; + break; + case AgentTaskField.Content: + taskDoc.Content = task.Content; + break; + case AgentTaskField.All: + taskDoc.Name = task.Name; + taskDoc.Description = task.Description; + taskDoc.Enabled = task.Enabled; + taskDoc.Content = task.Content; + break; + } + + taskDoc.UpdatedTime = DateTime.UtcNow; + _dc.AgentTasks.ReplaceOne(filter, taskDoc); + } + + public bool DeleteAgentTask(string agentId, string taskId) + { + if (string.IsNullOrEmpty(taskId)) return false; + + var filter = Builders.Filter.Eq(x => x.Id, taskId); + var taskDeleted = _dc.AgentTasks.DeleteOne(filter); + return taskDeleted.DeletedCount > 0; + } + #endregion +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index 0a1c2530e..9f1b2a204 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -217,9 +217,9 @@ public PagedItems GetConversations(ConversationFilter filter) if (!string.IsNullOrEmpty(filter.UserId)) filters.Add(builder.Eq(x => x.UserId, filter.UserId)); var filterDef = builder.And(filters); - var sortDefinition = Builders.Sort.Descending(x => x.CreatedTime); + var sortDef = Builders.Sort.Descending(x => x.CreatedTime); var pager = filter?.Pager ?? new Pagination(); - var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDefinition).Skip(pager.Offset).Limit(pager.Size).ToList(); + var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList(); var count = _dc.Conversations.CountDocuments(filterDef); foreach (var conv in conversationDocs)