diff --git a/README.md b/README.md index 1116a8b5..11c9e34c 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,11 @@ It's written in C# running on .Net Core that is full cross-platform framework, t ### Some Features -* Built-in multi-agents and conversation management. -* Support multiple LLM platforms. -* Support export/ import agent from other bot platforms directly. +* Built-in multi-agents and conversation with state management. +* Built-in RAG related interfaces, Memeory based vector searching. +* Support multiple LLM platforms (ChatGPT, PaLM 2, LLaMA 2). +* Allow multiple agents with different responsibilities cooperate to complete complex tasks. +* Build, test, evaluate and audit your LLM agent in one place. * Support different open source UI [Chatbot UI](src/Plugins/BotSharp.Plugin.ChatbotUI/Chatbot-UI.md), [HuggingChat UI](src/Plugins/BotSharp.Plugin.HuggingFace/HuggingChat-UI.md). * Integrate with popular message channels like Facebook Messenger, Slack and Telegram. @@ -50,7 +52,3 @@ Read the docs: https://botsharp.readthedocs.io If you feel that this project is helpful to you, please Star the project, we would be very grateful. Member project of [SciSharp STACK](https://github.com/SciSharp) which is the .NET based ecosystem of open-source software for mathematics, science, and engineering. - -Scan QR code to join TIM group: - -![SciSharp STACK](https://raw.githubusercontent.com/SciSharp/TensorFlow.NET/master/docs/TIM.jpg) diff --git a/docs/architecture/assets/routing.drawio b/docs/architecture/assets/routing.drawio index 7df280ab..0838d91c 100644 --- a/docs/architecture/assets/routing.drawio +++ b/docs/architecture/assets/routing.drawio @@ -1,37 +1,37 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -39,18 +39,18 @@ - + - + - + - + diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs index c1870d34..04159b95 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs @@ -1,7 +1,18 @@ +using BotSharp.Abstraction.Models; + namespace BotSharp.Abstraction.Functions.Models; -public class ParameterPropertyDef +public class ParameterPropertyDef : NameDesc { + public ParameterPropertyDef(string name, string description, string type = "string") + : base(name, description) + { + Type = type; + } + + /// + /// string, number, object + /// + [JsonPropertyName("type")] public string Type { get; set; } = "string"; - public string Description { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs index b487620b..d86c6298 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs @@ -11,9 +11,9 @@ public interface IRoutingHandler { string Name { get; } string Description { get; } - bool IsReasoning { get => false; } - bool Enabled { get => true; } - List Parameters { get => new List(); } + bool IsReasoning => false; + bool Enabled => true; + List Parameters => new List(); void SetRouter(Agent router) { } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 8b4ebaf9..802f8f73 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -5,6 +5,7 @@ namespace BotSharp.Abstraction.Routing; public interface IRoutingService { List Dialogs { get; } + void ResetRecursiveCounter(); void RefreshDialogs(); Task GetNextInstruction(); Task InvokeAgent(string agentId); diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs index cd18008a..49b38274 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs @@ -1,7 +1,15 @@ +using BotSharp.Abstraction.Routing.Settings; + namespace BotSharp.Abstraction.Routing.Models; public class RoutingContext { + private readonly RoutingSettings _setting; + public RoutingContext(RoutingSettings setting) + { + _setting = setting; + } + private Stack _stack { get; set; } = new Stack(); @@ -13,8 +21,14 @@ public class RoutingContext public string OriginAgentId => _stack.Last(); - public string CurrentAgentId - => _stack.Peek(); + public string GetCurrentAgentId() + { + if (_stack.Count == 0) + { + _stack.Push(_setting.RouterId); + } + return _stack.Peek(); + } public void Push(string agentId) { diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs index f98c26e2..8ea5269d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingItem.cs @@ -1,4 +1,4 @@ -using BotSharp.Abstraction.Models; +using BotSharp.Abstraction.Functions.Models; namespace BotSharp.Abstraction.Routing.Models; @@ -14,5 +14,5 @@ public class RoutingItem public string Description { get; set; } = string.Empty; [JsonPropertyName("required_fields")] - public List RequiredFields { get; set; } = new List(); + public List RequiredFields { get; set; } = new List(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRule.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRule.cs index eef0bbb2..5aa7667a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRule.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRule.cs @@ -10,6 +10,10 @@ public class RoutingRule public string Field { get; set; } public string Description { get; set; } + /// + /// Field type: string, number, object + /// + public string Type { get; set; } public bool Required { get; set; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 58f71bd0..e80b5b1e 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -18,7 +18,12 @@ public partial class ConversationService var agentService = _services.GetRequiredService(); Agent agent = await agentService.LoadAgent(agentId); - _logger.LogInformation($"[{agent.Name}] {incoming.Role}: {incoming.Content}"); + var message = $"Received [{agent.Name}] {incoming.Role}: {incoming.Content}"; +#if DEBUG + Console.WriteLine(message, Color.OrangeRed); +#else + _logger.LogInformation(message); +#endif incoming.CurrentAgentId = agent.Id; @@ -56,6 +61,7 @@ public partial class ConversationService var statistics = _services.GetRequiredService(); statistics.PrintStatistics(); + routing.ResetRecursiveCounter(); routing.RefreshDialogs(); return true; @@ -81,16 +87,15 @@ public partial class ConversationService private async Task HandleAssistantMessage(RoleDialogModel message, Func onMessageReceived) { - var routingSetting = _services.GetRequiredService(); - var agentName = routingSetting.RouterId == message.CurrentAgentId ? - "Router" : - (await _services.GetRequiredService().GetAgent(message.CurrentAgentId)).Name; + var agentService = _services.GetRequiredService(); + var agent = await agentService.GetAgent(message.CurrentAgentId); + var agentName = agent.Name; var text = message.Role == AgentRole.Function ? - $"[{agentName}] {message.FunctionName}: {message.Content}" : - $"[{agentName}] {message.Role}: {message.Content}"; + $"Sending [{agentName}] {message.FunctionName}: {message.Content}" : + $"Sending [{agentName}] {message.Role}: {message.Content}"; #if DEBUG - Console.WriteLine(text, Color.Pink); + Console.WriteLine(text, Color.Yellow); #else _logger.LogInformation(text); #endif diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index f0b28d3b..9d5fb17b 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -46,8 +46,7 @@ public class ConversationStorage : IConversationStorage } else { - var routingSetting = _services.GetRequiredService(); - var agentName = routingSetting.RouterId == agentId ? "Router" : db.GetAgent(agentId)?.Name; + var agentName = db.GetAgent(agentId)?.Name; sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agentId}|{agentName}|"); var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs index 29d640e3..162a8e0a 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs @@ -29,7 +29,7 @@ public class RouteToAgentFn : IFunctionCallback if (!string.IsNullOrEmpty(args.OriginalAgent) && args.OriginalAgent.Length < 32) { var db = _services.GetRequiredService(); - var originalAgent = db.GetAgents(args.OriginalAgent).FirstOrDefault(); + var originalAgent = db.GetAgents(name: args.OriginalAgent).FirstOrDefault(); if (originalAgent != null) { _context.Push(originalAgent.Id); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index 2ab5a595..0e959eb4 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -30,18 +30,19 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst) { + var context = _services.GetRequiredService(); + var function = _services.GetServices().FirstOrDefault(x => x.Name == inst.Function); var message = new RoleDialogModel(AgentRole.Function, inst.Question) { FunctionName = inst.Function, FunctionArgs = JsonSerializer.Serialize(inst), - CurrentAgentId = routing.Dialogs.Last().CurrentAgentId + CurrentAgentId = context.GetCurrentAgentId(), }; var ret = await function.Execute(message); - var context = _services.GetRequiredService(); - var result = await routing.InvokeAgent(context.CurrentAgentId); + var result = await routing.InvokeAgent(context.GetCurrentAgentId()); // Keep last message data for debug result.ExecutionData = result.ExecutionData ?? message.ExecutionData; result.FunctionName = result.FunctionName ?? message.FunctionName; diff --git a/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs b/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs index 5eb4e306..2ee8e023 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RouterInstance.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing; @@ -89,8 +90,9 @@ public class RouterInstance : IRouterInstance AgentId = x.Id, Description = x.Description, Name = x.Name, - RequiredFields = x.RoutingRules.Where(x => x.Required) - .Select(x => new NameDesc(x.Field, x.Description)) + RequiredFields = x.RoutingRules + .Where(x => x.Required) + .Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type)) .ToList() }).ToArray(); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs index 7304f5fe..515d6922 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetNextInstruction.cs @@ -94,7 +94,7 @@ public partial class RoutingService } #if DEBUG - Console.WriteLine(response.Content, Color.Gray); + Console.WriteLine(response.Content, Color.Green); #else _logger.LogInformation(response.Content); #endif diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs index aa1a2523..992afbcd 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs @@ -6,12 +6,13 @@ namespace BotSharp.Core.Routing; public partial class RoutingService { const int MAXIMUM_RECURSION_DEPTH = 3; - int CurrentRecursionDepth = 0; + private int _currentRecursionDepth = 0; public async Task InvokeAgent(string agentId) { - CurrentRecursionDepth++; - if (CurrentRecursionDepth > MAXIMUM_RECURSION_DEPTH) + _currentRecursionDepth++; + if (_currentRecursionDepth > MAXIMUM_RECURSION_DEPTH) { + _logger.LogWarning($"Current recursive call depth greater than {MAXIMUM_RECURSION_DEPTH}, which will cause unexpected result."); return Dialogs.Last(); } @@ -23,13 +24,15 @@ public partial class RoutingService if (response.Role == AgentRole.Function) { - await InvokeFunction(agent, response); + return await InvokeFunction(agent, response); + } + else + { + return response; } - - return response; } - private async Task InvokeFunction(Agent agent, RoleDialogModel response) + private async Task InvokeFunction(Agent agent, RoleDialogModel response) { // execute function // Save states @@ -41,11 +44,12 @@ public partial class RoutingService if (string.IsNullOrEmpty(response.Content)) { - response.Content = response.ExecutionResult; + response.Content = response.ExecutionResult ?? JsonSerializer.Serialize(response.ExecutionData); } Dialogs.Add(response); + // Pass execution result to LLM to get response if (!response.StopCompletion) { // Find response template @@ -54,17 +58,14 @@ public partial class RoutingService if (!string.IsNullOrEmpty(responseTemplate)) { response.Role = AgentRole.Assistant; - response.Content = responseTemplate; + response.Content = responseTemplate.Trim(); } else { - var recursiveResponse = await InvokeAgent(response.CurrentAgentId); - response.Role = recursiveResponse.Role; - response.Content = recursiveResponse.Content; - response.ExecutionResult = recursiveResponse.ExecutionResult; - response.ExecutionData = recursiveResponse.ExecutionData ?? response.ExecutionData; - response.StopCompletion = recursiveResponse.StopCompletion; + response = await InvokeAgent(response.CurrentAgentId); } } + + return response; } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index cb0e5246..9159434d 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -25,6 +25,11 @@ public partial class RoutingService : IRoutingService } } + public void ResetRecursiveCounter() + { + _currentRecursionDepth = 0; + } + public void RefreshDialogs() { _dialogs = null; @@ -88,7 +93,6 @@ public partial class RoutingService : IRoutingService if (handler == null) { handler = handlers.FirstOrDefault(x => x.Name == "get_next_instruction"); - router.Instruction += $"\r\n{AgentRole.System}: the function must be one of {string.Join(",", _routerInstance.GetHandlers().Select(x => x.Name))}."; continue; } handler.SetRouter(router); @@ -96,8 +100,6 @@ public partial class RoutingService : IRoutingService result = await handler.Handle(this, inst); - message = result.Content.Replace("\r\n", " "); - stop = !_settings.EnableReasoning; } diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs index 06d6e9db..64a8608f 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Templating; @@ -21,6 +22,7 @@ public class TemplateRender : ITemplateRender _options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.SnakeCase; _options.MemberAccessStrategy.Register(); + _options.MemberAccessStrategy.Register(); _options.MemberAccessStrategy.Register(); _options.MemberAccessStrategy.Register(); _options.MemberAccessStrategy.Register(); diff --git a/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid index 5989c175..8416e7ae 100644 --- a/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid +++ b/src/WebStarter/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instruction.liquid @@ -20,10 +20,10 @@ Response: { "function": "{{ handler.name }}", * Agent: {{ agent.name }} {{ agent.description}} {% if agent.required_fields and agent.required_fields != empty -%} -Required args: { +Required args: {% for f in agent.required_fields -%} - "{{ f.name }}": "{{ f.description }}"{{ ",\r\n " }} - {%- endfor %}} + - {{ f.name }} ({{ f.type }}): {{ f.description }}{{ "\r\n " }} + {%- endfor %} {%- endif %} {% endfor %} diff --git a/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid b/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid index dec5c66b..62041ece 100644 --- a/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid +++ b/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid @@ -1,10 +1,10 @@ {% assign pizza_type = pizza_type | downcase %} {% if pizza_type contains "cheese" -%} - The price for a slice of {{pizza_type}} pizza is {{ cheese_unit_price }}. Would you like to proceed the order? + The price for a slice of {{pizza_type}} pizza is ${{ cheese_unit_price }}. Would you like to proceed the order? {%- elsif pizza_type contains "pepperoni" -%} - The price for a slice of {{pizza_type}} pizza is {{ pepperoni_unit_price }}. Would you like to proceed the order? + The price for a slice of {{pizza_type}} pizza is ${{ pepperoni_unit_price }}. Would you like to proceed the order? {%- elsif pizza_type contains "margherita" -%} - The price for a slice of {{pizza_type}} pizza is {{ margherita_unit_price }}. Would you like to proceed the order? + The price for a slice of {{pizza_type}} pizza is ${{ margherita_unit_price }}. Would you like to proceed the order? {%- else -%} We don't have {{pizza_type}} pizza, would you like something else? {%- endif %} diff --git a/src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json b/src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json index 22eaa329..230fbca1 100644 --- a/src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json +++ b/src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json @@ -9,6 +9,7 @@ { "field": "order_number", "description": "order number", + "type": "string", "required": true, "redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd" } diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs index 071f9654..540aaa51 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs @@ -10,9 +10,9 @@ public class GetPizzaPricesFn : IFunctionCallback { message.ExecutionData = new { - pepperoni_unit_price = "$3.2", - cheese_unit_price = "$3.5", - margherita_unit_price = "$3.8", + pepperoni_unit_price = 3.2, + cheese_unit_price = 3.5, + margherita_unit_price = 3.8, }; return true; } diff --git a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs index 3e0a8171..096ead98 100644 --- a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs +++ b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs @@ -7,13 +7,6 @@ public class PizzaBotPlugin : IBotSharpPlugin { public void RegisterDI(IServiceCollection services, IConfiguration config) { - // Register callback function - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - // Register hooks services.AddScoped(); }