Merge pull request #1019 from hchen2020/master
Optimize realtime route_to_agent
This commit is contained in:
commit
dedf30a870
|
|
@ -12,7 +12,7 @@ public interface IAgentService
|
|||
Task<Agent> CreateAgent(Agent agent);
|
||||
Task<string> RefreshAgents();
|
||||
Task<PagedItems<Agent>> GetAgents(AgentFilter filter);
|
||||
Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null);
|
||||
Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null, bool byName = false);
|
||||
|
||||
/// <summary>
|
||||
/// Load agent configurations and trigger hooks
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ public class RoleDialogModel : ITrackableMessage
|
|||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? FunctionArgs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set this flag is in OnFunctionExecuting, if true, it won't be executed by InvokeFunction.
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
|
||||
public bool Handled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Function execution structured data, this data won't pass to LLM.
|
||||
/// It's ideal to render in rich content in UI.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using BotSharp.Abstraction.Utilities;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
||||
namespace BotSharp.Core.Realtime.Hooks;
|
||||
|
||||
|
|
@ -40,33 +39,32 @@ public class RealtimeConversationHook : ConversationHookBase, IConversationHook
|
|||
|
||||
if (message.FunctionName == "route_to_agent")
|
||||
{
|
||||
var inst = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs ?? "{}") ?? new();
|
||||
message.Content = $"I'm your AI assistant '{inst.AgentName}' to help with: '{inst.NextActionReason}'";
|
||||
hub.HubConn.CurrentAgentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
var instruction = await hub.Completer.UpdateSession(hub.HubConn);
|
||||
await hub.Completer.InsertConversationItem(message);
|
||||
await hub.Completer.TriggerModelInference($"{instruction}\r\n\r\nAssist user task: {inst.NextActionReason}");
|
||||
await hub.Completer.UpdateSession(hub.HubConn);
|
||||
await hub.Completer.TriggerModelInference();
|
||||
}
|
||||
else if (message.FunctionName == "util-routing-fallback_to_router")
|
||||
{
|
||||
var inst = JsonSerializer.Deserialize<FallbackArgs>(message.FunctionArgs ?? "{}") ?? new();
|
||||
message.Content = $"Returned to Router due to {inst.Reason}";
|
||||
hub.HubConn.CurrentAgentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
var instruction = await hub.Completer.UpdateSession(hub.HubConn);
|
||||
await hub.Completer.InsertConversationItem(message);
|
||||
await hub.Completer.TriggerModelInference(instruction);
|
||||
await hub.Completer.UpdateSession(hub.HubConn);
|
||||
await hub.Completer.TriggerModelInference();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear cache to force to rebuild the agent instruction
|
||||
Utilities.ClearCache();
|
||||
|
||||
// Update session for changed states
|
||||
var instruction = await hub.Completer.UpdateSession(hub.HubConn);
|
||||
await hub.Completer.InsertConversationItem(message);
|
||||
await hub.Completer.TriggerModelInference(instruction);
|
||||
|
||||
if (message.StopCompletion)
|
||||
{
|
||||
await hub.Completer.TriggerModelInference($"Say to user: \"{message.Content}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
await hub.Completer.TriggerModelInference(instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,18 @@ public partial class AgentService
|
|||
}
|
||||
|
||||
[SharpCache(10)]
|
||||
public async Task<List<IdName>> GetAgentOptions(List<string>? agentIds)
|
||||
public async Task<List<IdName>> GetAgentOptions(List<string>? agentIdsOrNames, bool byName = false)
|
||||
{
|
||||
var agents = _db.GetAgents(new AgentFilter
|
||||
{
|
||||
AgentIds = !agentIds.IsNullOrEmpty() ? agentIds : null
|
||||
});
|
||||
var agents = byName ?
|
||||
_db.GetAgents(new AgentFilter
|
||||
{
|
||||
AgentNames = !agentIdsOrNames.IsNullOrEmpty() ? agentIdsOrNames : null
|
||||
}) :
|
||||
_db.GetAgents(new AgentFilter
|
||||
{
|
||||
AgentIds = !agentIdsOrNames.IsNullOrEmpty() ? agentIdsOrNames : null
|
||||
});
|
||||
|
||||
return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? [];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,10 +86,12 @@ public class RoutingContext : IRoutingContext
|
|||
if (!Guid.TryParse(agentId, out _))
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
agentId = agentService.GetAgents(new AgentFilter
|
||||
var agents = agentService.GetAgentOptions([agentId], byName: true).Result;
|
||||
|
||||
if (agents.Count > 0)
|
||||
{
|
||||
AgentNames = [agentId]
|
||||
}).Result.Items.First().Id;
|
||||
agentId = agents.First().Id;
|
||||
}
|
||||
}
|
||||
|
||||
if (_stack.Count == 0 || _stack.Peek() != agentId)
|
||||
|
|
|
|||
|
|
@ -49,8 +49,11 @@ public partial class RoutingService
|
|||
await progressService.OnFunctionExecuting(clonedMessage);
|
||||
}
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.GetAgent(clonedMessage.CurrentAgentId);
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.SetAgent(agent);
|
||||
await hook.OnFunctionExecuting(clonedMessage);
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +61,11 @@ public partial class RoutingService
|
|||
|
||||
try
|
||||
{
|
||||
if (!isFillDummyContent)
|
||||
if (clonedMessage.Handled)
|
||||
{
|
||||
clonedMessage.Content = clonedMessage.Content;
|
||||
}
|
||||
else if (!isFillDummyContent)
|
||||
{
|
||||
result = await function.Execute(clonedMessage);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
}
|
||||
else if (response.Type == "response.audio_transcript.delta")
|
||||
{
|
||||
|
||||
_logger.LogDebug($"{response.Type}: {receivedText}");
|
||||
}
|
||||
else if (response.Type == "response.audio_transcript.done")
|
||||
{
|
||||
|
|
@ -211,9 +211,14 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
}
|
||||
else if (response.Type == "input_audio_buffer.speech_started")
|
||||
{
|
||||
_logger.LogInformation($"{response.Type}: {receivedText}");
|
||||
// Handle user interuption
|
||||
onInterruptionDetected();
|
||||
}
|
||||
else if (response.Type == "input_audio_buffer.speech_stopped")
|
||||
{
|
||||
_logger.LogInformation($"{response.Type}: {receivedText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ public class TwilioService
|
|||
}
|
||||
else
|
||||
{
|
||||
response.Pause(5);
|
||||
response.Say("Goodbye.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,13 @@ public class TwilioStreamMiddleware
|
|||
}
|
||||
else if (eventType == "user_dtmf_receiving")
|
||||
{
|
||||
// Send a Stop command to Twilio
|
||||
string clearEvent = JsonSerializer.Serialize(new
|
||||
{
|
||||
@event = "clear",
|
||||
streamSid = conn.StreamId
|
||||
});
|
||||
await SendEventToUser(webSocket, clearEvent);
|
||||
}
|
||||
else if (eventType == "user_dtmf_received")
|
||||
{
|
||||
|
|
@ -183,14 +190,6 @@ public class TwilioStreamMiddleware
|
|||
streamSid = response.StreamSid
|
||||
});
|
||||
|
||||
/*if (response.Event == "dtmf")
|
||||
{
|
||||
// Send a Stop command to Twilio
|
||||
string stopPlaybackCommand = "{ \"action\": \"stop_playback\" }";
|
||||
var stopBytes = Encoding.UTF8.GetBytes(stopPlaybackCommand);
|
||||
webSocket.SendAsync(new ArraySegment<byte>(stopBytes), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}*/
|
||||
|
||||
return (eventType, data);
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +224,7 @@ public class TwilioStreamMiddleware
|
|||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var hookProvider = _services.GetRequiredService<ConversationHookProvider>();
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(conn.CurrentAgentId);
|
||||
var agent = await agentService.GetAgent(conn.CurrentAgentId);
|
||||
var dialogs = routing.Context.GetDialogs();
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var conversation = await convService.GetConversation(conn.ConversationId);
|
||||
|
|
@ -248,7 +247,6 @@ public class TwilioStreamMiddleware
|
|||
}
|
||||
|
||||
await completer.InsertConversationItem(message);
|
||||
var instruction = await completer.UpdateSession(conn);
|
||||
await completer.TriggerModelInference($"{instruction}\r\n\r\nReply based on the user input: {message.Content}");
|
||||
await completer.TriggerModelInference($"Response based on the user input: {message.Content}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,8 @@
|
|||
"reason": {
|
||||
"type": "string",
|
||||
"description": "The reason why user wants to end the phone call."
|
||||
},
|
||||
"response_content": {
|
||||
"type": "string",
|
||||
"description": "A response statement said to the user to politely and gratefully ending a conversation before hanging up."
|
||||
}
|
||||
},
|
||||
"required": [ "reason", "response_content" ]
|
||||
"required": [ "reason" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
|
|
@ -26,7 +26,7 @@ namespace BotSharp.Plugin.Google.Core
|
|||
return Task.FromResult(new PagedItems<Agent>());
|
||||
}
|
||||
|
||||
public Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null)
|
||||
public Task<List<IdName>> GetAgentOptions(List<string>? agentIds = null, bool byName = false)
|
||||
{
|
||||
return Task.FromResult(new List<IdName> { new IdName(id: "1", name: "Fake Agent") });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue