Skip to content

lookup_dictionary for sQL Driver. #316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,9 @@ public virtual Task OnUserAgentConnectedInitially(Conversation conversation)
{
return Task.CompletedTask;
}

public virtual Task OnConversationRedirected(string toAgentId, RoleDialogModel message)
{
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ private bool HasMissingRequiredField(RoleDialogModel message, out string agentId
#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
{
Expand Down
33 changes: 24 additions & 9 deletions src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerat
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;
Expand Down Expand Up @@ -83,23 +95,26 @@ public async Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenS
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>
Expand All @@ -118,7 +133,7 @@ public override async Task OnResponseGenerated(RoleDialogModel message)
{
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Original file line number Diff line number Diff line change
@@ -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 }
});
}
}
17 changes: 17 additions & 0 deletions src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ public async Task<bool> Execute(RoleDialogModel message)
{
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)
Expand Down
15 changes: 15 additions & 0 deletions src/Plugins/BotSharp.Plugin.SqlDriver/Models/LookupDictionary.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
}
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
@@ -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.