diff --git a/docs/architecture/agent-utility.md b/docs/architecture/agent-utility.md
new file mode 100644
index 00000000..b25ee396
--- /dev/null
+++ b/docs/architecture/agent-utility.md
@@ -0,0 +1,459 @@
+# Agent Utility
+
+## Introduction
+This document aims to introduce the agent utility concept and provide an instruction on adding custom agent utilities in AI project “BotSharp”. We will start by explaining the mechanism of agent utility in [Section 2](#agent-utility). Then, we illustrate the custom agent utility setup and integration in [Section 3](#agent-utility-setup) and [Section 4](#agent-utility-integration), respectively. A use case will be demonstrated in [Section 5](#use-case-demo). We wrap up the document with a short summary.
+
+## Agent Utility
+The agent utility is a unique feature that can be integrated into agent to enhance its capability. Its core principle is that it can dynamically and seamlessly add extra prompts and add task-oriented functions (or tools) during conversation, without disrupting the agent’s primary purpose. In other words, the agent utility can perform extra tasks based on the context of conversation. Typical examples of agent utility include reading images/pdf, generating image, and sending http requests. [Fig 2.1.1](#agent-utility-example) demonstrates an example of these utilities. In this example, “Chatbot” is a simple agent to answer user questions and the utilities extend its capability to explain image content, generate requested image, and send a specific http request.
+
+
+

+
+
+
+## Agent Utility Setup
+In this section, we outline the steps to set up a custom agent utility. We start with the basic code structure and then add essential utility data, such as prompts and functions. The utility hooks are used to incorporate the utility into the agent. Finally, we give a brief overview of the utility implementation.
+
+### Basic Code Structure
+The basic code structure of a typical agent utility includes prompt/function data, hooks, and function implementation. We can add specific utility prompts and functions in different projects. Note that the agent “6745151e-6d46-4a02-8de4-1c4f21c7da95” is considered as a dedicated utility assistant, and every prompt and function can be optionally used as a utility. [Fig 3.1.1](#agent-utility-code-structure) presents the structure of prompt, function, hooks, and implementation of an http utility.
+
+
+

+
+
+
+### Utility Data
+For a typical agent utility, it is essential to add at least a prompt and a function. The prompt is added under the “templates” folder and its recommended name is “[function name].fn.liquid”, while the function is added under the “functions” folder and its recommended name is “[function name].json”. Once we compile the project, we can find the aggregated utility assistant folder at location: “\BotSharp\src\WebStarter\bin\Debug\net8.0\data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95”.
+
+### Utility Hooks
+The utility hooks are used to connect the agent utility to the agent system. The code snippet below demonstrates an implementation of the http utility hook, where we define the utility name, prompts and functions. Note that each utility can be used across different agents.
+
+```csharp
+public class HttpHandlerPlugin : IBotSharpPlugin
+{
+ public string Id => "2c1eb1c4-16e5-4c65-8ee4-032324c26b81";
+ public string Name => "HTTP Handler";
+ public string Description => "Empower agent to handle HTTP request in RESTful API or GraphQL";
+ public string IconUrl => "https://lirp.cdn-website.com/6f8d6d8a/dms3rep/multi/opt/API_Icon-640w.png";
+ public string[] AgentIds => new[] { "87c458fc-ec5f-40ae-8ed6-05dda8a07523" };
+
+ public void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+ services.AddScoped(provider =>
+ {
+ var settingService = provider.GetRequiredService();
+ return settingService.Bind("HttpHandler");
+ });
+
+ services.AddScoped();
+ }
+}
+```
+
+The agent hook is used to append the utility prompt and function during the conversation. Note that the utility data is only allowed to be included in the context of conversation. The utility mechanism is implemented in the “OnAgentUtilityLoaded” hook, and it is invoked when we load any agent.
+
+```csharp
+public async Task LoadAgent(string id)
+{
+ if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString())
+ {
+ return null;
+ }
+
+ var hooks = _services.GetServices();
+
+ // Before agent is loaded.
+ foreach (var hook in hooks)
+ {
+ if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != id)
+ {
+ continue;
+ }
+
+ hook.OnAgentLoading(ref id);
+ }
+
+ var agent = await GetAgent(id);
+ if (agent == null)
+ {
+ return null;
+ }
+
+ await InheritAgent(agent);
+ OverrideInstructionByChannel(agent);
+ AddOrUpdateParameters(agent);
+
+ // Populate state into dictionary
+ agent.TemplateDict = new Dictionary();
+ PopulateState(agent.TemplateDict);
+
+ // After agent is loaded
+ foreach (var hook in hooks)
+ {
+ if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != id)
+ {
+ continue;
+ }
+
+ hook.SetAget(agent);
+
+ if (!string.IsNullOrEmpty(agent.Instruction))
+ {
+ hook.OnInstructionLoaded(agent.Instruction, agent.TemplateDict);
+ }
+
+ if (agent.Functions != null)
+ {
+ hook.OnFunctionsLoaded(agent.Functions);
+ }
+
+ if (agent.Samples != null)
+ {
+ hook.OnSamplesLoaded(agent.Samples);
+ }
+
+ hook.OnAgentUtilityLoaded(agent);
+ hook.OnAgentLoaded(agent);
+ }
+
+ _logger.LogInformation($"Loaded agent {agent}.");
+
+ return agent;
+}
+```
+
+### Utility Function Implementation
+Here we introduce a simple utility function implementation. The actual content depends on what task you want this utility to fulfill. The code snippet below illustrates the implementation of the “handle http request” function. Note that the property “Name” must be consistent with the function added in the utility data. The “Indication” is an optional property whose content will be displayed in the chat while the user is waiting for the assistant response.
+
+```csharp
+public class HandleHttpRequestFn : IFunctionCallback
+{
+ public string Name => "handle_http_request";
+ public string Indication => "Handling http request";
+
+ private readonly IServiceProvider _services;
+ private readonly ILogger _logger;
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly IHttpContextAccessor _context;
+ private readonly BotSharpOptions _options;
+
+ public HandleHttpRequestFn(IServiceProvider services,
+ ILogger logger,
+ IHttpClientFactory httpClientFactory,
+ IHttpContextAccessor context,
+ BotSharpOptions options)
+ {
+ _services = services;
+ _logger = logger;
+ _httpClientFactory = httpClientFactory;
+ _context = context;
+ _options = options;
+ }
+
+ public async Task Execute(RoleDialogModel message)
+ {
+ var args = JsonSerializer.Deserialize(message.FunctionArgs, _options.JsonSerializerOptions);
+ var url = args?.RequestUrl;
+ var method = args?.HttpMethod;
+ var content = args?.RequestContent;
+
+ try
+ {
+ var response = await SendHttpRequest(url, method, content);
+ var responseContent = await HandleHttpResponse(response);
+ message.Content = responseContent;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ var msg = $"Fail when sending http request. Url: {url}, method: {method}, content: {content}";
+ _logger.LogWarning($"{msg}\n(Error: {ex.Message})");
+ message.Content = msg;
+ return false;
+ }
+ }
+
+ private async Task SendHttpRequest(string? url, string? method, string? content)
+ {
+ if (string.IsNullOrEmpty(url)) return null;
+
+ using var client = _httpClientFactory.CreateClient();
+ AddRequestHeaders(client);
+
+ var (uri, request) = BuildHttpRequest(url, method, content);
+ var response = await client.SendAsync(request);
+ if (response == null || !response.IsSuccessStatusCode)
+ {
+ _logger.LogWarning($"Response status code: {response?.StatusCode}");
+ }
+
+ return response;
+ }
+
+ private void AddRequestHeaders(HttpClient client)
+ {
+ client.DefaultRequestHeaders.Add("Authorization", $"{_context.HttpContext.Request.Headers["Authorization"]}");
+
+ var settings = _services.GetRequiredService();
+ var origin = !string.IsNullOrEmpty(settings.Origin) ? settings.Origin : $"{_context.HttpContext.Request.Headers["Origin"]}";
+ if (!string.IsNullOrEmpty(origin))
+ {
+ client.DefaultRequestHeaders.Add("Origin", origin);
+ }
+ }
+
+ private (Uri, HttpRequestMessage) BuildHttpRequest(string url, string? method, string? content)
+ {
+ var httpMethod = GetHttpMethod(method);
+ StringContent httpContent;
+
+ var requestUrl = url;
+ if (httpMethod == HttpMethod.Get)
+ {
+ httpContent = BuildHttpContent("{}");
+ requestUrl = BuildQuery(url, content);
+ }
+ else
+ {
+ httpContent = BuildHttpContent(content);
+ }
+
+ if (!Uri.TryCreate(requestUrl, UriKind.Absolute, out var uri))
+ {
+ var settings = _services.GetRequiredService();
+ var baseUri = new Uri(settings.BaseAddress);
+ uri = new Uri(baseUri, requestUrl);
+ }
+
+ return (uri, new HttpRequestMessage
+ {
+ RequestUri = uri,
+ Method = httpMethod,
+ Content = httpContent
+ });
+ }
+
+ private HttpMethod GetHttpMethod(string? method)
+ {
+ var localMethod = method?.Trim()?.ToUpper();
+ HttpMethod matchMethod;
+
+ switch (localMethod)
+ {
+ case "GET":
+ matchMethod = HttpMethod.Get;
+ break;
+ case "DELETE":
+ matchMethod = HttpMethod.Delete;
+ break;
+ case "PUT":
+ matchMethod = HttpMethod.Put;
+ break;
+ case "Patch":
+ matchMethod = HttpMethod.Patch;
+ break;
+ default:
+ matchMethod = HttpMethod.Post;
+ break;
+ }
+ return matchMethod;
+ }
+
+ private StringContent BuildHttpContent(string? content)
+ {
+ var str = string.Empty;
+ try
+ {
+ var json = JsonSerializer.Deserialize(content ?? "{}", _options.JsonSerializerOptions);
+ str = JsonSerializer.Serialize(json, _options.JsonSerializerOptions);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when build http content: {content}\n(Error: {ex.Message})");
+ }
+
+ return new StringContent(str, Encoding.UTF8, MediaTypeNames.Application.Json);
+ }
+
+ private string BuildQuery(string url, string? content)
+ {
+ if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(content)) return url;
+
+ try
+ {
+ var queries = new List();
+ var json = JsonSerializer.Deserialize(content, _options.JsonSerializerOptions);
+ var root = json.RootElement;
+ foreach (var prop in root.EnumerateObject())
+ {
+ var name = prop.Name.Trim();
+ var value = prop.Value.ToString().Trim();
+ if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value))
+ {
+ continue;
+ }
+
+ queries.Add($"{name}={value}");
+ }
+
+ if (!queries.IsNullOrEmpty())
+ {
+ url += $"?{string.Join('&', queries)}";
+ }
+ return url;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when building url query. Url: {url}, Content: {content}\n(Error: {ex.Message})");
+ return url;
+ }
+ }
+
+ private async Task HandleHttpResponse(HttpResponseMessage? response)
+ {
+ if (response == null) return string.Empty;
+
+ return await response.Content.ReadAsStringAsync();
+ }
+}
+```
+
+### Utility Inheritance
+Here we introduce a new feature: utility inheritance. As we are using the routing-based multi-agent architecture, different agents may come into the call stack while we are handling user requests. With the utility inheritance, the agent can not only use the utilities added to itself but also inherit the utilities from the entry agent, which is the first agent that comes into the call stack, such as a router agent. [Fig 3.5.1](#routing-architecture) illustrates a typical example of routing-based multi-agent architecture, where “Pizza Bot” is the router and "Order Inquery", "Ordering", "Payment" are task agents. When the user starts chatting, “Pizza Bot” first comes into the call stack, with "Order Inquery", "Ordering", or "Payment" joining next depending on what the user actually requests. For example, if we allow "Order Inquery" to inherit utilities, all the utilities from itself as well as “Pizza Bot” will be invoked once the "Order Inquery" agent is in action.
+
+
+

+
+
+
+### Agent Setup
+Here we introduce the agent setup with utilities and inheritance. As we introduced in [Section 3.3](#utility-hooks), each utility of an agent is structured with utility name, functions, and prompts. [Fig 3.6.1](#router-utility-ui) and [Fig 3.6.2](#task-agent-utility-ui) presents the utility configuration and utility inheritance of “Pizza Bot” and “Order Inquery”, respectively. As is displayed, we can apply the utility configuration and inheritance via agent files or agent detail ui. Note that we can uncheck the box to disable a utility ([Fig 3.6.2](#router-utility-ui)).
+
+```json
+{
+ "id": "8970b1e5-d260-4e2c-90b1-f1415a257c18",
+ "name": "Pizza Bot",
+ "description": "AI assistant that can help customer place pizza order.",
+ "type": "routing",
+ "inheritAgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
+ "createdDateTime": "2023-08-18T10:39:32.2349685Z",
+ "updatedDateTime": "2023-08-18T14:39:32.2349686Z",
+ "iconUrl": "https://cdn-icons-png.flaticon.com/512/6978/6978255.png",
+ "disabled": false,
+ "isPublic": true,
+ "profiles": [ "pizza" ],
+ "utilities": [
+ {
+ "name": "http_handler",
+ "functions": [
+ { "name": "handle_http_request" }
+ ],
+ "templates": [
+ { "name": "handle_http_request.fn" }
+ ]
+ }
+ ],
+ "routingRules": [
+ {
+ "type": "reasoner",
+ "field": "NaiveReasoner"
+ }
+ ]
+}
+```
+
+
+
+
+

+
+
+
+
+
+```json
+{
+ "name": "Order Inquiry",
+ "description": "Check the order status like payment, delivery or baking.",
+ "createdDateTime": "2023-08-18T14:39:32.2349685Z",
+ "updatedDateTime": "2023-08-18T14:39:32.2349686Z",
+ "id": "b284db86-e9c2-4c25-a59e-4649797dd130",
+ "disabled": false,
+ "isPublic": true,
+ "mergeUtility": true,
+ "profiles": [ "pizza" ]
+}
+```
+
+
+
+

+
+
+
+
+## Agent Utility Integration
+In this section, we outline the steps to integrate a custom agent utility, including registering plugin, registering assembly, and adding project reference.
+
+The code snippet below presents the “Http Handler Plugin” in the “BotSharp.Plugin.HttpHandler”, where we can register the hooks and other essential settings. Note that there is no need to register the function here, since it is automatically registered on the application level.
+
+```csharp
+public class HttpHandlerPlugin : IBotSharpPlugin
+{
+ public string Id => "2c1eb1c4-16e5-4c65-8ee4-032324c26b81";
+ public string Name => "HTTP Handler";
+ public string Description => "Empower agent to handle HTTP request in RESTful API or GraphQL";
+ public string IconUrl => "https://lirp.cdn-website.com/6f8d6d8a/dms3rep/multi/opt/API_Icon-640w.png";
+ public string[] AgentIds => new[] { "87c458fc-ec5f-40ae-8ed6-05dda8a07523" };
+
+ public void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+ services.AddScoped(provider =>
+ {
+ var settingService = provider.GetRequiredService();
+ return settingService.Bind("HttpHandler");
+ });
+
+ services.AddScoped();
+ }
+}
+```
+
+[Fig 4.1.2](#register-assembly) demonstrates the utility assembly registration in “appsettings.json”. It is important to note that we are required to add the project reference to the Startup project, e.g., WebStarter. Moreover, we are required to add any new custom agent utility in the “Plugin” folder instead of the “BotSharp” folder.
+
+
+

+
+
+
+
+## Use Case Demo
+In this section, we demonstrate an http utility. After we set up and integrate the custom agent utility in backend, we can start the BotSharp-UI and go to any specific agent. [Fig 5.1.1](#add-utility) shows an example of the “Chatbot” agent, where we can add any registered utilities in the highlight section.
+
+
+

+
+
+
+Once we add the utility, we can initialize a conversation by clicking the bot icon at the top left corner. [Fig 5.1.2](#chat-window-demo) shows the conversation window, where we can find the number of utilities at the left panel. We can also click the agent name to go back to the agent page.
+
+
+

+
+
+
+Here we use dummy rest APIs (source: https://dummy.restapiexample.com/) for the demo purpose. [Fig 5.1.3](#dummy-http) displays the various http requests sent in the conversation with “Chatbot”. We can see that the “Http Handler” utility has successfully extends the agent to send http request and receive response.
+
+
+

+
+
+
+## Summary
+In this document, we introduce the agent utility concept and provide a step-by-step instruction on adding custom agent utilities in AI project “BotSharp”.
+
+The agent utility is designed for enhancing the agent capability to perform dedicated tasks, such as sending http request, reading images, and generating images, by adding extra prompts and functions.
+
+The agent utility setup and integration are explained step by step in [Section 3](#agent-utility-setup) and [Section 4](#agent-utility-integration), respectively.
+
+We end up the document by demonstrating the Http utility, where we prove the utility can handle various http requests in the chat with agents.
diff --git a/docs/architecture/assets/agent-utility/add-utility.png b/docs/architecture/assets/agent-utility/add-utility.png
new file mode 100644
index 00000000..940f00ed
Binary files /dev/null and b/docs/architecture/assets/agent-utility/add-utility.png differ
diff --git a/docs/architecture/assets/agent-utility/agent-utility-code-structure.png b/docs/architecture/assets/agent-utility/agent-utility-code-structure.png
new file mode 100644
index 00000000..9232f90e
Binary files /dev/null and b/docs/architecture/assets/agent-utility/agent-utility-code-structure.png differ
diff --git a/docs/architecture/assets/agent-utility/agent-utility-example.png b/docs/architecture/assets/agent-utility/agent-utility-example.png
new file mode 100644
index 00000000..81d1a7d4
Binary files /dev/null and b/docs/architecture/assets/agent-utility/agent-utility-example.png differ
diff --git a/docs/architecture/assets/agent-utility/chat-window-demo.png b/docs/architecture/assets/agent-utility/chat-window-demo.png
new file mode 100644
index 00000000..e31c6953
Binary files /dev/null and b/docs/architecture/assets/agent-utility/chat-window-demo.png differ
diff --git a/docs/architecture/assets/agent-utility/dummy-http.png b/docs/architecture/assets/agent-utility/dummy-http.png
new file mode 100644
index 00000000..d5ca8df7
Binary files /dev/null and b/docs/architecture/assets/agent-utility/dummy-http.png differ
diff --git a/docs/architecture/assets/agent-utility/register-assembly.png b/docs/architecture/assets/agent-utility/register-assembly.png
new file mode 100644
index 00000000..7c5a478e
Binary files /dev/null and b/docs/architecture/assets/agent-utility/register-assembly.png differ
diff --git a/docs/architecture/assets/agent-utility/router-utility-ui.png b/docs/architecture/assets/agent-utility/router-utility-ui.png
new file mode 100644
index 00000000..7a2fc465
Binary files /dev/null and b/docs/architecture/assets/agent-utility/router-utility-ui.png differ
diff --git a/docs/architecture/assets/agent-utility/routing-arch.png b/docs/architecture/assets/agent-utility/routing-arch.png
new file mode 100644
index 00000000..6485c3bb
Binary files /dev/null and b/docs/architecture/assets/agent-utility/routing-arch.png differ
diff --git a/docs/architecture/assets/agent-utility/task-agent-utility-ui.png b/docs/architecture/assets/agent-utility/task-agent-utility-ui.png
new file mode 100644
index 00000000..9838673f
Binary files /dev/null and b/docs/architecture/assets/agent-utility/task-agent-utility-ui.png differ
diff --git a/docs/index.rst b/docs/index.rst
index b7c8b78e..d9dac903 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -115,6 +115,7 @@ The main documentation for the site is organized into the following sections:
architecture/plugin
architecture/hooks
architecture/routing
+ architecture/agent-utility
architecture/logging
architecture/data-persistence
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs
index 496466e2..7759a4bf 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs
@@ -71,7 +71,14 @@ public abstract class AgentHookBase : IAgentHook
var (functions, templates) = GetUtilityContent(agent);
- agent.Functions.AddRange(functions);
+ foreach (var fn in functions)
+ {
+ if (!agent.Functions.Any(x => x.Name.Equals(fn.Name, StringComparison.OrdinalIgnoreCase)))
+ {
+ agent.Functions.Add(fn);
+ }
+ }
+
foreach (var prompt in templates)
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs
index c3c69f08..7f22c8f2 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs
@@ -51,4 +51,14 @@ public class BuiltInAgentId
/// Evaluate prompt and conversation
///
public const string Evaluator = "dfd9b46d-d00c-40af-8a75-3fbdc2b89869";
+
+ ///
+ /// Translates user-defined natural language rules into programmatic code
+ ///
+ public const string RuleEncoder = "6acfb93c-3412-402e-9ba5-c5d3cd8f0161";
+
+ ///
+ /// Schedule job
+ ///
+ public const string Crontab = "c2o139da-a62a-4355-8605-fdf0ffaca58e";
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs
index cf0defbc..d03a4208 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs
@@ -7,4 +7,5 @@ public class ConversationChannel
public const string Phone = "phone";
public const string Messenger = "messenger";
public const string Email = "email";
+ public const string Cron = "cron";
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/IDistributedLocker.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/IDistributedLocker.cs
new file mode 100644
index 00000000..2f5a8069
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/IDistributedLocker.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Infrastructures;
+
+public interface IDistributedLocker
+{
+ bool Lock(string resource, Action action, int timeout = 30);
+ Task LockAsync(string resource, Func action, int timeout = 30);
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs
index 238dbffc..4dcff7cb 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs
@@ -5,6 +5,9 @@ public interface IPlanningHook
Task GetSummaryAdditionalRequirements(string planner, RoleDialogModel message)
=> Task.FromResult(string.Empty);
+ Task OnSourceCodeGenerated(string planner, RoleDialogModel msg, string language)
+ => Task.CompletedTask;
+
Task OnPlanningCompleted(string planner, RoleDialogModel msg)
=> Task.CompletedTask;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs
index 35e2e1d3..7c6c738a 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs
@@ -15,4 +15,9 @@ public class RoleFilter
{
return new RoleFilter();
}
+
+ public bool IsInit()
+ {
+ return Names.IsNullOrEmpty();
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs
new file mode 100644
index 00000000..49ffe54c
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs
@@ -0,0 +1,8 @@
+using BotSharp.Core.Crontab.Models;
+
+namespace BotSharp.Core.Crontab.Abstraction;
+
+public interface ICrontabHook
+{
+ Task OnCronTriggered(CrontabItem item);
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabService.cs
new file mode 100644
index 00000000..6a18018d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabService.cs
@@ -0,0 +1,9 @@
+using BotSharp.Core.Crontab.Models;
+
+namespace BotSharp.Core.Crontab.Abstraction;
+
+public interface ICrontabService
+{
+ Task> GetCrontable();
+ Task ScheduledTimeArrived(CrontabItem item);
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/BotSharp.Core.Crontab.csproj b/src/Infrastructure/BotSharp.Core.Crontab/BotSharp.Core.Crontab.csproj
new file mode 100644
index 00000000..f1c70ad7
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/BotSharp.Core.Crontab.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/CrontabPlugin.cs b/src/Infrastructure/BotSharp.Core.Crontab/CrontabPlugin.cs
new file mode 100644
index 00000000..a449cbbb
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/CrontabPlugin.cs
@@ -0,0 +1,40 @@
+/*****************************************************************************
+ Copyright 2024 Written by Haiping Chen. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+******************************************************************************/
+
+namespace BotSharp.Core.Crontab;
+
+///
+/// Crontab plugin is a time-based job scheduler in agent framework.
+/// The cron system is used for automating repetitive tasks, such as trigger AI Agent to do specific task periodically.
+///
+public class CrontabPlugin : IBotSharpPlugin
+{
+ public string Id => "3155c15e-28d3-43f7-8ead-fc43324ec21a";
+ public string Name => "BotSharp Crontab";
+ public string Description => "Crontab plugin is a time-based job scheduler in agent framework. The cron system is used to trigger AI Agent to do specific task periodically.";
+ public string IconUrl => "https://icon-library.com/images/stop-watch-icon/stop-watch-icon-10.jpg";
+
+ public string[] AgentIds =
+ [
+ BuiltInAgentId.Crontab
+ ];
+
+ public void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+ services.AddScoped();
+ services.AddHostedService();
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Models/CrontabItem.cs b/src/Infrastructure/BotSharp.Core.Crontab/Models/CrontabItem.cs
new file mode 100644
index 00000000..50cafd02
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Models/CrontabItem.cs
@@ -0,0 +1,14 @@
+namespace BotSharp.Core.Crontab.Models;
+
+public class CrontabItem
+{
+ public string UserId { get; set; } = null!;
+ public string AgentId { get; set; } = null!;
+ public string Topic { get; set; } = null!;
+ public string Cron { get; set; } = null!;
+
+ public override string ToString()
+ {
+ return $"AgentId: {AgentId}, UserId: {UserId}, Topic: {Topic}";
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs
new file mode 100644
index 00000000..a8b5f6dd
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs
@@ -0,0 +1,77 @@
+/*****************************************************************************
+ Copyright 2024 Written by Haiping Chen. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+******************************************************************************/
+
+using BotSharp.Abstraction.Agents.Enums;
+using BotSharp.Core.Crontab.Models;
+using BotSharp.Core.Infrastructures;
+
+
+/*****************************************************************************
+ Copyright 2024 Written by Haiping Chen. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+******************************************************************************/
+
+using Microsoft.Extensions.Logging;
+
+namespace BotSharp.Core.Crontab.Services;
+
+///
+/// The Crontab service schedules distributed events based on the execution times provided by users.
+/// In a scalable environment, distributed locks are used to ensure that each event is triggered only once.
+///
+public class CrontabService : ICrontabService
+{
+ private readonly IServiceProvider _services;
+ private ILogger _logger;
+
+ public CrontabService(IServiceProvider services, ILogger logger)
+ {
+ _services = services;
+ _logger = logger;
+ }
+
+ public async Task> GetCrontable()
+ {
+ return
+ [
+ new CrontabItem
+ {
+ Cron = "*/30 * * * * *",
+ AgentId = BuiltInAgentId.AIAssistant,
+ }
+ ];
+ }
+
+ public async Task ScheduledTimeArrived(CrontabItem item)
+ {
+ _logger.LogInformation("ScheduledTimeArrived");
+ await HookEmitter.Emit(_services, async hook =>
+ await hook.OnCronTriggered(item)
+ );
+ await Task.Delay(1000 * 10);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
new file mode 100644
index 00000000..0abd050f
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
@@ -0,0 +1,70 @@
+using BotSharp.Abstraction.Infrastructures;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using NCrontab;
+
+namespace BotSharp.Core.Crontab.Services;
+
+public class CrontabWatcher : BackgroundService
+{
+ private readonly ILogger _logger;
+ private readonly IServiceProvider _services;
+
+ public CrontabWatcher(IServiceProvider services, ILogger logger)
+ {
+ _logger = logger;
+ _services = services;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("Crontab Watcher background service is running.");
+
+ using (var scope = _services.CreateScope())
+ {
+ var locker = scope.ServiceProvider.GetRequiredService();
+
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ var delay = Task.Delay(1000, stoppingToken);
+
+ await locker.LockAsync("CrontabWatcher", async () =>
+ {
+ await RunCronChecker(scope.ServiceProvider);
+ });
+
+ await delay;
+ }
+
+ _logger.LogWarning("Crontab Watcher background service is stopped.");
+ }
+ }
+
+ private async Task RunCronChecker(IServiceProvider services)
+ {
+ var cron = services.GetRequiredService();
+ var crons = await cron.GetCrontable();
+ foreach (var item in crons)
+ {
+ var schedule = CrontabSchedule.Parse(item.Cron, new CrontabSchedule.ParseOptions
+ {
+ IncludingSeconds = true // Ensure you account for seconds
+ });
+
+ // Get the current time
+ var currentTime = DateTime.UtcNow;
+
+ // Get the next occurrence from the schedule
+ var nextOccurrence = schedule.GetNextOccurrence(currentTime.AddSeconds(-1));
+
+ // Check if the current time matches the schedule
+ bool matches = currentTime >= nextOccurrence && currentTime < nextOccurrence.AddSeconds(1);
+
+ if (matches)
+ {
+ _logger.LogInformation($"The current time matches the cron expression {item}");
+ cron.ScheduledTimeArrived(item);
+ }
+ }
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Using.cs b/src/Infrastructure/BotSharp.Core.Crontab/Using.cs
new file mode 100644
index 00000000..0da87fb2
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Using.cs
@@ -0,0 +1,7 @@
+global using Microsoft.Extensions.Configuration;
+global using Microsoft.Extensions.DependencyInjection;
+
+global using BotSharp.Abstraction.Agents.Enums;
+global using BotSharp.Abstraction.Plugins;
+global using BotSharp.Core.Crontab.Services;
+global using BotSharp.Core.Crontab.Abstraction;
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/data/agents/c2o139da-a62a-4355-8605-fdf0ffaca58e/agent.json b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/c2o139da-a62a-4355-8605-fdf0ffaca58e/agent.json
new file mode 100644
index 00000000..b17e1e6d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/c2o139da-a62a-4355-8605-fdf0ffaca58e/agent.json
@@ -0,0 +1,24 @@
+{
+ "id": "c2o139da-a62a-4355-8605-fdf0ffaca58e",
+ "name": "Crontab",
+ "description": "Convert the user-specified schedule into a Cron expression, accurate to the second level.",
+ "iconUrl": "https://icon-library.com/images/stop-watch-icon/stop-watch-icon-10.jpg",
+ "type": "task",
+ "createdDateTime": "2024-12-03T00:00:00Z",
+ "updatedDateTime": "2024-12-03T00:00:00Z",
+ "disabled": false,
+ "isPublic": true,
+ "profiles": [ "cron" ],
+ "llmConfig": {
+ "provider": "openai",
+ "model": "gpt-4o-mini"
+ },
+ "routingRules": [
+ {
+ "field": "cron_expression",
+ "required": true,
+ "field_type": "string",
+ "description": "A Cron expression, accurate to the second level."
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core.Rules/BotSharp.Core.Rules.csproj b/src/Infrastructure/BotSharp.Core.Rules/BotSharp.Core.Rules.csproj
new file mode 100644
index 00000000..caef78b8
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Rules/BotSharp.Core.Rules.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/Infrastructure/BotSharp.Core.Rules/RulesPlugin.cs b/src/Infrastructure/BotSharp.Core.Rules/RulesPlugin.cs
new file mode 100644
index 00000000..14177437
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Rules/RulesPlugin.cs
@@ -0,0 +1,19 @@
+namespace BotSharp.Core.Rules;
+
+public class RulesPlugin : IBotSharpPlugin
+{
+ public string Id => "0197c1bc-9ae6-4c56-a305-8a1b4095bebc";
+ public string Name => "BotSharp Rules";
+ public string Description => "Translates user-defined natural language rules into programmatic code and is responsible for executing these rules under user-specified conditions.";
+ public string IconUrl => "https://w7.pngwing.com/pngs/442/614/png-transparent-regulation-computer-icons-regulatory-compliance-medical-device-manufacturing-others-miscellaneous-blue-text-thumbnail.png";
+
+ public string[] AgentIds =
+ [
+ BuiltInAgentId.RuleEncoder
+ ];
+
+ public void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Rules/Using.cs b/src/Infrastructure/BotSharp.Core.Rules/Using.cs
new file mode 100644
index 00000000..d982daae
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Rules/Using.cs
@@ -0,0 +1,4 @@
+global using BotSharp.Abstraction.Agents.Enums;
+global using BotSharp.Abstraction.Plugins;
+global using Microsoft.Extensions.Configuration;
+global using Microsoft.Extensions.DependencyInjection;
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs
index efef5201..d3ace389 100644
--- a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs
@@ -8,8 +8,10 @@ namespace BotSharp.Core.SideCar;
public class BotSharpSideCarPlugin : IBotSharpPlugin
{
public string Id => "06e5a276-bba0-45af-9625-889267c341c9";
- public string Name => "Side Car";
- public string Description => "Provides side car for calling agent cluster in conversation";
+ public string Name => "BotSharp SideCar";
+ public string Description => "Provide side car pattern to to better handle Agent Cluster calls in the same conversation. Agent cluster is composed of multiple Routing Agents.";
+ public string? IconUrl => "https://icons.veryicon.com/png/128/internet--web/2022-alibaba-cloud-product-icon-cloud/aliyuncvc-cloud-video-conference.png";
+
public SettingsMeta Settings => new SettingsMeta("SideCar");
public object GetNewSettingsInstance() => new SideCarSettings();
diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
index ec3ad0fe..4ddc3a43 100644
--- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
+++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
@@ -1,3 +1,19 @@
+/*****************************************************************************
+ Copyright 2024 Written by Jicheng Lu. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+******************************************************************************/
+
using BotSharp.Core.Infrastructures;
namespace BotSharp.Core.SideCar.Services;
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
index c5e33a4b..af8a9324 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
@@ -25,7 +25,7 @@ public static class BotSharpCoreExtensions
config.Bind("Interpreter", interpreterSettings);
services.AddSingleton(x => interpreterSettings);
- services.AddSingleton();
+ services.AddSingleton();
// Register template render
services.AddSingleton();
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
index 152b74ca..eedcdf9a 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
@@ -1,3 +1,19 @@
+/*****************************************************************************
+ Copyright 2024 Written by Jicheng Lu. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+******************************************************************************/
+
using BotSharp.Abstraction.Conversations.Enums;
namespace BotSharp.Core.Conversations.Services;
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs
index 32c7af52..48ff193f 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs
@@ -1,9 +1,10 @@
+using BotSharp.Abstraction.Infrastructures;
using Medallion.Threading.Redis;
using StackExchange.Redis;
namespace BotSharp.Core.Infrastructures;
-public class DistributedLocker
+public class DistributedLocker : IDistributedLocker
{
private readonly IConnectionMultiplexer _redis;
private readonly ILogger _logger;
@@ -14,7 +15,7 @@ public class DistributedLocker
_logger = logger;
}
- public async Task Lock(string resource, Func> action, int timeoutInSeconds = 30)
+ public async Task LockAsync(string resource, Func action, int timeoutInSeconds = 30)
{
var timeout = TimeSpan.FromSeconds(timeoutInSeconds);
@@ -24,9 +25,11 @@ public class DistributedLocker
if (handle == null)
{
_logger.LogWarning($"Acquire lock for {resource} failed due to after {timeout}s timeout.");
+ return false;
}
- return await action();
+ await action();
+ return true;
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs
index 8cd7935b..9636fe18 100644
--- a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs
+++ b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs
@@ -39,7 +39,15 @@ public class RoleService : IRoleService
public async Task> GetRoles(RoleFilter filter)
{
var db = _services.GetRequiredService();
+
var roles = db.GetRoles(filter);
+ if (filter.IsInit() && roles.IsNullOrEmpty())
+ {
+ await RefreshRoles();
+ await Task.Delay(100);
+ roles = db.GetRoles(filter);
+ }
+
return roles;
}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs
index 4f7bba6c..931b53e8 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs
@@ -29,6 +29,8 @@ public class NaiveReasoner : IRoutingReasoner
private readonly IServiceProvider _services;
private readonly ILogger _logger;
+ public string Name => "Naive Reasoner";
+
public NaiveReasoner(IServiceProvider services, ILogger logger)
{
_services = services;
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/OneStepForwardReasoner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/OneStepForwardReasoner.cs
index 97568751..eb58d3e3 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/OneStepForwardReasoner.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/OneStepForwardReasoner.cs
@@ -34,7 +34,7 @@ public class OneStepForwardReasoner : IRoutingReasoner
private readonly IServiceProvider _services;
private readonly ILogger _logger;
- public OneStepForwardReasoner(IServiceProvider services, ILogger logger)
+ public OneStepForwardReasoner(IServiceProvider services, ILogger logger)
{
_services = services;
_logger = logger;
@@ -116,7 +116,7 @@ public class OneStepForwardReasoner : IRoutingReasoner
}
else
{
- context.Empty(reason: $"Agent queue is cleared by {nameof(NaiveReasoner)}");
+ context.Empty(reason: $"Agent queue is cleared by {nameof(OneStepForwardReasoner)}");
// context.Push(inst.OriginalAgent, "Push user goal agent");
}
return true;
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/SequentialReasoner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/SequentialReasoner.cs
index 959747db..77f069fc 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/SequentialReasoner.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/SequentialReasoner.cs
@@ -33,7 +33,7 @@ public class SequentialReasoner : IRoutingReasoner
public int MaxLoopCount => 100;
private FunctionCallFromLlm _lastInst;
- public SequentialReasoner(IServiceProvider services, ILogger logger)
+ public SequentialReasoner(IServiceProvider services, ILogger logger)
{
_services = services;
_logger = logger;
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs
index 5b84122c..c3e1a8f4 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs
@@ -97,14 +97,20 @@ public partial class RoutingService
{
var rule = router.RoutingRules.FirstOrDefault(x => x.Type == RuleType.Reasoner);
+ if (rule == null)
+ {
+ _logger.LogError($"Can't find any reasoner");
+ return _services.GetServices().First(x => x.Name == "Naive Reasoner");
+ }
+
var reasoner = _services.GetServices().
FirstOrDefault(x => x.GetType().Name.EndsWith(rule.Field));
if (reasoner == null)
{
- _logger.LogError($"Can't find specific planner named {rule.Field}");
+ _logger.LogError($"Can't find specific reasoner named {rule.Field}");
// Default use NaiveReasoner
- return _services.GetRequiredService();
+ return _services.GetServices().First(x => x.Name == "Naive Reasoner");
}
return reasoner;
diff --git a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs
index 453b2277..7b81ac10 100644
--- a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs
@@ -1,3 +1,19 @@
+/*****************************************************************************
+ Copyright 2024 Written by Jicheng Lu. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+******************************************************************************/
+
using BotSharp.Abstraction.Infrastructures.Enums;
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Options;
diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj
index c5bd88d0..d053f4d6 100644
--- a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj
+++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj b/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj
index a57e28b0..e23bd5e7 100644
--- a/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj
+++ b/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -28,7 +28,6 @@
-
diff --git a/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/BotSharp.Plugin.JavaScriptInterpreter.csproj b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/BotSharp.Plugin.JavaScriptInterpreter.csproj
new file mode 100644
index 00000000..4596ce1b
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/BotSharp.Plugin.JavaScriptInterpreter.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/JsInterpreterPlugin.cs b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/JsInterpreterPlugin.cs
new file mode 100644
index 00000000..e3811809
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/JsInterpreterPlugin.cs
@@ -0,0 +1,24 @@
+using BotSharp.Abstraction.Plugins;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace BotSharp.Plugin.JavaScriptInterpreter;
+
+public class JsInterpreterPlugin : IBotSharpAppPlugin
+{
+ public string Id => "7a5a8cd7-26d9-4ac3-9d79-d02084bea372";
+ public string Name => "JavaScript Interpreter";
+ public string Description => "";
+ public string? IconUrl => "";
+
+ public void Configure(IApplicationBuilder app)
+ {
+
+ }
+
+ public void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj b/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj
index bd7cf7b7..68ce651c 100644
--- a/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj
+++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs
index 9d55dead..a7d83def 100644
--- a/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs
+++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs
@@ -36,7 +36,6 @@ public class LlamaAiModel
_params = new ModelParams(Path.Combine(_settings.ModelDir, model))
{
ContextSize = (uint)_settings.MaxContextLength,
- Seed = 1337,
GpuLayerCount = _settings.NumberOfGpuLayer
};
diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs
index a9288064..1ec461ae 100644
--- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs
@@ -1,4 +1,6 @@
using System.IO;
+using System.Xml.Linq;
+using static System.Net.Mime.MediaTypeNames;
namespace BotSharp.Plugin.LLamaSharp.Providers;
@@ -19,7 +21,7 @@ public class TextEmbeddingProvider : ITextEmbedding
_settings = settings;
}
- public Task GetVectorAsync(string text)
+ public async Task GetVectorAsync(string text)
{
if (_embedder == null)
{
@@ -29,10 +31,10 @@ public class TextEmbeddingProvider : ITextEmbedding
_embedder = new LLamaEmbedder(weights, @params);
}
- return _embedder.GetEmbeddings(text);
+ return (await _embedder.GetEmbeddings(text)).First();
}
- public Task> GetVectorsAsync(List texts)
+ public async Task> GetVectorsAsync(List texts)
{
throw new NotImplementedException();
}
diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
index 2f5a1708..2cd16ae9 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
@@ -71,13 +71,15 @@ public class SummaryPlanFn : IFunctionCallback
var summary = await GetAiResponse(plannerAgent);
message.Content = summary.Content;
- // Validate the sql result
+ // Emit event if the sql statement is generated by planner
var args = JsonSerializer.Deserialize(message.FunctionArgs);
- if (args.IsSqlTemplate == false)
+ if (args != null && !args.IsSqlTemplate && args.ContainsSqlStatements)
{
- await fn.InvokeFunction("validate_sql", message);
+ await HookEmitter.Emit(_services, async hook =>
+ await hook.OnSourceCodeGenerated(nameof(TwoStageTaskPlanner), message, "sql")
+ );
}
-
+
await HookEmitter.Emit(_services, async hook =>
await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message)
);
diff --git a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs
index 459237a8..6b156c79 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/SummaryPlan.cs
@@ -4,4 +4,7 @@ public class SummaryPlan
{
[JsonPropertyName("is_sql_template")]
public bool IsSqlTemplate { get; set; } = false;
+
+ [JsonPropertyName("contains_sql_statements")]
+ public bool ContainsSqlStatements { get; set; } = false;
}
diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/agent.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/agent.json
index d1b5eb52..91ee618c 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/agent.json
+++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/agent.json
@@ -1,7 +1,7 @@
{
"id": "282a7128-69a1-44b0-878c-a9159b88f3b9",
"name": "Planner",
- "description": "Plan feasible implementation steps for user task request",
+ "description": "Plan feasible implementation steps for complex user task request",
"type": "task",
"createdDateTime": "2023-08-27T10:39:00Z",
"updatedDateTime": "2023-08-27T14:39:00Z",
diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json
index c13bfb25..17ec0639 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json
+++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/functions/plan_summary.json
@@ -8,6 +8,10 @@
"type": "boolean",
"description": "If user request is to generate sql template instead of actual sql statement."
},
+ "contains_sql_statements": {
+ "type": "boolean",
+ "description": "Set to true if the response contains sql statements."
+ },
"related_tables": {
"type": "array",
"description": "table name in planning steps",
@@ -17,6 +21,6 @@
}
}
},
- "required": [ "related_tables", "is_sql_template" ]
+ "required": [ "related_tables", "is_sql_template", "contains_sql_statements" ]
}
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj b/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj
index b3293a0f..c5e3d4ff 100644
--- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj
+++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj
@@ -25,7 +25,7 @@
-
+
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
index 99060105..f571fa6c 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
@@ -1,14 +1,8 @@
-using BotSharp.Abstraction.Agents.Enums;
-using BotSharp.Abstraction.Routing;
using BotSharp.Core.Infrastructures;
-using BotSharp.Plugin.SqlDriver.Interfaces;
-using BotSharp.Plugin.SqlDriver.Models;
using Dapper;
using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Logging;
using MySqlConnector;
using Npgsql;
-using System.Data.Common;
namespace BotSharp.Plugin.SqlDriver.Functions;
@@ -113,6 +107,7 @@ public class ExecuteQueryFn : IFunctionCallback
using var connection = new SqlConnection(settings.SqlServerExecutionConnectionString ?? settings.SqlServerConnectionString);
return connection.Query(string.Join("\r\n", sqlTexts));
}
+
private IEnumerable RunQueryInRedshift(string[] sqlTexts)
{
var settings = _services.GetRequiredService();
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs
index cfc9765f..eb9253e4 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs
@@ -1,6 +1,6 @@
-using BotSharp.Plugin.SqlDriver.Models;
using Microsoft.Data.SqlClient;
using MySqlConnector;
+using Npgsql;
using static Dapper.SqlMapper;
namespace BotSharp.Plugin.SqlDriver.Functions;
@@ -26,12 +26,15 @@ public class SqlSelect : IFunctionCallback
}
// check if need to instantely
- var settings = _services.GetRequiredService();
- var result = settings.DatabaseType switch
+ var dbHook = _services.GetRequiredService();
+ var dbType = dbHook.GetDatabaseType(message);
+
+ var result = dbType switch
{
- "MySql" => RunQueryInMySql(args),
- "SqlServer" => RunQueryInSqlServer(args),
- _ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.")
+ "mysql" => RunQueryInMySql(args),
+ "sqlserver" => RunQueryInSqlServer(args),
+ "redshift" => RunQueryInRedshift(args),
+ _ => throw new NotImplementedException($"Database type {dbType} is not supported.")
};
if (result == null)
@@ -70,4 +73,16 @@ public class SqlSelect : IFunctionCallback
}
return connection.Query(args.Statement, dictionary);
}
+
+ private IEnumerable RunQueryInRedshift(SqlStatement args)
+ {
+ var settings = _services.GetRequiredService();
+ using var connection = new NpgsqlConnection(settings.RedshiftConnectionString);
+ var dictionary = new Dictionary();
+ foreach (var p in args.Parameters)
+ {
+ dictionary["@" + p.Name] = p.Value;
+ }
+ return connection.Query(args.Statement, dictionary);
+ }
}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs
index 94de1b04..224f8fd6 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs
@@ -20,14 +20,23 @@ public class SqlDriverPlanningHook : IPlanningHook
_services = services;
}
- public async Task OnPlanningCompleted(string planner, RoleDialogModel msg)
+ public async Task OnSourceCodeGenerated(string planner, RoleDialogModel msg, string language)
{
+ // envoke validate
+ if (language != "sql")
+ {
+ return;
+ }
+
+ var routing = _services.GetRequiredService();
+ await routing.InvokeFunction("validate_sql", msg);
+
await HookEmitter.Emit(_services, async (hook) =>
{
await hook.SqlGenerated(msg);
});
- var settings = _services.GetRequiredService();
+ var settings = _services.GetRequiredService();
if (!settings.ExecuteSqlSelectAutonomous)
{
var conversationStateService = _services.GetRequiredService();
@@ -51,7 +60,6 @@ public class SqlDriverPlanningHook : IPlanningHook
var response = await completion.GetChatCompletions(agent, wholeDialogs);
// Invoke "execute_sql"
- var routing = _services.GetRequiredService();
await routing.InvokeFunction(response.FunctionName, response);
msg.CurrentAgentId = agent.Id;
@@ -61,6 +69,11 @@ public class SqlDriverPlanningHook : IPlanningHook
msg.StopCompletion = response.StopCompletion;
}
+ public async Task OnPlanningCompleted(string planner, RoleDialogModel msg)
+ {
+
+ }
+
public async Task GetSummaryAdditionalRequirements(string planner, RoleDialogModel message)
{
var settings = _services.GetRequiredService();
diff --git a/tests/BotSharp.Plugin.PizzaBot/data/agents/8970b1e5-d260-4e2c-90b1-f1415a257c18/agent.json b/tests/BotSharp.Plugin.PizzaBot/data/agents/8970b1e5-d260-4e2c-90b1-f1415a257c18/agent.json
index 4da6bdd3..94746011 100644
--- a/tests/BotSharp.Plugin.PizzaBot/data/agents/8970b1e5-d260-4e2c-90b1-f1415a257c18/agent.json
+++ b/tests/BotSharp.Plugin.PizzaBot/data/agents/8970b1e5-d260-4e2c-90b1-f1415a257c18/agent.json
@@ -12,8 +12,8 @@
"profiles": [ "pizza" ],
"routingRules": [
{
- "type": "planner",
- "field": "NaivePlanner"
+ "type": "reasoner",
+ "field": "NaiveReasoner"
}
]
}
\ No newline at end of file