diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs index 01d2bfdf..c3e0a737 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs @@ -18,6 +18,7 @@ public class PrimaryStagePlanFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { // Debug + var agentService = _services.GetRequiredService(); var state = _services.GetRequiredService(); var knowledgeService = _services.GetRequiredService(); var knowledgeSettings = _services.GetRequiredService(); @@ -28,16 +29,14 @@ public class PrimaryStagePlanFn : IFunctionCallback // Get knowledge from vectordb var collectionName = knowledgeSettings.Default.CollectionName ?? KnowledgeCollectionName.BotSharp; ; - var knowledges = await knowledgeService.SearchVectorKnowledge(task.Question, collectionName, new VectorSearchOptions + var knowledges = await knowledgeService.SearchVectorKnowledge(task.Requirements, collectionName, new VectorSearchOptions { Confidence = 0.1f }); message.Content = string.Join("\r\n\r\n=====\r\n", knowledges.Select(x => x.ToQuestionAnswer())); - var agentService = _services.GetRequiredService(); - var currentAgent = await agentService.LoadAgent(message.CurrentAgentId); - // Send knowledge to AI to refine and summarize the primary planning + var currentAgent = await agentService.LoadAgent(message.CurrentAgentId); var firstPlanningPrompt = await GetFirstStagePlanPrompt(task, message); var plannerAgent = new Agent { diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs index 0f9da4aa..eda46f50 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs @@ -5,11 +5,13 @@ namespace BotSharp.Plugin.Planner.Functions; public class SummaryPlanFn : IFunctionCallback { public string Name => "plan_summary"; - private readonly IServiceProvider _services; - private readonly ILogger _logger; - private object aiAssistant; - public SummaryPlanFn(IServiceProvider services, ILogger logger) + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public SummaryPlanFn( + IServiceProvider services, + ILogger logger) { _services = services; _logger = logger; @@ -17,32 +19,51 @@ public class SummaryPlanFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { - //debug + var fn = _services.GetRequiredService(); + var agentService = _services.GetRequiredService(); var state = _services.GetRequiredService(); + + var currentAgent = await agentService.LoadAgent(message.CurrentAgentId); state.SetState("max_tokens", "4096"); var task = state.GetState("requirement_detail"); - // summarize and generate query - var summaryPlanningPrompt = await GetPlanSummaryPrompt(task, message); - _logger.LogInformation(summaryPlanningPrompt); + // Get DDL + var steps = message.Content.JsonArrayContent(); + + // Get all the related tables + var allTables = new List(); + foreach (var step in steps) + { + allTables.AddRange(step.Tables); + } + message.Data = allTables.Distinct().ToList(); + + // Get table DDL and stores in content + var msgCopy = RoleDialogModel.From(message); + await fn.InvokeFunction("get_table_definition", msgCopy); + var ddlStatements = msgCopy.Content; + + // Summarize and generate query + var summaryPlanPrompt = await GetPlanSummaryPrompt(task, message.Content, ddlStatements); + _logger.LogInformation($"Summary plan prompt:\r\n{summaryPlanPrompt}"); var plannerAgent = new Agent { Id = BuiltInAgentId.Planner, - Name = "planner_summary", - Instruction = summaryPlanningPrompt, - TemplateDict = new Dictionary() + Name = "Planner Summary", + Instruction = summaryPlanPrompt, + LlmConfig = currentAgent.LlmConfig }; - var response_summary = await GetAiResponse(plannerAgent); - message.Content = response_summary.Content; + var summary = await GetAiResponse(plannerAgent); + message.Content = summary.Content; message.StopCompletion = true; return true; } - private async Task GetPlanSummaryPrompt(string task, RoleDialogModel message) + private async Task GetPlanSummaryPrompt(string task, string knowledge, string ddlStatement) { // save to knowledge base var agentService = _services.GetRequiredService(); @@ -58,9 +79,9 @@ public class SummaryPlanFn : IFunctionCallback return render.Render(template, new Dictionary { - { "table_structure", message.SecondaryContent }, ////check - { "task_description", task}, - { "relevant_knowledges", message.Content }, + { "table_structure", ddlStatement }, + { "task_description", task }, + { "relevant_knowledges", knowledge }, { "response_format", responseFormat } }); } @@ -69,17 +90,17 @@ public class SummaryPlanFn : IFunctionCallback var conv = _services.GetRequiredService(); var wholeDialogs = conv.GetDialogHistory(); - //add "test" to wholeDialogs' last element + // Add "test" to wholeDialogs' last element if (plannerAgent.Name == "planner_summary") { - //add "test" to wholeDialogs' last element in a new paragraph + // Add "test" to wholeDialogs' last element in a new paragraph 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"; } if (plannerAgent.Name == "planning_1st") { - //add "test" to wholeDialogs' last element in a new paragraph + // Add "test" to wholeDialogs' last element in a new paragraph wholeDialogs.Last().Content += "\n\nYou must analyze the table description to infer the table relations."; } 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 f40fa6c8..7209f7dd 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,3 +1,3 @@ Use the TwoStagePlanner approach to plan the overall implementation steps, call plan_primary_stage. If need_additional_information is true, call plan_secondary_stage for the specific primary stage. -Call plan_summary to summarize the final planning steps. \ No newline at end of file +You must Call plan_summary as the last step to summarize the final planning steps. \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/AddDatabaseKnowledge.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/AddDatabaseKnowledge.cs deleted file mode 100644 index 5a50e355..00000000 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/AddDatabaseKnowledge.cs +++ /dev/null @@ -1,122 +0,0 @@ -using BotSharp.Abstraction.Routing; -using BotSharp.Core.Infrastructures; -using MySqlConnector; -using static Dapper.SqlMapper; -using BotSharp.Abstraction.Agents.Enums; -using Microsoft.Extensions.Logging; - -namespace BotSharp.Plugin.Planner.Functions; - -public class AddDatabaseKnowledgeFn : IFunctionCallback -{ - public string Name => "add_database_knowledge"; - - private readonly IServiceProvider _services; - private readonly ILogger _logger; - - public AddDatabaseKnowledgeFn( - IServiceProvider services, - ILogger logger) - { - _services = services; - _logger = logger; - } - - public async Task Execute(RoleDialogModel message) - { - var agentService = _services.GetRequiredService(); - var sqlDriver = _services.GetRequiredService(); - var fn = _services.GetRequiredService(); - var settings = _services.GetRequiredService(); - - - var allTables = new HashSet(); - using var connection = new MySqlConnection(settings.MySqlConnectionString); - - var sql = $"select table_name from information_schema.tables;"; - var results = connection.Query(sql, new Dictionary()); - - foreach (var item in results) - { - if (item == null) continue; - - allTables.Add(item.TABLE_NAME); - } - message.Data = allTables.ToList(); - - var currentAgent = await agentService.LoadAgent(message.CurrentAgentId); - var errorNote = string.Empty; - - foreach (var table in allTables) - { - message.Data = new List { table }; - - await fn.InvokeFunction("get_table_definition", message); - var planningPrompt = await GetPrompt(message); - var plannerAgent = new Agent - { - Id = string.Empty, - Name = "Database Knowledge", - Instruction = planningPrompt, - LlmConfig = currentAgent.LlmConfig - }; - - try - { - var response = await GetAiResponse(plannerAgent); - var knowledges = response.Content.JsonArrayContent(); - foreach (var k in knowledges) - { - try - { - message.FunctionArgs = JsonSerializer.Serialize(new ExtractedKnowledge - { - Question = k.Question, - Answer = k.Answer - }); - await fn.InvokeFunction("memorize_knowledge", message); - message.SecondaryContent += $"Table: {table}, Question: {k.Question}, {message.Content}\r\n"; - } - catch (Exception e) - { - var note = $"Error processing table {table}: {e.Message}\r\n{e.InnerException}"; - errorNote += note; - _logger.LogWarning(note); - } - } - } - catch (Exception e) - { - errorNote += $"Error processing table {table}: {e.Message}\r\n{e.InnerException}\r\n"; - _logger.LogWarning(errorNote); - } - } - return true; - } - - private async Task GetAiResponse(Agent plannerAgent) - { - var conv = _services.GetRequiredService(); - var wholeDialogs = conv.GetDialogHistory(); - - var completion = CompletionProvider.GetChatCompletion(_services, - provider: plannerAgent.LlmConfig.Provider, - model: plannerAgent.LlmConfig.Model); - - return await completion.GetChatCompletions(plannerAgent, wholeDialogs); - } - - private async Task GetPrompt(RoleDialogModel message) - { - var agentService = _services.GetRequiredService(); - var render = _services.GetRequiredService(); - - var aiAssistant = await agentService.GetAgent(BuiltInAgentId.AIAssistant); - var template = aiAssistant.Templates.FirstOrDefault(x => x.Name == "database_knowledge")?.Content ?? string.Empty; - - return render.Render(template, new Dictionary - { - { "table_structure", message.Content } - }); - } -} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs index 6fcee0bd..14314a8f 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using MySqlConnector; using static Dapper.SqlMapper; @@ -7,10 +8,14 @@ public class GetTableDefinitionFn : IFunctionCallback { public string Name => "get_table_definition"; private readonly IServiceProvider _services; + private readonly ILogger _logger; - public GetTableDefinitionFn(IServiceProvider services) + public GetTableDefinitionFn( + IServiceProvider services, + ILogger logger) { _services = services; + _logger = logger; } public async Task Execute(RoleDialogModel message) @@ -20,37 +25,47 @@ public class GetTableDefinitionFn : IFunctionCallback var settings = _services.GetRequiredService(); // Get table DDL from database + var tables = message.Data as List; + if (tables.IsNullOrEmpty()) return false; + var tableDdls = new List(); using var connection = new MySqlConnection(settings.MySqlConnectionString); connection.Open(); - foreach (var table in (List)message.Data) + foreach (var table in tables) { - var escapedTableName = MySqlHelper.EscapeString(table); - - var sql = $"select * from information_schema.tables where table_name = @tableName"; - var result = connection.QueryFirstOrDefault(sql, new + try { - tableName = escapedTableName - }); + var escapedTableName = MySqlHelper.EscapeString(table); - if (result == null) continue; + var sql = $"select * from information_schema.tables where table_name = @tableName"; + var result = connection.QueryFirstOrDefault(sql, new + { + tableName = escapedTableName + }); - sql = $"SHOW CREATE TABLE `{escapedTableName}`"; - using var command = new MySqlCommand(sql, connection); - using var reader = command.ExecuteReader(); - if (reader.Read()) - { - result = reader.GetString(1); - tableDdls.Add(result); + if (result == null) continue; + + sql = $"SHOW CREATE TABLE `{escapedTableName}`"; + using var command = new MySqlCommand(sql, connection); + using var reader = command.ExecuteReader(); + if (reader.Read()) + { + result = reader.GetString(1); + tableDdls.Add(result); + } + + reader.Close(); + command.Dispose(); + } + catch (Exception ex) + { + _logger.LogWarning($"Error when getting ddl statement of table {table}."); } - - reader.Close(); - command.Dispose(); } connection.Close(); - message.Content = string.Join("\r\n", tableDdls); + message.Content = string.Join("\r\n\r\n", tableDdls); return true; } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/DbKnowledgeService.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/DbKnowledgeService.cs index 7807f87f..746798c9 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/DbKnowledgeService.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/DbKnowledgeService.cs @@ -94,7 +94,7 @@ public class DbKnowledgeService using var connection = new MySqlConnection(settings.MySqlConnectionString); connection.Open(); - var result = string.Empty; + var ddl = string.Empty; var escapedTableName = MySqlHelper.EscapeString(table); var sql = $"SHOW CREATE TABLE `{escapedTableName}`"; @@ -102,13 +102,13 @@ public class DbKnowledgeService using var reader = command.ExecuteReader(); if (reader.Read()) { - result = reader.GetString(1); + ddl = reader.GetString(1); } reader.Close(); command.Dispose(); connection.Close(); - return result; + return ddl; } private async Task GetPrompt(string content)