Merge pull request #316 from hchen2020/master
lookup_dictionary for sQL Driver.
This commit is contained in:
commit
7500546426
|
|
@ -91,4 +91,9 @@ public abstract class ConversationHookBase : IConversationHook
|
|||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task OnConversationRedirected(string toAgentId, RoleDialogModel message)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,4 +82,12 @@ public interface IConversationHook
|
|||
/// <param name="conversation"></param>
|
||||
/// <returns></returns>
|
||||
Task OnHumanInterventionNeeded(RoleDialogModel message);
|
||||
|
||||
/// <summary>
|
||||
/// Conversation is redirected to another agent
|
||||
/// </summary>
|
||||
/// <param name="toAgentId"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
Task OnConversationRedirected(string toAgentId, RoleDialogModel message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ public static class ContentLogSource
|
|||
public const string Prompt = "prompt";
|
||||
public const string FunctionCall = "function call";
|
||||
public const string AgentResponse = "agent response";
|
||||
public const string HardRule = "hard rule";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,11 @@ public class RouteToAgentFn : IFunctionCallback
|
|||
#else
|
||||
logger.LogInformation($"*** Routing redirect to {record.Name.ToUpper()} ***");
|
||||
#endif
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.OnConversationRedirected(routingRule.RedirectTo, message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
|||
BuildContentLog(conversationId, _user.UserName, log, ContentLogSource.UserInput, message));
|
||||
}
|
||||
|
||||
public override async Task OnConversationRedirected(string toAgentId, RoleDialogModel message)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var conversationId = _state.GetConversationId();
|
||||
var fromAgent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var toAgent = await agentService.LoadAgent(toAgentId);
|
||||
|
||||
var log = $"{message.Content}\r\n=====\r\nREDIRECTED TO {toAgent.Name}";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated",
|
||||
BuildContentLog(conversationId, fromAgent.Name, log, ContentLogSource.HardRule, message));
|
||||
}
|
||||
|
||||
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
if (!_convSettings.ShowVerboseLog) return;
|
||||
|
|
@ -83,23 +95,26 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
|||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var logSource = string.Empty;
|
||||
|
||||
var log = tokenStats.Prompt;
|
||||
logSource = ContentLogSource.Prompt;
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated",
|
||||
BuildContentLog(conversationId, agent?.Name, log, logSource, message));
|
||||
|
||||
// Log routing output
|
||||
try
|
||||
{
|
||||
var inst = message.Content.JsonContent<FunctionCallFromLlm>();
|
||||
logSource = ContentLogSource.AgentResponse;
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated",
|
||||
BuildContentLog(conversationId, agent?.Name, message.Content, logSource, message));
|
||||
if (!string.IsNullOrEmpty(inst.Function))
|
||||
{
|
||||
logSource = ContentLogSource.AgentResponse;
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated",
|
||||
BuildContentLog(conversationId, agent?.Name, message.Content, logSource, message));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
var log = tokenStats.Prompt;
|
||||
logSource = ContentLogSource.Prompt;
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated",
|
||||
BuildContentLog(conversationId, agent?.Name, log, logSource, message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -118,7 +133,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
|||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
var log = $"[{agent?.Name}]: {message.Content}";
|
||||
var log = $"{message.Content}";
|
||||
if (message.RichContent != null && message.RichContent.Message.RichType != "text")
|
||||
{
|
||||
var richContent = JsonSerializer.Serialize(message.RichContent, _serializerOptions);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\agent.json" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions.json" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instruction.liquid" />
|
||||
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\lookup_dictionary.liquid" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -26,6 +27,9 @@
|
|||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\lookup_dictionary.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -38,8 +42,4 @@
|
|||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\templates\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
using Amazon.Runtime.Internal.Transform;
|
||||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.SqlDriver.Models;
|
||||
using MySqlConnector;
|
||||
using static Dapper.SqlMapper;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Functions;
|
||||
|
||||
public class LookupDictionaryFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "lookup_dictionary";
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public LookupDictionaryFn(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<LookupDictionary>(message.FunctionArgs);
|
||||
|
||||
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)
|
||||
{
|
||||
items.Add(JsonSerializer.Serialize(item));
|
||||
}
|
||||
|
||||
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>
|
||||
{
|
||||
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;
|
||||
|
||||
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 }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,23 @@ public class SqlInsertFn : IFunctionCallback
|
|||
{
|
||||
var args = JsonSerializer.Deserialize<SqlStatement>(message.FunctionArgs);
|
||||
var sqlDriver = _services.GetRequiredService<SqlDriverService>();
|
||||
|
||||
// Check duplication
|
||||
if (sqlDriver.Statements.Exists(x => x.Statement == args.Statement))
|
||||
{
|
||||
var list = sqlDriver.Statements.Where(x => x.Statement == args.Statement).ToList();
|
||||
foreach (var statement in list)
|
||||
{
|
||||
var p1 = string.Join(", ", statement.Parameters.OrderBy(x => x.Name).Select(x => x.Value));
|
||||
var p2 = string.Join(", ", args.Parameters.OrderBy(x => x.Name).Select(x => x.Value));
|
||||
if (p1 == p2)
|
||||
{
|
||||
message.Content = "Skip duplicated INSERT statement";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlDriver.Enqueue(args);
|
||||
message.Content = $"Inserted new record successfully.";
|
||||
if (args.Return != null)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.SqlDriver.Models;
|
||||
|
||||
public class LookupDictionary
|
||||
{
|
||||
[JsonPropertyName("table")]
|
||||
public string Table { get; set; }
|
||||
|
||||
[JsonPropertyName("keyword")]
|
||||
public string Keyword { get; set; }
|
||||
|
||||
[JsonPropertyName("columns")]
|
||||
public string[] Columns { get; set; }
|
||||
}
|
||||
|
|
@ -124,5 +124,31 @@
|
|||
},
|
||||
"required": [ "sql_statement", "reason", "table", "parameters", "return_field" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "columns",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "column"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [ "table", "columns", "keyword" ]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -4,8 +4,6 @@ Output the next step smartly.
|
|||
|
||||
Your response must meet below requirements:
|
||||
* Walk through the provided information, don't run query if there is already related information;
|
||||
* DO NOT generate duplicated sql statements;
|
||||
* The return field alias should be meaningful, it can be similar name of reference table column;
|
||||
* Make sure the SELECT and WHERE fields are in corresponding table schema definition;
|
||||
* Make sure you have the corresponding table columns information before generating SQL;
|
||||
* The return field alias should be meaningful, you can use the combination of column and value as the alias name;
|
||||
* Use "Unique Index" to help check record existence;
|
||||
* For INSERT statement with mutliple records, should return in different meaningful alias;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
DICTIONARY:
|
||||
|
||||
{% for item in items %}
|
||||
* {{ item }}
|
||||
{% endfor %}
|
||||
|
||||
=====
|
||||
Which item is the best matching with "{{ keyword }}"?
|
||||
You must return Id and Name field.
|
||||
Loading…
Reference in a new issue