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

169 lines
6.4 KiB
C#
Raw Normal View History

2025-04-03 19:32:04 +00:00
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Options;
2025-04-02 15:35:03 +00:00
using BotSharp.Core.Infrastructures;
2025-03-06 18:01:43 +00:00
namespace BotSharp.Core.Realtime.Services;
2025-02-07 22:40:57 +00:00
public class RealtimeHub : IRealtimeHub
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
2025-02-27 04:40:50 +00:00
2025-03-06 21:10:17 +00:00
private RealtimeHubConnection _conn;
public RealtimeHubConnection HubConn => _conn;
private IRealTimeCompletion _completer;
public IRealTimeCompletion Completer => _completer;
2025-02-07 22:40:57 +00:00
public RealtimeHub(IServiceProvider services, ILogger<RealtimeHub> logger)
{
_services = services;
_logger = logger;
}
2025-04-07 04:15:27 +00:00
public async Task ConnectToModel(Func<string, Task> responseToUser)
2025-02-07 22:40:57 +00:00
{
2025-04-07 04:15:27 +00:00
var hookProvider = _services.GetService<ConversationHookProvider>();
2025-02-09 23:31:46 +00:00
var convService = _services.GetRequiredService<IConversationService>();
2025-03-06 21:10:17 +00:00
convService.SetConversationId(_conn.ConversationId, []);
var conversation = await convService.GetConversation(_conn.ConversationId);
2025-02-11 23:27:07 +00:00
2025-02-09 23:31:46 +00:00
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(conversation.AgentId);
2025-03-06 21:10:17 +00:00
_conn.CurrentAgentId = agent.Id;
2025-02-11 23:27:07 +00:00
2025-02-09 23:31:46 +00:00
var routing = _services.GetRequiredService<IRoutingService>();
2025-02-28 18:44:59 +00:00
routing.Context.Push(agent.Id);
2025-03-12 19:03:38 +00:00
var storage = _services.GetRequiredService<IConversationStorage>();
2025-02-09 23:31:46 +00:00
var dialogs = convService.GetDialogHistory();
2025-04-03 17:13:42 +00:00
if (dialogs.Count == 0)
2025-02-28 03:19:20 +00:00
{
dialogs.Add(new RoleDialogModel(AgentRole.User, "Hi"));
2025-03-12 19:03:38 +00:00
storage.Append(_conn.ConversationId, dialogs.First());
2025-04-03 17:13:42 +00:00
}
2025-03-12 19:03:38 +00:00
2025-02-09 23:31:46 +00:00
routing.Context.SetDialogs(dialogs);
2025-04-03 17:13:42 +00:00
routing.Context.SetMessageId(_conn.ConversationId, dialogs.Last().MessageId);
2025-02-09 23:31:46 +00:00
var states = _services.GetRequiredService<IConversationStateService>();
2025-03-06 21:10:17 +00:00
await _completer.Connect(_conn,
2025-02-09 23:31:46 +00:00
onModelReady: async () =>
{
2025-03-15 02:06:40 +00:00
// Not TriggerModelInference, waiting for user utter.
2025-03-15 14:29:46 +00:00
var instruction = await _completer.UpdateSession(_conn);
2025-04-11 21:29:44 +00:00
var data = _conn.OnModelReady();
await responseToUser(data);
2025-04-02 15:35:03 +00:00
await HookEmitter.Emit<IRealtimeHook>(_services, async hook => await hook.OnModeReady(agent, _completer));
2025-04-11 21:29:44 +00:00
2025-02-09 23:31:46 +00:00
},
2025-03-06 09:03:30 +00:00
onModelAudioDeltaReceived: async (audioDeltaData, itemId) =>
2025-02-09 23:31:46 +00:00
{
2025-03-06 21:10:17 +00:00
var data = _conn.OnModelMessageReceived(audioDeltaData);
2025-04-07 04:15:27 +00:00
await responseToUser(data);
2025-03-05 18:29:28 +00:00
2025-03-06 09:03:30 +00:00
// If this is the first delta of a new response, set the start timestamp
2025-03-06 21:10:17 +00:00
if (!_conn.ResponseStartTimestamp.HasValue)
2025-03-05 18:29:28 +00:00
{
2025-03-06 21:10:17 +00:00
_conn.ResponseStartTimestamp = _conn.LatestMediaTimestamp;
_logger.LogDebug($"Setting start timestamp for new response: {_conn.ResponseStartTimestamp}ms");
2025-03-06 09:03:30 +00:00
}
// Record last assistant item ID for interruption handling
if (!string.IsNullOrEmpty(itemId))
{
2025-03-06 21:10:17 +00:00
_conn.LastAssistantItemId = itemId;
2025-03-05 18:29:28 +00:00
}
2025-03-06 09:03:30 +00:00
// Send mark messages to Media Streams so we know if and when AI response playback is finished
2025-04-07 04:15:27 +00:00
// await SendMark(userWebSocket, _conn);
2025-02-09 23:31:46 +00:00
},
onModelAudioResponseDone: async () =>
{
2025-03-06 21:10:17 +00:00
var data = _conn.OnModelAudioResponseDone();
2025-04-07 04:15:27 +00:00
await responseToUser(data);
2025-02-09 23:31:46 +00:00
},
onAudioTranscriptDone: async transcript =>
{
},
2025-02-11 23:27:07 +00:00
onModelResponseDone: async messages =>
2025-02-09 23:31:46 +00:00
{
foreach (var message in messages)
{
// Invoke function
2025-03-06 21:10:17 +00:00
if (message.MessageType == MessageTypeName.FunctionCall &&
!string.IsNullOrEmpty(message.FunctionName))
2025-02-09 23:31:46 +00:00
{
2025-04-03 19:32:04 +00:00
if (message.FunctionName == "route_to_agent")
{
var instruction = JsonSerializer.Deserialize<FunctionCallFromLlm>(message.FunctionArgs, BotSharpOptions.defaultJsonOptions);
await HookEmitter.Emit<IRoutingHook>(_services, async hook => await hook.OnRoutingInstructionReceived(instruction, message));
}
2025-04-07 14:56:15 +00:00
var delay = Task.Delay(1000);
routing.InvokeFunction(message.FunctionName, message);
await delay;
2025-02-09 23:31:46 +00:00
}
2025-03-05 18:29:28 +00:00
else
{
2025-03-05 18:29:28 +00:00
// append output audio transcript to conversation
dialogs.Add(message);
2025-03-12 19:03:38 +00:00
storage.Append(_conn.ConversationId, message);
2025-03-05 18:29:28 +00:00
2025-04-07 04:15:27 +00:00
foreach (var hook in hookProvider?.HooksOrderByPriority ?? [])
2025-03-05 18:29:28 +00:00
{
hook.SetAgent(agent)
.SetConversation(conversation);
2025-02-11 23:27:07 +00:00
2025-03-05 18:29:28 +00:00
await hook.OnResponseGenerated(message);
}
2025-02-11 23:27:07 +00:00
}
2025-02-09 23:31:46 +00:00
}
},
2025-02-11 23:27:07 +00:00
onConversationItemCreated: async response =>
{
},
onInputAudioTranscriptionCompleted: async message =>
{
2025-03-01 02:42:03 +00:00
// append input audio transcript to conversation
2025-02-11 23:27:07 +00:00
dialogs.Add(message);
2025-03-12 19:03:38 +00:00
storage.Append(_conn.ConversationId, message);
2025-03-17 22:09:02 +00:00
routing.Context.SetMessageId(_conn.ConversationId, message.MessageId);
2025-03-01 02:42:03 +00:00
2025-04-07 04:15:27 +00:00
foreach (var hook in hookProvider?.HooksOrderByPriority ?? [])
2025-03-01 02:42:03 +00:00
{
hook.SetAgent(agent)
.SetConversation(conversation);
await hook.OnMessageReceived(message);
}
2025-02-11 23:27:07 +00:00
},
2025-02-09 23:31:46 +00:00
onUserInterrupted: async () =>
{
2025-03-05 18:29:28 +00:00
// Reset states
2025-03-06 21:10:17 +00:00
_conn.ResetResponseState();
2025-03-05 18:29:28 +00:00
2025-03-06 21:10:17 +00:00
var data = _conn.OnModelUserInterrupted();
2025-04-07 04:15:27 +00:00
await responseToUser(data);
2025-02-09 23:31:46 +00:00
});
2025-02-07 22:40:57 +00:00
}
2025-03-06 21:10:17 +00:00
public RealtimeHubConnection SetHubConnection(string conversationId)
{
_conn = new RealtimeHubConnection
{
ConversationId = conversationId
};
return _conn;
}
public IRealTimeCompletion SetCompleter(string provider)
{
_completer = _services.GetServices<IRealTimeCompletion>().First(x => x.Provider == provider);
return _completer;
}
2025-02-07 22:40:57 +00:00
}