Merge pull request #646 from Qtoss-AI/master
ExecuteSqlSelectAutonomous
This commit is contained in:
commit
b30f743a5a
|
|
@ -12,5 +12,5 @@ public class MessageInfo : ICacheKey
|
|||
public string StepId { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
public string GetCacheKey()
|
||||
=> $"{nameof(MessageInfo)}-{ContextId}";
|
||||
=> $"{nameof(MessageInfo)}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Planning;
|
||||
|
||||
public interface IPlanningHook
|
||||
{
|
||||
Task<string> GetSummaryAdditionalRequirements(string planner)
|
||||
=> Task.FromResult(string.Empty);
|
||||
Task OnPlanningCompleted(string planner, RoleDialogModel msg)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ public interface IBotSharpRepository
|
|||
|
||||
#region User
|
||||
User? GetUserByEmail(string email) => throw new NotImplementedException();
|
||||
User? GetUserByPhone(string phone) => throw new NotImplementedException();
|
||||
User? GetUserById(string id) => throw new NotImplementedException();
|
||||
User? GetUserByUserName(string userName) => throw new NotImplementedException();
|
||||
void CreateUser(User user) => throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@
|
|||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.5.1" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.11.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Nanoid" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ public static class BotSharpCoreExtensions
|
|||
var cacheSettings = new SharpCacheSettings();
|
||||
config.Bind("SharpCache", cacheSettings);
|
||||
services.AddSingleton(x => cacheSettings);
|
||||
services.AddSingleton<ICacheService, CacheService>();
|
||||
|
||||
services.AddSingleton<ICacheService, RedisCacheService>();
|
||||
|
||||
services.AddMemoryCache();
|
||||
|
||||
RegisterPlugins(services, config);
|
||||
ConfigureBotSharpOptions(services, configOptions);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BotSharp.Core.Infrastructures;
|
||||
|
||||
public class MemoryCacheService : ICacheService
|
||||
{
|
||||
private static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions
|
||||
{
|
||||
});
|
||||
private readonly BotSharpDatabaseSettings _settings;
|
||||
|
||||
public MemoryCacheService(BotSharpDatabaseSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key)
|
||||
{
|
||||
return (T?)(_cache.Get(key) ?? default(T));
|
||||
}
|
||||
|
||||
public async Task<object> GetAsync(string key, Type type)
|
||||
{
|
||||
return _cache.Get(key) ?? default;
|
||||
}
|
||||
|
||||
public async Task SetAsync<T>(string key, T value, TimeSpan? expiry)
|
||||
{
|
||||
_cache.Set(key, value, new MemoryCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = expiry
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,12 @@ using StackExchange.Redis;
|
|||
|
||||
namespace BotSharp.Core.Infrastructures;
|
||||
|
||||
public class CacheService : ICacheService
|
||||
public class RedisCacheService : ICacheService
|
||||
{
|
||||
private readonly BotSharpDatabaseSettings _settings;
|
||||
private static ConnectionMultiplexer redis = null!;
|
||||
|
||||
public CacheService(BotSharpDatabaseSettings settings)
|
||||
public RedisCacheService(BotSharpDatabaseSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
|
@ -68,6 +68,11 @@ public class CacheService : ICacheService
|
|||
return;
|
||||
}
|
||||
|
||||
if (redis == null)
|
||||
{
|
||||
redis = ConnectionMultiplexer.Connect(_settings.Redis);
|
||||
}
|
||||
|
||||
var db = redis.GetDatabase();
|
||||
await db.StringSetAsync(key, JsonConvert.SerializeObject(value), expiry);
|
||||
}
|
||||
|
|
@ -11,6 +11,11 @@ public partial class FileRepository
|
|||
return Users.FirstOrDefault(x => x.Email == email.ToLower());
|
||||
}
|
||||
|
||||
public User? GetUserByPhone(string phone)
|
||||
{
|
||||
return Users.FirstOrDefault(x => x.Phone == phone);
|
||||
}
|
||||
|
||||
public User? GetUserById(string id = null)
|
||||
{
|
||||
return Users.FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
|
||||
|
|
|
|||
|
|
@ -156,12 +156,10 @@ public class UserService : IUserService
|
|||
return default;
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
if (!isAuthenticatedByHook && Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
#endif
|
||||
|
||||
var accessToken = GenerateJwtToken(record);
|
||||
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
|
||||
|
|
@ -325,8 +323,24 @@ public class UserService : IUserService
|
|||
|
||||
public async Task<bool> SendVerificationCodeResetPassword(User user)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetUserByEmail(user.Email);
|
||||
|
||||
User? record = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Email))
|
||||
{
|
||||
record = db.GetUserByEmail(user.Email);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
record = db.GetUserByPhone(user.Phone);
|
||||
}
|
||||
if (record == null)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -349,8 +363,23 @@ public class UserService : IUserService
|
|||
|
||||
public async Task<bool> ResetUserPassword(User user)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetUserByEmail(user.Email);
|
||||
|
||||
User? record = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Email))
|
||||
{
|
||||
record = db.GetUserByEmail(user.Email);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
record = db.GetUserByPhone(user.Phone);
|
||||
}
|
||||
|
||||
if (record == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ public class UserController : ControllerBase
|
|||
}
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/user/verifycode")]
|
||||
public async Task<bool> SendVerificationCodeResetPassword([FromQuery] UserCreationModel user)
|
||||
public async Task<bool> SendVerificationCodeResetPassword([FromBody] UserCreationModel user)
|
||||
{
|
||||
return await _userService.SendVerificationCodeResetPassword(user.ToUser());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ public partial class MongoRepository
|
|||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
public User? GetUserByPhone(string phone)
|
||||
{
|
||||
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Phone == phone);
|
||||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
public User? GetUserById(string id)
|
||||
{
|
||||
var user = _dc.Users.AsQueryable()
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@
|
|||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\functions\plan_secondary_stage.json" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\functions\plan_summary.json" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.1st.next.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\database.summarize.MySql.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\database.summarize.SqlServer.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.2nd.plan.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.next.liquid" />
|
||||
<None Remove="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.summarize.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\planner_prompt.two_stage.1st.plan.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\plan_primary_stage.fn.liquid" />
|
||||
|
|
@ -41,7 +43,13 @@
|
|||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.1st.next.liquid">
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\database.summarize.mysql.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\database.summarize.sqlserver.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.next.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\282a7128-69a1-44b0-878c-a9159b88f3b9\templates\two_stage.2nd.plan.liquid">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace BotSharp.Plugin.Planner.Functions;
|
|||
public class PrimaryStagePlanFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "plan_primary_stage";
|
||||
|
||||
public string Indication => "Currently analyzing and breaking down user requirements.";
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<PrimaryStagePlanFn> _logger;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace BotSharp.Plugin.Planner.Functions;
|
|||
public class SecondaryStagePlanFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "plan_secondary_stage";
|
||||
|
||||
public string Indication => "Further analyzing and breaking down user sub-needs.";
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<SecondaryStagePlanFn> _logger;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Planning;
|
||||
using BotSharp.Plugin.Planner.TwoStaging;
|
||||
using BotSharp.Plugin.Planner.TwoStaging.Models;
|
||||
|
||||
namespace BotSharp.Plugin.Planner.Functions;
|
||||
|
|
@ -5,7 +7,7 @@ namespace BotSharp.Plugin.Planner.Functions;
|
|||
public class SummaryPlanFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "plan_summary";
|
||||
|
||||
public string Indication => "Organizing and summarizing the final output results.";
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<SummaryPlanFn> _logger;
|
||||
|
||||
|
|
@ -62,7 +64,9 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
|
||||
var summary = await GetAiResponse(plannerAgent);
|
||||
message.Content = summary.Content;
|
||||
message.StopCompletion = true;
|
||||
|
||||
await HookEmitter.Emit<IPlanningHook>(_services, x =>
|
||||
x.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -74,18 +78,20 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
|
||||
var agent = await agentService.GetAgent(BuiltInAgentId.Planner);
|
||||
var template = agent.Templates.FirstOrDefault(x => x.Name == "two_stage.summarize")?.Content ?? string.Empty;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
|
||||
var additionalRequirements = new List<string>();
|
||||
await HookEmitter.Emit<IPlanningHook>(_services, async x =>
|
||||
{
|
||||
Parameters = [JsonDocument.Parse("{}")],
|
||||
Results = [""]
|
||||
var requirement = await x.GetSummaryAdditionalRequirements(nameof(TwoStageTaskPlanner));
|
||||
additionalRequirements.Add(requirement);
|
||||
});
|
||||
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "table_structure", ddlStatement },
|
||||
{ "task_description", taskDescription },
|
||||
{ "summary_requirements", string.Join("\r\n",additionalRequirements) },
|
||||
{ "relevant_knowledges", relevantKnowledge },
|
||||
{ "response_format", responseFormat }
|
||||
{ "table_structure", ddlStatement },
|
||||
});
|
||||
}
|
||||
private async Task<RoleDialogModel> GetAiResponse(Agent plannerAgent)
|
||||
|
|
@ -94,8 +100,8 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
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) instead of LAST_INSERT_ID function.\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";
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace BotSharp.Plugin.Planner.TwoStaging.Models;
|
|||
public class SecondStagePlan
|
||||
{
|
||||
[JsonPropertyName("related_tables")]
|
||||
public string[] Tables { get; set; } = new string[0];
|
||||
public string[] Tables { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; } = "";
|
||||
|
|
@ -12,8 +12,8 @@ public class SecondStagePlan
|
|||
public string Tool { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("input_args")]
|
||||
public JsonDocument[] Parameters { get; set; } = new JsonDocument[0];
|
||||
public JsonDocument[] Parameters { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("output_results")]
|
||||
public string[] Results { get; set; } = new string[0];
|
||||
public string[] Results { get; set; } = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
|
|||
|
||||
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var nextStepPrompt = await GetNextStepPrompt(router);
|
||||
var inst = new FunctionCallFromLlm();
|
||||
var nextStepPrompt = await GetNextStepPrompt(router);
|
||||
|
||||
// chat completion
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
|
|
@ -125,7 +125,7 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
|
|||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var planner = await agentService.LoadAgent(BuiltInAgentId.Planner);
|
||||
var template = planner.Templates.First(x => x.Name == "two_stage.1st.next").Content;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
"profiles": [ "planning" ],
|
||||
"utilities": [ "two-stage-planner" ],
|
||||
"llmConfig": {
|
||||
"provider": "anthropic",
|
||||
"model": "claude-3-5-sonnet-20240620",
|
||||
"provider": "azure-openai",
|
||||
"model": "gpt-4o",
|
||||
"max_recursion_depth": 10
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,10 @@ Use the TwoStagePlanner approach to plan the overall implementation steps, follo
|
|||
2. If need_additional_information is true, call plan_secondary_stage for the specific primary stage.
|
||||
3. You must call plan_summary as the last planning step to summarize the final query.
|
||||
|
||||
*** IMPORTANT ***
|
||||
Don't run the planning process repeatedly if you have already got the result of user's request.
|
||||
|
||||
|
||||
{% if global_knowledges != empty -%}
|
||||
=====
|
||||
Global Knowledge:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
Try if you can generate a single query to fulfill the needs. The step should contains all needed parameters.
|
||||
The parameters can be extracted from the original task.
|
||||
If not, generate the query step by step based on the planning.
|
||||
|
||||
The query must exactly based on the provided table structure. And carefully review the foreign keys to make sure you include all the accurate information.
|
||||
|
||||
Note: Output should be only the sql query with sql comments that can be directly run in mysql database with version 8.0.
|
||||
|
||||
Don't use the sql statement that specify target table for update in FROM clause.
|
||||
For example, you CAN'T write query as below:
|
||||
INSERT INTO data_Service (Id, Name)
|
||||
VALUES ((SELECT MAX(Id) + 1 FROM data_Service), 'HVAC');
|
||||
|
||||
If the table structure didn't mention auto incremental, the data field id needs to insert id manually and you need to use max(id) instead of LAST_INSERT_ID function.
|
||||
For example, you should use SET @id = select max(id) from table;
|
||||
|
||||
* the alias of the table name in the sql query should be identical.
|
||||
*** the generated sql query MUST be basedd on the provided table structure. ***
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
Try if you can generate a SQL Server single query to fulfill the needs. The step should contains all needed parameters.
|
||||
The parameters can be extracted from the original task.
|
||||
If not, generate the query step by step based on the planning.
|
||||
|
||||
The query must exactly based on the provided table structure. And carefully review the foreign keys to make sure you include all the accurate information.
|
||||
|
||||
Note: Output should be only the sql query with sql comments that can be directly run in SQL Server.
|
||||
|
||||
*** the alias of the table name in the sql query should be identical. ***
|
||||
*** The generated sql query MUST be basedd on the provided table structure. ***
|
||||
*** All queries return a maximum of 10 records. ***
|
||||
*** Only select user friendly columns. ***
|
||||
|
|
@ -1,25 +1,7 @@
|
|||
You are a planning summarizer and sql generator. You will convert the requirement into the excutable MySQL query statement based on the task description and related table structure and relationship.
|
||||
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.
|
||||
|
||||
Try if you can generate a single query to fulfill the needs. The step should contains all needed parameters.
|
||||
The parameters can be extracted from the original task.
|
||||
If not, generate the query step by step based on the planning.
|
||||
|
||||
The query must exactly based on the provided table structure. And carefully review the foreign keys to make sure you include all the accurate information.
|
||||
|
||||
|
||||
Note: Output should be only the sql query with sql comments that can be directly run in mysql database with version 8.0.
|
||||
|
||||
Don't use the sql statement that specify target table for update in FROM clause.
|
||||
For example, you CAN'T write query as below:
|
||||
INSERT INTO data_Service (Id, Name)
|
||||
VALUES ((SELECT MAX(Id) + 1 FROM data_Service), 'HVAC');
|
||||
|
||||
If the table structure didn't mention auto incremental, the data field id needs to insert id manually and you need to use max(id) instead of LAST_INSERT_ID function.
|
||||
For example, you should use SET @id = select max(id) from table;
|
||||
|
||||
Additional Requirements:
|
||||
* the alias of the table name in the sql query should be identical.
|
||||
*** the generated sql query MUST be basedd on the provided table structure. ***
|
||||
Requirements:
|
||||
{{ summary_requirements }}
|
||||
|
||||
=====
|
||||
Task description:
|
||||
|
|
@ -31,4 +13,4 @@ Relevant Knowledges:
|
|||
|
||||
=====
|
||||
Table Structure:
|
||||
{{ table_structure }}
|
||||
{{ table_structure }}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,19 @@
|
|||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="packages\**" />
|
||||
<EmbeddedResource Remove="packages\**" />
|
||||
<None Remove="packages\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\get_table_definition.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_select.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\get_table_definition.fn.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_executor.fn.liquid" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\agent.json" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\execute_sql.json" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\lookup_dictionary.json" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_insert.json" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_select.json" />
|
||||
|
|
@ -33,6 +40,9 @@
|
|||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\execute_sql.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,50 @@
|
|||
using BotSharp.Plugin.SqlDriver.Models;
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using MySqlConnector;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Functions;
|
||||
|
||||
public class ExecuteQueryFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "execute_sql";
|
||||
|
||||
public string Indication => "Performing data retrieval operation.";
|
||||
private readonly SqlDriverSetting _setting;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public ExecuteQueryFn(SqlDriverSetting setting)
|
||||
public ExecuteQueryFn(IServiceProvider services, SqlDriverSetting setting)
|
||||
{
|
||||
_services = services;
|
||||
_setting = setting;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
message.Content = "Executed";
|
||||
/*using var connection = new MySqlConnection(_setting.MySqlConnectionString);
|
||||
message.Content = JsonSerializer.Serialize(connection.Query(args.SqlStatement), new JsonSerializerOptions
|
||||
var args = JsonSerializer.Deserialize<ExecuteQueryArgs>(message.FunctionArgs);
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
var results = settings.DatabaseType switch
|
||||
{
|
||||
WriteIndented = true,
|
||||
});*/
|
||||
// message.StopCompletion = true;
|
||||
"MySql" => RunQueryInMySql(args.SqlStatements),
|
||||
"SqlServer" => RunQueryInSqlServer(args.SqlStatements),
|
||||
_ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.")
|
||||
};
|
||||
|
||||
message.Content = JsonSerializer.Serialize(results);
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<dynamic> RunQueryInMySql(string[] sqlTexts)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
using var connection = new MySqlConnection(settings.MySqlExecutionConnectionString);
|
||||
return connection.Query(string.Join(";\r\n", sqlTexts));
|
||||
}
|
||||
|
||||
private IEnumerable<dynamic> RunQueryInSqlServer(string[] sqlTexts)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
using var connection = new SqlConnection(settings.SqlServerExecutionConnectionString ?? settings.SqlServerConnectionString);
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
return connection.Query(string.Join("\r\n", sqlTexts));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using BotSharp.Plugin.SqlDriver.Models;
|
||||
using Fluid.Ast.BinaryExpressions;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MySqlConnector;
|
||||
|
||||
|
|
@ -7,6 +9,7 @@ namespace BotSharp.Plugin.SqlDriver.Functions;
|
|||
public class GetTableDefinitionFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "get_table_definition";
|
||||
public string Indication => "Obtain the relevant data structure definitions.";
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<GetTableDefinitionFn> _logger;
|
||||
|
||||
|
|
@ -23,11 +26,24 @@ public class GetTableDefinitionFn : IFunctionCallback
|
|||
var args = JsonSerializer.Deserialize<SqlStatement>(message.FunctionArgs);
|
||||
var tables = new string[] { args.Table };
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var sqlDriver = _services.GetRequiredService<SqlDriverService>();
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
|
||||
// Get table DDL from database
|
||||
var tableDdls = settings.DatabaseType switch
|
||||
{
|
||||
"MySql" => GetDdlFromMySql(tables),
|
||||
"SqlServer" => GetDdlFromSqlServer(tables),
|
||||
_ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.")
|
||||
};
|
||||
|
||||
message.Content = string.Join("\r\n\r\n", tableDdls);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<string> GetDdlFromMySql(string[] tables)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
var tableDdls = new List<string>();
|
||||
using var connection = new MySqlConnection(settings.MySqlExecutionConnectionString);
|
||||
connection.Open();
|
||||
|
|
@ -57,7 +73,60 @@ public class GetTableDefinitionFn : IFunctionCallback
|
|||
}
|
||||
|
||||
connection.Close();
|
||||
message.Content = string.Join("\r\n\r\n", tableDdls);
|
||||
return true;
|
||||
|
||||
return tableDdls;
|
||||
}
|
||||
|
||||
private List<string> GetDdlFromSqlServer(string[] tables)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
var tableDdls = new List<string>();
|
||||
using var connection = new SqlConnection(settings.SqlServerExecutionConnectionString ?? settings.SqlServerConnectionString);
|
||||
connection.Open();
|
||||
|
||||
foreach (var table in tables)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = @$"DECLARE @TableName NVARCHAR(128) = '{table}';
|
||||
DECLARE @SQL NVARCHAR(MAX) = 'CREATE TABLE ' + @TableName + ' (';
|
||||
|
||||
SELECT @SQL = @SQL + '
|
||||
' + COLUMN_NAME + ' ' +
|
||||
DATA_TYPE +
|
||||
CASE
|
||||
WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL AND DATA_TYPE LIKE '%char%'
|
||||
THEN '(' + CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10)) + ')'
|
||||
WHEN DATA_TYPE IN ('decimal', 'numeric')
|
||||
THEN '(' + CAST(NUMERIC_PRECISION AS VARCHAR(10)) + ',' + CAST(NUMERIC_SCALE AS VARCHAR(10)) + ')'
|
||||
ELSE ''
|
||||
END + ' ' +
|
||||
CASE WHEN IS_NULLABLE = 'NO' THEN 'NOT NULL' ELSE 'NULL' END + ','
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = @TableName
|
||||
ORDER BY ORDINAL_POSITION;
|
||||
|
||||
-- Remove the last comma and add closing parenthesis
|
||||
SET @SQL = LEFT(@SQL, LEN(@SQL) - 1) + ');';
|
||||
|
||||
SELECT @SQL;";
|
||||
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
using var reader = command.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
var result = reader.GetString(0);
|
||||
tableDdls.Add(result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when getting ddl statement of table {table}. {ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
connection.Close();
|
||||
|
||||
return tableDdls;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Plugin.SqlDriver.Models;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using MySqlConnector;
|
||||
using static Dapper.SqlMapper;
|
||||
|
||||
|
|
@ -26,13 +27,12 @@ public class SqlSelect : IFunctionCallback
|
|||
|
||||
// check if need to instantely
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
using var connection = new MySqlConnection(settings.MySqlExecutionConnectionString);
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
foreach(var p in args.Parameters)
|
||||
var result = settings.DatabaseType switch
|
||||
{
|
||||
dictionary["@" + p.Name] = p.Value;
|
||||
}
|
||||
var result = connection.Query(args.Statement, dictionary);
|
||||
"MySql" => RunQueryInMySql(args),
|
||||
"SqlServer" => RunQueryInSqlServer(args),
|
||||
_ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.")
|
||||
};
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
|
|
@ -46,4 +46,28 @@ public class SqlSelect : IFunctionCallback
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<dynamic> RunQueryInMySql(SqlStatement args)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
using var connection = new MySqlConnection(settings.MySqlExecutionConnectionString);
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
foreach (var p in args.Parameters)
|
||||
{
|
||||
dictionary["@" + p.Name] = p.Value;
|
||||
}
|
||||
return connection.Query(args.Statement, dictionary);
|
||||
}
|
||||
|
||||
private IEnumerable<dynamic> RunQueryInSqlServer(SqlStatement args)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
using var connection = new SqlConnection(settings.SqlServerExecutionConnectionString ?? settings.SqlServerConnectionString);
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
foreach (var p in args.Parameters)
|
||||
{
|
||||
dictionary["@" + p.Name] = p.Value;
|
||||
}
|
||||
return connection.Query(args.Statement, dictionary);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Planning;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Core.Agents.Services;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Hooks;
|
||||
|
||||
public class SqlDriverPlanningHook : IPlanningHook
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public SqlDriverPlanningHook(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<string> GetSummaryAdditionalRequirements(string planner)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.GetAgent(BuiltInAgentId.Planner);
|
||||
return agent.Templates.FirstOrDefault(x => x.Name == $"database.summarize.{settings.DatabaseType.ToLower()}")?.Content ?? string.Empty;
|
||||
}
|
||||
|
||||
public async Task OnPlanningCompleted(string planner, RoleDialogModel msg)
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
if (!settings.ExecuteSqlSelectAutonomous)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var wholeDialogs = conv.GetDialogHistory();
|
||||
wholeDialogs.Add(RoleDialogModel.From(msg));
|
||||
wholeDialogs.Add(RoleDialogModel.From(msg, AgentRole.User, "use execute_sql to run query"));
|
||||
|
||||
var agent = await _services.GetRequiredService<IAgentService>().LoadAgent("beda4c12-e1ec-4b4b-b328-3df4a6687c4f");
|
||||
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: agent.LlmConfig.Provider,
|
||||
model: agent.LlmConfig.Model);
|
||||
|
||||
var response = await completion.GetChatCompletions(agent, wholeDialogs);
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
await routing.InvokeFunction(response.FunctionName, response);
|
||||
msg.CurrentAgentId = agent.Id;
|
||||
msg.FunctionName = response.FunctionName;
|
||||
msg.FunctionArgs = response.FunctionArgs;
|
||||
msg.Content = response.Content;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Models;
|
||||
|
||||
public class ExecuteQueryArgs
|
||||
{
|
||||
[JsonPropertyName("sql_statements")]
|
||||
public string[] SqlStatements { get; set; } = [];
|
||||
}
|
||||
|
|
@ -2,8 +2,11 @@ namespace BotSharp.Plugin.SqlHero.Settings;
|
|||
|
||||
public class SqlDriverSetting
|
||||
{
|
||||
public string DatabaseType { get; set; } = "MySql";
|
||||
public string MySqlConnectionString { get; set; } = null!;
|
||||
public string MySqlExecutionConnectionString { get; set; } = null!;
|
||||
public string SqlServerConnectionString { get; set; } = null!;
|
||||
public string SqlServerExecutionConnectionString { get; set; } = null!;
|
||||
public string SqlLiteConnectionString { get; set; } = null!;
|
||||
public bool ExecuteSqlSelectAutonomous { get; set; } = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Planning;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver;
|
||||
|
||||
public class SqlDriverPlugin : IBotSharpPlugin
|
||||
|
|
@ -20,5 +22,6 @@ public class SqlDriverPlugin : IBotSharpPlugin
|
|||
services.AddScoped<IKnowledgeHook, SqlDriverKnowledgeHook>();
|
||||
services.AddScoped<IAgentHook, SqlExecutorHook>();
|
||||
services.AddScoped<IAgentUtilityHook, SqlExecutorUtilityHook>();
|
||||
services.AddScoped<IPlanningHook, SqlDriverPlanningHook>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"isPublic": true,
|
||||
"profiles": [ "database" ],
|
||||
"llmConfig": {
|
||||
"provider": "openai",
|
||||
"provider": "azure-openai",
|
||||
"model": "gpt-4o-mini"
|
||||
},
|
||||
"routingRules": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "execute_sql",
|
||||
"description": "Run the sql statements provided in the last converastion",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sql_statements": {
|
||||
"type": "array",
|
||||
"description": "raw sql statements",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "sql statement"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [ "sql_statement" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -203,15 +203,18 @@ public class PlaywrightInstance : IDisposable
|
|||
/// <param name="ctxId"></param>
|
||||
/// <param name="timeout">seconds</param>
|
||||
/// <returns></returns>
|
||||
public async Task Wait(string ctxId, int timeout = 60)
|
||||
public async Task Wait(string ctxId, int timeout = 10, bool waitNetworkIdle = true)
|
||||
{
|
||||
foreach (var page in _pages[ctxId])
|
||||
{
|
||||
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle, new PageWaitForLoadStateOptions
|
||||
if (waitNetworkIdle)
|
||||
{
|
||||
Timeout = 1000 * timeout
|
||||
});
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle, new PageWaitForLoadStateOptions
|
||||
{
|
||||
Timeout = 1000 * timeout
|
||||
});
|
||||
}
|
||||
}
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
public async Task<BrowserActionResult> ActionOnElement(MessageInfo message, ElementLocatingArgs location, ElementActionArgs action)
|
||||
{
|
||||
await _instance.Wait(message.ContextId);
|
||||
await _instance.Wait(message.ContextId, waitNetworkIdle: false);
|
||||
var result = await LocateElement(message, location);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue