diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs index 918b33e5..e228d607 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs @@ -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(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(); + var knowledges = new List(); + 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); diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs index 22ddc3c3..e9fd3b37 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs @@ -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; diff --git a/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs b/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs index 1a85e1c0..503de3d5 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Hooks/PlannerAgentHook.cs @@ -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 dict) + { + var knowledgeHooks = _services.GetServices(); + + // Get global knowledges + var Knowledges = new List(); + 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(); diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid index 5dda81e3..d15329a8 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/instructions/instruction.liquid @@ -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 %} diff --git a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid index 113767ae..81e22fe8 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid +++ b/src/Plugins/BotSharp.Plugin.Planner/data/agents/282a7128-69a1-44b0-878c-a9159b88f3b9/templates/two_stage.2nd.plan.liquid @@ -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. diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj index 4f93597a..d4d0a243 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj @@ -30,6 +30,7 @@ + @@ -69,6 +70,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs index 78ab8541..e3d8e57e 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs @@ -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 logger) { _services = services; _setting = setting; + _logger = logger; } public async Task Execute(RoleDialogModel message) { var args = JsonSerializer.Deserialize(message.FunctionArgs); - var settings = _services.GetRequiredService(); - 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(); + + 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 RefineSqlStatement(RoleDialogModel message, ExecuteQueryArgs args) + { + // get table DDL + var fn = _services.GetRequiredService(); + var msg = RoleDialogModel.From(message); + await fn.InvokeFunction("sql_table_definition", msg); + + // refine SQL + var agentService = _services.GetRequiredService(); + 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(), + LlmConfig = currentAgent.LlmConfig + }; + + var completion = CompletionProvider.GetChatCompletion(_services, + provider: agent.LlmConfig.Provider, + model: agent.LlmConfig.Model); + + var refinedMessage = await completion.GetChatCompletions(agent, new List + { + new RoleDialogModel(AgentRole.User, "Check and output the correct SQL statements") + }); + + return refinedMessage.Content.JsonContent(); + } + + private async Task GetDictionarySQLPrompt(string originalSql, string tableStructure) + { + var agentService = _services.GetRequiredService(); + var render = _services.GetRequiredService(); + var knowledgeHooks = _services.GetServices(); + + 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 + { + { "original_sql", originalSql }, + { "table_structure", tableStructure }, + { "response_format", responseFormat } + }); + } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs index ac99c699..29160780 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs @@ -33,20 +33,25 @@ public class SqlDriverPlanningHook : IPlanningHook var conv = _services.GetRequiredService(); 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().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().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(); 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(); + await routing.InvokeAgent(BuiltInAgentId.SqlDriver, wholeDialogs);*/ } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs index ac2279ea..bc5c6fbd 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/ExecuteQueryArgs.cs @@ -7,8 +7,12 @@ public class ExecuteQueryArgs [JsonPropertyName("sql_statements")] public string[] SqlStatements { get; set; } = []; + [JsonPropertyName("tables")] + public string[] Tables { get; set; } = []; + /// /// Beautifying query result /// + [JsonPropertyName("formatting_result")] public bool FormattingResult { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs index 5815151c..3a4095a6 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs @@ -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; } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json index 60309dff..eb79816f 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json @@ -10,7 +10,7 @@ "profiles": [ "database" ], "llmConfig": { "provider": "openai", - "model": "gpt-4o-mini" + "model": "gpt-4o" }, "routingRules": [ { diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json index 15e6d281..9cdafc04 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json @@ -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" ] } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid index 7c40b11a..5d07a941 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid @@ -1 +1,5 @@ -Output in human readable format. If there is large amount of information, shape it in tabular. \ No newline at end of file +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 }} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid new file mode 100644 index 00000000..cf61eb1b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/sql_statement_correctness.liquid @@ -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 }}