From 4dcb00ca83d92d9d7cbf7160c43530eef4e00cbd Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Wed, 30 Aug 2023 18:29:01 -0500 Subject: [PATCH] Function templating response. --- .../Templating/IResponseTemplateService.cs | 6 ++ .../Templating/ITemplateRender.cs | 2 +- .../BotSharp.Abstraction/Using.cs | 3 +- .../Agents/Services/AgentService.LoadAgent.cs | 5 +- .../BotSharpServiceCollectionExtensions.cs | 4 +- ...vice.GetChatCompletionsAsyncRecursively.cs | 55 +++++++++++---- .../Templating/ResponseTemplateService.cs | 69 +++++++++++++++++++ .../Templating/TemplateRender.cs | 11 ++- src/Infrastructure/BotSharp.Core/Using.cs | 7 +- .../Functions/GetPizzaPricesFn.cs | 5 ++ 10 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Templating/IResponseTemplateService.cs create mode 100644 src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Templating/IResponseTemplateService.cs b/src/Infrastructure/BotSharp.Abstraction/Templating/IResponseTemplateService.cs new file mode 100644 index 00000000..2aecafcc --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Templating/IResponseTemplateService.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Templating; + +public interface IResponseTemplateService +{ + Task RenderFunctionResponse(string agentId, RoleDialogModel fn); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs index 61ba9ff7..e5ddeb61 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs @@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.Templating; public interface ITemplateRender { - bool Render(Agent agent, Dictionary dict); + string Render(string template, Dictionary dict); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index 6a73f3ff..b198abb3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Using.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs @@ -3,4 +3,5 @@ global using System.Collections.Generic; global using System.Text; global using System.Threading.Tasks; global using System.ComponentModel.DataAnnotations; -global using BotSharp.Abstraction.Agents.Models; \ No newline at end of file +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Conversations.Models; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index ec89334f..e2f13567 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Templating; using BotSharp.Core.Templating; namespace BotSharp.Core.Agents.Services; @@ -45,8 +46,8 @@ public partial class AgentService } // render liquid template - var render = _services.GetRequiredService(); - render.Render(agent, templateDict); + var render = _services.GetRequiredService(); + agent.Instruction = render.Render(agent.Instruction, templateDict); _logger.LogInformation($"Loaded agent {agent}."); diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 8316e274..544fc826 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -8,6 +8,7 @@ using BotSharp.Core.Plugins.Knowledges.Services; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using BotSharp.Abstraction.Routing.Settings; +using BotSharp.Abstraction.Templating; namespace BotSharp.Core; @@ -42,7 +43,8 @@ public static class BotSharpServiceCollectionExtensions RegisterPlugins(services, config); // Register template render - services.AddSingleton(); + services.AddSingleton(); + services.AddScoped(); // Register router var routingSettings = new RoutingSettings(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index 4e5a0d23..cad2a556 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -2,6 +2,11 @@ using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.Templating; +using BotSharp.Core.Templating; +using System.IO; +using Tensorflow.Keras.Layers.Rnn; +using static System.Net.Mime.MediaTypeNames; namespace BotSharp.Core.Conversations.Services; @@ -68,22 +73,48 @@ public partial class ConversationService { var agentService = _services.GetRequiredService(); agent = await agentService.LoadAgent(fn.CurrentAgentId); + + wholeDialogs.Add(fn); + + await GetChatCompletionsAsyncRecursively(chatCompletion, + conversationId, + agent, + wholeDialogs, + onMessageReceived, + onFunctionExecuting, + onFunctionExecuted); } + else + { + // Find response template + var templateService = _services.GetRequiredService(); + var response = await templateService.RenderFunctionResponse(agent.Id, fn); + if (!string.IsNullOrEmpty(response)) + { + await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, response) + { + CurrentAgentId = agent.Id, + Channel = wholeDialogs.Last().Channel + }, onMessageReceived); - // Add to dialog history - // The server had an error processing your request. Sorry about that! - // _storage.Append(conversationId, preAgentId, fn); + return; + } + + // Add to dialog history + // The server had an error processing your request. Sorry about that! + // _storage.Append(conversationId, preAgentId, fn); - // After function is executed, pass the result to LLM to get a natural response - wholeDialogs.Add(fn); + // After function is executed, pass the result to LLM to get a natural response + wholeDialogs.Add(fn); - await GetChatCompletionsAsyncRecursively(chatCompletion, - conversationId, - agent, - wholeDialogs, - onMessageReceived, - onFunctionExecuting, - onFunctionExecuted); + await GetChatCompletionsAsyncRecursively(chatCompletion, + conversationId, + agent, + wholeDialogs, + onMessageReceived, + onFunctionExecuting, + onFunctionExecuted); + } }); return result; diff --git a/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs b/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs new file mode 100644 index 00000000..495d42a3 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs @@ -0,0 +1,69 @@ +using BotSharp.Abstraction.Templating; +using System.IO; +using System.Reflection; + +namespace BotSharp.Core.Templating; + +public class ResponseTemplateService : IResponseTemplateService +{ + private readonly IServiceProvider _services; + public ResponseTemplateService(IServiceProvider services) + { + _services = services; + } + + public async Task RenderFunctionResponse(string agentId, RoleDialogModel fn) + { + // Find response template + var agentService = _services.GetRequiredService(); + var dir = Path.Combine(agentService.GetAgentDataDir(agentId), "responses"); + var responses = Directory.GetFiles(dir) + .Where(f => f.Split(Path.DirectorySeparatorChar).Last().Split('.')[1] == fn.FunctionName) + .ToList(); + + if (responses.Count == 0) + { + return string.Empty; + } + + var randomIndex = new Random().Next(0, responses.Count); + var template = File.ReadAllText(responses[randomIndex]); + + var render = _services.GetRequiredService(); + + // Convert args and execute data to dictionary + var dict = new Dictionary(); + ExtractArgs(JsonSerializer.Deserialize(fn.FunctionArgs), dict); + ExtractExecuteData(fn.ExecutionData, dict); + + var text = render.Render(template, dict); + + return text; + } + + private void ExtractArgs(JsonDocument args, Dictionary dict) + { + if (args.RootElement is JsonElement root) + { + foreach (JsonProperty property in root.EnumerateObject()) + { + if (!string.IsNullOrEmpty(property.Value.ToString())) + { + dict[property.Name] = property.Value.ToString(); + } + } + } + } + + private void ExtractExecuteData(object data, Dictionary dict) + { + foreach (PropertyInfo property in data.GetType().GetProperties()) + { + var value = property.GetValue(data, null); + if (value != null) + { + dict[property.Name] = value; + } + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs index 97263119..d268db4c 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -1,8 +1,6 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Templating; using Fluid; -using Microsoft.Extensions.Options; namespace BotSharp.Core.Templating; @@ -22,19 +20,18 @@ public class TemplateRender : ITemplateRender _options.MemberAccessStrategy.Register(); } - public bool Render(Agent agent, Dictionary dict) + public string Render(string template, Dictionary dict) { - var template = agent.Instruction; if (_parser.TryParse(template, out var t, out var error)) { var context = new TemplateContext(dict, _options); - agent.Instruction = t.Render(context); - return true; + template = t.Render(context); + return template; } else { - return false; + return template; } } } diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index eae0a5a5..dca1b515 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -13,6 +13,9 @@ global using BotSharp.Abstraction.Conversations; global using BotSharp.Abstraction.Knowledges; global using BotSharp.Abstraction.Users; global using BotSharp.Abstraction.Utilities; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Agents.Settings; +global using BotSharp.Abstraction.Conversations.Settings; global using BotSharp.Core.Repository; global using BotSharp.Core.Repository.Abstraction; global using BotSharp.Core.Repository.DbTables; @@ -20,6 +23,4 @@ global using BotSharp.Core.Agents.Services; global using BotSharp.Core.Conversations.Services; global using BotSharp.Core.Infrastructures; global using BotSharp.Core.Plugins; -global using BotSharp.Core.Users.Services; -global using BotSharp.Abstraction.Agents.Settings; -global using BotSharp.Abstraction.Conversations.Settings; \ No newline at end of file +global using BotSharp.Core.Users.Services; \ No newline at end of file diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs index cf1d3a0c..625e9ec4 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Conversations.Models; +using System.Text.Json; namespace BotSharp.Plugin.PizzaBot.Functions; @@ -8,6 +9,10 @@ public class GetPizzaPricesFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { + message.ExecutionData = new + { + cheese = "3.5" + }; message.ExecutionResult = "Pepperoni Pizza: $3.5/slice, Cheese Pizza: $2.5/slice, Margherita Pizza: $3.0/slice"; return true; }