diff --git a/BotSharp.sln b/BotSharp.sln index 53eb3da9..c70f6c74 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -127,12 +127,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Rules", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.DeepSeekAI", "src\Plugins\BotSharp.Plugin.DeepSeekAI\BotSharp.Plugin.DeepSeekAI.csproj", "{AF329442-B48E-4B48-A18A-1C869D1BA6F5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MCP", "src\Infrastructure\BotSharp.MCP\BotSharp.MCP.csproj", "{684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Realtime", "src\Infrastructure\BotSharp.Core.Realtime\BotSharp.Core.Realtime.csproj", "{781F1465-365C-0F22-1775-25025DAFA4C7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.PizzaBot.MCPServer", "tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj", "{8D2AD45F-836A-516F-DE6A-71443CEBB18A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.MCP", "src\Infrastructure\BotSharp.Core.MCP\BotSharp.Core.MCP.csproj", "{534D1DD5-9D62-115C-F230-47080D76CE52}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -525,14 +525,6 @@ Global {AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|Any CPU.Build.0 = Release|Any CPU {AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.ActiveCfg = Release|Any CPU {AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.Build.0 = Release|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|x64.ActiveCfg = Debug|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Debug|x64.Build.0 = Debug|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|Any CPU.Build.0 = Release|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|x64.ActiveCfg = Release|Any CPU - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C}.Release|x64.Build.0 = Release|Any CPU {781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU {781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -549,6 +541,14 @@ Global {8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|Any CPU.Build.0 = Release|Any CPU {8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|x64.ActiveCfg = Release|Any CPU {8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|x64.Build.0 = Release|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|x64.ActiveCfg = Debug|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|x64.Build.0 = Debug|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|Any CPU.Build.0 = Release|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|x64.ActiveCfg = Release|Any CPU + {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -610,9 +610,9 @@ Global {F812BAAE-5A7D-4DF7-8E71-70696B51C61F} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {AFD64412-4D6A-452E-82A2-79E5D8842E29} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA} - {684781D5-3DD4-6A0B-B53F-0A362CD6BB0C} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {781F1465-365C-0F22-1775-25025DAFA4C7} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {8D2AD45F-836A-516F-DE6A-71443CEBB18A} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} + {534D1DD5-9D62-115C-F230-47080D76CE52} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/Directory.Packages.props b/Directory.Packages.props index f70cb1ff..71e9c387 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,8 +19,7 @@ - - + @@ -110,7 +109,7 @@ - + diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs index 1103f2f5..5c8ad126 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs @@ -60,7 +60,7 @@ public abstract class AgentHookBase : IAgentHook } - public virtual void OnAgentMCPToolLoaded(Agent agent) + public virtual void OnAgentMcpToolLoaded(Agent agent) { } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs index 624616d7..c37688ed 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs @@ -27,7 +27,7 @@ public interface IAgentHook void OnAgentUtilityLoaded(Agent agent); - void OnAgentMCPToolLoaded(Agent agent); + void OnAgentMcpToolLoaded(Agent agent); /// /// Triggered when agent is loaded completely. diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index fc28e571..a880a0c6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -107,7 +107,7 @@ public class Agent /// /// Agent MCP Tools /// - public List McpTools { get; set; } = new(); + public List McpTools { get; set; } = new(); /// /// Agent rules @@ -305,7 +305,7 @@ public class Agent return this; } - public Agent SetMcps(List mcps) + public Agent SetMcpTools(List? mcps) { McpTools = mcps ?? []; return this; diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs index a555a7e5..88e2d4b0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/MCPTool.cs @@ -1,21 +1,26 @@ namespace BotSharp.Abstraction.Agents.Models; -public class MCPTool +public class McpTool { + public string Name { get; set; } public string ServerId { get; set; } - public bool Disabled { get; set; } + public IEnumerable Functions { get; set; } = []; - public IEnumerable Functions { get; set; } = []; - - public MCPTool() + public McpTool() { } - public MCPTool( - IEnumerable? functions = null) + public McpTool( + string name, + string serverId, + bool disabled = false, + IEnumerable? functions = null) { + Name = name; + ServerId = serverId; + Disabled = disabled; Functions = functions ?? []; } @@ -26,12 +31,12 @@ public class MCPTool } -public class MCPFunction +public class McpFunction { public string Name { get; set; } - public MCPFunction(string name) + public McpFunction(string name) { - this.Name = name; + Name = name; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs index abdfd67b..4efa1448 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs @@ -7,5 +7,7 @@ public enum BroswerActionEnum Typing = 3, Hover = 4, Scroll = 5, - DragAndDrop = 6 + DragAndDrop = 6, + DropDown = 7, + FileUpload = 8 } diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs index 8885feb3..869186b1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs @@ -12,6 +12,8 @@ public class ElementActionArgs [JsonPropertyName("content")] public string? Content { get; set; } + [JsonPropertyName("file_urls")] + public string[] FileUrl { get; set; } public ElementPosition? Position { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IFileInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IFileInstructService.cs index 9ec3c8ed..0b5b41ab 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/IFileInstructService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/IFileInstructService.cs @@ -1,13 +1,15 @@ +using BotSharp.Abstraction.Instructs.Models; + namespace BotSharp.Abstraction.Files; public interface IFileInstructService { #region Image - Task ReadImages(string? provider, string? model, string text, IEnumerable images, string? agentId = null); - Task GenerateImage(string? provider, string? model, string text, string? agentId = null); - Task VaryImage(string? provider, string? model, InstructFileModel image, string? agentId = null); - Task EditImage(string? provider, string? model, string text, InstructFileModel image, string? agentId = null); - Task EditImage(string? provider, string? model, string text, InstructFileModel image, InstructFileModel mask, string? agentId = null); + Task ReadImages(string text, IEnumerable images, InstructOptions? options = null); + Task GenerateImage(string text, InstructOptions? options = null); + Task VaryImage(InstructFileModel image, InstructOptions? options = null); + Task EditImage(string text, InstructFileModel image, InstructOptions? options = null); + Task EditImage(string text, InstructFileModel image, InstructFileModel mask, InstructOptions? options = null); #endregion #region Pdf @@ -17,11 +19,11 @@ public interface IFileInstructService /// /// Pdf files /// - Task ReadPdf(string? provider, string? model, string? modelId, string prompt, List files, string? agentId = null); + Task ReadPdf(string text, List files, InstructOptions? options = null); #endregion #region Audio - Task SpeechToText(string? provider, string? model, InstructFileModel audio, string? text = null); + Task SpeechToText(InstructFileModel audio, string? text = null, InstructOptions? options = null); #endregion #region Select file diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs index e6a659cc..7dce2423 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs @@ -12,7 +12,8 @@ public interface IInstructService /// Template name /// System prompt /// - Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null); + Task Execute(string agentId, RoleDialogModel message, + string? templateName = null, string? instruction = null, IEnumerable? files = null); /// /// A generic way to execute completion by using specified instruction or template @@ -22,5 +23,5 @@ public interface IInstructService /// Agent id /// Llm Provider, model, message, prompt data /// - Task Instruct(string instruction, string agentId, InstructOptions options) where T : class; + Task Instruct(string text, InstructOptions? options = null) where T : class; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs index 46c6c900..ece08b0e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs @@ -5,23 +5,28 @@ public class InstructOptions /// /// Llm provider /// - public string Provider { get; set; } = null!; + public string? Provider { get; set; } /// /// Llm model /// - public string Model { get; set; } = null!; + public string? Model { get; set; } + + /// + /// Agent + /// + public string? AgentId { get; set; } + + /// + /// Agent template name + /// + public string? TemplateName { get; set; } /// /// Conversation id. When this field is not null, it will get dialogs from conversation. /// public string? ConversationId { get; set; } - /// - /// The single message. It can be append to the whole dialogs or sent alone. - /// - public string? Message { get; set; } - /// /// Data to fill in prompt /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs index 70564846..1a08fc5b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs @@ -3,4 +3,6 @@ namespace BotSharp.Abstraction.Plugins; public class PluginSettings { public string[] Assemblies { get; set; } = new string[0]; + + public string[] ExcludedFunctions { get; set; } = new string[0]; } diff --git a/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj b/src/Infrastructure/BotSharp.Core.MCP/BotSharp.Core.MCP.csproj similarity index 90% rename from src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj rename to src/Infrastructure/BotSharp.Core.MCP/BotSharp.Core.MCP.csproj index 94aba37c..5f513dd9 100644 --- a/src/Infrastructure/BotSharp.MCP/BotSharp.MCP.csproj +++ b/src/Infrastructure/BotSharp.Core.MCP/BotSharp.Core.MCP.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Infrastructure/BotSharp.Core.MCP/BotSharpMCPExtensions.cs b/src/Infrastructure/BotSharp.Core.MCP/BotSharpMCPExtensions.cs new file mode 100644 index 00000000..0f6c3a8f --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.MCP/BotSharpMCPExtensions.cs @@ -0,0 +1,58 @@ +using BotSharp.Core.MCP.Functions; +using BotSharp.Core.MCP.Settings; +using BotSharp.Core.MCP.Hooks; +using Microsoft.Extensions.Configuration; +using ModelContextProtocol.Configuration; +using ModelContextProtocol.Client; +using BotSharp.Core.MCP.Managers; + +namespace BotSharp.Core.MCP; + +public static class BotSharpMcpExtensions +{ + /// + /// Add mcp + /// + /// + /// + /// + public static IServiceCollection AddBotSharpMCP(this IServiceCollection services, IConfiguration config) + { + var settings = config.GetSection("MCPSettings").Get(); + services.AddScoped(provider => { return settings; }); + if (settings != null && !settings.McpServerConfigs.IsNullOrEmpty()) + { + var clientManager = new McpClientManager(settings); + services.AddSingleton(clientManager); + + foreach (var server in settings.McpServerConfigs) + { + RegisterFunctionCall(services, server, clientManager) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + } + + // Register hooks + services.AddScoped(); + } + return services; + } + + private static async Task RegisterFunctionCall(IServiceCollection services, McpServerConfig server, McpClientManager clientManager) + { + var client = await clientManager.GetMcpClientAsync(server.Id); + var tools = await client.ListToolsAsync(); + + foreach (var tool in tools) + { + services.AddScoped(provider => { return tool; }); + + services.AddScoped(provider => + { + var funcTool = new McpToolAdapter(provider, tool, clientManager); + return funcTool; + }); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.MCP/Functions/McpToolAdapter.cs b/src/Infrastructure/BotSharp.Core.MCP/Functions/McpToolAdapter.cs similarity index 77% rename from src/Infrastructure/BotSharp.MCP/Functions/McpToolAdapter.cs rename to src/Infrastructure/BotSharp.Core.MCP/Functions/McpToolAdapter.cs index 60cfb531..2d4fdc70 100644 --- a/src/Infrastructure/BotSharp.MCP/Functions/McpToolAdapter.cs +++ b/src/Infrastructure/BotSharp.Core.MCP/Functions/McpToolAdapter.cs @@ -1,27 +1,18 @@ -using BotSharp.Abstraction.Agents; -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Utilities; -using Microsoft.Extensions.DependencyInjection; -using ModelContextProtocol.Client; -using ModelContextProtocol.Protocol.Types; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.Json; -using System.Threading.Tasks; +using BotSharp.Core.MCP.Managers; +using ModelContextProtocol.Client; -namespace BotSharp.Core.Mcp.Functions; +namespace BotSharp.Core.MCP.Functions; public class McpToolAdapter : IFunctionCallback { - private readonly Tool _tool; - private readonly MCPClientManager _clientManager; - private readonly IServiceProvider _serviceProvider; + private readonly McpClientTool _tool; + private readonly McpClientManager _clientManager; + private readonly IServiceProvider _services; - public McpToolAdapter(IServiceProvider provider, Tool tool, MCPClientManager client) + public McpToolAdapter(IServiceProvider services, McpClientTool tool, McpClientManager client) { - _serviceProvider = provider ?? throw new ArgumentNullException(nameof(provider)); + _services = services ?? throw new ArgumentNullException(nameof(services)); _tool = tool ?? throw new ArgumentNullException(nameof(tool)); _clientManager = client ?? throw new ArgumentNullException(nameof(client)); } @@ -33,21 +24,18 @@ public class McpToolAdapter : IFunctionCallback // Convert arguments to dictionary format expected by mcpdotnet Dictionary argDict = JsonToDictionary(message.FunctionArgs); var currentAgentId = message.CurrentAgentId; - var agentService = _serviceProvider.GetRequiredService(); + var agentService = _services.GetRequiredService(); var agent = await agentService.LoadAgent(currentAgentId); var serverId = agent.McpTools.Where(t => t.Functions.Any(f => f.Name == Name)).FirstOrDefault().ServerId; var client = await _clientManager.GetMcpClientAsync(serverId); + // Call the tool through mcpdotnet - var result = await client.CallToolAsync( - _tool.Name, - argDict.Count == 0 ? new() : argDict - ); + var result = await client.CallToolAsync(_tool.Name, argDict.IsNullOrEmpty() ? new() : argDict); // Extract the text content from the result - var json = string.Join("\n", result.Content - .Where(c => c.Type == "text") - .Select(c => c.Text)); + var json = string.Join("\n", result.Content.Where(c => c.Type == "text").Select(c => c.Text)); + message.Content = json; message.Data = json.JsonContent(); return true; diff --git a/src/Infrastructure/BotSharp.MCP/AIFunctionUtilities.cs b/src/Infrastructure/BotSharp.Core.MCP/Helpers/AiFunctionHelper.cs similarity index 57% rename from src/Infrastructure/BotSharp.MCP/AIFunctionUtilities.cs rename to src/Infrastructure/BotSharp.Core.MCP/Helpers/AiFunctionHelper.cs index 8a409f67..d6f1cb5b 100644 --- a/src/Infrastructure/BotSharp.MCP/AIFunctionUtilities.cs +++ b/src/Infrastructure/BotSharp.Core.MCP/Helpers/AiFunctionHelper.cs @@ -1,24 +1,21 @@ -using BotSharp.Abstraction.Functions.Models; -using ModelContextProtocol.Protocol.Types; -using System; -using System.Collections.Generic; using System.Text.Json; +using ModelContextProtocol.Client; -namespace BotSharp.Core.MCP; +namespace BotSharp.Core.MCP.Helpers; -internal static class AIFunctionUtilities +internal static class AiFunctionHelper { - public static FunctionDef MapToFunctionDef(Tool tool) + public static FunctionDef MapToFunctionDef(McpClientTool tool) { if (tool == null) { throw new ArgumentNullException(nameof(tool)); } - var properties = tool.InputSchema.GetProperty("properties"); - var required = tool.InputSchema.GetProperty("required"); + var properties = tool.JsonSchema.GetProperty("properties"); + var required = tool.JsonSchema.GetProperty("required"); - FunctionDef funDef = new FunctionDef + var funDef = new FunctionDef { Name = tool.Name, Description = tool.Description ?? string.Empty, diff --git a/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs b/src/Infrastructure/BotSharp.Core.MCP/Hooks/MCPToolAgentHook.cs similarity index 53% rename from src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs rename to src/Infrastructure/BotSharp.Core.MCP/Hooks/MCPToolAgentHook.cs index 84039cfb..40a59e2b 100644 --- a/src/Infrastructure/BotSharp.MCP/Hooks/MCPToolAgentHook.cs +++ b/src/Infrastructure/BotSharp.Core.MCP/Hooks/MCPToolAgentHook.cs @@ -1,41 +1,32 @@ -using BotSharp.Abstraction.Agents; -using BotSharp.Abstraction.Agents.Enums; -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Agents.Settings; -using BotSharp.Abstraction.Conversations; -using BotSharp.Abstraction.Functions.Models; -using BotSharp.Core.Mcp; -using BotSharp.Core.MCP; -using Microsoft.Extensions.DependencyInjection; +using BotSharp.Core.MCP.Helpers; +using BotSharp.Core.MCP.Managers; using ModelContextProtocol.Client; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -namespace BotSharp.MCP.Hooks; +namespace BotSharp.Core.MCP.Hooks; -public class MCPToolAgentHook : AgentHookBase +public class McpToolAgentHook : AgentHookBase { public override string SelfId => string.Empty; - public MCPToolAgentHook(IServiceProvider services, AgentSettings settings) + public McpToolAgentHook(IServiceProvider services, AgentSettings settings) : base(services, settings) { } - public override void OnAgentMCPToolLoaded(Agent agent) + public override void OnAgentMcpToolLoaded(Agent agent) { if (agent.Type == AgentType.Routing) + { return; + } + var conv = _services.GetRequiredService(); var isConvMode = conv.IsConversationMode(); if (!isConvMode) return; agent.SecondaryFunctions ??= []; - var functions = GetMCPContent(agent).Result; - + var functions = GetMcpContent(agent).Result; foreach (var fn in functions) { if (!agent.SecondaryFunctions.Any(x => x.Name.Equals(fn.Name, StringComparison.OrdinalIgnoreCase))) @@ -45,21 +36,21 @@ public class MCPToolAgentHook : AgentHookBase } } - private async Task> GetMCPContent(Agent agent) + private async Task> GetMcpContent(Agent agent) { - List functionDefs = new List(); - var mcpClientManager = _services.GetRequiredService(); - var mcps = agent.McpTools; + var functionDefs = new List(); + var mcpClientManager = _services.GetRequiredService(); + var mcps = agent.McpTools.Where(x => !x.Disabled); foreach (var item in mcps) { var mcpClient = await mcpClientManager.GetMcpClientAsync(item.ServerId); if (mcpClient != null) { - var tools = await mcpClient.ListToolsAsync().ToListAsync(); + var tools = await mcpClient.ListToolsAsync(); var toolnames = item.Functions.Select(x => x.Name).ToList(); foreach (var tool in tools.Where(x => toolnames.Contains(x.Name, StringComparer.OrdinalIgnoreCase))) { - var funDef = AIFunctionUtilities.MapToFunctionDef(tool); + var funDef = AiFunctionHelper.MapToFunctionDef(tool); functionDefs.Add(funDef); } } diff --git a/src/Infrastructure/BotSharp.Core.MCP/Managers/McpClientManager.cs b/src/Infrastructure/BotSharp.Core.MCP/Managers/McpClientManager.cs new file mode 100644 index 00000000..523e4b00 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.MCP/Managers/McpClientManager.cs @@ -0,0 +1,27 @@ +using BotSharp.Core.MCP.Settings; +using ModelContextProtocol.Client; + +namespace BotSharp.Core.MCP.Managers; + +public class McpClientManager : IDisposable +{ + + private readonly McpSettings mcpSettings; + + public McpClientManager(McpSettings settings) + { + mcpSettings = settings; + } + + public async Task GetMcpClientAsync(string serverId) + { + return await McpClientFactory.CreateAsync( + mcpSettings.McpServerConfigs.Where(x=> x.Name == serverId).First(), + mcpSettings.McpClientOptions); + } + + public void Dispose() + { + + } +} diff --git a/src/Infrastructure/BotSharp.Core.MCP/Settings/MCPSettings.cs b/src/Infrastructure/BotSharp.Core.MCP/Settings/MCPSettings.cs new file mode 100644 index 00000000..09884f2b --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.MCP/Settings/MCPSettings.cs @@ -0,0 +1,11 @@ +using ModelContextProtocol.Client; +using ModelContextProtocol.Configuration; + +namespace BotSharp.Core.MCP.Settings; + +public class McpSettings +{ + public McpClientOptions McpClientOptions { get; set; } + public List McpServerConfigs { get; set; } = new(); + +} diff --git a/src/Infrastructure/BotSharp.Core.MCP/Using.cs b/src/Infrastructure/BotSharp.Core.MCP/Using.cs new file mode 100644 index 00000000..a404f960 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.MCP/Using.cs @@ -0,0 +1,16 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading.Tasks; +global using Microsoft.Extensions.DependencyInjection; + +global using BotSharp.Abstraction.Agents; +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Agents.Settings; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Functions; +global using BotSharp.Abstraction.Functions.Models; +global using BotSharp.Abstraction.Utilities; + diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index c8f97206..d3ceea8b 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -72,9 +72,9 @@ public partial class AgentService hook.OnAgentUtilityLoaded(agent); } - if(agent.McpTools != null && agent.McpTools.Count >0) + if(!agent.McpTools.IsNullOrEmpty()) { - hook.OnAgentMCPToolLoaded(agent); + hook.OnAgentMcpToolLoaded(agent); } hook.OnAgentLoaded(agent); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs index 87da12b2..288a7ca1 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs @@ -119,7 +119,7 @@ public partial class AgentService var conv = _services.GetRequiredService(); var render = _services.GetRequiredService(); - var template = agent.Templates.First(x => x.Name == templateName).Content; + var template = agent.Templates.FirstOrDefault(x => x.Name == templateName)?.Content ?? string.Empty; // update states foreach (var t in conv.States.GetStates()) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index dd8743d4..387cdfd5 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -107,7 +107,7 @@ public partial class AgentService .SetResponses(foundAgent.Responses) .SetSamples(foundAgent.Samples) .SetUtilities(foundAgent.Utilities) - .SetMcps(foundAgent.McpTools) + .SetMcpTools(foundAgent.McpTools) .SetKnowledgeBases(foundAgent.KnowledgeBases) .SetRules(foundAgent.Rules) .SetLlmConfig(foundAgent.LlmConfig); diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 6b10ad81..cd85e358 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -135,6 +135,8 @@ public static class BotSharpCoreExtensions { var pluginSettings = new PluginSettings(); config.Bind("PluginLoader", pluginSettings); + var excludedFunctions = pluginSettings.ExcludedFunctions ?? []; + services.AddScoped(provider => { var settingService = provider.GetRequiredService(); @@ -161,8 +163,9 @@ public static class BotSharpCoreExtensions // Register function callback var functions = assembly.GetTypes() - .Where(x => x.IsClass) - .Where(x => x.GetInterface(nameof(IFunctionCallback)) != null) + .Where(x => x.IsClass + && x.GetInterface(nameof(IFunctionCallback)) != null + && !excludedFunctions.Contains(x.Name)) .ToArray(); foreach (var function in functions) diff --git a/src/Infrastructure/BotSharp.Core/Evaluations/Services/EvaluatingService.Evaluate.cs b/src/Infrastructure/BotSharp.Core/Evaluations/Services/EvaluatingService.Evaluate.cs index c122d552..8cdbc0da 100644 --- a/src/Infrastructure/BotSharp.Core/Evaluations/Services/EvaluatingService.Evaluate.cs +++ b/src/Infrastructure/BotSharp.Core/Evaluations/Services/EvaluatingService.Evaluate.cs @@ -62,8 +62,6 @@ public partial class EvaluatingService var query = "Please see yourself as a user and follow the instruction to generate a message."; var targetAgentId = request.AgentId; - var evaluator = await agentService.GetAgent(BuiltInAgentId.Evaluator); - var simulatorPrompt = evaluator.Templates.FirstOrDefault(x => x.Name == "instruction.simulator")?.Content ?? string.Empty; while (true) { @@ -77,12 +75,13 @@ public partial class EvaluatingService count++; - var result = await instructService.Instruct(simulatorPrompt, BuiltInAgentId.Evaluator, + var result = await instructService.Instruct(query, new InstructOptions { Provider = request.Provider, Model = request.Model, - Message = query, + AgentId = BuiltInAgentId.Evaluator, + TemplateName = "instruction.simulator", Data = new Dictionary { { "ref_conversation", refDialogs }, @@ -122,16 +121,14 @@ public partial class EvaluatingService var curDialogs = storage.GetDialogs(curConversationId); var curDialogContents = GetConversationContent(curDialogs); - var evaluator = await agentService.GetAgent(BuiltInAgentId.Evaluator); - var metricPrompt = evaluator.Templates.FirstOrDefault(x => x.Name == "instruction.metrics")?.Content ?? string.Empty; var query = "Please follow the instruction for evaluation."; - - var result = await instructService.Instruct(metricPrompt, BuiltInAgentId.Evaluator, + var result = await instructService.Instruct(query, new InstructOptions { Provider = request.Provider, Model = request.Model, - Message = query, + AgentId = BuiltInAgentId.Evaluator, + TemplateName = "instruction.metrics", Data = new Dictionary { { "ref_conversation", refDialogs }, diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs index 0b8f25cd..d61b6165 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Audio.cs @@ -1,19 +1,26 @@ +using BotSharp.Abstraction.Instructs.Models; using System.IO; namespace BotSharp.Core.Files.Services; public partial class FileInstructService { - public async Task SpeechToText(string? provider, string? model, InstructFileModel audio, string? text = null) + public async Task SpeechToText(InstructFileModel audio, string? text = null, InstructOptions? options = null) { - var completion = CompletionProvider.GetAudioTranscriber(_services, provider: provider, model: model); + if (string.IsNullOrWhiteSpace(text)) + { + var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); + text = await GetAgentTemplate(innerAgentId, options?.TemplateName); + } + + var completion = CompletionProvider.GetAudioTranscriber(_services, provider: options?.Provider, model: options?.Model); var audioBytes = await DownloadFile(audio); using var stream = new MemoryStream(); stream.Write(audioBytes, 0, audioBytes.Length); stream.Position = 0; var fileName = $"{audio.FileName ?? "audio"}.{audio.FileExtension ?? "wav"}"; - var content = await completion.TranscriptTextAsync(stream, fileName, text); + var content = await completion.TranscriptTextAsync(stream, fileName, text ?? string.Empty); stream.Close(); return content; } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs index 2976ab47..2f70bd22 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs @@ -6,25 +6,28 @@ namespace BotSharp.Core.Files.Services; public partial class FileInstructService { - public async Task ReadImages(string? provider, string? model, string text, IEnumerable images, string? agentId = null) + public async Task ReadImages(string text, IEnumerable images, InstructOptions? options = null) { - var innerAgentId = agentId ?? Guid.Empty.ToString(); - var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai", model: model ?? "gpt-4o", multiModal: true); + var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); + var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + + var completion = CompletionProvider.GetChatCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "gpt-4o", multiModal: true); var message = await completion.GetChatCompletions(new Agent() { Id = innerAgentId, + Instruction = instruction }, new List { new RoleDialogModel(AgentRole.User, text) { - Files = images?.Select(x => new BotSharpFile { FileUrl = x.FileUrl, FileData = x.FileData }).ToList() ?? new List() + Files = images?.Select(x => new BotSharpFile { FileUrl = x.FileUrl, FileData = x.FileData }).ToList() ?? [] } }); var hooks = _services.GetServices(); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) { continue; } @@ -34,7 +37,9 @@ public partial class FileInstructService AgentId = innerAgentId, Provider = completion.Provider, Model = completion.Model, + TemplateName = options?.TemplateName, UserMessage = text, + SystemInstruction = instruction, CompletionText = message.Content }); } @@ -42,19 +47,22 @@ public partial class FileInstructService return message.Content; } - public async Task GenerateImage(string? provider, string? model, string text, string? agentId = null) + public async Task GenerateImage(string text, InstructOptions? options = null) { - var innerAgentId = agentId ?? Guid.Empty.ToString(); - var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-3"); + var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); + var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + + var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "dall-e-3"); var message = await completion.GetImageGeneration(new Agent() { Id = innerAgentId, + Instruction = instruction }, new RoleDialogModel(AgentRole.User, text)); var hooks = _services.GetServices(); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) { continue; } @@ -64,7 +72,9 @@ public partial class FileInstructService AgentId = innerAgentId, Provider = completion.Provider, Model = completion.Model, + TemplateName = options?.TemplateName, UserMessage = text, + SystemInstruction = instruction, CompletionText = message.Content }); } @@ -72,15 +82,17 @@ public partial class FileInstructService return message; } - public async Task VaryImage(string? provider, string? model, InstructFileModel image, string? agentId = null) + public async Task VaryImage(InstructFileModel image, InstructOptions? options = null) { if (string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData)) { throw new ArgumentException($"Cannot find image url or data!"); } - var innerAgentId = agentId ?? Guid.Empty.ToString(); - var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2"); + var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); + var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + + var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "dall-e-2"); var bytes = await DownloadFile(image); using var stream = new MemoryStream(); stream.Write(bytes, 0, bytes.Length); @@ -89,7 +101,8 @@ public partial class FileInstructService var fileName = $"{image.FileName ?? "image"}.{image.FileExtension ?? "png"}"; var message = await completion.GetImageVariation(new Agent() { - Id = innerAgentId + Id = innerAgentId, + Instruction = instruction }, new RoleDialogModel(AgentRole.User, string.Empty), stream, fileName); stream.Close(); @@ -97,7 +110,7 @@ public partial class FileInstructService var hooks = _services.GetServices(); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) { continue; } @@ -107,7 +120,9 @@ public partial class FileInstructService AgentId = innerAgentId, Provider = completion.Provider, Model = completion.Model, + TemplateName = options?.TemplateName, UserMessage = string.Empty, + SystemInstruction = instruction, CompletionText = message.Content }); } @@ -115,15 +130,17 @@ public partial class FileInstructService return message; } - public async Task EditImage(string? provider, string? model, string text, InstructFileModel image, string? agentId = null) + public async Task EditImage(string text, InstructFileModel image, InstructOptions? options = null) { if (string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData)) { throw new ArgumentException($"Cannot find image url or data!"); } - var innerAgentId = agentId ?? Guid.Empty.ToString(); - var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2"); + var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); + var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + + var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "dall-e-2"); var bytes = await DownloadFile(image); using var stream = new MemoryStream(); stream.Write(bytes, 0, bytes.Length); @@ -132,7 +149,8 @@ public partial class FileInstructService var fileName = $"{image.FileName ?? "image"}.{image.FileExtension ?? "png"}"; var message = await completion.GetImageEdits(new Agent() { - Id = innerAgentId + Id = innerAgentId, + Instruction = instruction }, new RoleDialogModel(AgentRole.User, text), stream, fileName); stream.Close(); @@ -140,7 +158,7 @@ public partial class FileInstructService var hooks = _services.GetServices(); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) { continue; } @@ -150,7 +168,9 @@ public partial class FileInstructService AgentId = innerAgentId, Provider = completion.Provider, Model = completion.Model, + TemplateName = options?.TemplateName, UserMessage = text, + SystemInstruction = instruction, CompletionText = message.Content }); } @@ -158,7 +178,7 @@ public partial class FileInstructService return message; } - public async Task EditImage(string? provider, string? model, string text, InstructFileModel image, InstructFileModel mask, string? agentId = null) + public async Task EditImage(string text, InstructFileModel image, InstructFileModel mask, InstructOptions? options = null) { if ((string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData)) || (string.IsNullOrWhiteSpace(mask?.FileUrl) && string.IsNullOrWhiteSpace(mask?.FileData))) @@ -166,8 +186,10 @@ public partial class FileInstructService throw new ArgumentException($"Cannot find image/mask url or data"); } - var innerAgentId = agentId ?? Guid.Empty.ToString(); - var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2"); + var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); + var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + + var completion = CompletionProvider.GetImageCompletion(_services, provider: options?.Provider ?? "openai", model: options?.Model ?? "dall-e-2"); var imageBytes = await DownloadFile(image); var maskBytes = await DownloadFile(mask); @@ -183,7 +205,8 @@ public partial class FileInstructService var maskName = $"{mask.FileName ?? "mask"}.{mask.FileExtension ?? "png"}"; var message = await completion.GetImageEdits(new Agent() { - Id = innerAgentId + Id = innerAgentId, + Instruction = instruction }, new RoleDialogModel(AgentRole.User, text), imageStream, imageName, maskStream, maskName); imageStream.Close(); @@ -192,7 +215,7 @@ public partial class FileInstructService var hooks = _services.GetServices(); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) { continue; } @@ -202,7 +225,9 @@ public partial class FileInstructService AgentId = innerAgentId, Provider = completion.Provider, Model = completion.Model, + TemplateName = options?.TemplateName, UserMessage = text, + SystemInstruction = instruction, CompletionText = message.Content }); } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs index af62e479..a1589de9 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs @@ -6,11 +6,11 @@ namespace BotSharp.Core.Files.Services; public partial class FileInstructService { - public async Task ReadPdf(string? provider, string? model, string? modelId, string prompt, List files, string? agentId = null) + public async Task ReadPdf(string text, List files, InstructOptions? options = null) { var content = string.Empty; - if (string.IsNullOrWhiteSpace(prompt) || files.IsNullOrEmpty()) + if (string.IsNullOrWhiteSpace(text) || files.IsNullOrEmpty()) { return content; } @@ -25,15 +25,18 @@ public partial class FileInstructService var images = await ConvertPdfToImages(pdfFiles); if (images.IsNullOrEmpty()) return content; - var innerAgentId = agentId ?? Guid.Empty.ToString(); - var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai", - model: model, modelId: modelId ?? "gpt-4o", multiModal: true); + var innerAgentId = options?.AgentId ?? Guid.Empty.ToString(); + var instruction = await GetAgentTemplate(innerAgentId, options?.TemplateName); + + var completion = CompletionProvider.GetChatCompletion(_services, provider: options?.Provider ?? "openai", + model: options?.Model ?? "gpt-4o", multiModal: true); var message = await completion.GetChatCompletions(new Agent() { Id = innerAgentId, + Instruction = instruction }, new List { - new RoleDialogModel(AgentRole.User, prompt) + new RoleDialogModel(AgentRole.User, text) { Files = images.Select(x => new BotSharpFile { FileStorageUrl = x }).ToList() } @@ -42,7 +45,7 @@ public partial class FileInstructService var hooks = _services.GetServices(); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) + if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) { continue; } @@ -52,7 +55,9 @@ public partial class FileInstructService AgentId = innerAgentId, Provider = completion.Provider, Model = completion.Model, - UserMessage = prompt, + TemplateName = options?.TemplateName, + UserMessage = text, + SystemInstruction = instruction, CompletionText = message.Content }); } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs index 416d9f30..b2da5f43 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.cs @@ -30,4 +30,22 @@ public partial class FileInstructService : IFileInstructService _fileStorage.CreateDirectory(dir); } } + + private async Task GetAgentTemplate(string agentId, string? templateName) + { + if (string.IsNullOrWhiteSpace(agentId) || string.IsNullOrWhiteSpace(templateName)) + { + return null; + } + + var agentService = _services.GetRequiredService(); + var agent = await agentService.GetAgent(agentId); + if (agent == null) + { + return null; + } + + var instruction = agentService.RenderedTemplate(agent, templateName); + return instruction; + } } diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 36f5b523..71a7488b 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -6,7 +6,8 @@ namespace BotSharp.Core.Instructs; public partial class InstructService { - public async Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null) + public async Task Execute(string agentId, RoleDialogModel message, + string? templateName = null, string? instruction = null, IEnumerable? files = null) { var agentService = _services.GetRequiredService(); Agent agent = await agentService.LoadAgent(agentId); @@ -88,7 +89,8 @@ public partial class InstructService new RoleDialogModel(AgentRole.User, prompt) { CurrentAgentId = agentId, - MessageId = message.MessageId + MessageId = message.MessageId, + Files = files?.Select(x => new BotSharpFile { FileUrl = x.FileUrl, FileData = x.FileData }).ToList() ?? [] } }); response.Text = result.Content; diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs index b2de2f28..4b7b5efb 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs @@ -8,10 +8,10 @@ namespace BotSharp.Core.Instructs; public partial class InstructService { - public async Task Instruct(string instruction, string agentId, InstructOptions options) where T : class + public async Task Instruct(string text, InstructOptions? options = null) where T : class { - var prompt = GetPrompt(instruction, options.Data); - var response = await GetAiResponse(agentId, prompt, options); + var agent = await BuildInnerAgent(options); + var response = await GetAiResponse(text, agent, options); if (string.IsNullOrWhiteSpace(response.Content)) return null; @@ -28,19 +28,18 @@ public partial class InstructService } else if (IsListType(type)) { - var text = response.Content.JsonArrayContent(); - if (!string.IsNullOrWhiteSpace(text)) + var content = response.Content.JsonArrayContent(); + if (!string.IsNullOrWhiteSpace(content)) { - - result = JsonSerializer.Deserialize(text, botsharpOptions.JsonSerializerOptions); + result = JsonSerializer.Deserialize(content, botsharpOptions.JsonSerializerOptions); } } else { - var text = response.Content.JsonContent(); - if (!string.IsNullOrWhiteSpace(text)) + var content = response.Content.JsonContent(); + if (!string.IsNullOrWhiteSpace(content)) { - result = JsonSerializer.Deserialize(text, botsharpOptions.JsonSerializerOptions); + result = JsonSerializer.Deserialize(content, botsharpOptions.JsonSerializerOptions); } } } @@ -52,47 +51,62 @@ public partial class InstructService return result; } - private string GetPrompt(string instruction, Dictionary data) + private async Task BuildInnerAgent(InstructOptions? options) + { + Agent? agent = null; + string? instruction = null; + + if (!string.IsNullOrWhiteSpace(options?.AgentId)) + { + var agentService = _services.GetRequiredService(); + agent = await agentService.GetAgent(options.AgentId); + + if (!string.IsNullOrWhiteSpace(options?.TemplateName)) + { + var template = agent?.Templates?.FirstOrDefault(x => x.Name == options.TemplateName)?.Content ?? string.Empty; + instruction = BuildInstruction(template, options?.Data ?? []); + } + } + + return new Agent + { + Id = agent?.Id ?? Guid.Empty.ToString(), + Name = agent?.Name ?? "Unknown", + Instruction = instruction, + LlmConfig = agent?.LlmConfig ?? new() + }; + } + + private string BuildInstruction(string instruction, Dictionary data) { var render = _services.GetRequiredService(); return render.Render(instruction, data ?? new Dictionary()); } - private async Task GetAiResponse(string agentId, string prompt, InstructOptions options) + private async Task GetAiResponse(string text, Agent agent, InstructOptions? options) { - var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(agentId); - - var localAgent = new Agent - { - Id = agentId, - Name = agent?.Name ?? "Unknown", - Instruction = prompt, - TemplateDict = new() - }; - - var messages = BuildDialogs(options); - var provider = options.Provider ?? agent?.LlmConfig?.Provider ?? "openai"; - var model = options.Model ?? agent?.LlmConfig?.Model ?? "gpt-4o"; + var dialogs = BuildDialogs(text, options); + var provider = options?.Provider ?? agent?.LlmConfig?.Provider ?? "openai"; + var model = options?.Model ?? agent?.LlmConfig?.Model ?? "gpt-4o"; var completion = CompletionProvider.GetChatCompletion(_services, provider: provider, model: model); - return await completion.GetChatCompletions(localAgent, messages); + return await completion.GetChatCompletions(agent, dialogs); } - private List BuildDialogs(InstructOptions options) + private List BuildDialogs(string text, InstructOptions? options) { var messages = new List(); - if (!string.IsNullOrWhiteSpace(options.ConversationId)) + if (!string.IsNullOrWhiteSpace(options?.ConversationId)) { var conv = _services.GetRequiredService(); var dialogs = conv.GetDialogHistory(); messages.AddRange(dialogs); } - if (!string.IsNullOrWhiteSpace(options.Message)) + if (!string.IsNullOrWhiteSpace(text)) { - messages.Add(new RoleDialogModel(AgentRole.User, options.Message)); + messages.Add(new RoleDialogModel(AgentRole.User, text)); } return messages; diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 4da61828..5ccc7088 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -194,7 +194,7 @@ namespace BotSharp.Core.Repository File.WriteAllText(agentFile, json); } - private void UpdateAgentMcpTools(string agentId, List mcptools) + private void UpdateAgentMcpTools(string agentId, List mcptools) { if (mcptools == null) return; diff --git a/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs b/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs deleted file mode 100644 index 90664c7b..00000000 --- a/src/Infrastructure/BotSharp.MCP/MCPClientManager.cs +++ /dev/null @@ -1,30 +0,0 @@ -using BotSharp.Core.Mcp.Settings; -using Microsoft.Extensions.Logging; -using ModelContextProtocol.Client; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace BotSharp.Core.Mcp; - -public class MCPClientManager : IDisposable -{ - - private readonly MCPSettings mcpSettings; - - public MCPClientManager(MCPSettings settings) - { - mcpSettings = settings; - } - - public async Task GetMcpClientAsync(string serverId) - { - return await McpClientFactory.CreateAsync(mcpSettings.McpServerConfigs - .Where(x=> x.Name == serverId).First(), mcpSettings.McpClientOptions); - } - - public void Dispose() - { - - } -} diff --git a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs b/src/Infrastructure/BotSharp.MCP/McpPlugin.cs deleted file mode 100644 index f95965ac..00000000 --- a/src/Infrastructure/BotSharp.MCP/McpPlugin.cs +++ /dev/null @@ -1,61 +0,0 @@ -using BotSharp.Abstraction.Agents; -using BotSharp.Abstraction.Conversations; -using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Plugins; -using BotSharp.Core.Mcp.Functions; -using BotSharp.Core.Mcp.Settings; -using BotSharp.MCP.Hooks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging.Abstractions; -using ModelContextProtocol.Client; -using ModelContextProtocol.Configuration; -using System.Linq; -using System.Threading.Tasks; - -namespace BotSharp.Core.Mcp; - -public class McpPlugin : IBotSharpPlugin -{ - public string Id => "5d779611-0012-46cb-a754-4ca4770e88ac"; - public string Name => "MCP Plugin"; - public string Description => "Integrated MCP tools"; - - private MCPClientManager clientManager; - - public void RegisterDI(IServiceCollection services, IConfiguration config) - { - var settings = config.GetSection("MCPSettings").Get(); - services.AddScoped(provider => { return settings; }); - - clientManager = new MCPClientManager(settings); - services.AddSingleton(clientManager); - - foreach (var server in settings.McpServerConfigs) - { - RegisterFunctionCall(services, server) - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); - } - // Register hooks - services.AddScoped(); - } - - private async Task RegisterFunctionCall(IServiceCollection services, McpServerConfig server) - { - var client = await clientManager.GetMcpClientAsync(server.Id); - var tools = await client.ListToolsAsync().ToListAsync(); - - foreach (var tool in tools) - { - services.AddScoped(provider => { return tool; }); - - services.AddScoped(provider => - { - var funcTool = new McpToolAdapter(provider, tool, clientManager); - return funcTool; - }); - } - } -} diff --git a/src/Infrastructure/BotSharp.MCP/Settings/MCPSettings.cs b/src/Infrastructure/BotSharp.MCP/Settings/MCPSettings.cs deleted file mode 100644 index 1279a08d..00000000 --- a/src/Infrastructure/BotSharp.MCP/Settings/MCPSettings.cs +++ /dev/null @@ -1,13 +0,0 @@ -using ModelContextProtocol.Client; -using ModelContextProtocol.Configuration; -using System.Collections.Generic; - -namespace BotSharp.Core.Mcp.Settings; - -public class MCPSettings -{ - public McpClientOptions McpClientOptions { get; set; } - - public List McpServerConfigs { get; set; } - -} diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj index 121afd35..9162cd16 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj @@ -47,8 +47,7 @@ - - + diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 6e49658d..401a693f 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -1,7 +1,4 @@ using BotSharp.Abstraction.Agents.Models; -using BotSharp.Core.Mcp; -using ModelContextProtocol.Client; -using ModelContextProtocol.Protocol.Types; namespace BotSharp.OpenAPI.Controllers; @@ -12,20 +9,17 @@ public class AgentController : ControllerBase private readonly IAgentService _agentService; private readonly IUserIdentity _user; private readonly IServiceProvider _services; - private readonly MCPClientManager _clientManager; - + public AgentController( IAgentService agentService, IUserIdentity user, - IServiceProvider services, - MCPClientManager mCPClientManager + IServiceProvider services ) { _agentService = agentService; _user = user; _services = services; - _clientManager = mCPClientManager; - } + } [HttpGet("/agent/settings")] public AgentSettings GetSettings() @@ -173,17 +167,7 @@ public class AgentController : ControllerBase } return utilities.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList(); } - - [HttpGet("/agent/mcp/tools")] - public async Task> GetMCPTools(string serverId) - { - var client = await _clientManager.GetMcpClientAsync(serverId); - var tools = await client.ListToolsAsync().ToListAsync(); - - return tools.Where(x => !string.IsNullOrWhiteSpace(x.Name)) - .OrderBy(x => x.Name).ToList(); - } - + [HttpGet("/agent/labels")] public async Task> GetAgentLabels() { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index 3db47703..55837f8d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -4,6 +4,7 @@ using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; using BotSharp.Core.Infrastructures; using BotSharp.OpenAPI.ViewModels.Instructs; +using static System.Net.Mime.MediaTypeNames; namespace BotSharp.OpenAPI.Controllers; @@ -36,7 +37,8 @@ public class InstructModeController : ControllerBase var result = await instructor.Execute(agentId, new RoleDialogModel(AgentRole.User, input.Text), templateName: input.Template, - instruction: input.Instruction); + instruction: input.Instruction, + files: input.Files); result.States = state.GetStates(); @@ -96,6 +98,9 @@ public class InstructModeController : ControllerBase }, new List { new RoleDialogModel(AgentRole.User, input.Text) + { + Files = input.Files?.Select(x => new BotSharpFile { FileUrl = x.FileUrl, FileData = x.FileData }).ToList() ?? [] + } }); var hooks = _services.GetServices(); @@ -131,7 +136,13 @@ public class InstructModeController : ControllerBase try { var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadImages(input.Provider, input.Model, input.Text, input.Files, input.AgentId); + var content = await fileInstruct.ReadImages(input.Text, input.Files, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); return content; } catch (Exception ex) @@ -144,7 +155,8 @@ public class InstructModeController : ControllerBase [HttpPost("/instruct/multi-modal/upload")] public async Task MultiModalCompletion(IFormFile file, [FromForm] string text, [FromForm] string? provider = null, - [FromForm] string? model = null, [FromForm] List? states = null, [FromForm] string? agentId = null) + [FromForm] string? model = null, [FromForm] List? states = null, + [FromForm] string? agentId = null, [FromForm] string? templateName = null) { var state = _services.GetRequiredService(); states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); @@ -158,7 +170,13 @@ public class InstructModeController : ControllerBase new InstructFileModel { FileData = data } }; var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadImages(provider, model, text, files, agentId); + var content = await fileInstruct.ReadImages(text, files, new InstructOptions + { + Provider = provider, + Model = model, + AgentId = agentId, + TemplateName = templateName + }); viewModel.Content = content; return viewModel; } @@ -183,7 +201,13 @@ public class InstructModeController : ControllerBase try { var fileInstruct = _services.GetRequiredService(); - var message = await fileInstruct.GenerateImage(input.Provider, input.Model, input.Text, input.AgentId); + var message = await fileInstruct.GenerateImage(input.Text, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList(); return imageViewModel; @@ -214,7 +238,13 @@ public class InstructModeController : ControllerBase } var fileInstruct = _services.GetRequiredService(); - var message = await fileInstruct.VaryImage(input.Provider, input.Model, input.File, input.AgentId); + var message = await fileInstruct.VaryImage(input.File, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList(); @@ -231,7 +261,8 @@ public class InstructModeController : ControllerBase [HttpPost("/instruct/image-variation/upload")] public async Task ImageVariation(IFormFile file, [FromForm] string? provider = null, - [FromForm] string? model = null, [FromForm] List? states = null, [FromForm] string? agentId = null) + [FromForm] string? model = null, [FromForm] List? states = null, + [FromForm] string? agentId = null, [FromForm] string? templateName = null) { var state = _services.GetRequiredService(); states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); @@ -239,20 +270,18 @@ public class InstructModeController : ControllerBase try { - using var stream = new MemoryStream(); - file.CopyTo(stream); - stream.Position = 0; - - var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2"); - var message = await completion.GetImageVariation(new Agent() + var fileInstruct = _services.GetRequiredService(); + var fileData = FileUtility.BuildFileDataFromFile(file); + var message = await fileInstruct.VaryImage(new InstructFileModel { FileData = fileData }, new InstructOptions { - Id = agentId ?? Guid.Empty.ToString() - }, new RoleDialogModel(AgentRole.User, string.Empty), stream, file.FileName); + Provider = provider, + Model = model, + AgentId = agentId, + TemplateName = templateName + }); imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList(); - stream.Close(); - return imageViewModel; } catch (Exception ex) @@ -278,7 +307,13 @@ public class InstructModeController : ControllerBase { return new ImageGenerationViewModel { Message = "Error! Cannot find a valid image file!" }; } - var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, input.File, input.AgentId); + var message = await fileInstruct.EditImage(input.Text, input.File, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList(); return imageViewModel; @@ -294,7 +329,8 @@ public class InstructModeController : ControllerBase [HttpPost("/instruct/image-edit/upload")] public async Task ImageEdit(IFormFile file, [FromForm] string text, [FromForm] string? provider = null, - [FromForm] string? model = null, [FromForm] List? states = null, [FromForm] string? agentId = null) + [FromForm] string? model = null, [FromForm] List? states = null, + [FromForm] string? agentId = null, [FromForm] string? templateName = null) { var fileInstruct = _services.GetRequiredService(); var state = _services.GetRequiredService(); @@ -303,19 +339,17 @@ public class InstructModeController : ControllerBase try { - using var stream = new MemoryStream(); - file.CopyTo(stream); - stream.Position = 0; - - var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2"); - var message = await completion.GetImageEdits(new Agent() + var fileData = FileUtility.BuildFileDataFromFile(file); + var message = await fileInstruct.EditImage(text, new InstructFileModel { FileData = fileData }, new InstructOptions { - Id = agentId ?? Guid.Empty.ToString() - }, new RoleDialogModel(AgentRole.User, text), stream, file.FileName); + Provider = provider, + Model = model, + AgentId = agentId, + TemplateName = templateName + }); imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList(); - stream.Close(); return imageViewModel; } @@ -344,7 +378,13 @@ public class InstructModeController : ControllerBase { return new ImageGenerationViewModel { Message = "Error! Cannot find a valid image or mask!" }; } - var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, image, mask, input.AgentId); + var message = await fileInstruct.EditImage(input.Text, image, mask, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList(); return imageViewModel; @@ -359,8 +399,9 @@ public class InstructModeController : ControllerBase } [HttpPost("/instruct/image-mask-edit/upload")] - public async Task ImageMaskEdit(IFormFile image, IFormFile mask, [FromForm] string text, [FromForm] string? provider = null, - [FromForm] string? model = null, [FromForm] List? states = null, [FromForm] string? agentId = null) + public async Task ImageMaskEdit(IFormFile image, IFormFile mask, + [FromForm] string text, [FromForm] string? provider = null, [FromForm] string? model = null, + [FromForm] List? states = null, [FromForm] string? agentId = null, [FromForm] string? templateName = null) { var fileInstruct = _services.GetRequiredService(); var state = _services.GetRequiredService(); @@ -369,24 +410,20 @@ public class InstructModeController : ControllerBase try { - using var imageStream = new MemoryStream(); - image.CopyTo(imageStream); - imageStream.Position = 0; - - using var maskStream = new MemoryStream(); - mask.CopyTo(maskStream); - maskStream.Position = 0; - - var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2"); - var message = await completion.GetImageEdits(new Agent() + var imageData = FileUtility.BuildFileDataFromFile(image); + var maskData = FileUtility.BuildFileDataFromFile(mask); + var message = await fileInstruct.EditImage(text, + new InstructFileModel { FileData = imageData }, + new InstructFileModel { FileData = maskData }, new InstructOptions { - Id = agentId ?? Guid.Empty.ToString() - }, new RoleDialogModel(AgentRole.User, text), imageStream, image.FileName, maskStream, mask.FileName); + Provider = provider, + Model = model, + AgentId = agentId, + TemplateName = templateName + }); imageViewModel.Content = message.Content; imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList(); - imageStream.Close(); - maskStream.Close(); return imageViewModel; } @@ -411,7 +448,13 @@ public class InstructModeController : ControllerBase try { var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadPdf(input.Provider, input.Model, input.ModelId, input.Text, input.Files, input.AgentId); + var content = await fileInstruct.ReadPdf(input.Text, input.Files, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); viewModel.Content = content; return viewModel; } @@ -425,8 +468,9 @@ public class InstructModeController : ControllerBase } [HttpPost("/instruct/pdf-completion/upload")] - public async Task PdfCompletion(IFormFile file, [FromForm] string text, [FromForm] string? provider = null, - [FromForm] string? model = null, [FromForm] string? modelId = null, [FromForm] List? states = null, [FromForm] string? agentId = null) + public async Task PdfCompletion(IFormFile file, [FromForm] string text, + [FromForm] string? provider = null, [FromForm] string? model = null, [FromForm] List? states = null, + [FromForm] string? agentId = null, [FromForm] string? templateName = null) { var state = _services.GetRequiredService(); states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); @@ -441,7 +485,13 @@ public class InstructModeController : ControllerBase }; var fileInstruct = _services.GetRequiredService(); - var content = await fileInstruct.ReadPdf(provider, model, modelId, text, files, agentId); + var content = await fileInstruct.ReadPdf(text, files, new InstructOptions + { + Provider = provider, + Model = model, + AgentId = agentId, + TemplateName = templateName + }); viewModel.Content = content; return viewModel; } @@ -471,7 +521,13 @@ public class InstructModeController : ControllerBase { return new SpeechToTextViewModel { Message = "Error! Cannot find a valid audio file!" }; } - var content = await fileInstruct.SpeechToText(input.Provider, input.Model, audio); + var content = await fileInstruct.SpeechToText(audio, input.Text, new InstructOptions + { + Provider = input.Provider, + Model = input.Model, + AgentId = input.AgentId, + TemplateName = input.TemplateName + }); viewModel.Content = content; return viewModel; } @@ -485,8 +541,10 @@ public class InstructModeController : ControllerBase } [HttpPost("/instruct/speech-to-text/upload")] - public async Task SpeechToText(IFormFile file, [FromForm] string? provider = null, [FromForm] string? model = null, - [FromForm] string? text = null, [FromForm] List? states = null) + public async Task SpeechToText(IFormFile file, + [FromForm] string? provider = null, [FromForm] string? model = null, + [FromForm] string? text = null, [FromForm] List? states = null, + [FromForm] string? agentId = null, [FromForm] string? templateName = null) { var fileInstruct = _services.GetRequiredService(); var state = _services.GetRequiredService(); @@ -495,14 +553,16 @@ public class InstructModeController : ControllerBase try { - using var stream = new MemoryStream(); - file.CopyTo(stream); - stream.Position = 0; + var auditData = FileUtility.BuildFileDataFromFile(file); + var content = await fileInstruct.SpeechToText(new InstructFileModel { FileData = auditData }, text, new InstructOptions + { + Provider = provider, + Model = model, + AgentId = agentId, + TemplateName = templateName + }); - var completion = CompletionProvider.GetAudioTranscriber(_services, provider: provider, model: model); - var content = await completion.TranscriptTextAsync(stream, file.FileName, text); viewModel.Content = content; - stream.Close(); return viewModel; } catch (Exception ex) diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCreationModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCreationModel.cs index 81ec1bc7..8265ebe4 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCreationModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentCreationModel.cs @@ -55,7 +55,7 @@ public class AgentCreationModel public int? MaxMessageCount { get; set; } public List Utilities { get; set; } = new(); - public List McpTools { get; set; } = new(); + public List McpTools { get; set; } = new(); public List RoutingRules { get; set; } = new(); public List KnowledgeBases { get; set; } = new(); public List Rules { get; set; } = new(); diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentUpdateModel.cs index 9f2925e5..5dad68b2 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentUpdateModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentUpdateModel.cs @@ -42,7 +42,7 @@ public class AgentUpdateModel /// /// McpTools /// - public List? McpTools { get; set; } + public List? McpTools { get; set; } /// /// knowledge bases diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs index db3af34c..8a9a790c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/View/AgentViewModel.cs @@ -24,7 +24,9 @@ public class AgentViewModel [JsonPropertyName("merge_utility")] public bool MergeUtility { get; set; } public List Utilities { get; set; } - public List Acps { get; set; } + + [JsonPropertyName("mcp_tools")] + public List McpTools { get; set; } [JsonPropertyName("knowledge_bases")] public List KnowledgeBases { get; set; } @@ -87,7 +89,7 @@ public class AgentViewModel Responses = agent.Responses ?? [], Samples = agent.Samples ?? [], Utilities = agent.Utilities ?? [], - Acps = agent.McpTools ?? [], + McpTools = agent.McpTools ?? [], KnowledgeBases = agent.KnowledgeBases ?? [], IsPublic= agent.IsPublic, Disabled = agent.Disabled, diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs index 88f011be..80368a70 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructBaseRequest.cs @@ -10,14 +10,14 @@ public class InstructBaseRequest [JsonPropertyName("model")] public virtual string? Model { get; set; } = null; - [JsonPropertyName("model_id")] - public virtual string? ModelId { get; set; } = null; - [JsonPropertyName("agent_id")] public virtual string? AgentId { get; set; } + [JsonPropertyName("template_name")] + public virtual string? TemplateName { get; set; } + [JsonPropertyName("states")] - public List States { get; set; } = new(); + public List States { get; set; } = []; } public class MultiModalRequest : InstructBaseRequest @@ -26,7 +26,7 @@ public class MultiModalRequest : InstructBaseRequest public string Text { get; set; } = string.Empty; [JsonPropertyName("files")] - public List Files { get; set; } = new(); + public List Files { get; set; } = []; } public class ImageGenerationRequest : InstructBaseRequest diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructMessageModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructMessageModel.cs index 1ad3aead..aec81ffb 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructMessageModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructMessageModel.cs @@ -8,6 +8,7 @@ public class InstructMessageModel : IncomingMessageModel public string? Instruction { get; set; } public override string Channel { get; set; } = ConversationChannel.OpenAPI; public string? Template { get; set; } + public List Files { get; set; } = []; } @@ -16,4 +17,5 @@ public class IncomingInstructRequest : IncomingMessageModel public string? AgentId { get; set; } public string? Instruction { get; set; } public string? Template { get; set; } + public List Files { get; set; } = []; } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs index a0d99e0d..ae628456 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs @@ -18,7 +18,7 @@ public class AgentDocument : MongoBase public List Responses { get; set; } public List Samples { get; set; } public List Utilities { get; set; } - public List McpTools { get; set; } + public List McpTools { get; set; } public List KnowledgeBases { get; set; } public List Profiles { get; set; } public List Labels { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs index 614683c3..99c39fce 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentMCPToolMongoElement.cs @@ -3,34 +3,37 @@ using BotSharp.Abstraction.Agents.Models; namespace BotSharp.Plugin.MongoStorage.Models; [BsonIgnoreExtraElements(Inherited = true)] -public class AgentMCPToolMongoElement +public class AgentMcpToolMongoElement { public string Name { get; set; } - public string ServerId { get; set; } - public bool Disabled { get; set; } public List Functions { get; set; } = []; - public static AgentMCPToolMongoElement ToMongoElement(MCPTool utility) + public static AgentMcpToolMongoElement ToMongoElement(McpTool tool) { - return new AgentMCPToolMongoElement + return new AgentMcpToolMongoElement { - Disabled = utility.Disabled, - Functions = utility.Functions?.Select(x => new McpFunctionMongoElement(x.Name))?.ToList() ?? [], + Name = tool.Name, + ServerId = tool.ServerId, + Disabled = tool.Disabled, + Functions = tool.Functions?.Select(x => new McpFunctionMongoElement(x.Name))?.ToList() ?? [], }; } - public static MCPTool ToDomainElement(AgentMCPToolMongoElement utility) + public static McpTool ToDomainElement(AgentMcpToolMongoElement tool) { - return new MCPTool + return new McpTool { - Disabled = utility.Disabled, - Functions = utility.Functions?.Select(x => new MCPFunction(x.Name))?.ToList() ?? [], + Name = tool.Name, + ServerId = tool.ServerId, + Disabled = tool.Disabled, + Functions = tool.Functions?.Select(x => new McpFunction(x.Name))?.ToList() ?? [], }; } } +[BsonIgnoreExtraElements(Inherited = true)] public class McpFunctionMongoElement { public string Name { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index ba4b3fde..75b2d470 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -264,11 +264,11 @@ public partial class MongoRepository _dc.Agents.UpdateOne(filter, update); } - private void UpdateAgentMcpTools(string agentId, List mcps) + private void UpdateAgentMcpTools(string agentId, List mcps) { if (mcps == null) return; - var elements = mcps?.Select(x => AgentMCPToolMongoElement.ToMongoElement(x))?.ToList() ?? []; + var elements = mcps?.Select(x => AgentMcpToolMongoElement.ToMongoElement(x))?.ToList() ?? []; var filter = Builders.Filter.Eq(x => x.Id, agentId); var update = Builders.Update @@ -346,7 +346,7 @@ public partial class MongoRepository .Set(x => x.Responses, agent.Responses.Select(r => AgentResponseMongoElement.ToMongoElement(r)).ToList()) .Set(x => x.Samples, agent.Samples) .Set(x => x.Utilities, agent.Utilities.Select(u => AgentUtilityMongoElement.ToMongoElement(u)).ToList()) - .Set(x => x.McpTools, agent.McpTools.Select(u => AgentMCPToolMongoElement.ToMongoElement(u)).ToList()) + .Set(x => x.McpTools, agent.McpTools.Select(u => AgentMcpToolMongoElement.ToMongoElement(u)).ToList()) .Set(x => x.KnowledgeBases, agent.KnowledgeBases.Select(u => AgentKnowledgeBaseMongoElement.ToMongoElement(u)).ToList()) .Set(x => x.Rules, agent.Rules.Select(e => AgentRuleMongoElement.ToMongoElement(e)).ToList()) .Set(x => x.LlmConfig, AgentLlmConfigMongoElement.ToMongoElement(agent.LlmConfig)) @@ -527,7 +527,7 @@ public partial class MongoRepository Responses = x.Responses?.Select(r => AgentResponseMongoElement.ToMongoElement(r))?.ToList() ?? [], RoutingRules = x.RoutingRules?.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?.ToList() ?? [], Utilities = x.Utilities?.Select(u => AgentUtilityMongoElement.ToMongoElement(u))?.ToList() ?? [], - McpTools = x.McpTools?.Select(u => AgentMCPToolMongoElement.ToMongoElement(u))?.ToList() ?? [], + McpTools = x.McpTools?.Select(u => AgentMcpToolMongoElement.ToMongoElement(u))?.ToList() ?? [], KnowledgeBases = x.KnowledgeBases?.Select(k => AgentKnowledgeBaseMongoElement.ToMongoElement(k))?.ToList() ?? [], Rules = x.Rules?.Select(e => AgentRuleMongoElement.ToMongoElement(e))?.ToList() ?? [], CreatedTime = x.CreatedDateTime, @@ -622,7 +622,7 @@ public partial class MongoRepository Responses = agentDoc.Responses?.Select(r => AgentResponseMongoElement.ToDomainElement(r))?.ToList() ?? [], RoutingRules = agentDoc.RoutingRules?.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))?.ToList() ?? [], Utilities = agentDoc.Utilities?.Select(u => AgentUtilityMongoElement.ToDomainElement(u))?.ToList() ?? [], - McpTools = agentDoc.McpTools?.Select(u => AgentMCPToolMongoElement.ToDomainElement(u))?.ToList() ?? [], + McpTools = agentDoc.McpTools?.Select(u => AgentMcpToolMongoElement.ToDomainElement(u))?.ToList() ?? [], KnowledgeBases = agentDoc.KnowledgeBases?.Select(x => AgentKnowledgeBaseMongoElement.ToDomainElement(x))?.ToList() ?? [], Rules = agentDoc.Rules?.Select(e => AgentRuleMongoElement.ToDomainElement(e))?.ToList() ?? [] }; diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/IOpenAiRealtimeApi.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/IOpenAiRealtimeApi.cs index 68382dff..bdf16ea7 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/IOpenAiRealtimeApi.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/IOpenAiRealtimeApi.cs @@ -7,5 +7,5 @@ namespace BotSharp.Plugin.OpenAI.Providers.Realtime; public interface IOpenAiRealtimeApi { [Post("/v1/realtime/sessions")] - Task GetSessionAsync(RealtimeSessionCreationRequest model, [Authorize("Bearer")] string token); + Task CreateSessionAsync(RealtimeSessionCreationRequest model, [Authorize("Bearer")] string token); } diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index ecc553d3..4499f851 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -297,7 +297,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion var settings = settingsService.GetSetting(Provider, args.Model ?? _model); var api = _services.GetRequiredService(); - var session = await api.GetSessionAsync(args, settings.ApiKey); + var session = await api.CreateSessionAsync(args, settings.ApiKey); return session; } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs index a4e6d4b8..89b38f93 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs @@ -53,17 +53,17 @@ public class SqlValidateFn : IFunctionCallback var instructService = _services.GetRequiredService(); var agentService = _services.GetRequiredService(); var states = _services.GetRequiredService(); - - var agent = await agentService.GetAgent(BuiltInAgentId.SqlDriver); - var template = agent.Templates.FirstOrDefault(x => x.Name == "sql_statement_correctness")?.Content ?? string.Empty; + + var query = "Correct SQL Statement and keep the comments/explanations"; var ddl = states.GetState("table_ddls"); - var correctedSql = await instructService.Instruct(template, BuiltInAgentId.SqlDriver, + var correctedSql = await instructService.Instruct(query, new InstructOptions { - Provider = agent?.LlmConfig?.Provider ?? "openai", - Model = agent?.LlmConfig?.Model ?? "gpt-4o", - Message = "Correct SQL Statement and keep the comments/explanations", + Provider = "openai", + Model = "gpt-4o", + AgentId = BuiltInAgentId.SqlDriver, + TemplateName = "sql_statement_correctness", Data = new Dictionary { { "original_sql", message.Content }, diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs index b851cd1a..865864a0 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs @@ -1,3 +1,6 @@ +using System.IO; +using System.Net.Http; + namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver; public partial class PlaywrightWebDriver @@ -50,6 +53,18 @@ public partial class PlaywrightWebDriver }); } } + else if (action.Action == BroswerActionEnum.DropDown) + { + await locator.ClickAsync(); + var optionLocator = page.Locator($"//div[text()='{action.Content}']"); + var optionCount = await optionLocator.CountAsync(); ; + if (optionCount == 0) + { + Serilog.Log.Error($"Dropdown option not found: {action.Content}"); + return; + } + await optionLocator.First.ClickAsync(); + } else if (action.Action == BroswerActionEnum.InputText) { await locator.FillAsync(action.Content); @@ -63,6 +78,36 @@ public partial class PlaywrightWebDriver await locator.PressAsync(action.PressKey); } } + else if (action.Action == BroswerActionEnum.FileUpload) + { + if (action.FileUrl.Length == 0) + { + return; + } + var fileChooser = await page.RunAndWaitForFileChooserAsync(async () => + { + await locator.ClickAsync(); + }); + var guid = Guid.NewGuid().ToString(); + var directory = Path.Combine(Path.GetTempPath(), guid); + if (Directory.Exists(directory)) + { + Directory.Delete(directory, true); + } + Directory.CreateDirectory(directory); + var localPaths = new List(); + using var httpClient = new HttpClient(); + foreach (var fileUrl in action.FileUrl) + { + var bytes = await httpClient.GetByteArrayAsync(fileUrl); + var fileName = new Uri(fileUrl).AbsolutePath; + var localPath = Path.Combine(directory, Path.GetFileName(fileName)); + await File.WriteAllBytesAsync(localPath, bytes); + await Task.Delay(2000); + localPaths.Add(localPath); + } + await fileChooser.SetFilesAsync(localPaths); + } else if (action.Action == BroswerActionEnum.Typing) { await locator.PressSequentiallyAsync(action.Content); @@ -128,7 +173,6 @@ public partial class PlaywrightWebDriver await Task.Delay(1000 * action.WaitTime); } } - public static List GetVelocityTrack(float distance) { // Initialize the track list to store the movement distances diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json b/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json index f91425b4..b397fc49 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json +++ b/src/Plugins/BotSharp.Plugin.WebDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-web-action_on_element.json @@ -11,12 +11,20 @@ "action": { "type": "string", "description": "action to perform on element", - "enum": [ "Click", "InputText" ] + "enum": [ "Click", "InputText", "DropDown", "FileUpload" ] }, "content": { "type": "string", "description": "content to input in element" }, + "file_urls": { + "type": "array", + "description": "The output format must be string array of file urls.", + "items": { + "type": "string", + "description": "file url" + } + }, "press_key": { "type": "string", "enum": [ "Enter" ], diff --git a/src/WebStarter/Program.cs b/src/WebStarter/Program.cs index d4015267..33651c05 100644 --- a/src/WebStarter/Program.cs +++ b/src/WebStarter/Program.cs @@ -1,4 +1,5 @@ using BotSharp.Core; +using BotSharp.Core.MCP; using BotSharp.OpenAPI; using BotSharp.Logger; using BotSharp.Plugin.ChatHub; @@ -23,6 +24,7 @@ string[] allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get options.JsonSerializerOptions.Converters.Add(new RichContentJsonConverter()); options.JsonSerializerOptions.Converters.Add(new TemplateMessageJsonConverter()); }).AddBotSharpOpenAPI(builder.Configuration, allowedOrigins, builder.Environment, true) + .AddBotSharpMCP(builder.Configuration) .AddBotSharpLogger(builder.Configuration); // Add service defaults & Aspire components. diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 7da8de59..0a115a1d 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -79,6 +79,7 @@ + @@ -121,7 +122,6 @@ - diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index b9d6746f..51b2d0bc 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -184,6 +184,7 @@ "Model": "gpt-4o-mini" } }, + "MCPSettings": { "McpClientOptions": { "ClientInfo": { @@ -201,6 +202,7 @@ } ] }, + "Conversation": { "DataDir": "conversations", "ShowVerboseLog": false, @@ -425,8 +427,8 @@ "BotSharp.Core.SideCar", "BotSharp.Core.Crontab", "BotSharp.Core.Realtime", + "BotSharp.Core.MCP", "BotSharp.Logger", - "BotSharp.MCP", "BotSharp.Plugin.MongoStorage", "BotSharp.Plugin.Dashboard", "BotSharp.Plugin.OpenAI", @@ -453,6 +455,9 @@ "BotSharp.Plugin.EmailHandler", "BotSharp.Plugin.AudioHandler", "BotSharp.Plugin.TencentCos" + ], + "ExcludedFunctions": [ + "McpToolAdapter" ] } } diff --git a/tests/BotSharp.PizzaBot.MCPServer/Program.cs b/tests/BotSharp.PizzaBot.MCPServer/Program.cs index fb39e6cd..d2cf912e 100644 --- a/tests/BotSharp.PizzaBot.MCPServer/Program.cs +++ b/tests/BotSharp.PizzaBot.MCPServer/Program.cs @@ -2,7 +2,8 @@ using BotSharp.PizzaBot.MCPServer; using ModelContextProtocol; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddMcpServer().WithTools(); +builder.Services.AddMcpServer() + .WithToolsFromAssembly(); var app = builder.Build(); app.MapGet("/", () => "This is a test server with only stub functionality!"); diff --git a/tests/BotSharp.PizzaBot.MCPServer/Tools/MakePayment.cs b/tests/BotSharp.PizzaBot.MCPServer/Tools/MakePayment.cs index a236d874..da198c47 100644 --- a/tests/BotSharp.PizzaBot.MCPServer/Tools/MakePayment.cs +++ b/tests/BotSharp.PizzaBot.MCPServer/Tools/MakePayment.cs @@ -4,10 +4,10 @@ using System.ComponentModel.DataAnnotations; namespace BotSharp.PizzaBot.MCPServer.Tools; -[McpToolType] +[McpServerToolType] public static class MakePayment { - [McpTool(name: "make_payment"), Description("call this function to make payment.")] + [McpServerTool(name: "make_payment"), Description("call this function to make payment.")] public static string Make_Payment( [Description("order number"),Required] string order_number, [Description("total amount"),Required] int total_amount) diff --git a/tests/BotSharp.PizzaBot.MCPServer/Tools/PizzaPrices.cs b/tests/BotSharp.PizzaBot.MCPServer/Tools/PizzaPrices.cs index 7ca766f3..762d67db 100644 --- a/tests/BotSharp.PizzaBot.MCPServer/Tools/PizzaPrices.cs +++ b/tests/BotSharp.PizzaBot.MCPServer/Tools/PizzaPrices.cs @@ -6,10 +6,10 @@ using System.Text.Json; namespace BotSharp.PizzaBot.MCPServer.Tools; -[McpToolType] +[McpServerToolType] public static class PizzaPrices { - [McpTool(name: "get_pizza_prices"), Description("call this function to get pizza unit price.")] + [McpServerTool(name: "get_pizza_prices"), Description("call this function to get pizza unit price.")] public static string GetPizzaPrices( [Description("The pizza type."), Required] string pizza_type, [Description("quantity of pizza"), Required] int quantity) diff --git a/tests/BotSharp.PizzaBot.MCPServer/Tools/PlaceOrder.cs b/tests/BotSharp.PizzaBot.MCPServer/Tools/PlaceOrder.cs index a6237b6d..73245751 100644 --- a/tests/BotSharp.PizzaBot.MCPServer/Tools/PlaceOrder.cs +++ b/tests/BotSharp.PizzaBot.MCPServer/Tools/PlaceOrder.cs @@ -4,10 +4,10 @@ using System.ComponentModel.DataAnnotations; namespace BotSharp.PizzaBot.MCPServer.Tools; -[McpToolType] +[McpServerToolType] public static class PlaceOrder { - [McpTool(name: "place_an_order"), Description("Place an order when user has confirmed the pizza type and quantity.")] + [McpServerTool(name: "place_an_order"), Description("Place an order when user has confirmed the pizza type and quantity.")] public static string PlaceAnOrder( [Description("The pizza type."), Required] string pizza_type, [Description("quantity of pizza"), Required] int quantity,