From cbc6b9815158abe69c6fcb27a96e64042a5615c4 Mon Sep 17 00:00:00 2001
From: Haiping Chen <101423@smsassist.com>
Date: Thu, 22 Feb 2024 13:37:14 -0600
Subject: [PATCH 1/2] lookup_dictionary for sQL Driver.
---
.../BotSharp.Plugin.SqlDriver.csproj | 8 +-
.../Functions/LookupDictionaryFn.cs | 79 +++++++++++++++++++
.../Functions/SqlInsertFn.cs | 17 ++++
.../Models/LookupDictionary.cs | 15 ++++
.../functions.json | 26 ++++++
.../instruction.liquid | 6 +-
.../templates/lookup_dictionary.liquid | 9 +++
7 files changed, 152 insertions(+), 8 deletions(-)
create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/Functions/LookupDictionaryFn.cs
create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/Models/LookupDictionary.cs
create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/lookup_dictionary.liquid
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
index fcc88874..171ba4cd 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
@@ -14,6 +14,7 @@
+
@@ -26,6 +27,9 @@
PreserveNewest
+
+ PreserveNewest
+
@@ -38,8 +42,4 @@
-
-
-
-
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/LookupDictionaryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/LookupDictionaryFn.cs
new file mode 100644
index 00000000..94607662
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/LookupDictionaryFn.cs
@@ -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 Execute(RoleDialogModel message)
+ {
+ var args = JsonSerializer.Deserialize(message.FunctionArgs);
+
+ var settings = _services.GetRequiredService();
+ using var connection = new MySqlConnection(settings.MySqlConnectionString);
+ var dictionary = new Dictionary();
+ var results = connection.Query($"SELECT * FROM {args.Table} LIMIT 10");
+ var items = new List();
+ foreach(var item in results)
+ {
+ items.Add(JsonSerializer.Serialize(item));
+ }
+
+ var agentService = _services.GetRequiredService();
+ 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();
+ 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
+ {
+ 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 task, string keyword)
+ {
+ var template = agent.Templates.First(x => x.Name == "lookup_dictionary").Content;
+
+ var render = _services.GetRequiredService();
+ return render.Render(template, new Dictionary
+ {
+ { "items", task },
+ { "keyword", keyword }
+ });
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs
index 42bcd074..e3499b1a 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs
@@ -16,6 +16,23 @@ public class SqlInsertFn : IFunctionCallback
{
var args = JsonSerializer.Deserialize(message.FunctionArgs);
var sqlDriver = _services.GetRequiredService();
+
+ // 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)
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/LookupDictionary.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/LookupDictionary.cs
new file mode 100644
index 00000000..cf7d6345
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/LookupDictionary.cs
@@ -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; }
+}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json
index bcc527ee..85703028 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json
@@ -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" ]
+ }
}
]
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid
index a2aac548..cb0dae8d 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid
@@ -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;
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/lookup_dictionary.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/lookup_dictionary.liquid
new file mode 100644
index 00000000..cf5b190a
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/lookup_dictionary.liquid
@@ -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.
\ No newline at end of file
From f4b4ca4329a7d63555aaae65d0066ccca2c0da24 Mon Sep 17 00:00:00 2001
From: Haiping Chen <101423@smsassist.com>
Date: Thu, 22 Feb 2024 15:14:13 -0600
Subject: [PATCH 2/2] OnConversationRedirected
---
.../Conversations/ConversationHookBase.cs | 5 +++
.../Conversations/IConversationHook.cs | 8 +++++
.../Loggers/Enums/ContentLogSource.cs | 1 +
.../Routing/Functions/RouteToAgentFn.cs | 5 +++
.../Hooks/StreamingLogHook.cs | 33 ++++++++++++++-----
5 files changed, 43 insertions(+), 9 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
index 0d0cb67e..cd45a39e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
@@ -91,4 +91,9 @@ public abstract class ConversationHookBase : IConversationHook
{
return Task.CompletedTask;
}
+
+ public virtual Task OnConversationRedirected(string toAgentId, RoleDialogModel message)
+ {
+ return Task.CompletedTask;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
index 4c7168a2..6056718d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
@@ -82,4 +82,12 @@ public interface IConversationHook
///
///
Task OnHumanInterventionNeeded(RoleDialogModel message);
+
+ ///
+ /// Conversation is redirected to another agent
+ ///
+ ///
+ ///
+ ///
+ Task OnConversationRedirected(string toAgentId, RoleDialogModel message);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs
index f6973770..45d74a51 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs
@@ -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";
}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs
index 26f14f45..c367d631 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs
@@ -153,6 +153,11 @@ public class RouteToAgentFn : IFunctionCallback
#else
logger.LogInformation($"*** Routing redirect to {record.Name.ToUpper()} ***");
#endif
+ var hooks = _services.GetServices();
+ foreach (var hook in hooks)
+ {
+ hook.OnConversationRedirected(routingRule.RedirectTo, message);
+ }
}
else
{
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
index 13997825..0f802b07 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
@@ -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();
+ 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 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();
- 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));
}
///
@@ -118,7 +133,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
{
var agentService = _services.GetRequiredService();
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);