Merge branch 'master' of https://github.com/Joannall/BotSharp
This commit is contained in:
commit
80e292a361
|
|
@ -17,7 +17,8 @@ public enum AgentField
|
|||
Response,
|
||||
Sample,
|
||||
LlmConfig,
|
||||
Utility
|
||||
Utility,
|
||||
MaxMessageCount
|
||||
}
|
||||
|
||||
public enum AgentTaskField
|
||||
|
|
|
|||
|
|
@ -104,6 +104,12 @@ public class Agent
|
|||
/// </summary>
|
||||
public string? InheritAgentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum message count when load conversation
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public int? MaxMessageCount { get; set; }
|
||||
|
||||
public List<RoutingRule> RoutingRules { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -133,6 +139,8 @@ public class Agent
|
|||
Knowledges = agent.Knowledges,
|
||||
IsPublic = agent.IsPublic,
|
||||
Disabled = agent.Disabled,
|
||||
MergeUtility = agent.MergeUtility,
|
||||
MaxMessageCount = agent.MaxMessageCount,
|
||||
Profiles = agent.Profiles,
|
||||
RoutingRules = agent.RoutingRules,
|
||||
LlmConfig = agent.LlmConfig,
|
||||
|
|
|
|||
|
|
@ -2,27 +2,23 @@ namespace BotSharp.Abstraction.Conversations;
|
|||
|
||||
public abstract class ConversationHookBase : IConversationHook
|
||||
{
|
||||
protected Agent _agent;
|
||||
public Agent Agent => _agent;
|
||||
public Agent Agent { get; private set; }
|
||||
|
||||
protected Conversation _conversation;
|
||||
public Conversation Conversation => _conversation;
|
||||
public Conversation Conversation { get; private set; }
|
||||
|
||||
protected List<RoleDialogModel> _dialogs;
|
||||
public List<RoleDialogModel> Dialogs => _dialogs;
|
||||
public List<RoleDialogModel> Dialogs { get; private set; }
|
||||
|
||||
protected int _priority = 0;
|
||||
public int Priority => _priority;
|
||||
public int Priority { get; protected set; } = 0;
|
||||
|
||||
public IConversationHook SetAgent(Agent agent)
|
||||
{
|
||||
_agent = agent;
|
||||
Agent = agent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConversationHook SetConversation(Conversation conversation)
|
||||
{
|
||||
_conversation = conversation;
|
||||
Conversation = conversation;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +33,7 @@ public abstract class ConversationHookBase : IConversationHook
|
|||
|
||||
public virtual Task OnDialogsLoaded(List<RoleDialogModel> dialogs)
|
||||
{
|
||||
_dialogs = dialogs;
|
||||
Dialogs = dialogs;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
namespace BotSharp.Abstraction.Conversations;
|
||||
|
||||
public class ConversationHookProvider
|
||||
{
|
||||
public IEnumerable<IConversationHook> Hooks { get; }
|
||||
|
||||
private readonly Lazy<IEnumerable<IConversationHook>> _hooksOrderByPriority;
|
||||
|
||||
public IEnumerable<IConversationHook> HooksOrderByPriority
|
||||
=> _hooksOrderByPriority.Value;
|
||||
|
||||
public ConversationHookProvider(IEnumerable<IConversationHook> conversationHooks)
|
||||
{
|
||||
Hooks = conversationHooks;
|
||||
_hooksOrderByPriority = new Lazy<IEnumerable<IConversationHook>>(() =>
|
||||
{
|
||||
return conversationHooks.OrderBy(hook => hook.Priority).ToArray();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ public interface IConversationService
|
|||
Task<Conversation> GetConversation(string id);
|
||||
Task<PagedItems<Conversation>> GetConversations(ConversationFilter filter);
|
||||
Task<Conversation> UpdateConversationTitle(string id, string title);
|
||||
Task<Conversation> UpdateConversationTitleAlias(string id, string titleAlias);
|
||||
Task<bool> UpdateConversationTags(string conversationId, List<string> tags);
|
||||
Task<bool> UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
|
||||
Task<List<Conversation>> GetLastConversations();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public class Conversation
|
|||
/// </summary>
|
||||
public string? TaskId { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string TitleAlias { get; set; } = string.Empty;
|
||||
|
||||
[JsonIgnore]
|
||||
public List<DialogElement> Dialogs { get; set; } = new();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@ public class CrontabItem : ScheduleTaskArgs
|
|||
[JsonPropertyName("execution_result")]
|
||||
public string ExecutionResult { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("execution_count")]
|
||||
public int ExecutionCount { get; set; }
|
||||
|
||||
[JsonPropertyName("max_execution_count")]
|
||||
public int MaxExecutionCount { get; set; }
|
||||
|
||||
[JsonPropertyName("expire_seconds")]
|
||||
public int ExpireSeconds { get; set; } = 60;
|
||||
|
||||
[JsonPropertyName("created_time")]
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ public class ConversationFilter
|
|||
/// </summary>
|
||||
public string? Id { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? TitleAlias { get; set; }
|
||||
public string? AgentId { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? Channel { get; set; }
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
|
||||
#region Agent
|
||||
void UpdateAgent(Agent agent, AgentField field);
|
||||
Agent? GetAgent(string agentId);
|
||||
Agent? GetAgent(string agentId, bool basicsOnly = false);
|
||||
List<Agent> GetAgents(AgentFilter filter);
|
||||
List<UserAgent> GetUserAgents(string userId);
|
||||
void BulkInsertAgents(List<Agent> agents);
|
||||
|
|
@ -86,6 +86,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
Conversation GetConversation(string conversationId);
|
||||
PagedItems<Conversation> GetConversations(ConversationFilter filter);
|
||||
void UpdateConversationTitle(string conversationId, string title);
|
||||
void UpdateConversationTitleAlias(string conversationId, string titleAlias);
|
||||
bool UpdateConversationTags(string conversationId, List<string> tags);
|
||||
bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
|
||||
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public class BasicAgentHook : AgentHookBase
|
|||
var entryAgentId = routing.EntryAgentId;
|
||||
if (!string.IsNullOrEmpty(entryAgentId))
|
||||
{
|
||||
var entryAgent = db.GetAgent(entryAgentId);
|
||||
var entryAgent = db.GetAgent(entryAgentId, basicsOnly: true);
|
||||
var (fns, tps) = GetUniqueContent(entryAgent?.Utilities);
|
||||
functionNames = functionNames.Concat(fns).Distinct().ToList();
|
||||
templateNames = templateNames.Concat(tps).Distinct().ToList();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace BotSharp.Core.Agents.Services;
|
|||
|
||||
public partial class AgentService
|
||||
{
|
||||
public static ConcurrentDictionary<string, Dictionary<string,string>> AgentParameterTypes = new();
|
||||
public static ConcurrentDictionary<string, Dictionary<string, string>> AgentParameterTypes = new();
|
||||
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
public async Task<Agent> LoadAgent(string id)
|
||||
|
|
@ -106,14 +106,14 @@ public partial class AgentService
|
|||
{
|
||||
var agentId = agent.Id ?? agent.Name;
|
||||
if (AgentParameterTypes.ContainsKey(agentId)) return;
|
||||
|
||||
|
||||
AddOrUpdateRoutesParameters(agentId, agent.RoutingRules);
|
||||
AddOrUpdateFunctionsParameters(agentId, agent.Functions);
|
||||
}
|
||||
|
||||
private void AddOrUpdateRoutesParameters(string agentId, List<RoutingRule> routingRules)
|
||||
{
|
||||
if(!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
|
||||
if (!AgentParameterTypes.TryGetValue(agentId, out var parameterTypes))
|
||||
{
|
||||
parameterTypes = new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public partial class AgentService
|
|||
record.IsPublic = agent.IsPublic;
|
||||
record.Disabled = agent.Disabled;
|
||||
record.MergeUtility = agent.MergeUtility;
|
||||
record.MaxMessageCount = agent.MaxMessageCount;
|
||||
record.Type = agent.Type;
|
||||
record.Profiles = agent.Profiles ?? [];
|
||||
record.RoutingRules = agent.RoutingRules ?? [];
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public partial class ConversationService
|
|||
var dialogs = conv.GetDialogHistory();
|
||||
|
||||
var statistics = _services.GetRequiredService<ITokenStatistics>();
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
var hookProvider = _services.GetRequiredService<ConversationHookProvider>();
|
||||
|
||||
RoleDialogModel response = message;
|
||||
bool stopCompletion = false;
|
||||
|
|
@ -44,9 +44,7 @@ public partial class ConversationService
|
|||
message.Payload = replyMessage.Payload;
|
||||
}
|
||||
|
||||
// Before chat completion hook
|
||||
hooks = ReOrderConversationHooks(hooks);
|
||||
foreach (var hook in hooks)
|
||||
foreach (var hook in hookProvider.HooksOrderByPriority)
|
||||
{
|
||||
hook.SetAgent(agent)
|
||||
.SetConversation(conversation);
|
||||
|
|
@ -173,18 +171,4 @@ public partial class ConversationService
|
|||
// Add to dialog history
|
||||
_storage.Append(_conversationId, response);
|
||||
}
|
||||
|
||||
private List<IConversationHook> ReOrderConversationHooks(List<IConversationHook> hooks)
|
||||
{
|
||||
var target = "ChatHubConversationHook";
|
||||
var chathub = hooks.FirstOrDefault(x => x.GetType().Name == target);
|
||||
var otherHooks = hooks.Where(x => x.GetType().Name != target).ToList();
|
||||
|
||||
if (chathub != null)
|
||||
{
|
||||
var newHooks = new List<IConversationHook> { chathub }.Concat(otherHooks);
|
||||
return newHooks.ToList();
|
||||
}
|
||||
return hooks;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public partial class ConversationService : IConversationService
|
|||
var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true);
|
||||
fileStorage.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnMessageDeleted(conversationId, messageId);
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ public partial class ConversationService : IConversationService
|
|||
states.CleanStates(excludedStates);
|
||||
}
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>()
|
||||
.OrderBy(x => x.Priority)
|
||||
.ToList();
|
||||
var hooks = _services
|
||||
.GetRequiredService<ConversationHookProvider>()
|
||||
.HooksOrderByPriority;
|
||||
|
||||
// Before executing functions
|
||||
foreach (var hook in hooks)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,14 @@ public partial class ConversationService : IConversationService
|
|||
return conversation;
|
||||
}
|
||||
|
||||
public async Task<Conversation> UpdateConversationTitleAlias(string id, string titleAlias)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
db.UpdateConversationTitleAlias(id, titleAlias);
|
||||
var conversation = db.GetConversation(id);
|
||||
return conversation;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateConversationTags(string conversationId, List<string> tags)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
|
|
@ -103,7 +111,8 @@ public partial class ConversationService : IConversationService
|
|||
|
||||
db.CreateNewConversation(record);
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
// If user connect agent first time
|
||||
|
|
@ -153,7 +162,10 @@ public partial class ConversationService : IConversationService
|
|||
}
|
||||
}
|
||||
|
||||
return dialogs.TakeLast(lastCount).ToList();
|
||||
var agentMsgCount = GetAgentMessageCount();
|
||||
var count = agentMsgCount.HasValue && agentMsgCount.Value > 0 ? agentMsgCount.Value : lastCount;
|
||||
|
||||
return dialogs.TakeLast(count).ToList();
|
||||
}
|
||||
|
||||
public void SetConversationId(string conversationId, List<MessageState> states, bool isReadOnly = false)
|
||||
|
|
@ -192,4 +204,16 @@ public partial class ConversationService : IConversationService
|
|||
{
|
||||
return !string.IsNullOrWhiteSpace(_conversationId);
|
||||
}
|
||||
|
||||
|
||||
private int? GetAgentMessageCount()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
|
||||
if (string.IsNullOrEmpty(routingCtx.EntryAgentId)) return null;
|
||||
|
||||
var agent = db.GetAgent(routingCtx.EntryAgentId, basicsOnly: true);
|
||||
return agent?.MaxMessageCount;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,45 +15,45 @@ public class EvaluationConversationHook : ConversationHookBase
|
|||
|
||||
public override Task OnMessageReceived(RoleDialogModel message)
|
||||
{
|
||||
if (_conversation != null && _convSettings.EnableExecutionLog)
|
||||
if (Conversation != null && _convSettings.EnableExecutionLog)
|
||||
{
|
||||
_logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
|
||||
_logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
|
||||
}
|
||||
return base.OnMessageReceived(message);
|
||||
}
|
||||
|
||||
public override Task OnFunctionExecuted(RoleDialogModel message)
|
||||
{
|
||||
if (_conversation != null && _convSettings.EnableExecutionLog)
|
||||
if (Conversation != null && _convSettings.EnableExecutionLog)
|
||||
{
|
||||
_logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.FunctionName}({message.FunctionArgs}) => {message.Content}");
|
||||
_logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.FunctionName}({message.FunctionArgs}) => {message.Content}");
|
||||
}
|
||||
return base.OnFunctionExecuted(message);
|
||||
}
|
||||
|
||||
public override Task OnResponseGenerated(RoleDialogModel message)
|
||||
{
|
||||
if (_conversation != null && _convSettings.EnableExecutionLog)
|
||||
if (Conversation != null && _convSettings.EnableExecutionLog)
|
||||
{
|
||||
_logger.Append(_conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
|
||||
}
|
||||
_logger.Append(Conversation.Id, $"[{DateTime.Now}] {message.Role}: {message.Content}");
|
||||
}
|
||||
return base.OnResponseGenerated(message);
|
||||
}
|
||||
|
||||
public override Task OnHumanInterventionNeeded(RoleDialogModel message)
|
||||
{
|
||||
if (_conversation != null && _convSettings.EnableExecutionLog)
|
||||
if (Conversation != null && _convSettings.EnableExecutionLog)
|
||||
{
|
||||
_logger.Append(_conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
|
||||
_logger.Append(Conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
|
||||
}
|
||||
return base.OnHumanInterventionNeeded(message);
|
||||
}
|
||||
|
||||
public override Task OnConversationEnding(RoleDialogModel message)
|
||||
{
|
||||
if (_conversation != null && _convSettings.EnableExecutionLog)
|
||||
if (Conversation != null && _convSettings.EnableExecutionLog)
|
||||
{
|
||||
_logger.Append(_conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
|
||||
_logger.Append(Conversation.Id, $"[{DateTime.Now}] {AgentRole.Function}: trigger_event({{\"event\": \"{message.FunctionName}\"}})");
|
||||
}
|
||||
return base.OnConversationEnding(message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
#endregion
|
||||
|
||||
#region Agent
|
||||
public Agent GetAgent(string agentId)
|
||||
public Agent GetAgent(string agentId, bool basicsOnly = false)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public List<Agent> GetAgents(AgentFilter filter)
|
||||
|
|
@ -105,6 +105,8 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
|
||||
public void UpdateConversationTitle(string conversationId, string title)
|
||||
=> throw new NotImplementedException();
|
||||
public void UpdateConversationTitleAlias(string conversationId, string titleAlias)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public bool UpdateConversationTags(string conversationId, List<string> tags)
|
||||
=> throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ namespace BotSharp.Core.Repository
|
|||
case AgentField.Utility:
|
||||
UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities);
|
||||
break;
|
||||
case AgentField.MaxMessageCount:
|
||||
UpdateAgentMaxMessageCount(agent.Id, agent.MaxMessageCount);
|
||||
break;
|
||||
case AgentField.All:
|
||||
UpdateAgentAllFields(agent);
|
||||
break;
|
||||
|
|
@ -283,6 +286,17 @@ namespace BotSharp.Core.Repository
|
|||
File.WriteAllText(agentFile, json);
|
||||
}
|
||||
|
||||
private void UpdateAgentMaxMessageCount(string agentId, int? maxMessageCount)
|
||||
{
|
||||
var (agent, agentFile) = GetAgentFromFile(agentId);
|
||||
if (agent == null) return;
|
||||
|
||||
agent.MaxMessageCount = maxMessageCount;
|
||||
agent.UpdatedDateTime = DateTime.UtcNow;
|
||||
var json = JsonSerializer.Serialize(agent, _options);
|
||||
File.WriteAllText(agentFile, json);
|
||||
}
|
||||
|
||||
private void UpdateAgentAllFields(Agent inputAgent)
|
||||
{
|
||||
var (agent, agentFile) = GetAgentFromFile(inputAgent.Id);
|
||||
|
|
@ -298,6 +312,7 @@ namespace BotSharp.Core.Repository
|
|||
agent.Utilities = inputAgent.Utilities;
|
||||
agent.RoutingRules = inputAgent.RoutingRules;
|
||||
agent.LlmConfig = inputAgent.LlmConfig;
|
||||
agent.MaxMessageCount = inputAgent.MaxMessageCount;
|
||||
agent.UpdatedDateTime = DateTime.UtcNow;
|
||||
var json = JsonSerializer.Serialize(agent, _options);
|
||||
File.WriteAllText(agentFile, json);
|
||||
|
|
@ -329,7 +344,7 @@ namespace BotSharp.Core.Repository
|
|||
return responses;
|
||||
}
|
||||
|
||||
public Agent? GetAgent(string agentId)
|
||||
public Agent? GetAgent(string agentId, bool basicsOnly = false)
|
||||
{
|
||||
var agentDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir);
|
||||
var dir = Directory.GetDirectories(agentDir).FirstOrDefault(x => x.Split(Path.DirectorySeparatorChar).Last() == agentId);
|
||||
|
|
@ -342,6 +357,8 @@ namespace BotSharp.Core.Repository
|
|||
var record = JsonSerializer.Deserialize<Agent>(json, _options);
|
||||
if (record == null) return null;
|
||||
|
||||
if (basicsOnly) return record;
|
||||
|
||||
var (defaultInstruction, channelInstructions) = FetchInstructions(dir);
|
||||
var functions = FetchFunctions(dir);
|
||||
var samples = FetchSamples(dir);
|
||||
|
|
|
|||
|
|
@ -134,6 +134,22 @@ namespace BotSharp.Core.Repository
|
|||
}
|
||||
}
|
||||
}
|
||||
public void UpdateConversationTitleAlias(string conversationId, string titleAlias)
|
||||
{
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
if (!string.IsNullOrEmpty(convDir))
|
||||
{
|
||||
var convFile = Path.Combine(convDir, CONVERSATION_FILE);
|
||||
var content = File.ReadAllText(convFile);
|
||||
var record = JsonSerializer.Deserialize<Conversation>(content, _options);
|
||||
if (record != null)
|
||||
{
|
||||
record.TitleAlias = titleAlias;
|
||||
record.UpdatedTime = DateTime.UtcNow;
|
||||
File.WriteAllText(convFile, JsonSerializer.Serialize(record, _options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateConversationTags(string conversationId, List<string> tags)
|
||||
{
|
||||
|
|
@ -356,6 +372,10 @@ namespace BotSharp.Core.Repository
|
|||
{
|
||||
matched = matched && record.Title.Contains(filter.Title);
|
||||
}
|
||||
if (filter?.TitleAlias != null)
|
||||
{
|
||||
matched = matched && record.TitleAlias.Contains(filter.TitleAlias);
|
||||
}
|
||||
if (filter?.AgentId != null)
|
||||
{
|
||||
matched = matched && record.AgentId == filter.AgentId;
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ public class HumanInterventionNeededFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var hooks = _services.GetServices<IConversationHook>()
|
||||
.OrderBy(x => x.Priority)
|
||||
.ToList();
|
||||
var hooks = _services
|
||||
.GetRequiredService<ConversationHookProvider>()
|
||||
.HooksOrderByPriority;
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ public partial class RoutingService
|
|||
var clonedMessage = RoleDialogModel.From(message);
|
||||
clonedMessage.FunctionName = name;
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>()
|
||||
.OrderBy(x => x.Priority)
|
||||
.ToList();
|
||||
var hooks = _services
|
||||
.GetRequiredService<ConversationHookProvider>()
|
||||
.HooksOrderByPriority;
|
||||
|
||||
var progressService = _services.GetService<IConversationProgressService>();
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class RateLimitConversationHook : ConversationHookBase
|
|||
}
|
||||
|
||||
// Check message sending frequency
|
||||
var userSents = _dialogs.Where(x => x.Role == AgentRole.User)
|
||||
var userSents = Dialogs.Where(x => x.Role == AgentRole.User)
|
||||
.TakeLast(2).ToList();
|
||||
|
||||
if (userSents.Count > 1)
|
||||
|
|
|
|||
|
|
@ -223,6 +223,30 @@ public class ConversationController : ControllerBase
|
|||
return response != null;
|
||||
}
|
||||
|
||||
[HttpPut("/conversation/{conversationId}/update-title-alias")]
|
||||
public async Task<bool> UpdateConversationTitleAlias([FromRoute] string conversationId, [FromBody] UpdateConversationTitleAliasModel newTile)
|
||||
{
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||
|
||||
var user = await userService.GetUser(_user.Id);
|
||||
var filter = new ConversationFilter
|
||||
{
|
||||
Id = conversationId,
|
||||
UserId = user.Role != UserRole.Admin ? user.Id : null
|
||||
};
|
||||
var conversations = await conversationService.GetConversations(filter);
|
||||
|
||||
if (conversations.Items.IsNullOrEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var response = await conversationService.UpdateConversationTitleAlias(conversationId, newTile.NewTitleAlias);
|
||||
return response != null;
|
||||
}
|
||||
|
||||
|
||||
[HttpPut("/conversation/{conversationId}/update-tags")]
|
||||
public async Task<bool> UpdateConversationTags([FromRoute] string conversationId, [FromBody] UpdateConversationRequest request)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ public class AgentCreationModel
|
|||
|
||||
public bool MergeUtility { get; set; }
|
||||
|
||||
public int? MaxMessageCount { get; set; }
|
||||
|
||||
public List<AgentUtility> Utilities { get; set; } = new();
|
||||
public List<RoutingRuleUpdateModel> RoutingRules { get; set; } = new();
|
||||
public AgentLlmConfig? LlmConfig { get; set; }
|
||||
|
|
@ -72,6 +74,7 @@ public class AgentCreationModel
|
|||
Type = Type,
|
||||
Disabled = Disabled,
|
||||
MergeUtility = MergeUtility,
|
||||
MaxMessageCount = MaxMessageCount,
|
||||
Profiles = Profiles,
|
||||
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
|
||||
LlmConfig = LlmConfig
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ public class AgentUpdateModel
|
|||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[JsonPropertyName("max_message_count")]
|
||||
public int? MaxMessageCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Profile by channel
|
||||
/// </summary>
|
||||
|
|
@ -77,6 +80,7 @@ public class AgentUpdateModel
|
|||
IsPublic = IsPublic,
|
||||
Disabled = Disabled,
|
||||
MergeUtility = MergeUtility,
|
||||
MaxMessageCount = MaxMessageCount,
|
||||
Type = Type,
|
||||
Profiles = Profiles ?? new List<string>(),
|
||||
RoutingRules = RoutingRules?.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?.ToList() ?? new List<RoutingRule>(),
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ public class AgentViewModel
|
|||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public AgentLlmConfig? LlmConfig { get; set; }
|
||||
|
||||
[JsonPropertyName("max_message_count")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public int? MaxMessageCount { get; set; }
|
||||
|
||||
public PluginDef Plugin { get; set; }
|
||||
|
||||
public IEnumerable<string>? Actions { get; set; }
|
||||
|
|
@ -75,6 +79,7 @@ public class AgentViewModel
|
|||
Disabled = agent.Disabled,
|
||||
MergeUtility = agent.MergeUtility,
|
||||
IconUrl = agent.IconUrl,
|
||||
MaxMessageCount = agent.MaxMessageCount,
|
||||
Profiles = agent.Profiles ?? new List<string>(),
|
||||
RoutingRules = agent.RoutingRules,
|
||||
LlmConfig = agent.LlmConfig,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ public class ConversationViewModel
|
|||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("title_alias")]
|
||||
public string TitleAlias { get; set; } = string.Empty;
|
||||
|
||||
public UserViewModel User { get; set; } = new UserViewModel();
|
||||
|
||||
public string Event { get; set; }
|
||||
|
|
@ -48,6 +51,7 @@ public class ConversationViewModel
|
|||
},
|
||||
AgentId = sess.AgentId,
|
||||
Title = sess.Title,
|
||||
TitleAlias = sess.TitleAlias,
|
||||
Channel = sess.Channel,
|
||||
Status = sess.Status,
|
||||
TaskId = sess.TaskId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Conversations;
|
||||
|
||||
public class UpdateConversationTitleAliasModel
|
||||
{
|
||||
[Required]
|
||||
public string NewTitleAlias { get; set; }
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ public class ChatHubPlugin : IBotSharpPlugin
|
|||
services.AddScoped<IConversationHook, ChatHubConversationHook>();
|
||||
services.AddScoped<IConversationHook, StreamingLogHook>();
|
||||
services.AddScoped<IConversationHook, WelcomeHook>();
|
||||
services.AddScoped<ConversationHookProvider>();
|
||||
services.AddScoped<IRoutingHook, StreamingLogHook>();
|
||||
services.AddScoped<IContentGeneratingHook, StreamingLogHook>();
|
||||
services.AddScoped<ICrontabHook, ChatHubCrontabHook>();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
_chatHub = chatHub;
|
||||
_user = user;
|
||||
_options = options;
|
||||
Priority = -1; // Make sure this hook is the top one.
|
||||
}
|
||||
|
||||
public override async Task OnConversationInitialized(Conversation conversation)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ public class ReadImageFn : IFunctionCallback
|
|||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
||||
var wholeDialogs = conv.GetDialogHistory();
|
||||
var dialogs = AssembleFiles(conv.ConversationId, wholeDialogs);
|
||||
var agent = await agentService.LoadAgent(BuiltInAgentId.UtilityAssistant);
|
||||
var dialogs = AssembleFiles(conv.ConversationId, args?.ImageUrls, wholeDialogs);
|
||||
var agentId = !string.IsNullOrWhiteSpace(message.CurrentAgentId) ? message.CurrentAgentId : BuiltInAgentId.UtilityAssistant;
|
||||
var agent = await agentService.LoadAgent(agentId);
|
||||
var fileAgent = new Agent
|
||||
{
|
||||
Id = agent?.Id ?? Guid.Empty.ToString(),
|
||||
|
|
@ -38,7 +39,7 @@ public class ReadImageFn : IFunctionCallback
|
|||
return true;
|
||||
}
|
||||
|
||||
private List<RoleDialogModel> AssembleFiles(string conversationId, List<RoleDialogModel> dialogs)
|
||||
private List<RoleDialogModel> AssembleFiles(string conversationId, IEnumerable<string>? imageUrls, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
if (dialogs.IsNullOrEmpty())
|
||||
{
|
||||
|
|
@ -66,6 +67,18 @@ public class ReadImageFn : IFunctionCallback
|
|||
}).ToList();
|
||||
}
|
||||
|
||||
if (!imageUrls.IsNullOrEmpty())
|
||||
{
|
||||
var lastDialog = dialogs.Last();
|
||||
var files = lastDialog.Files ?? [];
|
||||
var addnFiles = imageUrls.Select(x => x?.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(x => new BotSharpFile { FileUrl = x }).ToList();
|
||||
|
||||
files.AddRange(addnFiles);
|
||||
lastDialog.Files = files;
|
||||
}
|
||||
|
||||
return dialogs;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,4 +11,12 @@ public class LlmContextIn
|
|||
[JsonPropertyName("image_description")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? ImageDescription { get; set; }
|
||||
|
||||
//[JsonPropertyName("image_url")]
|
||||
//[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
//public string? ImageUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("image_urls")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public IEnumerable<string>? ImageUrls { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@
|
|||
"user_request": {
|
||||
"type": "string",
|
||||
"description": "The request posted by user, which is related to analyzing requested images. User can request for multiple images to process at one time."
|
||||
},
|
||||
"image_urls": {
|
||||
"type": "array",
|
||||
"description": "The image, photo or picture urls that user requests for analysis. They typically start with 'http' or 'https'. If user doesn't include any url, then leave this array empty. Please remove any duplicated urls",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The image, photo or picture url that user requests for analysis. It typically starts with http or https."
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [ "user_request" ]
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ public class AgentDocument : MongoBase
|
|||
public bool IsPublic { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public bool MergeUtility { get; set; }
|
||||
public int? MaxMessageCount { get; set; }
|
||||
public List<ChannelInstructionMongoElement> ChannelInstructions { get; set; }
|
||||
public List<AgentTemplateMongoElement> Templates { get; set; }
|
||||
public List<FunctionDefMongoElement> Functions { get; set; }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ public class ConversationDocument : MongoBase
|
|||
public string UserId { get; set; }
|
||||
public string? TaskId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string TitleAlias { get; set; }
|
||||
public string Channel { get; set; }
|
||||
public string ChannelId { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ public class CrontabItemDocument : MongoBase
|
|||
public string Cron { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public int ExecutionCount { get; set; }
|
||||
public int MaxExecutionCount { get; set; }
|
||||
public int ExpireSeconds { get; set; }
|
||||
public IEnumerable<CronTaskMongoElement> Tasks { get; set; } = [];
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
|
|
@ -25,6 +28,9 @@ public class CrontabItemDocument : MongoBase
|
|||
Cron = item.Cron,
|
||||
Title = item.Title,
|
||||
Description = item.Description,
|
||||
ExecutionCount = item.ExecutionCount,
|
||||
MaxExecutionCount = item.MaxExecutionCount,
|
||||
ExpireSeconds = item.ExpireSeconds,
|
||||
Tasks = item.Tasks?.Select(x => CronTaskMongoElement.ToDomainElement(x))?.ToArray() ?? [],
|
||||
CreatedTime = item.CreatedTime
|
||||
};
|
||||
|
|
@ -41,6 +47,9 @@ public class CrontabItemDocument : MongoBase
|
|||
Cron = item.Cron,
|
||||
Title = item.Title,
|
||||
Description = item.Description,
|
||||
ExecutionCount = item.ExecutionCount,
|
||||
MaxExecutionCount = item.MaxExecutionCount,
|
||||
ExpireSeconds = item.ExpireSeconds,
|
||||
Tasks = item.Tasks?.Select(x => CronTaskMongoElement.ToMongoElement(x))?.ToList() ?? [],
|
||||
CreatedTime = item.CreatedTime
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public partial class MongoRepository
|
|||
{
|
||||
public void UpdateAgent(Agent agent, AgentField field)
|
||||
{
|
||||
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
|
||||
if (agent == null || string.IsNullOrWhiteSpace(agent.Id)) return;
|
||||
|
||||
switch (field)
|
||||
{
|
||||
|
|
@ -58,6 +58,9 @@ public partial class MongoRepository
|
|||
case AgentField.Utility:
|
||||
UpdateAgentUtilities(agent.Id, agent.MergeUtility, agent.Utilities);
|
||||
break;
|
||||
case AgentField.MaxMessageCount:
|
||||
UpdateAgentMaxMessageCount(agent.Id, agent.MaxMessageCount);
|
||||
break;
|
||||
case AgentField.All:
|
||||
UpdateAgentAllFields(agent);
|
||||
break;
|
||||
|
|
@ -158,10 +161,8 @@ public partial class MongoRepository
|
|||
|
||||
private void UpdateAgentInstructions(string agentId, string instruction, List<ChannelInstruction>? channelInstructions)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(agentId)) return;
|
||||
|
||||
var instructionElements = channelInstructions?.Select(x => ChannelInstructionMongoElement.ToMongoElement(x))?
|
||||
.ToList() ?? new List<ChannelInstructionMongoElement>();
|
||||
.ToList() ?? [];
|
||||
|
||||
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
|
||||
var update = Builders<AgentDocument>.Update
|
||||
|
|
@ -200,7 +201,7 @@ public partial class MongoRepository
|
|||
|
||||
private void UpdateAgentResponses(string agentId, List<AgentResponse> responses)
|
||||
{
|
||||
if (responses == null) return;
|
||||
if (responses == null || string.IsNullOrWhiteSpace(agentId)) return;
|
||||
|
||||
var responsesToUpdate = responses.Select(r => AgentResponseMongoElement.ToMongoElement(r)).ToList();
|
||||
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
|
||||
|
|
@ -249,6 +250,16 @@ public partial class MongoRepository
|
|||
_dc.Agents.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
private void UpdateAgentMaxMessageCount(string agentId, int? maxMessageCount)
|
||||
{
|
||||
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
|
||||
var update = Builders<AgentDocument>.Update
|
||||
.Set(x => x.MaxMessageCount, maxMessageCount)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
|
||||
_dc.Agents.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
private void UpdateAgentAllFields(Agent agent)
|
||||
{
|
||||
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agent.Id);
|
||||
|
|
@ -258,6 +269,7 @@ public partial class MongoRepository
|
|||
.Set(x => x.Disabled, agent.Disabled)
|
||||
.Set(x => x.MergeUtility, agent.MergeUtility)
|
||||
.Set(x => x.Type, agent.Type)
|
||||
.Set(x => x.MaxMessageCount, agent.MaxMessageCount)
|
||||
.Set(x => x.Profiles, agent.Profiles)
|
||||
.Set(x => x.RoutingRules, agent.RoutingRules.Select(r => RoutingRuleMongoElement.ToMongoElement(r)).ToList())
|
||||
.Set(x => x.Instruction, agent.Instruction)
|
||||
|
|
@ -277,7 +289,7 @@ public partial class MongoRepository
|
|||
#endregion
|
||||
|
||||
|
||||
public Agent? GetAgent(string agentId)
|
||||
public Agent? GetAgent(string agentId, bool basicsOnly = false)
|
||||
{
|
||||
var agent = _dc.Agents.AsQueryable().FirstOrDefault(x => x.Id == agentId);
|
||||
if (agent == null) return null;
|
||||
|
|
@ -420,6 +432,7 @@ public partial class MongoRepository
|
|||
InheritAgentId = x.InheritAgentId,
|
||||
Disabled = x.Disabled,
|
||||
MergeUtility = x.MergeUtility,
|
||||
MaxMessageCount = x.MaxMessageCount,
|
||||
Profiles = x.Profiles,
|
||||
RoutingRules = x.RoutingRules?.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?.ToList() ?? [],
|
||||
LlmConfig = AgentLlmConfigMongoElement.ToMongoElement(x.LlmConfig),
|
||||
|
|
@ -513,6 +526,7 @@ public partial class MongoRepository
|
|||
Type = agentDoc.Type,
|
||||
InheritAgentId = agentDoc.InheritAgentId,
|
||||
Profiles = agentDoc.Profiles,
|
||||
MaxMessageCount = agentDoc.MaxMessageCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,17 @@ public partial class MongoRepository
|
|||
|
||||
_dc.Conversations.UpdateOne(filterConv, updateConv);
|
||||
}
|
||||
public void UpdateConversationTitleAlias(string conversationId, string titleAlias)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId)) return;
|
||||
|
||||
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
|
||||
var updateConv = Builders<ConversationDocument>.Update
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow)
|
||||
.Set(x => x.TitleAlias, titleAlias);
|
||||
|
||||
_dc.Conversations.UpdateOne(filterConv, updateConv);
|
||||
}
|
||||
|
||||
public bool UpdateConversationTags(string conversationId, List<string> tags)
|
||||
{
|
||||
|
|
@ -301,6 +312,10 @@ public partial class MongoRepository
|
|||
{
|
||||
convFilters.Add(convBuilder.Regex(x => x.Title, new BsonRegularExpression(filter.Title, "i")));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(filter?.TitleAlias))
|
||||
{
|
||||
convFilters.Add(convBuilder.Regex(x => x.Title, new BsonRegularExpression(filter.TitleAlias, "i")));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(filter?.AgentId))
|
||||
{
|
||||
convFilters.Add(convBuilder.Eq(x => x.AgentId, filter.AgentId));
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class RoutingConversationHook: ConversationHookBase
|
|||
|
||||
// Render by template
|
||||
var templateService = _services.GetRequiredService<IResponseTemplateService>();
|
||||
var response = await templateService.RenderIntentResponse(_agent.Id, message);
|
||||
var response = await templateService.RenderIntentResponse(Agent.Id, message);
|
||||
|
||||
if (!string.IsNullOrEmpty(response))
|
||||
{
|
||||
|
|
@ -54,7 +54,7 @@ public class RoutingConversationHook: ConversationHookBase
|
|||
public override async Task OnResponseGenerated(RoleDialogModel message)
|
||||
{
|
||||
var routerSettings = _services.GetRequiredService<RoutingSettings>();
|
||||
bool saveFlag = _agent.Type != AgentType.Routing;
|
||||
bool saveFlag = Agent.Type != AgentType.Routing;
|
||||
|
||||
if (saveFlag)
|
||||
{
|
||||
|
|
@ -63,7 +63,7 @@ public class RoutingConversationHook: ConversationHookBase
|
|||
var rootDataPath = agentService.GetDataDir();
|
||||
|
||||
string rawDataDir = Path.Combine(rootDataPath, "raw_data", $"agent.{message.CurrentAgentId}.txt");
|
||||
var lastThreeDialogs = _dialogs.Where(x => x.Role == AgentRole.User || x.Role == AgentRole.Assistant)
|
||||
var lastThreeDialogs = Dialogs.Where(x => x.Role == AgentRole.User || x.Role == AgentRole.Assistant)
|
||||
.Select(x => x.Content.Replace('\r', ' ').Replace('\n', ' '))
|
||||
.TakeLast(3)
|
||||
.ToArray();
|
||||
|
|
|
|||
63
tests/UnitTest/MainTest.cs
Normal file
63
tests/UnitTest/MainTest.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using BotSharp.Abstraction.Conversations;
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class MainTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestConversationHookProvider()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddSingleton<IConversationHook, TestHookC>();
|
||||
services.AddSingleton<IConversationHook, TestHookA>();
|
||||
services.AddSingleton<IConversationHook, TestHookB>();
|
||||
|
||||
services.AddSingleton<ConversationHookProvider>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var conversationHookProvider = serviceProvider.GetService<ConversationHookProvider>();
|
||||
|
||||
Assert.AreEqual(3, conversationHookProvider.Hooks.Count());
|
||||
|
||||
var prevHook = default(IConversationHook);
|
||||
|
||||
// Assert priority
|
||||
foreach (var hook in conversationHookProvider.HooksOrderByPriority)
|
||||
{
|
||||
if (prevHook != null)
|
||||
{
|
||||
Assert.IsTrue(prevHook.Priority < hook.Priority);
|
||||
}
|
||||
|
||||
prevHook = hook;
|
||||
}
|
||||
}
|
||||
|
||||
class TestHookA : ConversationHookBase
|
||||
{
|
||||
public TestHookA()
|
||||
{
|
||||
Priority = 1;
|
||||
}
|
||||
}
|
||||
|
||||
class TestHookB : ConversationHookBase
|
||||
{
|
||||
public TestHookB()
|
||||
{
|
||||
Priority = 2;
|
||||
}
|
||||
}
|
||||
|
||||
class TestHookC : ConversationHookBase
|
||||
{
|
||||
public TestHookC()
|
||||
{
|
||||
Priority = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,10 +13,14 @@
|
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
namespace UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class UnitTest1
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestMethod1()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue