Merge pull request #316 from hchen2020/master

lookup_dictionary for sQL Driver.
This commit is contained in:
C. Oceania 2024-02-22 15:15:13 -06:00 committed by GitHub
commit 7500546426
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 195 additions and 17 deletions

View file

@ -91,4 +91,9 @@ public abstract class ConversationHookBase : IConversationHook
{
return Task.CompletedTask;
}
public virtual Task OnConversationRedirected(string toAgentId, RoleDialogModel message)
{
return Task.CompletedTask;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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