SeemGen/Services/OpenAIRealtimeService.cs

275 lines
12 KiB
C#

using System.Net.WebSockets;
using System.Text.Json;
using System.Text;
namespace BLAIzor.Services
{
public class OpenAiRealtimeService
{
private readonly IConfiguration _configuration;
private readonly string _apiKey;
private readonly Uri _openAiUri = new Uri("wss://api.openai.com/v1/realtime?model=gpt-4o-mini-realtime-preview");
private Action<string, string> _callback;
private string streamedHtmlContent = string.Empty;
public OpenAiRealtimeService(IConfiguration configuration)
{
_configuration = configuration;
_apiKey = GetApiKey();
}
public void RegisterCallback(Action<string, string> callback)
{
_callback = callback;
}
private string GetApiKey()
{
return _configuration?.GetSection("OpenAI")?.GetValue<string>("ApiKey") ?? string.Empty;
}
public async Task<string> GetFullChatGPTResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null)
{
streamedHtmlContent = string.Empty;
using (ClientWebSocket webSocket = new ClientWebSocket())
{
webSocket.Options.SetRequestHeader("Authorization", $"Bearer {_apiKey}");
webSocket.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1");
try
{
await webSocket.ConnectAsync(_openAiUri, CancellationToken.None);
Console.Write("\n✅ Connected to OpenAI WebSocket..." + "\n");
// Step 1: Wait for session.created event (return immediately)
string sessionResponse = await ReceiveSingleMessage(webSocket);
Console.Write("\n🟢 Session Created: " + sessionResponse + "\n");
// Step 2: Send user message (conversation.item.create)
string messageId = Guid.NewGuid().ToString("N").Substring(0, 32);
var userMessagePayload = new
{
type = "conversation.item.create",
previous_item_id = (string?)null,
item = new
{
id = messageId,
type = "message",
role = "user",
content = new[] { new { type = "input_text", text = userMessage } }
}
};
string userMessageJson = JsonSerializer.Serialize(userMessagePayload);
Console.Write("\n📤 Sending user message: " + userMessageJson + "\n");
await webSocket.SendAsync(Encoding.UTF8.GetBytes(userMessageJson), WebSocketMessageType.Text, true, CancellationToken.None);
// Step 3: Wait for message confirmation (return immediately)
string messageResponse = await ReceiveSingleMessage(webSocket);
Console.Write("\n🟢 Message Created: " + messageResponse + "\n");
// Step 4: Send response.create to trigger AI response
var responseRequestPayload = new
{
type = "response.create",
response = new
{
modalities = new[] { "text" },
instructions = systemMessage,
temperature = 0.6,
max_output_tokens = 4096
}
};
string responseRequestJson = JsonSerializer.Serialize(responseRequestPayload);
Console.Write("\n📤 Sending response.create: " + responseRequestJson + "\n");
await webSocket.SendAsync(Encoding.UTF8.GetBytes(responseRequestJson), WebSocketMessageType.Text, true, CancellationToken.None);
// Step 5: Keep listening for AI's full response (STREAMING)
string aiResponse = await ReceiveStreamingMessages(sessionId, webSocket, false);
Console.Write("\n🔵 AI Final Response: " + aiResponse + "\n");
return aiResponse;
}
catch (Exception ex)
{
Console.Write($"❌ Error: {ex.Message}" + "\n");
return "Error communicating with OpenAI Realtime API";
}
finally
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection", CancellationToken.None);
}
}
}
public async Task GetChatGPTResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null)
{
streamedHtmlContent = string.Empty;
using (ClientWebSocket webSocket = new ClientWebSocket())
{
webSocket.Options.SetRequestHeader("Authorization", $"Bearer {_apiKey}");
webSocket.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1");
try
{
await webSocket.ConnectAsync(_openAiUri, CancellationToken.None);
Console.Write("\n✅ Connected to OpenAI WebSocket..." + "\n");
// Step 1: Wait for session.created event (return immediately)
string sessionResponse = await ReceiveSingleMessage(webSocket);
Console.Write("\n🟢 Session Created: " + sessionResponse + "\n");
// Step 2: Send user message (conversation.item.create)
string messageId = Guid.NewGuid().ToString("N").Substring(0, 32);
var userMessagePayload = new
{
type = "conversation.item.create",
previous_item_id = (string?)null,
item = new
{
id = messageId,
type = "message",
role = "user",
content = new[] { new { type = "input_text", text = userMessage } }
}
};
string userMessageJson = JsonSerializer.Serialize(userMessagePayload);
Console.Write("\n📤 Sending user message: " + userMessageJson + "\n");
await webSocket.SendAsync(Encoding.UTF8.GetBytes(userMessageJson), WebSocketMessageType.Text, true, CancellationToken.None);
// Step 3: Wait for message confirmation (return immediately)
string messageResponse = await ReceiveSingleMessage(webSocket);
Console.Write("\n🟢 Message Created: " + messageResponse + "\n");
// Step 4: Send response.create to trigger AI response
var responseRequestPayload = new
{
type = "response.create",
response = new
{
modalities = new[] { "text" },
instructions = systemMessage,
temperature = 0.6,
max_output_tokens = 4096
}
};
string responseRequestJson = JsonSerializer.Serialize(responseRequestPayload);
Console.Write("\n📤 Sending response.create: " + responseRequestJson + "\n");
await webSocket.SendAsync(Encoding.UTF8.GetBytes(responseRequestJson), WebSocketMessageType.Text, true, CancellationToken.None);
// Step 5: Keep listening for AI's full response (STREAMING)
string aiResponse = await ReceiveStreamingMessages(sessionId, webSocket);
//Console.Write("\n🔵 AI Final Response: " + aiResponse + "\n");
//return aiResponse;
}
catch (Exception ex)
{
Console.Write($"❌ Error: {ex.Message}" + "\n");
_callback?.Invoke(sessionId, "Error communicating with OpenAI Realtime API");
//return "Error communicating with OpenAI Realtime API";
}
finally
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection", CancellationToken.None);
}
}
}
private async Task<string> ReceiveSingleMessage(ClientWebSocket webSocket)
{
var buffer = new byte[8192];
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.Write("\n 🔵 Received WebSocket Message: " + message);
return message;
}
private async Task<string> ReceiveStreamingMessages(string sessionId, ClientWebSocket webSocket, bool invokeCallback = true)
{
var buffer = new byte[8192];
var responseBuilder = new StringBuilder();
while (true)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.Write("\n 🔵 Received WebSocket Message: " + message);
using JsonDocument doc = JsonDocument.Parse(message);
JsonElement root = doc.RootElement;
if (root.TryGetProperty("type", out JsonElement typeElement))
{
string eventType = typeElement.GetString() ?? "";
switch (eventType)
{
case "conversation.item.created":
Console.WriteLine("🟡 Assistant response item created, continuing...");
continue;
case "response.output_item.added":
Console.WriteLine("🟡 Assistant started responding...");
continue;
case "response.text.delta":
if (root.TryGetProperty("delta", out JsonElement deltaElement))
{
string textPart = deltaElement.GetString() ?? "";
if (!string.IsNullOrEmpty(textPart))
{
//Console.WriteLine($"🔠 Appending Text: {textPart}");
streamedHtmlContent += textPart;
if (invokeCallback)
_callback?.Invoke(sessionId, streamedHtmlContent);
responseBuilder.Append(textPart);
}
}
break;
case "response.done":
Console.WriteLine("✅ Assistant finished responding.");
// Check if response failed
if (root.TryGetProperty("response", out JsonElement responseElement) &&
responseElement.TryGetProperty("status", out JsonElement statusElement) &&
statusElement.GetString() == "failed")
{
// Extract error message if available
string errorMessage = "Unknown error occurred.";
if (responseElement.TryGetProperty("status_details", out JsonElement statusDetails) &&
statusDetails.TryGetProperty("error", out JsonElement errorElement))
{
errorMessage = errorElement.GetProperty("message").GetString() ?? errorMessage;
_callback?.Invoke(sessionId, errorMessage);
}
Console.WriteLine($"❌ Error: {errorMessage}");
return $"Error: {errorMessage}";
}
return responseBuilder.ToString();
}
}
}
}
}
}