From b03e38950e673d7f2fe3a7f98300b695ff921635 Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Fri, 25 Aug 2023 10:33:21 -0500 Subject: [PATCH] Render router instruction by route record dynamically. --- docs/llm/prompt.md | 2 +- docs/llm/template.md | 8 +++- .../Agents/IAgentRouting.cs | 1 + .../{RoutingTable.cs => RoutingRecord.cs} | 9 +++-- .../Templating/ITemplateRender.cs | 6 +++ .../Agents/Services/AgentHookBase.cs | 30 +++----------- .../Agents/Services/AgentRouter.cs | 10 ++++- .../Agents/Services/AgentService.LoadAgent.cs | 19 ++++++++- .../BotSharpServiceCollectionExtensions.cs | 10 +++++ .../BotSharp.Core/Functions/RouteToAgentFn.cs | 14 ++----- .../BotSharp.Core/Hooks/AgentHook.cs | 16 ++++++++ .../Templating/TemplateRender.cs | 39 +++++++++++++++++++ 12 files changed, 121 insertions(+), 43 deletions(-) rename src/Infrastructure/BotSharp.Abstraction/Agents/Models/{RoutingTable.cs => RoutingRecord.cs} (71%) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs create mode 100644 src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs create mode 100644 src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs diff --git a/docs/llm/prompt.md b/docs/llm/prompt.md index da1ae5f2..af101646 100644 --- a/docs/llm/prompt.md +++ b/docs/llm/prompt.md @@ -1,3 +1,3 @@ # Prompt Engineering -LLM uses prompt as input, and the model produces different outputs according to the input. \ No newline at end of file +LLM uses prompt as input, and the model produces different outputs according to the input. diff --git a/docs/llm/template.md b/docs/llm/template.md index 35260c44..f0c197b1 100644 --- a/docs/llm/template.md +++ b/docs/llm/template.md @@ -1,3 +1,9 @@ # Template -We can define the prompt as a template, and the template can be changed according to variables, so that a instruction file can be used to generate a dynamic prompt. \ No newline at end of file +We can define the prompt as a template, and the template can be changed according to variables, so that a instruction file can be used to generate a dynamic prompt. +`BotSharp` uses [liquid](https://shopify.github.io/liquid/) templates to support various complex dynamic prompt engineering. + +`ITemplateRender` +```csharp +bool Render(Agent agent, Dictionary dict) +``` \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs index 8b0769a7..4df491db 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs @@ -4,4 +4,5 @@ public interface IAgentRouting { Task LoadRouter(); Task LoadCurrentAgent(); + RoutingRecord[] GetRoutingRecords(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs similarity index 71% rename from src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs rename to src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs index fd2ba8f7..8f83c74b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs @@ -2,13 +2,16 @@ using System.Text.Json.Serialization; namespace BotSharp.Abstraction.Agents.Models; -public class RoutingTable +public class RoutingRecord { [JsonPropertyName("agent_id")] public string AgentId { get; set; } [JsonPropertyName("name")] - public string AgentName { get; set; } + public string Name { get; set; } + + [JsonPropertyName("description")] + public string Description { get; set; } [JsonPropertyName("required")] public List RequiredFields { get; set; } @@ -18,6 +21,6 @@ public class RoutingTable public override string ToString() { - return AgentName; + return Name; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs new file mode 100644 index 00000000..61ba9ff7 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Templating; + +public interface ITemplateRender +{ + bool Render(Agent agent, Dictionary dict); +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentHookBase.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentHookBase.cs index f1b71eb8..eaf28792 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentHookBase.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentHookBase.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Agents.Models; -using Fluid; namespace BotSharp.Core.Agents.Services; @@ -7,13 +6,14 @@ public abstract class AgentHookBase : IAgentHook { protected Agent _agent; public Agent Agent => _agent; - private static readonly FluidParser _parser = new FluidParser(); - private readonly IServiceProvider _services; + protected readonly IServiceProvider _services; + protected readonly AgentSettings _settings; - public AgentHookBase(IServiceProvider services) + public AgentHookBase(IServiceProvider services, AgentSettings settings) { _services = services; + _settings = settings; } public void SetAget(Agent agent) @@ -28,27 +28,7 @@ public abstract class AgentHookBase : IAgentHook public virtual bool OnInstructionLoaded(string template, Dictionary dict) { - if (_parser.TryParse(template, out var t, out var error)) - { - PopulateStateTokens(dict); - var context = new TemplateContext(dict); - _agent.Instruction = t.Render(context); - return true; - } - else - { - return false; - } - } - - private void PopulateStateTokens(Dictionary dict) - { - var stateService = _services.GetRequiredService(); - var state = stateService.Load(); - foreach (var t in state) - { - dict[t.Key] = t.Value; - } + return true; } public virtual bool OnFunctionsLoaded(ref string functions) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs index f14ed93c..0aed1531 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs @@ -1,5 +1,5 @@ -using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Agents.Models; +using System.IO; namespace BotSharp.Core.Agents.Services; @@ -42,4 +42,12 @@ public class AgentRouter : IAgentRouting return agent; } + + public RoutingRecord[] GetRoutingRecords() + { + var agentSettings = _services.GetRequiredService(); + var dbSettings = _services.GetRequiredService(); + var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json"); + return JsonSerializer.Deserialize(File.ReadAllText(filePath)); + } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index f3b91e3e..ec89334f 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.Core.Templating; namespace BotSharp.Core.Agents.Services; @@ -15,6 +16,8 @@ public partial class AgentService } var agent = await GetAgent(id); + var templateDict = new Dictionary(); + PopulateState(templateDict); // After agent is loaded foreach (var hook in hooks) @@ -23,7 +26,7 @@ public partial class AgentService if (!string.IsNullOrEmpty(agent.Instruction)) { - hook.OnInstructionLoaded(agent.Instruction, new Dictionary()); + hook.OnInstructionLoaded(agent.Instruction, templateDict); } if (!string.IsNullOrEmpty(agent.Functions)) @@ -41,8 +44,22 @@ public partial class AgentService hook.OnAgentLoaded(agent); } + // render liquid template + var render = _services.GetRequiredService(); + render.Render(agent, templateDict); + _logger.LogInformation($"Loaded agent {agent}."); return agent; } + + private void PopulateState(Dictionary dict) + { + var stateService = _services.GetRequiredService(); + var state = stateService.Load(); + foreach (var t in state) + { + dict[t.Key] = t.Value; + } + } } diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 9081ca05..1b38659c 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -1,5 +1,7 @@ using BotSharp.Abstraction.Functions; using BotSharp.Core.Functions; +using BotSharp.Core.Hooks; +using BotSharp.Core.Templating; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; @@ -35,10 +37,18 @@ public static class BotSharpServiceCollectionExtensions RegisterPlugins(services, config); + // Register template render + services.AddSingleton(); + + // Register router services.AddScoped(); + // Register function callback services.AddScoped(); + // Register Hooks + services.AddScoped(); + return services; } diff --git a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs index c34cc189..3834b035 100644 --- a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs @@ -50,9 +50,9 @@ public class RouteToAgentFn : IFunctionCallback private bool HasMissingRequiredField(RoleDialogModel message, out string agentId) { var args = JsonSerializer.Deserialize(message.FunctionArgs); - - var routes = GetRoutingTable(); - var routingRule = routes.FirstOrDefault(x => x.AgentName.ToLower() == args.AgentName.ToLower()); + var router = _services.GetRequiredService(); + var records = router.GetRoutingRecords(); + var routingRule = records.FirstOrDefault(x => x.Name.ToLower() == args.AgentName.ToLower()); if (routingRule == null) { @@ -93,12 +93,4 @@ public class RouteToAgentFn : IFunctionCallback return hasMissingField; } - - private RoutingTable[] GetRoutingTable() - { - var agentSettings = _services.GetRequiredService(); - var dbSettings = _services.GetRequiredService(); - var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json"); - return JsonSerializer.Deserialize(File.ReadAllText(filePath)); - } } diff --git a/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs b/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs new file mode 100644 index 00000000..e030a904 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Hooks/AgentHook.cs @@ -0,0 +1,16 @@ +namespace BotSharp.Core.Hooks; + +public class AgentHook : AgentHookBase +{ + public AgentHook(IServiceProvider services, AgentSettings settings) + : base(services, settings) + { + } + + public override bool OnInstructionLoaded(string template, Dictionary dict) + { + var router = _services.GetRequiredService(); + dict["routing_records"] = router.GetRoutingRecords(); + return true; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs new file mode 100644 index 00000000..ca80356a --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -0,0 +1,39 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Templating; +using Fluid; +using Microsoft.Extensions.Options; + +namespace BotSharp.Core.Templating; + +public class TemplateRender : ITemplateRender +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + private static readonly FluidParser _parser = new FluidParser(); + private TemplateOptions _options; + + public TemplateRender(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + _options = new TemplateOptions(); + _options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase; + _options.MemberAccessStrategy.Register(); + } + + public bool Render(Agent agent, 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; + } + else + { + + return false; + } + } +}