This commit is contained in:
hchen2020 2023-09-15 17:49:26 -05:00
commit 5d06d9ad5d
17 changed files with 766 additions and 365 deletions

View file

@ -15,9 +15,7 @@
It's written in C# running on .Net Core that is full cross-platform framework, the plug-in and pipeline flow execution design is adopted to completely decouple the plug-ins. C# is a enterprise grade programming language which is widely used to code business logic in information management related system. More friendly to corporate developers. BotSharp adopts machine learning algrithm in C# directly. That will facilitate the feature of the typed language C#, and be more easier when refactoring code in system scope.
Why we do this? Because we all know Python is not friendly programming language for enterprise developers, it's not only because it's low performance but also it's a type weak language, it will be a disaster if you use Python to build your bussiness system.
BotSharp is in accordance with components principle strictly, decouples every part that is needed in the platform builder. So you can choose different UI/UX, or pick up a different NLP Tagger, or select a more advanced algorithm to do NER task. They are all modulized based on unified interfaces.
**BotSharp** is in accordance with components principle strictly, decouples every part that is needed in the platform builder. So you can choose different UI/UX, or pick up a different LLM providers. They are all modulized based on unified interfaces. **BotSharp** provides an advanced Agent abstraction layer to efficiently manage complex application scenarios in enterprises, allowing enterprise developers to efficiently integrate AI into business systems.
### Some Features

View file

@ -0,0 +1,13 @@
namespace BotSharp.Abstraction.Agents.Enums;
public enum AgentField
{
All = 1,
Name,
Description,
IsPublic,
Instruction,
Function,
Template,
Response
}

View file

@ -17,7 +17,7 @@ public interface IAgentService
Task<Agent> GetAgent(string id);
Task<bool> DeleteAgent(string id);
Task UpdateAgent(Agent agent);
Task UpdateAgent(Agent agent, AgentField updateField);
Task UpdateAgentFromFile(string id);
string GetDataDir();
string GetAgentDataDir(string agentId);

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Users.Models;
@ -15,27 +16,32 @@ public interface IBotSharpRepository
int Transaction<TTableInterface>(Action action);
void Add<TTableInterface>(object entity);
#region User
User GetUserByEmail(string email);
void CreateUser(User user);
void UpdateAgent(Agent agent);
#endregion
#region Agent
void UpdateAgent(Agent agent, AgentField field);
Agent GetAgent(string agentId);
List<string> GetAgentResponses(string agentId, string prefix, string intent);
string GetAgentTemplate(string agentId, string templateName);
#endregion
#region Conversation
void CreateNewConversation(Conversation conversation);
string GetConversationDialog(string conversationId);
void UpdateConversationDialog(string conversationId, string dialogs);
List<StateKeyValue> GetConversationStates(string conversationId);
void UpdateConversationStates(string conversationId, List<StateKeyValue> states);
Conversation GetConversation(string conversationId);
List<Conversation> GetConversations(string userId);
#endregion
#region Routing
List<RoutingItem> CreateRoutingItems(List<RoutingItem> routingItems);
List<RoutingProfile> CreateRoutingProfiles(List<RoutingProfile> profiles);
void DeleteRoutingItems();
void DeleteRoutingProfiles();
Agent GetAgent(string agentId);
List<string> GetAgentResponses(string agentId, string prefix, string intent);
void CreateNewConversation(Conversation conversation);
string GetConversationDialog(string conversationId);
void UpdateConversationDialog(string conversationId, string dialogs);
List<StateKeyValue> GetConversationStates(string conversationId);
void UpdateConversationStates(string conversationId, List<StateKeyValue> states);
Conversation GetConversation(string conversationId);
List<Conversation> GetConversations(string userId);
string GetAgentTemplate(string agentId, string templateName);
#endregion
}

View file

@ -5,4 +5,5 @@ global using System.Linq;
global using System.Threading.Tasks;
global using System.ComponentModel.DataAnnotations;
global using BotSharp.Abstraction.Agents.Models;
global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Agents.Enums;

View file

@ -8,11 +8,9 @@ public partial class AgentService
{
public async Task<Agent> CreateAgent(Agent agent)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agentRecord = (from a in db.Agents
join ua in db.UserAgents on a.Id equals ua.AgentId
join u in db.Users on ua.UserId equals u.Id
var agentRecord = (from a in _db.Agents
join ua in _db.UserAgents on a.Id equals ua.AgentId
join u in _db.Users on ua.UserId equals u.Id
where u.ExternalId == _user.Id && a.Name == agent.Name
select a).FirstOrDefault();
@ -43,7 +41,7 @@ public partial class AgentService
.SetResponses(foundAgent.Responses);
}
var user = db.Users.FirstOrDefault(x => x.ExternalId == _user.Id);
var user = _db.Users.FirstOrDefault(x => x.ExternalId == _user.Id);
var userAgentRecord = new UserAgent
{
Id = Guid.NewGuid().ToString(),
@ -53,10 +51,10 @@ public partial class AgentService
UpdatedTime = DateTime.UtcNow
};
db.Transaction<IBotSharpTable>(delegate
_db.Transaction<IBotSharpTable>(delegate
{
db.Add<IBotSharpTable>(agentRecord);
db.Add<IBotSharpTable>(userAgentRecord);
_db.Add<IBotSharpTable>(agentRecord);
_db.Add<IBotSharpTable>(userAgentRecord);
});
return agentRecord;
@ -86,7 +84,7 @@ public partial class AgentService
private string FetchInstructionFromFile(string fileDir)
{
var file = Path.Combine(fileDir, "instruction.liquid");
var file = Path.Combine(fileDir, $"instruction.{_agentSettings.TemplateFormat}");
if (!File.Exists(file)) return null;
var instruction = File.ReadAllText(file);
@ -102,7 +100,7 @@ public partial class AgentService
var splits = fileName.ToLower().Split('.');
var name = splits[0];
var extension = splits[1];
if (name != "instruction" && extension == "liquid")
if (name != "instruction" && extension == _agentSettings.TemplateFormat)
{
var content = File.ReadAllText(file);
templates.Add(new AgentTemplate(name, content));

View file

@ -7,10 +7,9 @@ public partial class AgentService
{
public async Task<List<Agent>> GetAgents()
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var query = from a in db.Agents
join ua in db.UserAgents on a.Id equals ua.AgentId
join u in db.Users on ua.UserId equals u.Id
var query = from a in _db.Agents
join ua in _db.UserAgents on a.Id equals ua.AgentId
join u in _db.Users on ua.UserId equals u.Id
where ua.UserId == _user.Id || u.ExternalId == _user.Id || a.IsPublic
select a;
return query.ToList();
@ -21,8 +20,7 @@ public partial class AgentService
#endif
public async Task<Agent> GetAgent(string id)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var profile = db.GetAgent(id);
var profile = _db.GetAgent(id);
var instructionFile = profile?.Instruction;
if (instructionFile != null)

View file

@ -6,44 +6,38 @@ namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
public async Task UpdateAgent(Agent agent)
public async Task UpdateAgent(Agent agent, AgentField updateField)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = (from a in db.Agents
join ua in db.UserAgents on a.Id equals ua.AgentId
join u in db.Users on ua.UserId equals u.Id
where (ua.UserId == _user.Id || u.ExternalId == _user.Id) &&
a.Id == agent.Id
select a).FirstOrDefault();
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var record = FindAgent(agent.Id);
if (record == null) return;
record.Name = agent.Name;
record.Name = agent.Name ?? string.Empty;
record.Description = agent.Description ?? string.Empty;
record.Instruction = agent.Instruction ?? string.Empty;
record.Functions = agent.Functions ?? new List<string>();
record.Templates = agent.Templates ?? new List<AgentTemplate>();
record.Responses = agent.Responses ?? new List<AgentResponse>();
if (!string.IsNullOrEmpty(agent.Description))
record.Description = agent.Description;
if (!string.IsNullOrEmpty(agent.Instruction))
record.Instruction = agent.Instruction;
if (!agent.Templates.IsNullOrEmpty())
record.Templates = agent.Templates;
if (!agent.Functions.IsNullOrEmpty())
record.Functions = agent.Functions;
if (!agent.Responses.IsNullOrEmpty())
record.Responses = agent.Responses;
db.UpdateAgent(record);
_db.UpdateAgent(record, updateField);
await Task.CompletedTask;
}
private Agent FindAgent(string agentId)
{
var record = (from a in _db.Agents
join ua in _db.UserAgents on a.Id equals ua.AgentId
join u in _db.Users on ua.UserId equals u.Id
where (ua.UserId == _user.Id || u.ExternalId == _user.Id) &&
a.Id == agentId
select a).FirstOrDefault();
return record;
}
public async Task UpdateAgentFromFile(string id)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var agent = db.Agents?.FirstOrDefault(x => x.Id == id);
var agent = _db.Agents?.FirstOrDefault(x => x.Id == id);
if (agent == null) return;
@ -64,10 +58,9 @@ public partial class AgentService
.SetFunctions(foundAgent.Functions)
.SetResponses(foundAgent.Responses);
db.UpdateAgent(clonedAgent);
_db.UpdateAgent(clonedAgent, AgentField.All);
}
await Task.CompletedTask;
}

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Repositories;
using Microsoft.Extensions.Logging;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@ -7,20 +6,23 @@ namespace BotSharp.Core.Agents.Services;
public partial class AgentService : IAgentService
{
private readonly IServiceProvider _services;
private readonly IBotSharpRepository _db;
private readonly ILogger _logger;
private readonly IUserIdentity _user;
private readonly AgentSettings _settings;
private readonly AgentSettings _agentSettings;
private readonly JsonSerializerOptions _options;
public AgentService(IServiceProvider services,
public AgentService(IServiceProvider services,
IBotSharpRepository db,
ILogger<AgentService> logger,
IUserIdentity user,
AgentSettings settings)
AgentSettings agentSettings)
{
_services = services;
_db = db;
_logger = logger;
_user = user;
_settings = settings;
_agentSettings = agentSettings;
_options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
@ -38,7 +40,7 @@ public partial class AgentService : IAgentService
public string GetAgentDataDir(string agentId)
{
var dbSettings = _services.GetRequiredService<BotSharpDatabaseSettings>();
var dir = Path.Combine(dbSettings.FileRepository, _settings.DataDir, agentId);
var dir = Path.Combine(dbSettings.FileRepository, _agentSettings.DataDir, agentId);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);

View file

@ -71,77 +71,55 @@ public class BotSharpDbContext : Database, IBotSharpRepository
}
public void CreateNewConversation(Conversation conversation)
{
throw new NotImplementedException();
}
public List<RoutingItem> CreateRoutingItems(List<RoutingItem> routingItems)
{
throw new NotImplementedException();
}
public List<RoutingProfile> CreateRoutingProfiles(List<RoutingProfile> profiles)
{
throw new NotImplementedException();
}
public void CreateUser(User user)
{
throw new NotImplementedException();
}
public void DeleteRoutingItems()
{
throw new NotImplementedException();
}
public void DeleteRoutingProfiles()
{
throw new NotImplementedException();
}
#region Agent
public Agent GetAgent(string agentId)
{
throw new NotImplementedException();
}
public void UpdateAgent(Agent agent, AgentField field)
{
throw new NotImplementedException();
}
public string GetAgentTemplate(string agentId, string templateName)
{
throw new NotImplementedException();
}
public List<string> GetAgentResponses(string agentId, string prefix, string intent)
{
throw new NotImplementedException();
}
#endregion
#region Conversation
public void CreateNewConversation(Conversation conversation)
{
throw new NotImplementedException();
}
public Conversation GetConversation(string conversationId)
{
throw new NotImplementedException();
}
public string GetConversationDialog(string conversationId)
{
throw new NotImplementedException();
}
public List<Conversation> GetConversations(string userId)
{
throw new NotImplementedException();
}
public string GetConversationDialog(string conversationId)
{
throw new NotImplementedException();
}
public List<StateKeyValue> GetConversationStates(string conversationId)
{
throw new NotImplementedException();
}
public User GetUserByEmail(string email)
{
throw new NotImplementedException();
}
public void UpdateAgent(Agent agent)
{
throw new NotImplementedException();
}
public void UpdateConversationDialog(string conversationId, string dialogs)
{
throw new NotImplementedException();
@ -151,9 +129,41 @@ public class BotSharpDbContext : Database, IBotSharpRepository
{
throw new NotImplementedException();
}
#endregion
public string GetAgentTemplate(string agentId, string templateName)
#region User
public User GetUserByEmail(string email)
{
throw new NotImplementedException();
}
public void CreateUser(User user)
{
throw new NotImplementedException();
}
#endregion
#region Routing
public List<RoutingItem> CreateRoutingItems(List<RoutingItem> routingItems)
{
throw new NotImplementedException();
}
public List<RoutingProfile> CreateRoutingProfiles(List<RoutingProfile> profiles)
{
throw new NotImplementedException();
}
public void DeleteRoutingItems()
{
throw new NotImplementedException();
}
public void DeleteRoutingProfiles()
{
throw new NotImplementedException();
}
#endregion
}

View file

@ -4,6 +4,11 @@ using FunctionDef = BotSharp.Abstraction.Functions.Models.FunctionDef;
using BotSharp.Abstraction.Users.Models;
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Routing.Models;
using MongoDB.Driver;
using Microsoft.Extensions.Logging;
using System.Xml.Linq;
using static Tensorflow.TensorShapeProto.Types;
namespace BotSharp.Core.Repository;
public class FileRepository : IBotSharpRepository
@ -66,7 +71,15 @@ public class FileRepository : IBotSharpRepository
foreach (var d in Directory.GetDirectories(dir))
{
var json = File.ReadAllText(Path.Combine(d, "agent.json"));
_agents.Add(JsonSerializer.Deserialize<Agent>(json, _options));
var agent = JsonSerializer.Deserialize<Agent>(json, _options);
if (agent != null)
{
agent = agent.SetInstruction(FetchInstruction(d))
.SetTemplates(FetchTemplates(d))
.SetFunctions(FetchFunctions(d))
.SetResponses(FetchResponses(d));
_agents.Add(agent);
}
}
return _agents.AsQueryable();
}
@ -201,9 +214,7 @@ public class FileRepository : IBotSharpRepository
{
foreach (var conversation in _conversations)
{
var dir = Path.Combine(_dbSettings.FileRepository,
_conversationSettings.DataDir,
conversation.Id);
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversation.Id);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
@ -216,9 +227,7 @@ public class FileRepository : IBotSharpRepository
{
foreach (var agent in _agents)
{
var dir = Path.Combine(_dbSettings.FileRepository,
_agentSettings.DataDir,
agent.Id);
var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agent.Id);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
@ -231,9 +240,7 @@ public class FileRepository : IBotSharpRepository
{
foreach (var user in _users)
{
var dir = Path.Combine(_dbSettings.FileRepository,
"users",
user.Id);
var dir = Path.Combine(_dbSettings.FileRepository, "users", user.Id);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
@ -262,79 +269,189 @@ public class FileRepository : IBotSharpRepository
return _changedTableNames.Count;
}
public User GetUserByEmail(string email)
#region Agent
public void UpdateAgent(Agent agent, AgentField field)
{
return Users.FirstOrDefault(x => x.Email == email);
}
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
public void CreateUser(User user)
{
var userId = Guid.NewGuid().ToString();
var dir = Path.Combine(_dbSettings.FileRepository, "users", userId);
if (!Directory.Exists(dir))
switch (field)
{
Directory.CreateDirectory(dir);
case AgentField.Name:
UpdateAgentName(agent.Id, agent.Name);
break;
case AgentField.Description:
UpdateAgentDescription(agent.Id, agent.Description);
break;
case AgentField.IsPublic:
UpdateAgentIsPublic(agent.Id, agent.IsPublic);
break;
case AgentField.Instruction:
UpdateAgentInstruction(agent.Id, agent.Instruction);
break;
case AgentField.Function:
UpdateAgentFunctions(agent.Id, agent.Functions);
break;
case AgentField.Template:
UpdateAgentTemplates(agent.Id, agent.Templates);
break;
case AgentField.Response:
UpdateAgentResponses(agent.Id, agent.Responses);
break;
case AgentField.All:
UpdateAgentAllFields(agent);
break;
default:
break;
}
var path = Path.Combine(dir, "user.json");
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
public void UpdateAgent(Agent agent)
#region Update Agent Fields
private void UpdateAgentName(string agentId, string name)
{
if (string.IsNullOrEmpty(name)) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var dir = GetAgentDataDir(agent.Id);
agent.Name = name;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
}
if (!string.IsNullOrEmpty(agent.Instruction))
private void UpdateAgentDescription(string agentId, string description)
{
if (string.IsNullOrEmpty(description)) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
agent.Description = description;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
}
private void UpdateAgentIsPublic(string agentId, bool isPublic)
{
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
agent.IsPublic = isPublic;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
}
private void UpdateAgentInstruction(string agentId, string instruction)
{
if (string.IsNullOrEmpty(instruction)) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var instructionFile = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir,
agentId, $"instruction.{_agentSettings.TemplateFormat}");
File.WriteAllText(instructionFile, instruction);
}
private void UpdateAgentFunctions(string agentId, List<string> inputFunctions)
{
if (inputFunctions.IsNullOrEmpty()) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var functionFile = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir,
agentId, "functions.json");
var functions = new List<string>();
foreach (var function in inputFunctions)
{
var instructionFile = Path.Combine(dir, "instruction.liquid");
File.WriteAllText(instructionFile, agent.Instruction);
var functionDef = JsonSerializer.Deserialize<FunctionDef>(function, _options);
functions.Add(JsonSerializer.Serialize(functionDef, _options));
}
if (!agent.Functions.IsNullOrEmpty())
var functionText = JsonSerializer.Serialize(functions, _options);
File.WriteAllText(functionFile, functionText);
}
private void UpdateAgentTemplates(string agentId, List<AgentTemplate> templates)
{
if (templates.IsNullOrEmpty()) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var baseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
foreach (var file in Directory.GetFiles(baseDir))
{
var functionFile = Path.Combine(dir, "functions.json");
var functions = new List<string>();
foreach (var function in agent.Functions)
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var splits = fileName.ToLower().Split('.');
var name = splits[0];
var extension = splits[1];
if (name != "instruction" && extension == _agentSettings.TemplateFormat)
{
var functionDef = JsonSerializer.Deserialize<FunctionDef>(function, _options);
functions.Add(JsonSerializer.Serialize(functionDef, _options));
File.Delete(file);
}
var functionText = JsonSerializer.Serialize(functions, _options);
File.WriteAllText(functionFile, functionText);
}
}
private string GetAgentDataDir(string agentId)
{
var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
if (!Directory.Exists(dir))
foreach (var template in templates)
{
Directory.CreateDirectory(dir);
var file = Path.Combine(baseDir, $"{template.Name}.{_agentSettings.TemplateFormat}");
File.WriteAllText(file, template.Content);
}
return dir;
}
public void DeleteRoutingItems()
private void UpdateAgentResponses(string agentId, List<AgentResponse> responses)
{
throw new NotImplementedException();
if (responses.IsNullOrEmpty()) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var baseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
var responseDir = Path.Combine(baseDir, "responses");
if (!Directory.Exists(responseDir))
{
Directory.CreateDirectory(responseDir);
}
foreach (var file in Directory.GetFiles(responseDir))
{
File.Delete(file);
}
for (int i = 0; i < responses.Count; i++)
{
var response = responses[i];
var fileName = $"{response.Prefix}.{response.Intent}.{i}.{_agentSettings.TemplateFormat}";
var file = Path.Combine(responseDir, fileName);
File.WriteAllText(file, response.Content);
}
}
public void DeleteRoutingProfiles()
private void UpdateAgentAllFields(Agent inputAgent)
{
throw new NotImplementedException();
}
var (agent, agentFile) = GetAgentFromFile(inputAgent.Id);
if (agent == null) return;
public List<RoutingItem> CreateRoutingItems(List<RoutingItem> routingItems)
{
throw new NotImplementedException();
}
agent.Name = inputAgent.Name;
agent.Description = inputAgent.Description;
agent.IsPublic = inputAgent.IsPublic;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
public List<RoutingProfile> CreateRoutingProfiles(List<RoutingProfile> profiles)
{
throw new NotImplementedException();
UpdateAgentInstruction(inputAgent.Id, inputAgent.Instruction);
UpdateAgentResponses(inputAgent.Id, inputAgent.Responses);
UpdateAgentTemplates(inputAgent.Id, inputAgent.Templates);
UpdateAgentFunctions(inputAgent.Id, inputAgent.Functions);
}
#endregion
#if !DEBUG
[MemoryCache(10 * 60)]
@ -375,26 +492,29 @@ public class FileRepository : IBotSharpRepository
return null;
}
private string FetchInstruction(string fileDir)
public string GetAgentTemplate(string agentId, string templateName)
{
var file = Path.Combine(fileDir, "instruction.liquid");
if (!File.Exists(file)) return null;
var fileDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
if (!Directory.Exists(fileDir)) return string.Empty;
var instruction = File.ReadAllText(file);
return instruction;
}
private List<string> FetchFunctions(string fileDir)
{
var file = Path.Combine(fileDir, "functions.json");
if (!File.Exists(file)) return new List<string>();
var functionsJson = File.ReadAllText(file);
var functionDefs = JsonSerializer.Deserialize<List<FunctionDef>>(functionsJson, _options);
var functions = functionDefs.Select(x => JsonSerializer.Serialize(x, _options)).ToList();
return functions;
var lowerTemplateName = templateName?.ToLower();
foreach (var file in Directory.GetFiles(fileDir))
{
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var splits = fileName.ToLower().Split('.');
var name = splits[0];
var extension = splits[1];
if (name == lowerTemplateName && extension == _agentSettings.TemplateFormat)
{
return File.ReadAllText(file);
}
}
return string.Empty;
}
#endregion
#region Conversation
public void CreateNewConversation(Conversation conversation)
{
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversation.Id);
@ -473,26 +593,6 @@ public class FileRepository : IBotSharpRepository
return curStates;
}
private string? FindConversationDirectory(string conversationId)
{
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
foreach (var d in Directory.GetDirectories(dir))
{
var path = Path.Combine(d, "conversation.json");
if (!File.Exists(path)) continue;
var json = File.ReadAllText(path);
var conv = JsonSerializer.Deserialize<Conversation>(json, _options);
if (conv != null && conv.Id == conversationId)
{
return d;
}
}
return null;
}
public void UpdateConversationStates(string conversationId, List<StateKeyValue> states)
{
var localStates = new List<string>();
@ -559,25 +659,148 @@ public class FileRepository : IBotSharpRepository
return records;
}
#endregion
public string GetAgentTemplate(string agentId, string templateName)
#region User
public User GetUserByEmail(string email)
{
var fileDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
if (!Directory.Exists(fileDir)) return string.Empty;
return Users.FirstOrDefault(x => x.Email == email);
}
public void CreateUser(User user)
{
var userId = Guid.NewGuid().ToString();
var dir = Path.Combine(_dbSettings.FileRepository, "users", userId);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var path = Path.Combine(dir, "user.json");
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
#endregion
#region Routing
public void DeleteRoutingItems()
{
throw new NotImplementedException();
}
public void DeleteRoutingProfiles()
{
throw new NotImplementedException();
}
public List<RoutingItem> CreateRoutingItems(List<RoutingItem> routingItems)
{
throw new NotImplementedException();
}
public List<RoutingProfile> CreateRoutingProfiles(List<RoutingProfile> profiles)
{
throw new NotImplementedException();
}
#endregion
#region Private methods
private string GetAgentDataDir(string agentId)
{
var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
if (!Directory.Exists(dir))
{
dir = string.Empty;
}
return dir;
}
private (Agent?, string) GetAgentFromFile(string agentId)
{
var dir = GetAgentDataDir(agentId);
var agentFile = Path.Combine(dir, "agent.json");
if (!File.Exists(agentFile)) return (null, string.Empty);
var json = File.ReadAllText(agentFile);
var agent = JsonSerializer.Deserialize<Agent>(json, _options);
return (agent, agentFile);
}
private string FetchInstruction(string fileDir)
{
var file = Path.Combine(fileDir, $"instruction.{_agentSettings.TemplateFormat}");
if (!File.Exists(file)) return string.Empty;
var instruction = File.ReadAllText(file);
return instruction;
}
private List<string> FetchFunctions(string fileDir)
{
var file = Path.Combine(fileDir, "functions.json");
if (!File.Exists(file)) return new List<string>();
var functionsJson = File.ReadAllText(file);
var functionDefs = JsonSerializer.Deserialize<List<FunctionDef>>(functionsJson, _options);
var functions = functionDefs.Select(x => JsonSerializer.Serialize(x, _options)).ToList();
return functions;
}
private List<AgentTemplate> FetchTemplates(string fileDir)
{
var templates = new List<AgentTemplate>();
var lowerTemplateName = templateName?.ToLower();
foreach (var file in Directory.GetFiles(fileDir))
{
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var splits = fileName.ToLower().Split('.');
var name = splits[0];
var extension = splits[1];
if (name == lowerTemplateName && extension == "liquid")
if (name != "instruction" && extension == _agentSettings.TemplateFormat)
{
return File.ReadAllText(file);
var content = File.ReadAllText(file);
templates.Add(new AgentTemplate(name, content));
}
}
return string.Empty;
return templates;
}
private List<AgentResponse> FetchResponses(string fileDir)
{
var responses = new List<AgentResponse>();
var responseDir = Path.Combine(fileDir, "responses");
if (!Directory.Exists(responseDir)) return responses;
foreach (var file in Directory.GetFiles(responseDir))
{
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var splits = fileName.ToLower().Split('.');
var prefix = splits[0];
var intent = splits[1];
var content = File.ReadAllText(file);
responses.Add(new AgentResponse(prefix, intent, content));
}
return responses;
}
private string? FindConversationDirectory(string conversationId)
{
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
foreach (var d in Directory.GetDirectories(dir))
{
var path = Path.Combine(d, "conversation.json");
if (!File.Exists(path)) continue;
var json = File.ReadAllText(path);
var conv = JsonSerializer.Deserialize<Conversation>(json, _options);
if (conv != null && conv.Id == conversationId)
{
return d;
}
}
return null;
}
#endregion
}

View file

@ -16,6 +16,7 @@ global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Agents.Settings;
global using BotSharp.Abstraction.Conversations.Settings;
global using BotSharp.Abstraction.Agents.Enums;
global using BotSharp.Core.Repository;
global using BotSharp.Core.Agents.Services;
global using BotSharp.Core.Conversations.Services;

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.ApiAdapters;
using BotSharp.OpenAPI.ViewModels.Agents;
@ -13,6 +14,13 @@ public class AgentController : ControllerBase, IApiAdapter
_agentService = agentService;
}
[HttpGet("/agents")]
public async Task<List<AgentViewModel>> GetAgents()
{
var agents = await _agentService.GetAgents();
return agents.Select(x => AgentViewModel.FromAgent(x)).ToList();
}
[HttpPost("/agent")]
public async Task<AgentViewModel> CreateAgent(AgentCreationModel agent)
{
@ -20,25 +28,73 @@ public class AgentController : ControllerBase, IApiAdapter
return AgentViewModel.FromAgent(createdAgent);
}
[HttpPut("/agent/{agentId}")]
public async Task UpdateAgent([FromRoute] string agentId,
[FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model);
}
[HttpPut("/agent/file/{agentId}")]
public async Task UpdateAgentFromFile([FromRoute] string agentId)
{
await _agentService.UpdateAgentFromFile(agentId);
}
[HttpGet("/agents")]
public async Task<List<AgentViewModel>> GetAgents()
[HttpPut("/agent/{agentId}/all")]
public async Task UpdateAgent([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var agents = await _agentService.GetAgents();
return agents.Select(x => AgentViewModel.FromAgent(x)).ToList();
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.All);
}
[HttpPut("/agent/{agentId}/name")]
public async Task UpdateAgentName([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.Name);
}
[HttpPut("/agent/{agentId}/description")]
public async Task UpdateAgentDescription([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.Description);
}
[HttpPut("/agent/{agentId}/is-public")]
public async Task UpdateAgentIsPublic([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.IsPublic);
}
[HttpPut("/agent/{agentId}/instruction")]
public async Task UpdateAgentInstruction([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.Instruction);
}
[HttpPut("/agent/{agentId}/functions")]
public async Task UpdateAgentFunctions([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.Function);
}
[HttpPut("/agent/{agentId}/templates")]
public async Task UpdateAgenttemplates([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.Template);
}
[HttpPut("/agent/{agentId}/responses")]
public async Task UpdateAgentResponses([FromRoute] string agentId, [FromBody] AgentUpdateModel agent)
{
var model = agent.ToAgent();
model.Id = agentId;
await _agentService.UpdateAgent(model, AgentField.Response);
}
}

View file

@ -4,7 +4,7 @@ namespace BotSharp.OpenAPI.ViewModels.Agents;
public class AgentUpdateModel
{
public string Name { get; set; } = string.Empty;
public string? Name { get; set; } = string.Empty;
public string? Description { get; set; }
/// <summary>
@ -34,29 +34,16 @@ public class AgentUpdateModel
public Agent ToAgent()
{
var agent = new Agent
var agent = new Agent()
{
Name = Name
Name = Name ?? string.Empty,
Description = Description ?? string.Empty,
Instruction = Instruction ?? string.Empty,
Templates = Templates ?? new List<AgentTemplate>(),
Functions = Functions ?? new List<string>(),
Responses = Responses ?? new List<AgentResponse>()
};
if (Description != null)
agent.Description = Description;
if (Instruction != null)
agent.Instruction = Instruction;
if (!Templates.IsNullOrEmpty())
agent.Templates = Templates;
if (Samples != null)
agent.Samples = Samples;
if (!Functions.IsNullOrEmpty())
agent.Functions = Functions;
if (!Responses.IsNullOrEmpty())
agent.Responses = Responses;
return agent;
}
}

View file

@ -12,6 +12,7 @@ public class AgentViewModel
public List<string> Functions { get; set; }
public List<AgentResponse> Responses { get; set; }
public bool IsPublic { get; set; }
public DateTime CreatedDateTime { get; set; }
public DateTime UpdatedDateTime { get; set; }
public static AgentViewModel FromAgent(Agent agent)
@ -26,6 +27,7 @@ public class AgentViewModel
Functions = agent.Functions,
Responses = agent.Responses,
IsPublic= agent.IsPublic,
CreatedDateTime = agent.CreatedDateTime,
UpdatedDateTime = agent.UpdatedDateTime
};
}

View file

@ -332,61 +332,127 @@ public class MongoRepository : IBotSharpRepository
return _changedTableNames.Count;
}
public User GetUserByEmail(string email)
{
var user = Users.FirstOrDefault(x => x.Email == email);
return user != null ? new User
{
Id = user.Id.ToString(),
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Password = user.Password,
Salt = user.Salt,
ExternalId = user.ExternalId,
CreatedTime = user.CreatedTime,
UpdatedTime = user.UpdatedTime
} : null;
}
public void CreateUser(User user)
{
if (user == null) return;
var userCollection = new UserCollection
{
Id = Guid.NewGuid(),
FirstName = user.FirstName,
LastName = user.LastName,
Salt = user.Salt,
Password = user.Password,
Email = user.Email,
ExternalId = user.ExternalId,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
};
_dc.Users.InsertOne(userCollection);
}
public void UpdateAgent(Agent agent)
#region Agent
public void UpdateAgent(Agent agent, AgentField field)
{
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var agentCollection = new AgentCollection
switch (field)
{
Id = Guid.Parse(agent.Id),
Name = agent.Name,
Description = agent.Description,
Instruction = agent.Instruction,
Templates = agent.Templates,
Functions = agent.Functions,
Responses = agent.Responses,
IsPublic = agent.IsPublic,
UpdatedTime = DateTime.UtcNow
};
case AgentField.Name:
UpdateAgentName(agent.Id, agent.Name);
break;
case AgentField.Description:
UpdateAgentDescription(agent.Id, agent.Description);
break;
case AgentField.IsPublic:
UpdateAgentIsPublic(agent.Id, agent.IsPublic);
break;
case AgentField.Instruction:
UpdateAgentInstruction(agent.Id, agent.Instruction);
break;
case AgentField.Function:
UpdateAgentFunctions(agent.Id, agent.Functions);
break;
case AgentField.Template:
UpdateAgentTemplates(agent.Id, agent.Templates);
break;
case AgentField.Response:
UpdateAgentResponses(agent.Id, agent.Responses);
break;
case AgentField.All:
UpdateAgentAllFields(agent);
break;
default:
break;
}
}
#region Update Agent Fields
private void UpdateAgentName(string agentId, string name)
{
if (string.IsNullOrEmpty(name)) return;
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agentId));
var update = Builders<AgentCollection>.Update
.Set(x => x.Name, name)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentDescription(string agentId, string description)
{
if (string.IsNullOrEmpty(description)) return;
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agentId));
var update = Builders<AgentCollection>.Update
.Set(x => x.Description, description)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentIsPublic(string agentId, bool isPublic)
{
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agentId));
var update = Builders<AgentCollection>.Update
.Set(x => x.IsPublic, isPublic)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentInstruction(string agentId, string instruction)
{
if (string.IsNullOrEmpty(instruction)) return;
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agentId));
var update = Builders<AgentCollection>.Update
.Set(x => x.Instruction, instruction)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentFunctions(string agentId, List<string> functions)
{
if (functions.IsNullOrEmpty()) return;
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agentId));
var update = Builders<AgentCollection>.Update
.Set(x => x.Functions, functions)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentTemplates(string agentId, List<AgentTemplate> templates)
{
if (templates.IsNullOrEmpty()) return;
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agentId));
var update = Builders<AgentCollection>.Update
.Set(x => x.Templates, templates)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentResponses(string agentId, List<AgentResponse> responses)
{
if (responses.IsNullOrEmpty()) return;
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agentId));
var update = Builders<AgentCollection>.Update
.Set(x => x.Responses, responses)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentAllFields(Agent agent)
{
var filter = Builders<AgentCollection>.Filter.Eq(x => x.Id, Guid.Parse(agent.Id));
var update = Builders<AgentCollection>.Update
.Set(x => x.Name, agent.Name)
@ -396,63 +462,19 @@ public class MongoRepository : IBotSharpRepository
.Set(x => x.Functions, agent.Functions)
.Set(x => x.Responses, agent.Responses)
.Set(x => x.IsPublic, agent.IsPublic)
.Set(x => x.UpdatedTime, agent.UpdatedDateTime);
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
#endregion
public void DeleteRoutingItems()
public Agent GetAgent(string agentId)
{
_dc.RoutingItems.DeleteMany(Builders<RoutingItemCollection>.Filter.Empty);
}
public void DeleteRoutingProfiles()
{
_dc.RoutingProfiles.DeleteMany(Builders<RoutingProfileCollection>.Filter.Empty);
}
public List<RoutingItem> CreateRoutingItems(List<RoutingItem> routingItems)
{
var collections = routingItems?.Select(x => new RoutingItemCollection
{
Id = Guid.NewGuid(),
AgentId = Guid.Parse(x.AgentId),
Name = x.Name,
Description = x.Description,
RequiredFields = x.RequiredFields,
RedirectTo = !string.IsNullOrEmpty(x.RedirectTo) ? Guid.Parse(x.RedirectTo) : null,
Disabled = x.Disabled
})?.ToList() ?? new List<RoutingItemCollection>();
_dc.RoutingItems.InsertMany(collections);
return collections.Select(x => new RoutingItem
{
Id = x.Id.ToString(),
AgentId = x.AgentId.ToString(),
Name = x.Name,
Description = x.Description,
RequiredFields = x.RequiredFields,
RedirectTo = x.RedirectTo?.ToString(),
Disabled = x.Disabled
}).ToList();
}
public List<RoutingProfile> CreateRoutingProfiles(List<RoutingProfile> profiles)
{
var collections = profiles?.Select(x => new RoutingProfileCollection
{
Id = Guid.NewGuid(),
Name = x.Name,
AgentIds = x.AgentIds.Select(x => Guid.Parse(x)).ToList()
})?.ToList() ?? new List<RoutingProfileCollection>();
_dc.RoutingProfiles.InsertMany(collections);
return collections.Select(x => new RoutingProfile
{
Id = x.Id.ToString(),
Name = x.Name,
AgentIds = x.AgentIds.Select(x => x.ToString()).ToList()
}).ToList();
var foundAgent = Agents.FirstOrDefault(x => x.Id == agentId);
return foundAgent;
}
public List<string> GetAgentResponses(string agentId, string prefix, string intent)
@ -464,12 +486,16 @@ public class MongoRepository : IBotSharpRepository
return agent.Responses.Where(x => x.Prefix == prefix && x.Intent == intent).Select(x => x.Content).ToList();
}
public Agent GetAgent(string agentId)
public string GetAgentTemplate(string agentId, string templateName)
{
var foundAgent = Agents.FirstOrDefault(x => x.Id == agentId);
return foundAgent;
}
var agent = Agents.FirstOrDefault(x => x.Id == agentId);
if (agent == null) return string.Empty;
return agent.Templates?.FirstOrDefault(x => x.Name == templateName.ToLower())?.Content ?? string.Empty;
}
#endregion
#region Conversation
public void CreateNewConversation(Conversation conversation)
{
if (conversation == null) return;
@ -565,7 +591,7 @@ public class MongoRepository : IBotSharpRepository
if (conv == null) return null;
return new Conversation
{
{
Id = conv.Id.ToString(),
AgentId = conv.AgentId.ToString(),
UserId = conv.UserId.ToString(),
@ -601,12 +627,100 @@ public class MongoRepository : IBotSharpRepository
return records;
}
#endregion
public string GetAgentTemplate(string agentId, string templateName)
#region User
public User GetUserByEmail(string email)
{
var agent = Agents.FirstOrDefault(x => x.Id == agentId);
if (agent == null) return string.Empty;
return agent.Templates?.FirstOrDefault(x => x.Name == templateName.ToLower())?.Content ?? string.Empty;
var user = Users.FirstOrDefault(x => x.Email == email);
return user != null ? new User
{
Id = user.Id.ToString(),
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Password = user.Password,
Salt = user.Salt,
ExternalId = user.ExternalId,
CreatedTime = user.CreatedTime,
UpdatedTime = user.UpdatedTime
} : null;
}
public void CreateUser(User user)
{
if (user == null) return;
var userCollection = new UserCollection
{
Id = Guid.NewGuid(),
FirstName = user.FirstName,
LastName = user.LastName,
Salt = user.Salt,
Password = user.Password,
Email = user.Email,
ExternalId = user.ExternalId,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
};
_dc.Users.InsertOne(userCollection);
}
#endregion
#region Routing
public void DeleteRoutingItems()
{
_dc.RoutingItems.DeleteMany(Builders<RoutingItemCollection>.Filter.Empty);
}
public void DeleteRoutingProfiles()
{
_dc.RoutingProfiles.DeleteMany(Builders<RoutingProfileCollection>.Filter.Empty);
}
public List<RoutingItem> CreateRoutingItems(List<RoutingItem> routingItems)
{
var collections = routingItems?.Select(x => new RoutingItemCollection
{
Id = Guid.NewGuid(),
AgentId = Guid.Parse(x.AgentId),
Name = x.Name,
Description = x.Description,
RequiredFields = x.RequiredFields,
RedirectTo = !string.IsNullOrEmpty(x.RedirectTo) ? Guid.Parse(x.RedirectTo) : null,
Disabled = x.Disabled
})?.ToList() ?? new List<RoutingItemCollection>();
_dc.RoutingItems.InsertMany(collections);
return collections.Select(x => new RoutingItem
{
Id = x.Id.ToString(),
AgentId = x.AgentId.ToString(),
Name = x.Name,
Description = x.Description,
RequiredFields = x.RequiredFields,
RedirectTo = x.RedirectTo?.ToString(),
Disabled = x.Disabled
}).ToList();
}
public List<RoutingProfile> CreateRoutingProfiles(List<RoutingProfile> profiles)
{
var collections = profiles?.Select(x => new RoutingProfileCollection
{
Id = Guid.NewGuid(),
Name = x.Name,
AgentIds = x.AgentIds.Select(x => Guid.Parse(x)).ToList()
})?.ToList() ?? new List<RoutingProfileCollection>();
_dc.RoutingProfiles.InsertMany(collections);
return collections.Select(x => new RoutingProfile
{
Id = x.Id.ToString(),
Name = x.Name,
AgentIds = x.AgentIds.Select(x => x.ToString()).ToList()
}).ToList();
}
#endregion
}

View file

@ -3,9 +3,8 @@ 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.Repositories;
global using BotSharp.Abstraction.Repositories.Records;
global using BotSharp.Abstraction.Agents.Enums;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Plugins;
global using Microsoft.Extensions.Configuration;