-
-
Save rnaud/417861e966bb019543cd23e4299f8631 to your computer and use it in GitHub Desktop.
Ruby Thin OpenAI Realtime API wrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
OPENAI_API_KEY=your key |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Gemfile | |
source 'https://rubygems.org' | |
gem 'faye-websocket' | |
gem 'thin' | |
gem 'dotenv' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# server.rb | |
# run with bundle exec ruby server.rb | |
# | |
require 'faye/websocket' | |
Faye::WebSocket.load_adapter('thin') | |
require 'thin' | |
require 'dotenv/load' | |
require 'thread' | |
require 'logger' | |
require 'json' | |
class OpenAIServer | |
def initialize | |
@clients = [] | |
@mutex = Mutex.new | |
@logger = Logger.new($stdout) | |
@logger.level = Logger::DEBUG | |
end | |
def call(env) | |
if Faye::WebSocket.websocket?(env) | |
ws = Faye::WebSocket.new(env, ['realtime']) | |
ws.on :open do |event| | |
@logger.info "WebSocket connection opened from #{env['REMOTE_ADDR']}" | |
# Initialize a WebSocket connection to OpenAI's Realtime API for this client | |
openai_ws = connect_to_openai(ws) | |
# Store the OpenAI WebSocket with the client WebSocket | |
@mutex.synchronize do | |
@clients << { client_ws: ws, openai_ws: openai_ws } | |
end | |
end | |
ws.on :message do |event| | |
@logger.info "Received message from client: #{event.data}" | |
handle_client_message(ws, event.data) | |
end | |
ws.on :error do |event| | |
@logger.error "WebSocket error: #{event.message}" | |
end | |
ws.on :close do |event| | |
@logger.info "WebSocket connection closed, Code: #{event.code}, Reason: #{event.reason}" | |
close_connection(ws) | |
end | |
# Return async Rack response | |
ws.rack_response | |
else | |
# Normal HTTP request | |
[200, { 'Content-Type' => 'text/plain' }, ['WebSocket server is running.']] | |
end | |
end | |
private | |
def connect_to_openai(client_ws) | |
@logger.info "Connecting to OpenAI's Realtime API..." | |
# Establish a WebSocket connection to OpenAI's Realtime API | |
url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01" | |
headers = { | |
"Authorization" => "Bearer #{ENV['OPENAI_API_KEY']}", | |
"OpenAI-Beta"=> "realtime=v1" | |
} | |
# openai_ws = WebSocket::Client::Simple.connect url, headers: headers | |
openai_ws = Faye::WebSocket::Client.new(url, nil, {headers: headers}) | |
openai_ws.on :message do |msg| | |
@logger.debug "Message from OpenAI: #{msg}" | |
@logger.debug "Message from OpenAI: #{msg.data}" | |
client_ws.send(msg.data) | |
end | |
openai_ws.on :open do | |
@logger.info "Connected to OpenAI's Realtime API" | |
end | |
openai_ws.on :error do |e| | |
@logger.error "Error with OpenAI WebSocket: #{e.message}" | |
client_ws.send("Error connecting to OpenAI: #{e}") | |
end | |
openai_ws.on :close do |e| | |
@logger.info "OpenAI WebSocket closed: #{e}" | |
client_ws.send("OpenAI connection closed: #{e}") | |
end | |
openai_ws | |
rescue StandardError => e | |
@logger.error "Failed to connect to OpenAI: #{e}" | |
client_ws.send("Failed to connect to OpenAI: #{e}") | |
nil | |
end | |
def handle_client_message(client_ws, message) | |
connection = nil | |
@mutex.synchronize do | |
connection = @clients.find { |conn| conn[:client_ws] == client_ws } | |
end | |
@logger.info "Forwarding message to OpenAI: #{message}" | |
connection[:openai_ws].send(message) | |
rescue StandardError => e | |
@logger.error "Error forwarding message to OpenAI: #{e.message}" | |
client_ws.send("Error: #{e.message}") | |
end | |
def close_connection(client_ws) | |
@mutex.synchronize do | |
connection = @clients.find { |conn| conn[:client_ws] == client_ws } | |
if connection | |
@logger.info "Closing OpenAI WebSocket connection for client." | |
connection[:openai_ws].close | |
@clients.delete(connection) | |
end | |
end | |
rescue StandardError => e | |
@logger.error "Error closing connection: #{e.message}" | |
end | |
end | |
# Start the server | |
app = OpenAIServer.new | |
puts "Starting WebSocket server on ws://localhost:3001" | |
Thin::Server.start('0.0.0.0', 3001, app) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In a folder, replace your API key, run
bundle install
thenbundle exec ruby server.rb
This should work with the https://github.com/openai/openai-realtime-console if you replace the URL.