diff --git a/BotSharp.sln b/BotSharp.sln
index 52b3f9d0..5baf8d10 100644
--- a/BotSharp.sln
+++ b/BotSharp.sln
@@ -121,6 +121,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Core.SideCar", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.VertexAI", "src\Plugins\BotSharp.Plugin.LangChain\BotSharp.Plugin.VertexAI.csproj", "{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Crontab", "src\Infrastructure\BotSharp.Core.Crontab\BotSharp.Core.Crontab.csproj", "{F812BAAE-5A7D-4DF7-8E71-70696B51C61F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -489,6 +491,14 @@ Global
{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|Any CPU.Build.0 = Release|Any CPU
{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|x64.ActiveCfg = Release|Any CPU
{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B}.Release|x64.Build.0 = Release|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Debug|x64.Build.0 = Debug|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Release|x64.ActiveCfg = Release|Any CPU
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -547,6 +557,7 @@ Global
{F57F4862-F8D4-44A1-AC12-5C131B5C9785} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{7DA2DCD0-551B-432E-AA5C-22DDD3ED459B} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
+ {F812BAAE-5A7D-4DF7-8E71-70696B51C61F} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
diff --git a/docs/architecture/agent-utility.md b/docs/architecture/agent-utility.md
new file mode 100644
index 00000000..b65dea8c
--- /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 one prompt and one function. The utility name is formatted as **“[plugin name].[utility name]”**, such as “http.http-handler”. The prompt is added under the “templates” folder and its recommended name is **“util-[plugin name]-[function name].fn.liquid”**, while the function is added under the “functions” folder and its recommended name is **“util-[plugin name]-[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 => "util-http-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.http-handler",
+ "functions": [
+ { "name": "util-http-handle_http_request" }
+ ],
+ "templates": [
+ { "name": "util-http-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.1](#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..cda3472d
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..8b2c4458
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..5e3f887c
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 808f7821..a3746ca8 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/AgentHookBase.cs
@@ -49,7 +49,11 @@ public abstract class AgentHookBase : IAgentHook
return true;
}
- public virtual void OnAgentLoaded(Agent agent)
+ public virtual void OnAgentLoaded(Agent agent)
+ {
+ }
+
+ public virtual void OnAgentUtilityLoaded(Agent agent)
{
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs
index 1bb221d3..34e8ac19 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentField.cs
@@ -17,7 +17,8 @@ public enum AgentField
Response,
Sample,
LlmConfig,
- Utility
+ Utility,
+ MaxMessageCount
}
public enum AgentTaskField
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs
index 53d64a07..455e87ff 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentType.cs
@@ -10,7 +10,7 @@ public class AgentType
///
/// Planning agent
///
- public const string Planning = "plan";
+ public const string Planning = "planning";
public const string Evaluating = "evaluation";
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs
index c3c69f08..ecdda1c7 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs
@@ -32,11 +32,6 @@ public class BuiltInAgentId
///
public const string Learner = "01acc3e5-0af7-49e6-ad7a-a760bd12dc40";
- ///
- /// Plan feasible implementation steps for complex problems
- ///
- public const string Planner = "282a7128-69a1-44b0-878c-a9159b88f3b9";
-
///
/// SQL statement generation
///
@@ -51,4 +46,9 @@ 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";
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs
index 99cdadca..a3f53fb6 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs
@@ -25,6 +25,8 @@ public interface IAgentHook
bool OnSamplesLoaded(List samples);
+ void OnAgentUtilityLoaded(Agent agent);
+
///
/// Triggered when agent is loaded completely.
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
index 1461cc22..b8945f40 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
@@ -59,5 +59,5 @@ public interface IAgentService
PluginDef GetPlugin(string agentId);
- IEnumerable GetAgentUtilities();
+ IEnumerable GetAgentUtilityOptions();
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentUtilityHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentUtilityHook.cs
index 9d5667b8..603ec64e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentUtilityHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentUtilityHook.cs
@@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.Agents;
public interface IAgentUtilityHook
{
- void AddUtilities(List utilities);
+ void AddUtilities(List utilities);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs
index 43f0f5ba..41b3800c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs
@@ -87,19 +87,29 @@ public class Agent
///
/// Profile by channel
///
- public List Profiles { get; set; }
- = new List();
+ public List Profiles { get; set; } = new();
+
+ ///
+ /// Merge utilities from entry agent
+ ///
+ public bool MergeUtility { get; set; }
///
/// Agent utilities
///
- public List Utilities { get; set; } = new();
+ public List Utilities { get; set; } = new();
///
/// Inherit from agent
///
public string? InheritAgentId { get; set; }
+ ///
+ /// Maximum message count when load conversation
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public int? MaxMessageCount { get; set; }
+
public List RoutingRules { get; set; } = new();
///
@@ -129,6 +139,8 @@ public class Agent
Knowledges = agent.Knowledges,
IsPublic = agent.IsPublic,
Disabled = agent.Disabled,
+ MergeUtility = agent.MergeUtility,
+ MaxMessageCount = agent.MaxMessageCount,
Profiles = agent.Profiles,
RoutingRules = agent.RoutingRules,
LlmConfig = agent.LlmConfig,
@@ -173,9 +185,9 @@ public class Agent
return this;
}
- public Agent SetUtilities(List utilities)
+ public Agent SetUtilities(List utilities)
{
- Utilities = utilities ?? new List();
+ Utilities = utilities ?? new List();
return this;
}
@@ -215,6 +227,12 @@ public class Agent
return this;
}
+ public Agent SetMergeUtility(bool merge)
+ {
+ MergeUtility = merge;
+ return this;
+ }
+
public Agent SetAgentType(string type)
{
Type = type;
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentUtility.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentUtility.cs
new file mode 100644
index 00000000..ce39c568
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentUtility.cs
@@ -0,0 +1,61 @@
+namespace BotSharp.Abstraction.Agents.Models;
+
+public class AgentUtility
+{
+ public string Name { get; set; }
+ public bool Disabled { get; set; }
+ public IEnumerable Functions { get; set; } = [];
+ public IEnumerable Templates { get; set; } = [];
+
+ public AgentUtility()
+ {
+
+ }
+
+ public AgentUtility(
+ string name,
+ IEnumerable? functions = null,
+ IEnumerable? templates = null)
+ {
+ Name = name;
+ Functions = functions ?? [];
+ Templates = templates ?? [];
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+}
+
+
+public class UtilityFunction : UtilityBase
+{
+ public UtilityFunction()
+ {
+
+ }
+
+ public UtilityFunction(string name)
+ {
+ Name = name;
+ }
+}
+
+public class UtilityTemplate : UtilityBase
+{
+ public UtilityTemplate()
+ {
+
+ }
+
+ public UtilityTemplate(string name)
+ {
+ Name = name;
+ }
+}
+
+public class UtilityBase
+{
+ public string Name { get; set; }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
index 5be126b8..9b31ae6f 100644
--- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
+++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
@@ -1,4 +1,4 @@
-
+
$(TargetFramework)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs
index 8f1cee10..abdfd67b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs
@@ -7,4 +7,5 @@ public enum BroswerActionEnum
Typing = 3,
Hover = 4,
Scroll = 5,
+ DragAndDrop = 6
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs
index fd95bd66..9a1ef0d3 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs
@@ -5,4 +5,9 @@ public class ElementPosition
public float X { get; set; } = default!;
public float Y { get; set; } = default!;
+
+ public override string ToString()
+ {
+ return $"[{X}, {Y}]";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
index 9239b865..da87646e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
@@ -2,27 +2,23 @@ namespace BotSharp.Abstraction.Conversations;
public abstract class ConversationHookBase : IConversationHook
{
- protected Agent _agent;
- public Agent Agent => _agent;
+ public Agent Agent { get; private set; }
- protected Conversation _conversation;
- public Conversation Conversation => _conversation;
+ public Conversation Conversation { get; private set; }
- protected List _dialogs;
- public List Dialogs => _dialogs;
+ public List Dialogs { get; private set; }
- protected int _priority = 0;
- public int Priority => _priority;
+ public int Priority { get; protected set; } = 0;
public IConversationHook SetAgent(Agent agent)
{
- _agent = agent;
+ Agent = agent;
return this;
}
public IConversationHook SetConversation(Conversation conversation)
{
- _conversation = conversation;
+ Conversation = conversation;
return this;
}
@@ -37,7 +33,7 @@ public abstract class ConversationHookBase : IConversationHook
public virtual Task OnDialogsLoaded(List dialogs)
{
- _dialogs = dialogs;
+ Dialogs = dialogs;
return Task.CompletedTask;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookProvider.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookProvider.cs
new file mode 100644
index 00000000..00393da4
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookProvider.cs
@@ -0,0 +1,20 @@
+namespace BotSharp.Abstraction.Conversations;
+
+public class ConversationHookProvider
+{
+ public IEnumerable Hooks { get; }
+
+ private readonly Lazy> _hooksOrderByPriority;
+
+ public IEnumerable HooksOrderByPriority
+ => _hooksOrderByPriority.Value;
+
+ public ConversationHookProvider(IEnumerable conversationHooks)
+ {
+ Hooks = conversationHooks;
+ _hooksOrderByPriority = new Lazy>(() =>
+ {
+ return conversationHooks.OrderBy(hook => hook.Priority).ToArray();
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs
index 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/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
index ffb4986a..6a04e796 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
@@ -12,6 +12,7 @@ public interface IConversationService
Task GetConversation(string id);
Task> GetConversations(ConversationFilter filter);
Task UpdateConversationTitle(string id, string title);
+ Task UpdateConversationTitleAlias(string id, string titleAlias);
Task UpdateConversationTags(string conversationId, List tags);
Task UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
Task> GetLastConversations();
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
index 5ded2c7c..a485a9c4 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
@@ -13,6 +13,7 @@ public class Conversation
///
public string? TaskId { get; set; }
public string Title { get; set; } = string.Empty;
+ public string TitleAlias { get; set; } = string.Empty;
[JsonIgnore]
public List Dialogs { get; set; } = new();
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
index 679527a3..5a67216d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
@@ -2,9 +2,11 @@ using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Messaging.Models.RichContent;
+using System.Diagnostics;
namespace BotSharp.Abstraction.Conversations.Models;
+[DebuggerStepThrough]
public class RoleDialogModel : ITrackableMessage
{
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs
new file mode 100644
index 00000000..6a9dd43a
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs
@@ -0,0 +1,33 @@
+namespace BotSharp.Abstraction.Crontab.Models;
+
+public class CrontabItem : ScheduleTaskArgs
+{
+ [JsonPropertyName("user_id")]
+ public string UserId { get; set; } = null!;
+
+ [JsonPropertyName("agent_id")]
+ public string AgentId { get; set; } = null!;
+
+ [JsonPropertyName("conversation_id")]
+ public string ConversationId { get; set; } = null!;
+
+ [JsonPropertyName("execution_result")]
+ public string ExecutionResult { get; set; } = null!;
+
+ [JsonPropertyName("execution_count")]
+ public int ExecutionCount { get; set; }
+
+ [JsonPropertyName("max_execution_count")]
+ public int MaxExecutionCount { get; set; }
+
+ [JsonPropertyName("expire_seconds")]
+ public int ExpireSeconds { get; set; } = 60;
+
+ [JsonPropertyName("created_time")]
+ public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
+
+ public override string ToString()
+ {
+ return $"{Title}: {Description} [AgentId: {AgentId}, UserId: {UserId}]";
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItemFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItemFilter.cs
new file mode 100644
index 00000000..7a949dc2
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItemFilter.cs
@@ -0,0 +1,23 @@
+namespace BotSharp.Abstraction.Crontab.Models;
+
+public class CrontabItemFilter : Pagination
+{
+ [JsonPropertyName("user_ids")]
+ public IEnumerable? UserIds { get; set; }
+
+ [JsonPropertyName("agent_ids")]
+ public IEnumerable? AgentIds { get; set; }
+
+ [JsonPropertyName("conversation_ids")]
+ public IEnumerable? ConversationIds { get; set; }
+
+ public CrontabItemFilter()
+ {
+
+ }
+
+ public static CrontabItemFilter Empty()
+ {
+ return new CrontabItemFilter();
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/ScheduleTaskArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/ScheduleTaskArgs.cs
new file mode 100644
index 00000000..af28374a
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/Models/ScheduleTaskArgs.cs
@@ -0,0 +1,31 @@
+namespace BotSharp.Abstraction.Crontab.Models;
+
+public class ScheduleTaskArgs
+{
+ [JsonPropertyName("cron_expression")]
+ public string Cron { get; set; } = null!;
+
+ [JsonPropertyName("less_than_60_seconds")]
+ public bool LessThan60Seconds { get; set; } = false;
+
+ [JsonPropertyName("title")]
+ public string Title { get; set; } = null!;
+
+ [JsonPropertyName("description")]
+ public string Description { get; set; } = null!;
+
+ [JsonPropertyName("to_do_list")]
+ public ScheduleTaskItemArgs[] Tasks { get; set; } = [];
+}
+
+public class ScheduleTaskItemArgs
+{
+ [JsonPropertyName("topic")]
+ public string Topic { get; set; } = null!;
+
+ [JsonPropertyName("script")]
+ public string Script { get; set; } = null!;
+
+ [JsonPropertyName("language")]
+ public string Language { get; set; } = null!;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/EventPriority.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/EventPriority.cs
new file mode 100644
index 00000000..75c4402d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/EventPriority.cs
@@ -0,0 +1,8 @@
+namespace BotSharp.Abstraction.Infrastructures.Enums;
+
+public enum EventPriority
+{
+ Low = 1,
+ Medium = 2,
+ High = 3
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs
index e242937e..58b18cba 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs
@@ -10,7 +10,11 @@ public interface IEventPublisher
///
Task BroadcastAsync(string channel, string message);
- Task PublishAsync(string channel, string message);
+ Task PublishAsync(string channel, string message, EventPriority? priority = null);
Task ReDispatchAsync(string channel, int count = 10, string order = "asc");
+
+ Task ReDispatchPendingAsync(string channel, string group, int count = 10);
+
+ Task RemoveAsync(string channel, int count = 10);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs
index f295e54e..465644ad 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs
@@ -1,8 +1,12 @@
+using System.Threading;
+
namespace BotSharp.Abstraction.Infrastructures.Events;
public interface IEventSubscriber
{
Task SubscribeAsync(string channel, Func received);
- Task SubscribeAsync(string channel, string group, Func received);
+ Task SubscribeAsync(string channel, string group, int? port, bool priorityEnabled,
+ Func received,
+ CancellationToken? stoppingToken = null);
}
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/Infrastructures/SharpCacheAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs
index c8b29ecd..c8f6a674 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs
@@ -50,7 +50,9 @@ public class SharpCacheAttribute : MoAttribute
}
var httpContext = Services.GetRequiredService();
- if (httpContext.HttpContext.Response.Headers["Cache-Control"].ToString().Contains("no-store"))
+ if (httpContext != null &&
+ httpContext.HttpContext != null &&
+ httpContext.HttpContext.Response.Headers["Cache-Control"].ToString().Contains("no-store"))
{
return;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs
similarity index 76%
rename from src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs
rename to src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs
index c8bffe6c..3362f1a4 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IExecutor.cs
@@ -1,6 +1,7 @@
using BotSharp.Abstraction.Functions.Models;
+using BotSharp.Abstraction.Routing;
-namespace BotSharp.Abstraction.Routing.Planning;
+namespace BotSharp.Abstraction.Planning;
public interface IExecutor
{
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/Planning/ITaskPlanner.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/ITaskPlanner.cs
index a5668a52..2bd8da83 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Planning/ITaskPlanner.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Planning/ITaskPlanner.cs
@@ -1,9 +1,20 @@
+using BotSharp.Abstraction.Functions.Models;
+
namespace BotSharp.Abstraction.Planning;
///
/// Planning process for Task Agent
+/// https://www.promptingguide.ai/techniques/cot
///
-public class ITaskPlanner
+public interface ITaskPlanner
{
-
+ string Name => "Unamed Task Planner";
+ Task GetNextInstruction(Agent router, string messageId, List dialogs);
+ Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs);
+ Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs);
+ List BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List dialogs)
+ => dialogs;
+ bool AfterHandleContext(List dialogs, List taskAgentDialogs)
+ => true;
+ int MaxLoopCount => 5;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginDef.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginDef.cs
index 2624bc17..0e2be5f9 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginDef.cs
@@ -6,14 +6,15 @@ public class PluginDef
public string Name { get; set; }
public string Description { get; set; }
public string Assembly { get; set; }
+
[JsonPropertyName("is_core")]
- public bool IsCore => Assembly == "BotSharp.Core" || Assembly == "BotSharp.Core.SideCar";
+ public bool IsCore => Assembly?.StartsWith("BotSharp.Core") == true;
[JsonPropertyName("icon_url")]
public string? IconUrl { get; set; }
[JsonPropertyName("agent_ids")]
- public string[] AgentIds { get; set; } = new string[0];
+ public string[] AgentIds { get; set; } = [];
[JsonPropertyName("settings_name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs
index b3c26033..19f5b141 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs
@@ -2,18 +2,19 @@ namespace BotSharp.Abstraction.Repositories;
public class BotSharpDatabaseSettings : DatabaseBasicSettings
{
- public string[] Assemblies { get; set; }
- public string FileRepository { get; set; }
- public string BotSharpMongoDb { get; set; }
- public string TablePrefix { get; set; }
- public DbConnectionSetting BotSharp { get; set; }
- public string Redis { get; set; }
+ public string[] Assemblies { get; set; } = [];
+ public string FileRepository { get; set; } = string.Empty;
+ public string BotSharpMongoDb { get; set; } = string.Empty;
+ public string TablePrefix { get; set; } = string.Empty;
+ public DbConnectionSetting BotSharp { get; set; } = new();
+ public string Redis { get; set; } = string.Empty;
+ public bool EnableReplica { get; set; } = true;
}
public class DatabaseBasicSettings
{
- public string Default { get; set; }
- public DbConnectionSetting DefaultConnection { get; set; }
+ public string Default { get; set; } = string.Empty;
+ public DbConnectionSetting DefaultConnection { get; set; } = new();
public bool EnableSqlLog { get; set; }
public bool EnableSensitiveDataLogging { get; set; }
public bool EnableRetryOnFailure { get; set; }
@@ -23,9 +24,11 @@ public class DbConnectionSetting
{
public string Master { get; set; }
public string[] Slavers { get; set; }
+ public int ConnectionTimeout { get; set; } = 30;
+ public int ExecutionTimeout { get; set; } = 30;
public DbConnectionSetting()
{
- Slavers = new string[0];
+ Slavers = [];
}
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
index ab1a0a9f..23543049 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
@@ -8,6 +8,7 @@ public class ConversationFilter
///
public string? Id { get; set; }
public string? Title { get; set; }
+ public string? TitleAlias { get; set; }
public string? AgentId { get; set; }
public string? Status { get; set; }
public string? Channel { get; set; }
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.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
index f3859a3a..4391f970 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
@@ -5,6 +5,7 @@ using BotSharp.Abstraction.Roles.Models;
using BotSharp.Abstraction.Shared;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Translation.Models;
+using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using BotSharp.Abstraction.VectorStorage.Models;
@@ -26,15 +27,19 @@ public interface IBotSharpRepository : IHaveServiceProvider
#region User
User? GetUserByEmail(string email) => throw new NotImplementedException();
- User? GetUserByPhone(string phone, string role = null, string regionCode = "CN") => throw new NotImplementedException();
+ User? GetUserByPhone(string phone, string type = UserType.Client, string regionCode = "CN") => throw new NotImplementedException();
User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException();
User? GetUserById(string id) => throw new NotImplementedException();
List GetUserByIds(List ids) => throw new NotImplementedException();
List GetUsersByAffiliateId(string affiliateId) => throw new NotImplementedException();
User? GetUserByUserName(string userName) => throw new NotImplementedException();
+ Dashboard? GetDashboard(string id = null) => throw new NotImplementedException();
void CreateUser(User user) => throw new NotImplementedException();
void UpdateExistUser(string userId, User user) => throw new NotImplementedException();
void UpdateUserVerified(string userId) => throw new NotImplementedException();
+ void AddDashboardConversation(string userId, string conversationId) => throw new NotImplementedException();
+ void RemoveDashboardConversation(string userId, string conversationId) => throw new NotImplementedException();
+ void UpdateDashboardConversation(string userId, DashboardConversation dashConv) => throw new NotImplementedException();
void UpdateUserVerificationCode(string userId, string verficationCode) => throw new NotImplementedException();
void UpdateUserPassword(string userId, string password) => throw new NotImplementedException();
void UpdateUserEmail(string userId, string email) => throw new NotImplementedException();
@@ -48,7 +53,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
#region Agent
void UpdateAgent(Agent agent, AgentField field);
- Agent? GetAgent(string agentId);
+ Agent? GetAgent(string agentId, bool basicsOnly = false);
List GetAgents(AgentFilter filter);
List GetUserAgents(string userId);
void BulkInsertAgents(List agents);
@@ -81,6 +86,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
Conversation GetConversation(string conversationId);
PagedItems GetConversations(ConversationFilter filter);
void UpdateConversationTitle(string conversationId, string title);
+ void UpdateConversationTitleAlias(string conversationId, string titleAlias);
bool UpdateConversationTags(string conversationId, List tags);
bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
@@ -140,4 +146,10 @@ public interface IBotSharpRepository : IHaveServiceProvider
bool DeleteKnolwedgeBaseFileMeta(string collectionName, string vectorStoreProvider, Guid? fileId = null);
PagedItems GetKnowledgeBaseFileMeta(string collectionName, string vectorStoreProvider, KnowledgeFileFilter filter);
#endregion
+
+ #region Crontab
+ bool UpsertCrontabItem(CrontabItem cron) => throw new NotImplementedException();
+ bool DeleteCrontabItem(string conversationId) => throw new NotImplementedException();
+ PagedItems GetCrontabItems(CrontabItemFilter filter) => throw new NotImplementedException();
+ #endregion
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs
index 1d1913dd..c595d59c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Enums/RuleType.cs
@@ -12,6 +12,11 @@ public class RuleType
///
public const string DataValidation = "data-validation";
+ ///
+ /// The reasoning approach name for next step
+ ///
+ public const string Reasoner = "reasoner";
+
///
/// The planning approach name for next step
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs
index f75e8e0e..1dd9f50a 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs
@@ -6,6 +6,7 @@ public interface IRoutingContext
string FirstGoalAgentId();
bool ContainsAgentId(string agentId);
string OriginAgentId { get; }
+ string EntryAgentId { get; }
string ConversationId { get; }
string MessageId { get; }
void SetMessageId(string conversationId, string messageId);
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutableAgent.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutableAgent.cs
index 14fd6826..861f3f2f 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutableAgent.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutableAgent.cs
@@ -13,6 +13,9 @@ public class RoutableAgent
[JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = AgentType.Task;
+
[JsonPropertyName("profiles")]
public List Profiles { get; set; }
= new List();
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IRoutingPlaner.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IRoutingPlaner.cs
deleted file mode 100644
index 7f3abde9..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IRoutingPlaner.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using BotSharp.Abstraction.Functions.Models;
-
-namespace BotSharp.Abstraction.Routing.Planning;
-
-///
-/// Task breakdown and execution plan
-/// https://www.promptingguide.ai/techniques/cot
-///
-public interface IRoutingPlaner
-{
- Task GetNextInstruction(Agent router, string messageId, List dialogs);
- Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs);
- Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs);
- List BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List dialogs)
- => dialogs;
- bool AfterHandleContext(List dialogs, List taskAgentDialogs)
- => true;
- int MaxLoopCount => 5;
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Reasoning/IRoutingReasoner.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Reasoning/IRoutingReasoner.cs
new file mode 100644
index 00000000..f7bb5eb4
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Reasoning/IRoutingReasoner.cs
@@ -0,0 +1,30 @@
+using BotSharp.Abstraction.Functions.Models;
+
+namespace BotSharp.Abstraction.Routing.Reasoning;
+
+///
+/// Reasoning approaches for large language models (LLMs) help enhance their ability to solve complex problems,
+/// handle tasks requiring logic, and provide accurate and contextually appropriate responses.
+///
+public interface IRoutingReasoner
+{
+ string Name => "Unnamed Reasoner";
+ string Description => "Each of these approaches leverages the capabilities of LLMs to reason more effectively, " +
+ "ensuring better performance and more coherent outputs across various types of complex tasks.";
+
+ int MaxLoopCount => 5;
+
+ Task GetNextInstruction(Agent router, string messageId, List dialogs);
+
+ Task AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs)
+ => Task.FromResult(true);
+
+ Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs)
+ => Task.FromResult(true);
+
+ List BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List dialogs)
+ => dialogs;
+
+ bool AfterHandleContext(List dialogs, List taskAgentDialogs)
+ => true;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs
index 9a1316fb..84b65573 100644
--- a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs
@@ -9,5 +9,6 @@ public interface IConversationSideCar
List GetConversationDialogs(string conversationId);
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
- Task SendMessage(string agentId, string text, PostbackMessageModel? postback = null, List? states = null);
+ Task SendMessage(string agentId, string text,
+ PostbackMessageModel? postback = null, List? states = null, List? dialogs = null);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs
index e5ddeb61..82eff1c1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs
@@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Templating;
public interface ITemplateRender
{
string Render(string template, Dictionary dict);
+ void Register(Type type);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs
index b565a260..b702ea01 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs
@@ -1,5 +1,8 @@
namespace BotSharp.Abstraction.Users.Enums;
+///
+/// User actions on agent level
+///
public static class UserAction
{
public const string Edit = "edit";
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs
index 58dfc186..64212fe4 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs
@@ -1,5 +1,8 @@
namespace BotSharp.Abstraction.Users.Enums;
+///
+/// User permission
+///
public static class UserPermission
{
public const string CreateAgent = "create-agent";
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
index cc67c1f7..134ca25b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
@@ -29,4 +29,8 @@ public interface IUserService
Task UpdatePassword(string newPassword, string verificationCode);
Task GetUserTokenExpires();
Task UpdateUsersIsDisable(List userIds, bool isDisable);
+ Task AddDashboardConversation(string userId, string conversationId);
+ Task RemoveDashboardConversation(string userId, string conversationId);
+ Task UpdateDashboardConversation(string userId, DashboardConversation dashConv);
+ Task GetDashboard(string userId);
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/Dashboard.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Dashboard.cs
new file mode 100644
index 00000000..753cb390
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Dashboard.cs
@@ -0,0 +1,20 @@
+
+namespace BotSharp.Abstraction.Users.Models;
+
+public class Dashboard
+{
+ public IList ConversationList { get; set; } = [];
+}
+
+public class DashboardComponent
+{
+ public required string Id { get; set; }
+ public string? Name { get; set; }
+}
+
+public class DashboardConversation : DashboardComponent
+{
+ public string? ConversationId { get; set; }
+ public string? Instruction { get; set; } = "";
+}
+
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
index 8d136dc5..7699a58b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
@@ -23,6 +23,7 @@ public class User
public bool Verified { get; set; }
public string RegionCode { get; set; } = "CN";
public string? AffiliateId { get; set; }
+ public string? ReferralCode { get; set; }
public string? EmployeeId { get; set; }
public bool IsDisabled { get; set; }
public IEnumerable Permissions { get; set; } = [];
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs
index 56df6a7d..f3c4c7f0 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAuthorization.cs
@@ -22,4 +22,26 @@ public static class UserAuthorizationExtension
var actions = found.Actions ?? [];
return actions.Any(x => x == targetAction);
}
+
+ ///
+ /// Get allowed user actions on the agent. If user is admin, returns null;
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable? GetAllowedAgentActions(this UserAuthorization auth, string agentId)
+ {
+ if (auth == null || string.IsNullOrEmpty(agentId))
+ {
+ return [];
+ }
+
+ if (auth.IsAdmin)
+ {
+ return null;
+ }
+
+ var found = auth.AgentActions.FirstOrDefault(x => x.AgentId == agentId);
+ return found?.Actions ?? [];
+ }
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs
index 825f7d8f..592803c2 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Using.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs
@@ -12,10 +12,10 @@ global using BotSharp.Abstraction.Agents.Enums;
global using BotSharp.Abstraction.Infrastructures.Enums;
global using BotSharp.Abstraction.Models;
global using BotSharp.Abstraction.Routing.Models;
-global using BotSharp.Abstraction.Routing.Planning;
global using BotSharp.Abstraction.Templating;
global using BotSharp.Abstraction.Translation.Attributes;
global using BotSharp.Abstraction.Messaging.Enums;
global using BotSharp.Abstraction.Files.Models;
global using BotSharp.Abstraction.Files.Enums;
-global using BotSharp.Abstraction.Knowledges.Models;
\ No newline at end of file
+global using BotSharp.Abstraction.Knowledges.Models;
+global using BotSharp.Abstraction.Crontab.Models;
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/MathExt.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/MathExt.cs
new file mode 100644
index 00000000..4ea18179
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/MathExt.cs
@@ -0,0 +1,14 @@
+namespace BotSharp.Abstraction.Utilities;
+
+public static class MathExt
+{
+ public static int Max(int a, int b, int c)
+ {
+ return Math.Max(Math.Max(a, b), c);
+ }
+
+ public static long Max(long a, long b, long c)
+ {
+ return Math.Max(Math.Max(a, b), c);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
index 8e5c7f23..4d21013b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
@@ -1,7 +1,9 @@
using BotSharp.Abstraction.Infrastructures;
+using System.Diagnostics;
namespace BotSharp.Abstraction.Utilities;
+[DebuggerStepThrough]
public class Pagination : ICacheKey
{
private int _page;
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..1738ef5e
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs
@@ -0,0 +1,6 @@
+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..39c13779
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabService.cs
@@ -0,0 +1,7 @@
+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..cc072fe6
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/BotSharp.Core.Crontab.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ 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..26f5cfad
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/CrontabPlugin.cs
@@ -0,0 +1,38 @@
+/*****************************************************************************
+ 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.Core.Crontab.Hooks;
+
+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 void RegisterDI(IServiceCollection services, IConfiguration config)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ services.AddHostedService();
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Enum/UtilityName.cs b/src/Infrastructure/BotSharp.Core.Crontab/Enum/UtilityName.cs
new file mode 100644
index 00000000..eba15bd9
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Enum/UtilityName.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Core.Crontab.Enum;
+
+public class UtilityName
+{
+ public const string ScheduleTask = "crontab.schedule-task";
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Functions/ScheduleTaskFn.cs b/src/Infrastructure/BotSharp.Core.Crontab/Functions/ScheduleTaskFn.cs
new file mode 100644
index 00000000..b538cb3d
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Functions/ScheduleTaskFn.cs
@@ -0,0 +1,55 @@
+using BotSharp.Abstraction.Repositories;
+using BotSharp.Abstraction.Routing;
+using BotSharp.Abstraction.Users;
+using BotSharp.Core.Crontab.Hooks;
+
+namespace BotSharp.Core.Crontab.Functions;
+
+public class ScheduleTaskFn : IFunctionCallback
+{
+ public string Name => $"{CrontabUtilityHook.PREFIX}schedule_task";
+ private readonly IServiceProvider _services;
+
+ public ScheduleTaskFn(IServiceProvider services)
+ {
+ _services = services;
+ }
+
+ public async Task Execute(RoleDialogModel message)
+ {
+ var args = JsonSerializer.Deserialize(message.FunctionArgs);
+ if (args.LessThan60Seconds)
+ {
+ message.Content = "Cron expression should not include seconds.";
+ return false;
+ }
+
+ var routing = _services.GetRequiredService();
+ var user = _services.GetRequiredService();
+ var db = _services.GetRequiredService();
+
+ if (string.IsNullOrEmpty(args.Cron))
+ {
+ var ret = db.DeleteCrontabItem(routing.ConversationId);
+ message.Content = $"Task schedule canceled result: {ret}";
+ }
+ else
+ {
+ var crontabItem = new CrontabItem
+ {
+ Title = args.Title,
+ Description = args.Description,
+ Cron = args.Cron,
+ UserId = user.Id,
+ AgentId = routing.EntryAgentId,
+ ConversationId = routing.ConversationId,
+ Tasks = args.Tasks,
+ };
+
+ var ret = db.UpsertCrontabItem(crontabItem);
+ message.Content = $"Task scheduled result: {ret}";
+ }
+
+ return true;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Hooks/CrontabUtilityHook.cs b/src/Infrastructure/BotSharp.Core.Crontab/Hooks/CrontabUtilityHook.cs
new file mode 100644
index 00000000..ed150861
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Hooks/CrontabUtilityHook.cs
@@ -0,0 +1,25 @@
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Core.Crontab.Enum;
+
+namespace BotSharp.Core.Crontab.Hooks;
+
+public class CrontabUtilityHook : IAgentUtilityHook
+{
+ public const string PREFIX = "util-crontab-";
+ private const string SCHEDULE_TASK_FN = $"{PREFIX}schedule_task";
+
+ public void AddUtilities(List utilities)
+ {
+ var items = new List
+ {
+ new AgentUtility
+ {
+ Name = UtilityName.ScheduleTask,
+ Functions = [new(SCHEDULE_TASK_FN)],
+ Templates = [new($"{SCHEDULE_TASK_FN}.fn")]
+ }
+ };
+
+ utilities.AddRange(items);
+ }
+}
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..78b737a0
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs
@@ -0,0 +1,54 @@
+/*****************************************************************************
+ 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.Repositories;
+using BotSharp.Core.Infrastructures;
+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()
+ {
+ var repo = _services.GetRequiredService();
+ var crontable = repo.GetCrontabItems(CrontabItemFilter.Empty());
+ return crontable.Items.ToList();
+ }
+
+ public async Task ScheduledTimeArrived(CrontabItem item)
+ {
+ _logger.LogDebug($"ScheduledTimeArrived {item}");
+
+ 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..5a48f58b
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs
@@ -0,0 +1,80 @@
+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)
+ {
+ try
+ {
+ // strip seconds from cron expression
+ item.Cron = string.Join(" ", item.Cron.Split(' ').TakeLast(5));
+ var schedule = CrontabSchedule.Parse(item.Cron, new CrontabSchedule.ParseOptions
+ {
+ IncludingSeconds = false // 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.LogDebug($"The current time matches the cron expression {item}");
+ cron.ScheduledTimeArrived(item);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when running cron task ({item.ConversationId}, {item.Title}, {item.Cron}): {ex.Message}\r\n{ex.InnerException}");
+ continue;
+ }
+ }
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Using.cs b/src/Infrastructure/BotSharp.Core.Crontab/Using.cs
new file mode 100644
index 00000000..0420faf7
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/Using.cs
@@ -0,0 +1,13 @@
+global using System.Text.Json;
+
+global using Microsoft.Extensions.Configuration;
+global using Microsoft.Extensions.DependencyInjection;
+
+global using BotSharp.Abstraction.Agents.Enums;
+global using BotSharp.Abstraction.Crontab.Models;
+global using BotSharp.Abstraction.Agents;
+global using BotSharp.Abstraction.Plugins;
+global using BotSharp.Abstraction.Conversations.Models;
+global using BotSharp.Abstraction.Functions;
+global using BotSharp.Core.Crontab.Services;
+global using BotSharp.Core.Crontab.Abstraction;
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-crontab-schedule_task.json b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-crontab-schedule_task.json
new file mode 100644
index 00000000..04131fe1
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-crontab-schedule_task.json
@@ -0,0 +1,48 @@
+{
+ "name": "util-crontab-schedule_task",
+ "description": "Set up or cancel a scheduled task",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "cron_expression": {
+ "type": "string",
+ "description": "cron expression. Set value as empty if user wants to cancel schedule."
+ },
+ "less_than_60_seconds": {
+ "type": "boolean",
+ "description": "whether schedule interval is less than 60 seconds"
+ },
+ "title": {
+ "type": "string",
+ "description": "task short title"
+ },
+ "description": {
+ "type": "string",
+ "description": "the task summary"
+ },
+ "to_do_list": {
+ "type": "array",
+ "description": "task to do list, should include all key business entities",
+ "items": {
+ "type": "object",
+ "properties": {
+ "topic": {
+ "type": "string",
+ "description": "task topic in 1-3 keywords related to the step"
+ },
+ "script": {
+ "type": "string",
+ "description": "task related script, function with parameters or plain text"
+ },
+ "language": {
+ "type": "string",
+ "enum": [ "function", "sql", "python", "text" ],
+ "description": "programming language script, function or plain text"
+ }
+ }
+ }
+ }
+ },
+ "required": [ "cron_expression", "include_seconds", "title", "description", "to_do_list" ]
+ }
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core.Crontab/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-crontab-schedule_task.fn.liquid b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-crontab-schedule_task.fn.liquid
new file mode 100644
index 00000000..17f9cc86
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-crontab-schedule_task.fn.liquid
@@ -0,0 +1,2 @@
+Call util-crontab-schedule_task if user needs to set up a scheduled task with appropriate programming script and language type.
+Set cron_expression as empty if user wants to cancel schedule.
\ 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/BotSharp.Core.SideCar.csproj b/src/Infrastructure/BotSharp.Core.SideCar/BotSharp.Core.SideCar.csproj
index 4b661c2a..b7f89568 100644
--- a/src/Infrastructure/BotSharp.Core.SideCar/BotSharp.Core.SideCar.csproj
+++ b/src/Infrastructure/BotSharp.Core.SideCar/BotSharp.Core.SideCar.csproj
@@ -10,7 +10,7 @@
-
+
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 fda96b17..59087311 100644
--- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
+++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs
@@ -1,3 +1,21 @@
+/*****************************************************************************
+ 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;
public class BotSharpConversationSideCar : IConversationSideCar
@@ -62,9 +80,9 @@ public class BotSharpConversationSideCar : IConversationSideCar
}
public async Task SendMessage(string agentId, string text,
- PostbackMessageModel? postback = null, List? states = null)
+ PostbackMessageModel? postback = null, List? states = null, List? dialogs = null)
{
- BeforeExecute();
+ BeforeExecute(dialogs);
var response = await InnerExecute(agentId, text, postback, states);
AfterExecute();
return response;
@@ -96,7 +114,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
return response;
}
- private void BeforeExecute()
+ private void BeforeExecute(List? dialogs)
{
enabled = true;
var state = _services.GetRequiredService();
@@ -105,8 +123,8 @@ public class BotSharpConversationSideCar : IConversationSideCar
var node = new ConversationContext
{
State = state.GetCurrentState(),
- Dialogs = new(),
- Breakpoints = new(),
+ Dialogs = dialogs ?? [],
+ Breakpoints = [],
RecursiveCounter = routing.Context.GetRecursiveCounter(),
RoutingStack = routing.Context.GetAgentStack()
};
@@ -116,7 +134,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
state.ResetCurrentState();
routing.Context.ResetRecursiveCounter();
routing.Context.ResetAgentStack();
-
+ Utilities.ClearCache();
}
private void AfterExecute()
@@ -130,6 +148,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
state.SetCurrentState(node.State);
routing.Context.SetRecursiveCounter(node.RecursiveCounter);
routing.Context.SetAgentStack(node.RoutingStack);
+ Utilities.ClearCache();
enabled = false;
}
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
index 2f51f1dd..b21772a1 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
@@ -1,7 +1,9 @@
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Settings;
+using BotSharp.Abstraction.Templating;
using BotSharp.Abstraction.Users.Enums;
+using BotSharp.Core.Agents.Hooks;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Agents;
@@ -29,10 +31,13 @@ public class AgentPlugin : IBotSharpPlugin
{
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped(provider =>
{
var settingService = provider.GetRequiredService();
+ var render = provider.GetRequiredService();
+ render.Register(typeof(AgentSettings));
return settingService.Bind("Agent");
});
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs b/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs
new file mode 100644
index 00000000..65d454c8
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Agents/Hooks/BasicAgentHook.cs
@@ -0,0 +1,83 @@
+namespace BotSharp.Core.Agents.Hooks;
+
+public class BasicAgentHook : AgentHookBase
+{
+ public override string SelfId => string.Empty;
+
+ public BasicAgentHook(IServiceProvider services, AgentSettings settings)
+ : base(services, settings)
+ {
+ }
+
+ public override void OnAgentUtilityLoaded(Agent agent)
+ {
+ if (agent.Type == AgentType.Routing) return;
+
+ var conv = _services.GetRequiredService();
+ var isConvMode = conv.IsConversationMode();
+ if (!isConvMode) return;
+
+ agent.Functions ??= [];
+ agent.Utilities ??= [];
+
+ var (functions, templates) = GetUtilityContent(agent);
+
+ 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";
+ }
+ }
+
+ private (IEnumerable, IEnumerable) GetUtilityContent(Agent agent)
+ {
+ var db = _services.GetRequiredService();
+ var (functionNames, templateNames) = GetUniqueContent(agent.Utilities);
+
+ if (agent.MergeUtility)
+ {
+ var routing = _services.GetRequiredService();
+ var entryAgentId = routing.EntryAgentId;
+ if (!string.IsNullOrEmpty(entryAgentId))
+ {
+ var entryAgent = db.GetAgent(entryAgentId, basicsOnly: true);
+ var (fns, tps) = GetUniqueContent(entryAgent?.Utilities);
+ functionNames = functionNames.Concat(fns).Distinct().ToList();
+ templateNames = templateNames.Concat(tps).Distinct().ToList();
+ }
+ }
+
+ var ua = db.GetAgent(BuiltInAgentId.UtilityAssistant);
+ var functions = ua?.Functions?.Where(x => functionNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase))?.ToList() ?? [];
+ var templates = ua?.Templates?.Where(x => templateNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase))?.Select(x => x.Content)?.ToList() ?? [];
+ return (functions, templates);
+ }
+
+ private (IEnumerable, IEnumerable) GetUniqueContent(IEnumerable? utilities)
+ {
+ if (utilities.IsNullOrEmpty())
+ {
+ return ([], []);
+ }
+
+ var prefix = "util-";
+ utilities = utilities?.Where(x => !string.IsNullOrEmpty(x.Name) && !x.Disabled)?.ToList() ?? [];
+ var functionNames = utilities.SelectMany(x => x.Functions)
+ .Where(x => !string.IsNullOrEmpty(x.Name) && x.Name.StartsWith(prefix))
+ .Select(x => x.Name)
+ .Distinct().ToList();
+ var templateNames = utilities.SelectMany(x => x.Templates)
+ .Where(x => !string.IsNullOrEmpty(x.Name) && x.Name.StartsWith(prefix))
+ .Select(x => x.Name)
+ .Distinct().ToList();
+
+ return (functionNames, templateNames);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
index f0a864a2..0e9c2e3c 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
@@ -5,7 +5,7 @@ namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
- public static ConcurrentDictionary> AgentParameterTypes = new();
+ public static ConcurrentDictionary> AgentParameterTypes = new();
[MemoryCache(10 * 60, perInstanceCache: true)]
public async Task LoadAgent(string id)
@@ -67,6 +67,7 @@ public partial class AgentService
hook.OnSamplesLoaded(agent.Samples);
}
+ hook.OnAgentUtilityLoaded(agent);
hook.OnAgentLoaded(agent);
}
@@ -105,14 +106,14 @@ public partial class AgentService
{
var agentId = agent.Id ?? agent.Name;
if (AgentParameterTypes.ContainsKey(agentId)) return;
-
+
AddOrUpdateRoutesParameters(agentId, agent.RoutingRules);
AddOrUpdateFunctionsParameters(agentId, agent.Functions);
}
private void AddOrUpdateRoutesParameters(string agentId, List routingRules)
{
- if(!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
+ if (!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
{
parameterTypes = new();
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
index e9617d17..ba76e310 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
@@ -9,13 +9,16 @@ public partial class AgentService
public string RenderedInstruction(Agent agent)
{
var render = _services.GetRequiredService();
- // update states
var conv = _services.GetRequiredService();
+
+ // update states
foreach (var t in conv.States.GetStates())
{
agent.TemplateDict[t.Key] = t.Value;
}
- return render.Render(agent.Instruction, agent.TemplateDict);
+
+ var res = render.Render(agent.Instruction, agent.TemplateDict);
+ return res;
}
public bool RenderFunction(Agent agent, FunctionDef def)
@@ -108,16 +111,18 @@ public partial class AgentService
public string RenderedTemplate(Agent agent, string templateName)
{
- // render liquid template
- var render = _services.GetRequiredService();
- var template = agent.Templates.First(x => x.Name == templateName).Content;
- // update states
var conv = _services.GetRequiredService();
+ var render = _services.GetRequiredService();
+
+ var template = agent.Templates.First(x => x.Name == templateName).Content;
+
+ // update states
foreach (var t in conv.States.GetStates())
{
agent.TemplateDict[t.Key] = t.Value;
}
+ // render liquid template
var content = render.Render(template, agent.TemplateDict);
HookEmitter.Emit(_services, async hook =>
@@ -126,4 +131,4 @@ public partial class AgentService
return content;
}
-}
+}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
index 18afb27f..ffea8f8d 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
@@ -1,5 +1,4 @@
using BotSharp.Abstraction.Repositories.Enums;
-using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using System.IO;
@@ -28,16 +27,18 @@ public partial class AgentService
record.Description = agent.Description ?? string.Empty;
record.IsPublic = agent.IsPublic;
record.Disabled = agent.Disabled;
+ record.MergeUtility = agent.MergeUtility;
+ record.MaxMessageCount = agent.MaxMessageCount;
record.Type = agent.Type;
- record.Profiles = agent.Profiles ?? new List();
- record.RoutingRules = agent.RoutingRules ?? new List();
+ record.Profiles = agent.Profiles ?? [];
+ record.RoutingRules = agent.RoutingRules ?? [];
record.Instruction = agent.Instruction ?? string.Empty;
- record.ChannelInstructions = agent.ChannelInstructions ?? new List();
- record.Functions = agent.Functions ?? new List();
- record.Templates = agent.Templates ?? new List();
- record.Responses = agent.Responses ?? new List();
- record.Samples = agent.Samples ?? new List();
- record.Utilities = agent.Utilities ?? new List();
+ record.ChannelInstructions = agent.ChannelInstructions ?? [];
+ record.Functions = agent.Functions ?? [];
+ record.Templates = agent.Templates ?? [];
+ record.Responses = agent.Responses ?? [];
+ record.Samples = agent.Samples ?? [];
+ record.Utilities = agent.Utilities ?? [];
if (agent.LlmConfig != null && !agent.LlmConfig.IsInherit)
{
record.LlmConfig = agent.LlmConfig;
@@ -90,6 +91,7 @@ public partial class AgentService
.SetDescription(foundAgent.Description)
.SetIsPublic(foundAgent.IsPublic)
.SetDisabled(foundAgent.Disabled)
+ .SetMergeUtility(foundAgent.MergeUtility)
.SetAgentType(foundAgent.Type)
.SetProfiles(foundAgent.Profiles)
.SetRoutingRules(foundAgent.RoutingRules)
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
index a48201d8..1dd331af 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
@@ -57,14 +57,14 @@ public partial class AgentService : IAgentService
return userAgents;
}
- public IEnumerable GetAgentUtilities()
+ public IEnumerable GetAgentUtilityOptions()
{
- var utilities = new List();
+ var utilities = new List();
var hooks = _services.GetServices();
foreach (var hook in hooks)
{
hook.AddUtilities(utilities);
}
- return utilities.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().OrderBy(x => x).ToList();
+ return utilities.Where(x => !string.IsNullOrWhiteSpace(x.Name)).OrderBy(x => x.Name).ToList();
}
}
diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
index 32987e69..1c9955aa 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
+++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
@@ -59,6 +59,11 @@
+
+
+
+
+
@@ -73,10 +78,6 @@
-
-
-
-
@@ -120,16 +121,19 @@
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
-
+
+ PreserveNewest
+
+
PreserveNewest
@@ -186,7 +190,7 @@
-
+
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
index a60fb4fd..af8a9324 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
@@ -12,6 +12,8 @@ using BotSharp.Core.Processors;
using StackExchange.Redis;
using BotSharp.Core.Infrastructures.Events;
using BotSharp.Core.Roles.Services;
+using BotSharp.Abstraction.Templating;
+using BotSharp.Core.Templating;
namespace BotSharp.Core;
@@ -23,7 +25,9 @@ public static class BotSharpCoreExtensions
config.Bind("Interpreter", interpreterSettings);
services.AddSingleton(x => interpreterSettings);
- services.AddSingleton();
+ services.AddSingleton();
+ // Register template render
+ services.AddSingleton();
services.AddScoped();
services.AddScoped();
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
index a9be6fd2..09936dec 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
@@ -1,13 +1,13 @@
using BotSharp.Abstraction.Google.Settings;
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Messaging;
+using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Plugins.Models;
-using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Templating;
using BotSharp.Core.Instructs;
using BotSharp.Core.Messaging;
-using BotSharp.Core.Routing.Planning;
+using BotSharp.Core.Routing.Reasoning;
using BotSharp.Core.Templating;
using BotSharp.Core.Translation;
using Microsoft.Extensions.Configuration;
@@ -30,6 +30,8 @@ public class ConversationPlugin : IBotSharpPlugin
services.AddScoped(provider =>
{
var settingService = provider.GetRequiredService();
+ var render = provider.GetRequiredService();
+ render.Register(typeof(ConversationSetting));
return settingService.Bind("Conversation");
});
@@ -48,8 +50,6 @@ public class ConversationPlugin : IBotSharpPlugin
// Rich content messaging
services.AddScoped();
- // Register template render
- services.AddSingleton();
services.AddScoped();
services.AddScoped();
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index 375cf3e6..f60d32cf 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -28,7 +28,7 @@ public partial class ConversationService
var dialogs = conv.GetDialogHistory();
var statistics = _services.GetRequiredService();
- var hooks = _services.GetServices().ToList();
+ var hookProvider = _services.GetRequiredService();
RoleDialogModel response = message;
bool stopCompletion = false;
@@ -44,9 +44,7 @@ public partial class ConversationService
message.Payload = replyMessage.Payload;
}
- // Before chat completion hook
- hooks = ReOrderConversationHooks(hooks);
- foreach (var hook in hooks)
+ foreach (var hook in hookProvider.HooksOrderByPriority)
{
hook.SetAgent(agent)
.SetConversation(conversation);
@@ -173,18 +171,4 @@ public partial class ConversationService
// Add to dialog history
_storage.Append(_conversationId, response);
}
-
- private List ReOrderConversationHooks(List hooks)
- {
- var target = "ChatHubConversationHook";
- var chathub = hooks.FirstOrDefault(x => x.GetType().Name == target);
- var otherHooks = hooks.Where(x => x.GetType().Name != target).ToList();
-
- if (chathub != null)
- {
- var newHooks = new List { chathub }.Concat(otherHooks);
- return newHooks.ToList();
- }
- return hooks;
- }
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
index ccc074e8..1a75d717 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
@@ -9,7 +9,7 @@ public partial class ConversationService : IConversationService
var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true);
fileStorage.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
- var hooks = _services.GetServices().ToList();
+ var hooks = _services.GetServices();
foreach (var hook in hooks)
{
await hook.OnMessageDeleted(conversationId, messageId);
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
index 8f88f44f..75a0bbfb 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
@@ -31,9 +31,9 @@ public partial class ConversationService : IConversationService
states.CleanStates(excludedStates);
}
- var hooks = _services.GetServices()
- .OrderBy(x => x.Priority)
- .ToList();
+ var hooks = _services
+ .GetRequiredService()
+ .HooksOrderByPriority;
// Before executing functions
foreach (var hook in hooks)
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
index f6326a7b..7e498afe 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
@@ -51,6 +51,14 @@ public partial class ConversationService : IConversationService
return conversation;
}
+ public async Task UpdateConversationTitleAlias(string id, string titleAlias)
+ {
+ var db = _services.GetRequiredService();
+ db.UpdateConversationTitleAlias(id, titleAlias);
+ var conversation = db.GetConversation(id);
+ return conversation;
+ }
+
public async Task UpdateConversationTags(string conversationId, List tags)
{
var db = _services.GetRequiredService();
@@ -103,7 +111,8 @@ public partial class ConversationService : IConversationService
db.CreateNewConversation(record);
- var hooks = _services.GetServices().ToList();
+ var hooks = _services.GetServices();
+
foreach (var hook in hooks)
{
// If user connect agent first time
@@ -153,7 +162,10 @@ public partial class ConversationService : IConversationService
}
}
- return dialogs.TakeLast(lastCount).ToList();
+ var agentMsgCount = GetAgentMessageCount();
+ var count = agentMsgCount.HasValue && agentMsgCount.Value > 0 ? agentMsgCount.Value : lastCount;
+
+ return dialogs.TakeLast(count).ToList();
}
public void SetConversationId(string conversationId, List states, bool isReadOnly = false)
@@ -173,12 +185,14 @@ public partial class ConversationService : IConversationService
var state = _services.GetRequiredService();
var channel = state.GetState("channel");
var channelId = state.GetState("channel_id");
+ var userId = state.GetState("current_user_id");
var sess = new Conversation
{
Id = _conversationId,
Channel = channel,
ChannelId = channelId,
- AgentId = agentId
+ AgentId = agentId,
+ UserId = userId,
};
converation = await NewConversation(sess);
}
@@ -190,4 +204,16 @@ public partial class ConversationService : IConversationService
{
return !string.IsNullOrWhiteSpace(_conversationId);
}
+
+
+ private int? GetAgentMessageCount()
+ {
+ var db = _services.GetRequiredService();
+ var routingCtx = _services.GetRequiredService();
+
+ if (string.IsNullOrEmpty(routingCtx.EntryAgentId)) return null;
+
+ var agent = db.GetAgent(routingCtx.EntryAgentId, basicsOnly: true);
+ return agent?.MaxMessageCount;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
index 8ee4c662..eedcdf9a 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
@@ -1,5 +1,20 @@
+/*****************************************************************************
+ 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;
-using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.Core.Conversations.Services;
@@ -21,13 +36,14 @@ public class ConversationStateService : IConversationStateService, IDisposable
///
private ConversationState _historyStates;
- public ConversationStateService(ILogger logger,
+ public ConversationStateService(
IServiceProvider services,
- IBotSharpRepository db)
+ IBotSharpRepository db,
+ ILogger logger)
{
- _logger = logger;
_services = services;
_db = db;
+ _logger = logger;
_curStates = new ConversationState();
_historyStates = new ConversationState();
}
@@ -125,8 +141,13 @@ public class ConversationStateService : IConversationStateService, IDisposable
var curMsgId = routingCtx.MessageId;
_historyStates = _db.GetConversationStates(conversationId);
+
+ var endNodes = new Dictionary();
+
+ if (_historyStates.IsNullOrEmpty()) return endNodes;
+
var dialogs = _db.GetConversationDialogs(conversationId);
- var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User || x.MetaData?.Role == UserRole.User)
+ var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User)
.GroupBy(x => x.MetaData?.MessageId)
.Select(g => g.First())
.OrderBy(x => x.MetaData?.CreateTime)
@@ -134,9 +155,6 @@ public class ConversationStateService : IConversationStateService, IDisposable
var curMsgIndex = userDialogs.FindIndex(x => !string.IsNullOrEmpty(curMsgId) && x.MetaData?.MessageId == curMsgId);
curMsgIndex = curMsgIndex < 0 ? userDialogs.Count() : curMsgIndex;
- var endNodes = new Dictionary();
- if (_historyStates.IsNullOrEmpty()) return endNodes;
-
foreach (var state in _historyStates)
{
var key = state.Key;
diff --git a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs
index bfc0c1ec..b315b683 100644
--- a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs
+++ b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluationConversationHook.cs
@@ -15,45 +15,45 @@ public class EvaluationConversationHook : ConversationHookBase
public override Task OnMessageReceived(RoleDialogModel message)
{
- if (_conversation != null && _convSettings.EnableExecutionLog)
+ if (Conversation != null && _convSettings.EnableExecutionLog)
{
- _logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
+ _logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
}
return base.OnMessageReceived(message);
}
public override Task OnFunctionExecuted(RoleDialogModel message)
{
- if (_conversation != null && _convSettings.EnableExecutionLog)
+ if (Conversation != null && _convSettings.EnableExecutionLog)
{
- _logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.FunctionName}({message.FunctionArgs}) => {message.Content}");
+ _logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.FunctionName}({message.FunctionArgs}) => {message.Content}");
}
return base.OnFunctionExecuted(message);
}
public override Task OnResponseGenerated(RoleDialogModel message)
{
- if (_conversation != null && _convSettings.EnableExecutionLog)
+ if (Conversation != null && _convSettings.EnableExecutionLog)
{
- _logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
- }
+ _logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
+ }
return base.OnResponseGenerated(message);
}
public override Task OnHumanInterventionNeeded(RoleDialogModel message)
{
- if (_conversation != null && _convSettings.EnableExecutionLog)
+ if (Conversation != null && _convSettings.EnableExecutionLog)
{
- _logger.Append(_conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
+ _logger.Append(Conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
}
return base.OnHumanInterventionNeeded(message);
}
public override Task OnConversationEnding(RoleDialogModel message)
{
- if (_conversation != null && _convSettings.EnableExecutionLog)
+ if (Conversation != null && _convSettings.EnableExecutionLog)
{
- _logger.Append(_conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
+ _logger.Append(Conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
}
return base.OnConversationEnding(message);
}
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/Infrastructures/Events/RedisPublisher.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs
index f6daa59d..89de2453 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Infrastructures.Enums;
using StackExchange.Redis;
namespace BotSharp.Core.Infrastructures.Events;
@@ -20,14 +21,65 @@ public class RedisPublisher : IEventPublisher
await _subscriber.PublishAsync(channel, message);
}
- public async Task PublishAsync(string channel, string message)
+ public async Task PublishAsync(string channel, string message, EventPriority? priority = null)
{
var db = _redis.GetDatabase();
+
+ // convert to apporiate channel by priority
+ if (priority != null)
+ {
+ channel = $"{channel}-{priority}";
+ }
+
+ if (CheckMessageExists(db, channel, "message", message))
+ {
+ _logger.LogError($"The message already exists {channel} {message}");
+ return null;
+ }
+
// Add a message to the stream, keeping only the latest 1 million messages
- await db.StreamAddAsync(channel, "message", message,
+ var messageId = await db.StreamAddAsync(channel,
+ [
+ new NameValueEntry("message", message),
+ new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
+ ],
maxLength: 1000 * 10000);
- _logger.LogInformation($"Published message {channel} {message}");
+ _logger.LogInformation($"Published message {channel} {message} ({messageId})");
+
+ return messageId;
+ }
+
+ private bool CheckMessageExists(IDatabase db, string channel, string fieldName, string desiredValue)
+ {
+ // Define the range to fetch all messages
+ RedisValue start = "-"; // Start from the smallest ID
+ RedisValue end = "+"; // End at the largest ID
+ int count = 10; // Number of messages to retrieve
+
+ // Fetch the latest 10 messages
+ var streamEntries = db.StreamRange(channel, start, end, count, Order.Descending);
+
+ if (streamEntries.Length == 0)
+ {
+ return false;
+ }
+
+ // Check if any message contains the specific field value
+ bool exists = streamEntries.Any(entry =>
+ {
+ // Each entry contains a collection of Name-Value pairs
+ foreach (var nameValue in entry.Values)
+ {
+ if (nameValue.Name == fieldName && nameValue.Value == desiredValue)
+ {
+ return true;
+ }
+ }
+ return false;
+ });
+
+ return exists;
}
public async Task ReDispatchAsync(string channel, int count = 10, string order = "asc")
@@ -41,7 +93,10 @@ public class RedisPublisher : IEventPublisher
try
{
- var messageId = await db.StreamAddAsync(channel, "message", entry.Values[0].Value);
+ var messageId = await db.StreamAddAsync(channel, [
+ new NameValueEntry("message", entry.Values[0].Value),
+ new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
+ ]);
_logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})");
@@ -50,7 +105,78 @@ public class RedisPublisher : IEventPublisher
}
catch (Exception ex)
{
- _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}");
+ _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
+ }
+ }
+ }
+
+ public async Task ReDispatchPendingAsync(string channel, string group, int count = 10)
+ {
+ var db = _redis.GetDatabase();
+
+ // Step 1: Fetch pending messages using XPending
+ try
+ {
+ var pendingInfo = await db.StreamPendingAsync(channel, group);
+
+ if (pendingInfo.PendingMessageCount == 0)
+ {
+ Console.WriteLine("No pending messages found.");
+ return;
+ }
+
+ // Step 2: Fetch pending message IDs
+ List pendingMessages = new List();
+ foreach (var consumer in pendingInfo.Consumers)
+ {
+ var pendingMessageIds = await db.StreamPendingMessagesAsync(channel, group, count: consumer.PendingMessageCount, consumerName: consumer.Name);
+ var messageIds = pendingMessageIds.Select(x => x.MessageId).ToArray();
+ // Step 3: Use XClaim to fetch the actual message
+ var claimedMessages = await db.StreamClaimAsync(channel, group, consumer.Name, minIdleTimeInMs: 60 * 1000, messageIds);
+ pendingMessages.AddRange(claimedMessages);
+
+ await db.StreamAcknowledgeAsync(channel, group, messageIds);
+ }
+
+ // Step 4: Process the messages
+ foreach (var message in pendingMessages)
+ {
+ /*if (message.IsNull)
+ {
+ await db.StreamAcknowledgeAsync(channel, group, [message.Id]);
+ }
+ else
+ {
+ var messageId = await db.StreamAddAsync(channel, "message", message.Values[0].Value);
+ _logger.LogWarning($"ReDispatched message: {channel} {message.Values[0].Value} ({messageId})");
+ await db.StreamDeleteAsync(channel, [messageId]);
+ }*/
+ }
+ }
+ catch (RedisException ex)
+ {
+ Console.WriteLine($"Redis error: {ex.Message}");
+ }
+ }
+
+ public async Task RemoveAsync(string channel, int count = 10)
+ {
+ var db = _redis.GetDatabase();
+
+ var entries = await db.StreamRangeAsync(channel, "-", "+", count: count, messageOrder: Order.Ascending);
+ foreach (var entry in entries)
+ {
+ _logger.LogInformation($"Fetched message: {channel} {entry.Values[0].Value} ({entry.Id})");
+
+ try
+ {
+ await db.StreamDeleteAsync(channel, [entry.Id]);
+
+ _logger.LogWarning($"Deleted message: {channel} {entry.Values[0].Value} ({entry.Id})");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
}
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs
index 726b9358..9a3c9982 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Infrastructures.Enums;
using StackExchange.Redis;
namespace BotSharp.Core.Infrastructures.Events;
@@ -24,10 +25,86 @@ public class RedisSubscriber : IEventSubscriber
});
}
- public async Task SubscribeAsync(string channel, string group, Func received)
+ public async Task SubscribeAsync(string channel, string group, int? port, bool priorityEnabled,
+ Func received,
+ CancellationToken? stoppingToken = null)
{
var db = _redis.GetDatabase();
+ if (priorityEnabled)
+ {
+ await CreateConsumerGroup(db, $"{channel}-{EventPriority.Low}", group);
+ await CreateConsumerGroup(db, $"{channel}-{EventPriority.Medium}", group);
+ await CreateConsumerGroup(db, $"{channel}-{EventPriority.High}", group);
+ }
+ else
+ {
+ await CreateConsumerGroup(db, channel, group);
+ }
+
+ var consumer = Environment.MachineName;
+ if (port.HasValue)
+ {
+ consumer += $"-{port}";
+ }
+
+ while (true)
+ {
+ await Task.Delay(100);
+
+ if (stoppingToken.HasValue && stoppingToken.Value.IsCancellationRequested)
+ {
+ _logger.LogInformation($"Stopping consumer channel & group: [{channel}, {group}]");
+ break;
+ }
+
+ if (priorityEnabled)
+ {
+ if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, consumer, received) > 0)
+ {
+ continue;
+ }
+
+ if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, consumer, received) > 0)
+ {
+ continue;
+ }
+
+ await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, consumer, received);
+ }
+ else
+ {
+ await HandleGroupMessage(db, channel, group, consumer, received);
+ }
+ }
+ }
+
+ private async Task HandleGroupMessage(IDatabase db, string channel, string group, string consumer, Func received)
+ {
+ var entries = await db.StreamReadGroupAsync(channel, group, consumer, count: 1);
+ foreach (var entry in entries)
+ {
+ _logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}");
+ await db.StreamAcknowledgeAsync(channel, group, entry.Id);
+
+ try
+ {
+ await received(channel, entry.Values[0].Value);
+
+ // Optionally delete the message to save space
+ await db.StreamDeleteAsync(channel, [entry.Id]);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
+ }
+ }
+
+ return entries.Length;
+ }
+
+ private async Task CreateConsumerGroup(IDatabase db, string channel, string group)
+ {
// Create the consumer group if it doesn't exist
try
{
@@ -43,33 +120,5 @@ public class RedisSubscriber : IEventSubscriber
_logger.LogError($"Error creating consumer group: '{group}' {ex.Message}");
throw;
}
-
- while (true)
- {
- var entries = await db.StreamReadGroupAsync(channel, group, Environment.MachineName, count: 1);
- foreach (var entry in entries)
- {
- _logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}");
-
- try
- {
- await received(channel, entry.Values[0].Value);
-
- // Optionally delete the message to save space
- await db.StreamDeleteAsync(channel, [entry.Id]);
- }
- catch (Exception ex)
- {
- _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}");
- }
- finally
- {
- await db.StreamAcknowledgeAsync(channel, group, entry.Id);
- }
- }
-
- await Task.Delay(Random.Shared.Next(1, 11) * 100);
- }
-
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs
index 943386e1..454ed840 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs
@@ -15,7 +15,7 @@ public static class HookEmitter
{
try
{
- logger.LogInformation($"Emit hook action on {action.Method.Name}({hook.GetType().Name})");
+ logger.LogDebug($"Emit hook action on {action.Method.Name}({hook.GetType().Name})");
action(hook);
if (option.OnlyOnce)
@@ -43,7 +43,7 @@ public static class HookEmitter
{
try
{
- logger.LogInformation($"Emit hook action on {action.Method.Name}({hook.GetType().Name})");
+ logger.LogDebug($"Emit hook action on {action.Method.Name}({hook.GetType().Name})");
await action(hook);
if (option.OnlyOnce)
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs
index 4cb26888..db5a9a7d 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs
@@ -23,17 +23,23 @@ public static class Utilities
var data = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
var sb = new StringBuilder();
- foreach(var c in data)
+ foreach (var c in data)
{
sb.Append(c.ToString("x2"));
}
return sb.ToString();
}
- public static (string, string) SplitAsTuple(this string str, string sep)
+ public static (string, string, string) SplitAsTuple(this string str, string sep)
{
var splits = str.Split(sep);
- return (splits[0], splits[1]);
+
+ if (splits.Length == 2 || string.IsNullOrWhiteSpace(splits[2]))
+ {
+ return (splits[0], splits[1], "CN");
+ }
+
+ return (splits[0], splits[1], splits[2]);
}
///
@@ -47,4 +53,36 @@ public static class Utilities
memcache.Compact(100);
}
}
+
+ public static string HideMiddleDigits(string input, bool isEmail = false)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ return input;
+ }
+
+ if (isEmail)
+ {
+ int atIndex = input.IndexOf('@');
+ if (atIndex > 1)
+ {
+ string localPart = input.Substring(0, atIndex);
+ if (localPart.Length > 2)
+ {
+ string maskedLocalPart = $"{localPart[0]}{new string('*', localPart.Length - 2)}{localPart[^1]}";
+ return $"{maskedLocalPart}@{input.Substring(atIndex + 1)}";
+ }
+ }
+ }
+ else
+ {
+ if (input.Length > 6)
+ {
+ return $"{input.Substring(0, 3)}{new string('*', input.Length - 6)}{input.Substring(input.Length - 3)}";
+ }
+ }
+
+ return input;
+ }
+
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
index d2c519eb..719f3177 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
@@ -16,7 +16,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
#endregion
#region Agent
- public Agent GetAgent(string agentId)
+ public Agent GetAgent(string agentId, bool basicsOnly = false)
=> throw new NotImplementedException();
public List GetAgents(AgentFilter filter)
@@ -105,6 +105,8 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public void UpdateConversationTitle(string conversationId, string title)
=> throw new NotImplementedException();
+ public void UpdateConversationTitleAlias(string conversationId, string titleAlias)
+ => throw new NotImplementedException();
public bool UpdateConversationTags(string conversationId, List tags)
=> throw new NotImplementedException();
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
index 7f545527..a838870f 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs
@@ -55,7 +55,10 @@ namespace BotSharp.Core.Repository
UpdateAgentLlmConfig(agent.Id, agent.LlmConfig);
break;
case AgentField.Utility:
- UpdateAgentUtilities(agent.Id, agent.Utilities);
+ UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities);
+ break;
+ case AgentField.MaxMessageCount:
+ UpdateAgentMaxMessageCount(agent.Id, agent.MaxMessageCount);
break;
case AgentField.All:
UpdateAgentAllFields(agent);
@@ -151,13 +154,14 @@ namespace BotSharp.Core.Repository
File.WriteAllText(agentFile, json);
}
- private void UpdateAgentUtilities(string agentId, List utilities)
+ private void UpdateAgentUtilities(string agentId, bool mergeUtility, List utilities)
{
if (utilities == null) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
+ agent.MergeUtility = mergeUtility;
agent.Utilities = utilities;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
@@ -282,6 +286,17 @@ namespace BotSharp.Core.Repository
File.WriteAllText(agentFile, json);
}
+ private void UpdateAgentMaxMessageCount(string agentId, int? maxMessageCount)
+ {
+ var (agent, agentFile) = GetAgentFromFile(agentId);
+ if (agent == null) return;
+
+ agent.MaxMessageCount = maxMessageCount;
+ agent.UpdatedDateTime = DateTime.UtcNow;
+ var json = JsonSerializer.Serialize(agent, _options);
+ File.WriteAllText(agentFile, json);
+ }
+
private void UpdateAgentAllFields(Agent inputAgent)
{
var (agent, agentFile) = GetAgentFromFile(inputAgent.Id);
@@ -291,11 +306,13 @@ namespace BotSharp.Core.Repository
agent.Description = inputAgent.Description;
agent.IsPublic = inputAgent.IsPublic;
agent.Disabled = inputAgent.Disabled;
+ agent.MergeUtility = inputAgent.MergeUtility;
agent.Type = inputAgent.Type;
agent.Profiles = inputAgent.Profiles;
agent.Utilities = inputAgent.Utilities;
agent.RoutingRules = inputAgent.RoutingRules;
agent.LlmConfig = inputAgent.LlmConfig;
+ agent.MaxMessageCount = inputAgent.MaxMessageCount;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
@@ -327,7 +344,7 @@ namespace BotSharp.Core.Repository
return responses;
}
- public Agent? GetAgent(string agentId)
+ public Agent? GetAgent(string agentId, bool basicsOnly = false)
{
var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir);
var dir = Directory.GetDirectories(agentDir).FirstOrDefault(x => x.Split(Path.DirectorySeparatorChar).Last() == agentId);
@@ -340,6 +357,8 @@ namespace BotSharp.Core.Repository
var record = JsonSerializer.Deserialize(json, _options);
if (record == null) return null;
+ if (basicsOnly) return record;
+
var (defaultInstruction, channelInstructions) = FetchInstructions(dir);
var functions = FetchFunctions(dir);
var samples = FetchSamples(dir);
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
index fd13936c..34345271 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
@@ -134,6 +134,22 @@ namespace BotSharp.Core.Repository
}
}
}
+ public void UpdateConversationTitleAlias(string conversationId, string titleAlias)
+ {
+ var convDir = FindConversationDirectory(conversationId);
+ if (!string.IsNullOrEmpty(convDir))
+ {
+ var convFile = Path.Combine(convDir, CONVERSATION_FILE);
+ var content = File.ReadAllText(convFile);
+ var record = JsonSerializer.Deserialize(content, _options);
+ if (record != null)
+ {
+ record.TitleAlias = titleAlias;
+ record.UpdatedTime = DateTime.UtcNow;
+ File.WriteAllText(convFile, JsonSerializer.Serialize(record, _options));
+ }
+ }
+ }
public bool UpdateConversationTags(string conversationId, List tags)
{
@@ -356,6 +372,10 @@ namespace BotSharp.Core.Repository
{
matched = matched && record.Title.Contains(filter.Title);
}
+ if (filter?.TitleAlias != null)
+ {
+ matched = matched && record.TitleAlias.Contains(filter.TitleAlias);
+ }
if (filter?.AgentId != null)
{
matched = matched && record.AgentId == filter.AgentId;
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs
new file mode 100644
index 00000000..5d3eb865
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs
@@ -0,0 +1,117 @@
+using System.IO;
+
+namespace BotSharp.Core.Repository;
+
+public partial class FileRepository
+{
+ public bool UpsertCrontabItem(CrontabItem cron)
+ {
+ if (cron == null || string.IsNullOrWhiteSpace(cron.ConversationId))
+ {
+ return false;
+ }
+
+ try
+ {
+ var baseDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, cron.ConversationId);
+ if (!Directory.Exists(baseDir))
+ {
+ return false;
+ }
+
+ var cronFile = Path.Combine(baseDir, CRON_FILE);
+ var json = JsonSerializer.Serialize(cron, _options);
+ File.WriteAllText(cronFile, json);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Error when saving crontab item: {ex.Message}\r\n{ex.InnerException}");
+ return false;
+ }
+ }
+
+ public bool DeleteCrontabItem(string conversationId)
+ {
+ if (string.IsNullOrWhiteSpace(conversationId))
+ {
+ return false;
+ }
+
+ try
+ {
+ var baseDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversationId);
+ if (!Directory.Exists(baseDir))
+ {
+ return false;
+ }
+
+ var cronFile = Path.Combine(baseDir, CRON_FILE);
+ if (!File.Exists(cronFile))
+ {
+ return false;
+ }
+
+ File.Delete(cronFile);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Error when deleting crontab item (${conversationId}): {ex.Message}\r\n{ex.InnerException}");
+ return false;
+ }
+ }
+
+
+ public PagedItems GetCrontabItems(CrontabItemFilter filter)
+ {
+
+ if (filter == null)
+ {
+ filter = CrontabItemFilter.Empty();
+ }
+
+ var records = new List();
+ var baseDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
+
+ if (!Directory.Exists(baseDir))
+ {
+ Directory.CreateDirectory(baseDir);
+ }
+
+ var totalDirs = Directory.GetDirectories(baseDir);
+ foreach (var d in totalDirs)
+ {
+ var file = Path.Combine(d, CRON_FILE);
+ if (!File.Exists(file)) continue;
+
+ var json = File.ReadAllText(file);
+ var record = JsonSerializer.Deserialize(json, _options);
+ if (record == null) continue;
+
+ var matched = true;
+ if (filter?.AgentIds != null)
+ {
+ matched = matched && filter.AgentIds.Contains(record.AgentId);
+ }
+ if (filter?.ConversationIds != null)
+ {
+ matched = matched && filter.ConversationIds.Contains(record.ConversationId);
+ }
+ if (filter?.UserIds != null)
+ {
+ matched = matched && filter.UserIds.Contains(record.UserId);
+ }
+
+ if (!matched) continue;
+
+ records.Add(record);
+ }
+
+ return new PagedItems
+ {
+ Items = records.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size),
+ Count = records.Count(),
+ };
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
index 9600bbc8..9d5b42be 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
@@ -11,9 +11,21 @@ public partial class FileRepository
return Users.FirstOrDefault(x => x.Email == email.ToLower());
}
- public User? GetUserByPhone(string phone)
+ public User? GetUserByPhone(string phone, string? type = UserType.Client, string regionCode = "CN")
{
- return Users.FirstOrDefault(x => x.Phone == phone);
+ var query = Users.Where(x => x.Phone == phone);
+
+ if (!string.IsNullOrEmpty(type))
+ {
+ query = query.Where(x => x.Type == type);
+ }
+
+ if (!string.IsNullOrEmpty(regionCode))
+ {
+ query = query.Where(x => x.RegionCode == regionCode);
+ }
+
+ return query.FirstOrDefault();
}
public User? GetAffiliateUserByPhone(string phone)
@@ -41,6 +53,11 @@ public partial class FileRepository
return Users.FirstOrDefault(x => x.UserName == userName.ToLower());
}
+ public Dashboard? GetDashboard(string id = null)
+ {
+ return Dashboards.FirstOrDefault();
+ }
+
public void CreateUser(User user)
{
var userId = Guid.NewGuid().ToString();
@@ -191,4 +208,68 @@ public partial class FileRepository
_users = [];
return true;
}
+
+ public void AddDashboardConversation(string userId, string conversationId)
+ {
+ var user = GetUserById(userId);
+ if (user == null) return;
+
+ // one user only has one dashboard currently
+ var dash = Dashboards.FirstOrDefault();
+ dash ??= new();
+ var existingConv = dash.ConversationList.FirstOrDefault(x => string.Equals(x.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase));
+ if (existingConv != null) return;
+
+ var dashconv = new DashboardConversation
+ {
+ Id = Guid.NewGuid().ToString(),
+ ConversationId = conversationId
+ };
+
+ dash.ConversationList.Add(dashconv);
+
+ var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, userId);
+ var path = Path.Combine(dir, DASHBOARD_FILE);
+ File.WriteAllText(path, JsonSerializer.Serialize(dash, _options));
+ }
+
+ public void RemoveDashboardConversation(string userId, string conversationId)
+ {
+ var user = GetUserById(userId);
+ if (user == null) return;
+
+ // one user only has one dashboard currently
+ var dash = Dashboards.FirstOrDefault();
+ if (dash == null) return;
+
+ var dashconv = dash.ConversationList.FirstOrDefault(
+ c => string.Equals(c.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase));
+ if (dashconv == null) return;
+
+ dash.ConversationList.Remove(dashconv);
+
+ var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, userId);
+ var path = Path.Combine(dir, DASHBOARD_FILE);
+ File.WriteAllText(path, JsonSerializer.Serialize(dash, _options));
+ }
+
+ public void UpdateDashboardConversation(string userId, DashboardConversation dashConv)
+ {
+ var user = GetUserById(userId);
+ if (user == null) return;
+
+ // one user only has one dashboard currently
+ var dash = Dashboards.FirstOrDefault();
+ if (dash == null) return;
+
+ var curIdx = dash.ConversationList.ToList().FindIndex(
+ x => string.Equals(x.ConversationId, dashConv.ConversationId, StringComparison.OrdinalIgnoreCase));
+ if (curIdx < 0) return;
+
+ dash.ConversationList[curIdx] = dashConv;
+
+ var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, userId);
+ var path = Path.Combine(dir, DASHBOARD_FILE);
+ File.WriteAllText(path, JsonSerializer.Serialize(dash, _options));
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
index 0edcb699..6a718d65 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs
@@ -22,6 +22,7 @@ public partial class FileRepository : IBotSharpRepository
private const string AGENT_FILE = "agent.json";
private const string AGENT_INSTRUCTION_FILE = "instruction";
private const string AGENT_SAMPLES_FILE = "samples.txt";
+ private const string DASHBOARD_FILE = "dashboard.json";
private const string AGENT_INSTRUCTIONS_FOLDER = "instructions";
private const string AGENT_FUNCTIONS_FOLDER = "functions";
private const string AGENT_TEMPLATES_FOLDER = "templates";
@@ -54,6 +55,8 @@ public partial class FileRepository : IBotSharpRepository
private const string PLUGIN_CONFIG_FILE = "config.json";
private const string STATS_FILE = "stats.json";
+ private const string CRON_FILE = "cron.json";
+
public FileRepository(
IServiceProvider services,
BotSharpDatabaseSettings dbSettings,
@@ -83,10 +86,10 @@ public partial class FileRepository : IBotSharpRepository
private List _roles = new List();
private List _users = new List();
+ private List _dashboards = [];
private List _agents = new List();
private List _roleAgents = new List();
private List _userAgents = new List();
- private List _conversations = new List();
private PluginConfig? _pluginConfig = null;
private IQueryable Roles
@@ -170,6 +173,36 @@ public partial class FileRepository : IBotSharpRepository
}
}
+ private IQueryable Dashboards
+ {
+ get
+ {
+ if (!_dashboards.IsNullOrEmpty())
+ {
+ return _dashboards.AsQueryable();
+ }
+
+ var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER);
+ _dashboards = [];
+ if (Directory.Exists(dir))
+ {
+ foreach (var d in Directory.GetDirectories(dir))
+ {
+ var dashboardFile = Path.Combine(d, DASHBOARD_FILE);
+ if (!Directory.Exists(d) || !File.Exists(dashboardFile))
+ continue;
+
+ var json = File.ReadAllText(dashboardFile);
+ var dash = JsonSerializer.Deserialize(json, _options);
+
+ if (dash == null) continue;
+ _dashboards.Add(dash);
+ }
+ }
+ return _dashboards.AsQueryable();
+ }
+ }
+
private IQueryable Agents
{
get
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/Functions/HumanInterventionNeededFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/HumanInterventionNeededFn.cs
index d0c26be0..719dd805 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Functions/HumanInterventionNeededFn.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/HumanInterventionNeededFn.cs
@@ -15,9 +15,9 @@ public class HumanInterventionNeededFn : IFunctionCallback
public async Task Execute(RoleDialogModel message)
{
- var hooks = _services.GetServices()
- .OrderBy(x => x.Priority)
- .ToList();
+ var hooks = _services
+ .GetRequiredService()
+ .HooksOrderByPriority;
foreach (var hook in hooks)
{
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs
index 646dd481..2ee6f9d7 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/ContinueExecuteTaskRoutingHandler.cs
@@ -4,7 +4,7 @@ using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
-using BotSharp.Core.Routing.Planning;
+using BotSharp.Core.Routing.Reasoning;
namespace BotSharp.Core.Routing.Handlers;
@@ -27,7 +27,7 @@ public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase//, IRoutingH
public List Planers => new List
{
- nameof(HFPlanner)
+ nameof(HFReasoner)
};
public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings)
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs
index da58b98b..0d11f296 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/InterruptTaskExecutionRoutingHandler.cs
@@ -1,7 +1,7 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
-using BotSharp.Core.Routing.Planning;
+using BotSharp.Core.Routing.Reasoning;
namespace BotSharp.Core.Routing.Handlers;
@@ -19,7 +19,7 @@ public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase//, IRouti
public List Planers => new List
{
- nameof(HFPlanner)
+ nameof(HFReasoner)
};
public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings)
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs
index 5b5b1025..038488eb 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs
@@ -1,5 +1,5 @@
using BotSharp.Abstraction.Routing.Settings;
-using BotSharp.Core.Routing.Planning;
+using BotSharp.Core.Routing.Reasoning;
namespace BotSharp.Core.Routing.Handlers;
@@ -26,7 +26,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase//, IRoutin
public List Planers => new List
{
- nameof(HFPlanner)
+ nameof(HFReasoner)
};
public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger logger, RoutingSettings settings)
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs
index a847f078..f83afedf 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs
@@ -1,4 +1,3 @@
-using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Routing.Enums;
using BotSharp.Abstraction.Routing.Settings;
@@ -26,6 +25,14 @@ public class RoutingAgentHook : AgentHookBase
var routing = _services.GetRequiredService();
var agents = routing.GetRoutableAgents(_agent.Profiles);
+ // 过滤 Planner
+ var planningRule = _agent.RoutingRules.FirstOrDefault(x => x.Type == "planner");
+ if (planningRule != null)
+ {
+ var planners = agents.Where(x => x.Type == AgentType.Planning && x.Name != planningRule.Field).ToArray();
+ agents = agents.Except(planners).ToArray();
+ }
+
// Postprocess agent required fields, remove it if the states exists
var states = _services.GetRequiredService();
foreach (var agent in agents)
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/FirstStagePlanParameter.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/FirstStagePlanParameter.cs
deleted file mode 100644
index 7bececc6..00000000
--- a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/FirstStagePlanParameter.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Text.Json.Serialization;
-
-public class FirstStagePlanParameter
-{
- [JsonPropertyName("input_args")]
- public JsonDocument[] Parameters { get; set; } = new JsonDocument[0];
-
- [JsonPropertyName("output_results")]
- public string[] Results { get; set; } = new string[0];
-
- public override string ToString()
- {
- return $"INPUTS:\r\n{JsonSerializer.Serialize(Parameters)}\r\n\r\nOUTPUTS:\r\n{JsonSerializer.Serialize(Results)}";
- }
-}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlan.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlan.cs
deleted file mode 100644
index f180c043..00000000
--- a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlan.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace BotSharp.Core.Routing.Planning;
-
-public class SecondStagePlan
-{
- [JsonPropertyName("related_tables")]
- public string[] Tables { get; set; } = new string[0];
-
- [JsonPropertyName("description")]
- public string Description { get; set; } = "";
-
- [JsonPropertyName("tool_name")]
- public string Tool { get; set; } = "";
-
- [JsonPropertyName("input_args")]
- public JsonDocument[] Parameters { get; set; } = new JsonDocument[0];
-
- [JsonPropertyName("output_results")]
- public string[] Results { get; set; } = new string[0];
-}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlanParameter.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlanParameter.cs
deleted file mode 100644
index 1d043740..00000000
--- a/src/Infrastructure/BotSharp.Core/Routing/Planning/TwoStagePlanner/SecondStagePlanParameter.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-public class SecondStagePlanParameter : FirstStagePlanParameter
-{
-
-}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/HFPlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/HFReasoner.cs
similarity index 71%
rename from src/Infrastructure/BotSharp.Core/Routing/Planning/HFPlanner.cs
rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/HFReasoner.cs
index 8984da51..e7d9b49e 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Planning/HFPlanner.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/HFReasoner.cs
@@ -1,17 +1,34 @@
-using BotSharp.Abstraction.Routing.Planning;
+/*****************************************************************************
+ 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.Routing.Reasoning;
using BotSharp.Abstraction.Templating;
-namespace BotSharp.Core.Routing.Planning;
+namespace BotSharp.Core.Routing.Reasoning;
///
-/// Human feedback based planner
+/// Human feedback based reasoner
///
-public class HFPlanner : IRoutingPlaner
+public class HFReasoner : IRoutingReasoner
{
+ public string Name => "Human-Feedback Reasoner";
private readonly IServiceProvider _services;
private readonly ILogger _logger;
- public HFPlanner(IServiceProvider services, ILogger logger)
+ public HFReasoner(IServiceProvider services, ILogger logger)
{
_services = services;
_logger = logger;
@@ -37,7 +54,7 @@ public class HFPlanner : IRoutingPlaner
{
new RoleDialogModel(AgentRole.User, next)
{
- FunctionName = nameof(HFPlanner),
+ FunctionName = nameof(HFReasoner),
MessageId = messageId
}
};
@@ -60,7 +77,7 @@ public class HFPlanner : IRoutingPlaner
}
// Fix LLM malformed response
- PlannerHelper.FixMalformedResponse(_services, inst);
+ ReasonerHelper.FixMalformedResponse(_services, inst);
return inst;
}
@@ -87,13 +104,13 @@ public class HFPlanner : IRoutingPlaner
public async Task AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List dialogs)
{
var context = _services.GetRequiredService();
- context.Empty(reason: $"Agent queue is cleared by {nameof(HFPlanner)}");
+ context.Empty(reason: $"Agent queue is cleared by {nameof(HFReasoner)}");
return true;
}
private string GetNextStepPrompt(Agent router)
{
- var template = router.Templates.First(x => x.Name == "planner_prompt.hf").Content;
+ var template = router.Templates.First(x => x.Name == "reasoner.hf").Content;
var render = _services.GetRequiredService();
// update states
var conv = _services.GetRequiredService();
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs
similarity index 91%
rename from src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs
rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs
index a1d413f0..6bb23e97 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs
@@ -1,6 +1,6 @@
-using BotSharp.Abstraction.Routing.Planning;
+using BotSharp.Abstraction.Planning;
-namespace BotSharp.Core.Routing.Planning;
+namespace BotSharp.Core.Routing.Reasoning;
public class InstructExecutor : IExecutor
{
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs
similarity index 73%
rename from src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs
rename to src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs
index 6ebc5012..931b53e8 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs
@@ -1,16 +1,37 @@
+/*****************************************************************************
+ 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.Infrastructures.Enums;
using BotSharp.Abstraction.Routing.Models;
-using BotSharp.Abstraction.Routing.Planning;
+using BotSharp.Abstraction.Routing.Reasoning;
using BotSharp.Abstraction.Templating;
-namespace BotSharp.Core.Routing.Planning;
+namespace BotSharp.Core.Routing.Reasoning;
-public class NaivePlanner : IRoutingPlaner
+///
+/// simple or unsophisticated methods used to decide which specialized model or module in a system to engage for a given task.
+///
+public class NaiveReasoner : IRoutingReasoner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
- public NaivePlanner(IServiceProvider services, ILogger logger)
+ public string Name => "Naive Reasoner";
+
+ public NaiveReasoner(IServiceProvider services, ILogger logger)
{
_services = services;
_logger = logger;
@@ -46,7 +67,7 @@ public class NaivePlanner : IRoutingPlaner
{
new RoleDialogModel(AgentRole.User, next)
{
- FunctionName = nameof(NaivePlanner),
+ FunctionName = nameof(NaiveReasoner),
MessageId = messageId
}
};
@@ -69,7 +90,7 @@ public class NaivePlanner : IRoutingPlaner
}
// Fix LLM malformed response
- PlannerHelper.FixMalformedResponse(_services, inst);
+ ReasonerHelper.FixMalformedResponse(_services, inst);
return inst;
}
@@ -99,7 +120,7 @@ public class NaivePlanner : IRoutingPlaner
}
else
{
- context.Empty(reason: $"Agent queue is cleared by {nameof(NaivePlanner)}");
+ context.Empty(reason: $"Agent queue is cleared by {nameof(NaiveReasoner)}");
// context.Push(inst.OriginalAgent, "Push user goal agent");
}
return true;
@@ -107,7 +128,7 @@ public class NaivePlanner : IRoutingPlaner
private string GetNextStepPrompt(Agent router)
{
- var template = router.Templates.First(x => x.Name == "planner_prompt.naive").Content;
+ var template = router.Templates.First(x => x.Name == "reasoner.naive").Content;
var states = _services.GetRequiredService();
var render = _services.GetRequiredService