Merge branch 'master' into lida_Dev

This commit is contained in:
AnonymousDotNet 2024-12-18 17:00:12 +08:00
commit e58a458329
61 changed files with 931 additions and 107 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -190,10 +190,10 @@
<ItemGroup>
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.6.0" />
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.7.0" />
<PackageReference Include="Fluid.Core" Version="2.11.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Nanoid" Version="3.1.0" />
</ItemGroup>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -79,14 +79,12 @@ public class RedisPublisher : IEventPublisher
return exists;
}
private NameValueEntry[] AssembleMessage(RedisValue message, int retry = 0)
private NameValueEntry[] AssembleMessage(RedisValue message)
{
return
[
new NameValueEntry("message", message),
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")),
new NameValueEntry("machine", Environment.MachineName),
new NameValueEntry("retry", retry),
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
];
}
@ -102,10 +100,8 @@ public class RedisPublisher : IEventPublisher
try
{
var message = entry.Values.First(x => x.Name == "message").Value;
var retryKv = entry.Values.FirstOrDefault(x => x.Name == "retry");
int.TryParse(retryKv.Value, out int retry);
var messageId = await db.StreamAddAsync(channel,
AssembleMessage(message, retry: retry + 1),
AssembleMessage(message),
maxLength: 1000 * 10000);
_logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})");

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@
},
{
"type": "planner",
"field": "Two-Stage-Planner"
"field": "SQL-Planner"
}
]
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace BotSharp.OpenAPI.ViewModels.Conversations;
public class UpdateConversationTitleAliasModel
{
[Required]
public string NewTitleAlias { get; set; }
}

View file

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

View file

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

View file

@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.0.0" />
<PackageReference Include="MongoDB.Driver" Version="3.1.0" />
</ItemGroup>
<ItemGroup>

View file

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

View file

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

View file

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

View file

@ -69,6 +69,36 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-planner-plan_summary.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\functions\plan_primary_stage.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\functions\plan_secondary_stage.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\functions\sql_review.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\functions\sql_generation.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\templates\two_stage.1st.plan.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\templates\two_stage.2nd.plan.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\templates\two_stage.next.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\da7aad2c-8112-48a2-ab7b-1f87da524741\templates\two_stage.summarize.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

View file

@ -1,9 +1,8 @@
using Microsoft.AspNetCore.Http;
namespace BotSharp.Plugin.Planner.Enums;
public class PlannerAgentId
{
public const string TwoStagePlanner = "282a7128-69a1-44b0-878c-a9159b88f3b9";
public const string SequentialPlanner = "3e75e818-a139-48a8-9e22-4662548c13a3";
public const string SqlPlanner = "da7aad2c-8112-48a2-ab7b-1f87da524741";
}

View file

@ -68,15 +68,6 @@ public class SummaryPlanFn : IFunctionCallback
var summary = await GetAiResponse(plannerAgent);
message.Content = summary.Content;
// Emit event if the sql statement is generated by planner
var args = JsonSerializer.Deserialize<SummaryPlan>(message.FunctionArgs);
if (args != null && !args.IsSqlTemplate && args.ContainsSqlStatements)
{
await HookEmitter.Emit<IPlanningHook>(_services, async hook =>
await hook.OnSourceCodeGenerated(nameof(TwoStageTaskPlanner), message, "sql")
);
}
await HookEmitter.Emit<IPlanningHook>(_services, async hook =>
await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message)
);

View file

@ -1,4 +1,5 @@
using BotSharp.Plugin.Planner.Sequential;
using BotSharp.Plugin.Planner.SqlGeneration;
using BotSharp.Plugin.Planner.TwoStaging;
namespace BotSharp.Plugin.Planner;
@ -16,13 +17,15 @@ public class PlannerPlugin : IBotSharpPlugin
public string[] AgentIds =>
[
PlannerAgentId.TwoStagePlanner,
PlannerAgentId.SequentialPlanner
PlannerAgentId.SequentialPlanner,
PlannerAgentId.SqlPlanner
];
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<ITaskPlanner, SequentialPlanner>();
services.AddScoped<ITaskPlanner, TwoStageTaskPlanner>();
services.AddScoped<ITaskPlanner, SqlGenerationPlanner>();
services.AddScoped<IAgentHook, PlannerAgentHook>();
services.AddScoped<IAgentUtilityHook, PlannerUtilityHook>();
}

View file

@ -0,0 +1,127 @@
using BotSharp.Plugin.Planner.TwoStaging;
using BotSharp.Plugin.Planner.TwoStaging.Models;
namespace BotSharp.Plugin.Planner.Functions;
public class SqlGenerationFn : IFunctionCallback
{
public string Name => "sql_generation";
public string Indication => "Organizing and summarizing the final SQL statements.";
private readonly IServiceProvider _services;
private readonly ILogger<SqlGenerationFn> _logger;
public SqlGenerationFn(
IServiceProvider services,
ILogger<SqlGenerationFn> logger)
{
_services = services;
_logger = logger;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var fn = _services.GetRequiredService<IRoutingService>();
var agentService = _services.GetRequiredService<IAgentService>();
var states = _services.GetRequiredService<IConversationStateService>();
states.SetState("max_tokens", "4096");
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
var taskRequirement = states.GetState("requirement_detail");
// Get table names
var steps = states.GetState("planning_result").JsonArrayContent<SecondStagePlan>();
var allTables = new List<string>();
var ddlStatements = string.Empty;
var domainKnowledge = states.GetState("planning_result");
domainKnowledge += "\r\n" + states.GetState("domain_knowledges");
var dictionaryItems = states.GetState("dictionary_items");
var excelImportResult = states.GetState("excel_import_result");
foreach (var step in steps)
{
allTables.AddRange(step.Tables);
}
var distinctTables = allTables.Distinct().ToList();
var msgCopy = RoleDialogModel.From(message);
msgCopy.FunctionArgs = JsonSerializer.Serialize(new
{
tables = distinctTables,
});
await fn.InvokeFunction("sql_table_definition", msgCopy);
ddlStatements += "\r\n" + msgCopy.Content;
states.SetState("table_ddls", ddlStatements);
// Summarize and generate query
var prompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, domainKnowledge, dictionaryItems, ddlStatements, excelImportResult);
_logger.LogInformation($"Summary plan prompt:\r\n{prompt}");
var plannerAgent = new Agent
{
Id = PlannerAgentId.TwoStagePlanner,
Name = Name,
Instruction = prompt,
LlmConfig = currentAgent.LlmConfig
};
var summary = await GetAiResponse(plannerAgent);
message.Content = summary.Content;
/*await HookEmitter.Emit<IPlanningHook>(_services, async hook =>
await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message)
);*/
return true;
}
private async Task<string> GetSummaryPlanPrompt(RoleDialogModel message, string taskDescription, string domainKnowledge, string dictionaryItems, string ddlStatement, string excelImportResult)
{
var agentService = _services.GetRequiredService<IAgentService>();
var render = _services.GetRequiredService<ITemplateRender>();
var knowledgeHooks = _services.GetServices<IKnowledgeHook>();
var agent = await agentService.GetAgent(PlannerAgentId.TwoStagePlanner);
var template = agent.Templates.FirstOrDefault(x => x.Name == "two_stage.summarize")?.Content ?? string.Empty;
var additionalRequirements = new List<string>();
await HookEmitter.Emit<IPlanningHook>(_services, async x =>
{
var requirement = await x.GetSummaryAdditionalRequirements(nameof(TwoStageTaskPlanner), message);
additionalRequirements.Add(requirement);
});
var globalKnowledges = new List<string>();
foreach (var hook in knowledgeHooks)
{
var k = await hook.GetGlobalKnowledges(message);
globalKnowledges.AddRange(k);
}
return render.Render(template, new Dictionary<string, object>
{
{ "task_description", taskDescription },
{ "summary_requirements", string.Join("\r\n", additionalRequirements) },
{ "global_knowledges", globalKnowledges },
{ "domain_knowledges", domainKnowledge },
{ "dictionary_items", dictionaryItems },
{ "table_structure", ddlStatement },
{ "excel_import_result", excelImportResult }
});
}
private async Task<RoleDialogModel> GetAiResponse(Agent plannerAgent)
{
var conv = _services.GetRequiredService<IConversationService>();
var wholeDialogs = conv.GetDialogHistory();
// Append text
wholeDialogs.Last().Content += "\n\nIf the table structure didn't mention auto incremental, the data field id needs to insert id manually and you need to use max(id).\nFor example, you should use SET @id = select max(id) from table;";
wholeDialogs.Last().Content += "\n\nTry if you can generate a single query to fulfill the needs.";
var completion = CompletionProvider.GetChatCompletion(_services,
provider: plannerAgent.LlmConfig.Provider,
model: plannerAgent.LlmConfig.Model);
return await completion.GetChatCompletions(plannerAgent, wholeDialogs);
}
}

View file

@ -0,0 +1,38 @@
using BotSharp.Plugin.Planner.SqlGeneration.Models;
using BotSharp.Plugin.Planner.TwoStaging;
using BotSharp.Plugin.Planner.TwoStaging.Models;
namespace BotSharp.Plugin.Planner.SqlGeneration.Functions;
public class SqlReviewFn : IFunctionCallback
{
public string Name => "sql_review";
public string Indication => "Currently reviewing SQL statement";
private readonly IServiceProvider _services;
private readonly ILogger<SqlReviewFn> _logger;
public SqlReviewFn(
IServiceProvider services,
ILogger<SqlReviewFn> logger)
{
_services = services;
_logger = logger;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<SqlReviewArgs>(message.FunctionArgs);
if (!message.Content.StartsWith("```sql"))
{
message.Content = $"```sql\r\n{args.SqlStatement}\r\n```";
}
if (args != null && !args.IsSqlTemplate && args.ContainsSqlStatements)
{
await HookEmitter.Emit<IPlanningHook>(_services, async hook =>
await hook.OnSourceCodeGenerated(nameof(TwoStageTaskPlanner), message, "sql")
);
}
return true;
}
}

View file

@ -0,0 +1,39 @@
namespace BotSharp.Plugin.Planner.SqlGeneration.Models;
public class FirstStagePlan
{
[JsonPropertyName("task_detail")]
public string Task { get; set; } = "";
//[JsonPropertyName("reason")]
//public string Reason { get; set; } = "";
[JsonPropertyName("step")]
public int Step { get; set; } = -1;
[JsonPropertyName("need_breakdown_task")]
public bool NeedAdditionalInformation { get; set; } = false;
[JsonPropertyName("need_lookup_dictionary")]
public bool NeedLookupDictionary { get; set; } = false;
[JsonPropertyName("related_tables")]
public string[] Tables { get; set; } = [];
[JsonPropertyName("has_found_relevant_knowledge")]
public bool HasFoundRelevantKnowledge { get; set; } = false;
//[JsonPropertyName("related_urls")]
//public string[] Urls { get; set; } = [];
//[JsonPropertyName("input_args")]
//public JsonDocument[] Parameters { get; set; } = [];
//[JsonPropertyName("output_results")]
//public string[] Results { get; set; } = [];
public override string ToString()
{
return $"STEP {Step}: {Task}";
}
}

View file

@ -0,0 +1,13 @@
namespace BotSharp.Plugin.Planner.SqlGeneration.Models;
public class PrimaryRequirementRequest
{
[JsonPropertyName("requirement_detail")]
public string Requirements { get; set; } = null!;
[JsonPropertyName("questions")]
public string[] Questions { get; set; } = [];
[JsonPropertyName("norm_questions")]
public string[] NormQuestions { get; set; } = [];
}

View file

@ -0,0 +1,19 @@
namespace BotSharp.Plugin.Planner.SqlGeneration.Models;
public class SecondStagePlan
{
[JsonPropertyName("related_tables")]
public string[] Tables { get; set; } = [];
[JsonPropertyName("need_lookup_dictionary")]
public bool NeedLookupDictionary { get; set; } = false;
[JsonPropertyName("description")]
public string Description { get; set; } = "";
[JsonPropertyName("input_args")]
public JsonDocument[] Parameters { get; set; } = [];
[JsonPropertyName("output_results")]
public string[] Results { get; set; } = [];
}

View file

@ -0,0 +1,13 @@
namespace BotSharp.Plugin.Planner.SqlGeneration.Models;
public class SecondaryBreakdownTask
{
[JsonPropertyName("task_description")]
public string TaskDescription { get; set; } = null!;
[JsonPropertyName("solution_search_question")]
public string SolutionQuestion { get; set; } = null!;
[JsonPropertyName("need_lookup_dictionary")]
public bool NeedLookupDictionary { get; set; }
}

View file

@ -0,0 +1,13 @@
namespace BotSharp.Plugin.Planner.SqlGeneration.Models;
public class SqlReviewArgs
{
[JsonPropertyName("is_sql_template")]
public bool IsSqlTemplate { get; set; } = false;
[JsonPropertyName("contains_sql_statements")]
public bool ContainsSqlStatements { get; set; } = false;
[JsonPropertyName("sql_statement")]
public string SqlStatement { get; set; } = string.Empty;
}

View file

@ -0,0 +1,107 @@
namespace BotSharp.Plugin.Planner.SqlGeneration;
public class SqlGenerationPlanner : ITaskPlanner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public string Name => "SQL-Planner";
public int MaxLoopCount => 10;
public SqlGenerationPlanner(IServiceProvider services, ILogger<SqlGenerationPlanner> logger)
{
_services = services;
_logger = logger;
}
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs)
{
var inst = new FunctionCallFromLlm();
var nextStepPrompt = await GetNextStepPrompt(router);
// chat completion
var completion = CompletionProvider.GetChatCompletion(_services,
provider: router?.LlmConfig?.Provider,
model: router?.LlmConfig?.Model);
// text completion
dialogs = new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, nextStepPrompt)
{
FunctionName = nameof(SqlGenerationPlanner),
MessageId = messageId
}
};
var response = await completion.GetChatCompletions(router, dialogs);
inst = response.Content.JsonContent<FunctionCallFromLlm>();
// Fix LLM malformed response
ReasonerHelper.FixMalformedResponse(_services, inst);
return inst;
}
public List<RoleDialogModel> BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
{
var question = inst.Response;
var taskAgentDialogs = new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, question)
{
MessageId = message.MessageId,
}
};
return taskAgentDialogs;
}
public bool AfterHandleContext(List<RoleDialogModel> dialogs, List<RoleDialogModel> taskAgentDialogs)
{
dialogs.AddRange(taskAgentDialogs.Skip(1));
return true;
}
public async Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
{
// Set user content as Planner's question
message.FunctionName = inst.Function;
message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments);
return true;
}
public async Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
{
var context = _services.GetRequiredService<IRoutingContext>();
if (message.StopCompletion)
{
context.Empty(reason: $"Agent queue is cleared by {nameof(SqlGenerationPlanner)}");
return false;
}
if (dialogs.Last().Role == AgentRole.Assistant)
{
context.Empty();
return false;
}
var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.ResetRecursiveCounter();
return true;
}
private async Task<string> GetNextStepPrompt(Agent router)
{
var agentService = _services.GetRequiredService<IAgentService>();
var planner = await agentService.LoadAgent(PlannerAgentId.TwoStagePlanner);
var template = planner.Templates.First(x => x.Name == "two_stage.next").Content;
var states = _services.GetRequiredService<IConversationStateService>();
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
{ StateConst.EXPECTED_ACTION_AGENT, states.GetState(StateConst.EXPECTED_ACTION_AGENT) },
{ StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) }
});
}
}

View file

@ -0,0 +1,19 @@
{
"id": "da7aad2c-8112-48a2-ab7b-1f87da524741",
"name": "SQL-Planner",
"description": "Plan feasible steps for user task related to sql generation, generate sql statement and/or review the sql statement that can be derived from context",
"type": "planning",
"createdDateTime": "2023-08-27T10:39:00Z",
"updatedDateTime": "2023-08-27T14:39:00Z",
"iconUrl": "https://e7.pngegg.com/pngimages/775/350/png-clipart-action-plan-computer-icons-plan-miscellaneous-text-thumbnail.png",
"disabled": false,
"isPublic": true,
"profiles": [ "planning" ],
"mergeUtility": true,
"utilities": [],
"llmConfig": {
"provider": "openai",
"model": "gpt-4o-2024-11-20",
"max_recursion_depth": 10
}
}

View file

@ -0,0 +1,47 @@
{
"name": "plan_primary_stage",
"description": "Plan the high level steps to finish the task",
"parameters": {
"type": "object",
"properties": {
"requirement_detail": {
"type": "string",
"description": "User requirements related to data tasks in detail, don't miss any information especially for those line items, values and numbers."
},
"questions": {
"type": "array",
"description": "Break down user data requirements in details and in multiple ways, don't miss any entity type/value. The output format must be string array.",
"items": {
"type": "string",
"description": "Question converted from requirement in different ways to search in the knowledge base, be short and you can refer to the global knowledge.One question should contain only one main topic that with one entity type."
}
},
"norm_questions": {
"type": "array",
"description": "normalize the generated questions, remove specific entity value. The output format must be string array.",
"items": {
"type": "string",
"description": "Normalized question"
}
},
"entities": {
"type": "array",
"description": "entities with type and value",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "entity type"
},
"value": {
"type": "string",
"description": "entity value"
}
}
}
}
},
"required": [ "requirement_detail", "questions" ]
}
}

View file

@ -0,0 +1,18 @@
{
"name": "plan_secondary_stage",
"description": "Based on the primary stage planning, make more detail steps of the second stage if the primary stage needs more information.",
"parameters": {
"type": "object",
"properties": {
"task_description": {
"type": "string",
"description": "task description from primary steps"
},
"solution_search_question": {
"type": "string",
"description": "Generate question to find the knowledge for text. Be short"
}
},
"required": [ "task_description", "solution_search_question" ]
}
}

View file

@ -0,0 +1,26 @@
{
"name": "sql_generation",
"description": "Based on the planning steps, summarize the planning steps and output final steps.",
"parameters": {
"type": "object",
"properties": {
"is_sql_template": {
"type": "boolean",
"description": "If user request is to generate sql template instead of actual sql statement."
},
"contains_sql_statements": {
"type": "boolean",
"description": "Set to true if the response contains sql statements."
},
"related_tables": {
"type": "array",
"description": "table name in planning steps",
"items": {
"type": "string",
"description": "table name"
}
}
},
"required": [ "related_tables", "is_sql_template", "contains_sql_statements" ]
}
}

View file

@ -0,0 +1,22 @@
{
"name": "sql_review",
"description": "Verify and optimize sql statement",
"parameters": {
"type": "object",
"properties": {
"sql_statement": {
"type": "string",
"description": "sql statement, must including sql identifier that wrapped with ```sql \r\n```"
},
"is_sql_template": {
"type": "boolean",
"description": "If user request is to generate sql template instead of actual sql statement."
},
"contains_sql_statements": {
"type": "boolean",
"description": "Set to true if the response contains sql statements."
}
},
"required": [ "sql_statement", "is_sql_template", "contains_sql_statements" ]
}
}

View file

@ -0,0 +1,33 @@
You're a SQL planner and reviewer, your goal is using function sql_generation and sql_review to response.
You are going convert the user requirement into sql statements.
The user is dealing with a complex problem, and you need to break this complex problem into several small tasks to more easily solve the user's needs.
Follow these steps strictly and in order.
1. If user raised a new task, call plan_primary_stage to generate the primary plan.
If the sql response can be generate directly based on the context, directly go to step 6 to call function sql_review.
2. If need_lookup_dictionary is True, call verify_dictionary_term to verify or get the enum/term/dictionary value. Pull id and name.
* If you no items retured, you can pull 100 records from the table and look for the match.
* If need_lookup_dictionary is False, skip calling verify_dictionary_term.
3. If need_breakdown_task is true, call plan_secondary_stage for the specific primary stage.
4. Repeat step 3 until you processed all the primary steps.
5. Call sql_generation function to generate SQL statements.
6. Call sql_review function to review SQL statements. This is the step you must go through before reply to the user.
{% if global_knowledges != empty -%}
=====
Global Knowledge:
Current date time is: {{ "now" | date: "%Y-%m-%d %H:%M" }}
{% for k in global_knowledges %}
{{ k }}
{% endfor %}
=====
{%- endif %}
==== IMPORTANT SYSTEM INSTRUCTION ====
* The verify_dictionary_term function CAN'T generate INSERT SQL Statement.
* The table name must come from the relevant knowledge. has_found_relevant_knowledge must be true.
* Do not introduce your actions or intentions in any way.
* You MUST explicitly call function sql_review even the sql query is provided in previous context.

View file

@ -0,0 +1,39 @@
You are a Task Planner. you will breakdown user business requirements into excutable sub-tasks.
Thinking process:
1. Reference to "Domain Knowledge" if there is relevant knowledge;
2. Breakdown task into subtasks.
- The subtask should contain all needed parameters for subsequent steps.
- If limited information provided and there are furture information needed, or miss relationship between steps, set the need_breakdown_task to true.
- If there is extra knowledge or relationship needed between steps, set the need_breakdown_task to true for both steps.
- If the solution mentioned "related solutions" is needed, set the need_breakdown_task to true.
- You should find the relationships between data structure based on the domain knowledge strictly. If lack of information, set the need_breakdown_task to true.
- If you need to lookup the dictionary to verify or get the enum/term/dictionary value(exclude example data from attachment), set the need_lookup_dictionary to true.
- Don't set need_lookup_dictionary to true for attachment data.
- Seperate the dictionary lookup and need additional information/knowledge into different subtask.
3. Input argument must reference to corresponding variable name that retrieved by previous steps, variable name must start with '@';
4. Output all the subtasks as much detail as possible in JSON: [{{ response_format }}]
5. You can NOT generate the final query before calling function plan_summary.
Note:
* If the task includes repeat steps,e.g.same steps for multiple elements, only generate a single detailed solution without repeating steps for each elements.
{% if global_knowledges != empty -%}
=====
Global Knowledge:
{% for k in global_knowledges %}
{{ k }}
{% endfor %}
{%- endif %}
{% if domain_knowledges != empty -%}
=====
Domain Knowledge:
{% for k in domain_knowledges %}
{{ k }}
{% endfor %}
{%- endif %}
=====
Task description:
{{ task_description }}

View file

@ -0,0 +1,22 @@
Reference to "Primary Planning" and the additional knowledge included. Breakdown task into multiple steps.
* The step should contains all needed parameters.
* The parameters can be extracted from the original task.
* You need to list all the steps in detail. Finding relationships should also be a step.
* When generate the steps, you should find the relationships between data structure based on the provided knowledge strictly.
* If need_lookup_dictionary is true, call verify_dictionary_term to verify or get the enum/term/dictionary value. Pull id and name/code.
* Output all the steps as much detail as possible in JSON: [{{ response_format }}]
Additional Requirements:
* "output_results" is variable name that needed to be used in the next step.
=====
Sub Task Description:
{{ task_description }}
=====
Primary Planning:
{{ primary_plan }}
=====
Additional Knowledge:
{{ additional_knowledge }}

View file

@ -0,0 +1,12 @@
What is the next step based on the CONVERSATION?
Route to the last handling agent in priority.
{% if expected_next_action_agent != empty -%}
Expected next action agent is {{ expected_next_action_agent }}.
{%- else -%}
Next action agent is inferred based on user lastest response.
{%- endif %}
{% if expected_user_goal_agent != empty -%}
Expected user goal agent is {{ expected_user_goal_agent }}.
{%- else -%}
User goal agent is inferred based on user initial request.
{%- endif %}

View file

@ -0,0 +1,29 @@
You are a planning summarizer. You will generate the final output in JSON format based on the task description, knowledge and related table structure and relationship.
Generate a simple business explaination of the quried data for the non tech audience. call sql_review as the final step after generating the sql statement.
Requirements:
{{ summary_requirements }}
=====
Task description:
{{ task_description }}
=====
Global Knowledges:
{{ global_knowledges }}
=====
Domain Knowledges:
{{ domain_knowledges }}
=====
Dictionary Items:
{{ dictionary_items }}
=====
Table Structure:
{{ table_structure }}
=====
Attached Excel Information:
{{ excel_import_result }}

View file

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

View file

@ -4,15 +4,19 @@
"parameters": {
"type": "object",
"properties": {
"table": {
"type": "string",
"description": "table name"
"tables": {
"type": "array",
"description": "table name in planning steps",
"items": {
"type": "string",
"description": "table name"
}
},
"reason": {
"type": "string",
"description": "the reason why you need to call sql_table_definition"
}
},
"required": [ "table", "reason" ]
"required": [ "tables", "reason" ]
}
}

View file

@ -1,7 +1,7 @@
{
"id": "beda4c12-e1ec-4b4b-b328-3df4a6687c4f",
"name": "SQL Driver",
"description": "Transfer to this Agent only when executable SQL statements are explicitly provided in the context.",
"description": "Transfer to this Agent when user mentions to execute the sql statement. Only call when executable SQL statements are explicitly provided in the context.",
"iconUrl": "https://cdn-icons-png.flaticon.com/512/3161/3161158.png",
"type": "task",
"createdDateTime": "2023-11-15T13:49:00Z",

View file

@ -4,11 +4,15 @@
"parameters": {
"type": "object",
"properties": {
"table": {
"type": "string",
"description": "table need to check"
"tables": {
"type": "array",
"description": "table name in planning steps",
"items": {
"type": "string",
"description": "table name"
}
}
},
"required": [ "table" ]
"required": [ "tables" ]
}
}

View file

@ -1,4 +1,4 @@
You're a SQL driver who can find the database information or query the data.
You're a SQL driver who can execute the sql statement.
Your response must meet below requirements:
* You can only execute the SQL from the conversation. You can't generate one by yourself;

View file

@ -12,15 +12,12 @@
<ItemGroup>
<Compile Remove="Drivers\SeleniumDriver\**" />
<Compile Remove="packages\**" />
<EmbeddedResource Remove="Drivers\SeleniumDriver\**" />
<EmbeddedResource Remove="packages\**" />
<None Remove="Drivers\SeleniumDriver\**" />
<None Remove="packages\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.48.0" />
<PackageReference Include="Microsoft.Playwright" Version="1.49.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.71" />
</ItemGroup>

View 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;
}
}
}
}

View file

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

View file

@ -1,11 +0,0 @@
namespace UnitTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}