About openai/openai-ruby
openai/openai-ruby is an open-source project on GitHub, mainly written in Ruby. Official Ruby SDK for the OpenAI API It currently holds 468 stars and 67 forks with 0 open issues, and was last pushed on an unknown date (repository created unknown).
Project Overview
AI Homed tracks it on the Today's Trending board.
GitHub Repository Details
README
OpenAI Ruby API library
The OpenAI Ruby library provides convenient access to the OpenAI REST API from any Ruby 3.3.0+ application. It ships with comprehensive types & docstrings in Yard, RBS, and RBI – see below for usage with Sorbet. The standard library's net/http is used as the HTTP transport, with connection pooling via the connection_pool gem.
Documentation
Documentation for releases of this gem can be found on RubyDoc.
The REST API documentation can be found on platform.openai.com.
Installation
To use this gem, install via Bundler by adding the following to your application's Gemfile:
gem "openai", "~> 0.103.0"
Usage
require "bundler/setup"
require "openai"
openai = OpenAI::Client.new(
api_key: ENV["OPENAI_API_KEY"] # This is the default and can be omitted
)
chat_completion = openai.chat.completions.create(messages: [{role: "user", content: "Say this is a test"}], model: "gpt-5.2")
puts(chat_completion)
Streaming
We provide support for streaming responses using Server-Sent Events (SSE).
stream = openai.responses.stream(
input: "Write a haiku about OpenAI.",
model: "gpt-5.2"
)
stream.each do |event|
puts(event.type)
end
Agents final output (beta)
Collect final output from an Agents creation stream:
stream = openai.beta.agents.sessions.create_streaming(
agent: {model: "gpt-5.2", instructions: "Explain the supplied policy clearly."},
environment: {type: :none},
input: "Explain this policy: ..."
)
result = stream.get_final_result
puts(result.output_text)
Use the same getter for follow-ups; to display progress first, call
stream.with_result_collection before iterating:
openai.beta.agents.sessions.stream(result.session_id, input: "Give an example.") do |stream|
puts(stream.get_final_result.output_text)
end
The beta result includes turn metadata and final messages; unsuccessful or
incomplete collection raises OpenAI::Helpers::Beta::Agents::ResultError.
Typed Agents output (beta)
Use an OpenAI::BaseModel with output_type to configure and parse a structured answer:
class Report < OpenAI::BaseModel
required :summary, String
required :findings, OpenAI::ArrayOf[String]
end
result = openai.beta.agents.sessions.create_streaming(
agent: {model: "gpt-5.2"}, environment: {type: :none},
input: "Summarize these notes: ...", output_type: Report
).get_final_result
puts(result.output_parsed.summary)
openai.beta.agents.sessions.stream(result.session_id, input: "Update the report.", output_type: Report) do |stream|
puts(stream.get_final_result.output_parsed.summary)
end
Parsing follows the SDK’s existing BaseModel conventions. Each output-text item is parsed separately; output_parsed returns the first. Follow-up output_type only parses the answer; it does not change the session's schema.
OutputParseError#raw_result preserves the completed answer if parsing fails.
Hosted files and artifacts
Prepare selected local files before creating a hosted session, or use upload to stage one in an existing environment. Keep upload_ids for explicit cleanup through the Files API.
prepared = openai.beta.agents.environments.files.prepare({
"/workspace/source.pdf" => Pathname("source.pdf")
})
stream = openai.beta.agents.sessions.create_streaming(
agent: {model: "gpt-6-astra"},
environment: {type: :openai_hosted, files: prepared.files},
input: "Read source.pdf and write /workspace/outputs/report.md"
)
result = stream.get_final_result
artifacts = openai.beta.agents.sessions.artifacts.for_result(result)
artifacts.download(path: "/workspace/outputs/report.md", to: Pathname("report.md"))
Or keep the downloaded bytes in memory:
buffer = StringIO.new
artifacts.download(path: "/workspace/outputs/report.md", to: buffer)
puts(buffer.string)
prepare_directory(directory, destination:, include:) stages an explicitly selected snapshot. Local path and directory helpers assume stable, application-owned source paths; they are not a filesystem sandbox for arbitrary user-supplied paths or hostile local writers. Downloads select the exact result turn and stream to an application-owned local path or the writer you supply. An interrupted transfer may leave partial contents.
Local audio
Record a clip and play generated speech with optional FFmpeg/FFplay helpers. See the local audio guide for setup, device selection and examples.
Realtime WebSockets
The SDK supports block-scoped, typed Realtime WebSocket sessions for
server-side text, committed-turn transcription, one-turn voice, WebRTC call
creation, and sideband control of existing WebRTC or SIP calls. Add the
optional async-websocket gem for WebSocket connections, then use
client.realtime.connect:
client.realtime.connect(model: "gpt-realtime-2.1") do |connection|
connection.session.update(type: :realtime, output_modalities: [:text])
connection.conversation.items.create(
type: :message,
role: :user,
content: [{type: :input_text, text: "Hello"}]
)
connection.response.create
connection.each do |event|
print(event.delta) if event.is_a?(OpenAI::Realtime::ResponseTextDeltaEvent)
break if event.is_a?(OpenAI::Realtime::ResponseDoneEvent)
end
end
WebRTC calls can be created with client.realtime.calls.create; its HTTP
response preserves the SDP answer and the Location header needed to attach a
sideband connection. See the Realtime WebSocket guide and the runnable
text, transcription, voice, and sideband examples
for lifecycle, authentication, proxy, TLS, and custom transport details.
Responses WebSockets
The Responses API also supports a persistent, block-scoped WebSocket for
sequential and multiplexed response creation. Add the optional
async-websocket gem, then send response.create events through
client.responses.connect:
client.responses.connect do |connection|
connection.response.create(
model: "gpt-5.2",
input: "Say hello.",
stream_id: "turn_1"
)
connection.each do |event|
print(event.delta) if event.type == :"response.output_text.delta"
break if event.type == :"response.completed"
end
end
The connection is intentionally single-owner: callers serialize writes and
use one reader (receive or each). The SDK forwards response fields and
stream_id values without imposing additional client-side policy. Known
server events are decoded into generated models on a best-effort basis, while
newer event types remain observable as UnknownServerEvent values. The SDK
does not automatically reconnect or replay an ambiguous write. When the server
closes a connection, open a new connection and continue with
previous_response_id when the response was stored.
See the Responses WebSocket guide and runnable workflows for tool turns, multiplexing, stored continuation, stateless replay, and operation deadlines.
Pagination
List methods in the OpenAI API are paginated.
This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
page = openai.fine_tuning.jobs.list(limit: 20)
Fetch single item from page.
job = page.data[0]
puts(job.id)
Automatically fetches more pages as needed.
page.auto_paging_each do |job|
puts(job.id)
end
Alternatively, you can use the #next_page? and #next_page methods for more granular control working with pages.
if page.next_page?
new_page = page.next_page
puts(new_page.data[0].id)
end
File uploads
Request parameters that correspond to file uploads can be passed as raw contents, a Pathname instance, StringIO, or more.
Raw String and StringIO values, and IO objects without a path, do not carry format-identifying metadata. The SDK sends them using the fallback filename upload; raw String values default to text/plain, while StringIO and pathless IO values default to application/octet-stream. For format-sensitive endpoints, such as audio transcriptions, wrap each value in OpenAI::FilePart and provide an extension-bearing filename and content type.
require "pathname"
Use Pathname to send the filename and/or avoid paging a large file into memory:
file_object = openai.files.create(file: Pathname("input.jsonl"), purpose: "fine-tune")
Alternatively, pass file contents or a StringIO directly:
file_object = openai.files.create(file: File.read("input.jsonl"), purpose: "fine-tune")
puts(file_object.id)
For format-sensitive uploads, provide the filename and content type:
audio_data = StringIO.new(File.binread("audio.wav"))
audio = OpenAI::FilePart.new(
audio_data,
filename: "audio.wav",
content_type: "audio/wav"
)
transcription = openai.audio.transcriptions.create(
model: "gpt-4o-transcribe",
file: audio
)
puts(transcription.text)
FilePart also accepts a Pathname:
image = OpenAI::FilePart.new(Pathname("dog.jpg"), content_type: "image/jpeg")
edited = openai.images.edit(
prompt: "make this image look like a painting",
model: "gpt-image-2",
size: "1024x1024",
image: image
)
puts(edited.data.first)
Note that you can also pass a raw IO descriptor, but this disables retries, as the library can't be sure if the descriptor is a file or pipe (which cannot be rewound).
Polling and vector store ingestion
Polling helpers wait for file processing and return the final resource. Durations are
expressed in seconds. By default, the helpers honor the API's recommended polling
interval, fall back to 5 seconds, and stop after 30 minutes. Pass timeout: nil to
wait indefinitely.
To keep a finite overall deadline strict, each polling retrieval includes
authentication and request replay time and does not perform transport-level retries.
Pass timeout: nil to retain the client's configured retry behavior.
file = openai.files.wait_for_processing(
file_object.id,
poll_interval: 2,
timeout: 10 * 60
)
vector_file = openai.vector_stores.files.upload_and_poll(
vector_store.id,
file: Pathname("handbook.pdf"),
attributes: {department: "engineering"}
)
batch = openai.vector_stores.file_batches.upload_and_poll(
vector_store.id,
files: [Pathname("one.pdf"), Pathname("two.pdf")],
file_ids: [file_object.id],
max_concurrency: 3
)
The vector store helpers return terminal failed and cancelled resources instead
of raising, so inspect status, last_error, or file_counts as appropriate. An
overall polling timeout raises OpenAI::Errors::PollingTimeoutError; its resource
attribute contains the last object returned by the API, or nil if the deadline
elapsed before the first response.
Batch inputs are enumerated before requests begin so the 2,000-file API limit can be checked without orphaning uploads. IO streams yielded by an enumerable must remain open until the helper returns; use paths for block-scoped lazy enumeration. Batch uploads are concurrent but not transactional. If one upload fails, the helper stops starting queued uploads and raises that error; files uploaded successfully before the failure remain available through the Files API.
Custom HTTP clients
OpenAI::Client accepts an http_client for advanced transport requirements.
Provide an object that implements execute(request) and returns an
OpenAI::HTTPClient::Response; subclassing OpenAI::HTTPClient is the easiest
way to make that contract explicit. The request exposes the SDK-prepared HTTP
method, URL, headers, encoded body, and timeout. Response bodies are enumerable
byte-string chunks (or a single buffered string), so large and streaming
responses do not need to be buffered.
The OpenAI client owns API authentication, redirects, and API-level retries.
The custom HTTP client owns connection pooling and lifecycle, must enforce
request.timeout, and should raise OpenAI::Errors::APIConnectionError or
OpenAI::Errors::APITimeoutError for retryable transport failures. Other
exceptions propagate without an SDK retry. The SDK does not close an injected
HTTP client. Connection failures are treated as potentially post-send by
default, so non-idempotent requests are not retried automatically. When a
transport can prove that no request bytes were sent, it may raise either error
with request_may_have_been_sent: false to allow the SDK retry policy to
retry a replayable request safely.
The default OpenAI::NetHTTPClient implements this contract with pooled
Net::HTTP connections. It accepts an optional block for native connection
configuration, as shown below. Call close to retire its current pools; the
HTTP client remains reusable and creates fresh connections on its next request.
Mutual TLS with a custom HTTP client
To opt in and activate mTLS for an organization or project, follow the OpenAI Mutual TLS Beta Program guide. If you use an intermediate chain, confirm that certificate-chain support is enabled for your organization.
For API-key requests that also require mutual TLS (mTLS), set the mTLS endpoint
explicitly and pass a configured OpenAI::NetHTTPClient as http_client.
OpenAI::Client accepts an HTTP client object so advanced transport behavior
does not require a separate SDK option for every use case. The default
OpenAI::NetHTTPClient accepts a block that configures each native
Net::HTTP connection before it is pooled and started.
Ruby's native TLS properties configure the client identity. The certificate file must contain the leaf certificate first, followed by any intermediate certificates needed when certificate-chain support is enabled for your organization:
require "openai"
mtls_endpoint = URI("https://mtls.api.openai.com/v1")
certificates = OpenSSL::X509::Certificate.load(
File.binread(ENV.fetch("OPENAI_CLIENT_CERTIFICATE_CHAIN"))
)
raise "Expected a client certificate" if certificates.empty?
leaf_certificate, *intermediates = certificates
private_key = OpenSSL::PKey.read(
File.binread(ENV.fetch("OPENAI_CLIENT_KEY")),
ENV["OPENAI_CLIENT_KEY_PASSPHRASE"]
)
raise "Certificate and key do not match" unless leaf_certificate.check_private_key(private_key)
now = Time.now
raise "Certificate is not yet valid" if now < leaf_certificate.not_before
raise "Certificate has expired" if now > leaf_certificate.not_after
mtls_destination = [mtls_endpoint.host, mtls_endpoint.port]
http_client = OpenAI::NetHTTPClient.new do |http|
unless http.use_ssl? && mtls_destination == [http.address, http.port]
raise "Refusing to present the client certificate to an unexpected origin"
end
http.cert = leaf_certificate
http.extra_chain_cert = intermediates
http.key = private_key
end
client = OpenAI::Client.new(
api_key: ENV.fetch("OPENAI_API_KEY"),
base_url: mtls_endpoint.to_s,
http_client: http_client
)
The SDK cannot infer whether custom HTTP configuration uses mTLS, so it does not
automatically change base_url. An explicit base_url, including an EU or
custom endpoint, is always preserved. Scope client certificates to the expected
origin, as above, so they cannot be presented elsewhere. Use the
OpenAI::NetHTTPClient block for TLS and other connection-level configuration.
Server trust remains separate from the client identity and can be customized
with Net::HTTP properties such as cert_store or ca_file.
Each OpenAI::NetHTTPClient owns its connection pool, so separate HTTP client
instances do not share connections or credentials.
After a process forks, create new OpenAI and HTTP clients in the child. Keep the
certificate configuration captured by an HTTP client immutable. For certificate
rotation, build and atomically swap in new OpenAI::NetHTTPClient and
OpenAI::Client instances, then call close on the retired HTTP client after
in-flight work finishes. See the complete custom HTTP client mTLS
example.
Microsoft Azure OpenAI
Use the standard client with the Azure provider to call model deployments through
the Azure OpenAI v1 API. The provider accepts an Azure resource endpoint and
adds /openai/v1 when needed:
require "openai"
client = OpenAI::Client.new(
provider: OpenAI::Providers.azure(
endpoint: ENV.fetch("AZURE_OPENAI_ENDPOINT"),
api_key: ENV.fetch("AZURE_OPENAI_API_KEY")
)
)
response = client.responses.create(
model: ENV.fetch("AZURE_OPENAI_DEPLOYMENT"),
input: "Say hello!"
)
puts(response.output_text)
Omit endpoint and api_key to use AZURE_OPENAI_ENDPOINT and
AZURE_OPENAI_API_KEY. For Microsoft Entra authentication, pass a callable
that returns a current bearer token. The provider invokes it before every
request attempt, including retries:
client = OpenAI::Client.new(
provider: OpenAI::Providers.azure(
endpoint: ENV.fetch("AZURE_OPENAI_ENDPOINT"),
token_provider: -> { fetch_azure_openai_token }
)
)
This integration targets the Azure OpenAI v1 API. It does not add dated
api-version query parameters or rewrite requests to legacy
/deployments/{deployment} paths. See azure.md for configuration,
authentication precedence, and endpoint security details.
Amazon Bedrock
Use the standard client with the Bedrock provider to call OpenAI models through Amazon Bedrock's OpenAI-compatible API. Add aws-sdk-core to your application for AWS credential discovery and SigV4 signing:
gem "openai"
gem "aws-sdk-core", "~> 3"
With your normal AWS credentials configured, only the region is required:
require "openai"
client = OpenAI::Client.new(
provider: OpenAI::Providers.bedrock(region: "us-west-2")
)
response = client.responses.create(
model: ENV.fetch("BEDROCK_MODEL"),
input: "Say hello!"
)
puts(response.output_text)
The provider uses the standard AWS credential chain, including environment credentials, ~/.aws/credentials, ~/.aws/config, AWS_PROFILE, named profiles, SSO and assume-role profiles, and workload credentials. Select a profile explicitly with:
client = OpenAI::Client.new(
provider: OpenAI::Providers.bedrock(
region: "us-west-2",
profile: "engineering"
)
)
AWS_BEARER_TOKEN_BEDROCK and explicit Bedrock bearer credentials are also supported. Bearer authentication does not load or require aws-sdk-core:
client = OpenAI::Client.new(
provider: OpenAI::Providers.bedrock(
region: "us-west-2",
api_key: ENV.fetch("AWS_BEARER_TOKEN_BEDROCK")
)
)
See bedrock.md for authentication precedence, static and refreshable credentials, endpoint configuration, and SigV4 request constraints.
Workload Identity Authentication
For secure, automated environments like cloud-managed Kubernetes, Azure, and GCP, you can use workload identity authentication with short-lived tokens from cloud identity providers instead of long-lived API keys.
client_id remains available as an optional parameter for token exchange setups that require an explicit OAuth client ID.
If OPENAI_API_KEY is already set in your environment, pass api_key: nil
to explicitly opt out of that ambient API key when constructing a workload
identity client.
X.509 Workload Identity (Preview)
Organizations enrolled in the X.509 workload identity preview can exchange a client certificate for a short-lived OpenAI bearer credential. Both the token exchange and subsequent API request use the same caller-attested mTLS transport. The bearer is not cryptographically bound to the certificate; the API separately requires an accepted client certificate.
require "openai"
certificates = OpenSSL::X509::Certificate.load(
File.binread(ENV.fetch("OPENAI_CLIENT_CERTIFICATE_CHAIN"))
)
raise "Expected an enrolled client certificate" if certificates.empty?
client_certificate, *intermediate_certificates = certificates
client_private_key = OpenSSL::PKey.read(
File.binread(ENV.fetch("OPENAI_CLIENT_KEY")),
ENV["OPENAI_CLIENT_KEY_PASSPHRASE"]
)
unless client_certificate.check_private_key(client_private_key)
raise "The enrolled certificate and private key do not match"
end
api_origin = ENV.fetch("OPENAI_X509_API_ORIGIN", "https://mtls.api.openai.com")
api_host = URI(api_origin).host&.downcase
approved_hosts = ["mtls.auth.openai.com", api_host].freeze
native_http_client = OpenAI::NetHTTPClient.new do |connection|
unless connection.use_ssl? && connection.port == 443 && approved_hosts.include?(connection.address.downcase)
raise "Refusing to present the enrolled certificate to an unexpected destination"
end
connection.cert = client_certificate
connection.extra_chain_cert = intermediate_certificates
connection.key = client_private_key
end
identity = OpenAI::Auth::X509WorkloadIdentity.new(
identity_provider_id: ENV.fetch("IDENTITY_PROVIDER_ID"),
service_account_id: ENV.fetch("SERVICE_ACCOUNT_ID"),
http_client: native_http_client,
api_origin: api_origin
)
client = OpenAI::Client.new(
api_key: nil,
workload_identity: identity
)
model = client.models.list.data.first
The identity creates its guarded transport internally and selects the matching
mTLS API endpoint automatically. The application still owns its certificate,
key, trust settings, and native HTTP client. Keep the selected certificate
identity static, configure only the approved issuer and API destinations, and
create a fresh native client and identity when rotating credentials. Clients
derived with with_options share the original identity's cached token when
their transport is unchanged.
The supported X.509 API regions are global, US, and EU. Set api_origin: to
https://mtls.api.openai.com, https://mtls-us.api.openai.com, or
https://mtls-eu.api.openai.com; an optional data_residency: on the client
must match. Direct mode rejects ambient proxies; pass proxy: :http_connect to
the identity only when an HTTP CONNECT proxy is configured. HTTPS proxies are
rejected before proxy credentials can be transmitted. Arbitrary custom
transports, Azure/Bedrock providers, and Realtime WebSockets are not supported.
The normal client or per-request timeout covers both token exchange and the API
request; timeout: nil disables both deadlines. Preview access must be enabled
for the organization, and mTLS can be configured for the enrolled organization
or project.
See the complete X.509 workload identity live smoke example. It performs a real token exchange and API request only when an enrolled certificate, private key, identity-provider ID, and mapped service-account ID are explicitly supplied.
Keep these values in a private environment file outside your checkout or in a secret manager, then run the example without an API key:
export OPENAI_CLIENT_CERTIFICATE_CHAIN=/secure/path/client-chain.pem
export OPENAI_CLIENT_KEY=/secure/path/client-key.pem
export IDENTITY_PROVIDER_ID=idp_example
export SERVICE_ACCOUNT_ID=svc_acct_example
ruby examples/x509_workload_identity.rb
Set OPENAI_X509_PROXY_MODE=http_connect only when an approved HTTP CONNECT
proxy is configured. Encrypted keys can use OPENAI_CLIENT_KEY_PASSPHRASE.
Kubernetes Service Account
```ruby require "openai"
Configure Kubernetes service account provider
provider = OpenAI::Auth::SubjectTokenProviders::K8sServiceAccountTokenProvider.newworkload_identity = OpenAI::Auth::WorkloadIdentity.new( identity_provider_id: ENV["IDENTITY_PROVIDER_ID"], # This is the default and can be omitted service_account_id: ENV["SERVICE