From 5be85d3f4ba0fbb1aabaf07fead6aed4aa5070f5 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Mon, 6 Oct 2025 01:27:18 -0500 Subject: [PATCH] refine repository for agent code script --- .../Agents/Enums/AgentCodeScriptType.cs | 7 + .../Agents/Enums/AgentFuncVisMode.cs | 2 +- .../Agents/Enums/AgentRole.cs | 2 +- .../Agents/Enums/AgentType.cs | 2 +- .../Agents/Enums/BuiltInAgentId.cs | 2 +- .../Agents/Models/AgentCodeScript.cs | 23 ++- .../Enums/ConversationChannel.cs | 2 +- .../Conversations/Enums/ConversationStatus.cs | 2 +- .../Conversations/Enums/StateDataType.cs | 2 +- .../Conversations/Enums/StateSource.cs | 2 +- .../Infrastructures/Enums/LanguageType.cs | 2 +- .../Infrastructures/Enums/StateConst.cs | 2 +- .../Instructs/IInstructHook.cs | 9 +- .../Instructs/Models/CodeInstructContext.cs | 6 + .../Filters/AgentCodeScriptFilter.cs | 12 ++ .../Repositories/IBotSharpRepository.cs | 6 +- .../Routing/Enums/RoutingMode.cs | 2 +- .../Routing/Enums/RuleType.cs | 2 +- .../Tasks/Enums/TaskStatus.cs | 2 +- .../Services/AgentService.CreateAgent.cs | 20 +- .../Services/InstructService.Execute.cs | 180 +++++++++++------- .../FileRepository.AgentCode.cs | 161 ---------------- .../FileRepository.AgentCodeScript.cs | 157 +++++++++++++++ .../Hooks/InstructionLogHook.cs | 5 +- ...Document.cs => AgentCodeScriptDocument.cs} | 15 +- .../MongoDbContext.cs | 4 +- .../Repository/MongoRepository.Agent.cs | 6 +- .../Repository/MongoRepository.AgentCode.cs | 113 ----------- .../MongoRepository.AgentCodeScript.cs | 129 +++++++++++++ 29 files changed, 494 insertions(+), 385 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentCodeScriptType.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Instructs/Models/CodeInstructContext.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs delete mode 100644 src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCode.cs create mode 100644 src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs rename src/Plugins/BotSharp.Plugin.MongoStorage/Collections/{AgentCodeDocument.cs => AgentCodeScriptDocument.cs} (54%) delete mode 100644 src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCode.cs create mode 100644 src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentCodeScriptType.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentCodeScriptType.cs new file mode 100644 index 00000000..4bf5f262 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentCodeScriptType.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Agents.Enums; + +public static class AgentCodeScriptType +{ + public const string Src = "src"; + public const string Test = "test"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentFuncVisMode.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentFuncVisMode.cs index 21224815..71323783 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentFuncVisMode.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentFuncVisMode.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Agents.Enums; -public class AgentFuncVisMode +public static class AgentFuncVisMode { public const string Manual = "manual"; public const string Auto = "auto"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs index 226313e0..2975e44f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentRole.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Agents.Enums; -public class AgentRole +public static class AgentRole { public const string System = "system"; public const string Assistant = "assistant"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs index 455e87ff..3b976784 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Agents.Enums; -public class AgentType +public static class AgentType { /// /// Routing agent diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs index 96aa4754..82b0efab 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Agents.Enums; -public class BuiltInAgentId +public static class BuiltInAgentId { /// /// A routing agent can be used as a base router. diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs index 9782f848..840d6721 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentCodeScript.cs @@ -1,18 +1,29 @@ namespace BotSharp.Abstraction.Agents.Models; -public class AgentCodeScript +public class AgentCodeScript : AgentCodeScriptBase { public string Id { get; set; } - public string AgentId { get; set; } - public string Name { get; set; } - public string Content { get; set; } + public string AgentId { get; set; } = null!; - public AgentCodeScript() + public AgentCodeScript() : base() { } public override string ToString() { - return Name; + return $"{CodePath}"; } } + +public class AgentCodeScriptBase +{ + public string Name { get; set; } = null!; + public string Content { get; set; } = null!; + + /// + /// Code script type: src, test + /// + public string ScriptType { get; set; } = null!; + + public string CodePath => $"{ScriptType}/{Name}"; +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs index 1843a1bc..d569da59 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Conversations.Enums; -public class ConversationChannel +public static class ConversationChannel { public const string WebChat = "webchat"; public const string OpenAPI = "openapi"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationStatus.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationStatus.cs index dc8a53b4..b82e094b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationStatus.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationStatus.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Conversations.Enums; -public class ConversationStatus +public static class ConversationStatus { public const string Open = "open"; public const string Closed = "closed"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateDataType.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateDataType.cs index 20d635f4..9f80a9e8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateDataType.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateDataType.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Conversations.Enums; -public class StateDataType +public static class StateDataType { public const string String = "string"; public const string Boolean = "boolean"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateSource.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateSource.cs index 31f32e16..cff79802 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateSource.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateSource.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Conversations.Enums; -public class StateSource +public static class StateSource { public const string External = "external"; public const string Application = "application"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/LanguageType.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/LanguageType.cs index 64351e45..9c5dd827 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/LanguageType.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/LanguageType.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Infrastructures.Enums; -public class LanguageType +public static class LanguageType { public const string UNKNOWN = "Unknown"; public const string ENGLISH = "English"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs index 038699ac..4a048b02 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Infrastructures.Enums; -public class StateConst +public static class StateConst { public const string EXPECTED_ACTION_AGENT = "expected_next_action_agent"; public const string EXPECTED_GOAL_AGENT = "expected_user_goal_agent"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs index 23b8bf60..a7e3c1df 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs @@ -5,7 +5,10 @@ namespace BotSharp.Abstraction.Instructs; public interface IInstructHook : IHookBase { - Task BeforeCompletion(Agent agent, RoleDialogModel message); - Task AfterCompletion(Agent agent, InstructResult result); - Task OnResponseGenerated(InstructResponseModel response); + Task BeforeCompletion(Agent agent, RoleDialogModel message) => Task.CompletedTask; + Task AfterCompletion(Agent agent, InstructResult result) => Task.CompletedTask; + Task OnResponseGenerated(InstructResponseModel response) => Task.CompletedTask; + + Task BeforeCodeExecution(Agent agent, RoleDialogModel message, CodeInstructContext context) => Task.CompletedTask; + Task AfterCodeExecution(Agent agent, InstructResult result) => Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/CodeInstructContext.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/CodeInstructContext.cs new file mode 100644 index 00000000..0cb3910b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/CodeInstructContext.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Instructs.Models; + +public class CodeInstructContext +{ + public List Arguments { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs new file mode 100644 index 00000000..530bb4aa --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentCodeScriptFilter.cs @@ -0,0 +1,12 @@ +namespace BotSharp.Abstraction.Repositories.Filters; + +public class AgentCodeScriptFilter +{ + public List? ScriptNames { get; set; } + public List? ScriptTypes { get; set; } + + public static AgentCodeScriptFilter Empty() + { + return new AgentCodeScriptFilter(); + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 85f75e52..f2ebe6a8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -107,15 +107,15 @@ public interface IBotSharpRepository : IHaveServiceProvider #endregion #region Agent Code - List GetAgentCodeScripts(string agentId, List? scriptNames = null) + List GetAgentCodeScripts(string agentId, AgentCodeScriptFilter? filter = null) => throw new NotImplementedException(); - string? GetAgentCodeScript(string agentId, string scriptName) + string? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) => throw new NotImplementedException(); bool UpdateAgentCodeScripts(string agentId, List scripts) => throw new NotImplementedException(); bool BulkInsertAgentCodeScripts(string agentId, List scripts) => throw new NotImplementedException(); - bool DeleteAgentCodeScripts(string agentId, List? scriptNames) + bool DeleteAgentCodeScripts(string agentId, List? scripts = null) => throw new NotImplementedException(); #endregion diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RoutingMode.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RoutingMode.cs index 8f946d01..bc63066f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RoutingMode.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RoutingMode.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Routing.Enums; -public class RoutingMode +public static class RoutingMode { public const string Eager = "eager"; public const string Lazy = "lazy"; diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs index c595d59c..dc74b6ca 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Routing.Enums; -public class RuleType +public static class RuleType { /// /// Fallback to redirect agent diff --git a/src/Infrastructure/BotSharp.Abstraction/Tasks/Enums/TaskStatus.cs b/src/Infrastructure/BotSharp.Abstraction/Tasks/Enums/TaskStatus.cs index f36d00a0..48d31c6e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Tasks/Enums/TaskStatus.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Tasks/Enums/TaskStatus.cs @@ -1,7 +1,7 @@ /// /// Agent task status /// -public class TaskStatus +public static class TaskStatus { public const string Scheduled = "scheduled"; public const string New = "new"; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index cc0a92bc..d61f0f3d 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -229,12 +229,22 @@ public partial class AgentService } var agentId = fileDir.Split(Path.DirectorySeparatorChar).Last(); - scripts = Directory.GetFiles(codeDir).Select(file => new AgentCodeScript + + foreach (var folder in Directory.EnumerateDirectories(codeDir)) { - AgentId = agentId, - Name = Path.GetFileName(file), - Content = File.ReadAllText(file) - }).ToList(); + var scriptType = folder.Split(Path.DirectorySeparatorChar).Last(); + foreach (var file in Directory.EnumerateFiles(folder)) + { + scripts.Add(new AgentCodeScript + { + AgentId = agentId, + Name = Path.GetFileName(file), + ScriptType = scriptType, + Content = File.ReadAllText(file) + }); + } + } + return scripts; } } diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 7eee1b55..70a2257b 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -40,9 +40,12 @@ public partial class InstructService } - var provider = string.Empty; - var model = string.Empty; - var prompt = string.Empty; + // Run code template + var codeResponse = await GetCodeResponse(agent, message, templateName, codeOptions); + if (codeResponse != null) + { + return codeResponse; + } // Before completion hooks @@ -63,48 +66,43 @@ public partial class InstructService } - // Run code template - var (text, isCodeComplete) = await GetCodeResponse(agentId, templateName, codeOptions); - if (isCodeComplete) + var provider = string.Empty; + var model = string.Empty; + + // Render prompt + var prompt = string.IsNullOrEmpty(templateName) ? + agentService.RenderInstruction(agent) : + agentService.RenderTemplate(agent, templateName); + + var completer = CompletionProvider.GetCompletion(_services, + agentConfig: agent.LlmConfig); + + if (completer is ITextCompletion textCompleter) { - response.Text = text; + instruction = null; + provider = textCompleter.Provider; + model = textCompleter.Model; + + var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId); + response.Text = result; } - else + else if (completer is IChatCompletion chatCompleter) { - // Render prompt - prompt = string.IsNullOrEmpty(templateName) ? - agentService.RenderInstruction(agent) : - agentService.RenderTemplate(agent, templateName); + provider = chatCompleter.Provider; + model = chatCompleter.Model; - var completer = CompletionProvider.GetCompletion(_services, - agentConfig: agent.LlmConfig); - - if (completer is ITextCompletion textCompleter) + if (instruction == "#TEMPLATE#") { - instruction = null; - provider = textCompleter.Provider; - model = textCompleter.Model; - - var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId); - response.Text = result; + instruction = prompt; + prompt = message.Content; } - else if (completer is IChatCompletion chatCompleter) + + var result = await chatCompleter.GetChatCompletions(new Agent { - provider = chatCompleter.Provider; - model = chatCompleter.Model; - - if (instruction == "#TEMPLATE#") - { - instruction = prompt; - prompt = message.Content; - } - - var result = await chatCompleter.GetChatCompletions(new Agent - { - Id = agentId, - Name = agent.Name, - Instruction = instruction - }, new List + Id = agentId, + Name = agent.Name, + Instruction = instruction + }, new List { new RoleDialogModel(AgentRole.User, prompt) { @@ -113,46 +111,49 @@ public partial class InstructService Files = files?.Select(x => new BotSharpFile { FileUrl = x.FileUrl, FileData = x.FileData, ContentType = x.ContentType }).ToList() ?? [] } }); - response.Text = result.Content; - } + response.Text = result.Content; } // After completion hooks foreach (var hook in hooks) { await hook.AfterCompletion(agent, response); - if (!isCodeComplete) + await hook.OnResponseGenerated(new InstructResponseModel { - await hook.OnResponseGenerated(new InstructResponseModel - { - AgentId = agentId, - Provider = provider, - Model = model, - TemplateName = templateName, - UserMessage = prompt, - SystemInstruction = instruction, - CompletionText = response.Text - }); - } + AgentId = agentId, + Provider = provider, + Model = model, + TemplateName = templateName, + UserMessage = prompt, + SystemInstruction = instruction, + CompletionText = response.Text + }); } return response; } /// - /// Get code response => return: (response text, whether code execution is completed) + /// Get code response /// - /// + /// + /// /// /// /// - private async Task<(string?, bool)> GetCodeResponse(string agentId, string templateName, CodeInstructOptions? codeOptions) + private async Task GetCodeResponse(Agent agent, RoleDialogModel message, string templateName, CodeInstructOptions? codeOptions) { + InstructResult? response = null; + + if (agent == null) + { + return response; + } + + var state = _services.GetRequiredService(); var db = _services.GetRequiredService(); - - var isComplete = false; - var response = string.Empty; + var hooks = _services.GetHooks(agent.Id); var codeProvider = codeOptions?.CodeInterpretProvider.IfNullOrEmptyAs("botsharp-py-interpreter"); var codeInterpreter = _services.GetServices() @@ -161,9 +162,9 @@ public partial class InstructService if (codeInterpreter == null) { #if DEBUG - _logger.LogWarning($"No code interpreter found. (Agent: {agentId}, Code interpreter: {codeProvider})"); + _logger.LogWarning($"No code interpreter found. (Agent: {agent.Id}, Code interpreter: {codeProvider})"); #endif - return (response, isComplete); + return response; } // Get code script name @@ -180,19 +181,19 @@ public partial class InstructService if (string.IsNullOrEmpty(scriptName)) { #if DEBUG - _logger.LogWarning($"Empty code script name. (Agent: {agentId}, {scriptName})"); + _logger.LogWarning($"Empty code script name. (Agent: {agent.Id}, {scriptName})"); #endif - return (response, isComplete); + return response; } // Get code script - var codeScript = db.GetAgentCodeScript(agentId, scriptName); + var codeScript = db.GetAgentCodeScript(agent.Id, scriptName, scriptType: AgentCodeScriptType.Src); if (string.IsNullOrWhiteSpace(codeScript)) { #if DEBUG - _logger.LogWarning($"Empty code script. (Agent: {agentId}, {scriptName})"); + _logger.LogWarning($"Empty code script. (Agent: {agent.Id}, {scriptName})"); #endif - return (response, isComplete); + return response; } // Get code arguments @@ -202,14 +203,55 @@ public partial class InstructService arguments = state.GetStates().Select(x => new KeyValue(x.Key, x.Value)).ToList(); } + var context = new CodeInstructContext + { + Arguments = arguments + }; + + // Before code execution + foreach (var hook in hooks) + { + await hook.BeforeCodeExecution(agent, message, context); + + // Interrupted by hook + if (message.StopCompletion) + { + return new InstructResult + { + MessageId = message.MessageId, + Text = message.Content + }; + } + } + // Run code script var result = await codeInterpreter.RunCode(codeScript, options: new() { - Arguments = arguments + Arguments = context.Arguments }); - response = result?.Result?.ToString(); - isComplete = true; - return (response, isComplete); + response = new InstructResult + { + MessageId = message.MessageId, + Text = result?.Result?.ToString() + }; + + // After code execution + foreach (var hook in hooks) + { + await hook.AfterCodeExecution(agent, response); + await hook.OnResponseGenerated(new InstructResponseModel + { + AgentId = agent.Id, + Provider = codeInterpreter.Provider, + Model = string.Empty, + TemplateName = scriptName, + UserMessage = string.Empty, + SystemInstruction = string.Empty, + CompletionText = response.Text + }); + } + + return response; } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCode.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCode.cs deleted file mode 100644 index bf11059a..00000000 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCode.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System.IO; - -namespace BotSharp.Core.Repository; - -public partial class FileRepository -{ - #region Code - public List GetAgentCodeScripts(string agentId, List? scriptNames = null) - { - if (string.IsNullOrWhiteSpace(agentId)) - { - return []; - } - - var dir = BuildAgentCodeDir(agentId); - if (!Directory.Exists(dir)) - { - return []; - } - - var results = new List(); - foreach (var file in Directory.GetFiles(dir)) - { - var fileName = Path.GetFileName(file); - if (scriptNames != null && !scriptNames.Contains(fileName)) - { - continue; - } - - var script = new AgentCodeScript - { - AgentId = agentId, - Name = fileName, - Content = File.ReadAllText(file) - }; - results.Add(script); - } - return results; - } - - public string? GetAgentCodeScript(string agentId, string scriptName) - { - if (string.IsNullOrWhiteSpace(agentId) - || string.IsNullOrWhiteSpace(scriptName)) - { - return null; - } - - var dir = BuildAgentCodeDir(agentId); - if (!Directory.Exists(dir)) - { - return null; - } - - foreach (var file in Directory.GetFiles(dir)) - { - var fileName = Path.GetFileName(file); - if (scriptName.IsEqualTo(fileName)) - { - return File.ReadAllText(file); - } - } - return string.Empty; - } - - public bool UpdateAgentCodeScripts(string agentId, List scripts) - { - if (string.IsNullOrWhiteSpace(agentId) || scripts.IsNullOrEmpty()) - { - return false; - } - - var dir = BuildAgentCodeDir(agentId); - if (!Directory.Exists(dir)) - { - return false; - } - - var dict = scripts.DistinctBy(x => x.Name).ToDictionary(x => x.Name, x => x); - var files = Directory.GetFiles(dir).Where(x => dict.Keys.Contains(Path.GetFileName(x))).ToList(); - - foreach (var file in files) - { - if (dict.TryGetValue(Path.GetFileName(file), out var script)) - { - File.WriteAllText(file, script.Content); - } - } - - return true; - } - - public bool BulkInsertAgentCodeScripts(string agentId, List scripts) - { - if (string.IsNullOrWhiteSpace(agentId) || scripts.IsNullOrEmpty()) - { - return false; - } - - var dir = BuildAgentCodeDir(agentId); - if (!Directory.Exists(dir)) - { - return false; - } - - foreach (var script in scripts) - { - if (string.IsNullOrWhiteSpace(script.Name)) - { - continue; - } - - var path = Path.Combine(dir, script.Name); - File.WriteAllText(path, script.Content); - } - - return true; - } - - public bool DeleteAgentCodeScripts(string agentId, List? scriptNames) - { - if (string.IsNullOrWhiteSpace(agentId)) - { - return false; - } - - var dir = BuildAgentCodeDir(agentId); - if (!Directory.Exists(dir)) - { - return false; - } - - if (scriptNames == null) - { - Directory.Delete(dir, true); - return true; - } - else if (!scriptNames.Any()) - { - return false; - } - - foreach (var file in Directory.GetFiles(dir)) - { - var fileName = Path.GetFileName(file); - if (scriptNames.Contains(fileName)) - { - File.Delete(file); - } - } - return true; - } - #endregion - - #region Private methods - private string BuildAgentCodeDir(string agentId) - { - return Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_CODES_FOLDER); - } - #endregion -} diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs new file mode 100644 index 00000000..7482f358 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs @@ -0,0 +1,157 @@ +using System.IO; + +namespace BotSharp.Core.Repository; + +public partial class FileRepository +{ + #region Code + public List GetAgentCodeScripts(string agentId, AgentCodeScriptFilter? filter = null) + { + if (string.IsNullOrWhiteSpace(agentId)) + { + return []; + } + + var dir = BuildAgentCodeScriptDir(agentId); + if (!Directory.Exists(dir)) + { + return []; + } + + filter ??= AgentCodeScriptFilter.Empty(); + var results = new List(); + + foreach (var folder in Directory.EnumerateDirectories(dir)) + { + var scriptType = folder.Split(Path.DirectorySeparatorChar).Last(); + if (filter.ScriptTypes != null && !filter.ScriptTypes.Contains(scriptType)) + { + continue; + } + + foreach (var file in Directory.EnumerateFiles(folder)) + { + var fileName = Path.GetFileName(file); + if (filter.ScriptNames != null && !filter.ScriptNames.Contains(fileName)) + { + continue; + } + + results.Add(new AgentCodeScript + { + AgentId = agentId, + Name = fileName, + ScriptType = scriptType, + Content = File.ReadAllText(file) + }); + } + } + + return results; + } + + public string? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + { + if (string.IsNullOrWhiteSpace(agentId) + || string.IsNullOrWhiteSpace(scriptName) + || string.IsNullOrWhiteSpace(scriptType)) + { + return null; + } + + var dir = BuildAgentCodeScriptDir(agentId, scriptType); + if (!Directory.Exists(dir)) + { + return null; + } + + var foundFile = Directory.GetFiles(dir).FirstOrDefault(file => scriptName.IsEqualTo(Path.GetFileName(file))); + if (!string.IsNullOrEmpty(foundFile)) + { + return File.ReadAllText(foundFile); + } + return string.Empty; + } + + public bool UpdateAgentCodeScripts(string agentId, List scripts) + { + if (string.IsNullOrWhiteSpace(agentId) || scripts.IsNullOrEmpty()) + { + return false; + } + + foreach (var script in scripts) + { + if (string.IsNullOrWhiteSpace(script.Name) + || string.IsNullOrWhiteSpace(script.ScriptType)) + { + continue; + } + + var dir = BuildAgentCodeScriptDir(agentId, script.ScriptType); + if (!Directory.Exists(dir)) + { + continue; + } + + var file = Path.Combine(dir, script.Name); + File.WriteAllText(file, script.Content); + } + + return true; + } + + public bool BulkInsertAgentCodeScripts(string agentId, List scripts) + { + return UpdateAgentCodeScripts(agentId, scripts); + } + + public bool DeleteAgentCodeScripts(string agentId, List? scripts = null) + { + if (string.IsNullOrWhiteSpace(agentId)) + { + return false; + } + + var dir = BuildAgentCodeScriptDir(agentId); + if (!Directory.Exists(dir)) + { + return false; + } + + if (scripts == null) + { + Directory.Delete(dir, true); + return true; + } + else if (!scripts.Any()) + { + return false; + } + + var dict = scripts.DistinctBy(x => x.CodePath).ToDictionary(x => x.CodePath, x => x); + foreach (var pair in dict) + { + var file = Path.Combine(dir, pair.Value.ScriptType, pair.Value.Name); + if (File.Exists(file)) + { + File.Delete(file); + } + } + + return true; + } + #endregion + + #region Private methods + private string BuildAgentCodeScriptDir(string agentId, string? scirptType = null) + { + var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_CODES_FOLDER); + if (!string.IsNullOrWhiteSpace(scirptType)) + { + dir = Path.Combine(dir, scirptType); + } + return dir; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs index 8de3db70..c95af0c1 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/InstructionLogHook.cs @@ -2,6 +2,7 @@ using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Instructs.Settings; using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Users; +using BotSharp.Abstraction.Utilities; namespace BotSharp.Logger.Hooks; @@ -39,7 +40,9 @@ public class InstructionLogHook : InstructHookBase var state = _services.GetRequiredService(); var user = db.GetUserById(_user.Id); - var templateName = response.TemplateName ?? state.GetState("instruct_template_name") ?? null; + var templateName = response.TemplateName + .IfNullOrEmptyAs(state.GetState("instruct_template_name")) + .IfNullOrEmptyAs(null); db.SaveInstructionLogs(new List { diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeScriptDocument.cs similarity index 54% rename from src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeDocument.cs rename to src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeScriptDocument.cs index f37db77d..7b877fda 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentCodeScriptDocument.cs @@ -2,31 +2,34 @@ using BotSharp.Abstraction.Agents.Models; namespace BotSharp.Plugin.MongoStorage.Collections; -public class AgentCodeDocument : MongoBase +public class AgentCodeScriptDocument : MongoBase { public string AgentId { get; set; } = default!; public string Name { get; set; } = default!; public string Content { get; set; } = default!; + public string ScriptType { get; set; } = default!; - public static AgentCodeDocument ToMongoModel(AgentCodeScript script) + public static AgentCodeScriptDocument ToMongoModel(AgentCodeScript script) { - return new AgentCodeDocument + return new AgentCodeScriptDocument { Id = script.Id, AgentId = script.AgentId, Name = script.Name, - Content = script.Content + Content = script.Content, + ScriptType = script.ScriptType }; } - public static AgentCodeScript ToDomainModel(AgentCodeDocument script) + public static AgentCodeScript ToDomainModel(AgentCodeScriptDocument script) { return new AgentCodeScript { Id = script.Id, AgentId = script.AgentId, Name = script.Name, - Content = script.Content + Content = script.Content, + ScriptType = script.ScriptType }; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs index 6f171cb3..a0d0ccff 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs @@ -160,8 +160,8 @@ public class MongoDbContext public IMongoCollection AgentTasks => CreateAgentTaskIndex(); - public IMongoCollection AgentCodes - => GetCollectionOrCreate("AgentCodes"); + public IMongoCollection AgentCodeScripts + => GetCollectionOrCreate("AgentCodeScripts"); public IMongoCollection Conversations => CreateConversationIndex(); diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 3d085c70..76cadd32 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -594,7 +594,7 @@ public partial class MongoRepository _dc.UserAgents.DeleteMany(Builders.Filter.Empty); _dc.RoleAgents.DeleteMany(Builders.Filter.Empty); _dc.AgentTasks.DeleteMany(Builders.Filter.Empty); - _dc.AgentCodes.DeleteMany(Builders.Filter.Empty); + _dc.AgentCodeScripts.DeleteMany(Builders.Filter.Empty); _dc.Agents.DeleteMany(Builders.Filter.Empty); return true; } @@ -614,12 +614,12 @@ public partial class MongoRepository var userAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); var roleAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); var agentTaskFilter = Builders.Filter.Eq(x => x.AgentId, agentId); - var agentCodeFilter = Builders.Filter.Eq(x => x.AgentId, agentId); + var agentCodeFilter = Builders.Filter.Eq(x => x.AgentId, agentId); _dc.UserAgents.DeleteMany(userAgentFilter); _dc.RoleAgents.DeleteMany(roleAgentFilter); _dc.AgentTasks.DeleteMany(agentTaskFilter); - _dc.AgentCodes.DeleteMany(agentCodeFilter); + _dc.AgentCodeScripts.DeleteMany(agentCodeFilter); _dc.Agents.DeleteOne(agentFilter); return true; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCode.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCode.cs deleted file mode 100644 index 35d67355..00000000 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCode.cs +++ /dev/null @@ -1,113 +0,0 @@ -using BotSharp.Abstraction.Agents.Models; - -namespace BotSharp.Plugin.MongoStorage.Repository; - -public partial class MongoRepository -{ - #region Code - public List GetAgentCodeScripts(string agentId, List? scriptNames = null) - { - if (string.IsNullOrWhiteSpace(agentId)) - { - return []; - } - - var builder = Builders.Filter; - var filters = new List>() - { - builder.Eq(x => x.AgentId, agentId) - }; - - if (!scriptNames.IsNullOrEmpty()) - { - filters.Add(builder.In(x => x.Name, scriptNames)); - } - - var found = _dc.AgentCodes.Find(builder.And(filters)).ToList(); - return found.Select(x => AgentCodeDocument.ToDomainModel(x)).ToList(); - } - - public string? GetAgentCodeScript(string agentId, string scriptName) - { - if (string.IsNullOrWhiteSpace(agentId) - || string.IsNullOrWhiteSpace(scriptName)) - { - return null; - } - - var builder = Builders.Filter; - var filters = new List>() - { - builder.Eq(x => x.AgentId, agentId), - builder.Eq(x => x.Name, scriptName) - }; - - var found = _dc.AgentCodes.Find(builder.And(filters)).FirstOrDefault(); - return found?.Content; - } - - public bool UpdateAgentCodeScripts(string agentId, List scripts) - { - if (string.IsNullOrWhiteSpace(agentId) || scripts.IsNullOrEmpty()) - { - return false; - } - - var builder = Builders.Filter; - var ops = scripts.Where(x => !string.IsNullOrWhiteSpace(x.Name)) - .Select(x => new UpdateOneModel( - builder.And(new List> - { - builder.Eq(y => y.AgentId, agentId), - builder.Eq(y => y.Name, x.Name) - }), - Builders.Update.Set(y => y.Content, x.Content) - )) - .ToList(); - - var result = _dc.AgentCodes.BulkWrite(ops, new BulkWriteOptions { IsOrdered = false }); - return result.ModifiedCount > 0 || result.MatchedCount > 0; - } - - public bool BulkInsertAgentCodeScripts(string agentId, List scripts) - { - if (string.IsNullOrWhiteSpace(agentId) || scripts.IsNullOrEmpty()) - { - return false; - } - - var docs = scripts.Select(x => - { - var script = AgentCodeDocument.ToMongoModel(x); - script.AgentId = agentId; - script.Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(); - return script; - }).ToList(); - - _dc.AgentCodes.InsertMany(docs); - return true; - } - - public bool DeleteAgentCodeScripts(string agentId, List? scriptNames) - { - if (string.IsNullOrWhiteSpace(agentId)) - { - return false; - } - - var filterDef = Builders.Filter.Empty; - if (scriptNames != null) - { - var builder = Builders.Filter; - var filters = new List> - { - builder.In(x => x.Name, scriptNames) - }; - filterDef = builder.And(filters); - } - - var deleted = _dc.AgentCodes.DeleteMany(filterDef); - return deleted.DeletedCount > 0; - } - #endregion -} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs new file mode 100644 index 00000000..8e81b293 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs @@ -0,0 +1,129 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Repositories.Filters; + +namespace BotSharp.Plugin.MongoStorage.Repository; + +public partial class MongoRepository +{ + #region Code + public List GetAgentCodeScripts(string agentId, AgentCodeScriptFilter? filter = null) + { + if (string.IsNullOrWhiteSpace(agentId)) + { + return []; + } + + filter ??= AgentCodeScriptFilter.Empty(); + + var builder = Builders.Filter; + var filters = new List>() + { + builder.Eq(x => x.AgentId, agentId) + }; + + if (!filter.ScriptNames.IsNullOrEmpty()) + { + filters.Add(builder.In(x => x.Name, filter.ScriptNames)); + } + if (!filter.ScriptTypes.IsNullOrEmpty()) + { + filters.Add(builder.In(x => x.ScriptType, filter.ScriptTypes)); + } + + var found = _dc.AgentCodeScripts.Find(builder.And(filters)).ToList(); + return found.Select(x => AgentCodeScriptDocument.ToDomainModel(x)).ToList(); + } + + public string? GetAgentCodeScript(string agentId, string scriptName, string scriptType = AgentCodeScriptType.Src) + { + if (string.IsNullOrWhiteSpace(agentId) + || string.IsNullOrWhiteSpace(scriptName) + || string.IsNullOrWhiteSpace(scriptType)) + { + return null; + } + + var builder = Builders.Filter; + var filters = new List>() + { + builder.Eq(x => x.AgentId, agentId), + builder.Eq(x => x.Name, scriptName), + builder.Eq(x => x.ScriptType, scriptType) + }; + + var found = _dc.AgentCodeScripts.Find(builder.And(filters)).FirstOrDefault(); + return found?.Content; + } + + public bool UpdateAgentCodeScripts(string agentId, List scripts) + { + if (string.IsNullOrWhiteSpace(agentId) || scripts.IsNullOrEmpty()) + { + return false; + } + + var builder = Builders.Filter; + var ops = scripts.Where(x => !string.IsNullOrWhiteSpace(x.Name)) + .Select(x => new UpdateOneModel( + builder.And(new List> + { + builder.Eq(y => y.AgentId, agentId), + builder.Eq(y => y.Name, x.Name), + builder.Eq(y => y.ScriptType, x.ScriptType) + }), + Builders.Update.Set(y => y.Content, x.Content) + )) + .ToList(); + + var result = _dc.AgentCodeScripts.BulkWrite(ops, new BulkWriteOptions { IsOrdered = false }); + return result.ModifiedCount > 0 || result.MatchedCount > 0; + } + + public bool BulkInsertAgentCodeScripts(string agentId, List scripts) + { + if (string.IsNullOrWhiteSpace(agentId) || scripts.IsNullOrEmpty()) + { + return false; + } + + var docs = scripts.Select(x => + { + var script = AgentCodeScriptDocument.ToMongoModel(x); + script.AgentId = agentId; + script.Id = x.Id.IfNullOrEmptyAs(Guid.NewGuid().ToString()); + return script; + }).ToList(); + + _dc.AgentCodeScripts.InsertMany(docs); + return true; + } + + public bool DeleteAgentCodeScripts(string agentId, List? scripts = null) + { + if (string.IsNullOrWhiteSpace(agentId)) + { + return false; + } + + DeleteResult deleted; + if (scripts != null) + { + var scriptPaths = scripts.Select(x => x.CodePath); + var exprFilter = new BsonDocument("$expr", new BsonDocument("$in", new BsonArray + { + new BsonDocument("$concat", new BsonArray { "$ScriptType", "/", "$Name" }), + new BsonArray(scriptPaths) + })); + + var filterDef = new BsonDocumentFilterDefinition(exprFilter); + deleted = _dc.AgentCodeScripts.DeleteMany(filterDef); + } + else + { + deleted = _dc.AgentCodeScripts.DeleteMany(Builders.Filter.Empty); + } + + return deleted.DeletedCount > 0; + } + #endregion +}