This commit is contained in:
Jicheng Lu 2024-09-24 15:34:53 -05:00
commit 689c5d2572
21 changed files with 185 additions and 141 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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; }
}

View file

@ -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" ]
}
}

View file

@ -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.

View file

@ -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.

View file

@ -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 }}]

View file

@ -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.

View file

@ -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">

View file

@ -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";
}

View file

@ -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 }
});
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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; }
}

View file

@ -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>();
}
}

View file

@ -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" ]
}
}

View file

@ -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" ]
}
}

View file

@ -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.
=====

View file

@ -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" ]
}
}