Skip to content

Commit 335b755

Browse files
authored
Merge pull request #116 from hchen2020/master
v Render router instruction by route record dynamically.
2 parents 88a69f3 + b03e389 commit 335b755

File tree

12 files changed

+121
-43
lines changed

12 files changed

+121
-43
lines changed

docs/llm/prompt.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Prompt Engineering
22

3-
LLM uses prompt as input, and the model produces different outputs according to the input.
3+
LLM uses prompt as input, and the model produces different outputs according to the input.

docs/llm/template.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
# Template
22

3-
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.
3+
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.
4+
`BotSharp` uses [liquid](https://shopify.github.io/liquid/) templates to support various complex dynamic prompt engineering.
5+
6+
`ITemplateRender`
7+
```csharp
8+
bool Render(Agent agent, Dictionary<string, object> dict)
9+
```

src/Infrastructure/BotSharp.Abstraction/Agents/IAgentRouting.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ public interface IAgentRouting
44
{
55
Task<Agent> LoadRouter();
66
Task<Agent> LoadCurrentAgent();
7+
RoutingRecord[] GetRoutingRecords();
78
}

src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs renamed to src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingRecord.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
namespace BotSharp.Abstraction.Agents.Models;
44

5-
public class RoutingTable
5+
public class RoutingRecord
66
{
77
[JsonPropertyName("agent_id")]
88
public string AgentId { get; set; }
99

1010
[JsonPropertyName("name")]
11-
public string AgentName { get; set; }
11+
public string Name { get; set; }
12+
13+
[JsonPropertyName("description")]
14+
public string Description { get; set; }
1215

1316
[JsonPropertyName("required")]
1417
public List<string> RequiredFields { get; set; }
@@ -18,6 +21,6 @@ public class RoutingTable
1821

1922
public override string ToString()
2023
{
21-
return AgentName;
24+
return Name;
2225
}
2326
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace BotSharp.Abstraction.Templating;
2+
3+
public interface ITemplateRender
4+
{
5+
bool Render(Agent agent, Dictionary<string, object> dict);
6+
}

src/Infrastructure/BotSharp.Core/Agents/Services/AgentHookBase.cs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
using BotSharp.Abstraction.Agents.Models;
2-
using Fluid;
32

43
namespace BotSharp.Core.Agents.Services;
54

65
public abstract class AgentHookBase : IAgentHook
76
{
87
protected Agent _agent;
98
public Agent Agent => _agent;
10-
private static readonly FluidParser _parser = new FluidParser();
119

12-
private readonly IServiceProvider _services;
10+
protected readonly IServiceProvider _services;
11+
protected readonly AgentSettings _settings;
1312

14-
public AgentHookBase(IServiceProvider services)
13+
public AgentHookBase(IServiceProvider services, AgentSettings settings)
1514
{
1615
_services = services;
16+
_settings = settings;
1717
}
1818

1919
public void SetAget(Agent agent)
@@ -28,27 +28,7 @@ public virtual bool OnAgentLoading(ref string id)
2828

2929
public virtual bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
3030
{
31-
if (_parser.TryParse(template, out var t, out var error))
32-
{
33-
PopulateStateTokens(dict);
34-
var context = new TemplateContext(dict);
35-
_agent.Instruction = t.Render(context);
36-
return true;
37-
}
38-
else
39-
{
40-
return false;
41-
}
42-
}
43-
44-
private void PopulateStateTokens(Dictionary<string, object> dict)
45-
{
46-
var stateService = _services.GetRequiredService<IConversationStateService>();
47-
var state = stateService.Load();
48-
foreach (var t in state)
49-
{
50-
dict[t.Key] = t.Value;
51-
}
31+
return true;
5232
}
5333

5434
public virtual bool OnFunctionsLoaded(ref string functions)

src/Infrastructure/BotSharp.Core/Agents/Services/AgentRouter.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using BotSharp.Abstraction.Agents;
21
using BotSharp.Abstraction.Agents.Models;
2+
using System.IO;
33

44
namespace BotSharp.Core.Agents.Services;
55

@@ -42,4 +42,12 @@ public async Task<Agent> LoadCurrentAgent()
4242

4343
return agent;
4444
}
45+
46+
public RoutingRecord[] GetRoutingRecords()
47+
{
48+
var agentSettings = _services.GetRequiredService<AgentSettings>();
49+
var dbSettings = _services.GetRequiredService<MyDatabaseSettings>();
50+
var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
51+
return JsonSerializer.Deserialize<RoutingRecord[]>(File.ReadAllText(filePath));
52+
}
4553
}

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using BotSharp.Abstraction.Agents.Models;
2+
using BotSharp.Core.Templating;
23

34
namespace BotSharp.Core.Agents.Services;
45

@@ -15,6 +16,8 @@ public async Task<Agent> LoadAgent(string id)
1516
}
1617

1718
var agent = await GetAgent(id);
19+
var templateDict = new Dictionary<string, object>();
20+
PopulateState(templateDict);
1821

1922
// After agent is loaded
2023
foreach (var hook in hooks)
@@ -23,7 +26,7 @@ public async Task<Agent> LoadAgent(string id)
2326

2427
if (!string.IsNullOrEmpty(agent.Instruction))
2528
{
26-
hook.OnInstructionLoaded(agent.Instruction, new Dictionary<string, object>());
29+
hook.OnInstructionLoaded(agent.Instruction, templateDict);
2730
}
2831

2932
if (!string.IsNullOrEmpty(agent.Functions))
@@ -41,8 +44,22 @@ public async Task<Agent> LoadAgent(string id)
4144
hook.OnAgentLoaded(agent);
4245
}
4346

47+
// render liquid template
48+
var render = _services.GetRequiredService<TemplateRender>();
49+
render.Render(agent, templateDict);
50+
4451
_logger.LogInformation($"Loaded agent {agent}.");
4552

4653
return agent;
4754
}
55+
56+
private void PopulateState(Dictionary<string, object> dict)
57+
{
58+
var stateService = _services.GetRequiredService<IConversationStateService>();
59+
var state = stateService.Load();
60+
foreach (var t in state)
61+
{
62+
dict[t.Key] = t.Value;
63+
}
64+
}
4865
}

src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using BotSharp.Abstraction.Functions;
22
using BotSharp.Core.Functions;
3+
using BotSharp.Core.Hooks;
4+
using BotSharp.Core.Templating;
35
using Microsoft.AspNetCore.Builder;
46
using Microsoft.Extensions.Configuration;
57

@@ -35,10 +37,18 @@ public static IServiceCollection AddBotSharp(this IServiceCollection services, I
3537

3638
RegisterPlugins(services, config);
3739

40+
// Register template render
41+
services.AddSingleton<TemplateRender>();
42+
43+
// Register router
3844
services.AddScoped<IAgentRouting, AgentRouter>();
3945

46+
// Register function callback
4047
services.AddScoped<IFunctionCallback, RouteToAgentFn>();
4148

49+
// Register Hooks
50+
services.AddScoped<IAgentHook, AgentHook>();
51+
4252
return services;
4353
}
4454

src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ public async Task<bool> Execute(RoleDialogModel message)
5050
private bool HasMissingRequiredField(RoleDialogModel message, out string agentId)
5151
{
5252
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
53-
54-
var routes = GetRoutingTable();
55-
var routingRule = routes.FirstOrDefault(x => x.AgentName.ToLower() == args.AgentName.ToLower());
53+
var router = _services.GetRequiredService<IAgentRouting>();
54+
var records = router.GetRoutingRecords();
55+
var routingRule = records.FirstOrDefault(x => x.Name.ToLower() == args.AgentName.ToLower());
5656

5757
if (routingRule == null)
5858
{
@@ -93,12 +93,4 @@ private bool HasMissingRequiredField(RoleDialogModel message, out string agentId
9393

9494
return hasMissingField;
9595
}
96-
97-
private RoutingTable[] GetRoutingTable()
98-
{
99-
var agentSettings = _services.GetRequiredService<AgentSettings>();
100-
var dbSettings = _services.GetRequiredService<MyDatabaseSettings>();
101-
var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json");
102-
return JsonSerializer.Deserialize<RoutingTable[]>(File.ReadAllText(filePath));
103-
}
10496
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace BotSharp.Core.Hooks;
2+
3+
public class AgentHook : AgentHookBase
4+
{
5+
public AgentHook(IServiceProvider services, AgentSettings settings)
6+
: base(services, settings)
7+
{
8+
}
9+
10+
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
11+
{
12+
var router = _services.GetRequiredService<IAgentRouting>();
13+
dict["routing_records"] = router.GetRoutingRecords();
14+
return true;
15+
}
16+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using BotSharp.Abstraction.Agents.Models;
2+
using BotSharp.Abstraction.Templating;
3+
using Fluid;
4+
using Microsoft.Extensions.Options;
5+
6+
namespace BotSharp.Core.Templating;
7+
8+
public class TemplateRender : ITemplateRender
9+
{
10+
private readonly IServiceProvider _services;
11+
private readonly ILogger _logger;
12+
private static readonly FluidParser _parser = new FluidParser();
13+
private TemplateOptions _options;
14+
15+
public TemplateRender(IServiceProvider services, ILogger<TemplateRender> logger)
16+
{
17+
_services = services;
18+
_logger = logger;
19+
_options = new TemplateOptions();
20+
_options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;
21+
_options.MemberAccessStrategy.Register<RoutingRecord>();
22+
}
23+
24+
public bool Render(Agent agent, Dictionary<string, object> dict)
25+
{
26+
var template = agent.Instruction;
27+
if (_parser.TryParse(template, out var t, out var error))
28+
{
29+
var context = new TemplateContext(dict, _options);
30+
agent.Instruction = t.Render(context);
31+
return true;
32+
}
33+
else
34+
{
35+
36+
return false;
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)