Mak initial audio is not interruptable

This commit is contained in:
Haiping Chen 2025-03-03 15:30:06 -06:00
parent ded711d164
commit b4862837da
3 changed files with 42 additions and 22 deletions

View file

@ -21,28 +21,22 @@ public class RealtimeHub : IRealtimeHub
public async Task Listen(WebSocket userWebSocket, public async Task Listen(WebSocket userWebSocket,
Func<string, RealtimeHubConnection> onUserMessageReceived) Func<string, RealtimeHubConnection> onUserMessageReceived)
{ {
var buffer = new byte[1024 * 4]; var buffer = new byte[1024 * 16];
WebSocketReceiveResult result; WebSocketReceiveResult result;
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
var model = llmProviderService.GetProviderModel("openai", "gpt-4",
realTime: true).Name;
var completer = _services.GetServices<IRealTimeCompletion>().First(x => x.Provider == "openai"); var completer = _services.GetServices<IRealTimeCompletion>().First(x => x.Provider == "openai");
completer.SetModelName(model);
do do
{ {
result = await userWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); result = await userWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
string receivedText = Encoding.UTF8.GetString(buffer, 0, result.Count); string receivedText = Encoding.UTF8.GetString(buffer, 0, result.Count);
_logger.LogDebug($"Received from user: {receivedText}");
if (string.IsNullOrEmpty(receivedText)) if (string.IsNullOrEmpty(receivedText))
{ {
continue; continue;
} }
var conn = onUserMessageReceived(receivedText); var conn = onUserMessageReceived(receivedText);
conn.Model = model;
if (conn.Event == "user_connected") if (conn.Event == "user_connected")
{ {
@ -74,6 +68,17 @@ public class RealtimeHub : IRealtimeHub
var agent = await agentService.LoadAgent(conversation.AgentId); var agent = await agentService.LoadAgent(conversation.AgentId);
conn.CurrentAgentId = agent.Id; conn.CurrentAgentId = agent.Id;
// Set model
var model = agent.LlmConfig.Model;
if (!model.Contains("-realtime-"))
{
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
model = llmProviderService.GetProviderModel("openai", "gpt-4", realTime: true).Name;
}
completer.SetModelName(model);
conn.Model = model;
var routing = _services.GetRequiredService<IRoutingService>(); var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.Push(agent.Id); routing.Context.Push(agent.Id);
@ -98,7 +103,7 @@ public class RealtimeHub : IRealtimeHub
if (dialogs.LastOrDefault()?.Role == AgentRole.Assistant) if (dialogs.LastOrDefault()?.Role == AgentRole.Assistant)
{ {
await completer.TriggerModelInference($"Rephase your last response:\r\n{dialogs.LastOrDefault()?.Content}"); // await completer.TriggerModelInference($"Rephase your last response:\r\n{dialogs.LastOrDefault()?.Content}");
} }
else else
{ {

View file

@ -128,9 +128,10 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
Action<RoleDialogModel> onInputAudioTranscriptionCompleted, Action<RoleDialogModel> onInputAudioTranscriptionCompleted,
Action onUserInterrupted) Action onUserInterrupted)
{ {
var buffer = new byte[1024 * 1024 * 1]; var buffer = new byte[1024 * 256];
WebSocketReceiveResult result; WebSocketReceiveResult result;
string lastAssistantItem = ""; string? lastAssistantItem = null;
do do
{ {
result = await _webSocket.ReceiveAsync( result = await _webSocket.ReceiveAsync(
@ -166,15 +167,17 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
{ {
_logger.LogInformation($"{response.Type}: {receivedText}"); _logger.LogInformation($"{response.Type}: {receivedText}");
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(receivedText); var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(receivedText);
await Task.Delay(1000);
onAudioTranscriptDone(data.Transcript); onAudioTranscriptDone(data.Transcript);
} }
else if (response.Type == "response.audio.delta") else if (response.Type == "response.audio.delta")
{ {
var audio = JsonSerializer.Deserialize<ResponseAudioDelta>(receivedText); var audio = JsonSerializer.Deserialize<ResponseAudioDelta>(receivedText);
lastAssistantItem = audio?.ItemId ?? ""; lastAssistantItem = audio?.ItemId;
if (audio != null && audio.Delta != null) if (audio != null && audio.Delta != null)
{ {
_logger.LogDebug($"{response.Type}: {receivedText}");
onModelAudioDeltaReceived(audio.Delta); onModelAudioDeltaReceived(audio.Delta);
} }
} }
@ -204,16 +207,19 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
{ {
// var elapsedTime = latestMediaTimestamp - responseStartTimestampTwilio; // var elapsedTime = latestMediaTimestamp - responseStartTimestampTwilio;
// handle use interuption // handle use interuption
var truncateEvent = new if (!string.IsNullOrEmpty(lastAssistantItem))
{ {
type = "conversation.item.truncate", var truncateEvent = new
item_id = lastAssistantItem, {
content_index = 0, type = "conversation.item.truncate",
audio_end_ms = 100 item_id = lastAssistantItem,
}; content_index = 0,
audio_end_ms = 300
};
await SendEventToModel(truncateEvent); await SendEventToModel(truncateEvent);
onUserInterrupted(); onUserInterrupted();
}
} }
} while (!result.CloseStatus.HasValue); } while (!result.CloseStatus.HasValue);
@ -612,7 +618,9 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
outputs.Add(new RoleDialogModel(output.Role, content.Transcript) outputs.Add(new RoleDialogModel(output.Role, content.Transcript)
{ {
CurrentAgentId = conn.CurrentAgentId CurrentAgentId = conn.CurrentAgentId,
MessageId = output.Id,
MessageType = MessageTypeName.Plain
}); });
} }
} }

View file

@ -189,7 +189,14 @@ public class TwilioService
{ {
foreach (var speechPath in conversationalVoiceResponse.SpeechPaths) foreach (var speechPath in conversationalVoiceResponse.SpeechPaths)
{ {
response.Play(new Uri($"{_settings.CallbackHost}/twilio/voice/speeches/{conversationId}/{speechPath}")); if (speechPath.StartsWith("twilio/"))
{
response.Play(new Uri($"{_settings.CallbackHost}/{speechPath}"));
}
else
{
response.Play(new Uri($"{_settings.CallbackHost}/twilio/voice/speeches/{conversationId}/{speechPath}"));
}
} }
} }
var connect = new Connect(); var connect = new Connect();