ruby/index.md +0 −596 deleted
File Deleted View Diff
1# OpenAI Ruby API library
2
3The OpenAI Ruby library provides convenient access to the OpenAI REST API from any Ruby 3.2.0+ application. It ships with comprehensive types & docstrings in Yard, RBS, and RBI – [see below](https://github.com/openai/openai-ruby#Sorbet) for usage with Sorbet. The standard library's `net/http` is used as the HTTP transport, with connection pooling via the `connection_pool` gem.
4
5## Documentation
6
7Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/openai).
8
9The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs).
10
11## Installation
12
13To use this gem, install via Bundler by adding the following to your application's `Gemfile`:
14
15<!-- x-release-please-start-version -->
16
17```ruby
18gem "openai", "~> 0.63.0"
19```
20
21<!-- x-release-please-end -->
22
23## Usage
24
25```ruby
26require "bundler/setup"
27require "openai"
28
29openai = OpenAI::Client.new(
30 api_key: ENV["OPENAI_API_KEY"] # This is the default and can be omitted
31)
32
33chat_completion = openai.chat.completions.create(messages: [{role: "user", content: "Say this is a test"}], model: "gpt-5.2")
34
35puts(chat_completion)
36```
37
38### Streaming
39
40We provide support for streaming responses using Server-Sent Events (SSE).
41
42```ruby
43stream = openai.responses.stream(
44 input: "Write a haiku about OpenAI.",
45 model: "gpt-5.2"
46)
47
48stream.each do |event|
49 puts(event.type)
50end
51```
52
53### Pagination
54
55List methods in the OpenAI API are paginated.
56
57This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
58
59```ruby
60page = openai.fine_tuning.jobs.list(limit: 20)
61
62# Fetch single item from page.
63job = page.data[0]
64puts(job.id)
65
66# Automatically fetches more pages as needed.
67page.auto_paging_each do |job|
68 puts(job.id)
69end
70```
71
72Alternatively, you can use the `#next_page?` and `#next_page` methods for more granular control working with pages.
73
74```ruby
75if page.next_page?
76 new_page = page.next_page
77 puts(new_page.data[0].id)
78end
79```
80
81### File uploads
82
83Request parameters that correspond to file uploads can be passed as raw contents, a [`Pathname`](https://rubyapi.org/3.2/o/pathname) instance, [`StringIO`](https://rubyapi.org/3.2/o/stringio), or more.
84
85```ruby
86require "pathname"
87
88# Use `Pathname` to send the filename and/or avoid paging a large file into memory:
89file_object = openai.files.create(file: Pathname("input.jsonl"), purpose: "fine-tune")
90
91# Alternatively, pass file contents or a `StringIO` directly:
92file_object = openai.files.create(file: File.read("input.jsonl"), purpose: "fine-tune")
93
94puts(file_object.id)
95
96# Or, to control the filename and/or content type:
97image = OpenAI::FilePart.new(Pathname('dog.jpg'), content_type: 'image/jpeg')
98edited = openai.images.edit(
99 prompt: "make this image look like a painting",
100 model: "gpt-image-1",
101 size: '1024x1024',
102 image: image
103)
104
105puts(edited.data.first)
106```
107
108Note 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).
109
110## Workload Identity Authentication
111
112For 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.
113
114`client_id` remains available as an optional parameter for token exchange setups that require an explicit OAuth client ID.
115
116### Kubernetes Service Account
117
118```ruby
119require "openai"
120
121# Configure Kubernetes service account provider
122provider = OpenAI::Auth::SubjectTokenProviders::K8sServiceAccountTokenProvider.new
123
124workload_identity = OpenAI::Auth::WorkloadIdentity.new(
125 identity_provider_id: ENV["IDENTITY_PROVIDER_ID"], # This is the default and can be omitted
126 service_account_id: ENV["SERVICE_ACCOUNT_ID"], # This is the default and can be omitted
127 provider: provider
128)
129
130client = OpenAI::Client.new(
131 workload_identity: workload_identity,
132)
133
134response = client.chat.completions.create(
135 messages: [{role: "user", content: "Hello!"}],
136 model: "gpt-5.2"
137)
138```
139
140### Azure Managed Identity
141
142```ruby
143provider = OpenAI::Auth::SubjectTokenProviders::AzureManagedIdentityTokenProvider.new
144
145workload_identity = OpenAI::Auth::WorkloadIdentity.new(
146 identity_provider_id: ENV["IDENTITY_PROVIDER_ID"], # This is the default and can be omitted
147 service_account_id: ENV["SERVICE_ACCOUNT_ID"], # This is the default and can be omitted
148 provider: provider
149)
150
151client = OpenAI::Client.new(
152 workload_identity: workload_identity,
153)
154```
155
156### GCP Metadata Server
157
158```ruby
159provider = OpenAI::Auth::SubjectTokenProviders::GCPIDTokenProvider.new
160
161workload_identity = OpenAI::Auth::WorkloadIdentity.new(
162 identity_provider_id: ENV["IDENTITY_PROVIDER_ID"], # This is the default and can be omitted
163 service_account_id: ENV["SERVICE_ACCOUNT_ID"], # This is the default and can be omitted
164 provider: provider
165)
166
167client = OpenAI::Client.new(
168 workload_identity: workload_identity,
169)
170```
171
172### Custom Token Providers
173
174You can implement custom token providers by including the `OpenAI::Auth::SubjectTokenProvider` module:
175
176```ruby
177class CustomProvider
178 include OpenAI::Auth::SubjectTokenProvider
179
180 def token_type
181 OpenAI::Auth::TokenType::JWT
182 end
183
184 def get_token
185 "custom-token"
186 end
187end
188
189provider = CustomProvider.new
190
191workload_identity = OpenAI::Auth::WorkloadIdentity.new(
192 identity_provider_id: ENV["IDENTITY_PROVIDER_ID"], # This is the default and can be omitted
193 service_account_id: ENV["SERVICE_ACCOUNT_ID"], # This is the default and can be omitted
194 provider: provider
195)
196
197client = OpenAI::Client.new(
198 workload_identity: workload_identity,
199 organization: ENV["OPENAI_ORG_ID"],
200 project: ENV["OPENAI_PROJECT_ID"]
201)
202```
203
204## Webhook Verification
205
206Verifying webhook signatures is _optional but encouraged_.
207
208For more information about webhooks, see [the API docs](https://platform.openai.com/docs/guides/webhooks).
209
210### Parsing webhook payloads
211
212For most use cases, you will likely want to verify the webhook and parse the payload at the same time. To achieve this, we provide the method `client.webhooks.unwrap`, which parses a webhook request and verifies that it was sent by OpenAI. This method will raise an error if the signature is invalid.
213
214Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). The `unwrap` method will parse this JSON for you into an event object after verifying the webhook was sent from OpenAI.
215
216```ruby
217require 'sinatra'
218require 'openai'
219
220# Set up the client with webhook secret from environment variable
221client = OpenAI::Client.new(webhook_secret: ENV['OPENAI_WEBHOOK_SECRET'])
222
223post '/webhook' do
224 request_body = request.body.read
225
226 begin
227 event = client.webhooks.unwrap(request_body, request.env)
228
229 case event.type
230 when 'response.completed'
231 puts "Response completed: #{event.data}"
232 when 'response.failed'
233 puts "Response failed: #{event.data}"
234 else
235 puts "Unhandled event type: #{event.type}"
236 end
237
238 status 200
239 'ok'
240 rescue StandardError => e
241 puts "Invalid signature: #{e}"
242 status 400
243 'Invalid signature'
244 end
245end
246```
247
248### Verifying webhook payloads directly
249
250In some cases, you may want to verify the webhook separately from parsing the payload. If you prefer to handle these steps separately, we provide the method `client.webhooks.verify_signature` to _only verify_ the signature of a webhook request. Like `unwrap`, this method will raise an error if the signature is invalid.
251
252Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). You will then need to parse the body after verifying the signature.
253
254```ruby
255require 'sinatra'
256require 'json'
257require 'openai'
258
259# Set up the client with webhook secret from environment variable
260client = OpenAI::Client.new(webhook_secret: ENV['OPENAI_WEBHOOK_SECRET'])
261
262post '/webhook' do
263 request_body = request.body.read
264
265 begin
266 client.webhooks.verify_signature(request_body, request.env)
267
268 # Parse the body after verification
269 event = JSON.parse(request_body)
270 puts "Verified event: #{event}"
271
272 status 200
273 'ok'
274 rescue StandardError => e
275 puts "Invalid signature: #{e}"
276 status 400
277 'Invalid signature'
278 end
279end
280```
281
282### [Structured outputs](https://platform.openai.com/docs/guides/structured-outputs) and function calling
283
284This SDK ships with helpers in `OpenAI::BaseModel`, `OpenAI::ArrayOf`, `OpenAI::EnumOf`, and `OpenAI::UnionOf` to help you define the supported JSON schemas used in making structured outputs and function calling requests.
285
286<details>
287<summary>Snippet</summary>
288
289```ruby
290# Participant model with an optional last_name and an enum for status
291class Participant < OpenAI::BaseModel
292 required :first_name, String
293 required :last_name, String, nil?: true
294 required :status, OpenAI::EnumOf[:confirmed, :unconfirmed, :tentative]
295end
296
297# CalendarEvent model with a list of participants.
298class CalendarEvent < OpenAI::BaseModel
299 required :name, String
300 required :date, String
301 required :participants, OpenAI::ArrayOf[Participant]
302end
303
304
305client = OpenAI::Client.new
306
307response = client.responses.create(
308 model: "gpt-5.2",
309 input: [
310 {role: :system, content: "Extract the event information."},
311 {
312 role: :user,
313 content: <<~CONTENT
314 Alice Shah and Lena are going to a science fair on Friday at 123 Main St. in San Diego.
315 They have also invited Jasper Vellani and Talia Groves - Jasper has not responded and Talia said she is thinking about it.
316 CONTENT
317 }
318 ],
319 text: CalendarEvent
320)
321
322response
323 .output
324 .flat_map { _1.content }
325 # filter out refusal responses
326 .grep_v(OpenAI::Models::Responses::ResponseOutputRefusal)
327 .each do |content|
328 # parsed is an instance of `CalendarEvent`
329 pp(content.parsed)
330 end
331```
332
333</details>
334
335See the [examples](https://github.com/openai/openai-ruby/tree/main/examples) directory for more usage examples for helper usage.
336
337To make the equivalent request using raw JSON schema format, you would do the following:
338
339<details>
340<summary>Snippet</summary>
341
342```ruby
343response = client.responses.create(
344 model: "gpt-5.2",
345 input: [
346 {role: :system, content: "Extract the event information."},
347 {
348 role: :user,
349 content: "..."
350 }
351 ],
352 text: {
353 format: {
354 type: :json_schema,
355 name: "CalendarEvent",
356 strict: true,
357 schema: {
358 type: "object",
359 properties: {
360 name: {type: "string"},
361 date: {type: "string"},
362 participants: {
363 type: "array",
364 items: {
365 type: "object",
366 properties: {
367 first_name: {type: "string"},
368 last_name: {type: %w[string null]},
369 status: {type: "string", enum: %w[confirmed unconfirmed tentative]}
370 },
371 required: %w[first_name last_name status],
372 additionalProperties: false
373 }
374 }
375 },
376 required: %w[name date participants],
377 additionalProperties: false
378 }
379 }
380 }
381)
382```
383
384</details>
385
386### Handling errors
387
388When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `OpenAI::Errors::APIError` will be thrown:
389
390```ruby
391begin
392 job = openai.fine_tuning.jobs.create(model: "gpt-4o", training_file: "file-abc123")
393rescue OpenAI::Errors::APIConnectionError => e
394 puts("The server could not be reached")
395 puts(e.cause) # an underlying Exception, likely raised within `net/http`
396rescue OpenAI::Errors::RateLimitError => e
397 puts("A 429 status code was received; we should back off a bit.")
398rescue OpenAI::Errors::APIStatusError => e
399 puts("Another non-200-range status code was received")
400 puts(e.status)
401end
402```
403
404Error codes are as follows:
405
406| Cause | Error Type |
407| ---------------- | -------------------------- |
408| HTTP 400 | `BadRequestError` |
409| HTTP 401 | `AuthenticationError` |
410| HTTP 403 | `PermissionDeniedError` |
411| HTTP 404 | `NotFoundError` |
412| HTTP 409 | `ConflictError` |
413| HTTP 422 | `UnprocessableEntityError` |
414| HTTP 429 | `RateLimitError` |
415| HTTP >= 500 | `InternalServerError` |
416| Other HTTP error | `APIStatusError` |
417| Timeout | `APITimeoutError` |
418| Network error | `APIConnectionError` |
419
420### Retries
421
422Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
423
424Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, >=500 Internal errors, and timeouts will all be retried by default.
425
426You can use the `max_retries` option to configure or disable this:
427
428```ruby
429# Configure the default for all requests:
430openai = OpenAI::Client.new(
431 max_retries: 0 # default is 2
432)
433
434# Or, configure per-request:
435openai.chat.completions.create(
436 messages: [{role: "user", content: "How can I get the name of the current day in JavaScript?"}],
437 model: "gpt-5.2",
438 request_options: {max_retries: 5}
439)
440```
441
442### Timeouts
443
444By default, requests will time out after 600 seconds. You can use the timeout option to configure or disable this:
445
446```ruby
447# Configure the default for all requests:
448openai = OpenAI::Client.new(
449 timeout: nil # default is 600
450)
451
452# Or, configure per-request:
453openai.chat.completions.create(
454 messages: [{role: "user", content: "How can I list all files in a directory using Python?"}],
455 model: "gpt-5.2",
456 request_options: {timeout: 5}
457)
458```
459
460On timeout, `OpenAI::Errors::APITimeoutError` is raised.
461
462Note that requests that time out are retried by default.
463
464## Advanced concepts
465
466### BaseModel
467
468All parameter and response objects inherit from `OpenAI::Internal::Type::BaseModel`, which provides several conveniences, including:
469
4701. All fields, including unknown ones, are accessible with `obj[:prop]` syntax, and can be destructured with `obj => {prop: prop}` or pattern-matching syntax.
471
4722. Structural equivalence for equality; if two API calls return the same values, comparing the responses with == will return true.
473
4743. Both instances and the classes themselves can be pretty-printed.
475
4764. Helpers such as `#to_h`, `#deep_to_h`, `#to_json`, and `#to_yaml`.
477
478### Making custom or undocumented requests
479
480#### Undocumented properties
481
482You can send undocumented parameters to any endpoint, and read undocumented response properties, like so:
483
484Note: the `extra_` parameters of the same name overrides the documented parameters.
485
486```ruby
487chat_completion =
488 openai.chat.completions.create(
489 messages: [{role: "user", content: "How can I get the name of the current day in JavaScript?"}],
490 model: "gpt-5.2",
491 request_options: {
492 extra_query: {my_query_parameter: value},
493 extra_body: {my_body_parameter: value},
494 extra_headers: {"my-header": value}
495 }
496 )
497
498puts(chat_completion[:my_undocumented_property])
499```
500
501#### Undocumented request params
502
503If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` under the `request_options:` parameter when making a request, as seen in the examples above.
504
505#### Undocumented endpoints
506
507To make requests to undocumented endpoints while retaining the benefit of auth, retries, and so on, you can make requests using `client.request`, like so:
508
509```ruby
510response = client.request(
511 method: :post,
512 path: '/undocumented/endpoint',
513 query: {"dog": "woof"},
514 headers: {"useful-header": "interesting-value"},
515 body: {"hello": "world"}
516)
517```
518
519### Concurrency & connection pooling
520
521The `OpenAI::Client` instances are threadsafe, but are only fork-safe when there are no in-flight HTTP requests.
522
523Each instance of `OpenAI::Client` has its own HTTP connection pool with a default size of 99. As such, we recommend instantiating the client once per application in most settings.
524
525When all available connections from the pool are checked out, requests wait for a new connection to become available, with queue time counting towards the request timeout.
526
527Unless otherwise specified, other classes in the SDK do not have locks protecting their underlying data structure.
528
529## Sorbet
530
531This library provides comprehensive [RBI](https://sorbet.org/docs/rbi) definitions and has no dependency on sorbet-runtime.
532
533You can provide typesafe request parameters like so:
534
535```ruby
536openai.chat.completions.create(
537 messages: [OpenAI::Chat::ChatCompletionUserMessageParam.new(content: "Say this is a test")],
538 model: "gpt-5.2"
539)
540```
541
542Or, equivalently:
543
544```ruby
545# Hashes work, but are not typesafe:
546openai.chat.completions.create(messages: [{role: "user", content: "Say this is a test"}], model: "gpt-5.2")
547
548# You can also splat a full Params class:
549params = OpenAI::Chat::CompletionCreateParams.new(
550 messages: [OpenAI::Chat::ChatCompletionUserMessageParam.new(content: "Say this is a test")],
551 model: "gpt-5.2"
552)
553openai.chat.completions.create(**params)
554```
555
556### Enums
557
558Since this library does not depend on `sorbet-runtime`, it cannot provide [`T::Enum`](https://sorbet.org/docs/tenum) instances. Instead, we provide "tagged symbols" instead, which is always a primitive at runtime:
559
560```ruby
561# :in_memory
562puts(OpenAI::Chat::CompletionCreateParams::PromptCacheRetention::IN_MEMORY)
563
564# Revealed type: `T.all(OpenAI::Chat::CompletionCreateParams::PromptCacheRetention, Symbol)`
565T.reveal_type(OpenAI::Chat::CompletionCreateParams::PromptCacheRetention::IN_MEMORY)
566```
567
568Enum parameters have a "relaxed" type, so you can either pass in enum constants or their literal value:
569
570```ruby
571# Using the enum constants preserves the tagged type information:
572openai.chat.completions.create(
573 prompt_cache_retention: OpenAI::Chat::CompletionCreateParams::PromptCacheRetention::IN_MEMORY,
574 # …
575)
576
577# Literal values are also permissible:
578openai.chat.completions.create(
579 prompt_cache_retention: :in_memory,
580 # …
581)
582```
583
584## Versioning
585
586This package follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions. As the library is in initial development and has a major version of `0`, APIs may change at any time.
587
588This package considers improvements to the (non-runtime) `*.rbi` and `*.rbs` type definitions to be non-breaking changes.
589
590## Requirements
591
592Ruby 3.2.0 or higher.
593
594## Contributing
595
596See [the contributing documentation](https://github.com/openai/openai-ruby/tree/main/CONTRIBUTING.md).