Skip to content

Commit dedf30a

Browse files
authored
Merge pull request #1019 from hchen2020/master
Optimize realtime route_to_agent
2 parents 69a5813 + d2eb232 commit dedf30a

File tree

11 files changed

+62
-45
lines changed

11 files changed

+62
-45
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public interface IAgentService
1212
Task<Agent> CreateAgent(Agent agent);
1313
Task<string> RefreshAgents();
1414
Task<PagedItems<Agent>> GetAgents(AgentFilter filter);
15-
Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null);
15+
Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null, bool byName = false);
1616

1717
/// <summary>
1818
/// Load agent configurations and trigger hooks

src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ public class RoleDialogModel : ITrackableMessage
6666
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
6767
public string? FunctionArgs { get; set; }
6868

69+
/// <summary>
70+
/// Set this flag is in OnFunctionExecuting, if true, it won't be executed by InvokeFunction.
71+
/// </summary>
72+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
73+
public bool Handled { get; set; } = false;
74+
6975
/// <summary>
7076
/// Function execution structured data, this data won't pass to LLM.
7177
/// It's ideal to render in rich content in UI.

src/Infrastructure/BotSharp.Core.Realtime/Hooks/RealtimeConversationHook.cs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using BotSharp.Abstraction.Utilities;
2-
using BotSharp.Core.Infrastructures;
32

43
namespace BotSharp.Core.Realtime.Hooks;
54

@@ -40,33 +39,32 @@ public async Task OnFunctionExecuted(RoleDialogModel message)
4039

4140
if (message.FunctionName == "route_to_agent")
4241
{
43-
var inst = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs ?? "{}") ?? new();
44-
message.Content = $"I'm your AI assistant '{inst.AgentName}' to help with: '{inst.NextActionReason}'";
4542
hub.HubConn.CurrentAgentId = routing.Context.GetCurrentAgentId();
4643

47-
var instruction = await hub.Completer.UpdateSession(hub.HubConn);
48-
await hub.Completer.InsertConversationItem(message);
49-
await hub.Completer.TriggerModelInference($"{instruction}\r\n\r\nAssist user task: {inst.NextActionReason}");
44+
await hub.Completer.UpdateSession(hub.HubConn);
45+
await hub.Completer.TriggerModelInference();
5046
}
5147
else if (message.FunctionName == "util-routing-fallback_to_router")
5248
{
53-
var inst = JsonSerializer.Deserialize<FallbackArgs>(message.FunctionArgs ?? "{}") ?? new();
54-
message.Content = $"Returned to Router due to {inst.Reason}";
5549
hub.HubConn.CurrentAgentId = routing.Context.GetCurrentAgentId();
5650

57-
var instruction = await hub.Completer.UpdateSession(hub.HubConn);
58-
await hub.Completer.InsertConversationItem(message);
59-
await hub.Completer.TriggerModelInference(instruction);
51+
await hub.Completer.UpdateSession(hub.HubConn);
52+
await hub.Completer.TriggerModelInference();
6053
}
6154
else
6255
{
63-
// Clear cache to force to rebuild the agent instruction
64-
Utilities.ClearCache();
65-
6656
// Update session for changed states
6757
var instruction = await hub.Completer.UpdateSession(hub.HubConn);
6858
await hub.Completer.InsertConversationItem(message);
69-
await hub.Completer.TriggerModelInference(instruction);
59+
60+
if (message.StopCompletion)
61+
{
62+
await hub.Completer.TriggerModelInference($"Say to user: \"{message.Content}\"");
63+
}
64+
else
65+
{
66+
await hub.Completer.TriggerModelInference(instruction);
67+
}
7068
}
7169
}
7270
}

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,18 @@ public async Task<PagedItems<Agent>> GetAgents(AgentFilter filter)
2626
}
2727

2828
[SharpCache(10)]
29-
public async Task<List<IdName>> GetAgentOptions(List<string>? agentIds)
29+
public async Task<List<IdName>> GetAgentOptions(List<string>? agentIdsOrNames, bool byName = false)
3030
{
31-
var agents = _db.GetAgents(new AgentFilter
32-
{
33-
AgentIds = !agentIds.IsNullOrEmpty() ? agentIds : null
34-
});
31+
var agents = byName ?
32+
_db.GetAgents(new AgentFilter
33+
{
34+
AgentNames = !agentIdsOrNames.IsNullOrEmpty() ? agentIdsOrNames : null
35+
}) :
36+
_db.GetAgents(new AgentFilter
37+
{
38+
AgentIds = !agentIdsOrNames.IsNullOrEmpty() ? agentIdsOrNames : null
39+
});
40+
3541
return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? [];
3642
}
3743

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,12 @@ public void Push(string agentId, string? reason = null, bool updateLazyRouting =
8686
if (!Guid.TryParse(agentId, out _))
8787
{
8888
var agentService = _services.GetRequiredService<IAgentService>();
89-
agentId = agentService.GetAgents(new AgentFilter
89+
var agents = agentService.GetAgentOptions([agentId], byName: true).Result;
90+
91+
if (agents.Count > 0)
9092
{
91-
AgentNames = [agentId]
92-
}).Result.Items.First().Id;
93+
agentId = agents.First().Id;
94+
}
9395
}
9496

9597
if (_stack.Count == 0 || _stack.Peek() != agentId)

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,23 @@ public async Task<bool> InvokeFunction(string name, RoleDialogModel message)
4949
await progressService.OnFunctionExecuting(clonedMessage);
5050
}
5151

52+
var agentService = _services.GetRequiredService<IAgentService>();
53+
var agent = await agentService.GetAgent(clonedMessage.CurrentAgentId);
5254
foreach (var hook in hooks)
5355
{
56+
hook.SetAgent(agent);
5457
await hook.OnFunctionExecuting(clonedMessage);
5558
}
5659

5760
bool result = false;
5861

5962
try
6063
{
61-
if (!isFillDummyContent)
64+
if (clonedMessage.Handled)
65+
{
66+
clonedMessage.Content = clonedMessage.Content;
67+
}
68+
else if (!isFillDummyContent)
6269
{
6370
result = await function.Execute(clonedMessage);
6471
}

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ private async Task ReceiveMessage(RealtimeHubConnection conn,
162162
}
163163
else if (response.Type == "response.audio_transcript.delta")
164164
{
165-
165+
_logger.LogDebug($"{response.Type}: {receivedText}");
166166
}
167167
else if (response.Type == "response.audio_transcript.done")
168168
{
@@ -211,9 +211,14 @@ private async Task ReceiveMessage(RealtimeHubConnection conn,
211211
}
212212
else if (response.Type == "input_audio_buffer.speech_started")
213213
{
214+
_logger.LogInformation($"{response.Type}: {receivedText}");
214215
// Handle user interuption
215216
onInterruptionDetected();
216217
}
218+
else if (response.Type == "input_audio_buffer.speech_stopped")
219+
{
220+
_logger.LogInformation($"{response.Type}: {receivedText}");
221+
}
217222
}
218223
}
219224

src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ public VoiceResponse HangUp(ConversationalVoiceResponse voiceResponse)
141141
}
142142
else
143143
{
144-
response.Pause(5);
145144
response.Say("Goodbye.");
146145
}
147146

src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ private async Task HandleWebSocket(IServiceProvider services, string conversatio
9494
}
9595
else if (eventType == "user_dtmf_receiving")
9696
{
97+
// Send a Stop command to Twilio
98+
string clearEvent = JsonSerializer.Serialize(new
99+
{
100+
@event = "clear",
101+
streamSid = conn.StreamId
102+
});
103+
await SendEventToUser(webSocket, clearEvent);
97104
}
98105
else if (eventType == "user_dtmf_received")
99106
{
@@ -183,14 +190,6 @@ await hub.ConnectToModel(async data =>
183190
streamSid = response.StreamSid
184191
});
185192

186-
/*if (response.Event == "dtmf")
187-
{
188-
// Send a Stop command to Twilio
189-
string stopPlaybackCommand = "{ \"action\": \"stop_playback\" }";
190-
var stopBytes = Encoding.UTF8.GetBytes(stopPlaybackCommand);
191-
webSocket.SendAsync(new ArraySegment<byte>(stopBytes), WebSocketMessageType.Text, true, CancellationToken.None);
192-
}*/
193-
194193
return (eventType, data);
195194
}
196195

@@ -225,7 +224,7 @@ private async Task HandleUserDtmfReceived(IServiceProvider _services, RealtimeHu
225224
var routing = _services.GetRequiredService<IRoutingService>();
226225
var hookProvider = _services.GetRequiredService<ConversationHookProvider>();
227226
var agentService = _services.GetRequiredService<IAgentService>();
228-
var agent = await agentService.LoadAgent(conn.CurrentAgentId);
227+
var agent = await agentService.GetAgent(conn.CurrentAgentId);
229228
var dialogs = routing.Context.GetDialogs();
230229
var convService = _services.GetRequiredService<IConversationService>();
231230
var conversation = await convService.GetConversation(conn.ConversationId);
@@ -248,7 +247,6 @@ private async Task HandleUserDtmfReceived(IServiceProvider _services, RealtimeHu
248247
}
249248

250249
await completer.InsertConversationItem(message);
251-
var instruction = await completer.UpdateSession(conn);
252-
await completer.TriggerModelInference($"{instruction}\r\n\r\nReply based on the user input: {message.Content}");
250+
await completer.TriggerModelInference($"Response based on the user input: {message.Content}");
253251
}
254252
}

src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-hangup_phone_call.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,8 @@
88
"reason": {
99
"type": "string",
1010
"description": "The reason why user wants to end the phone call."
11-
},
12-
"response_content": {
13-
"type": "string",
14-
"description": "A response statement said to the user to politely and gratefully ending a conversation before hanging up."
1511
}
1612
},
17-
"required": [ "reason", "response_content" ]
13+
"required": [ "reason" ]
1814
}
1915
}

0 commit comments

Comments
 (0)