Skip to content

Commit 32c1876

Browse files
authored
Merge pull request #902 from hchen2020/master
routing utility
2 parents 5f7fc18 + 9d57031 commit 32c1876

File tree

38 files changed

+433
-196
lines changed

38 files changed

+433
-196
lines changed

src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ public class StateConst
88
public const string NEXT_ACTION_REASON = "next_action_reason";
99
public const string USER_GOAL_AGENT = "user_goal_agent";
1010
public const string AGENT_REDIRECTION_REASON = "agent_redirection_reason";
11+
// lazy or eager
12+
public const string ROUTING_MODE = "routing_mode";
13+
public const string LAZY_ROUTING_AGENT_ID = "lazy_routing_agent_id";
1114

1215
public const string LANGUAGE = "language";
1316

1417
public const string SUB_CONVERSATION_ID = "sub_conversation_id";
18+
public const string ORIGIN_CONVERSATION_ID = "origin_conversation_id";
1519
}

src/Infrastructure/BotSharp.Abstraction/MLTasks/IRealTimeCompletion.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Task Connect(RealtimeHubConnection conn,
2424
Task Disconnect();
2525

2626
Task<RealtimeSession> CreateSession(Agent agent, List<RoleDialogModel> conversations);
27-
Task UpdateInitialSession(RealtimeHubConnection conn);
27+
Task UpdateSession(RealtimeHubConnection conn);
2828
Task InsertConversationItem(RoleDialogModel message);
2929
Task TriggerModelInference(string? instructions = null);
3030
Task<List<RoleDialogModel>> OnResponsedDone(RealtimeHubConnection conn, string response);

src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ public interface IRoutingContext
1313
bool IsEmpty { get; }
1414
string IntentName { get; set; }
1515
int AgentCount { get; }
16-
void Push(string agentId, string? reason = null);
17-
void Pop(string? reason = null);
18-
void PopTo(string agentId, string reason);
19-
void Replace(string agentId, string? reason = null);
16+
void Push(string agentId, string? reason = null, bool updateLazyRouting = true);
17+
void Pop(string? reason = null, bool updateLazyRouting = true);
18+
void PopTo(string agentId, string reason, bool updateLazyRouting = true);
19+
void Replace(string agentId, string? reason = null, bool updateLazyRouting = true);
2020
void Empty(string? reason = null);
2121

2222

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace BotSharp.Abstraction.Routing.Models;
2+
3+
public class FallbackArgs
4+
{
5+
[JsonPropertyName("fallback_reason")]
6+
public string Reason { get; set; } = null!;
7+
8+
[JsonPropertyName("user_question")]
9+
public string Question { get; set; } = null;
10+
}

src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(TargetFramework)</TargetFramework>
@@ -66,6 +66,8 @@
6666
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.get_remaining_task.liquid" />
6767
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.liquid" />
6868
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json" />
69+
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-routing-fallback_to_router.json" />
70+
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-routing-redirect_to_agent.json" />
6971
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid" />
7072
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
7173
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions.json" />
@@ -82,6 +84,7 @@
8284
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid" />
8385
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid" />
8486
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid" />
87+
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-routing-fallback_to_router.fn.liquid" />
8588
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json" />
8689
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instructions\instruction.liquid" />
8790
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid" />
@@ -146,6 +149,15 @@
146149
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid">
147150
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
148151
</Content>
152+
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-routing-fallback_to_router.json">
153+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
154+
</Content>
155+
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\functions\route_to_agent.json">
156+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
157+
</Content>
158+
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-routing-fallback_to_router.fn.liquid">
159+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
160+
</Content>
149161
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json">
150162
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
151163
</Content>

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using BotSharp.Abstraction.Infrastructures.Enums;
12
using BotSharp.Abstraction.Messaging;
23
using BotSharp.Abstraction.Messaging.Models.RichContent;
34
using BotSharp.Abstraction.Routing.Settings;
@@ -36,7 +37,17 @@ public async Task<bool> SendMessage(string agentId,
3637
// Enqueue receiving agent first in case it stop completion by OnMessageReceived
3738
var routing = _services.GetRequiredService<IRoutingService>();
3839
routing.Context.SetMessageId(_conversationId, message.MessageId);
39-
routing.Context.Push(agent.Id, reason: "request started");
40+
41+
// Check the routing mode
42+
var states = _services.GetRequiredService<IConversationStateService>();
43+
var routingMode = states.GetState(StateConst.ROUTING_MODE, "hard");
44+
routing.Context.Push(agent.Id, reason: "request started", updateLazyRouting: false);
45+
46+
if (routingMode == "lazy")
47+
{
48+
message.CurrentAgentId = states.GetState(StateConst.LAZY_ROUTING_AGENT_ID, message.CurrentAgentId);
49+
routing.Context.Push(message.CurrentAgentId, reason: "lazy routing", updateLazyRouting: false);
50+
}
4051

4152
// Save payload in order to assign the payload before hook is invoked
4253
if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
@@ -77,7 +88,7 @@ public async Task<bool> SendMessage(string agentId,
7788
{
7889
agent = await agentService.LoadAgent(message.CurrentAgentId);
7990
}
80-
91+
8192
if (agent.Type == AgentType.Routing)
8293
{
8394
response = await routing.InstructLoop(message, dialogs);

src/Infrastructure/BotSharp.Core/Realtime/RealtimeHub.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
using BotSharp.Abstraction.Realtime;
22
using System.Net.WebSockets;
3-
using System;
43
using BotSharp.Abstraction.Realtime.Models;
54
using BotSharp.Abstraction.MLTasks;
6-
using BotSharp.Abstraction.Agents.Models;
5+
using BotSharp.Abstraction.Conversations.Enums;
76

87
namespace BotSharp.Core.Realtime;
98

109
public class RealtimeHub : IRealtimeHub
1110
{
1211
private readonly IServiceProvider _services;
1312
private readonly ILogger _logger;
13+
1414
public RealtimeHub(IServiceProvider services, ILogger<RealtimeHub> logger)
1515
{
1616
_services = services;
@@ -81,8 +81,7 @@ await completer.Connect(conn,
8181
onModelReady: async () =>
8282
{
8383
// Control initial session
84-
await completer.UpdateInitialSession(conn);
85-
84+
await completer.UpdateSession(conn);
8685

8786
// Add dialog history
8887
foreach (var item in dialogs)
@@ -118,12 +117,25 @@ await completer.Connect(conn,
118117
foreach (var message in messages)
119118
{
120119
// Invoke function
121-
if (message.MessageType == "function_call")
120+
if (message.MessageType == MessageTypeName.FunctionCall)
122121
{
123122
await routing.InvokeFunction(message.FunctionName, message);
124123
message.Role = AgentRole.Function;
125-
await completer.InsertConversationItem(message);
126-
await completer.TriggerModelInference("Reply based on the function's output.");
124+
if (message.FunctionName == "route_to_agent")
125+
{
126+
var routedAgentId = routing.Context.GetCurrentAgentId();
127+
if (conn.EntryAgentId != routedAgentId)
128+
{
129+
conn.EntryAgentId = routedAgentId;
130+
await completer.UpdateSession(conn);
131+
await completer.TriggerModelInference("Reply based on the function's output.");
132+
}
133+
}
134+
else
135+
{
136+
await completer.InsertConversationItem(message);
137+
await completer.TriggerModelInference("Reply based on the function's output.");
138+
}
127139
}
128140
else
129141
{
@@ -138,7 +150,7 @@ await completer.Connect(conn,
138150

139151
if (!string.IsNullOrEmpty(message.Content))
140152
{
141-
await hook.OnMessageReceived(message);
153+
await hook.OnResponseGenerated(message);
142154
}
143155
}
144156
}

src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,20 @@ namespace BotSharp.Core.Routing.Functions;
55

66
public class FallbackToRouterFn : IFunctionCallback
77
{
8-
public string Name => "fallback_to_router";
8+
public string Name => "util-routing-fallback_to_router";
99
private readonly IServiceProvider _services;
10+
1011
public FallbackToRouterFn(IServiceProvider services)
1112
{
1213
_services = services;
1314
}
1415

1516
public async Task<bool> Execute(RoleDialogModel message)
1617
{
17-
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
18-
var agentService = _services.GetRequiredService<IAgentService>();
19-
var agents = await agentService.GetAgents(new AgentFilter
20-
{
21-
AgentNames = [args.AgentName]
22-
});
23-
var targetAgent = agents.Items.FirstOrDefault();
24-
if (targetAgent == null)
25-
{
26-
message.Content = $"Can't find routing agent {args.AgentName}";
27-
return false;
28-
}
29-
30-
var conv = _services.GetRequiredService<IConversationService>();
31-
var dialogs = conv.GetDialogHistory();
32-
18+
var args = JsonSerializer.Deserialize<FallbackArgs>(message.FunctionArgs);
3319
var routing = _services.GetRequiredService<IRoutingService>();
34-
routing.Context.Replace(targetAgent.Id);
35-
message.CurrentAgentId = targetAgent.Id;
36-
37-
var response = await routing.InstructLoop(message, dialogs);
38-
39-
message.Content = response.Content;
40-
message.StopCompletion = true;
20+
routing.Context.PopTo(routing.Context.EntryAgentId, "pop to entry agent");
21+
message.Content = args.Question;
4122

4223
return true;
4324
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace BotSharp.Core.Routing.Hooks;
2+
3+
public class RoutingUtilityHook : IAgentUtilityHook
4+
{
5+
private static string PREFIX = "util-routing-";
6+
private static string REDIRECT_TO_AGENT = $"{PREFIX}redirect_to_agent";
7+
private static string FALLBACK_TO_ROUTER = $"{PREFIX}fallback_to_router";
8+
9+
public void AddUtilities(List<AgentUtility> utilities)
10+
{
11+
var utility = new AgentUtility
12+
{
13+
Name = "routing.tools",
14+
Functions = [new($"{REDIRECT_TO_AGENT}"), new($"{FALLBACK_TO_ROUTER}")],
15+
Templates = [new($"{REDIRECT_TO_AGENT}.fn"), new($"{FALLBACK_TO_ROUTER}.fn")]
16+
};
17+
18+
utilities.Add(utility);
19+
}
20+
}

src/Infrastructure/BotSharp.Core/Routing/Reasoning/NaiveReasoner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string m
7373
};
7474
var response = await completion.GetChatCompletions(router, dialogs);
7575

76-
inst = response.Content.JsonContent<FunctionCallFromLlm>();
76+
inst = (response.FunctionArgs ?? response.Content).JsonContent<FunctionCallFromLlm>();
7777
break;
7878
}
7979
catch (Exception ex)

src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using BotSharp.Abstraction.Infrastructures.Enums;
12
using BotSharp.Abstraction.Routing.Settings;
23

34
namespace BotSharp.Core.Routing;
@@ -79,7 +80,7 @@ public string GetCurrentAgentId()
7980
/// </summary>
8081
/// <param name="agentId">Id or Name</param>
8182
/// <param name="reason"></param>
82-
public void Push(string agentId, string? reason = null)
83+
public void Push(string agentId, string? reason = null, bool updateLazyRouting = true)
8384
{
8485
// Convert id to name
8586
if (!Guid.TryParse(agentId, out _))
@@ -99,13 +100,15 @@ public void Push(string agentId, string? reason = null)
99100
HookEmitter.Emit<IRoutingHook>(_services, async hook =>
100101
await hook.OnAgentEnqueued(agentId, preAgentId, reason: reason)
101102
).Wait();
103+
104+
UpdateLazyRoutingAgent(updateLazyRouting);
102105
}
103106
}
104107

105108
/// <summary>
106109
/// Pop current agent
107110
/// </summary>
108-
public void Pop(string? reason = null)
111+
public void Pop(string? reason = null, bool updateLazyRouting = true)
109112
{
110113
if (_stack.Count == 0)
111114
{
@@ -149,15 +152,17 @@ await hook.OnAgentDequeued(agentId, currentAgentId, reason: reason)
149152
_stack.Push(agentId);
150153
}
151154
}
155+
156+
UpdateLazyRoutingAgent(updateLazyRouting);
152157
}
153158

154-
public void PopTo(string agentId, string reason)
159+
public void PopTo(string agentId, string reason, bool updateLazyRouting = true)
155160
{
156161
var currentAgentId = GetCurrentAgentId();
157162
while (!string.IsNullOrEmpty(currentAgentId) &&
158163
currentAgentId != agentId)
159164
{
160-
Pop(reason);
165+
Pop(reason, updateLazyRouting: updateLazyRouting);
161166
currentAgentId = GetCurrentAgentId();
162167
}
163168
}
@@ -181,7 +186,7 @@ public bool ContainsAgentId(string agentId)
181186
return _stack.ToArray().Contains(agentId);
182187
}
183188

184-
public void Replace(string agentId, string? reason = null)
189+
public void Replace(string agentId, string? reason = null, bool updateLazyRouting = true)
185190
{
186191
var fromAgent = agentId;
187192
var toAgent = agentId;
@@ -200,6 +205,8 @@ public void Replace(string agentId, string? reason = null)
200205
await hook.OnAgentReplaced(fromAgent, toAgent, reason: reason)
201206
).Wait();
202207
}
208+
209+
UpdateLazyRoutingAgent(updateLazyRouting);
203210
}
204211

205212
public void Empty(string? reason = null)
@@ -275,4 +282,24 @@ public void ResetDialogs()
275282
{
276283
_dialogs = [];
277284
}
285+
286+
private void UpdateLazyRoutingAgent(bool updateLazyRouting)
287+
{
288+
if (!updateLazyRouting)
289+
{
290+
return;
291+
}
292+
293+
// Set next handling agent for lazy routing mode
294+
var states = _services.GetRequiredService<IConversationStateService>();
295+
var routingMode = states.GetState(StateConst.ROUTING_MODE, "hard");
296+
if (routingMode == "lazy")
297+
{
298+
var agentId = GetCurrentAgentId();
299+
if (agentId != BuiltInAgentId.Fallback)
300+
{
301+
states.SetState(StateConst.LAZY_ROUTING_AGENT_ID, agentId);
302+
}
303+
}
304+
}
278305
}

src/Infrastructure/BotSharp.Core/Routing/RoutingPlugin.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,7 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
3737
services.AddScoped<IRoutingReasoner, HFReasoner>();
3838

3939
services.AddScoped<IRoutingReasoner, OneStepForwardReasoner>();
40+
41+
services.AddScoped<IAgentUtilityHook, RoutingUtilityHook>();
4042
}
4143
}

src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public async Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialog
5353
// Handle output routing exception.
5454
if (agent.Type == AgentType.Routing)
5555
{
56-
response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?";
56+
// Forgot about what situation needs to handle in this way
57+
// response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?";
5758
}
5859

5960
message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: response.Content);

0 commit comments

Comments
 (0)