Render router instruction by route record dynamically.

This commit is contained in:
hchen2020 2023-08-25 10:33:21 -05:00
parent 88a69f3d1d
commit b03e38950e
12 changed files with 121 additions and 43 deletions

View file

@ -1,3 +1,3 @@
# Prompt Engineering
LLM uses prompt as input, and the model produces different outputs according to the input.
LLM uses prompt as input, and the model produces different outputs according to the input.

View file

@ -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.
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<string, object> dict)
```

View file

@ -4,4 +4,5 @@ public interface IAgentRouting
{
Task<Agent> LoadRouter();
Task<Agent> LoadCurrentAgent();
RoutingRecord[] GetRoutingRecords();
}

View file

@ -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<string> RequiredFields { get; set; }
@ -18,6 +21,6 @@ public class RoutingTable
public override string ToString()
{
return AgentName;
return Name;
}
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Templating;
public interface ITemplateRender
{
bool Render(Agent agent, Dictionary<string, object> dict);
}

View file

@ -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<string, object> 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<string, object> dict)
{
var stateService = _services.GetRequiredService<IConversationStateService>();
var state = stateService.Load();
foreach (var t in state)
{
dict[t.Key] = t.Value;
}
return true;
}
public virtual bool OnFunctionsLoaded(ref string functions)

View file

@ -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<AgentSettings>();
var dbSettings = _services.GetRequiredService<MyDatabaseSettings>();
var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
return JsonSerializer.Deserialize<RoutingRecord[]>(File.ReadAllText(filePath));
}
}

View file

@ -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<string, object>();
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<string, object>());
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<TemplateRender>();
render.Render(agent, templateDict);
_logger.LogInformation($"Loaded agent {agent}.");
return agent;
}
private void PopulateState(Dictionary<string, object> dict)
{
var stateService = _services.GetRequiredService<IConversationStateService>();
var state = stateService.Load();
foreach (var t in state)
{
dict[t.Key] = t.Value;
}
}
}

View file

@ -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<TemplateRender>();
// Register router
services.AddScoped<IAgentRouting, AgentRouter>();
// Register function callback
services.AddScoped<IFunctionCallback, RouteToAgentFn>();
// Register Hooks
services.AddScoped<IAgentHook, AgentHook>();
return services;
}

View file

@ -50,9 +50,9 @@ public class RouteToAgentFn : IFunctionCallback
private bool HasMissingRequiredField(RoleDialogModel message, out string agentId)
{
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
var routes = GetRoutingTable();
var routingRule = routes.FirstOrDefault(x => x.AgentName.ToLower() == args.AgentName.ToLower());
var router = _services.GetRequiredService<IAgentRouting>();
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<AgentSettings>();
var dbSettings = _services.GetRequiredService<MyDatabaseSettings>();
var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
return JsonSerializer.Deserialize<RoutingTable[]>(File.ReadAllText(filePath));
}
}

View file

@ -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<string, object> dict)
{
var router = _services.GetRequiredService<IAgentRouting>();
dict["routing_records"] = router.GetRoutingRecords();
return true;
}
}

View file

@ -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<TemplateRender> logger)
{
_services = services;
_logger = logger;
_options = new TemplateOptions();
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;
_options.MemberAccessStrategy.Register<RoutingRecord>();
}
public bool Render(Agent agent, Dictionary<string, object> 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;
}
}
}