Merge branch 'SciSharp:master' into master

This commit is contained in:
Haiping 2024-11-27 15:32:03 -06:00 committed by GitHub
commit 4841c43755
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
119 changed files with 1389 additions and 1307 deletions

View file

@ -1,5 +1,10 @@
using BotSharp.Abstraction.Agents.Settings;
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Routing;
using Microsoft.Extensions.DependencyInjection;
using System.Data;
namespace BotSharp.Abstraction.Agents;
@ -49,7 +54,71 @@ 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)
{
if (agent.Type == AgentType.Routing) return;
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
if (!isConvMode) return;
agent.Functions ??= [];
agent.Utilities ??= [];
var (functions, templates) = GetUtilityContent(agent);
agent.Functions.AddRange(functions);
foreach (var prompt in templates)
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
}
private (IEnumerable<FunctionDef>, IEnumerable<string>) GetUtilityContent(Agent agent)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var (functionNames, templateNames) = GetUniqueContent(agent.Utilities);
if (agent.MergeUtility)
{
var routing = _services.GetRequiredService<IRoutingContext>();
var entryAgentId = routing.EntryAgentId;
if (!string.IsNullOrEmpty(entryAgentId))
{
var entryAgent = db.GetAgent(entryAgentId);
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<string>, IEnumerable<string>) GetUniqueContent(IEnumerable<AgentUtility>? utilities)
{
if (utilities.IsNullOrEmpty())
{
return ([], []);
}
utilities = utilities?.Where(x => !string.IsNullOrEmpty(x.Name) && !x.Disabled)?.ToList() ?? [];
var functionNames = utilities.SelectMany(x => x.Functions)
.Where(x => !string.IsNullOrEmpty(x.Name))
.Select(x => x.Name)
.Distinct().ToList();
var templateNames = utilities.SelectMany(x => x.Templates)
.Where(x => !string.IsNullOrEmpty(x.Name))
.Select(x => x.Name)
.Distinct().ToList();
return (functionNames, templateNames);
}
}

View file

@ -25,6 +25,8 @@ public interface IAgentHook
bool OnSamplesLoaded(List<string> samples);
void OnAgentUtilityLoaded(Agent agent);
/// <summary>
/// Triggered when agent is loaded completely.
/// </summary>

View file

@ -59,5 +59,5 @@ public interface IAgentService
PluginDef GetPlugin(string agentId);
IEnumerable<string> GetAgentUtilities();
IEnumerable<AgentUtility> GetAgentUtilityOptions();
}

View file

@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.Agents;
public interface IAgentUtilityHook
{
void AddUtilities(List<string> utilities);
void AddUtilities(List<AgentUtility> utilities);
}

View file

@ -87,13 +87,17 @@ public class Agent
/// <summary>
/// Profile by channel
/// </summary>
public List<string> Profiles { get; set; }
= new List<string>();
public List<string> Profiles { get; set; } = new();
/// <summary>
/// Merge utilities from entry agent
/// </summary>
public bool MergeUtility { get; set; }
/// <summary>
/// Agent utilities
/// </summary>
public List<string> Utilities { get; set; } = new();
public List<AgentUtility> Utilities { get; set; } = new();
/// <summary>
/// Inherit from agent
@ -173,9 +177,9 @@ public class Agent
return this;
}
public Agent SetUtilities(List<string> utilities)
public Agent SetUtilities(List<AgentUtility> utilities)
{
Utilities = utilities ?? new List<string>();
Utilities = utilities ?? new List<AgentUtility>();
return this;
}
@ -215,6 +219,12 @@ public class Agent
return this;
}
public Agent SetMergeUtility(bool merge)
{
MergeUtility = merge;
return this;
}
public Agent SetAgentType(string type)
{
Type = type;

View file

@ -0,0 +1,56 @@
namespace BotSharp.Abstraction.Agents.Models;
public class AgentUtility
{
public string Name { get; set; }
public bool Disabled { get; set; }
public IEnumerable<UtilityFunction> Functions { get; set; } = [];
public IEnumerable<UtilityTemplate> Templates { get; set; } = [];
public AgentUtility()
{
}
public AgentUtility(
string name,
IEnumerable<UtilityFunction>? functions = null,
IEnumerable<UtilityTemplate>? templates = null)
{
Name = name;
Functions = functions ?? [];
Templates = templates ?? [];
}
}
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; }
}

View file

@ -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
{

View file

@ -1,9 +1,19 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Planning;
/// <summary>
/// Planning process for Task Agent
/// https://www.promptingguide.ai/techniques/cot
/// </summary>
public class ITaskPlanner
public interface ITaskPlanner
{
Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs);
Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs);
Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs);
List<RoleDialogModel> BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
=> dialogs;
bool AfterHandleContext(List<RoleDialogModel> dialogs, List<RoleDialogModel> taskAgentDialogs)
=> true;
int MaxLoopCount => 5;
}

View file

@ -33,9 +33,13 @@ public interface IBotSharpRepository : IHaveServiceProvider
List<User> GetUserByIds(List<string> ids) => throw new NotImplementedException();
List<User> 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();

View file

@ -12,6 +12,11 @@ public class RuleType
/// </summary>
public const string DataValidation = "data-validation";
/// <summary>
/// The reasoning approach name for next step
/// </summary>
public const string Reasoner = "reasoner";
/// <summary>
/// The planning approach name for next step
/// </summary>

View file

@ -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);

View file

@ -1,19 +0,0 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Routing.Planning;
/// <summary>
/// Task breakdown and execution plan
/// https://www.promptingguide.ai/techniques/cot
/// </summary>
public interface IRoutingPlaner
{
Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs);
Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs);
Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs);
List<RoleDialogModel> BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
=> dialogs;
bool AfterHandleContext(List<RoleDialogModel> dialogs, List<RoleDialogModel> taskAgentDialogs)
=> true;
int MaxLoopCount => 5;
}

View file

@ -0,0 +1,30 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Routing.Reasoning;
/// <summary>
/// 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.
/// </summary>
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<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs);
Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
=> Task.FromResult(true);
Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
=> Task.FromResult(true);
List<RoleDialogModel> BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
=> dialogs;
bool AfterHandleContext(List<RoleDialogModel> dialogs, List<RoleDialogModel> taskAgentDialogs)
=> true;
}

View file

@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Templating;
public interface ITemplateRender
{
string Render(string template, Dictionary<string, object> dict);
void Register(Type type);
}

View file

@ -1,5 +1,8 @@
namespace BotSharp.Abstraction.Users.Enums;
/// <summary>
/// User actions on agent level
/// </summary>
public static class UserAction
{
public const string Edit = "edit";

View file

@ -1,5 +1,8 @@
namespace BotSharp.Abstraction.Users.Enums;
/// <summary>
/// User permission
/// </summary>
public static class UserPermission
{
public const string CreateAgent = "create-agent";

View file

@ -29,4 +29,8 @@ public interface IUserService
Task<bool> UpdatePassword(string newPassword, string verificationCode);
Task<DateTime> GetUserTokenExpires();
Task<bool> UpdateUsersIsDisable(List<string> userIds, bool isDisable);
Task<bool> AddDashboardConversation(string userId, string conversationId);
Task<bool> RemoveDashboardConversation(string userId, string conversationId);
Task UpdateDashboardConversation(string userId, DashboardConversation dashConv);
Task<Dashboard?> GetDashboard(string userId);
}

View file

@ -0,0 +1,20 @@
namespace BotSharp.Abstraction.Users.Models;
public class Dashboard
{
public IList<DashboardConversation> 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; } = "";
}

View file

@ -22,4 +22,26 @@ public static class UserAuthorizationExtension
var actions = found.Actions ?? [];
return actions.Any(x => x == targetAction);
}
/// <summary>
/// Get allowed user actions on the agent. If user is admin, returns null;
/// </summary>
/// <param name="auth"></param>
/// <param name="agentId"></param>
/// <returns></returns>
public static IEnumerable<string>? 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 ?? [];
}
}

View file

@ -12,7 +12,6 @@ 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;

View file

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
<ProjectReference Include="..\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -1,3 +1,5 @@
using BotSharp.Core.Infrastructures;
namespace BotSharp.Core.SideCar.Services;
public class BotSharpConversationSideCar : IConversationSideCar
@ -116,7 +118,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
state.ResetCurrentState();
routing.Context.ResetRecursiveCounter();
routing.Context.ResetAgentStack();
Utilities.ClearCache();
}
private void AfterExecute()
@ -130,6 +132,7 @@ public class BotSharpConversationSideCar : IConversationSideCar
state.SetCurrentState(node.State);
routing.Context.SetRecursiveCounter(node.RecursiveCounter);
routing.Context.SetAgentStack(node.RoutingStack);
Utilities.ClearCache();
enabled = false;
}
}

View file

@ -1,6 +1,7 @@
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Templating;
using BotSharp.Abstraction.Users.Enums;
using Microsoft.Extensions.Configuration;
@ -33,6 +34,8 @@ public class AgentPlugin : IBotSharpPlugin
services.AddScoped(provider =>
{
var settingService = provider.GetRequiredService<ISettingService>();
var render = provider.GetRequiredService<ITemplateRender>();
render.Register(typeof(AgentSettings));
return settingService.Bind<AgentSettings>("Agent");
});
}

View file

@ -67,6 +67,7 @@ public partial class AgentService
hook.OnSamplesLoaded(agent.Samples);
}
hook.OnAgentUtilityLoaded(agent);
hook.OnAgentLoaded(agent);
}

View file

@ -9,13 +9,16 @@ public partial class AgentService
public string RenderedInstruction(Agent agent)
{
var render = _services.GetRequiredService<ITemplateRender>();
// update states
var conv = _services.GetRequiredService<IConversationService>();
// 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<ITemplateRender>();
var template = agent.Templates.First(x => x.Name == templateName).Content;
// update states
var conv = _services.GetRequiredService<IConversationService>();
var render = _services.GetRequiredService<ITemplateRender>();
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<IContentGeneratingHook>(_services, async hook =>
@ -126,4 +131,4 @@ public partial class AgentService
return content;
}
}
}

View file

@ -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,17 @@ public partial class AgentService
record.Description = agent.Description ?? string.Empty;
record.IsPublic = agent.IsPublic;
record.Disabled = agent.Disabled;
record.MergeUtility = agent.MergeUtility;
record.Type = agent.Type;
record.Profiles = agent.Profiles ?? new List<string>();
record.RoutingRules = agent.RoutingRules ?? new List<RoutingRule>();
record.Profiles = agent.Profiles ?? [];
record.RoutingRules = agent.RoutingRules ?? [];
record.Instruction = agent.Instruction ?? string.Empty;
record.ChannelInstructions = agent.ChannelInstructions ?? new List<ChannelInstruction>();
record.Functions = agent.Functions ?? new List<FunctionDef>();
record.Templates = agent.Templates ?? new List<AgentTemplate>();
record.Responses = agent.Responses ?? new List<AgentResponse>();
record.Samples = agent.Samples ?? new List<string>();
record.Utilities = agent.Utilities ?? new List<string>();
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 +90,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)

View file

@ -57,14 +57,14 @@ public partial class AgentService : IAgentService
return userAgents;
}
public IEnumerable<string> GetAgentUtilities()
public IEnumerable<AgentUtility> GetAgentUtilityOptions()
{
var utilities = new List<string>();
var utilities = new List<AgentUtility>();
var hooks = _services.GetServices<IAgentUtilityHook>();
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();
}
}

View file

@ -59,6 +59,11 @@
<ItemGroup>
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\database_knowledge.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.hf.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.naive.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.one-step-forward.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.get_remaining_task.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid" />
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
@ -73,10 +78,6 @@
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instructions\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\.welcome.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\conversation.summary.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.hf.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.naive.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.get_remaining_task.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid" />
@ -120,16 +121,19 @@
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\database_knowledge.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.get_remaining_task.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.get_remaining_task.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.hf.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.hf.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.naive.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.naive.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.one-step-forward.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid">

View file

@ -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;
@ -24,6 +26,8 @@ public static class BotSharpCoreExtensions
services.AddSingleton(x => interpreterSettings);
services.AddSingleton<DistributedLocker>();
// Register template render
services.AddSingleton<ITemplateRender, TemplateRender>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IRoleService, RoleService>();

View file

@ -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<ISettingService>();
var render = provider.GetRequiredService<ITemplateRender>();
render.Register(typeof(ConversationSetting));
return settingService.Bind<ConversationSetting>("Conversation");
});
@ -48,8 +50,6 @@ public class ConversationPlugin : IBotSharpPlugin
// Rich content messaging
services.AddScoped<IRichContentService, RichContentService>();
// Register template render
services.AddSingleton<ITemplateRender, TemplateRender>();
services.AddScoped<IResponseTemplateService, ResponseTemplateService>();
services.AddScoped<IExecutor, InstructExecutor>();

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.Core.Conversations.Services;
@ -21,13 +20,14 @@ public class ConversationStateService : IConversationStateService, IDisposable
/// </summary>
private ConversationState _historyStates;
public ConversationStateService(ILogger<ConversationStateService> logger,
public ConversationStateService(
IServiceProvider services,
IBotSharpRepository db)
IBotSharpRepository db,
ILogger<ConversationStateService> logger)
{
_logger = logger;
_services = services;
_db = db;
_logger = logger;
_curStates = new ConversationState();
_historyStates = new ConversationState();
}
@ -125,8 +125,13 @@ public class ConversationStateService : IConversationStateService, IDisposable
var curMsgId = routingCtx.MessageId;
_historyStates = _db.GetConversationStates(conversationId);
var endNodes = new Dictionary<string, string>();
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 +139,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<string, string>();
if (_historyStates.IsNullOrEmpty()) return endNodes;
foreach (var state in _historyStates)
{
var key = state.Key;

View file

@ -55,7 +55,7 @@ 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.All:
UpdateAgentAllFields(agent);
@ -151,13 +151,14 @@ namespace BotSharp.Core.Repository
File.WriteAllText(agentFile, json);
}
private void UpdateAgentUtilities(string agentId, List<string> utilities)
private void UpdateAgentUtilities(string agentId, bool mergeUtility, List<AgentUtility> 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);
@ -291,6 +292,7 @@ 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;

View file

@ -53,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();
@ -203,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));
}
}

View file

@ -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";
@ -83,6 +84,7 @@ public partial class FileRepository : IBotSharpRepository
private List<Role> _roles = new List<Role>();
private List<User> _users = new List<User>();
private List<Dashboard> _dashboards = [];
private List<Agent> _agents = new List<Agent>();
private List<RoleAgent> _roleAgents = new List<RoleAgent>();
private List<UserAgent> _userAgents = new List<UserAgent>();
@ -170,6 +172,36 @@ public partial class FileRepository : IBotSharpRepository
}
}
private IQueryable<Dashboard> 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<Dashboard>(json, _options);
if (dash == null) continue;
_dashboards.Add(dash);
}
}
return _dashboards.AsQueryable();
}
}
private IQueryable<Agent> Agents
{
get

View file

@ -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<string> Planers => new List<string>
{
nameof(HFPlanner)
nameof(HFReasoner)
};
public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger<ContinueExecuteTaskRoutingHandler> logger, RoutingSettings settings)

View file

@ -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<string> Planers => new List<string>
{
nameof(HFPlanner)
nameof(HFReasoner)
};
public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger<InterruptTaskExecutionRoutingHandler> logger, RoutingSettings settings)

View file

@ -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<string> Planers => new List<string>
{
nameof(HFPlanner)
nameof(HFReasoner)
};
public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger<RetrieveDataFromAgentRoutingHandler> logger, RoutingSettings settings)

View file

@ -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)}";
}
}

View file

@ -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];
}

View file

@ -1,4 +0,0 @@
public class SecondStagePlanParameter : FirstStagePlanParameter
{
}

View file

@ -1,17 +1,33 @@
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;
/// <summary>
/// Human feedback based planner
/// Human feedback based reasoner
/// </summary>
public class HFPlanner : IRoutingPlaner
public class HFReasoner : IRoutingReasoner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public HFPlanner(IServiceProvider services, ILogger<HFPlanner> logger)
public HFReasoner(IServiceProvider services, ILogger<HFReasoner> logger)
{
_services = services;
_logger = logger;
@ -37,7 +53,7 @@ public class HFPlanner : IRoutingPlaner
{
new RoleDialogModel(AgentRole.User, next)
{
FunctionName = nameof(HFPlanner),
FunctionName = nameof(HFReasoner),
MessageId = messageId
}
};
@ -60,7 +76,7 @@ public class HFPlanner : IRoutingPlaner
}
// Fix LLM malformed response
PlannerHelper.FixMalformedResponse(_services, inst);
ReasonerHelper.FixMalformedResponse(_services, inst);
return inst;
}
@ -87,13 +103,13 @@ public class HFPlanner : IRoutingPlaner
public async Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
{
var context = _services.GetRequiredService<IRoutingContext>();
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<ITemplateRender>();
// update states
var conv = _services.GetRequiredService<IConversationService>();

View file

@ -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
{

View file

@ -1,16 +1,35 @@
/*****************************************************************************
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
/// <summary>
/// simple or unsophisticated methods used to decide which specialized model or module in a system to engage for a given task.
/// </summary>
public class NaiveReasoner : IRoutingReasoner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public NaivePlanner(IServiceProvider services, ILogger<NaivePlanner> logger)
public NaiveReasoner(IServiceProvider services, ILogger<NaiveReasoner> logger)
{
_services = services;
_logger = logger;
@ -46,7 +65,7 @@ public class NaivePlanner : IRoutingPlaner
{
new RoleDialogModel(AgentRole.User, next)
{
FunctionName = nameof(NaivePlanner),
FunctionName = nameof(NaiveReasoner),
MessageId = messageId
}
};
@ -69,7 +88,7 @@ public class NaivePlanner : IRoutingPlaner
}
// Fix LLM malformed response
PlannerHelper.FixMalformedResponse(_services, inst);
ReasonerHelper.FixMalformedResponse(_services, inst);
return inst;
}
@ -99,7 +118,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 +126,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<IConversationStateService>();
var render = _services.GetRequiredService<ITemplateRender>();

View file

@ -0,0 +1,137 @@
/*****************************************************************************
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.Reasoning;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Routing.Reasoning;
/// <summary>
/// One-step forward reasoning is a straightforward reasoning approach where the model or agent evaluates its current state
/// and takes the next best logical step toward the solution without extensive lookahead or planning.
/// This type of reasoning involves making a decision based on the current situation and immediate context
/// rather than considering multiple future steps or possibilities.
/// </summary>
public class OneStepForwardReasoner : IRoutingReasoner
{
public string Name => "one-step-forward";
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public OneStepForwardReasoner(IServiceProvider services, ILogger<NaiveReasoner> logger)
{
_services = services;
_logger = logger;
}
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs)
{
var next = GetNextStepPrompt(router);
var inst = new FunctionCallFromLlm();
// chat completion
var completion = CompletionProvider.GetChatCompletion(_services,
provider: router?.LlmConfig?.Provider,
model: router?.LlmConfig?.Model);
int retryCount = 0;
while (retryCount < 3)
{
string text = string.Empty;
try
{
// text completion
// text = await completion.GetCompletion(content, router.Id, messageId);
dialogs = new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, next)
{
FunctionName = Name,
MessageId = messageId
}
};
var response = await completion.GetChatCompletions(router, dialogs);
inst = response.Content.JsonContent<FunctionCallFromLlm>();
break;
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {text}");
inst.Function = "response_to_user";
inst.Response = ex.Message;
inst.AgentName = "Router";
}
finally
{
retryCount++;
}
}
// Fix LLM malformed response
ReasonerHelper.FixMalformedResponse(_services, inst);
return inst;
}
public async Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
{
// Set user content as Planner's question
message.FunctionName = inst.Function;
message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments);
return true;
}
public async Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
{
var context = _services.GetRequiredService<IRoutingContext>();
if (inst.UnmatchedAgent)
{
var unmatchedAgentId = context.GetCurrentAgentId();
// Exclude the wrong routed agent
var agents = router.TemplateDict["routing_agents"] as RoutableAgent[];
router.TemplateDict["routing_agents"] = agents.Where(x => x.AgentId != unmatchedAgentId).ToArray();
// Handover to Router;
context.Pop();
}
else
{
context.Empty(reason: $"Agent queue is cleared by {nameof(NaiveReasoner)}");
// context.Push(inst.OriginalAgent, "Push user goal agent");
}
return true;
}
private string GetNextStepPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "reasoner.one-step-forward").Content;
var states = _services.GetRequiredService<IConversationStateService>();
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
{ StateConst.EXPECTED_ACTION_AGENT, states.GetState(StateConst.EXPECTED_ACTION_AGENT) },
{ StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) }
});
}
}

View file

@ -1,6 +1,6 @@
namespace BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Routing.Reasoning;
public static class PlannerHelper
public static class ReasonerHelper
{
/// <summary>
/// Sometimes LLM hallucinates and fails to set function names correctly.

View file

@ -1,11 +1,30 @@
/*****************************************************************************
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.MLTasks;
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 SequentialPlanner : IRoutingPlaner
/// <summary>
/// Sequential tasks focused reasoning approach
/// </summary>
public class SequentialReasoner : IRoutingReasoner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
@ -14,7 +33,7 @@ public class SequentialPlanner : IRoutingPlaner
public int MaxLoopCount => 100;
private FunctionCallFromLlm _lastInst;
public SequentialPlanner(IServiceProvider services, ILogger<NaivePlanner> logger)
public SequentialReasoner(IServiceProvider services, ILogger<NaiveReasoner> logger)
{
_services = services;
_logger = logger;
@ -72,7 +91,7 @@ public class SequentialPlanner : IRoutingPlaner
{
new RoleDialogModel(AgentRole.User, next)
{
FunctionName = nameof(SequentialPlanner),
FunctionName = nameof(SequentialReasoner),
MessageId = messageId
}
};
@ -139,7 +158,7 @@ public class SequentialPlanner : IRoutingPlaner
if (message.StopCompletion)
{
context.Empty(reason: $"Agent queue is cleared by {nameof(SequentialPlanner)}");
context.Empty(reason: $"Agent queue is cleared by {nameof(SequentialReasoner)}");
return false;
}
@ -154,7 +173,7 @@ public class SequentialPlanner : IRoutingPlaner
private string GetNextStepPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "planner_prompt.sequential").Content;
var template = router.Templates.First(x => x.Name == "reasoner.sequential").Content;
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
@ -169,11 +188,11 @@ public class SequentialPlanner : IRoutingPlaner
var inst = new DecomposedStep();
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
var model = llmProviderService.GetProviderModel("azure-openai", "gpt-4");
var model = llmProviderService.GetProviderModel("openai", "gpt-4o");
// chat completion
var completion = CompletionProvider.GetChatCompletion(_services,
provider: "azure-openai",
provider: "openai",
model: model.Name);
int retryCount = 0;
@ -185,7 +204,7 @@ public class SequentialPlanner : IRoutingPlaner
var response = await completion.GetChatCompletions(new Agent
{
Id = router.Id,
Name = nameof(SequentialPlanner),
Name = nameof(SequentialReasoner),
Instruction = systemPrompt
}, dialogs);
@ -208,16 +227,11 @@ public class SequentialPlanner : IRoutingPlaner
private string GetDecomposeTaskPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "planner_prompt.sequential.get_remaining_task").Content;
var template = router.Templates.First(x => x.Name == "reasoner.sequential.get_remaining_task").Content;
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
});
}
public Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId)
{
throw new NotImplementedException();
}
}

View file

@ -41,7 +41,8 @@ public class RoutingContext : IRoutingContext
var agentService = _services.GetRequiredService<IAgentService>();
_routerAgentIds = agentService.GetAgents(new AgentFilter
{
Type = AgentType.Routing
Type = AgentType.Routing,
Pager = new Pagination { Size = 100 }
}).Result.Items.Select(x => x.Id).ToArray();
}
@ -49,6 +50,17 @@ public class RoutingContext : IRoutingContext
}
}
/// <summary>
/// Entry agent
/// </summary>
public string EntryAgentId
{
get
{
return _stack.LastOrDefault() ?? string.Empty;
}
}
public bool IsEmpty => !_stack.Any();
public string GetCurrentAgentId()
@ -231,12 +243,16 @@ public class RoutingContext : IRoutingContext
public Stack<string> GetAgentStack()
{
return new Stack<string>(_stack);
var copy = _stack.ToList();
copy.Reverse();
return new Stack<string>(copy);
}
public void SetAgentStack(Stack<string> stack)
{
_stack = new Stack<string>(stack);
var copy = stack.ToList();
copy.Reverse();
_stack = new Stack<string>(copy);
}
public void ResetAgentStack()

View file

@ -1,10 +1,8 @@
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Routing.Reasoning;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Abstraction.Settings;
using BotSharp.Core.Routing.Hooks;
using BotSharp.Core.Routing.Planning;
using BotSharp.Core.Routing.Reasoning;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Routing;
@ -35,8 +33,10 @@ public class RoutingPlugin : IBotSharpPlugin
services.AddScoped<IRoutingService, RoutingService>();
services.AddScoped<IAgentHook, RoutingAgentHook>();
services.AddScoped<IRoutingPlaner, NaivePlanner>();
services.AddScoped<IRoutingPlaner, HFPlanner>();
services.AddScoped<IRoutingPlaner, SequentialPlanner>();
services.AddScoped<IRoutingReasoner, NaiveReasoner>();
services.AddScoped<IRoutingReasoner, HFReasoner>();
services.AddScoped<IRoutingReasoner, SequentialReasoner>();
services.AddScoped<IRoutingReasoner, OneStepForwardReasoner>();
}
}

View file

@ -1,24 +0,0 @@
using BotSharp.Abstraction.Routing.Enums;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Routing;
public partial class RoutingService
{
public IRoutingPlaner GetPlanner(Agent router)
{
var rule = router.RoutingRules.FirstOrDefault(x => x.Type == RuleType.Planner);
var planner = _services.GetServices<IRoutingPlaner>().
FirstOrDefault(x => x.GetType().Name.EndsWith(rule.Field));
if (planner == null)
{
_logger.LogError($"Can't find specific planner named {rule.Field}");
return _services.GetRequiredService<NaivePlanner>();
}
return planner;
}
}

View file

@ -1,6 +1,5 @@
using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Routing.Models;
using System.Drawing;
namespace BotSharp.Core.Routing;

View file

@ -0,0 +1,112 @@
using BotSharp.Abstraction.Infrastructures.Enums;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing.Enums;
using BotSharp.Abstraction.Routing.Reasoning;
using BotSharp.Core.Routing.Reasoning;
namespace BotSharp.Core.Routing;
public partial class RoutingService
{
public async Task<RoleDialogModel> InstructLoop(RoleDialogModel message, List<RoleDialogModel> dialogs)
{
RoleDialogModel response = default;
var agentService = _services.GetRequiredService<IAgentService>();
var convService = _services.GetRequiredService<IConversationService>();
var storage = _services.GetRequiredService<IConversationStorage>();
_router = await agentService.LoadAgent(message.CurrentAgentId);
var states = _services.GetRequiredService<IConversationStateService>();
var executor = _services.GetRequiredService<IExecutor>();
var planner = GetReasoner(_router);
_context.Push(_router.Id);
// Handle multi-language for input
var agentSettings = _services.GetRequiredService<AgentSettings>();
if (agentSettings.EnableTranslator)
{
var translator = _services.GetRequiredService<ITranslationService>();
var language = states.GetState(StateConst.LANGUAGE, LanguageType.ENGLISH);
if (language != LanguageType.ENGLISH)
{
message.SecondaryContent = message.Content;
message.Content = await translator.Translate(_router, message.MessageId, message.Content,
language: LanguageType.ENGLISH,
clone: false);
}
}
dialogs.Add(message);
storage.Append(convService.ConversationId, message);
// Get first instruction
_router.TemplateDict["conversation"] = await GetConversationContent(dialogs);
var inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs);
int loopCount = 1;
while (true)
{
await HookEmitter.Emit<IRoutingHook>(_services, async hook =>
await hook.OnRoutingInstructionReceived(inst, message)
);
// Save states
states.SaveStateByArgs(inst.Arguments);
#if DEBUG
Console.WriteLine($"*** Next Instruction *** {inst}");
#else
_logger.LogInformation($"*** Next Instruction *** {inst}");
#endif
await planner.AgentExecuting(_router, inst, message, dialogs);
// Handover to Task Agent
if (inst.HandleDialogsByPlanner)
{
var dialogWithoutContext = planner.BeforeHandleContext(inst, message, dialogs);
response = await executor.Execute(this, inst, message, dialogWithoutContext);
planner.AfterHandleContext(dialogs, dialogWithoutContext);
}
else
{
response = await executor.Execute(this, inst, message, dialogs);
}
await planner.AgentExecuted(_router, inst, response, dialogs);
if (loopCount >= planner.MaxLoopCount || _context.IsEmpty)
{
break;
}
// Get next instruction from Planner
_router.TemplateDict["conversation"] = await GetConversationContent(dialogs);
inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs);
loopCount++;
}
return response;
}
public IRoutingReasoner GetReasoner(Agent router)
{
var rule = router.RoutingRules.FirstOrDefault(x => x.Type == RuleType.Reasoner);
var reasoner = _services.GetServices<IRoutingReasoner>().
FirstOrDefault(x => x.GetType().Name.EndsWith(rule.Field));
if (reasoner == null)
{
_logger.LogError($"Can't find specific planner named {rule.Field}");
// Default use NaiveReasoner
return _services.GetRequiredService<NaiveReasoner>();
}
return reasoner;
}
}

View file

@ -4,13 +4,11 @@ namespace BotSharp.Core.Routing;
public partial class RoutingService
{
//private int _currentRecursionDepth = 0;
public async Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs)
{
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(agentId);
//_currentRecursionDepth++;
Context.IncreaseRecursiveCounter();
if (Context.CurrentRecursionDepth > agent.LlmConfig.MaxRecursionDepth)
{

View file

@ -1,6 +1,4 @@
using BotSharp.Abstraction.Infrastructures.Enums;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Routing.Settings;
namespace BotSharp.Core.Routing;
@ -16,21 +14,6 @@ public partial class RoutingService : IRoutingService
public IRoutingContext Context => _context;
public Agent Router => _router;
//public int GetRecursiveCounter()
//{
// return _currentRecursionDepth;
//}
//public void SetRecursiveCounter(int counter)
//{
// _currentRecursionDepth = counter;
//}
//public void ResetRecursiveCounter()
//{
// _currentRecursionDepth = 0;
//}
public RoutingService(
IServiceProvider services,
RoutingSettings settings,
@ -75,97 +58,12 @@ public partial class RoutingService : IRoutingService
return response;
}
public async Task<RoleDialogModel> InstructLoop(RoleDialogModel message, List<RoleDialogModel> dialogs)
{
RoleDialogModel response = default;
var agentService = _services.GetRequiredService<IAgentService>();
var convService = _services.GetRequiredService<IConversationService>();
var storage = _services.GetRequiredService<IConversationStorage>();
_router = await agentService.LoadAgent(message.CurrentAgentId);
var states = _services.GetRequiredService<IConversationStateService>();
var executor = _services.GetRequiredService<IExecutor>();
var planner = GetPlanner(_router);
_context.Push(_router.Id);
// Handle multi-language for input
var agentSettings = _services.GetRequiredService<AgentSettings>();
if (agentSettings.EnableTranslator)
{
var translator = _services.GetRequiredService<ITranslationService>();
var language = states.GetState(StateConst.LANGUAGE, LanguageType.ENGLISH);
if (language != LanguageType.ENGLISH)
{
message.SecondaryContent = message.Content;
message.Content = await translator.Translate(_router, message.MessageId, message.Content,
language: LanguageType.ENGLISH,
clone: false);
}
}
dialogs.Add(message);
storage.Append(convService.ConversationId, message);
// Get first instruction
_router.TemplateDict["conversation"] = await GetConversationContent(dialogs);
var inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs);
int loopCount = 1;
while (true)
{
await HookEmitter.Emit<IRoutingHook>(_services, async hook =>
await hook.OnRoutingInstructionReceived(inst, message)
);
// Save states
states.SaveStateByArgs(inst.Arguments);
#if DEBUG
Console.WriteLine($"*** Next Instruction *** {inst}");
#else
_logger.LogInformation($"*** Next Instruction *** {inst}");
#endif
await planner.AgentExecuting(_router, inst, message, dialogs);
// Handover to Task Agent
if (inst.HandleDialogsByPlanner)
{
var dialogWithoutContext = planner.BeforeHandleContext(inst, message, dialogs);
response = await executor.Execute(this, inst, message, dialogWithoutContext);
planner.AfterHandleContext(dialogs, dialogWithoutContext);
}
else
{
response = await executor.Execute(this, inst, message, dialogs);
}
await planner.AgentExecuted(_router, inst, response, dialogs);
if (loopCount >= planner.MaxLoopCount || _context.IsEmpty)
{
break;
}
// Get next instruction from Planner
_router.TemplateDict["conversation"] = await GetConversationContent(dialogs);
inst = await planner.GetNextInstruction(_router, message.MessageId, dialogs);
loopCount++;
}
return response;
}
public List<RoutingHandlerDef> GetHandlers(Agent router)
{
var planer = GetPlanner(router);
var reasoner = GetReasoner(router);
return _services.GetServices<IRoutingHandler>()
.Where(x => x.Planers == null || x.Planers.Contains(planer.GetType().Name))
.Where(x => x.Planers == null || x.Planers.Contains(reasoner.GetType().Name))
.Where(x => !string.IsNullOrEmpty(x.Description))
.Select((x, i) => new RoutingHandlerDef
{

View file

@ -1,10 +1,10 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Templating;
using BotSharp.Abstraction.Translation.Models;
using Fluid;
using System.Collections;
using System.Reflection;
namespace BotSharp.Core.Templating;
@ -48,4 +48,47 @@ public class TemplateRender : ITemplateRender
return template;
}
}
public void Register(Type type)
{
if (type == null || IsStringType(type)) return;
if (IsListType(type))
{
if (type.IsGenericType)
{
var genericType = type.GetGenericArguments()[0];
Register(genericType);
}
}
else if (IsTrackToNextLevel(type))
{
_options.MemberAccessStrategy.Register(type);
var props = type.GetProperties();
foreach (var prop in props)
{
Register(prop.PropertyType);
}
}
}
#region Private methods
private static bool IsStringType(Type type)
{
return type == typeof(string);
}
private static bool IsListType(Type type)
{
var interfaces = type.GetTypeInfo().ImplementedInterfaces;
return type.IsArray || interfaces.Any(x => x.Name == typeof(IEnumerable).Name);
}
private static bool IsTrackToNextLevel(Type type)
{
return type.IsClass || type.IsInterface || type.IsAbstract;
}
#endregion
}

View file

@ -736,4 +736,42 @@ public class UserService : IUserService
}
return true;
}
public async Task<bool> AddDashboardConversation(string userId, string conversationId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
db.AddDashboardConversation(userId, conversationId);
await Task.CompletedTask;
return true;
}
public async Task<bool> RemoveDashboardConversation(string userId, string conversationId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
db.RemoveDashboardConversation(userId, conversationId);
await Task.CompletedTask;
return true;
}
public async Task UpdateDashboardConversation(string userId, DashboardConversation newDashConv)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var dashConv = db.GetDashboard(userId)?.ConversationList.FirstOrDefault(x => string.Equals(x.ConversationId, newDashConv.ConversationId));
if (dashConv == null) return;
dashConv.Name = newDashConv.Name ?? dashConv.Name;
dashConv.Instruction = newDashConv.Instruction ?? dashConv.Instruction;
db.UpdateDashboardConversation(userId, dashConv);
await Task.CompletedTask;
return;
}
public async Task<Dashboard?> GetDashboard(string userId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var dash = db.GetDashboard();
await Task.CompletedTask;
return dash;
}
}

View file

@ -11,8 +11,8 @@
"profiles": [ "tool" ],
"routingRules": [
{
"type": "planner",
"field": "HFPlanner"
"type": "reasoner",
"field": "HFReasoner"
}
]
}

View file

@ -0,0 +1,2 @@
Analyze the user's problem. Which prerequisite task needs to be completed? Output the next step of routing instructions.
Check the job responsibilities of the routable Agent and do not transfer to an Agent that exceeds the scope of responsibility.

View file

@ -1,4 +1,4 @@
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Agents.Models;
namespace BotSharp.OpenAPI.Controllers;
@ -59,11 +59,7 @@ public class AgentController : ControllerBase
var userService = _services.GetRequiredService<IUserService>();
var auth = await userService.GetUserAuthorizations(new List<string> { targetAgent.Id });
targetAgent.Editable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Edit);
targetAgent.Chatable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Chat);
targetAgent.Trainable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Train);
targetAgent.Evaluable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Evaluate);
targetAgent.Actions = auth.GetAllowedAgentActions(targetAgent.Id);
return targetAgent;
}
@ -90,10 +86,7 @@ public class AgentController : ControllerBase
agents = pagedAgents?.Items?.Select(x =>
{
var model = AgentViewModel.FromAgent(x);
model.Editable = auth.IsAgentActionAllowed(x.Id, UserAction.Edit);
model.Chatable = auth.IsAgentActionAllowed(x.Id, UserAction.Chat);
model.Trainable = auth.IsAgentActionAllowed(x.Id, UserAction.Train);
model.Evaluable = auth.IsAgentActionAllowed(x.Id, UserAction.Evaluate);
model.Actions = auth.GetAllowedAgentActions(x.Id);
return model;
})?.ToList() ?? [];
@ -153,9 +146,9 @@ public class AgentController : ControllerBase
return await _agentService.DeleteAgent(agentId);
}
[HttpGet("/agent/utilities")]
public IEnumerable<string> GetAgentUtilities()
[HttpGet("/agent/utility/options")]
public IEnumerable<AgentUtility> GetAgentUtilityOptions()
{
return _agentService.GetAgentUtilities();
return _agentService.GetAgentUtilityOptions();
}
}

View file

@ -511,6 +511,28 @@ public class ConversationController : ControllerBase
}
#endregion
#region miscellaneous
[HttpPut("/agent/{agentId}/conversation/{conversationId}/dashboard")]
public async Task<bool> PinConversationToDashboard([FromRoute] string agentId, [FromRoute] string conversationId)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
var pinned = await userService.AddDashboardConversation(user.Id, conversationId);
return pinned;
}
[HttpDelete("/agent/{agentId}/conversation/{conversationId}/dashboard")]
public async Task<bool> UnpinConversationFromDashboard([FromRoute] string agentId, [FromRoute] string conversationId)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
var unpinned = await userService.RemoveDashboardConversation(user.Id, conversationId);
return unpinned;
}
#endregion
#region Private methods
private void SetStates(IConversationService conv, NewMessageModel input)
{

View file

@ -0,0 +1,73 @@
using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Users.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.OpenAPI.Controllers;
[Authorize]
[ApiController]
public class DashboardController : ControllerBase
{
private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
public DashboardController(IServiceProvider services,
IUserIdentity user,
BotSharpOptions options)
{
_services = services;
_user = user;
}
#region User Components
[HttpGet("/dashboard/components")]
public async Task<UserDashboardModel> GetComponents(string userId)
{
var userService = _services.GetRequiredService<IUserService>();
var dashboardProfile = await userService.GetDashboard(userId);
if (dashboardProfile == null) return new UserDashboardModel();
var result = new UserDashboardModel
{
ConversationList = dashboardProfile.ConversationList.Select(
x => new UserDashboardConversationModel
{
Name = x.Name,
ConversationId = x.ConversationId,
Instruction = x.Instruction
}
).ToList()
};
return result;
}
[HttpPost("/dashboard/component/conversation")]
public async Task UpdateDashboardConversationInstruction(string userId, UserDashboardConversationModel dashConv)
{
if (string.IsNullOrEmpty(dashConv.Name) && string.IsNullOrEmpty(dashConv.Instruction))
{
return;
}
var newDashConv = new DashboardConversation
{
Id = Guid.Empty.ToString(),
ConversationId = dashConv.ConversationId
};
if (!string.IsNullOrEmpty(dashConv.Name))
{
newDashConv.Name = dashConv.Name;
}
if (!string.IsNullOrEmpty(dashConv.Instruction))
{
newDashConv.Instruction = dashConv.Instruction;
}
var userService = _services.GetRequiredService<IUserService>();
await userService.UpdateDashboardConversation(userId, newDashConv);
return;
}
#endregion
}

View file

@ -48,7 +48,10 @@ public class AgentCreationModel
/// Combine different Agents together to form a Profile.
/// </summary>
public List<string> Profiles { get; set; } = new();
public List<string> Utilities { get; set; } = new();
public bool MergeUtility { get; set; }
public List<AgentUtility> Utilities { get; set; } = new();
public List<RoutingRuleUpdateModel> RoutingRules { get; set; } = new();
public AgentLlmConfig? LlmConfig { get; set; }
@ -68,6 +71,7 @@ public class AgentCreationModel
IsPublic = IsPublic,
Type = Type,
Disabled = Disabled,
MergeUtility = MergeUtility,
Profiles = Profiles,
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
LlmConfig = LlmConfig

View file

@ -31,10 +31,13 @@ public class AgentUpdateModel
/// </summary>
public List<string>? Samples { get; set; }
[JsonPropertyName("merge_utility")]
public bool MergeUtility { get; set; }
/// <summary>
/// Utilities
/// </summary>
public List<string>? Utilities { get; set; }
public List<AgentUtility>? Utilities { get; set; }
/// <summary>
/// Functions
@ -73,6 +76,7 @@ public class AgentUpdateModel
Description = Description ?? string.Empty,
IsPublic = IsPublic,
Disabled = Disabled,
MergeUtility = MergeUtility,
Type = Type,
Profiles = Profiles ?? new List<string>(),
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
@ -81,7 +85,7 @@ public class AgentUpdateModel
Templates = Templates ?? new List<AgentTemplate>(),
Functions = Functions ?? new List<FunctionDef>(),
Responses = Responses ?? new List<AgentResponse>(),
Utilities = Utilities ?? new List<string>(),
Utilities = Utilities ?? new List<AgentUtility>(),
LlmConfig = LlmConfig
};

View file

@ -20,7 +20,10 @@ public class AgentViewModel
public List<FunctionDef> Functions { get; set; }
public List<AgentResponse> Responses { get; set; }
public List<string> Samples { get; set; }
public List<string> Utilities { get; set; }
[JsonPropertyName("merge_utility")]
public bool MergeUtility { get; set; }
public List<AgentUtility> Utilities { get; set; }
[JsonPropertyName("is_public")]
public bool IsPublic { get; set; }
@ -33,8 +36,7 @@ public class AgentViewModel
[JsonPropertyName("icon_url")]
public string IconUrl { get; set; }
public List<string> Profiles { get; set; }
= new List<string>();
public List<string> Profiles { get; set; } = new();
[JsonPropertyName("routing_rules")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
@ -46,10 +48,7 @@ public class AgentViewModel
public PluginDef Plugin { get; set; }
public bool Editable { get; set; }
public bool Chatable { get; set; }
public bool Trainable { get; set; }
public bool Evaluable { get; set; }
public IEnumerable<string>? Actions { get; set; }
[JsonPropertyName("created_datetime")]
public DateTime CreatedDateTime { get; set; }
@ -74,6 +73,7 @@ public class AgentViewModel
Utilities = agent.Utilities,
IsPublic= agent.IsPublic,
Disabled = agent.Disabled,
MergeUtility = agent.MergeUtility,
IconUrl = agent.IconUrl,
Profiles = agent.Profiles ?? new List<string>(),
RoutingRules = agent.RoutingRules,

View file

@ -12,6 +12,7 @@ public class ConversationViewModel
[JsonPropertyName("agent_name")]
public string AgentName { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
public UserViewModel User { get; set; } = new UserViewModel();

View file

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace BotSharp.OpenAPI.ViewModels.Users;
public class UserDashboardModel
{
[JsonPropertyName("conversation_list")]
public IList<UserDashboardConversationModel> ConversationList { get; set; } = [];
}
public class UserDashboardConversationModel
{
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("conversation_id")]
public string? ConversationId { get; set; }
[JsonPropertyName("instruction")]
public string? Instruction { get; set; }
}

View file

@ -17,7 +17,6 @@ public class AudioHandlerPlugin : IBotSharpPlugin
});
services.AddScoped<IAudioCompletion, NativeWhisperProvider>();
services.AddScoped<IAgentHook, AudioHandlerHook>();
services.AddScoped<IAgentUtilityHook, AudioHandlerUtilityHook>();
}
}

View file

@ -1,60 +0,0 @@
using BotSharp.Abstraction.Agents.Settings;
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Plugin.AudioHandler.Hooks;
public class AudioHandlerHook : AgentHookBase, IAgentHook
{
private const string HANDLER_AUDIO = "handle_audio_request";
public override string SelfId => string.Empty;
public AudioHandlerHook(IServiceProvider services, AgentSettings settings) : base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.AudioHandler);
if (isEnabled && isConvMode)
{
AddUtility(agent, HANDLER_AUDIO);
}
base.OnAgentLoaded(agent);
}
private void AddUtility(Agent agent, string functionName)
{
var (prompt, fn) = GetPromptAndFunction(functionName);
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
private (string, FunctionDef?) GetPromptAndFunction(string functionName)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{functionName}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(functionName));
return (prompt, loadAttachmentFn);
}
}

View file

@ -2,8 +2,17 @@ namespace BotSharp.Plugin.AudioHandler.Hooks;
public class AudioHandlerUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
private const string HANDLER_AUDIO = "handle_audio_request";
public void AddUtilities(List<AgentUtility> utilities)
{
utilities.Add(UtilityName.AudioHandler);
var utility = new AgentUtility
{
Name = UtilityName.AudioHandler,
Functions = [new(HANDLER_AUDIO)],
Templates = [new($"{HANDLER_AUDIO}.fn")]
};
utilities.Add(utility);
}
}

View file

@ -8,7 +8,7 @@
"updatedDateTime": "2024-11-23T00:00:00Z",
"disabled": false,
"isPublic": true,
"profiles": [ "database" ],
"profiles": [ "coding" ],
"llmConfig": {
"provider": "openai",
"model": "gpt-4o",

View file

@ -20,8 +20,6 @@ namespace BotSharp.Plugin.EmailHandler
return settingService.Bind<EmailSenderSettings>("EmailSender");
});
services.AddScoped<IAgentHook, EmailSenderHook>();
services.AddScoped<IAgentHook, EmailReaderHook>();
services.AddScoped<IAgentUtilityHook, EmailHandlerUtilityHook>();
var emailReaderSettings = new EmailReaderSettings();

View file

@ -1,18 +1,22 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Plugin.EmailHandler.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.Plugin.EmailHandler.Hooks
namespace BotSharp.Plugin.EmailHandler.Hooks;
public class EmailHandlerUtilityHook : IAgentUtilityHook
{
public class EmailHandlerUtilityHook : IAgentUtilityHook
private static string EMAIL_READER_FN = "handle_email_reader";
private static string EMAIL_SENDER_FN = "handle_email_sender";
public void AddUtilities(List<AgentUtility> utilities)
{
public void AddUtilities(List<string> utilities)
var utility = new AgentUtility
{
utilities.Add(UtilityName.EmailHandler);
}
Name = UtilityName.EmailHandler,
Functions = [new(EMAIL_READER_FN), new(EMAIL_SENDER_FN)],
Templates = [new($"{EMAIL_READER_FN}.fn"), new($"{EMAIL_SENDER_FN}.fn")]
};
utilities.Add(utility);
}
}

View file

@ -1,56 +0,0 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Settings;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Plugin.EmailHandler.Enums;
namespace BotSharp.Plugin.EmailHandler.Hooks;
public class EmailReaderHook : AgentHookBase
{
private static string FUNCTION_NAME = "handle_email_reader";
public override string SelfId => string.Empty;
public EmailReaderHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.EmailHandler);
if (isConvMode && isEnabled)
{
var (prompt, fn) = GetPromptAndFunction();
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
base.OnAgentLoaded(agent);
}
private (string, FunctionDef?) GetPromptAndFunction()
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME));
return (prompt, loadAttachmentFn);
}
}

View file

@ -1,56 +0,0 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Settings;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Plugin.EmailHandler.Enums;
namespace BotSharp.Plugin.EmailHandler.Hooks;
public class EmailSenderHook : AgentHookBase
{
private static string FUNCTION_NAME = "handle_email_sender";
public override string SelfId => string.Empty;
public EmailSenderHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.EmailHandler);
if (isConvMode && isEnabled)
{
var (prompt, fn) = GetPromptAndFunction();
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
base.OnAgentLoaded(agent);
}
private (string, FunctionDef?) GetPromptAndFunction()
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME));
return (prompt, loadAttachmentFn);
}
}

View file

@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BotSharp.Abstraction.Plugins;
using BotSharp.Abstraction.Settings;
using BotSharp.Plugin.ExcelHandler.Helpers.MySql;
@ -30,7 +25,6 @@ public class ExcelHandlerPlugin : IBotSharpPlugin
});
services.AddScoped<IAgentUtilityHook, ExcelHandlerUtilityHook>();
services.AddScoped<IAgentHook, ExcelHandlerHook>();
services.AddScoped<ISqliteDbHelpers, SqliteDbHelpers>();
services.AddScoped<IMySqlDbHelper, MySqlDbHelpers>();
services.AddScoped<ISqliteService, SqliteService>();

View file

@ -1,12 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BotSharp.Plugin.SqlHero.Settings;
using Microsoft.Data.Sqlite;
using BotSharp.Plugin.SqlDriver.Settings;
using MySql.Data.MySqlClient;
namespace BotSharp.Plugin.ExcelHandler.Helpers.MySql

View file

@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BotSharp.Plugin.SqlDriver.Settings;
using Microsoft.Data.Sqlite;
using BotSharp.Plugin.SqlDriver.Models;
using BotSharp.Plugin.SqlHero.Settings;
namespace BotSharp.Plugin.ExcelHandler.Helpers.Sqlite;

View file

@ -1,58 +0,0 @@
namespace BotSharp.Plugin.ExcelHandler.Hooks;
public class ExcelHandlerHook : AgentHookBase, IAgentHook
{
private const string HANDLER_EXCEL = "handle_excel_request";
public override string SelfId => string.Empty;
public ExcelHandlerHook(IServiceProvider services, AgentSettings settings) : base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.ExcelHandler);
if (isEnabled && isConvMode)
{
AddUtility(agent, HANDLER_EXCEL);
}
base.OnAgentLoaded(agent);
}
private void AddUtility(Agent agent, string functionName)
{
var (prompt, fn) = GetPromptAndFunction(functionName);
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
private (string, FunctionDef?) GetPromptAndFunction(string functionName)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{functionName}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(functionName));
return (prompt, loadAttachmentFn);
}
}

View file

@ -2,8 +2,17 @@ namespace BotSharp.Plugin.ExcelHandler.Hooks;
public class ExcelHandlerUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
private const string HANDLER_EXCEL = "handle_excel_request";
public void AddUtilities(List<AgentUtility> utilities)
{
utilities.Add(UtilityName.ExcelHandler);
var utility = new AgentUtility
{
Name = UtilityName.ExcelHandler,
Functions = [new(HANDLER_EXCEL)],
Templates = [new($"{HANDLER_EXCEL}.fn")]
};
utilities.Add(utility);
}
}

View file

@ -19,7 +19,6 @@ public class FileHandlerPlugin : IBotSharpPlugin
return settingService.Bind<FileHandlerSettings>("FileHandler");
});
services.AddScoped<IAgentHook, FileHandlerHook>();
services.AddScoped<IAgentUtilityHook, FileHandlerUtilityHook>();
}

View file

@ -1,69 +0,0 @@
namespace BotSharp.Plugin.FileHandler.Hooks;
public class FileHandlerHook : AgentHookBase, IAgentHook
{
private const string READ_IMAGE_FN = "read_image";
private const string READ_PDF_FN = "read_pdf";
private const string GENERATE_IMAGE_FN = "generate_image";
private const string EDIT_IMAGE_FN = "edit_image";
public override string SelfId => string.Empty;
public FileHandlerHook(IServiceProvider services, AgentSettings settings) : base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
if (isConvMode)
{
AddUtility(agent, UtilityName.ImageGenerator, GENERATE_IMAGE_FN);
AddUtility(agent, UtilityName.ImageReader, READ_IMAGE_FN);
AddUtility(agent, UtilityName.ImageEditor, EDIT_IMAGE_FN);
AddUtility(agent, UtilityName.PdfReader, READ_PDF_FN);
}
base.OnAgentLoaded(agent);
}
private void AddUtility(Agent agent, string utility, string functionName)
{
if (!IsEnableUtility(agent, utility)) return;
var (prompt, fn) = GetPromptAndFunction(functionName);
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
private bool IsEnableUtility(Agent agent, string utility)
{
return !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(utility);
}
private (string, FunctionDef?) GetPromptAndFunction(string functionName)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{functionName}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(functionName));
return (prompt, loadAttachmentFn);
}
}

View file

@ -2,11 +2,41 @@ namespace BotSharp.Plugin.FileHandler.Hooks;
public class FileHandlerUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
private const string READ_IMAGE_FN = "read_image";
private const string READ_PDF_FN = "read_pdf";
private const string GENERATE_IMAGE_FN = "generate_image";
private const string EDIT_IMAGE_FN = "edit_image";
public void AddUtilities(List<AgentUtility> utilities)
{
utilities.Add(UtilityName.ImageGenerator);
utilities.Add(UtilityName.ImageReader);
utilities.Add(UtilityName.ImageEditor);
utilities.Add(UtilityName.PdfReader);
var items = new List<AgentUtility>
{
new AgentUtility
{
Name = UtilityName.ImageGenerator,
Functions = [new(GENERATE_IMAGE_FN)],
Templates = [new($"{GENERATE_IMAGE_FN}.fn")]
},
new AgentUtility
{
Name = UtilityName.ImageReader,
Functions = [new(READ_IMAGE_FN)],
Templates = [new($"{READ_IMAGE_FN}.fn")]
},
new AgentUtility
{
Name = UtilityName.ImageEditor,
Functions = [new(EDIT_IMAGE_FN)],
Templates = [new($"{EDIT_IMAGE_FN}.fn")]
},
new AgentUtility
{
Name = UtilityName.PdfReader,
Functions = [new(READ_PDF_FN)],
Templates = [new($"{READ_PDF_FN}.fn")]
}
};
utilities.AddRange(items);
}
}

View file

@ -1,58 +0,0 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Agents.Settings;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories;
namespace BotSharp.Plugin.HttpHandler.Hooks;
public class HttpHandlerHook : AgentHookBase
{
private static string FUNCTION_NAME = "handle_http_request";
public override string SelfId => string.Empty;
public HttpHandlerHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.HttpHandler);
if (isConvMode && isEnabled)
{
var (prompt, fn) = GetPromptAndFunction(FUNCTION_NAME);
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
base.OnAgentLoaded(agent);
}
private (string, FunctionDef?) GetPromptAndFunction(string functionName)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{functionName}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(functionName));
return (prompt, loadAttachmentFn);
}
}

View file

@ -4,8 +4,17 @@ namespace BotSharp.Plugin.HttpHandler.Hooks;
public class HttpHandlerUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
private static string HTTP_HANDLER_FN = "handle_http_request";
public void AddUtilities(List<AgentUtility> utilities)
{
utilities.Add(UtilityName.HttpHandler);
var utility = new AgentUtility
{
Name = UtilityName.HttpHandler,
Functions = [new(HTTP_HANDLER_FN)],
Templates = [new($"{HTTP_HANDLER_FN}.fn")]
};
utilities.Add(utility);
}
}

View file

@ -20,7 +20,6 @@ public class HttpHandlerPlugin : IBotSharpPlugin
return settingService.Bind<HttpHandlerSettings>("HttpHandler");
});
services.AddScoped<IAgentHook, HttpHandlerHook>();
services.AddScoped<IAgentUtilityHook, HttpHandlerUtilityHook>();
}
}

View file

@ -1,60 +0,0 @@
namespace BotSharp.Plugin.KnowledgeBase.Hooks;
public class KnowledgeBaseAgentHook : AgentHookBase, IAgentHook
{
public override string SelfId => string.Empty;
public KnowledgeBaseAgentHook(IServiceProvider services, AgentSettings settings) : base(services, settings)
{
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
if (isConvMode)
{
AddUtility(agent, UtilityName.KnowledgeRetrieval, "knowledge_retrieval");
}
base.OnAgentLoaded(agent);
}
private void AddUtility(Agent agent, string utility, string functionName)
{
if (!IsEnableUtility(agent, utility)) return;
var (prompt, fn) = GetPromptAndFunction(functionName);
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
private bool IsEnableUtility(Agent agent, string utility)
{
return !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(utility);
}
private (string, FunctionDef?) GetPromptAndFunction(string functionName)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{functionName}.fn"))?.Content ?? string.Empty;
var fn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(functionName));
return (prompt, fn);
}
}

View file

@ -2,8 +2,17 @@ namespace BotSharp.Plugin.KnowledgeBase.Hooks;
public class KnowledgeBaseUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
private const string KNOWLEDGE_RETRIEVAL_FN = "knowledge_retrieval";
public void AddUtilities(List<AgentUtility> utilities)
{
utilities.Add(UtilityName.KnowledgeRetrieval);
var utility = new AgentUtility
{
Name = UtilityName.KnowledgeRetrieval,
Functions = [new(KNOWLEDGE_RETRIEVAL_FN)],
Templates = [new($"{KNOWLEDGE_RETRIEVAL_FN}.fn")]
};
utilities.Add(utility);
}
}

View file

@ -25,7 +25,6 @@ public class KnowledgeBasePlugin : IBotSharpPlugin
services.AddSingleton<IPdf2TextConverter, PigPdf2TextConverter>();
services.AddScoped<IAgentUtilityHook, KnowledgeBaseUtilityHook>();
services.AddScoped<IAgentHook, KnowledgeBaseAgentHook>();
services.AddScoped<IKnowledgeService, KnowledgeService>();
}

View file

@ -8,14 +8,15 @@ public class AgentDocument : MongoBase
public string? InheritAgentId { get; set; }
public string? IconUrl { get; set; }
public string Instruction { get; set; }
public bool IsPublic { get; set; }
public bool Disabled { get; set; }
public bool MergeUtility { get; set; }
public List<ChannelInstructionMongoElement> ChannelInstructions { get; set; }
public List<AgentTemplateMongoElement> Templates { get; set; }
public List<FunctionDefMongoElement> Functions { get; set; }
public List<AgentResponseMongoElement> Responses { get; set; }
public List<string> Samples { get; set; }
public List<string> Utilities { get; set; }
public bool IsPublic { get; set; }
public bool Disabled { get; set; }
public List<AgentUtilityMongoElement> Utilities { get; set; }
public List<string> Profiles { get; set; }
public List<RoutingRuleMongoElement> RoutingRules { get; set; }
public AgentLlmConfigMongoElement? LlmConfig { get; set; }

View file

@ -26,6 +26,8 @@ public class UserDocument : MongoBase
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public Dashboard? Dashboard { get; set; }
public User ToUser()
{
return new User

View file

@ -0,0 +1,63 @@
using BotSharp.Abstraction.Agents.Models;
namespace BotSharp.Plugin.MongoStorage.Models;
public class AgentUtilityMongoElement
{
public string Name { get; set; }
public bool Disabled { get; set; }
public List<UtilityFunctionMongoElement> Functions { get; set; } = [];
public List<UtilityTemplateMongoElement> Templates { get; set; } = [];
public static AgentUtilityMongoElement ToMongoElement(AgentUtility utility)
{
return new AgentUtilityMongoElement
{
Name = utility.Name,
Disabled = utility.Disabled,
Functions = utility.Functions?.Select(x => new UtilityFunctionMongoElement(x.Name))?.ToList() ?? [],
Templates = utility.Templates?.Select(x => new UtilityTemplateMongoElement(x.Name))?.ToList() ?? []
};
}
public static AgentUtility ToDomainElement(AgentUtilityMongoElement utility)
{
return new AgentUtility
{
Name = utility.Name,
Disabled = utility.Disabled,
Functions = utility.Functions?.Select(x => new UtilityFunction(x.Name))?.ToList() ?? [],
Templates = utility.Templates?.Select(x => new UtilityTemplate(x.Name))?.ToList() ?? []
};
}
}
public class UtilityFunctionMongoElement
{
public string Name { get; set; }
public UtilityFunctionMongoElement()
{
}
public UtilityFunctionMongoElement(string name)
{
Name = name;
}
}
public class UtilityTemplateMongoElement
{
public string Name { get; set; }
public UtilityTemplateMongoElement()
{
}
public UtilityTemplateMongoElement(string name)
{
Name = name;
}
}

View file

@ -2,7 +2,6 @@ using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
using MongoDB.Driver;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -57,7 +56,7 @@ public partial class MongoRepository
UpdateAgentLlmConfig(agent.Id, agent.LlmConfig);
break;
case AgentField.Utility:
UpdateAgentUtilities(agent.Id, agent.Utilities);
UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities);
break;
case AgentField.All:
UpdateAgentAllFields(agent);
@ -224,13 +223,16 @@ public partial class MongoRepository
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentUtilities(string agentId, List<string> utilities)
private void UpdateAgentUtilities(string agentId, bool mergeUtility, List<AgentUtility> utilities)
{
if (utilities == null) return;
var elements = utilities?.Select(x => AgentUtilityMongoElement.ToMongoElement(x))?.ToList() ?? [];
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var update = Builders<AgentDocument>.Update
.Set(x => x.Utilities, utilities)
.Set(x => x.MergeUtility, mergeUtility)
.Set(x => x.Utilities, elements)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
@ -254,6 +256,7 @@ public partial class MongoRepository
.Set(x => x.Name, agent.Name)
.Set(x => x.Description, agent.Description)
.Set(x => x.Disabled, agent.Disabled)
.Set(x => x.MergeUtility, agent.MergeUtility)
.Set(x => x.Type, agent.Type)
.Set(x => x.Profiles, agent.Profiles)
.Set(x => x.RoutingRules, agent.RoutingRules.Select(r => RoutingRuleMongoElement.ToMongoElement(r)).ToList())
@ -263,7 +266,7 @@ public partial class MongoRepository
.Set(x => x.Functions, agent.Functions.Select(f => FunctionDefMongoElement.ToMongoElement(f)).ToList())
.Set(x => x.Responses, agent.Responses.Select(r => AgentResponseMongoElement.ToMongoElement(r)).ToList())
.Set(x => x.Samples, agent.Samples)
.Set(x => x.Utilities, agent.Utilities)
.Set(x => x.Utilities, agent.Utilities.Select(u => AgentUtilityMongoElement.ToMongoElement(u)).ToList())
.Set(x => x.LlmConfig, AgentLlmConfigMongoElement.ToMongoElement(agent.LlmConfig))
.Set(x => x.IsPublic, agent.IsPublic)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
@ -406,28 +409,19 @@ public partial class MongoRepository
IconUrl = x.IconUrl,
Description = x.Description,
Instruction = x.Instruction,
ChannelInstructions = x.ChannelInstructions?
.Select(i => ChannelInstructionMongoElement.ToMongoElement(i))?
.ToList() ?? new List<ChannelInstructionMongoElement>(),
Templates = x.Templates?
.Select(t => AgentTemplateMongoElement.ToMongoElement(t))?
.ToList() ?? new List<AgentTemplateMongoElement>(),
Functions = x.Functions?
.Select(f => FunctionDefMongoElement.ToMongoElement(f))?
.ToList() ?? new List<FunctionDefMongoElement>(),
Responses = x.Responses?
.Select(r => AgentResponseMongoElement.ToMongoElement(r))?
.ToList() ?? new List<AgentResponseMongoElement>(),
ChannelInstructions = x.ChannelInstructions?.Select(i => ChannelInstructionMongoElement.ToMongoElement(i))?.ToList() ?? [],
Templates = x.Templates?.Select(t => AgentTemplateMongoElement.ToMongoElement(t))?.ToList() ?? [],
Functions = x.Functions?.Select(f => FunctionDefMongoElement.ToMongoElement(f))?.ToList() ?? [],
Responses = x.Responses?.Select(r => AgentResponseMongoElement.ToMongoElement(r))?.ToList() ?? [],
Samples = x.Samples ?? new List<string>(),
Utilities = x.Utilities ?? new List<string>(),
Utilities = x.Utilities?.Select(u => AgentUtilityMongoElement.ToMongoElement(u))?.ToList() ?? [],
IsPublic = x.IsPublic,
Type = x.Type,
InheritAgentId = x.InheritAgentId,
Disabled = x.Disabled,
MergeUtility = x.MergeUtility,
Profiles = x.Profiles,
RoutingRules = x.RoutingRules?
.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?
.ToList() ?? new List<RoutingRuleMongoElement>(),
RoutingRules = x.RoutingRules?.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?.ToList() ?? [],
LlmConfig = AgentLlmConfigMongoElement.ToMongoElement(x.LlmConfig),
CreatedTime = x.CreatedDateTime,
UpdatedTime = x.UpdatedDateTime
@ -505,26 +499,17 @@ public partial class MongoRepository
IconUrl = agentDoc.IconUrl,
Description = agentDoc.Description,
Instruction = agentDoc.Instruction,
ChannelInstructions = !agentDoc.ChannelInstructions.IsNullOrEmpty() ? agentDoc.ChannelInstructions
.Select(i => ChannelInstructionMongoElement.ToDomainElement(i))
.ToList() : new List<ChannelInstruction>(),
Templates = !agentDoc.Templates.IsNullOrEmpty() ? agentDoc.Templates
.Select(t => AgentTemplateMongoElement.ToDomainElement(t))
.ToList() : new List<AgentTemplate>(),
Functions = !agentDoc.Functions.IsNullOrEmpty() ? agentDoc.Functions
.Select(f => FunctionDefMongoElement.ToDomainElement(f))
.ToList() : new List<FunctionDef>(),
Responses = !agentDoc.Responses.IsNullOrEmpty() ? agentDoc.Responses
.Select(r => AgentResponseMongoElement.ToDomainElement(r))
.ToList() : new List<AgentResponse>(),
RoutingRules = !agentDoc.RoutingRules.IsNullOrEmpty() ? agentDoc.RoutingRules
.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))
.ToList() : new List<RoutingRule>(),
ChannelInstructions = agentDoc.ChannelInstructions?.Select(i => ChannelInstructionMongoElement.ToDomainElement(i))?.ToList() ?? [],
Templates = agentDoc.Templates?.Select(t => AgentTemplateMongoElement.ToDomainElement(t))?.ToList() ?? [],
Functions = agentDoc.Functions?.Select(f => FunctionDefMongoElement.ToDomainElement(f)).ToList() ?? [],
Responses = agentDoc.Responses?.Select(r => AgentResponseMongoElement.ToDomainElement(r))?.ToList() ?? [],
RoutingRules = agentDoc.RoutingRules?.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))?.ToList() ?? [],
LlmConfig = AgentLlmConfigMongoElement.ToDomainElement(agentDoc.LlmConfig),
Samples = agentDoc.Samples ?? new List<string>(),
Utilities = agentDoc.Utilities ?? new List<string>(),
Samples = agentDoc.Samples ?? [],
Utilities = agentDoc.Utilities?.Select(u => AgentUtilityMongoElement.ToDomainElement(u))?.ToList() ?? [],
IsPublic = agentDoc.IsPublic,
Disabled = agentDoc.Disabled,
MergeUtility = agentDoc.MergeUtility,
Type = agentDoc.Type,
InheritAgentId = agentDoc.InheritAgentId,
Profiles = agentDoc.Profiles,

View file

@ -324,4 +324,51 @@ public partial class MongoRepository
return true;
}
public void AddDashboardConversation(string userId, string conversationId)
{
var user = _dc.Users.AsQueryable()
.FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId));
if (user == null) return;
var curDash = user.Dashboard ?? new Dashboard();
curDash.ConversationList.Add(new DashboardConversation
{
Id = Guid.NewGuid().ToString(),
ConversationId = conversationId
});
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
var update = Builders<UserDocument>.Update.Set(x => x.Dashboard, curDash)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
}
public void RemoveDashboardConversation(string userId, string conversationId)
{
var user = _dc.Users.AsQueryable()
.FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId));
if (user == null || user.Dashboard == null || user.Dashboard.ConversationList.IsNullOrEmpty()) return;
var curDash = user.Dashboard;
var unpinConv = user.Dashboard.ConversationList.FirstOrDefault(
x => string.Equals(x.ConversationId, conversationId, StringComparison.OrdinalIgnoreCase));
if (unpinConv == null) return;
curDash.ConversationList.Remove(unpinConv);
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
var update = Builders<UserDocument>.Update.Set(x => x.Dashboard, curDash)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
}
public void UpdateDashboardConversation(string userId, DashboardConversation dashConv)
{
var user = _dc.Users.AsQueryable()
.FirstOrDefault(x => x.Id == userId || (x.ExternalId != null && x.ExternalId == userId));
if (user == null || user.Dashboard == null || user.Dashboard.ConversationList.IsNullOrEmpty()) return;
var curIdx = user.Dashboard.ConversationList.ToList().FindIndex(
x => string.Equals(x.ConversationId, dashConv.ConversationId, StringComparison.OrdinalIgnoreCase));
if (curIdx < 0) return;
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
var update = Builders<UserDocument>.Update.Set(x => x.Dashboard.ConversationList[curIdx], dashConv)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
}
}

View file

@ -27,79 +27,4 @@ public class PlannerAgentHook : AgentHookBase
return true;
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();
var isConvMode = conv.IsConversationMode();
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.TwoStagePlanner);
if (isConvMode && isEnabled)
{
var (prompt, fn) = GetPromptAndFunction("plan_primary_stage");
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
(prompt, fn) = GetPromptAndFunction("plan_secondary_stage");
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
(prompt, fn) = GetPromptAndFunction("plan_summary");
if (fn != null)
{
if (!string.IsNullOrWhiteSpace(prompt))
{
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
}
if (agent.Functions == null)
{
agent.Functions = new List<FunctionDef> { fn };
}
else
{
agent.Functions.Add(fn);
}
}
}
base.OnAgentLoaded(agent);
}
private (string, FunctionDef?) GetPromptAndFunction(string functionName)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{functionName}.fn"))?.Content ?? string.Empty;
var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(functionName));
return (prompt, loadAttachmentFn);
}
}

View file

@ -2,8 +2,27 @@ namespace BotSharp.Plugin.Planner.Hooks;
public class PlannerUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
private const string PRIMARY_STAGE_FN = "plan_primary_stage";
private const string SECONDARY_STAGE_FN = "plan_secondary_stage";
private const string SUMMARY_FN = "plan_summary";
public void AddUtilities(List<AgentUtility> utilities)
{
utilities.Add(UtilityName.TwoStagePlanner);
var utility = new AgentUtility
{
Name = UtilityName.TwoStagePlanner,
Functions = [
new(PRIMARY_STAGE_FN),
new(SECONDARY_STAGE_FN),
new(SUMMARY_FN)
],
Templates = [
new($"{PRIMARY_STAGE_FN}.fn"),
new($"{SECONDARY_STAGE_FN}.fn"),
new($"{SUMMARY_FN}.fn")
]
};
utilities.Add(utility);
}
}

View file

@ -1,4 +1,4 @@
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Planning;
using BotSharp.Plugin.Planner.TwoStaging;
namespace BotSharp.Plugin.Planner;
@ -17,7 +17,7 @@ public class PlannerPlugin : IBotSharpPlugin
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<IRoutingPlaner, TwoStageTaskPlanner>();
services.AddScoped<ITaskPlanner, TwoStageTaskPlanner>();
services.AddScoped<IAgentHook, PlannerAgentHook>();
services.AddScoped<IAgentUtilityHook, PlannerUtilityHook>();
}

View file

@ -1,10 +1,10 @@
using BotSharp.Abstraction.Infrastructures.Enums;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Core.Routing.Planning;
using BotSharp.Abstraction.Planning;
using BotSharp.Core.Routing.Reasoning;
namespace BotSharp.Plugin.Planner.TwoStaging;
public partial class TwoStageTaskPlanner : IRoutingPlaner
public partial class TwoStageTaskPlanner : ITaskPlanner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
@ -40,7 +40,7 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
inst = response.Content.JsonContent<FunctionCallFromLlm>();
// Fix LLM malformed response
PlannerHelper.FixMalformedResponse(_services, inst);
ReasonerHelper.FixMalformedResponse(_services, inst);
return inst;
}

Some files were not shown because too many files have changed in this diff Show more