Skip to content

Instantly share code, notes, and snippets.

@rnaud
Last active October 9, 2024 21:09
Show Gist options
  • Save rnaud/417861e966bb019543cd23e4299f8631 to your computer and use it in GitHub Desktop.
Save rnaud/417861e966bb019543cd23e4299f8631 to your computer and use it in GitHub Desktop.
Ruby Thin OpenAI Realtime API wrapper
OPENAI_API_KEY=your key
# Gemfile
source 'https://rubygems.org'
gem 'faye-websocket'
gem 'thin'
gem 'dotenv'
# 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)
@rnaud
Copy link
Author

rnaud commented Oct 9, 2024

In a folder, replace your API key, run bundle install then bundle exec ruby server.rb

This should work with the https://github.com/openai/openai-realtime-console if you replace the URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment