Merge branch 'SciSharp:master' into master

This commit is contained in:
Haiping 2024-08-15 06:43:02 -05:00 committed by GitHub
commit d4c6039f88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 938 additions and 485 deletions

View file

@ -105,6 +105,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileStorages", "FileStorage
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.TencentCos", "src\Plugins\BotSharp.Plugin.TencentCos\BotSharp.Plugin.TencentCos.csproj", "{BF029B0A-768B-43A1-8D91-E70B95505716}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interpreters", "Interpreters", "{C4C59872-3C8A-450D-83D5-2BE402D610D5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.PythonInterpreter", "src\Plugins\BotSharp.Plugin.PythonInterpreter\BotSharp.Plugin.PythonInterpreter.csproj", "{05E6E405-5021-406E-8A5E-0A7CEC881F6D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -425,6 +429,14 @@ Global
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|Any CPU.Build.0 = Release|Any CPU
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|x64.ActiveCfg = Release|Any CPU
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|x64.Build.0 = Release|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|x64.ActiveCfg = Debug|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Debug|x64.Build.0 = Debug|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|Any CPU.Build.0 = Release|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.ActiveCfg = Release|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -475,6 +487,8 @@ Global
{54E83C6F-54EE-4ADC-8D72-93C009CC4FB4} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{38B37C0D-1930-4D47-BCBF-E358EC1096B1} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
{BF029B0A-768B-43A1-8D91-E70B95505716} = {38B37C0D-1930-4D47-BCBF-E358EC1096B1}
{C4C59872-3C8A-450D-83D5-2BE402D610D5} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
{05E6E405-5021-406E-8A5E-0A7CEC881F6D} = {C4C59872-3C8A-450D-83D5-2BE402D610D5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}

View file

@ -20,6 +20,13 @@ public interface IAgentService
/// <returns></returns>
Task<Agent> LoadAgent(string id);
/// <summary>
/// Inherit from an agent
/// </summary>
/// <param name="agent"></param>
/// <returns></returns>
Task InheritAgent(Agent agent);
string RenderedInstruction(Agent agent);
string RenderedTemplate(Agent agent, string templateName);

View file

@ -1,6 +1,5 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Tasks.Models;
namespace BotSharp.Abstraction.Agents.Models;
@ -21,8 +20,7 @@ public class Agent
/// Default LLM settings
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AgentLlmConfig LlmConfig { get; set; }
= new AgentLlmConfig();
public AgentLlmConfig LlmConfig { get; set; } = new();
/// <summary>
/// Instruction
@ -30,33 +28,35 @@ public class Agent
[JsonIgnore]
public string? Instruction { get; set; }
/// <summary>
/// Channel instructions
/// </summary>
[JsonIgnore]
public List<ChannelInstruction> ChannelInstructions { get; set; } = new();
/// <summary>
/// Templates
/// </summary>
[JsonIgnore]
public List<AgentTemplate> Templates { get; set; }
= new List<AgentTemplate>();
public List<AgentTemplate> Templates { get; set; } = new();
/// <summary>
/// Agent tasks
/// </summary>
[JsonIgnore]
public List<AgentTask> Tasks { get; set; }
= new List<AgentTask>();
public List<AgentTask> Tasks { get; set; } = new();
/// <summary>
/// Samples
/// </summary>
[JsonIgnore]
public List<string> Samples { get; set; }
= new List<string>();
public List<string> Samples { get; set; } = new();
/// <summary>
/// Functions
/// </summary>
[JsonIgnore]
public List<FunctionDef> Functions { get; set; }
= new List<FunctionDef>();
public List<FunctionDef> Functions { get; set; } = new();
/// <summary>
/// Responses
@ -93,23 +93,20 @@ public class Agent
/// <summary>
/// Agent utilities
/// </summary>
public List<string> Utilities { get; set; }
= new List<string>();
public List<string> Utilities { get; set; } = new();
/// <summary>
/// Inherit from agent
/// </summary>
public string? InheritAgentId { get; set; }
public List<RoutingRule> RoutingRules { get; set; }
= new List<RoutingRule>();
public List<RoutingRule> RoutingRules { get; set; } = new();
/// <summary>
/// For rendering deferral
/// </summary>
[JsonIgnore]
public Dictionary<string, object> TemplateDict { get; set; }
= new Dictionary<string, object>();
public Dictionary<string, object> TemplateDict { get; set; } = new();
public override string ToString()
=> $"{Name} {Id}";
@ -124,6 +121,7 @@ public class Agent
Description = agent.Description,
Type = agent.Type,
Instruction = agent.Instruction,
ChannelInstructions = agent.ChannelInstructions,
Functions = agent.Functions,
Responses = agent.Responses,
Samples = agent.Samples,
@ -145,6 +143,12 @@ public class Agent
return this;
}
public Agent SetChannelInstructions(List<ChannelInstruction> instructions)
{
ChannelInstructions = instructions ?? new List<ChannelInstruction>();
return this;
}
public Agent SetTemplates(List<AgentTemplate> templates)
{
Templates = templates ?? new List<AgentTemplate>();

View file

@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.Agents.Models;
public class ChannelInstruction
{
public string Channel { get; set; }
public string Instruction { get; set; }
}

View file

@ -8,7 +8,12 @@ public class FunctionDef
[JsonPropertyName("description")]
public string Description { get; set; } = null!;
[JsonPropertyName("channels")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? Channels { get; set; }
[JsonPropertyName("visibility_expression")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? VisibilityExpression { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

View file

@ -0,0 +1,10 @@
namespace BotSharp.Abstraction.Interpreters.Models;
public class InterpretationRequest
{
[JsonPropertyName("script")]
public string Script { get; set; } = null!;
[JsonPropertyName("language")]
public string Language { get; set; } = null!;
}

View file

@ -2,8 +2,9 @@ namespace BotSharp.Abstraction.Knowledges;
public interface IKnowledgeService
{
Task<IEnumerable<KnowledgeRetrievalResult>> SearchKnowledge(string collectionName, KnowledgeRetrievalOptions options);
Task<IEnumerable<string>> GetKnowledgeCollections();
Task<IEnumerable<KnowledgeSearchResult>> SearchKnowledge(string collectionName, KnowledgeSearchOptions options);
Task FeedKnowledge(string collectionName, KnowledgeCreationModel model);
Task<StringIdPagedItems<KnowledgeCollectionData>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter);
Task<StringIdPagedItems<KnowledgeSearchResult>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter);
Task<bool> DeleteKnowledgeCollectionData(string collectionName, string id);
}

View file

@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Knowledges.Models;
public class KnowledgeCollectionData
{
public string Id { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
public Dictionary<string, string> Data { get; set; } = new();
public double? Score { get; set; }
public float[]? Vector { get; set; }
}
}

View file

@ -2,7 +2,7 @@ using BotSharp.Abstraction.Knowledges.Enums;
namespace BotSharp.Abstraction.Knowledges.Models;
public class KnowledgeRetrievalOptions
public class KnowledgeSearchOptions
{
public string Text { get; set; } = string.Empty;
public IEnumerable<string>? Fields { get; set; } = new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };

View file

@ -1,12 +1,20 @@
namespace BotSharp.Abstraction.Knowledges.Models;
public class KnowledgeSearchResult
public class KnowledgeSearchResult : KnowledgeCollectionData
{
public IDictionary<string, string> Data { get; set; } = new Dictionary<string, string>();
public double Score { get; set; }
public float[]? Vector { get; set; }
}
public KnowledgeSearchResult()
{
}
public class KnowledgeRetrievalResult : KnowledgeSearchResult
{
public static KnowledgeSearchResult CopyFrom(KnowledgeCollectionData data)
{
return new KnowledgeSearchResult
{
Id = data.Id,
Data = data.Data,
Score = data.Score,
Vector = data.Vector
};
}
}

View file

@ -1,8 +1,11 @@
using BotSharp.Abstraction.Knowledges.Enums;
namespace BotSharp.Abstraction.Knowledges.Settings;
public class KnowledgeBaseSettings
{
public string VectorDb { get; set; }
public string DefaultCollection { get; set; } = KnowledgeCollectionName.BotSharp;
public KnowledgeModelSetting TextEmbedding { get; set; }
}

View file

@ -3,11 +3,11 @@ namespace BotSharp.Abstraction.VectorStorage;
public interface IVectorDb
{
string Name { get; }
Task<IEnumerable<string>> GetCollections();
Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter);
Task CreateCollection(string collectionName, int dim);
Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null);
Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector, IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false);
Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector, IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false);
Task<bool> DeleteCollectionData(string collectionName, string id);
}

View file

@ -41,44 +41,52 @@ public partial class AgentService
});
Utilities.ClearCache();
return await Task.FromResult(agentRecord);
}
private Agent FetchAgentFileByName(string agentName, string filePath)
private (string, List<ChannelInstruction>) GetInstructionsFromFile(string fileDir)
{
foreach (var dir in Directory.GetDirectories(filePath))
var defaultInstruction = string.Empty;
var channelInstructions = new List<ChannelInstruction>();
var instructionDir = Path.Combine(fileDir, "instructions");
if (!Directory.Exists(instructionDir))
{
var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
if (agent != null && agent.Name.IsEqualTo(agentName))
{
var functions = FetchFunctionsFromFile(dir);
var instruction = FetchInstructionFromFile(dir);
var responses = FetchResponsesFromFile(dir);
var templates = FetchTemplatesFromFile(dir);
var samples = FetchSamplesFromFile(dir);
return agent.SetInstruction(instruction)
.SetTemplates(templates)
.SetFunctions(functions)
.SetResponses(responses)
.SetSamples(samples);
}
return (defaultInstruction, channelInstructions);
}
return null;
foreach (var file in Directory.GetFiles(instructionDir))
{
var extension = Path.GetExtension(file).Substring(1);
if (!extension.IsEqualTo(_agentSettings.TemplateFormat))
{
continue;
}
var segments = Path.GetFileName(file).Split(".", StringSplitOptions.RemoveEmptyEntries);
if (segments.IsNullOrEmpty() || !segments[0].IsEqualTo("instruction"))
{
continue;
}
if (segments.Length == 2)
{
defaultInstruction = File.ReadAllText(file);
}
else if (segments.Length == 3)
{
var item = new ChannelInstruction
{
Channel = segments[1],
Instruction = File.ReadAllText(file)
};
channelInstructions.Add(item);
}
}
return (defaultInstruction, channelInstructions);
}
private string FetchInstructionFromFile(string fileDir)
{
var file = Path.Combine(fileDir, $"instruction.{_agentSettings.TemplateFormat}");
if (!File.Exists(file)) return null;
var instruction = File.ReadAllText(file);
return instruction;
}
private List<AgentTemplate> FetchTemplatesFromFile(string fileDir)
private List<AgentTemplate> GetTemplatesFromFile(string fileDir)
{
var templates = new List<AgentTemplate>();
var templateDir = Path.Combine(fileDir, "templates");
@ -86,10 +94,10 @@ public partial class AgentService
foreach (var file in Directory.GetFiles(templateDir))
{
var name = Path.GetFileNameWithoutExtension(file);
var extension = Path.GetExtension(file).Substring(1);
if (extension.IsEqualTo(_agentSettings.TemplateFormat))
{
var name = Path.GetFileNameWithoutExtension(file);
var content = File.ReadAllText(file);
templates.Add(new AgentTemplate(name, content));
}
@ -98,7 +106,7 @@ public partial class AgentService
return templates;
}
private List<FunctionDef> FetchFunctionsFromFile(string fileDir)
private List<FunctionDef> GetFunctionsFromFile(string fileDir)
{
var functions = new List<FunctionDef>();
var functionDir = Path.Combine(fileDir, "functions");
@ -125,7 +133,7 @@ public partial class AgentService
return functions;
}
private List<AgentResponse> FetchResponsesFromFile(string fileDir)
private List<AgentResponse> GetResponsesFromFile(string fileDir)
{
var responses = new List<AgentResponse>();
var responseDir = Path.Combine(fileDir, "responses");
@ -143,7 +151,7 @@ public partial class AgentService
return responses;
}
private List<string> FetchSamplesFromFile(string fileDir)
private List<string> GetSamplesFromFile(string fileDir)
{
var file = Path.Combine(fileDir, "samples.txt");
if (!File.Exists(file)) return new List<string>();
@ -152,7 +160,7 @@ public partial class AgentService
return samples?.ToList() ?? new List<string>();
}
private List<AgentTask> FetchTasksFromFile(string fileDir)
private List<AgentTask> GetTasksFromFile(string fileDir)
{
var tasks = new List<AgentTask>();
var taskDir = Path.Combine(fileDir, "tasks");

View file

@ -53,7 +53,29 @@ public partial class AgentService
}
profile.Plugin = GetPlugin(profile.Id);
return profile;
}
public async Task InheritAgent(Agent agent)
{
if (string.IsNullOrWhiteSpace(agent?.InheritAgentId)) return;
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 (string.IsNullOrWhiteSpace(agent.Instruction))
{
agent.Instruction = inheritedAgent.Instruction;
}
}
}

View file

@ -34,31 +34,12 @@ public partial class AgentService
return null;
}
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;
}
}
await InheritAgent(agent);
OverrideInstructionByChannel(agent);
AddOrUpdateParameters(agent);
agent.TemplateDict = new Dictionary<string, object>();
// Populate state into dictionary
agent.TemplateDict = new Dictionary<string, object>();
PopulateState(agent.TemplateDict);
// After agent is loaded
@ -94,6 +75,23 @@ public partial class AgentService
return agent;
}
private void OverrideInstructionByChannel(Agent agent)
{
var instructions = agent.ChannelInstructions;
if (instructions.IsNullOrEmpty()) return;
var state = _services.GetRequiredService<IConversationStateService>();
var channel = state.GetState("channel");
if (string.IsNullOrWhiteSpace(channel))
{
return;
}
var found = instructions.FirstOrDefault(x => x.Channel.IsEqualTo(channel));
agent.Instruction = !string.IsNullOrWhiteSpace(found?.Instruction) ? found.Instruction : agent.Instruction;
}
private void PopulateState(Dictionary<string, object> dict)
{
var conv = _services.GetRequiredService<IConversationService>();
@ -114,18 +112,27 @@ public partial class AgentService
private void AddOrUpdateRoutesParameters(string agentId, List<RoutingRule> routingRules)
{
if(!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes)) parameterTypes = new();
if(!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
{
parameterTypes = new();
}
foreach (var rule in routingRules.Where(x => x.Required))
{
if (string.IsNullOrEmpty(rule.FieldType)) continue;
parameterTypes.TryAdd(rule.Field, rule.FieldType);
}
AgentParameterTypes.TryAdd(agentId, parameterTypes);
}
private void AddOrUpdateFunctionsParameters(string agentId, List<FunctionDef> functions)
{
if (!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes)) parameterTypes = new();
if (!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
{
parameterTypes = new();
}
var parameters = functions.Select(p => p.Parameters);
foreach (var param in parameters)
{
@ -139,6 +146,7 @@ public partial class AgentService
}
}
}
AgentParameterTypes.TryAdd(agentId, parameterTypes);
}
}

View file

@ -42,19 +42,20 @@ public partial class AgentService
continue;
}
var functions = FetchFunctionsFromFile(dir);
var instruction = FetchInstructionFromFile(dir);
var responses = FetchResponsesFromFile(dir);
var templates = FetchTemplatesFromFile(dir);
var samples = FetchSamplesFromFile(dir);
agent.SetInstruction(instruction)
var (defaultInstruction, channelInstructions) = GetInstructionsFromFile(dir);
var functions = GetFunctionsFromFile(dir);
var responses = GetResponsesFromFile(dir);
var templates = GetTemplatesFromFile(dir);
var samples = GetSamplesFromFile(dir);
agent.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetTemplates(templates)
.SetFunctions(functions)
.SetResponses(responses)
.SetSamples(samples);
var userAgent = BuildUserAgent(agent.Id, user.Id);
var tasks = FetchTasksFromFile(dir);
var tasks = GetTasksFromFile(dir);
var isAgentDeleted = _db.DeleteAgent(agent.Id);
if (isAgentDeleted)

View file

@ -20,17 +20,32 @@ public partial class AgentService
public bool RenderFunction(Agent agent, FunctionDef def)
{
if (!string.IsNullOrEmpty(def.VisibilityExpression))
var isRender = true;
var channels = def.Channels;
if (channels != null)
{
var state = _services.GetRequiredService<IConversationStateService>();
var channel = state.GetState("channel");
if (!string.IsNullOrWhiteSpace(channel))
{
isRender = isRender && channels.Contains(channel);
}
}
if (!isRender) return false;
if (!string.IsNullOrWhiteSpace(def.VisibilityExpression))
{
var render = _services.GetRequiredService<ITemplateRender>();
var result = render.Render(def.VisibilityExpression, new Dictionary<string, object>
{
{ "states", agent.TemplateDict }
});
return result == "visible";
isRender = isRender && result == "visible";
}
return true;
return isRender;
}
public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def)

View file

@ -28,6 +28,7 @@ public partial class AgentService
record.Profiles = agent.Profiles ?? new List<string>();
record.RoutingRules = agent.RoutingRules ?? new List<RoutingRule>();
record.Instruction = agent.Instruction ?? string.Empty;
record.ChannelInstructions = agent.ChannelInstructions ?? new List<ChannelInstruction>();
record.Functions = agent.Functions ?? new List<FunctionDef>();
record.Templates = agent.Templates ?? new List<AgentTemplate>();
record.Responses = agent.Responses ?? new List<AgentResponse>();
@ -41,7 +42,6 @@ public partial class AgentService
_db.UpdateAgent(record, updateField);
Utilities.ClearCache();
await Task.CompletedTask;
}
@ -71,7 +71,7 @@ public partial class AgentService
agentSettings.DataDir);
var clonedAgent = Agent.Clone(agent);
var foundAgent = FetchAgentFileById(agent.Id, filePath);
var foundAgent = GetAgentFileById(agent.Id, filePath);
if (foundAgent == null)
{
updateResult = $"Cannot find agent {agent.Name} in file directory: {filePath}";
@ -90,6 +90,7 @@ public partial class AgentService
.SetProfiles(foundAgent.Profiles)
.SetRoutingRules(foundAgent.RoutingRules)
.SetInstruction(foundAgent.Instruction)
.SetChannelInstructions(foundAgent.ChannelInstructions)
.SetTemplates(foundAgent.Templates)
.SetFunctions(foundAgent.Functions)
.SetResponses(foundAgent.Responses)
@ -165,7 +166,7 @@ public partial class AgentService
return patchResult;
}
private Agent? FetchAgentFileById(string agentId, string filePath)
private Agent? GetAgentFileById(string agentId, string filePath)
{
if (!Directory.Exists(filePath)) return null;
@ -175,12 +176,13 @@ public partial class AgentService
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
if (agent != null && agent.Id == agentId)
{
var functions = FetchFunctionsFromFile(dir);
var instruction = FetchInstructionFromFile(dir);
var responses = FetchResponsesFromFile(dir);
var templates = FetchTemplatesFromFile(dir);
var samples = FetchSamplesFromFile(dir);
return agent.SetInstruction(instruction)
var (defaultInstruction, channelInstructions) = GetInstructionsFromFile(dir);
var functions = GetFunctionsFromFile(dir);
var responses = GetResponsesFromFile(dir);
var templates = GetTemplatesFromFile(dir);
var samples = GetSamplesFromFile(dir);
return agent.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetTemplates(templates)
.SetFunctions(functions)
.SetResponses(responses)

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
@ -56,17 +56,17 @@
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instruction.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid" />
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions.json" />
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions\human_intervention_needed.json" />
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instruction.liquid" />
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instructions\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\agent.json" />
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instructions\instruction.liquid" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instruction.liquid" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instructions\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\instructions\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\.welcome.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\conversation.summary.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.hf.liquid" />
@ -80,7 +80,7 @@
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid" />
<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\instructions\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" />
@ -90,7 +90,7 @@
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instruction.liquid">
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions\human_intervention_needed.json">
@ -99,19 +99,19 @@
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instruction.liquid">
<Content Include="data\agents\01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instruction.liquid">
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instruction.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.two_stage.1st.plan.liquid">
@ -147,7 +147,7 @@
<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">
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid">
@ -162,7 +162,7 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instruction.liquid">
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid">

View file

@ -6,19 +6,21 @@ namespace BotSharp.Core.Files.Services
{
public async Task SaveSpeechFileAsync(string conversationId, string fileName, BinaryData data)
{
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, TEXT_TO_SPEECH_FOLDER, conversationId);
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, TEXT_TO_SPEECH_FOLDER);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
using var file = File.Create(Path.Combine(dir, fileName));
var filePath = Path.Combine(dir, fileName);
if (File.Exists(filePath)) return;
using var file = File.Create(filePath);
using var input = data.ToStream();
await input.CopyToAsync(file);
}
public async Task<BinaryData> RetrieveSpeechFileAsync(string conversationId, string fileName)
{
var path = Path.Combine(_baseDir, CONVERSATION_FOLDER, TEXT_TO_SPEECH_FOLDER, conversationId, fileName);
var path = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, TEXT_TO_SPEECH_FOLDER, fileName);
using var file = new FileStream(path, FileMode.Open, FileAccess.Read);
return await BinaryData.FromStreamAsync(file);
}

View file

@ -31,6 +31,7 @@ public class SettingService : ISettingService
var plugins = pluginService.GetPlugins(_services);
var plugin = plugins.First(x => x.Module.Settings.Name == settingName);
var instance = plugin.Module.GetNewSettingsInstance();
_config.Bind(settingName, instance);
if (mask)
{

View file

@ -1,6 +1,6 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing.Models;
using System.IO;
using System.Threading;
namespace BotSharp.Core.Repository
{
@ -37,7 +37,7 @@ namespace BotSharp.Core.Repository
UpdateAgentRoutingRules(agent.Id, agent.RoutingRules);
break;
case AgentField.Instruction:
UpdateAgentInstruction(agent.Id, agent.Instruction);
UpdateAgentInstructions(agent.Id, agent.Instruction, agent.ChannelInstructions);
break;
case AgentField.Function:
UpdateAgentFunctions(agent.Id, agent.Functions);
@ -175,17 +175,30 @@ namespace BotSharp.Core.Repository
File.WriteAllText(agentFile, json);
}
private void UpdateAgentInstruction(string agentId, string instruction)
private void UpdateAgentInstructions(string agentId, string instruction, List<ChannelInstruction> channelInstructions)
{
if (string.IsNullOrWhiteSpace(instruction)) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var instructionFile = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir,
agentId, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
var instructionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_INSTRUCTIONS_FOLDER);
DeleteBeforeCreateDirectory(instructionDir);
File.WriteAllText(instructionFile, instruction);
// Save default instructions
var instructionFile = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
File.WriteAllText(instructionFile, instruction ?? string.Empty);
Thread.Sleep(100);
// Save channel instructions
foreach (var ci in channelInstructions)
{
if (string.IsNullOrWhiteSpace(ci.Channel)) continue;
var file = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{ci.Channel}.{_agentSettings.TemplateFormat}");
File.WriteAllText(file, ci.Instruction ?? string.Empty);
Thread.Sleep(100);
}
}
private void UpdateAgentFunctions(string agentId, List<FunctionDef> inputFunctions)
@ -195,14 +208,8 @@ namespace BotSharp.Core.Repository
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var functionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir,
agentId, AGENT_FUNCTIONS_FOLDER);
if (Directory.Exists(functionDir))
{
Directory.Delete(functionDir, true);
}
Directory.CreateDirectory(functionDir);
var functionDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_FUNCTIONS_FOLDER);
DeleteBeforeCreateDirectory(functionDir);
foreach (var func in inputFunctions)
{
@ -211,7 +218,7 @@ namespace BotSharp.Core.Repository
var text = JsonSerializer.Serialize(func, _options);
var file = Path.Combine(functionDir, $"{func.Name}.json");
File.WriteAllText(file, text);
Thread.Sleep(200);
Thread.Sleep(100);
}
}
@ -223,16 +230,7 @@ namespace BotSharp.Core.Repository
if (agent == null) return;
var templateDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_TEMPLATES_FOLDER);
if (!Directory.Exists(templateDir))
{
Directory.CreateDirectory(templateDir);
}
foreach (var file in Directory.GetFiles(templateDir))
{
File.Delete(file);
}
DeleteBeforeCreateDirectory(templateDir);
foreach (var template in templates)
{
@ -249,15 +247,7 @@ namespace BotSharp.Core.Repository
if (agent == null) return;
var responseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_RESPONSES_FOLDER);
if (!Directory.Exists(responseDir))
{
Directory.CreateDirectory(responseDir);
}
foreach (var file in Directory.GetFiles(responseDir))
{
File.Delete(file);
}
DeleteBeforeCreateDirectory(responseDir);
for (int i = 0; i < responses.Count; i++)
{
@ -308,7 +298,7 @@ namespace BotSharp.Core.Repository
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
UpdateAgentInstruction(inputAgent.Id, inputAgent.Instruction);
UpdateAgentInstructions(inputAgent.Id, inputAgent.Instruction, inputAgent.ChannelInstructions);
UpdateAgentResponses(inputAgent.Id, inputAgent.Responses);
UpdateAgentTemplates(inputAgent.Id, inputAgent.Templates);
UpdateAgentFunctions(inputAgent.Id, inputAgent.Functions);
@ -348,12 +338,13 @@ namespace BotSharp.Core.Repository
var record = JsonSerializer.Deserialize<Agent>(json, _options);
if (record == null) return null;
var instruction = FetchInstruction(dir);
var (defaultInstruction, channelInstructions) = FetchInstructions(dir);
var functions = FetchFunctions(dir);
var samples = FetchSamples(dir);
var templates = FetchTemplates(dir);
var responses = FetchResponses(dir);
return record.SetInstruction(instruction)
return record.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetFunctions(functions)
.SetTemplates(templates)
.SetSamples(samples)
@ -451,13 +442,9 @@ namespace BotSharp.Core.Repository
return true;
}
public void BulkInsertAgents(List<Agent> agents)
{
}
public void BulkInsertAgents(List<Agent> agents) { }
public void BulkInsertUserAgents(List<UserAgent> userAgents)
{
}
public void BulkInsertUserAgents(List<UserAgent> userAgents) { }
public bool DeleteAgents()
{

View file

@ -34,6 +34,7 @@ public partial class FileRepository : IBotSharpRepository
private const string AGENT_TASK_PREFIX = "#metadata";
private const string AGENT_TASK_SUFFIX = "/metadata";
private const string TRANSLATION_MEMORY_FILE = "memory.json";
private const string AGENT_INSTRUCTIONS_FOLDER = "instructions";
private const string AGENT_FUNCTIONS_FOLDER = "functions";
private const string AGENT_TEMPLATES_FOLDER = "templates";
private const string AGENT_RESPONSES_FOLDER = "responses";
@ -123,7 +124,9 @@ public partial class FileRepository : IBotSharpRepository
var agent = JsonSerializer.Deserialize<Agent>(json, _options);
if (agent != null)
{
agent = agent.SetInstruction(FetchInstruction(d))
var (defaultInstruction, channelInstructions) = FetchInstructions(d);
agent = agent.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetFunctions(FetchFunctions(d))
.SetTemplates(FetchTemplates(d))
.SetResponses(FetchResponses(d))
@ -165,6 +168,17 @@ public partial class FileRepository : IBotSharpRepository
#region Private methods
private void DeleteBeforeCreateDirectory(string dir)
{
if (string.IsNullOrWhiteSpace(dir)) return;
if (Directory.Exists(dir))
{
Directory.Delete(dir, true);
}
Directory.CreateDirectory(dir);
}
private string GetAgentDataDir(string agentId)
{
var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId);
@ -186,13 +200,46 @@ public partial class FileRepository : IBotSharpRepository
return (agent, agentFile);
}
private string? FetchInstruction(string fileDir)
private (string, List<ChannelInstruction>) FetchInstructions(string fileDir)
{
var file = Path.Combine(fileDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
if (!File.Exists(file)) return null;
var defaultInstruction = string.Empty;
var channelInstructions = new List<ChannelInstruction>();
var instruction = File.ReadAllText(file);
return instruction;
var instructionDir = Path.Combine(fileDir, AGENT_INSTRUCTIONS_FOLDER);
if (!Directory.Exists(instructionDir))
{
return (defaultInstruction, channelInstructions);
}
foreach (var file in Directory.GetFiles(instructionDir))
{
var extension = Path.GetExtension(file).Substring(1);
if (!extension.IsEqualTo(_agentSettings.TemplateFormat))
{
continue;
}
var segments = Path.GetFileName(file).Split(".", StringSplitOptions.RemoveEmptyEntries);
if (segments.IsNullOrEmpty() || !segments[0].IsEqualTo(AGENT_INSTRUCTION_FILE))
{
continue;
}
if (segments.Length == 2)
{
defaultInstruction = File.ReadAllText(file);
}
else if (segments.Length == 3)
{
var item = new ChannelInstruction
{
Channel = segments[1],
Instruction = File.ReadAllText(file)
};
channelInstructions.Add(item);
}
}
return (defaultInstruction, channelInstructions);
}
private List<FunctionDef> FetchFunctions(string fileDir)
@ -298,13 +345,14 @@ public partial class FileRepository : IBotSharpRepository
var agent = JsonSerializer.Deserialize<Agent>(agentJson, _options);
if (agent == null) return null;
var instruction = FetchInstruction(agentDir);
var (defaultInstruction, channelInstructions) = FetchInstructions(agentDir);
var functions = FetchFunctions(agentDir);
var samples = FetchSamples(agentDir);
var templates = FetchTemplates(agentDir);
var responses = FetchResponses(agentDir);
return agent.SetInstruction(instruction)
return agent.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetFunctions(functions)
.SetTemplates(templates)
.SetSamples(samples)

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.OpenAPI.Controllers;
@ -28,17 +27,24 @@ public class AgentController : ControllerBase
[HttpGet("/agent/{id}")]
public async Task<AgentViewModel?> GetAgent([FromRoute] string id)
{
var agents = await GetAgents(new AgentFilter
var pagedAgents = await _agentService.GetAgents(new AgentFilter
{
AgentIds = new List<string> { id }
}, useHook: true);
});
var targetAgent = agents.Items.FirstOrDefault();
if (targetAgent == null) return null;
var foundAgent = pagedAgents.Items.FirstOrDefault();
if (foundAgent == null) return null;
await _agentService.InheritAgent(foundAgent);
var targetAgent = AgentViewModel.FromAgent(foundAgent);
var agentSetting = _services.GetRequiredService<AgentSettings>();
targetAgent.IsHost = targetAgent.Id == agentSetting.HostAgentId;
var redirectAgentIds = targetAgent.RoutingRules
.Where(x => !string.IsNullOrEmpty(x.RedirectTo))
.Select(x => x.RedirectTo).ToList();
.Select(x => x.RedirectTo)
.ToList();
var redirectAgents = await _agentService.GetAgents(new AgentFilter
{
AgentIds = redirectAgentIds
@ -65,39 +71,16 @@ public class AgentController : ControllerBase
}
[HttpGet("/agents")]
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter, [FromQuery] bool useHook = false)
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
{
var agentSetting = _services.GetRequiredService<AgentSettings>();
var pagedAgents = await _agentService.GetAgents(filter);
var items = new List<Agent>();
var agents = new List<AgentViewModel>();
if (useHook)
{
// prerender agent
foreach (var agent in pagedAgents.Items)
{
var renderedAgent = await _agentService.LoadAgent(agent.Id);
items.Add(renderedAgent);
}
// Set IsHost
agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
foreach (var agent in agents)
{
agent.IsHost = agentSetting.HostAgentId == agent.Id;
}
}
else
{
items = pagedAgents.Items.ToList();
agents = items.Select(x => AgentViewModel.FromAgent(x)).ToList();
}
var agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? new List<AgentViewModel>();
return new PagedItems<AgentViewModel>
{
Items = agents,
Count = pagedAgents.Count
Count = pagedAgents?.Count ?? 0
};
}

View file

@ -16,30 +16,36 @@ public class KnowledgeBaseController : ControllerBase
_services = services;
}
[HttpPost("/knowledge/{collection}/search")]
public async Task<IEnumerable<KnowledgeRetrivalViewModel>> SearchKnowledge([FromRoute] string collection, [FromBody] SearchKnowledgeModel model)
[HttpGet("knowledge/collections")]
public async Task<IEnumerable<string>> GetKnowledgeCollections()
{
var options = new KnowledgeRetrievalOptions
return await _knowledgeService.GetKnowledgeCollections();
}
[HttpPost("/knowledge/{collection}/search")]
public async Task<IEnumerable<KnowledgeSearchResultViewModel>> SearchKnowledge([FromRoute] string collection, [FromBody] SearchKnowledgeRequest request)
{
var options = new KnowledgeSearchOptions
{
Text = model.Text,
Fields = model.Fields,
Limit = model.Limit ?? 5,
Confidence = model.Confidence ?? 0.5f,
WithVector = model.WithVector
Text = request.Text,
Fields = request.Fields,
Limit = request.Limit ?? 5,
Confidence = request.Confidence ?? 0.5f,
WithVector = request.WithVector
};
var results = await _knowledgeService.SearchKnowledge(collection, options);
return results.Select(x => KnowledgeRetrivalViewModel.From(x)).ToList();
return results.Select(x => KnowledgeSearchResultViewModel.From(x)).ToList();
}
[HttpPost("/knowledge/{collection}/data")]
public async Task<StringIdPagedItems<KnowledgeCollectionDataViewModel>> GetKnowledgeCollectionData([FromRoute] string collection, [FromBody] KnowledgeFilter filter)
public async Task<StringIdPagedItems<KnowledgeSearchResultViewModel>> GetKnowledgeCollectionData([FromRoute] string collection, [FromBody] KnowledgeFilter filter)
{
var data = await _knowledgeService.GetKnowledgeCollectionData(collection, filter);
var items = data.Items?.Select(x => KnowledgeCollectionDataViewModel.From(x))?
.ToList() ?? new List<KnowledgeCollectionDataViewModel>();
var items = data.Items?.Select(x => KnowledgeSearchResultViewModel.From(x))?
.ToList() ?? new List<KnowledgeSearchResultViewModel>();
return new StringIdPagedItems<KnowledgeCollectionDataViewModel>
return new StringIdPagedItems<KnowledgeSearchResultViewModel>
{
Count = data.Count,
NextId = data.NextId,

View file

@ -1,7 +1,6 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Core.Infrastructures;
namespace BotSharp.OpenAPI.ViewModels.Agents;
@ -16,21 +15,26 @@ public class AgentCreationModel
/// </summary>
public string Instruction { get; set; } = string.Empty;
/// <summary>
///
/// </summary>
public List<ChannelInstruction> ChannelInstructions { get; set; } = new();
/// <summary>
/// LLM extensible Instructions in addition to the default Instructions
/// </summary>
public List<AgentTemplate> Templates { get; set; } = new List<AgentTemplate>();
public List<AgentTemplate> Templates { get; set; } = new();
/// <summary>
/// LLM callable function definition
/// </summary>
public List<FunctionDef> Functions { get; set; } = new List<FunctionDef>();
public List<FunctionDef> Functions { get; set; } = new();
/// <summary>
/// Response template
/// </summary>
public List<AgentResponse> Responses { get; set; } = new List<AgentResponse>();
public List<string> Samples { get; set; } = new List<string>();
public List<AgentResponse> Responses { get; set; } = new();
public List<string> Samples { get; set; } = new();
public bool IsPublic { get; set; }
@ -43,9 +47,9 @@ public class AgentCreationModel
/// <summary>
/// Combine different Agents together to form a Profile.
/// </summary>
public List<string> Profiles { get; set; } = new List<string>();
public List<string> Utilities { get; set; } = new List<string>();
public List<RoutingRuleUpdateModel> RoutingRules { get; set; } = new List<RoutingRuleUpdateModel>();
public List<string> Profiles { get; set; } = new();
public List<string> Utilities { get; set; } = new();
public List<RoutingRuleUpdateModel> RoutingRules { get; set; } = new();
public AgentLlmConfig? LlmConfig { get; set; }
public Agent ToAgent()
@ -55,6 +59,7 @@ public class AgentCreationModel
Name = Name,
Description = Description,
Instruction = Instruction,
ChannelInstructions = ChannelInstructions,
Templates = Templates,
Functions = Functions,
Responses = Responses,
@ -64,9 +69,7 @@ public class AgentCreationModel
Type = Type,
Disabled = Disabled,
Profiles = Profiles,
RoutingRules = RoutingRules?
.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?
.ToList() ?? new List<RoutingRule>(),
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
LlmConfig = LlmConfig
};
}

View file

@ -15,6 +15,12 @@ public class AgentUpdateModel
/// </summary>
public string Instruction { get; set; } = string.Empty;
/// <summary>
/// Channel instructions
/// </summary>
[JsonPropertyName("channel_instructions")]
public List<ChannelInstruction>? ChannelInstructions { get; set; }
/// <summary>
/// Templates
/// </summary>
@ -39,11 +45,11 @@ public class AgentUpdateModel
/// Routes
/// </summary>
public List<AgentResponse>? Responses { get; set; }
[JsonPropertyName("is_public")]
public bool IsPublic { get; set; }
[JsonPropertyName("allow_routing")]
[JsonPropertyName("allow_routing")]
public bool AllowRouting { get; set; }
public bool Disabled { get; set; }
@ -52,8 +58,8 @@ public class AgentUpdateModel
/// Profile by channel
/// </summary>
public List<string>? Profiles { get; set; }
[JsonPropertyName("routing_rules")]
[JsonPropertyName("routing_rules")]
public List<RoutingRuleUpdateModel>? RoutingRules { get; set; }
[JsonPropertyName("llm_config")]
@ -69,10 +75,9 @@ public class AgentUpdateModel
Disabled = Disabled,
Type = Type,
Profiles = Profiles ?? new List<string>(),
RoutingRules = RoutingRules?
.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?
.ToList() ?? new List<RoutingRule>(),
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
Instruction = Instruction ?? string.Empty,
ChannelInstructions = ChannelInstructions ?? new List<ChannelInstruction>(),
Templates = Templates ?? new List<AgentTemplate>(),
Functions = Functions ?? new List<FunctionDef>(),
Responses = Responses ?? new List<AgentResponse>(),

View file

@ -13,6 +13,9 @@ public class AgentViewModel
public string Description { get; set; }
public string Type { get; set; } = AgentType.Task;
public string Instruction { get; set; }
[JsonPropertyName("channel_instructions")]
public List<ChannelInstruction> ChannelInstructions { get; set; }
public List<AgentTemplate> Templates { get; set; }
public List<FunctionDef> Functions { get; set; }
public List<AgentResponse> Responses { get; set; }
@ -60,6 +63,7 @@ public class AgentViewModel
Description = agent.Description,
Type = agent.Type,
Instruction = agent.Instruction,
ChannelInstructions = agent.ChannelInstructions,
Templates = agent.Templates,
Functions = agent.Functions,
Responses = agent.Responses,

View file

@ -1,33 +0,0 @@
using BotSharp.Abstraction.Knowledges.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class KnowledgeCollectionDataViewModel
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("question")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Question { get; set; }
[JsonPropertyName("answer")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Answer { get; set; }
[JsonPropertyName("vector")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public float[]? Vector { get; set; }
public static KnowledgeCollectionDataViewModel From(KnowledgeCollectionData data)
{
return new KnowledgeCollectionDataViewModel
{
Id = data.Id,
Question = data.Question,
Answer = data.Answer,
Vector = data.Vector
};
}
}

View file

@ -1,27 +0,0 @@
using BotSharp.Abstraction.Knowledges.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class KnowledgeRetrivalViewModel
{
[JsonPropertyName("data")]
public IDictionary<string, string> Data { get; set; }
[JsonPropertyName("score")]
public double Score { get; set; }
[JsonPropertyName("vector")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public float[]? Vector { get; set; }
public static KnowledgeRetrivalViewModel From(KnowledgeRetrievalResult model)
{
return new KnowledgeRetrivalViewModel
{
Data = model.Data,
Score = model.Score,
Vector = model.Vector
};
}
}

View file

@ -0,0 +1,33 @@
using BotSharp.Abstraction.Knowledges.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class KnowledgeSearchResultViewModel
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("data")]
public IDictionary<string, string> Data { get; set; }
[JsonPropertyName("score")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public double? Score { get; set; }
[JsonPropertyName("vector")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public float[]? Vector { get; set; }
public static KnowledgeSearchResultViewModel From(KnowledgeSearchResult result)
{
return new KnowledgeSearchResultViewModel
{
Id = result.Id,
Data = result.Data,
Score = result.Score,
Vector = result.Vector
};
}
}

View file

@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class SearchKnowledgeModel
public class SearchKnowledgeRequest
{
[JsonPropertyName("text")]
public string Text { get; set; } = string.Empty;

View file

@ -15,14 +15,14 @@
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\handle_http_request.fn.liquid" />
<None Remove="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\agent.json" />
<None Remove="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\functions.json" />
<None Remove="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\instruction.liquid" />
<None Remove="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\instructions\instruction.liquid" />
</ItemGroup>
<ItemGroup>
<Content Include="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\instruction.liquid">
<Content Include="data\agents\87c458fc-ec5f-40ae-8ed6-05dda8a07523\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\handle_http_request.json">

View file

@ -20,7 +20,7 @@
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\agent.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\confirm_knowledge_persistence.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\memorize_knowledge.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instruction.liquid" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instructions\instruction.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\knowledge_retrieval.fn.liquid" />
</ItemGroup>
@ -34,7 +34,7 @@
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\memorize_knowledge.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instruction.liquid">
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\knowledge_retrieval.json">

View file

@ -21,8 +21,9 @@ public class KnowledgeRetrievalFn : IFunctionCallback
embedding.SetModelName(_settings.TextEmbedding.Model);
var vector = await embedding.GetVectorAsync(args.Question);
var vectorDb = _services.GetRequiredService<IVectorDb>();
var knowledges = await vectorDb.Search(KnowledgeCollectionName.BotSharp, vector, new List<string> { KnowledgePayloadName.Answer });
var vectorDb = _services.GetServices<IVectorDb>().FirstOrDefault(x => x.Name == _settings.VectorDb);
var collectionName = !string.IsNullOrWhiteSpace(_settings.DefaultCollection) ? _settings.DefaultCollection : KnowledgeCollectionName.BotSharp;
var knowledges = await vectorDb.Search(collectionName, vector, new List<string> { KnowledgePayloadName.Answer });
if (!knowledges.IsNullOrEmpty())
{

View file

@ -25,11 +25,12 @@ public class MemorizeKnowledgeFn : IFunctionCallback
args.Question
});
var vectorDb = _services.GetRequiredService<IVectorDb>();
await vectorDb.CreateCollection(KnowledgeCollectionName.BotSharp, vector[0].Length);
var vectorDb = _services.GetServices<IVectorDb>().FirstOrDefault(x => x.Name == _settings.VectorDb);
var collectionName = !string.IsNullOrWhiteSpace(_settings.DefaultCollection) ? _settings.DefaultCollection : KnowledgeCollectionName.BotSharp;
await vectorDb.CreateCollection(collectionName, vector[0].Length);
var id = Guid.NewGuid().ToString();
var result = await vectorDb.Upsert(KnowledgeCollectionName.BotSharp, id, vector[0],
var result = await vectorDb.Upsert(collectionName, id, vector[0],
args.Question,
new Dictionary<string, string>
{

View file

@ -27,12 +27,12 @@ public class MemoryVectorDb : IVectorDb
throw new NotImplementedException();
}
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
public async Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
{
if (!_vectors.ContainsKey(collectionName))
{
return new List<KnowledgeSearchResult>();
return new List<KnowledgeCollectionData>();
}
var similarities = VectorUtility.CalCosineSimilarity(vector, _vectors[collectionName]);
@ -41,7 +41,7 @@ public class MemoryVectorDb : IVectorDb
var results = np.argsort(similarities).ToArray<int>()
.Reverse()
.Take(limit)
.Select(i => new KnowledgeSearchResult
.Select(i => new KnowledgeCollectionData
{
Data = new Dictionary<string, string> { { "text", _vectors[collectionName][i].Text } },
Score = similarities[i],
@ -64,8 +64,8 @@ public class MemoryVectorDb : IVectorDb
return true;
}
public Task<bool> DeleteCollectionData(string collectionName, string id)
public async Task<bool> DeleteCollectionData(string collectionName, string id)
{
throw new NotImplementedException();
return await Task.FromResult(false);
}
}

View file

@ -2,36 +2,58 @@ namespace BotSharp.Plugin.KnowledgeBase.Services;
public partial class KnowledgeService
{
public async Task<StringIdPagedItems<KnowledgeCollectionData>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter)
public async Task<IEnumerable<string>> GetKnowledgeCollections()
{
try
{
var db = GetVectorDb();
return await db.GetCollectionData(collectionName, filter);
return await db.GetCollections();
}
catch (Exception ex)
{
_logger.LogWarning($"Error when getting knowledge collections. {ex.Message}\r\n{ex.InnerException}");
return Enumerable.Empty<string>();
}
}
public async Task<StringIdPagedItems<KnowledgeSearchResult>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter)
{
try
{
var db = GetVectorDb();
var pagedResult = await db.GetCollectionData(collectionName, filter);
return new StringIdPagedItems<KnowledgeSearchResult>
{
Count = pagedResult.Count,
Items = pagedResult.Items.Select(x => KnowledgeSearchResult.CopyFrom(x)),
NextId = pagedResult.NextId,
};
}
catch (Exception ex)
{
_logger.LogWarning($"Error when getting knowledge collection data ({collectionName}). {ex.Message}\r\n{ex.InnerException}");
return new StringIdPagedItems<KnowledgeCollectionData>();
return new StringIdPagedItems<KnowledgeSearchResult>();
}
}
public async Task<IEnumerable<KnowledgeRetrievalResult>> SearchKnowledge(string collectionName, KnowledgeRetrievalOptions options)
public async Task<IEnumerable<KnowledgeSearchResult>> SearchKnowledge(string collectionName, KnowledgeSearchOptions options)
{
var textEmbedding = GetTextEmbedding();
var vector = await textEmbedding.GetVectorAsync(options.Text);
// Vector search
var db = GetVectorDb();
var fields = !options.Fields.IsNullOrEmpty() ? options.Fields : new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
var found = await db.Search(collectionName, vector, fields, limit: options.Limit ?? 5, confidence: options.Confidence ?? 0.5f, withVector: options.WithVector);
var results = found.Select(x => new KnowledgeRetrievalResult
try
{
Data = x.Data,
Score = x.Score,
Vector = x.Vector
}).ToList();
return results;
var textEmbedding = GetTextEmbedding();
var vector = await textEmbedding.GetVectorAsync(options.Text);
// Vector search
var db = GetVectorDb();
var found = await db.Search(collectionName, vector, options.Fields, limit: options.Limit ?? 5, confidence: options.Confidence ?? 0.5f, withVector: options.WithVector);
var results = found.Select(x => KnowledgeSearchResult.CopyFrom(x)).ToList();
return results;
}
catch (Exception ex)
{
_logger.LogWarning($"Error when searching knowledge ({collectionName}). {ex.Message}\r\n{ex.InnerException}");
return new List<KnowledgeSearchResult>();
}
}
}

View file

@ -26,8 +26,8 @@ public class FaissDb : IVectorDb
throw new NotImplementedException();
}
public Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
IEnumerable<string> fields, int limit = 10, float confidence = 0.5f, bool withVector = false)
public Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 10, float confidence = 0.5f, bool withVector = false)
{
throw new NotImplementedException();
}

View file

@ -8,6 +8,7 @@ public class AgentDocument : MongoBase
public string? InheritAgentId { get; set; }
public string? IconUrl { get; set; }
public string Instruction { get; set; }
public List<ChannelInstructionMongoElement> ChannelInstructions { get; set; }
public List<AgentTemplateMongoElement> Templates { get; set; }
public List<FunctionDefMongoElement> Functions { get; set; }
public List<AgentResponseMongoElement> Responses { get; set; }

View file

@ -0,0 +1,27 @@
using BotSharp.Abstraction.Agents.Models;
namespace BotSharp.Plugin.MongoStorage.Models;
public class ChannelInstructionMongoElement
{
public string Channel { get; set; }
public string Instruction { get; set; }
public static ChannelInstructionMongoElement ToMongoElement(ChannelInstruction instruction)
{
return new ChannelInstructionMongoElement
{
Channel = instruction.Channel,
Instruction = instruction.Instruction
};
}
public static ChannelInstruction ToDomainElement(ChannelInstructionMongoElement instruction)
{
return new ChannelInstruction
{
Channel = instruction.Channel,
Instruction = instruction.Instruction
};
}
}

View file

@ -8,6 +8,7 @@ public class FunctionDefMongoElement
{
public string Name { get; set; }
public string Description { get; set; }
public List<string>? Channels { get; set; }
public string? VisibilityExpression { get; set; }
public string? Impact { get; set; }
public FunctionParametersDefMongoElement Parameters { get; set; } = new FunctionParametersDefMongoElement();
@ -23,6 +24,7 @@ public class FunctionDefMongoElement
{
Name = function.Name,
Description = function.Description,
Channels = function.Channels,
VisibilityExpression = function.VisibilityExpression,
Impact = function.Impact,
Parameters = new FunctionParametersDefMongoElement
@ -34,19 +36,20 @@ public class FunctionDefMongoElement
};
}
public static FunctionDef ToDomainElement(FunctionDefMongoElement mongoFunction)
public static FunctionDef ToDomainElement(FunctionDefMongoElement function)
{
return new FunctionDef
{
Name = mongoFunction.Name,
Description = mongoFunction.Description,
VisibilityExpression = mongoFunction.VisibilityExpression,
Impact = mongoFunction.Impact,
Name = function.Name,
Description = function.Description,
Channels = function.Channels,
VisibilityExpression = function.VisibilityExpression,
Impact = function.Impact,
Parameters = new FunctionParametersDef
{
Type = mongoFunction.Parameters.Type,
Properties = JsonSerializer.Deserialize<JsonDocument>(mongoFunction.Parameters.Properties.IfNullOrEmptyAs("{}")),
Required = mongoFunction.Parameters.Required,
Type = function.Parameters.Type,
Properties = JsonSerializer.Deserialize<JsonDocument>(function.Parameters.Properties.IfNullOrEmptyAs("{}")),
Required = function.Parameters.Required,
}
};
}

View file

@ -38,7 +38,7 @@ public partial class MongoRepository
UpdateAgentRoutingRules(agent.Id, agent.RoutingRules);
break;
case AgentField.Instruction:
UpdateAgentInstruction(agent.Id, agent.Instruction);
UpdateAgentInstructions(agent.Id, agent.Instruction, agent.ChannelInstructions);
break;
case AgentField.Function:
UpdateAgentFunctions(agent.Id, agent.Functions);
@ -156,13 +156,17 @@ public partial class MongoRepository
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentInstruction(string agentId, string instruction)
private void UpdateAgentInstructions(string agentId, string instruction, List<ChannelInstruction>? channelInstructions)
{
if (string.IsNullOrWhiteSpace(instruction)) return;
if (string.IsNullOrWhiteSpace(agentId)) return;
var instructionElements = channelInstructions?.Select(x => ChannelInstructionMongoElement.ToMongoElement(x))?
.ToList() ?? new List<ChannelInstructionMongoElement>();
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var update = Builders<AgentDocument>.Update
.Set(x => x.Instruction, instruction)
.Set(x => x.ChannelInstructions, instructionElements)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
@ -253,6 +257,7 @@ public partial class MongoRepository
.Set(x => x.Profiles, agent.Profiles)
.Set(x => x.RoutingRules, agent.RoutingRules.Select(r => RoutingRuleMongoElement.ToMongoElement(r)).ToList())
.Set(x => x.Instruction, agent.Instruction)
.Set(x => x.ChannelInstructions, agent.ChannelInstructions.Select(i => ChannelInstructionMongoElement.ToMongoElement(i)).ToList())
.Set(x => x.Templates, agent.Templates.Select(t => AgentTemplateMongoElement.ToMongoElement(t)).ToList())
.Set(x => x.Functions, agent.Functions.Select(f => FunctionDefMongoElement.ToMongoElement(f)).ToList())
.Set(x => x.Responses, agent.Responses.Select(r => AgentResponseMongoElement.ToMongoElement(r)).ToList())
@ -373,6 +378,9 @@ public partial class MongoRepository
IconUrl = x.IconUrl,
Description = x.Description,
Instruction = x.Instruction,
ChannelInstructions = x.ChannelInstructions?
.Select(i => ChannelInstructionMongoElement.ToMongoElement(i))?
.ToList() ?? new List<ChannelInstructionMongoElement>(),
Templates = x.Templates?
.Select(t => AgentTemplateMongoElement.ToMongoElement(t))?
.ToList() ?? new List<AgentTemplateMongoElement>(),
@ -463,6 +471,9 @@ public partial class MongoRepository
IconUrl = agentDoc.IconUrl,
Description = agentDoc.Description,
Instruction = agentDoc.Instruction,
ChannelInstructions = !agentDoc.ChannelInstructions.IsNullOrEmpty() ? agentDoc.ChannelInstructions
.Select(i => ChannelInstructionMongoElement.ToDomainElement(i))
.ToList() : new List<ChannelInstruction>(),
Templates = !agentDoc.Templates.IsNullOrEmpty() ? agentDoc.Templates
.Select(t => AgentTemplateMongoElement.ToDomainElement(t))
.ToList() : new List<AgentTemplate>(),
@ -472,6 +483,10 @@ public partial class MongoRepository
Responses = !agentDoc.Responses.IsNullOrEmpty() ? agentDoc.Responses
.Select(r => AgentResponseMongoElement.ToDomainElement(r))
.ToList() : new List<AgentResponse>(),
RoutingRules = !agentDoc.RoutingRules.IsNullOrEmpty() ? agentDoc.RoutingRules
.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))
.ToList() : new List<RoutingRule>(),
LlmConfig = AgentLlmConfigMongoElement.ToDomainElement(agentDoc.LlmConfig),
Samples = agentDoc.Samples ?? new List<string>(),
Utilities = agentDoc.Utilities ?? new List<string>(),
IsPublic = agentDoc.IsPublic,
@ -479,10 +494,6 @@ public partial class MongoRepository
Type = agentDoc.Type,
InheritAgentId = agentDoc.InheritAgentId,
Profiles = agentDoc.Profiles,
RoutingRules = !agentDoc.RoutingRules.IsNullOrEmpty() ? agentDoc.RoutingRules
.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))
.ToList() : new List<RoutingRule>(),
LlmConfig = AgentLlmConfigMongoElement.ToDomainElement(agentDoc.LlmConfig)
};
}
}

View file

@ -37,9 +37,12 @@ public partial class MongoRepository
{
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
Name = x.Name,
IconUrl = x.IconUrl,
Description = x.Description,
Instruction = x.Instruction,
IconUrl = x.IconUrl,
ChannelInstructions = x.ChannelInstructions?
.Select(i => ChannelInstructionMongoElement.ToMongoElement(i))?
.ToList() ?? new List<ChannelInstructionMongoElement>(),
Templates = x.Templates?
.Select(t => AgentTemplateMongoElement.ToMongoElement(t))?
.ToList() ?? new List<AgentTemplateMongoElement>(),
@ -71,6 +74,7 @@ public partial class MongoRepository
.Set(x => x.Name, agent.Name)
.Set(x => x.Description, agent.Description)
.Set(x => x.Instruction, agent.Instruction)
.Set(x => x.ChannelInstructions, agent.ChannelInstructions)
.Set(x => x.Templates, agent.Templates)
.Set(x => x.Functions, agent.Functions)
.Set(x => x.Responses, agent.Responses)

View file

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>$(LangVersion)</LangVersion>
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
<OutputPath>$(SolutionDir)packages</OutputPath>
</PropertyGroup>
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\python_interpreter.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\python_interpreter.fn.liquid" />
</ItemGroup>
<ItemGroup>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\python_interpreter.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\python_interpreter.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="pythonnet" Version="3.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,6 @@
namespace BotSharp.Plugin.PythonInterpreter.Enums;
public class UtilityName
{
public const string PythonInterpreter = "python-interpreter";
}

View file

@ -0,0 +1,46 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Interpreters.Models;
using Microsoft.Extensions.Logging;
using Python.Runtime;
using System.Text.Json;
using System.Threading.Tasks;
namespace BotSharp.Plugin.PythonInterpreter.Functions;
public class InterpretationFn : IFunctionCallback
{
public string Name => "python_interpreter";
public string Indication => "Interpreting python code";
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<InterpretationRequest>(message.FunctionArgs);
using (Py.GIL())
{
// Import necessary Python modules
dynamic sys = Py.Import("sys");
dynamic io = Py.Import("io");
// Redirect standard output to capture it
dynamic stringIO = io.StringIO();
sys.stdout = stringIO;
// Execute a simple Python script
using var locals = new PyDict();
PythonEngine.Exec(args.Script, null, locals);
// Console.WriteLine($"Result from Python: {result}");
message.Content = stringIO.getvalue();
// Restore the original stdout
sys.stdout = sys.__stdout__;
}
return true;
}
}

View file

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

View file

@ -0,0 +1,9 @@
namespace BotSharp.Plugin.PythonInterpreter.Hooks;
public class InterpreterUtilityHook : IAgentUtilityHook
{
public void AddUtilities(List<string> utilities)
{
utilities.Add(UtilityName.PythonInterpreter);
}
}

View file

@ -0,0 +1,17 @@
using BotSharp.Plugin.PythonInterpreter.Hooks;
namespace BotSharp.Plugin.PythonInterpreter;
public class InterpreterPlugin : IBotSharpPlugin
{
public string Id => "23174e08-e866-4173-824a-cf1d97afa8d0";
public string Name => "Python Interpreter";
public string Description => "Python Interpreter enables AI to write and execute Python code within a secure, sandboxed environment.";
public string? IconUrl => "https://static.vecteezy.com/system/resources/previews/012/697/295/non_2x/3d-python-programming-language-logo-free-png.png";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<IAgentHook, InterpreterAgentHook>();
services.AddScoped<IAgentUtilityHook, InterpreterUtilityHook>();
}
}

View file

@ -0,0 +1,18 @@
global using System;
global using System.Linq;
global using System.Collections.Generic;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Plugins;
global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Agents.Enums;
global using BotSharp.Abstraction.Agents.Models;
global using BotSharp.Abstraction.Agents.Settings;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Functions.Models;
global using BotSharp.Abstraction.Repositories;
global using BotSharp.Plugin.PythonInterpreter.Enums;

View file

@ -0,0 +1,19 @@
{
"name": "python_interpreter",
"description": "write and execute python code, print the result in Console",
"parameters": {
"type": "object",
"properties": {
"script": {
"type": "string",
"description": "python code"
},
"language": {
"type": "string",
"enum": [ "python" ],
"description": "python code"
}
},
"required": [ "language", "script" ]
}
}

View file

@ -0,0 +1 @@
Write and execute Python script in python_interpreter function, and use python function print(a) to output the result in stand output.

View file

@ -57,8 +57,7 @@ public class QdrantDb : IVectorDb
var points = response?.Result?.Select(x => new KnowledgeCollectionData
{
Id = x.Id?.Uuid ?? string.Empty,
Question = x.Payload.ContainsKey(KnowledgePayloadName.Text) ? x.Payload[KnowledgePayloadName.Text].StringValue : string.Empty,
Answer = x.Payload.ContainsKey(KnowledgePayloadName.Answer) ? x.Payload[KnowledgePayloadName.Answer].StringValue : string.Empty,
Data = x.Payload.ToDictionary(x => x.Key, x => x.Value.StringValue),
Vector = filter.WithVector ? x.Vectors?.Vector?.Data?.ToArray() : null
})?.ToList() ?? new List<KnowledgeCollectionData>();
@ -125,30 +124,46 @@ public class QdrantDb : IVectorDb
return result.Status == UpdateStatus.Completed;
}
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
public async Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
{
var results = new List<KnowledgeCollectionData>();
var client = GetClient();
var exist = await DoesCollectionExist(client, collectionName);
if (!exist)
{
return results;
}
var points = await client.SearchAsync(collectionName, vector, limit: (ulong)limit, scoreThreshold: confidence);
var results = new List<KnowledgeSearchResult>();
var pickFields = fields != null;
foreach (var point in points)
{
var data = new Dictionary<string, string>();
foreach (var field in fields)
if (pickFields)
{
if (point.Payload.ContainsKey(field))
foreach (var field in fields)
{
data[field] = point.Payload[field].StringValue;
}
else
{
data[field] = "";
if (point.Payload.ContainsKey(field))
{
data[field] = point.Payload[field].StringValue;
}
else
{
data[field] = "";
}
}
}
results.Add(new KnowledgeSearchResult
else
{
data = point.Payload.ToDictionary(k => k.Key, v => v.Value.StringValue);
}
results.Add(new KnowledgeCollectionData
{
Id = point.Id.Uuid,
Data = data,
Score = point.Score,
Vector = withVector ? point.Vectors?.Vector?.Data?.ToArray() : null

View file

@ -2,7 +2,6 @@ using BotSharp.Abstraction.Knowledges.Models;
using BotSharp.Abstraction.Utilities;
using BotSharp.Abstraction.VectorStorage;
using Microsoft.SemanticKernel.Memory;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -44,15 +43,15 @@ namespace BotSharp.Plugin.SemanticKernel
return result;
}
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
public async Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
{
var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, limit);
var resultTexts = new List<KnowledgeSearchResult>();
var resultTexts = new List<KnowledgeCollectionData>();
await foreach (var (record, score) in results)
{
resultTexts.Add(new KnowledgeSearchResult
resultTexts.Add(new KnowledgeCollectionData
{
Data = new Dictionary<string, string> { { "text", record.Metadata.Text } },
Score = score,

View file

@ -18,7 +18,7 @@
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\lookup_dictionary.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_insert.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_select.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instruction.liquid" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instructions\instruction.liquid" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\lookup_dictionary.liquid" />
</ItemGroup>
@ -26,7 +26,7 @@
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instruction.liquid">
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\lookup_dictionary.liquid">

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.Routing;
using BotSharp.Core.Infrastructures;
using BotSharp.Plugin.Twilio.Models;
using BotSharp.Plugin.Twilio.Services;
@ -10,7 +9,7 @@ using System.IdentityModel.Tokens.Jwt;
namespace BotSharp.Plugin.Twilio.Controllers;
[AllowAnonymous]
[Route("[controller]")]
[Route("twilio/voice")]
public class TwilioVoiceController : TwilioController
{
private readonly TwilioSetting _settings;
@ -38,73 +37,25 @@ public class TwilioVoiceController : TwilioController
};
}
[HttpPost("/twilio/voice/welcome")]
public async Task<TwiMLResult> StartConversation(VoiceRequest request)
{
string sessionId = $"TwilioVoice_{request.CallSid}";
var twilio = _services.GetRequiredService<TwilioService>();
var response = twilio.ReturnInstructions("Hello, how may I help you?");
return TwiML(response);
}
[HttpPost("/twilio/voice/{agentId}")]
public async Task<TwiMLResult> ReceivedVoiceMessage([FromRoute] string agentId, VoiceRequest input)
{
string sessionId = $"TwilioVoice_{input.CallSid}";
var inputMsg = new RoleDialogModel(AgentRole.User, input.SpeechResult);
var conv = _services.GetRequiredService<IConversationService>();
var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.SetMessageId(sessionId, inputMsg.MessageId);
conv.SetConversationId(sessionId, new List<MessageState>
{
new MessageState("channel", ConversationChannel.Phone),
new MessageState("calling_phone", input.DialCallSid)
});
var twilio = _services.GetRequiredService<TwilioService>();
VoiceResponse response = default;
var result = await conv.SendMessage(agentId,
inputMsg,
replyMessage: null,
async msg =>
{
response = twilio.ReturnInstructions(msg.Content);
if (msg.FunctionName == "conversation_end")
{
response = twilio.HangUp(msg.Content);
}
}, async functionExecuting =>
{
}, async functionExecuted =>
{
});
return TwiML(response);
}
[HttpPost("start")]
public TwiMLResult InitiateConversation(VoiceRequest request)
[HttpPost("welcome")]
public TwiMLResult InitiateConversation(VoiceRequest request, [FromQuery] string states)
{
if (request?.CallSid == null) throw new ArgumentNullException(nameof(VoiceRequest.CallSid));
string sessionId = $"TwilioVoice_{request.CallSid}";
string conversationId = $"TwilioVoice_{request.CallSid}";
var twilio = _services.GetRequiredService<TwilioService>();
var url = $"twiliovoice/{sessionId}/send/0";
var response = twilio.ReturnInstructions("twilio/welcome.mp3", url, false);
var url = $"twilio/voice/{conversationId}/receive/0?states={states}";
var response = twilio.ReturnInstructions("twilio/welcome.mp3", url, true);
return TwiML(response);
}
[HttpPost("{sessionId}/send/{seqNum}")]
public async Task<TwiMLResult> SendCallerMessage([FromRoute] string sessionId, [FromRoute] int seqNum, VoiceRequest request)
[HttpPost("{conversationId}/receive/{seqNum}")]
public async Task<TwiMLResult> ReceiveCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request)
{
var twilio = _services.GetRequiredService<TwilioService>();
var messageQueue = _services.GetRequiredService<TwilioMessageQueue>();
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
var url = $"twiliovoice/{sessionId}/reply/{seqNum}";
var messages = await sessionManager.RetrieveStagedCallerMessagesAsync(sessionId, seqNum);
var url = $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}";
var messages = await sessionManager.RetrieveStagedCallerMessagesAsync(conversationId, seqNum);
if (!string.IsNullOrWhiteSpace(request.SpeechResult))
{
messages.Add(request.SpeechResult);
@ -113,48 +64,78 @@ public class TwilioVoiceController : TwilioController
VoiceResponse response;
if (!string.IsNullOrWhiteSpace(messageContent))
{
var callerMessage = new CallerMessage()
{
SessionId = sessionId,
ConversationId = conversationId,
SeqNumber = seqNum,
Content = messageContent,
From = request.From
};
if (!string.IsNullOrEmpty(states))
{
var kvp = states.Split(':');
if (kvp.Length == 2)
{
callerMessage.States.Add(kvp[0], kvp[1]);
}
}
await messageQueue.EnqueueAsync(callerMessage);
response = twilio.ReturnInstructions("twilio/holdon.mp3", url, true);
response = twilio.ReturnInstructions(null, url, true, 1);
}
else
{
response = twilio.HangUp("twilio/holdon.mp3");
var speechPath = seqNum > 0 ? $"twilio/voice/speeches/{conversationId}/{seqNum - 1}.mp3" : "twilio/welcome.mp3";
response = twilio.ReturnInstructions(speechPath, $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}", true);
}
return TwiML(response);
}
[HttpPost("{sessionId}/reply/{seqNum}")]
public async Task<TwiMLResult> ReplyCallerMessage([FromRoute] string sessionId, [FromRoute] int seqNum, VoiceRequest request)
[HttpPost("{conversationId}/reply/{seqNum}")]
public async Task<TwiMLResult> ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request)
{
var nextSeqNum = seqNum + 1;
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
var twilio = _services.GetRequiredService<TwilioService>();
if (request.SpeechResult != null)
{
await sessionManager.StageCallerMessageAsync(sessionId, nextSeqNum, request.SpeechResult);
await sessionManager.StageCallerMessageAsync(conversationId, nextSeqNum, request.SpeechResult);
}
var reply = await sessionManager.GetAssistantReplyAsync(sessionId, seqNum);
var reply = await sessionManager.GetAssistantReplyAsync(conversationId, seqNum);
VoiceResponse response;
if (string.IsNullOrEmpty(reply))
if (reply == null)
{
response = twilio.ReturnInstructions(null, $"twiliovoice/{sessionId}/reply/{seqNum}", true);
var indication = await sessionManager.GetReplyIndicationAsync(conversationId, seqNum);
if (indication != null)
{
var textToSpeechService = CompletionProvider.GetTextToSpeech(_services, "openai", "tts-1");
var fileService = _services.GetRequiredService<IFileStorageService>();
var data = await textToSpeechService.GenerateSpeechFromTextAsync(indication);
var fileName = $"indication_{seqNum}.mp3";
await fileService.SaveSpeechFileAsync(conversationId, fileName, data);
response = twilio.ReturnInstructions($"twilio/voice/speeches/{conversationId}/{fileName}", $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 2);
}
else
{
response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1);
}
}
else
{
var textToSpeechService = CompletionProvider.GetTextToSpeech(_services, "openai", "tts-1");
var fileService = _services.GetRequiredService<IFileStorageService>();
var data = await textToSpeechService.GenerateSpeechFromTextAsync(reply);
var fileName = $"{seqNum}.mp3";
await fileService.SaveSpeechFileAsync(sessionId, fileName, data);
response = twilio.ReturnInstructions($"twiliovoice/speeches/{sessionId}/{fileName}", $"twiliovoice/{sessionId}/send/{nextSeqNum}", true);
var data = await textToSpeechService.GenerateSpeechFromTextAsync(reply.Content);
var fileName = $"reply_{seqNum}.mp3";
await fileService.SaveSpeechFileAsync(conversationId, fileName, data);
if (reply.ConversationEnd)
{
response = twilio.HangUp($"twilio/voice/speeches/{conversationId}/{fileName}");
}
else
{
response = twilio.ReturnInstructions($"twilio/voice/speeches/{conversationId}/{fileName}", $"twilio/voice/{conversationId}/receive/{nextSeqNum}?states={states}", true);
}
}
return TwiML(response);
}

View file

@ -0,0 +1,8 @@
namespace BotSharp.Plugin.Twilio.Models
{
public class AssistantMessage
{
public bool ConversationEnd { get; set; }
public string Content { get; set; }
}
}

View file

@ -2,14 +2,15 @@ namespace BotSharp.Plugin.Twilio.Models
{
public class CallerMessage
{
public string SessionId { get; set; }
public string ConversationId { get; set; }
public int SeqNumber { get; set; }
public string Content { get; set; }
public string From { get; set; }
public Dictionary<string, string> States { get; set; } = new();
public override string ToString()
{
return $"{SessionId}-{SeqNumber}";
return $"{ConversationId}-{SeqNumber}";
}
}
}

View file

@ -1,12 +1,15 @@
using BotSharp.Plugin.Twilio.Models;
using Task = System.Threading.Tasks.Task;
namespace BotSharp.Plugin.Twilio.Services
{
public interface ITwilioSessionManager
{
Task SetAssistantReplyAsync(string sessionId, int seqNum, string message);
Task<string> GetAssistantReplyAsync(string sessionId, int seqNum);
Task StageCallerMessageAsync(string sessionId, int seqNum, string message);
Task<List<string>> RetrieveStagedCallerMessagesAsync(string sessionId, int seqNum);
Task SetAssistantReplyAsync(string conversationId, int seqNum, AssistantMessage message);
Task<AssistantMessage> GetAssistantReplyAsync(string conversationId, int seqNum);
Task StageCallerMessageAsync(string conversationId, int seqNum, string message);
Task<List<string>> RetrieveStagedCallerMessagesAsync(string conversationId, int seqNum);
Task SetReplyIndicationAsync(string conversationId, int seqNum, string indication);
Task<string> GetReplyIndicationAsync(string conversationId, int seqNum);
}
}

View file

@ -55,35 +55,53 @@ namespace BotSharp.Plugin.Twilio.Services
{
using var scope = _serviceProvider.CreateScope();
var sp = scope.ServiceProvider;
string reply = null;
AssistantMessage reply = null;
var inputMsg = new RoleDialogModel(AgentRole.User, message.Content);
var conv = sp.GetRequiredService<IConversationService>();
var routing = sp.GetRequiredService<IRoutingService>();
var config = sp.GetRequiredService<TwilioSetting>();
routing.Context.SetMessageId(message.SessionId, inputMsg.MessageId);
conv.SetConversationId(message.SessionId, new List<MessageState>
routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId);
var states = new List<MessageState>
{
new MessageState("channel", ConversationChannel.Phone),
new MessageState("calling_phone", message.From)
});
};
foreach (var kvp in message.States)
{
states.Add(new MessageState(kvp.Key, kvp.Value));
}
conv.SetConversationId(message.ConversationId, states);
var sessionManager = sp.GetRequiredService<ITwilioSessionManager>();
var result = await conv.SendMessage(config.AgentId,
inputMsg,
replyMessage: null,
async msg =>
{
reply = msg.Content;
reply = new AssistantMessage()
{
ConversationEnd = msg.Instruction.ConversationEnd,
Content = msg.Content
};
},
async msg =>
{
if (!string.IsNullOrEmpty(msg.Indication))
{
await sessionManager.SetReplyIndicationAsync(message.ConversationId, message.SeqNumber, msg.Indication);
}
},
async functionExecuting =>
{ },
async functionExecuted =>
{ }
);
if (string.IsNullOrWhiteSpace(reply))
if (reply == null || string.IsNullOrWhiteSpace(reply.Content))
{
reply = "Sorry, something was wrong.";
}
var sessionManager = sp.GetRequiredService<ITwilioSessionManager>();
await sessionManager.SetAssistantReplyAsync(message.SessionId, message.SeqNumber, reply);
reply = new AssistantMessage()
{
ConversationEnd = true,
Content = "Sorry, something was wrong."
};
}
await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply);
}
}
}

View file

@ -63,7 +63,7 @@ public class TwilioService
return response;
}
public VoiceResponse ReturnInstructions(string speechPath, string callbackPath, bool actionOnEmptyResult)
public VoiceResponse ReturnInstructions(string speechPath, string callbackPath, bool actionOnEmptyResult, int timeout = 3)
{
var response = new VoiceResponse();
var gather = new Gather()
@ -73,7 +73,9 @@ public class TwilioService
Gather.InputEnum.Speech
},
Action = new Uri($"{_settings.CallbackHost}/{callbackPath}"),
SpeechTimeout = "3",
SpeechModel = Gather.SpeechModelEnum.PhoneCall,
SpeechTimeout = timeout > 0 ? timeout.ToString() : "3",
Timeout = timeout > 0 ? timeout : 3,
ActionOnEmptyResult = actionOnEmptyResult
};
if (!string.IsNullOrEmpty(speechPath))

View file

@ -1,3 +1,4 @@
using BotSharp.Plugin.Twilio.Models;
using StackExchange.Redis;
using Task = System.Threading.Tasks.Task;
@ -12,35 +13,51 @@ namespace BotSharp.Plugin.Twilio.Services
_redis = redis;
}
public async Task<string> GetAssistantReplyAsync(string sessionId, int seqNum)
public async Task<AssistantMessage> GetAssistantReplyAsync(string conversationId, int seqNum)
{
var db = _redis.GetDatabase();
var key = $"{sessionId}:Assisist:{seqNum}";
return await db.StringGetAsync(key);
var key = $"{conversationId}:Assisist:{seqNum}";
var jsonStr = await db.StringGetAsync(key);
return jsonStr.IsNull ? null : JsonSerializer.Deserialize<AssistantMessage>(jsonStr);
}
public async Task<List<string>> RetrieveStagedCallerMessagesAsync(string sessionId, int seqNum)
public async Task<List<string>> RetrieveStagedCallerMessagesAsync(string conversationId, int seqNum)
{
var db = _redis.GetDatabase();
var key = $"{sessionId}:Caller:{seqNum}";
var key = $"{conversationId}:Caller:{seqNum}";
return (await db.ListRangeAsync(key))
.Select(x => (string)x)
.ToList();
}
public async Task SetAssistantReplyAsync(string sessionId, int seqNum, string message)
public async Task SetAssistantReplyAsync(string conversationId, int seqNum, AssistantMessage message)
{
var jsonStr = JsonSerializer.Serialize(message);
var db = _redis.GetDatabase();
var key = $"{sessionId}:Assisist:{seqNum}";
await db.StringSetAsync(key, message, TimeSpan.FromMinutes(5));
var key = $"{conversationId}:Assisist:{seqNum}";
await db.StringSetAsync(key, jsonStr, TimeSpan.FromMinutes(5));
}
public async Task StageCallerMessageAsync(string sessionId, int seqNum, string message)
public async Task StageCallerMessageAsync(string conversationId, int seqNum, string message)
{
var db = _redis.GetDatabase();
var key = $"{sessionId}:Caller:{seqNum}";
var key = $"{conversationId}:Caller:{seqNum}";
await db.ListRightPushAsync(key, message);
await db.KeyExpireAsync(key, DateTime.UtcNow.AddMinutes(10));
}
public async Task SetReplyIndicationAsync(string conversationId, int seqNum, string indication)
{
var db = _redis.GetDatabase();
var key = $"{conversationId}:Indication:{seqNum}";
await db.StringSetAsync(key, indication, TimeSpan.FromMinutes(5));
}
public async Task<string> GetReplyIndicationAsync(string conversationId, int seqNum)
{
var db = _redis.GetDatabase();
var key = $"{conversationId}:Indication:{seqNum}";
return await db.StringGetAsync(key);
}
}
}

View file

@ -17,7 +17,6 @@ public class TwilioPlugin : IBotSharpPlugin
var settingService = provider.GetRequiredService<ISettingService>();
return settingService.Bind<TwilioSetting>("Twilio");
});
services.AddScoped<TwilioService>();
var conn = ConnectionMultiplexer.Connect(config["Twilio:RedisConnectionString"]);
var sessionManager = new TwilioSessionManager(conn);

View file

@ -40,7 +40,7 @@
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\scroll_page.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\send_http_request.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\functions\take_screenshot.json" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\instruction.liquid" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\instructions\instruction.liquid" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\extract_data.liquid" />
<None Remove="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\html_parser.liquid" />
<None Remove="README.md" />
@ -50,7 +50,7 @@
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\instruction.liquid">
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b\templates\extract_data.liquid">

View file

@ -4,6 +4,7 @@ using BotSharp.Logger;
using BotSharp.Plugin.ChatHub;
using Serilog;
using BotSharp.Abstraction.Messaging.JsonConverters;
using Python.Runtime;
var builder = WebApplication.CreateBuilder(args);
@ -41,4 +42,11 @@ app.UseBotSharp()
.UseBotSharpOpenAPI(app.Environment)
.UseBotSharpUI();
Runtime.PythonDLL = @"C:\Users\xxx\AppData\Local\Programs\Python\Python311\python311.dll";
PythonEngine.Initialize();
PythonEngine.BeginAllowThreads();
app.Run();
// Shut down the Python engine
PythonEngine.Shutdown();

View file

@ -30,6 +30,7 @@
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaGLM\BotSharp.Plugin.MetaGLM.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Planner\BotSharp.Plugin.Planner.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.PythonInterpreter\BotSharp.Plugin.PythonInterpreter.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.SparkDesk\BotSharp.Plugin.SparkDesk.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.TencentCos\BotSharp.Plugin.TencentCos.csproj" />
</ItemGroup>

View file

@ -259,6 +259,7 @@
"KnowledgeBase": {
"VectorDb": "Qdrant",
"DefaultCollection": "BotSharp",
"TextEmbedding": {
"Provider": "openai",
"Model": "text-embedding-3-small"
@ -319,7 +320,8 @@
"BotSharp.Plugin.HttpHandler",
"BotSharp.Plugin.FileHandler",
"BotSharp.Plugin.EmailHandler",
"BotSharp.Plugin.TencentCos"
"BotSharp.Plugin.TencentCos",
"BotSharp.Plugin.PythonInterpreter"
]
}
}

View file

@ -24,16 +24,16 @@
<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\get_order_status.json" />
<None Remove="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\instruction.liquid" />
<None Remove="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\instructions\instruction.liquid" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\agent.json" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\get_pizza_price.json" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\get_pizza_types.json" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\place_an_order.json" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\instruction.liquid" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\instructions\instruction.liquid" />
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\responses\func.get_pizza_price.0.liquid" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\agent.json" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\functions\make_payment.json" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instruction.liquid" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instructions\instruction.liquid" />
<None Remove="data\users\10d12798-08fb-4aa6-977b-5dd94d82dbfe\user.json" />
<None Remove="data\users\456e35c5-caf0-4d45-9084-b44a8ca717e4\user.json" />
<None Remove="data\users\d0e6680d-03d5-4ed8-bdcd-aa7d86f2a1bc\user.json" />
@ -50,13 +50,13 @@
<Content Include="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\instruction.liquid">
<Content Include="data\agents\b284db86-e9c2-4c25-a59e-4649797dd130\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\instruction.liquid">
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\responses\func.get_pizza_price.0.liquid">
@ -65,7 +65,7 @@
<Content Include="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instruction.liquid">
<Content Include="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\10d12798-08fb-4aa6-977b-5dd94d82dbfe\user.json">