optimize sql driver

This commit is contained in:
Haiping Chen 2024-10-11 20:18:45 -05:00
parent c95ac8b935
commit 30591f40d3
14 changed files with 168 additions and 32 deletions

View file

@ -1,4 +1,5 @@
using BotSharp.Plugin.Planner.TwoStaging.Models;
using System.Threading.Tasks;
namespace BotSharp.Plugin.Planner.Functions;
@ -27,14 +28,18 @@ public class SecondaryStagePlanFn : IFunctionCallback
var planPrimary = states.GetState("planning_result");
var taskSecondary = JsonSerializer.Deserialize<SecondaryBreakdownTask>(msgSecondary.FunctionArgs);
// Search knowledgebase
var knowledges = await knowledgeService.SearchVectorKnowledge(taskSecondary.SolutionQuestion, collectionName, new VectorSearchOptions
{
Confidence = 0.7f
});
var knowledgeResults = string.Join("\r\n\r\n=====\r\n", knowledges.Select(x => x.ToQuestionAnswer()));
// Search knowledgebase
var hooks = _services.GetServices<IKnowledgeHook>();
var knowledges = new List<string>();
foreach (var hook in hooks)
{
var k = await hook.GetRelevantKnowledges(message, taskSecondary.SolutionQuestion);
knowledges.AddRange(k);
}
knowledges = knowledges.Distinct().ToList();
var knowledgeResults = string.Join("\r\n\r\n=====\r\n", knowledges);
// Get second stage planning prompt
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);

View file

@ -1,8 +1,6 @@
using BotSharp.Abstraction.Knowledges;
using BotSharp.Abstraction.Planning;
using BotSharp.Plugin.Planner.TwoStaging;
using BotSharp.Plugin.Planner.TwoStaging.Models;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace BotSharp.Plugin.Planner.Functions;

View file

@ -2,13 +2,32 @@ namespace BotSharp.Plugin.Planner.Hooks;
public class PlannerAgentHook : AgentHookBase
{
public override string SelfId => string.Empty;
public override string SelfId => BuiltInAgentId.Planner;
public PlannerAgentHook(IServiceProvider services, AgentSettings settings)
: base(services, settings)
{
}
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
{
var knowledgeHooks = _services.GetServices<IKnowledgeHook>();
// Get global knowledges
var Knowledges = new List<string>();
foreach (var hook in knowledgeHooks)
{
var k = hook.GetGlobalKnowledges(new RoleDialogModel(AgentRole.User, template)
{
CurrentAgentId = BuiltInAgentId.Planner
}).Result;
Knowledges.AddRange(k);
}
dict["global_knowledges"] = Knowledges;
return true;
}
public override void OnAgentLoaded(Agent agent)
{
var conv = _services.GetRequiredService<IConversationService>();

View file

@ -1,4 +1,6 @@
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.
Use the TwoStagePlanner approach to plan the overall implementation steps, follow the below steps strictly.
1. Call plan_primary_stage to generate the primary plan.
If you've already got the plan to meet the user goal, directly go to step 5.
2. If need_lookup_dictionary is True, call verify_dictionary_term to verify or get the enum/term/dictionary value. Pull id and name.
@ -15,6 +17,7 @@ Don't run the planning process repeatedly if you have already got the result of
{% if global_knowledges != empty -%}
=====
Global Knowledge:
Current date time is: {{ "now" | date: "%Y-%m-%d %H:%M" }}
{% for k in global_knowledges %}
{{ k }}
{% endfor %}

View file

@ -6,7 +6,6 @@ Reference to "Primary Planning" and the additional knowledge included. Breakdown
* 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.

View file

@ -30,6 +30,7 @@
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_select.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instructions\instruction.liquid" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\query_result_formatting.liquid" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\sql_statement_correctness.liquid" />
</ItemGroup>
<ItemGroup>
@ -69,6 +70,9 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_executor.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\sql_statement_correctness.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\query_result_formatting.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View file

@ -1,8 +1,10 @@
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Routing;
using BotSharp.Core.Infrastructures;
using BotSharp.Plugin.SqlDriver.Models;
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;
using MySqlConnector;
namespace BotSharp.Plugin.SqlDriver.Functions;
@ -13,31 +15,47 @@ public class ExecuteQueryFn : IFunctionCallback
public string Indication => "Performing data retrieval operation.";
private readonly SqlDriverSetting _setting;
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public ExecuteQueryFn(IServiceProvider services, SqlDriverSetting setting)
public ExecuteQueryFn(IServiceProvider services, SqlDriverSetting setting, ILogger<ExecuteQueryFn> logger)
{
_services = services;
_setting = setting;
_logger = logger;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<ExecuteQueryArgs>(message.FunctionArgs);
var settings = _services.GetRequiredService<SqlDriverSetting>();
var results = settings.DatabaseType switch
{
"MySql" => RunQueryInMySql(args.SqlStatements),
"SqlServer" => RunQueryInSqlServer(args.SqlStatements),
_ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.")
};
if (results.Count() == 0)
{
message.Content = "No record found";
return true;
}
message.Content = JsonSerializer.Serialize(results);
var refinedArgs = await RefineSqlStatement(message, args);
var settings = _services.GetRequiredService<SqlDriverSetting>();
try
{
var results = settings.DatabaseType switch
{
"MySql" => RunQueryInMySql(refinedArgs.SqlStatements),
"SqlServer" => RunQueryInSqlServer(refinedArgs.SqlStatements),
_ => throw new NotImplementedException($"Database type {settings.DatabaseType} is not supported.")
};
if (results.Count() == 0)
{
message.Content = "No record found";
return true;
}
message.Content = JsonSerializer.Serialize(results);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while executing SQL query.");
message.Content = "Error occurred while retrieving information.";
message.StopCompletion = true;
return false;
}
if (args.FormattingResult)
{
@ -59,6 +77,7 @@ public class ExecuteQueryFn : IFunctionCallback
});
message.Content = result.Content;
message.StopCompletion = true;
}
return true;
@ -77,4 +96,54 @@ public class ExecuteQueryFn : IFunctionCallback
using var connection = new SqlConnection(settings.SqlServerExecutionConnectionString ?? settings.SqlServerConnectionString);
return connection.Query(string.Join("\r\n", sqlTexts));
}
private async Task<ExecuteQueryArgs> RefineSqlStatement(RoleDialogModel message, ExecuteQueryArgs args)
{
// get table DDL
var fn = _services.GetRequiredService<IRoutingService>();
var msg = RoleDialogModel.From(message);
await fn.InvokeFunction("sql_table_definition", msg);
// refine SQL
var agentService = _services.GetRequiredService<IAgentService>();
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
var dictionarySqlPrompt = await GetDictionarySQLPrompt(string.Join("\r\n\r\n", args.SqlStatements), msg.Content);
var agent = new Agent
{
Id = message.CurrentAgentId ?? string.Empty,
Name = "sqlDriver_ExecuteQuery",
Instruction = dictionarySqlPrompt,
TemplateDict = new Dictionary<string, object>(),
LlmConfig = currentAgent.LlmConfig
};
var completion = CompletionProvider.GetChatCompletion(_services,
provider: agent.LlmConfig.Provider,
model: agent.LlmConfig.Model);
var refinedMessage = await completion.GetChatCompletions(agent, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, "Check and output the correct SQL statements")
});
return refinedMessage.Content.JsonContent<ExecuteQueryArgs>();
}
private async Task<string> GetDictionarySQLPrompt(string originalSql, string tableStructure)
{
var agentService = _services.GetRequiredService<IAgentService>();
var render = _services.GetRequiredService<ITemplateRender>();
var knowledgeHooks = _services.GetServices<IKnowledgeHook>();
var agent = await agentService.GetAgent(BuiltInAgentId.SqlDriver);
var template = agent.Templates.FirstOrDefault(x => x.Name == "sql_statement_correctness")?.Content ?? string.Empty;
var responseFormat = JsonSerializer.Serialize(new ExecuteQueryArgs { });
return render.Render(template, new Dictionary<string, object>
{
{ "original_sql", originalSql },
{ "table_structure", tableStructure },
{ "response_format", responseFormat }
});
}
}

View file

@ -33,20 +33,25 @@ public class SqlDriverPlanningHook : IPlanningHook
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");
wholeDialogs.Add(RoleDialogModel.From(msg, AgentRole.User, $"call execute_sql to run query, set formatting_result as {settings.FormattingResult}"));
var agent = await _services.GetRequiredService<IAgentService>().LoadAgent(BuiltInAgentId.SqlDriver);
var completion = CompletionProvider.GetChatCompletion(_services,
provider: agent.LlmConfig.Provider,
model: agent.LlmConfig.Model);
var response = await completion.GetChatCompletions(agent, wholeDialogs);
// Invoke "execute_sql"
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;
msg.StopCompletion = response.StopCompletion;
/*var routing = _services.GetRequiredService<IRoutingService>();
await routing.InvokeAgent(BuiltInAgentId.SqlDriver, wholeDialogs);*/
}
}

View file

@ -7,8 +7,12 @@ public class ExecuteQueryArgs
[JsonPropertyName("sql_statements")]
public string[] SqlStatements { get; set; } = [];
[JsonPropertyName("tables")]
public string[] Tables { get; set; } = [];
/// <summary>
/// Beautifying query result
/// </summary>
[JsonPropertyName("formatting_result")]
public bool FormattingResult { get; set; }
}

View file

@ -10,4 +10,5 @@ public class SqlDriverSetting
public string SqlServerExecutionConnectionString { get; set; } = null!;
public string SqlLiteConnectionString { get; set; } = null!;
public bool ExecuteSqlSelectAutonomous { get; set; } = false;
public bool FormattingResult { get; set; } = true;
}

View file

@ -10,7 +10,7 @@
"profiles": [ "database" ],
"llmConfig": {
"provider": "openai",
"model": "gpt-4o-mini"
"model": "gpt-4o"
},
"routingRules": [
{

View file

@ -11,8 +11,22 @@
"type": "string",
"description": "sql statement"
}
},
"formatting_result": {
"type": "boolean",
"description": "formatting the results"
},
"tables": {
"type": "array",
"description": "all related tables",
"items": {
"type": "string",
"description": "table name"
}
}
},
"required": [ "sql_statement" ]
"required": [ "sql_statement", "tables", "formatting_result" ]
}
}

View file

@ -1 +1,5 @@
Output in human readable format. If there is large amount of information, shape it in tabular.
Output in human readable format. If there is large amount of rows, shape it in tabular, otherwise, output in plain text.
Put user task description in the first line in the same language, for example, user is using Chinese, you have to output the result in Chinese.
User Task Description:
{{ requirement_detail }}

View file

@ -0,0 +1,11 @@
You are a sql statement corrector. You will need to refer to the table structure and rewrite the original sql statement so it's using the correct information, e.g. column name.
Output the sql statement only without comments, in JSON format: {{ response_format }}
Make sure all the column names are defined in the Table Structure.
=====
Original SQL statements:
{{ original_sql }}
=====
Table Structure:
{{ table_structure }}