Merge branch 'master' of https://github.com/SciSharp/BotSharp
This commit is contained in:
commit
689c5d2572
|
|
@ -33,7 +33,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
{
|
||||
var list = await knowledgeService.SearchVectorKnowledge(question, collectionName, new VectorSearchOptions
|
||||
{
|
||||
Confidence = 0.2f
|
||||
Confidence = 0.4f
|
||||
});
|
||||
knowledges.Add(string.Join("\r\n\r\n=====\r\n", list.Select(x => x.ToQuestionAnswer())));
|
||||
|
||||
|
|
@ -56,7 +56,10 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
LlmConfig = currentAgent.LlmConfig
|
||||
};
|
||||
var response = await GetAiResponse(plannerAgent);
|
||||
message.Content = response.Content;
|
||||
message.Content = response.Content;
|
||||
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
states.SetState("planning_result", response.Content);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
|
|||
|
||||
var knowledges = await knowledgeService.SearchVectorKnowledge(item.Task, collectionName, new VectorSearchOptions
|
||||
{
|
||||
Confidence = 0.5f
|
||||
Confidence = 0.6f
|
||||
});
|
||||
message.Content += string.Join("\r\n\r\n=====\r\n", knowledges.Select(x => x.ToQuestionAnswer()));
|
||||
}
|
||||
|
|
@ -63,6 +63,9 @@ public class SecondaryStagePlanFn : IFunctionCallback
|
|||
var response = await GetAiResponse(plannerAgent);
|
||||
message.Content = response.Content;
|
||||
_logger.LogInformation(response.Content);
|
||||
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
states.SetState("planning_result", response.Content);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,13 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
var taskRequirement = state.GetState("requirement_detail");
|
||||
|
||||
// Get table names
|
||||
var steps = message.Content.JsonArrayContent<SecondStagePlan>();
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var steps = states.GetState("planning_result").JsonArrayContent<SecondStagePlan>();
|
||||
var allTables = new List<string>();
|
||||
var ddlStatements = "";
|
||||
var relevantKnowledge = message.Content;
|
||||
var relevantKnowledge = states.GetState("planning_result");
|
||||
relevantKnowledge += states.GetState("dictionary_items");
|
||||
|
||||
foreach (var step in steps)
|
||||
{
|
||||
allTables.AddRange(step.Tables);
|
||||
|
|
|
|||
|
|
@ -7,4 +7,7 @@ public class SecondaryBreakdownTask
|
|||
|
||||
[JsonPropertyName("solution_search_question")]
|
||||
public string SolutionQuestion { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("need_lookup_dictionary")]
|
||||
public bool NeedLookupDictionary { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,15 @@
|
|||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"related_tables": {
|
||||
"type": "array",
|
||||
"description": "table name in planning steps",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "table name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
"required": [ "related_tables" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
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.
|
||||
2. If need_additional_information is true, call plan_secondary_stage for the specific primary stage.
|
||||
3. You must call plan_summary to generate final planned steps.
|
||||
4. If you can't generate the final accurate planning steps due to missing some specific informations, please ask user for more information.
|
||||
3. Repeat step 2 until you processed all the primary stages.
|
||||
4. If need_lookup_dictionary is true, call sql_dictionary_lookup to verify or get the enum/term/dictionary value. Pull id and name.
|
||||
If you no items retured, you can pull all the list and find the match.
|
||||
5. You must call plan_summary for you final planned output.
|
||||
|
||||
*** IMPORTANT ***
|
||||
Don't run the planning process repeatedly if you have already got the result of user's request.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ Thinking process:
|
|||
- If there is extra knowledge or relationship needed between steps, set the need_additional_information to true for both steps.
|
||||
- If the solution mentioned "related solutions" is needed, set the need_additional_information to true.
|
||||
- You should find the relationships between data structure based on the task knowledge strictly. If lack of information, set the need_additional_information to true.
|
||||
- If you need to verify or get the enum/term/dictionary value, set the need_additional_information to true.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Reference to "Primary Planning" and the additional knowledge included. Breakdown
|
|||
* 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 sql_dictionary_lookup 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 }}]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
For every primary step, if need_additional_information is true, you have to call plan_secondary_stage to plan the detail steps to complete the primary step.
|
||||
For every primary step, if need_additional_information is true, you have to call plan_secondary_stage to plan the detail steps to complete the primary step.
|
||||
if need_lookup_dictionary is true, you have to call sql_dictionary_lookup to verify or get the enum/term/dictionary value. Pull id and name/code.
|
||||
|
|
@ -18,8 +18,10 @@
|
|||
|
||||
<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_dictionary_lookup.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_dictionary_lookup.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" />
|
||||
|
|
@ -27,10 +29,16 @@
|
|||
<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" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\lookup_dictionary.liquid" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\sql_dictionary_lookup.liquid" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_dictionary_lookup.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_dictionary_lookup.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\get_table_definition.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
@ -46,10 +54,7 @@
|
|||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\lookup_dictionary.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\lookup_dictionary.json">
|
||||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\sql_dictionary_lookup.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_insert.json">
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ namespace BotSharp.Plugin.SqlDriver.Enum;
|
|||
public class Utility
|
||||
{
|
||||
public const string SqlExecutor = "sql-executor";
|
||||
public const string SqlDictionaryLookup = "sql-dictionary-lookup";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Azure;
|
||||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
|
@ -9,7 +10,7 @@ namespace BotSharp.Plugin.SqlDriver.Functions;
|
|||
|
||||
public class LookupDictionaryFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "lookup_dictionary";
|
||||
public string Name => "sql_dictionary_lookup";
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public LookupDictionaryFn(IServiceProvider services)
|
||||
|
|
@ -21,58 +22,24 @@ public class LookupDictionaryFn : IFunctionCallback
|
|||
{
|
||||
var args = JsonSerializer.Deserialize<LookupDictionary>(message.FunctionArgs);
|
||||
|
||||
// check if need to instantely
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
using var connection = new MySqlConnection(settings.MySqlConnectionString);
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
var results = connection.Query($"SELECT * FROM {args.Table} LIMIT 10");
|
||||
var items = new List<string>();
|
||||
foreach(var item in results)
|
||||
using var connection = new MySqlConnection(settings.MySqlExecutionConnectionString);
|
||||
var result = connection.Query(args.SqlStatement);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
items.Add(JsonSerializer.Serialize(item));
|
||||
message.Content = "Record not found";
|
||||
}
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var prompt = GetPrompt(agent, items, args.Keyword);
|
||||
|
||||
// Ask LLM which one is the best
|
||||
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var model = llmProviderService.GetProviderModel("azure-openai", "gpt-35-turbo");
|
||||
|
||||
// chat completion
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: "azure-openai",
|
||||
model: model.Name);
|
||||
|
||||
var conversations = new List<RoleDialogModel>
|
||||
else
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, prompt)
|
||||
{
|
||||
CurrentAgentId = message.CurrentAgentId,
|
||||
MessageId = message.MessageId,
|
||||
}
|
||||
};
|
||||
|
||||
var response = await completion.GetChatCompletions(new Agent
|
||||
{
|
||||
Id = message.CurrentAgentId,
|
||||
Instruction = ""
|
||||
}, conversations);
|
||||
|
||||
message.Content = response.Content;
|
||||
message.Content = JsonSerializer.Serialize(result);
|
||||
}
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var dictionaryItems = states.GetState("dictionary_items", "");
|
||||
dictionaryItems += "\r\n\r\n" + args.Reason + ":\r\n" + message.Content + "\r\n";
|
||||
states.SetState("dictionary_items", dictionaryItems);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetPrompt(Agent agent, List<string> task, string keyword)
|
||||
{
|
||||
var template = agent.Templates.First(x => x.Name == "lookup_dictionary").Content;
|
||||
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "items", task },
|
||||
{ "keyword", keyword }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Settings;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Hooks;
|
||||
|
||||
public class SqlDictionaryLookupHook : AgentHookBase, IAgentHook
|
||||
{
|
||||
private const string SQL_EXECUTOR_TEMPLATE = "sql_dictionary_lookup.fn";
|
||||
private IEnumerable<string> _targetSqlExecutorFunctions = new List<string>
|
||||
{
|
||||
"sql_dictionary_lookup",
|
||||
};
|
||||
|
||||
public override string SelfId => BuiltInAgentId.Planner;
|
||||
|
||||
public SqlDictionaryLookupHook(IServiceProvider services, AgentSettings settings) : base(services, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnAgentLoaded(Agent agent)
|
||||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var isConvMode = conv.IsConversationMode();
|
||||
var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(Utility.SqlDictionaryLookup);
|
||||
|
||||
if (isConvMode && isEnabled)
|
||||
{
|
||||
var (prompt, fns) = GetPromptAndFunctions();
|
||||
if (!fns.IsNullOrEmpty())
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n";
|
||||
}
|
||||
|
||||
if (agent.Functions == null)
|
||||
{
|
||||
agent.Functions = fns;
|
||||
}
|
||||
else
|
||||
{
|
||||
agent.Functions.AddRange(fns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.OnAgentLoaded(agent);
|
||||
}
|
||||
|
||||
private (string, List<FunctionDef>?) GetPromptAndFunctions()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant);
|
||||
var fns = agent?.Functions?.Where(x => _targetSqlExecutorFunctions.Contains(x.Name))?.ToList();
|
||||
|
||||
var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(SQL_EXECUTOR_TEMPLATE))?.Content ?? string.Empty;
|
||||
var dbType = GetDatabaseType();
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
prompt = render.Render(prompt, new Dictionary<string, object>
|
||||
{
|
||||
{ "db_type", dbType }
|
||||
});
|
||||
|
||||
return (prompt, fns);
|
||||
}
|
||||
|
||||
private string GetDatabaseType()
|
||||
{
|
||||
var settings = _services.GetRequiredService<SqlDriverSetting>();
|
||||
var dbType = "MySQL";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings?.SqlServerConnectionString))
|
||||
{
|
||||
dbType = "SQL Server";
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(settings?.SqlLiteConnectionString))
|
||||
{
|
||||
dbType = "SQL Lite";
|
||||
}
|
||||
return dbType;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
namespace BotSharp.Plugin.SqlDriver.Hooks;
|
||||
|
||||
public class SqlExecutorUtilityHook : IAgentUtilityHook
|
||||
public class SqlUtilityHook : IAgentUtilityHook
|
||||
{
|
||||
public void AddUtilities(List<string> utilities)
|
||||
{
|
||||
utilities.Add(Utility.SqlExecutor);
|
||||
utilities.Add(Utility.SqlDictionaryLookup);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,12 @@ namespace BotSharp.Plugin.SqlDriver.Models;
|
|||
|
||||
public class LookupDictionary
|
||||
{
|
||||
[JsonPropertyName("table")]
|
||||
public string Table { get; set; }
|
||||
|
||||
[JsonPropertyName("keyword")]
|
||||
public string Keyword { get; set; }
|
||||
[JsonPropertyName("sql_statement")]
|
||||
public string SqlStatement { get; set; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; set; }
|
||||
|
||||
[JsonPropertyName("columns")]
|
||||
public string[] Columns { get; set; }
|
||||
[JsonPropertyName("table")]
|
||||
public string Table { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ public class SqlDriverPlugin : IBotSharpPlugin
|
|||
services.AddScoped<DbKnowledgeService>();
|
||||
services.AddScoped<IKnowledgeHook, SqlDriverKnowledgeHook>();
|
||||
services.AddScoped<IAgentHook, SqlExecutorHook>();
|
||||
services.AddScoped<IAgentUtilityHook, SqlExecutorUtilityHook>();
|
||||
services.AddScoped<IAgentUtilityHook, SqlUtilityHook>();
|
||||
services.AddScoped<IPlanningHook, SqlDriverPlanningHook>();
|
||||
services.AddScoped<IAgentHook, SqlDictionaryLookupHook>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "sql_dictionary_lookup",
|
||||
"description": "Get id from dictionary table by keyword if tool or solution mentioned this approach",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sql_statement": {
|
||||
"type": "string",
|
||||
"description": "sql text"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "the reason why you need to call sql_dictionary_lookup"
|
||||
},
|
||||
"table": {
|
||||
"type": "string",
|
||||
"description": "table name"
|
||||
}
|
||||
},
|
||||
"required": [ "sql_statement", "reason", "table" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"name": "lookup_dictionary",
|
||||
"description": "Get id from dictionary table by keyword if tool or solution mentioned this approach",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"table": {
|
||||
"type": "string",
|
||||
"description": "table name"
|
||||
},
|
||||
"keyword": {
|
||||
"type": "string",
|
||||
"description": "table name"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "the reason why you need to call lookup_dictionary"
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "columns",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "column"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [ "table", "keyword", "reason", "columns" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Dictionary Lookup Rules:
|
||||
=====
|
||||
Please call function sql_dictionary_lookup if user wants to get or retrieve dictionary/enum/term from data tables.
|
||||
You must return the id and name/code.
|
||||
|
||||
You are connecting to {{ db_type }} database. You can run provided SQL statements by following {{ db_type }} rules.
|
||||
Dictionary table pattern is table name starting with "data_". You can only query the dictionary table without join other non-dictionary tables.
|
||||
=====
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sql_select",
|
||||
"description": "Get the specific value from table",
|
||||
"description": "Execute the reporting related query in the database and get the result",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -11,46 +11,8 @@
|
|||
"reason": {
|
||||
"type": "string",
|
||||
"description": "reason"
|
||||
},
|
||||
"table": {
|
||||
"type": "string",
|
||||
"description": "related table"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "array",
|
||||
"description": "data criteria for the query",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "the name and value for the parameter",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "field name"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "real value inferred by the context"
|
||||
}
|
||||
},
|
||||
"required": [ "name", "value" ]
|
||||
}
|
||||
},
|
||||
"return_field": {
|
||||
"type": "object",
|
||||
"description": "the name and alias for the return field",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "field in the table"
|
||||
},
|
||||
"alias": {
|
||||
"type": "string",
|
||||
"description": "meaningful field alias"
|
||||
}
|
||||
},
|
||||
"required": [ "name", "value" ]
|
||||
}
|
||||
},
|
||||
"required": [ "sql_statement", "reason", "table", "parameters", "return_field" ]
|
||||
"required": [ "sql_statement", "reason" ]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue