Merge pull request #280 from SciSharp/master

merge latest code
This commit is contained in:
geffzhang 2024-01-30 19:52:19 +08:00 committed by GitHub
commit f7cef69803
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
92 changed files with 794 additions and 202 deletions

6
.gitignore vendored
View file

@ -286,6 +286,10 @@ __pycache__/
/docs/_build
*.bin
/src/WebStarter/data/conversations
src/WebStarter/Properties/PublishProfiles
src/WebStarter/Properties/ServiceDependencies
XMLs
logs
wwwroot
appsettings.Production.json
*.csproj.user

View file

@ -81,6 +81,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.HttpHandler
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SqlDriver", "src\Plugins\BotSharp.Plugin.SqlDriver\BotSharp.Plugin.SqlDriver.csproj", "{D775DB67-A4B4-44E5-9144-522689590057}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.Dashboard", "src\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj", "{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -313,6 +315,14 @@ Global
{D775DB67-A4B4-44E5-9144-522689590057}.Release|Any CPU.Build.0 = Release|Any CPU
{D775DB67-A4B4-44E5-9144-522689590057}.Release|x64.ActiveCfg = Release|Any CPU
{D775DB67-A4B4-44E5-9144-522689590057}.Release|x64.Build.0 = Release|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|x64.ActiveCfg = Debug|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|x64.Build.0 = Debug|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|Any CPU.Build.0 = Release|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|x64.ActiveCfg = Release|Any CPU
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -353,6 +363,7 @@ Global
{5CA3335E-E6AD-46FD-B277-29BBC3A16500} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{32D9E720-6FE6-4F29-94B1-B10B05BFAD75} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{D775DB67-A4B4-44E5-9144-522689590057} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}

View file

@ -103,6 +103,7 @@ BotSharp uses component design, the kernel is kept to a minimum, and business fu
#### Tools
- BotSharp.Plugin.RoutingSpeeder
- BotSharp.Plugin.WebDriver
- BotSharp.Plugin.PizzaBot
#### UIs

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View file

@ -8,6 +8,7 @@ public enum AgentField
IsPublic,
Disabled,
Type,
InheritAgentId,
Profiles,
RoutingRule,
Instruction,

View file

@ -33,7 +33,8 @@ public class Agent
/// Templates
/// </summary>
[JsonIgnore]
public List<AgentTemplate>? Templates { get; set; }
public List<AgentTemplate> Templates { get; set; }
= new List<AgentTemplate>();
/// <summary>
/// Samples
@ -46,7 +47,8 @@ public class Agent
/// Functions
/// </summary>
[JsonIgnore]
public List<FunctionDef> Functions { get; set; } = new List<FunctionDef>();
public List<FunctionDef> Functions { get; set; }
= new List<FunctionDef>();
/// <summary>
/// Responses
@ -62,9 +64,6 @@ public class Agent
public bool IsPublic { get; set; }
[JsonIgnore]
public bool IsHost { get; set; }
[JsonIgnore]
public PluginDef Plugin { get; set; }
@ -83,6 +82,11 @@ public class Agent
public List<string> Profiles { get; set; }
= new List<string>();
/// <summary>
/// Inherit from agent
/// </summary>
public string? InheritAgentId { get; set; }
public List<RoutingRule> RoutingRules { get; set; }
= new List<RoutingRule>();

View file

@ -13,6 +13,7 @@ public interface IConversationService
Task<Conversation> UpdateConversationTitle(string id, string title);
Task<List<Conversation>> GetLastConversations();
Task<bool> DeleteConversation(string id);
Task<bool> TruncateConversation(string conversationId, string messageId);
/// <summary>
/// Send message to LLM

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Conversations.Models;
public class TruncateMessageRequest
{
public string? TruncateMessageId { get; set; }
}

View file

@ -1,6 +1,6 @@
namespace BotSharp.Abstraction.Models;
public class MessageConfig
public class MessageConfig : TruncateMessageRequest
{
/// <summary>
/// Completion Provider

View file

@ -0,0 +1,11 @@
namespace BotSharp.Abstraction.Plugins;
public class PluginDependencyAttribute : Attribute
{
public string[] PluginNames { get; set; }
public PluginDependencyAttribute(params string[] pluginNames)
{
PluginNames = pluginNames;
}
}

View file

@ -46,8 +46,11 @@ public interface IBotSharpRepository
PagedItems<Conversation> GetConversations(ConversationFilter filter);
void UpdateConversationTitle(string conversationId, string title);
List<Conversation> GetLastConversations();
bool TruncateConversation(string conversationId, string messageId);
#endregion
#region Statistics
void IncrementConversationCount();
#endregion
#region Execution Log
void AddExecutionLogs(string conversationId, List<string> logs);
List<string> GetExecutionLogs(string conversationId);

View file

@ -26,7 +26,7 @@ public class RoutingRule
public override string ToString()
{
return $"{AgentName} {Field}";
return $"{Type} {AgentName} {Field}";
}
public RoutingRule()

View file

@ -1,11 +1,10 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
namespace BotSharp.Abstraction.Planning;
namespace BotSharp.Abstraction.Routing.Planning;
public interface IExecutor
{
Task<RoleDialogModel> Execute(IRoutingService routing,
Task<RoleDialogModel> Execute(IRoutingService routing,
FunctionCallFromLlm inst,
RoleDialogModel message,
List<RoleDialogModel> dialogs);

View file

@ -1,6 +1,6 @@
using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Planning;
namespace BotSharp.Abstraction.Routing.Planning;
/// <summary>
/// Task breakdown and execution plan

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Abstraction.Statistics.Model
{
public class Statistics
{
public string Id { get; set; } = string.Empty;
public int ConversationCount { get; set; }
public DateTime UpdatedDateTime { get; set; }
}
}

View file

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Abstraction.Statistics.Settings
{
public class StatisticsSettings
{
public string DataDir { get; set; }
}
}

View file

@ -107,9 +107,9 @@ public partial class AgentService
foreach (var file in Directory.GetFiles(templateDir))
{
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var splits = fileName.ToLower().Split('.');
var name = splits[0];
var extension = splits[1];
var splitIdx = fileName.LastIndexOf(".");
var name = fileName.Substring(0, splitIdx);
var extension = fileName.Substring(splitIdx + 1);
if (extension.IsEqualTo(_agentSettings.TemplateFormat))
{
var content = File.ReadAllText(file);

View file

@ -13,20 +13,12 @@ public partial class AgentService
{
var agents = _db.GetAgents(filter);
// Set IsRouter
var routeSetting = _services.GetRequiredService<RoutingSettings>();
foreach (var agent in agents)
{
agent.Plugin = GetPlugin(agent.Id);
}
// Set IsHost
var agentSetting = _services.GetRequiredService<AgentSettings>();
foreach (var agent in agents)
{
agent.IsHost = agentSetting.HostAgentId == agent.Id;
}
agents = agents.Where(x => x.Installed).ToList();
var pager = filter?.Pager ?? new Pagination();
return new PagedItems<Agent>

View file

@ -32,6 +32,27 @@ public partial class AgentService
throw new Exception($"Can't load agent by id: {id}");
}
if (agent.InheritAgentId != null)
{
var inheritedAgent = await GetAgent(agent.InheritAgentId);
agent.Templates.AddRange(inheritedAgent.Templates
// exclude private template
.Where(x => !x.Name.StartsWith("."))
// exclude duplicate name
.Where(x => !agent.Templates.Exists(t => t.Name == x.Name)));
agent.Functions.AddRange(inheritedAgent.Functions
// exclude private template
.Where(x => !x.Name.StartsWith("."))
// exclude duplicate name
.Where(x => !agent.Functions.Exists(t => t.Name == x.Name)));
if (agent.Instruction == null)
{
agent.Instruction = inheritedAgent.Instruction;
}
}
agent.TemplateDict = new Dictionary<string, object>();
// Populate state into dictionary

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@ -50,11 +50,15 @@
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\agent.json" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\.welcome.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.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\welcome.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instruction.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.reviewer.liquid" />
<None Remove="data\plugins\config.json" />
</ItemGroup>
@ -83,7 +87,19 @@
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\welcome.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\.welcome.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.reviewer.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\plugins\config.json">

View file

@ -1,12 +1,12 @@
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.Planning;
using BotSharp.Core.Routing.Planning;
using BotSharp.Core.Templating;
using Microsoft.Extensions.Configuration;

View file

@ -0,0 +1,13 @@
using BotSharp.Abstraction.Repositories;
namespace BotSharp.Core.Conversations.Services;
public partial class ConversationService : IConversationService
{
public async Task<bool> TruncateConversation(string conversationId, string messageId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var isSaved = db.TruncateConversation(conversationId, messageId);
return await Task.FromResult(isSaved);
}
}

View file

@ -21,10 +21,11 @@ public class LlmProviderPlugin : IBotSharpPlugin
services.AddScoped(provider =>
{
var settingService = provider.GetRequiredService<ISettingService>();
var loger = provider.GetRequiredService<ILogger<LlmProviderPlugin>>();
var llmProviders = settingService.Bind<List<LlmProviderSetting>>("LlmProviders");
foreach (var llmProvider in llmProviders)
{
Console.WriteLine($"Loaded LlmProvider {llmProvider.Provider} settings with {llmProvider.Models.Count} models.");
loger.LogInformation($"Loaded LlmProvider {llmProvider.Provider} settings with {llmProvider.Models.Count} models.");
}
return llmProviders;
});

View file

@ -16,6 +16,14 @@ public partial class InstructService : IInstructService
_logger = logger;
}
/// <summary>
/// Execute completion by using specified instruction or template
/// </summary>
/// <param name="agentId">Agent (static agent)</param>
/// <param name="message">Additional message provided by user</param>
/// <param name="templateName">Template name</param>
/// <param name="instruction">System prompt</param>
/// <returns></returns>
public async Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
{
var agentService = _services.GetRequiredService<IAgentService>();

View file

@ -27,12 +27,17 @@ public class PluginLoader
_settings = settings;
}
public void Load(Action<Assembly> loaded)
public void Load(Action<Assembly> loaded, string? plugin = null)
{
_executingDir = Directory.GetParent(Assembly.GetEntryAssembly().Location).FullName;
_settings.Assemblies.ToList().ForEach(assemblyName =>
{
if (plugin != null && plugin != assemblyName)
{
return;
}
var assemblyPath = Path.Combine(_executingDir, assemblyName + ".dll");
if (File.Exists(assemblyPath))
{
@ -45,38 +50,66 @@ public class PluginLoader
foreach (var module in modules)
{
module.RegisterDI(_services, _config);
// string classSummary = GetSummaryComment(module.GetType());
var name = string.IsNullOrEmpty(module.Name) ? module.GetType().Name : module.Name;
_modules.Add(module);
_plugins.Add(new PluginDef
if (_plugins.Exists(x => x.Id == module.Id))
{
Id = module.Id,
Name = name,
Module = module,
Description = module.Description,
Assembly = assemblyName,
IconUrl = module.IconUrl,
AgentIds = module.AgentIds
});
Console.Write($"Loaded plugin ");
Console.Write(name, Color.Green);
Console.WriteLine($" from {assemblyName}.");
if (!string.IsNullOrEmpty(module.Description))
{
Console.WriteLine(module.Description);
continue;
}
// Solve plugin dependency
var attr = module.GetType().GetCustomAttribute<PluginDependencyAttribute>();
if (attr != null)
{
foreach (var plugin in attr.PluginNames)
{
if (!_plugins.Any(x => x.Assembly == plugin))
{
Load(loaded, plugin);
}
if (!_plugins.Any(x => x.Assembly == plugin))
{
Console.WriteLine($"Load dependent plugin {plugin} failed by {module.Name}.", Color.Red);
}
}
}
InitModule(assemblyName, module);
}
loaded(assembly);
}
else
{
Console.WriteLine($"Can't find assemble {assemblyPath}.");
Console.WriteLine($"Can't find assemble {assemblyPath}.", Color.Red);
}
});
}
private void InitModule(string assembly, IBotSharpPlugin module)
{
module.RegisterDI(_services, _config);
// string classSummary = GetSummaryComment(module.GetType());
var name = string.IsNullOrEmpty(module.Name) ? module.GetType().Name : module.Name;
_modules.Add(module);
_plugins.Add(new PluginDef
{
Id = module.Id,
Name = name,
Module = module,
Description = module.Description,
Assembly = assembly,
IconUrl = module.IconUrl,
AgentIds = module.AgentIds
});
Console.Write($"Loaded plugin ");
Console.Write(name, Color.Green);
Console.WriteLine($" from {assembly}.");
if (!string.IsNullOrEmpty(module.Description))
{
Console.WriteLine(module.Description);
}
}
public List<PluginDef> GetPlugins(IServiceProvider services)
{
// Apply user configurations
@ -124,6 +157,13 @@ public class PluginLoader
var agent = agentService.LoadAgent(agentId).Result;
agent.Disabled = false;
agentService.UpdateAgent(agent, AgentField.Disabled);
if (agent.InheritAgentId != null)
{
agent = agentService.LoadAgent(agent.InheritAgentId).Result;
agent.Disabled = false;
agentService.UpdateAgent(agent, AgentField.Disabled);
}
}
}
else

View file

@ -178,8 +178,18 @@ public class BotSharpDbContext : Database, IBotSharpRepository
{
throw new NotImplementedException();
}
#endregion
public bool TruncateConversation(string conversationId, string messageId)
{
throw new NotImplementedException();
}
#endregion
#region Stats
public void IncrementConversationCount()
{
throw new NotImplementedException();
}
#endregion
#region User
public User? GetUserByEmail(string email)

View file

@ -31,6 +31,9 @@ namespace BotSharp.Core.Repository
case AgentField.Type:
UpdateAgentType(agent.Id, agent.Type);
break;
case AgentField.InheritAgentId:
UpdateAgentInheritAgentId(agent.Id, agent.InheritAgentId);
break;
case AgentField.Profiles:
UpdateAgentProfiles(agent.Id, agent.Profiles);
break;
@ -123,6 +126,17 @@ namespace BotSharp.Core.Repository
File.WriteAllText(agentFile, json);
}
private void UpdateAgentInheritAgentId(string agentId, string? inheritAgentId)
{
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
agent.InheritAgentId = inheritAgentId;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
}
private void UpdateAgentProfiles(string agentId, List<string> profiles)
{
if (profiles.IsNullOrEmpty()) return;
@ -379,9 +393,9 @@ namespace BotSharp.Core.Repository
foreach (var file in Directory.GetFiles(dir))
{
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var splits = ParseFileNameByPath(fileName.ToLower());
var name = splits[0];
var extension = splits[1];
var splitIdx = fileName.LastIndexOf(".");
var name = fileName.Substring(0, splitIdx);
var extension = fileName.Substring(splitIdx + 1);
if (name.IsEqualTo(templateName) && extension.IsEqualTo(_agentSettings.TemplateFormat))
{
return File.ReadAllText(file);

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Repositories.Models;
using System.Globalization;
using System.IO;
namespace BotSharp.Core.Repository
@ -256,6 +257,35 @@ namespace BotSharp.Core.Repository
}
public bool TruncateConversation(string conversationId, string messageId)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;
var dialogs = new List<DialogElement>();
var convDir = FindConversationDirectory(conversationId);
if (string.IsNullOrEmpty(convDir)) return false;
var dialogDir = Path.Combine(convDir, DIALOG_FILE);
dialogs = CollectDialogElements(dialogDir);
if (dialogs.IsNullOrEmpty()) return false;
var foundIdx = dialogs.FindIndex(x => x.MetaData?.MessageId == messageId);
if (foundIdx < 0) return false;
// Handle truncated dialogs
var isSaved = HandleTruncatedDialogs(dialogDir, dialogs, foundIdx);
if (!isSaved) return false;
// Handle truncated states
var refTime = dialogs.ElementAt(foundIdx).MetaData.CreateTime;
var stateDir = Path.Combine(convDir, STATE_FILE);
var states = CollectConversationStates(stateDir);
isSaved = HandleTruncatedStates(stateDir, states, refTime);
return isSaved;
}
#region Private methods
private string? FindConversationDirectory(string conversationId)
{
@ -304,8 +334,9 @@ namespace BotSharp.Core.Repository
foreach (var element in dialogs)
{
var meta = element.MetaData;
var createTime = meta.CreateTime.ToString("MM/dd/yyyy hh:mm:ss.fff tt", CultureInfo.InvariantCulture);
var source = meta.FunctionName ?? meta.SenderId;
var metaStr = $"{meta.CreateTime}|{meta.Role}|{meta.AgentId}|{meta.MessageId}|{source}";
var metaStr = $"{createTime}|{meta.Role}|{meta.AgentId}|{meta.MessageId}|{source}";
dialogTexts.Add(metaStr);
var content = $" - {element.Content}";
dialogTexts.Add(content);
@ -325,6 +356,49 @@ namespace BotSharp.Core.Repository
states = JsonSerializer.Deserialize<List<StateKeyValue>>(stateStr, _options);
return states ?? new List<StateKeyValue>();
}
private bool HandleTruncatedDialogs(string dialogDir, List<DialogElement> dialogs, int foundIdx)
{
var truncatedDialogs = dialogs.Where((x, idx) => idx < foundIdx).ToList();
var isSaved = SaveTruncatedDialogs(dialogDir, truncatedDialogs);
return isSaved;
}
private bool HandleTruncatedStates(string stateDir, List<StateKeyValue> states, DateTime refTime)
{
var truncatedStates = new List<StateKeyValue>();
foreach (var state in states)
{
var values = state.Values.Where(x => x.UpdateTime < refTime).ToList();
if (values.Count == 0) continue;
state.Values = values;
truncatedStates.Add(state);
}
var isSaved = SaveTruncatedStates(stateDir, truncatedStates);
return isSaved;
}
private bool SaveTruncatedDialogs(string dialogDir, List<DialogElement> dialogs)
{
if (string.IsNullOrEmpty(dialogDir) || dialogs == null) return false;
if (!File.Exists(dialogDir)) File.Create(dialogDir);
var texts = ParseDialogElements(dialogs);
File.WriteAllLines(dialogDir, texts);
return true;
}
private bool SaveTruncatedStates(string stateDir, List<StateKeyValue> states)
{
if (string.IsNullOrEmpty(stateDir) || states == null) return false;
if (!File.Exists(stateDir)) File.Create(stateDir);
var stateStr = JsonSerializer.Serialize(states, _options);
File.WriteAllText(stateDir, stateStr);
return true;
}
#endregion
}
}

View file

@ -0,0 +1,57 @@
using BotSharp.Abstraction.Statistics.Model;
using System.IO;
namespace BotSharp.Core.Repository
{
public partial class FileRepository
{
public void IncrementConversationCount()
{
var statsFileDirectory = FindCurrentStatsDirectory();
if (statsFileDirectory == null)
{
statsFileDirectory = CreateStatsFileDirectory();
}
var fileName = GenerateStatsFileName();
var statsFile = Path.Combine(statsFileDirectory, fileName);
if (!File.Exists(statsFile))
{
File.WriteAllText(statsFile, JsonSerializer.Serialize(new Statistics()
{
Id = Guid.NewGuid().ToString(),
UpdatedDateTime = DateTime.UtcNow
}, _options));
}
var json = File.ReadAllText(statsFile);
var stats = JsonSerializer.Deserialize<Statistics>(json, _options);
stats.ConversationCount += 1;
stats.UpdatedDateTime = DateTime.UtcNow;
File.WriteAllText(statsFile, JsonSerializer.Serialize(stats, _options));
}
public string? CreateStatsFileDirectory()
{
var dir = GenerateStatsDirectoryName();
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
return dir;
}
private string? FindCurrentStatsDirectory()
{
var dir = GenerateStatsDirectoryName();
if (!Directory.Exists(dir)) return null;
return dir;
}
private string GenerateStatsDirectoryName()
{
return Path.Combine(_dbSettings.FileRepository, _statisticsSetting.DataDir, DateTime.UtcNow.Year.ToString(), DateTime.UtcNow.ToString("MM"));
}
private string GenerateStatsFileName()
{
var fileName = DateTime.UtcNow.ToString("MMdd");
return $"{fileName}-{STATS_FILE}";
}
}
}

View file

@ -11,6 +11,7 @@ using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Abstraction.Evaluations.Settings;
using System.Text.Encodings.Web;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Statistics.Settings;
namespace BotSharp.Core.Repository;
@ -20,6 +21,7 @@ public partial class FileRepository : IBotSharpRepository
private readonly BotSharpDatabaseSettings _dbSettings;
private readonly AgentSettings _agentSettings;
private readonly ConversationSetting _conversationSettings;
private readonly StatisticsSettings _statisticsSetting;
private JsonSerializerOptions _options;
private const string AGENT_FILE = "agent.json";
@ -29,6 +31,7 @@ public partial class FileRepository : IBotSharpRepository
private const string USER_FILE = "user.json";
private const string USER_AGENT_FILE = "agents.json";
private const string CONVERSATION_FILE = "conversation.json";
private const string STATS_FILE = "stats.json";
private const string DIALOG_FILE = "dialogs.txt";
private const string STATE_FILE = "state.json";
private const string EXECUTION_LOG_FILE = "execution.log";
@ -38,12 +41,14 @@ public partial class FileRepository : IBotSharpRepository
IServiceProvider services,
BotSharpDatabaseSettings dbSettings,
AgentSettings agentSettings,
ConversationSetting conversationSettings)
ConversationSetting conversationSettings,
StatisticsSettings statisticsSettings)
{
_services = services;
_dbSettings = dbSettings;
_agentSettings = agentSettings;
_conversationSettings = conversationSettings;
_statisticsSetting = statisticsSettings;
_options = new JsonSerializerOptions
{
@ -176,10 +181,10 @@ public partial class FileRepository : IBotSharpRepository
return (agent, agentFile);
}
private string FetchInstruction(string fileDir)
private string? FetchInstruction(string fileDir)
{
var file = Path.Combine(fileDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
if (!File.Exists(file)) return string.Empty;
if (!File.Exists(file)) return null;
var instruction = File.ReadAllText(file);
return instruction;
@ -212,9 +217,9 @@ public partial class FileRepository : IBotSharpRepository
foreach (var file in Directory.GetFiles(templateDir))
{
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var splits = fileName.ToLower().Split('.');
var name = string.Join('.', splits.Take(splits.Length - 1));
var extension = splits.Last();
var splitIdx = fileName.LastIndexOf(".");
var name = fileName.Substring(0, splitIdx);
var extension = fileName.Substring(splitIdx + 1);
if (extension.Equals(_agentSettings.TemplateFormat, StringComparison.OrdinalIgnoreCase))
{
var content = File.ReadAllText(file);

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.Planning;
using BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Routing.Handlers;

View file

@ -1,7 +1,7 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Core.Planning;
using BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Routing.Handlers;

View file

@ -2,7 +2,7 @@ using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Core.Planning;
using BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Routing.Handlers;

View file

@ -1,7 +1,7 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Core.Planning;
using BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Routing.Handlers;

View file

@ -1,12 +1,12 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
namespace BotSharp.Core.Routing.Planning;
/// <summary>
/// Human feedback based planner

View file

@ -1,8 +1,8 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Planning;
namespace BotSharp.Core.Planning;
namespace BotSharp.Core.Routing.Planning;
public class InstructExecutor : IExecutor
{

View file

@ -1,11 +1,11 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
namespace BotSharp.Core.Routing.Planning;
public class NaivePlanner : IPlaner
{
@ -32,7 +32,7 @@ public class NaivePlanner : IPlaner
var completion = CompletionProvider.GetTextCompletion(_services);*/
// chat completion
var completion = CompletionProvider.GetChatCompletion(_services,
var completion = CompletionProvider.GetChatCompletion(_services,
provider: router?.LlmConfig?.Provider,
model: router?.LlmConfig?.Model);
@ -44,7 +44,7 @@ public class NaivePlanner : IPlaner
{
// text completion
// text = await completion.GetCompletion(content, router.Id, messageId);
var dialogs = new List<RoleDialogModel>
var dialogs = new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, next)
{
@ -91,7 +91,7 @@ public class NaivePlanner : IPlaner
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();
@ -123,8 +123,8 @@ public class NaivePlanner : IPlaner
private void FixMalformedResponse(FunctionCallFromLlm args)
{
var agentService = _services.GetRequiredService<IAgentService>();
var agents = agentService.GetAgents(new AgentFilter
{
var agents = agentService.GetAgents(new AgentFilter
{
Type = AgentType.Task
}).Result.Items.ToList();
var malformed = false;

View file

@ -1,11 +1,11 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
namespace BotSharp.Core.Routing.Planning;
public class SequentialPlanner : IPlaner
{
@ -91,7 +91,7 @@ public class SequentialPlanner : IPlaner
context.Empty();
return false;
}
// Handover to Router;
context.Pop();

View file

@ -1,10 +1,9 @@
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Abstraction.Settings;
using BotSharp.Core.Planning;
using BotSharp.Core.Routing.Hooks;
using BotSharp.Core.Routing.Planning;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Routing;

View file

@ -1,7 +1,7 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing.Enums;
using BotSharp.Core.Planning;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Core.Routing.Planning;
namespace BotSharp.Core.Routing;

View file

@ -1,10 +1,10 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Routing.Settings;
using System.Drawing;

View file

@ -1,12 +1,11 @@
{
"id": "01e2fc5c-2c89-4ec7-8470-7688608b496c",
"name": "Chatbot",
"description": "AI chatbot that can do variaty of tasks",
"description": "Chatbot is used to test the performance of different large models and does not interact with external APIs.",
"type": "task",
"createdDateTime": "2024-01-15T10:39:32Z",
"updatedDateTime": "2024-01-15T14:39:32Z",
"iconUrl": "/images/users/bot.png",
"iconUrl": "/images/logo.png",
"disabled": false,
"isPublic": true,
"profiles": [ "standalone" ]
"isPublic": true
}

View file

@ -1,2 +1,2 @@
In order to execute the instructions listed by the user in the order specified by the user.
In order to execute the listed instructions in the order specified by the user.
What is the next step based on the CONVERSATION?

View file

@ -0,0 +1,11 @@
{
"id": "dfd9b46d-d00c-40af-8a75-3fbdc2b89869",
"name": "Evaluator",
"description": "Evaluate the performance of the LLM agents",
"createdDateTime": "2023-08-18T00:00:00Z",
"updatedDateTime": "2023-08-18T00:00:00Z",
"disabled": true,
"llmConfig": {
"model": "gpt-35-turbo-instruct"
}
}

View file

@ -25,23 +25,37 @@ public class AgentController : ControllerBase
[HttpGet("/agent/{id}")]
public async Task<AgentViewModel> GetAgent([FromRoute] string id)
{
var agent = await _agentService.GetAgent(id);
return AgentViewModel.FromAgent(agent);
var agents = await GetAgents(new AgentFilter
{
AgentIds = new List<string> { id }
});
return agents.Items.First();
}
[HttpGet("/agents")]
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
{
var agentSetting = _services.GetRequiredService<AgentSettings>();
var pagedAgents = await _agentService.GetAgents(filter);
// prerender agent
var items = new List<Agent>();
foreach (var agent in pagedAgents.Items)
{
var renderedAgent = await _agentService.LoadAgent(agent.Id);
items.Add(renderedAgent);
}
// Set IsHost
var agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
foreach(var agent in agents)
{
agent.IsHost = agentSetting.HostAgentId == agent.Id;
}
return new PagedItems<AgentViewModel>
{
Items = items.Select(x => AgentViewModel.FromAgent(x)).ToList(),
Items = agents,
Count = pagedAgents.Count
};
}

View file

@ -37,6 +37,7 @@ public class ConversationController : ControllerBase
var conversations = await service.GetConversations(filter);
var userService = _services.GetRequiredService<IUserService>();
var agentService = _services.GetRequiredService<IAgentService>();
var list = conversations.Items
.Select(x => ConversationViewModel.FromSession(x))
.ToList();
@ -45,6 +46,9 @@ public class ConversationController : ControllerBase
{
var user = await userService.GetUser(item.User.Id);
item.User = UserViewModel.FromUser(user);
var agent = await agentService.GetAgent(item.AgentId);
item.AgentName = agent.Name;
}
return new PagedItems<ConversationViewModel>
@ -110,14 +114,26 @@ public class ConversationController : ControllerBase
return response;
}
[HttpDelete("/conversation/{conversationId}/message/{messageId}")]
public async Task<bool> DeleteConversationMessage([FromRoute] string conversationId, [FromRoute] string messageId)
{
var conversationService = _services.GetRequiredService<IConversationService>();
var response = await conversationService.TruncateConversation(conversationId, messageId);
return response;
}
[HttpPost("/conversation/{agentId}/{conversationId}")]
public async Task<ChatResponseModel> SendMessage([FromRoute] string agentId,
[FromRoute] string conversationId,
[FromBody] NewMessageModel input)
{
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
var conv = _services.GetRequiredService<IConversationService>();
if (!string.IsNullOrEmpty(input.TruncateMessageId))
{
await conv.TruncateConversation(conversationId, input.TruncateMessageId);
}
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
conv.SetConversationId(conversationId, input.States);
conv.States.SetState("channel", input.Channel)
.SetState("provider", input.Provider)

View file

@ -28,7 +28,6 @@ public class PluginController : ControllerBase
{
var menu = new List<PluginMenuDef>
{
new PluginMenuDef("Dashboard", link: "/page/dashboard", icon: "bx bx-home-circle", weight: 1),
new PluginMenuDef("Apps", weight: 5)
{
IsHeader = true,
@ -54,15 +53,15 @@ public class PluginController : ControllerBase
return menu;
}
[HttpPost("/plugin/{id}/enable")]
public PluginDef EnablePlugin([FromRoute] string id)
[HttpPost("/plugin/{id}/install")]
public PluginDef InstallPlugin([FromRoute] string id)
{
var loader = _services.GetRequiredService<PluginLoader>();
return loader.UpdatePluginStatus(_services, id, true);
}
[HttpPost("/plugin/{id}/disable")]
public PluginDef DisablePluginStats([FromRoute] string id)
[HttpPost("/plugin/{id}/remove")]
public PluginDef RemovePluginStats([FromRoute] string id)
{
var loader = _services.GetRequiredService<PluginLoader>();
return loader.UpdatePluginStatus(_services, id, false);

View file

@ -62,7 +62,6 @@ public class AgentViewModel
Responses = agent.Responses,
Samples = agent.Samples,
IsPublic= agent.IsPublic,
IsHost = agent.IsHost,
Disabled = agent.Disabled,
IconUrl = agent.IconUrl,
Profiles = agent.Profiles ?? new List<string>(),

View file

@ -9,13 +9,13 @@ public class ConversationViewModel
[JsonPropertyName("agent_id")]
public string AgentId { get; set; }
[JsonPropertyName("agent_name")]
public string AgentName { get; set; }
public string Title { get; set; } = string.Empty;
public UserViewModel User { get; set; } = new UserViewModel();
[JsonPropertyName("unread_msg_count")]
public int UnreadMsgCount { get; set; }
public string Event { get; set; }
public string Channel { get; set; } = ConversationChannel.OpenAPI;

View file

@ -292,7 +292,9 @@ public class ChatCompletionProvider : IChatCompletion
else if (x.Role == ChatRole.User)
{
var m = x as ChatRequestUserMessage;
return $"{m.Role}: {m.Content}";
return !string.IsNullOrEmpty(m.Name) ?
$"{m.Name}: {m.Content}" :
$"{m.Role}: {m.Content}";
}
else if (x.Role == ChatRole.Assistant)
{

View file

@ -36,7 +36,7 @@ public class ChatHubConversationHook : ConversationHookBase
var agent = await agentService.LoadAgent(conversation.AgentId);
// Check if the Welcome template exists.
var welcomeTemplate = agent.Templates?.FirstOrDefault(x => x.Name == "welcome");
var welcomeTemplate = agent.Templates?.FirstOrDefault(x => x.Name == ".welcome");
if (welcomeTemplate != null)
{
var richContentService = _services.GetRequiredService<IRichContentService>();

View file

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,32 @@
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Plugins;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Statistics.Settings;
using BotSharp.Plugin.Dashboard.Hooks;
namespace BotSharp.Plugin.Dashboard;
public class DashboardPlugin : IBotSharpPlugin
{
public string Id => "d42a0c21-b461-44f6-ada2-499510d260af";
public string Name => "Dashboard";
public string Description => "Dashboard that offers real-time statistics on model performance, usage trends, and user feedback";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<IConversationHook, StatsConversationHook>();
services.AddScoped(provider =>
{
var settingService = provider.GetRequiredService<ISettingService>();
return settingService.Bind<StatisticsSettings>("Statistics");
});
}
public bool AttachMenu(List<PluginMenuDef> menu)
{
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("Dashboard", link: "/page/dashboard", icon: "bx bx-home-circle", weight: section.Weight - 1));
return true;
}
}

View file

@ -0,0 +1,20 @@
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories;
namespace BotSharp.Plugin.Dashboard.Hooks;
public class StatsConversationHook : ConversationHookBase
{
private readonly IServiceProvider _services;
public StatsConversationHook(IServiceProvider services)
{
_services = services;
}
public override async Task OnConversationInitialized(Conversation conversation)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
db.IncrementConversationCount();
}
}

View file

@ -0,0 +1,13 @@
global using System;
global using System.Collections.Generic;
global using System.Text;
global using System.Threading.Tasks;
global using System.Linq;
global using System.Text.Json;
global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Agents.Models;
global using BotSharp.Abstraction.MLTasks;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using System.Text.Json.Serialization;
global using BotSharp.Abstraction.Utilities;

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@ -35,7 +35,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="PdfPig" Version="0.1.9-alpha-20231119-4537e" />
<PackageReference Include="PdfPig" Version="0.1.9-alpha-20240121-04fc8" />
<PackageReference Include="TensorFlow.Keras" Version="0.15.0" />
</ItemGroup>

View file

@ -7,6 +7,7 @@ public class AgentDocument : MongoBase
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string? InheritAgentId { get; set; }
public string? IconUrl { get; set; }
public string Instruction { get; set; }
public List<AgentTemplateMongoElement> Templates { get; set; }

View file

@ -9,6 +9,7 @@ public class RoutingRuleMongoElement
public string Description { get; set; }
public bool Required { get; set; }
public string? RedirectTo { get; set; }
public string Type { get; set; }
public RoutingRuleMongoElement()
{
@ -22,7 +23,8 @@ public class RoutingRuleMongoElement
Field = routingRule.Field,
Description = routingRule.Description,
Required = routingRule.Required,
RedirectTo = routingRule.RedirectTo
RedirectTo = routingRule.RedirectTo,
Type = routingRule.Type,
};
}
@ -35,7 +37,8 @@ public class RoutingRuleMongoElement
Field = rule.Field,
Description = rule.Description,
Required = rule.Required,
RedirectTo = rule.RedirectTo
RedirectTo = rule.RedirectTo,
Type = rule.Type,
};
}
}

View file

@ -32,6 +32,9 @@ public partial class MongoRepository
case AgentField.Type:
UpdateAgentType(agent.Id, agent.Type);
break;
case AgentField.InheritAgentId:
UpdateAgentInheritAgentId(agent.Id, agent.InheritAgentId);
break;
case AgentField.Profiles:
UpdateAgentProfiles(agent.Id, agent.Profiles);
break;
@ -119,6 +122,16 @@ public partial class MongoRepository
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentInheritAgentId(string agentId, string? inheritAgentId)
{
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var update = Builders<AgentDocument>.Update
.Set(x => x.InheritAgentId, inheritAgentId)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentProfiles(string agentId, List<string> profiles)
{
if (profiles.IsNullOrEmpty()) return;
@ -268,6 +281,7 @@ public partial class MongoRepository
IsPublic = agent.IsPublic,
Disabled = agent.Disabled,
Type = agent.Type,
InheritAgentId = agent.InheritAgentId,
Profiles = agent.Profiles,
RoutingRules = !agent.RoutingRules.IsNullOrEmpty() ? agent.RoutingRules
.Select(r => RoutingRuleMongoElement.ToDomainElement(agent.Id, agent.Name, r))
@ -329,6 +343,7 @@ public partial class MongoRepository
IsPublic = x.IsPublic,
Disabled = x.Disabled,
Type = x.Type,
InheritAgentId = x.InheritAgentId,
Profiles = x.Profiles,
RoutingRules = !x.RoutingRules.IsNullOrEmpty() ? x.RoutingRules
.Select(r => RoutingRuleMongoElement.ToDomainElement(x.Id, x.Name, r))
@ -393,6 +408,7 @@ public partial class MongoRepository
Samples = x.Samples ?? new List<string>(),
IsPublic = x.IsPublic,
Type = x.Type,
InheritAgentId = x.InheritAgentId,
Disabled = x.Disabled,
Profiles = x.Profiles,
RoutingRules = x.RoutingRules?

View file

@ -263,4 +263,42 @@ public partial class MongoRepository
UpdatedTime = c.UpdatedTime
}).ToList();
}
public bool TruncateConversation(string conversationId, string messageId)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;
var dialogFilter = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundDialog = _dc.ConversationDialogs.Find(dialogFilter).FirstOrDefault();
if (foundDialog == null || foundDialog.Dialogs.IsNullOrEmpty()) return false;
var foundIdx = foundDialog.Dialogs.FindIndex(x => x.MetaData?.MessageId == messageId);
if (foundIdx < 0) return false;
// Handle truncated dialogs
var truncatedDialogs = foundDialog.Dialogs.Where((x, idx) => idx < foundIdx).ToList();
// Handle truncated states
var refTime = foundDialog.Dialogs.ElementAt(foundIdx).MetaData.CreateTime;
var stateFilter = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundStates = _dc.ConversationStates.Find(stateFilter).FirstOrDefault();
if (foundStates == null || foundStates.States.IsNullOrEmpty()) return false;
var truncatedStates = new List<StateMongoElement>();
foreach (var state in foundStates.States)
{
var values = state.Values.Where(x => x.UpdateTime < refTime).ToList();
if (values.Count == 0) continue;
state.Values = values;
truncatedStates.Add(state);
}
// Save
foundDialog.Dialogs = truncatedDialogs;
foundStates.States = truncatedStates;
_dc.ConversationDialogs.ReplaceOne(dialogFilter, foundDialog);
_dc.ConversationStates.ReplaceOne(stateFilter, foundStates);
return true;
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Plugin.MongoStorage.Repository
{
public partial class MongoRepository
{
#region Statistics
public void IncrementConversationCount()
{
}
#endregion
}
}

View file

@ -55,6 +55,7 @@ public partial class MongoRepository
Samples = x.Samples ?? new List<string>(),
IsPublic = x.IsPublic,
Type = x.Type,
InheritAgentId = x.InheritAgentId,
Disabled = x.Disabled,
Profiles = x.Profiles,
RoutingRules = x.RoutingRules?
@ -78,6 +79,7 @@ public partial class MongoRepository
.Set(x => x.Samples, agent.Samples)
.Set(x => x.IsPublic, agent.IsPublic)
.Set(x => x.Type, agent.Type)
.Set(x => x.InheritAgentId, agent.InheritAgentId)
.Set(x => x.Disabled, agent.Disabled)
.Set(x => x.Profiles, agent.Profiles)
.Set(x => x.RoutingRules, agent.RoutingRules)

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.41.1" />
<PackageReference Include="Microsoft.Playwright" Version="1.41.2" />
</ItemGroup>
<ItemGroup>

View file

@ -16,6 +16,11 @@ public class PlaywrightInstance : IDisposable
{
_playwright = await Playwright.CreateAsync();
/*_browser = await _playwright.Chromium.LaunchPersistentContextAsync(@"C:\Users\haipi\AppData\Local\Google\Chrome\User Data", new BrowserTypeLaunchPersistentContextOptions
{
Headless = false,
Channel = "chrome",
});*/
_browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false,

View file

@ -57,23 +57,11 @@ public partial class PlaywrightWebDriver
}
var driverService = _services.GetRequiredService<WebDriverService>();
var htmlElementContextOut = await driverService.LocateElement(agent, string.Join("", str), context.ElementName, messageId);
if (htmlElementContextOut.Index < 0)
{
throw new Exception($"Can't locate the web element {context.ElementName}.");
}
ILocator element = default;
if (!string.IsNullOrEmpty(htmlElementContextOut.ElementId))
{
// await _instance.Page.WaitForSelectorAsync($"#{htmlElementContextOut.ElementId}", new PageWaitForSelectorOptions { Timeout = 3 });
element = _instance.Page.Locator($"#{htmlElementContextOut.ElementId}");
}
else
{
element = _instance.Page.Locator(htmlElementContextOut.TagName).Nth(htmlElementContextOut.Index);
}
var htmlElementContextOut = await driverService.LocateElement(agent,
string.Join("", str),
context.ElementName,
messageId);
ILocator element = Locator(htmlElementContextOut);
try
{

View file

@ -1,11 +1,11 @@
using BotSharp.Plugin.WebDriver.Services;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
public async Task ClickElement(Agent agent, BrowsingContextIn context, string messageId)
{
var driverService = _services.GetRequiredService<WebDriverService>();
// Retrieve the page raw html and infer the element path
var body = await _instance.Page.QuerySelectorAsync("body");
@ -23,18 +23,19 @@ public partial class PlaywrightWebDriver
var text = await btn.TextContentAsync();
var name = await btn.GetAttributeAsync("name");
var id = await btn.GetAttributeAsync("id");
str.Add($"<button name='{name}' id='{id}'>{text}</button>");
str.Add(driverService.AssembleMarkup("button", new MarkupProperties
{
Id = id,
Name = name,
Text = text
}));
}
var driverService = _services.GetRequiredService<WebDriverService>();
var htmlElementContextOut = await driverService.LocateElement(agent, string.Join("", str), context.ElementName, messageId);
var tags = await _instance.Page.QuerySelectorAllAsync(htmlElementContextOut.TagName);
var button = tags[htmlElementContextOut.Index];
if (button.AsElement() == null)
{
throw new Exception($"Can't find web element {context.ElementName}");
}
await button.ClickAsync();
var htmlElementContextOut = await driverService.LocateElement(agent,
string.Join("", str),
context.ElementName,
messageId);
ILocator element = Locator(htmlElementContextOut);
await element.ClickAsync();
}
}

View file

@ -1,11 +1,11 @@
using BotSharp.Plugin.WebDriver.Services;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
public partial class PlaywrightWebDriver
{
public async Task InputUserText(Agent agent, BrowsingContextIn context, string messageId)
{
var driverService = _services.GetRequiredService<WebDriverService>();
// Retrieve the page raw html and infer the element path
var body = await _instance.Page.QuerySelectorAsync("body");
@ -14,32 +14,47 @@ public partial class PlaywrightWebDriver
foreach (var input in inputs)
{
var text = await input.TextContentAsync();
var id = await input.GetAttributeAsync("id");
var name = await input.GetAttributeAsync("name");
var type = await input.GetAttributeAsync("type");
str.Add($"<input name='{name}' type='{type}'>{text}</input>");
str.Add(driverService.AssembleMarkup("input", new MarkupProperties
{
Id = id,
Name = name,
Type = type,
Text = text
}));
}
inputs = await body.QuerySelectorAllAsync("textarea");
foreach (var input in inputs)
{
var text = await input.TextContentAsync();
var id = await input.GetAttributeAsync("id");
var name = await input.GetAttributeAsync("name");
var type = await input.GetAttributeAsync("type");
str.Add($"<textarea name='{name}' type='{type}'>{text}</textarea>");
str.Add(driverService.AssembleMarkup("textarea", new MarkupProperties
{
Id = id,
Name = name,
Type = type,
Text = text
}));
}
var htmlElementContextOut = await driverService.LocateElement(agent,
string.Join("", str),
context.ElementName,
messageId);
ILocator element = Locator(htmlElementContextOut);
var driverService = _services.GetRequiredService<WebDriverService>();
var htmlElementContextOut = await driverService.LocateElement(agent, string.Join("", str), context.ElementName, messageId);
if (htmlElementContextOut.Index < 0)
{
throw new Exception($"Can't locate the web element {context.ElementName}.");
}
var element = _instance.Page.Locator(htmlElementContextOut.TagName).Nth(htmlElementContextOut.Index);
try
{
await element.FillAsync(context.InputText);
if (context.PressEnter)
{
await element.PressAsync("Enter");
}
}
catch (Exception ex)
{

View file

@ -11,4 +11,24 @@ public partial class PlaywrightWebDriver
_services = services;
_instance = instance;
}
private ILocator Locator(HtmlElementContextOut context)
{
ILocator element = default;
if (!string.IsNullOrEmpty(context.ElementId))
{
// await _instance.Page.WaitForSelectorAsync($"#{htmlElementContextOut.ElementId}", new PageWaitForSelectorOptions { Timeout = 3 });
element = _instance.Page.Locator($"#{context.ElementId}");
}
else
{
if (context.Index < 0)
{
throw new Exception($"Can't locate the web element {context.Index}.");
}
element = _instance.Page.Locator(context.TagName).Nth(context.Index);
}
return element;
}
}

View file

@ -13,6 +13,9 @@ public class BrowsingContextIn
[JsonPropertyName("input_text")]
public string? InputText { get; set; }
[JsonPropertyName("press_enter")]
public bool PressEnter { get; set; }
[JsonPropertyName("update_value")]
public string? UpdateValue { get; set; }

View file

@ -0,0 +1,9 @@
namespace BotSharp.Plugin.WebDriver.Models;
internal class MarkupProperties
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Type { get; set; }
public string? Text { get; set; }
}

View file

@ -0,0 +1,33 @@
using BotSharp.Plugin.WebDriver.LlmContexts;
namespace BotSharp.Plugin.WebDriver.Services;
public partial class WebDriverService
{
internal string AssembleMarkup(string tagName, MarkupProperties properties)
{
var html = $"<{tagName}";
if (!string.IsNullOrEmpty(properties.Id))
{
html += $" id=\"{properties.Id}\"";
}
if (!string.IsNullOrEmpty(properties.Name))
{
html += $" name=\"{properties.Name}\"";
}
if (!string.IsNullOrEmpty(properties.Type))
{
html += $" type=\"{properties.Type}\"";
}
if (!string.IsNullOrEmpty(properties.Text))
{
html += $">{properties.Text}</{tagName}>";
return html;
}
return html + $"></{tagName}>";
}
}

View file

@ -14,4 +14,6 @@ global using BotSharp.Abstraction.Templating;
global using BotSharp.Plugin.WebDriver.LlmContexts;
global using Microsoft.Extensions.DependencyInjection;
global using System.Linq;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Plugin.WebDriver.Models;
global using BotSharp.Plugin.WebDriver.Services;

View file

@ -1,12 +1,13 @@
{
"id": "f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b",
"name": "Web Driver",
"description": "Perform a specific action on a web browser",
"description": "Perform the web page related task in browser by using automation tools.",
"type": "task",
"createdDateTime": "2024-01-02T00:00:00Z",
"updatedDateTime": "2024-01-02T00:00:00Z",
"isPublic": true,
"profiles": [ "web-driver" ],
"llmConfig": {
"max_recursion_depth": 10
"max_recursion_depth": 5
}
}

View file

@ -51,10 +51,14 @@
"type": "string",
"description": "the html input box element name."
},
"input_text": {
"type": "string",
"description": "non-sensitive text user provided."
}
"input_text": {
"type": "string",
"description": "non-sensitive text user provided."
},
"press_enter": {
"type": "boolean",
"description": "whether to press ENTER"
}
},
"required": ["element_name", "input_text"]
}

View file

@ -29,6 +29,7 @@
<ItemGroup>
<ProjectReference Include="..\..\tests\BotSharp.Plugin.PizzaBot\BotSharp.Plugin.PizzaBot.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj" />
</ItemGroup>
<ItemGroup>

View file

@ -82,7 +82,9 @@
"EnableLlmCompletionLog": false,
"EnableExecutionLog": true
},
"Stats": {
"DataDir": "stats"
},
"LlamaSharp": {
"Interactive": true,
"ModelDir": "C:/Users/haipi/Downloads",
@ -161,8 +163,10 @@
"PluginLoader": {
"Assemblies": [
"BotSharp.Plugin.MongoStorage",
"BotSharp.Core",
"BotSharp.Logger",
"BotSharp.Plugin.MongoStorage",
"BotSharp.Plugin.Dashboard",
"BotSharp.Plugin.AzureOpenAI",
"BotSharp.Plugin.GoogleAI",
"BotSharp.Plugin.MetaAI",

View file

@ -20,6 +20,8 @@
</ItemGroup>
<ItemGroup>
<None Remove="data\agents\8970b1e5-d260-4e2c-90b1-f1415a257c18\agent.json" />
<None Remove="data\agents\8970b1e5-d260-4e2c-90b1-f1415a257c18\templates\task.place_pizza_order.liquid" />
<None Remove="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\agent.json" />
<None Remove="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\functions.json" />
<None Remove="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\instruction.liquid" />
@ -27,11 +29,6 @@
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions.json" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\instruction.liquid" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\responses\func.get_pizza_price.0.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instruction.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.reviewer.liquid" />
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\task.place_pizza_order.liquid" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\agent.json" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\functions.json" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instruction.liquid" />
@ -42,6 +39,12 @@
</ItemGroup>
<ItemGroup>
<Content Include="data\agents\8970b1e5-d260-4e2c-90b1-f1415a257c18\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\8970b1e5-d260-4e2c-90b1-f1415a257c18\templates\task.place_pizza_order.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -63,21 +66,6 @@
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\responses\func.get_pizza_price.0.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.reviewer.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\task.place_pizza_order.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View file

@ -11,6 +11,7 @@ public class PizzaBotPlugin : IBotSharpPlugin
public string IconUrl => "https://cdn-icons-png.flaticon.com/512/6978/6978255.png";
public string[] AgentIds => new[]
{
"8970b1e5-d260-4e2c-90b1-f1415a257c18",
"b284db86-e9c2-4c25-a59e-4649797dd130",
"c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",
"dfd9b46d-d00c-40af-8a75-3fbdc2b89869",

View file

@ -0,0 +1,18 @@
{
"id": "8970b1e5-d260-4e2c-90b1-f1415a257c18",
"name": "Pizza Bot",
"description": "AI assistant that can help customer place pizza order.",
"type": "routing",
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
"iconUrl": "https://cdn-icons-png.flaticon.com/512/6978/6978255.png",
"disabled": true,
"isPublic": true,
"profiles": [ "pizza" ],
"routingRules": [
{
"type": "planner",
"field": "NaviePlanner"
}
]
}

View file

@ -4,7 +4,7 @@
"createdDateTime": "2023-08-18T14:39:32.2349685Z",
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
"allowRouting": true,
"disabled": true,
"isPublic": true,
"profiles": [ "pizza" ],
"routingRules": [

View file

@ -4,7 +4,7 @@
"createdDateTime": "2023-07-26T02:29:25.123224Z",
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
"id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",
"allowRouting": true,
"disabled": true,
"isPublic": true,
"profiles": [ "pizza" ]
}

View file

@ -1,10 +0,0 @@
{
"name": "EvaluationAgent",
"description": "Evaluate the performance of the LLM agents",
"createdDateTime": "2023-08-18T00:00:00Z",
"updatedDateTime": "2023-08-18T00:00:00Z",
"id": "dfd9b46d-d00c-40af-8a75-3fbdc2b89869",
"llmConfig": {
"model": "gpt-35-turbo-instruct"
}
}

View file

@ -4,8 +4,9 @@
"createdDateTime": "2023-07-26T02:29:25.123224Z",
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
"id": "fe8c60aa-b114-4ef3-93cb-a8efeac80f75",
"allowRouting": true,
"disabled": true,
"isPublic": true,
"profiles": [ "pizza" ],
"routingRules": [
{
"field": "order_number",

View file

@ -12,7 +12,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.1.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.2.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">