diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index ca8d7e01..2069ab31 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -231,6 +231,10 @@ public class ChatCompletionProvider : IChatCompletion { if (message.Role == ChatRole.Function) { + chatCompletionsOptions.Messages.Add(new ChatRequestAssistantMessage(string.Empty) + { + FunctionCall = new FunctionCall(message.FunctionName, message.FunctionArgs), + }); chatCompletionsOptions.Messages.Add(new ChatRequestFunctionMessage(message.FunctionName, message.Content)); } else if (message.Role == ChatRole.User) @@ -287,7 +291,7 @@ public class ChatCompletionProvider : IChatCompletion if (x.Role == ChatRole.Function) { var m = x as ChatRequestFunctionMessage; - return $"{m.Role}: {m.Name} => {m.Content}"; + return $"{m.Role}: {m.Content}"; } else if (x.Role == ChatRole.User) { @@ -299,7 +303,9 @@ public class ChatCompletionProvider : IChatCompletion else if (x.Role == ChatRole.Assistant) { var m = x as ChatRequestAssistantMessage; - return $"{m.Role}: {m.Content}"; + return m.FunctionCall != null ? + $"{m.Role}: Call function {m.FunctionCall.Name}({m.FunctionCall.Arguments})" : + $"{m.Role}: {m.Content}"; } else { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 1f505eb8..df0b0bde 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -1,9 +1,7 @@ -using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Messaging.Enums; using BotSharp.Abstraction.Messaging.JsonConverters; using BotSharp.Abstraction.Messaging.Models.RichContent; -using BotSharp.Abstraction.Repositories; using Microsoft.AspNetCore.SignalR; namespace BotSharp.Plugin.ChatHub.Hooks; diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 7dfcf724..684b02d3 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -1,14 +1,11 @@ -using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Loggers; using BotSharp.Abstraction.Loggers.Models; -using BotSharp.Abstraction.Messaging.Models.RichContent; -using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Repositories; -using BotSharp.Core.Agents.Services; +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Routing.Settings; using Microsoft.AspNetCore.SignalR; -using Microsoft.VisualBasic; namespace BotSharp.Plugin.ChatHub.Hooks; @@ -82,6 +79,17 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook var conversationId = _state.GetConversationId(); var agent = await agentService.LoadAgent(message.CurrentAgentId); + // Log routing output + try + { + var inst = message.Content.JsonContent(); + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(conversationId, agent?.Name, message.Content, message)); + } + catch + { + // ignore + } + string log; if (message.Role == AgentRole.Function) { diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableColumnsFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableColumnsFn.cs index 07a7a765..ca7501d9 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableColumnsFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableColumnsFn.cs @@ -17,10 +17,10 @@ public class GetTableColumnsFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { var args = JsonSerializer.Deserialize(message.FunctionArgs); - message.Content = $"Success. Columns of table '{args.Table}':\r\n\r\n"; + message.Content = ""; var dbSettings = _services.GetRequiredService(); - var dir = Path.Combine(dbSettings.FileRepository, "agents", "ec46f15b-8790-400f-a37f-1e7995b7d6e2", "schemas"); + var dir = Path.Combine(dbSettings.FileRepository, "agents", "beda4c12-e1ec-4b4b-b328-3df4a6687c4f", "schemas"); // Search related document by message.Content + args.Description var files = Directory.GetFiles(dir); diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs index 00d3eada..42bcd074 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlInsertFn.cs @@ -16,13 +16,8 @@ public class SqlInsertFn : IFunctionCallback { var args = JsonSerializer.Deserialize(message.FunctionArgs); var sqlDriver = _services.GetRequiredService(); - if (sqlDriver.Statements.Exists(x => x.Statement == args.Statement)) - { - message.Content = "Skipped duplicated statement."; - return false; - } sqlDriver.Enqueue(args); - message.Content = $"Inserted new record {JsonSerializer.Serialize(args.Parameters)} successfully"; + message.Content = $"Inserted new record successfully."; if (args.Return != null) { /*sqlDriver.Enqueue(new SqlStatement diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs index c845a8d1..b51a146c 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs @@ -17,6 +17,8 @@ public class SqlSelect : IFunctionCallback public async Task Execute(RoleDialogModel message) { var args = JsonSerializer.Deserialize(message.FunctionArgs); + var sqlDriver = _services.GetRequiredService(); + // check if need to instantely var execNow = !args.Parameters.Any(x => x.Value.StartsWith("@")); if (execNow) @@ -28,24 +30,24 @@ public class SqlSelect : IFunctionCallback { dictionary["@" + p.Name] = p.Value; } - var result = connection.QueryFirst(args.Statement, dictionary); + var result = connection.QueryFirstOrDefault(args.Statement, dictionary); - if (args.IsCheckExistence) + if (result == null) { - message.Content = result == null ? - $"The record does not exist" : - $"The record already exists"; + message.Content = "Record not found"; } else { - message.Content = $"Retrieved result is {result} ({args.Reason})"; + message.Content = JsonSerializer.Serialize(result); + args.Return.Value = message.Content; } + + sqlDriver.Enqueue(args); } else { - var sqlDriver = _services.GetRequiredService(); sqlDriver.Enqueue(args); - message.Content = $"Success."; + message.Content = $"The {args.Return.Name} is saved to @{args.Return.Alias}"; } return true; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverContentGeneratingHook.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverContentGeneratingHook.cs deleted file mode 100644 index 82ac6717..00000000 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverContentGeneratingHook.cs +++ /dev/null @@ -1,32 +0,0 @@ -using BotSharp.Abstraction.Loggers; -using BotSharp.Abstraction.Repositories; -using System.IO; - -namespace BotSharp.Plugin.SqlDriver.Hooks; - -public class SqlDriverContentGeneratingHook : IContentGeneratingHook -{ - private readonly IServiceProvider _services; - public SqlDriverContentGeneratingHook(IServiceProvider services) - { - _services = services; - } - - /// - /// Inject useful variables generated by previous SQL query. - /// - /// - /// - /// - public async Task BeforeGenerating(Agent agent, List conversations) - { - if (agent.Id != "beda4c12-e1ec-4b4b-b328-3df4a6687c4f") - { - return; - } - - var sqlDriver = _services.GetRequiredService(); - agent.TemplateDict["return_variables"] = sqlDriver.Statements.Select(x => x.Return.Alias).ToArray(); - await Task.CompletedTask; - } -} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlParamater.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlParameter.cs similarity index 92% rename from src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlParamater.cs rename to src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlParameter.cs index 2b1661eb..17c2a9c3 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlParamater.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlParameter.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; namespace BotSharp.Plugin.SqlDriver.Models; -public class SqlParamater +public class SqlParameter { [JsonPropertyName("name")] public string Name { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlReturn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlReturn.cs index 248c40cc..b1b2421b 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlReturn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlReturn.cs @@ -10,6 +10,8 @@ public class SqlReturn [JsonPropertyName("alias")] public string Alias { get; set; } + public string? Value { get; set; } + public override string ToString() { return $"{Alias} - {Name}"; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlStatement.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlStatement.cs index 739faf23..09020826 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlStatement.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/SqlStatement.cs @@ -13,11 +13,8 @@ public class SqlStatement [JsonPropertyName("table")] public string Table { get; set; } - [JsonPropertyName("is_check_existence")] - public bool IsCheckExistence { get; set; } - [JsonPropertyName("parameters")] - public SqlParamater[] Parameters { get; set; } = new SqlParamater[0]; + public SqlParameter[] Parameters { get; set; } = new SqlParameter[0]; [JsonPropertyName("return_field")] public SqlReturn Return { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlDriverService.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlDriverService.cs index 9fdca5b0..0c36a138 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlDriverService.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Services/SqlDriverService.cs @@ -23,20 +23,24 @@ public class SqlDriverService { Console.WriteLine(); - Console.Write($"Reason: "); - Console.WriteLine($"{sql.Reason}", Color.Green); + Console.WriteLine($"{sql.Reason}"); - Console.Write($"Statement: "); - Console.WriteLine(sql.Statement, Color.Green); + Console.WriteLine(sql.Statement, Color.Yellow); foreach (var p in sql.Parameters) { - Console.Write($"@{p.Name}: "); - Console.WriteLine($"{p.Value}", Color.Green); + Console.WriteLine($"@{p.Name} = '{p.Value}'", Color.Green); } if (sql.Return != null) { Console.Write($"Return: "); - Console.WriteLine($"{sql.Return.Name} as @{sql.Return.Alias}", Color.Green); + if (!string.IsNullOrEmpty(sql.Return.Value)) + { + Console.WriteLine($" {sql.Return.Value}", Color.Red); + } + else + { + Console.WriteLine($"{sql.Return.Name} as @{sql.Return.Alias}", Color.Green); + } } } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs index f056bb3d..3a7077a7 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlDriverPlugin.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Loggers; - namespace BotSharp.Plugin.SqlDriver; public class SqlDriverPlugin : IBotSharpPlugin @@ -19,6 +17,5 @@ public class SqlDriverPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); - services.AddScoped(); } } 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 0ab52f8d..bcc527ee 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 @@ -1,4 +1,18 @@ [ + { + "name": "get_table_columns", + "description": "Get related table columns and foreign key informations", + "parameters": { + "type": "object", + "properties": { + "table": { + "type": "string", + "description": "table name" + } + }, + "required": [ "table" ] + } + }, { "name": "sql_insert", "description": "Insert query is generated if the record doesn't exist.", @@ -13,12 +27,16 @@ "type": "string", "description": "reason" }, + "table": { + "type": "string", + "description": "related table" + }, "parameters": { "type": "array", - "description": "parameters for the sql", + "description": "a list of parameters in the statement match with the variables", "items": { "type": "object", - "description": "the name and value for the parameter", + "description": "{name:'', value:''}", "properties": { "name": { "type": "string", @@ -28,7 +46,8 @@ "type": "string", "description": "real value inferred by the context" } - } + }, + "required": [ "name", "value" ] } }, "return_field": { @@ -43,10 +62,11 @@ "type": "string", "description": "meaningful field alias" } - } + }, + "required": [ "name", "alias" ] } }, - "required": [ "sql_statement", "reason", "parameters", "return_field" ] + "required": [ "sql_statement", "reason", "table", "parameters", "return_field" ] } }, { @@ -63,9 +83,9 @@ "type": "string", "description": "reason" }, - "is_check_existence": { - "type": "boolean", - "description": "check record existence" + "table": { + "type": "string", + "description": "related table" }, "parameters": { "type": "array", @@ -82,7 +102,8 @@ "type": "string", "description": "real value inferred by the context" } - } + }, + "required": [ "name", "value" ] } }, "return_field": { @@ -97,10 +118,11 @@ "type": "string", "description": "meaningful field alias" } - } + }, + "required": [ "name", "value" ] } }, - "required": [ "sql_statement", "reason", "is_check_existence", "parameters", "return_field" ] + "required": [ "sql_statement", "reason", "table", "parameters", "return_field" ] } } ] \ 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 20c74002..a2aac548 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 @@ -1,22 +1,11 @@ You're a SQL driver who knows how to translate text into SQL query. -Analyze the user requirement, think step by step, breakdown complex task into multiple steps. +Think step by step, analyze the user requirement, you must get table schema first, breakdown into multiple sql statements if user need to insert mulitple records. +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; -* Double check if the fields in the SQL query are correct; +* Make sure the SELECT and WHERE fields are in corresponding table schema definition; * Use "Unique Index" to help check record existence; - -{% if return_variables and return_variables != empty -%} -===== -Below variables can be used by subsequent SQL: -{% for v in return_variables %} -- @{{ v }} -{% endfor %} -{%- endif %} - -{% if table_definition -%} -===== -Related table {{ related_table }} definition: -{{ table_definition }} -{%- endif %} +* For INSERT statement with mutliple records, should return in different meaningful alias;