ruby/index.md +137 −9
82 82
83Request parameters that correspond to file uploads can be passed as raw contents, a [`Pathname`](https://rubyapi.org/3.3/o/pathname) instance, [`StringIO`](https://rubyapi.org/3.3/o/stringio), or more.83Request parameters that correspond to file uploads can be passed as raw contents, a [`Pathname`](https://rubyapi.org/3.3/o/pathname) instance, [`StringIO`](https://rubyapi.org/3.3/o/stringio), or more.
84 84
85Raw `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.
86
85```ruby87```ruby
86require "pathname"88require "pathname"
87 89
93 95
94puts(file_object.id)96puts(file_object.id)
95 97
9698# Or, to control the filename and/or content type:# For format-sensitive uploads, provide the filename and content type:
9799image = OpenAI::FilePart.new(Pathname('dog.jpg'), content_type: 'image/jpeg')audio_data = StringIO.new(File.binread("audio.wav"))
100audio = OpenAI::FilePart.new(
101 audio_data,
102 filename: "audio.wav",
103 content_type: "audio/wav"
104)
105transcription = openai.audio.transcriptions.create(
106 model: "gpt-4o-transcribe",
107 file: audio
108)
109puts(transcription.text)
110
111# FilePart also accepts a Pathname:
112image = OpenAI::FilePart.new(Pathname("dog.jpg"), content_type: "image/jpeg")
98edited = openai.images.edit(113edited = openai.images.edit(
99 prompt: "make this image look like a painting",114 prompt: "make this image look like a painting",
100 model: "gpt-image-1",115 model: "gpt-image-1",
101116 size: '1024x1024', size: "1024x1024",
102 image: image117 image: image
103)118)
104 119
608| Timeout | `APITimeoutError` |623| Timeout | `APITimeoutError` |
609| Network error | `APIConnectionError` |624| Network error | `APIConnectionError` |
610 625
611626### Request IDs### Request logging
627
628Request logging is disabled by default. Enable it with a standard Ruby logger
629when creating the client:
630
631```ruby
632client = OpenAI::Client.new(
633 api_key: ENV.fetch("OPENAI_API_KEY"),
634 logger: Rails.logger,
635 log_level: :info
636)
637```
638
639The logger can be any object that responds to `debug`, `info`, `warn`, and
640`error`; the SDK does not depend on Rails. Supplying a logger enables `:info`
641logging by default. When logging is enabled without a custom logger, the SDK
642uses a standard-library `Logger` that writes to stderr. Use `log_level: :off`
643when you only need retry notifications.
644
645You can instead set `OPENAI_LOG=info` or `OPENAI_LOG=debug`. An explicit
646`log_level:` takes precedence over the environment variable.
647
648For example, to use the stderr logger for one process:
649
650```sh
651OPENAI_LOG=info bundle exec ruby app.rb
652```
653
654A completion message includes the logical request and retry context:
655
656```text
657[openai] request complete log_id=log_a1b2c3d4e5f6 method=POST path=/v1/responses status=200 request_id=req_123 attempts=1 duration_ms=42.7
658```
659
660| Level | Behavior |
661| --- | --- |
662| `:off` | No SDK request logs (default) |
663| `:error` | Terminal request failures after retries are exhausted |
664| `:warn` | Error events plus retry reason and delay |
665| `:info` | Safe request completion summaries |
666| `:debug` | Per-attempt headers and bounded body diagnostics |
667
668Info, warning, and error logs include operational fields such as the HTTP
669method, sanitized path, status, request ID, duration, and attempt count. They
670never include headers or bodies. Debug logs redact credential-bearing headers
671and query parameters, including authorization, API-key, cookie, token,
672credential, and signature values.
673
674Debug logging can still disclose sensitive prompts, model responses, and tool
675arguments. Do not enable it in production unless your log destination and data
676retention policy are appropriate. The built-in logger omits uploaded file
677contents, multipart bodies, binary bodies, large opaque/base64-like values, and
678server-sent event contents. Text bodies are truncated to a fixed bound;
679oversized JSON and incomplete bodies are marked as omitted. Response bodies are
680observed only as the application consumes them and are never read eagerly for
681logging.
682
683SDK log messages are intended for human diagnostics. Their text format is not
684a stable structured-event API and may change between releases. Exceptions from
685a supplied logger are isolated and never replace an API result or API error.
686
687### Response metadata and request IDs
612 688
613OpenAI recommends logging request IDs in production so requests can be traced689OpenAI recommends logging request IDs in production so requests can be traced
614690during troubleshooting. Successful typed responses expose `_request_id`, whichduring troubleshooting. Top-level models and pages returned by the client expose
615691is populated from the `x-request-id` response header:immutable HTTP response metadata through `last_response`:
616 692
617```ruby693```ruby
618response = openai.responses.create(model: "gpt-5.2", input: "Say 'this is a test'.")694response = openai.responses.create(model: "gpt-5.2", input: "Say 'this is a test'.")
695puts(response.last_response.status) # 200
696puts(response.last_response.headers["x-request-id"]) # req_123
697puts(response.last_response.request_id) # req_123
619puts(response._request_id) # req_123698puts(response._request_id) # req_123
620```699```
621 700
622701The `_request_id` property is only populated on the top-level response objectHeader names and values are normalized to strings, header names are lowercase,
623702and is not included in `to_h`, JSON, or YAML output. Unlike other propertiesand the metadata and header map are frozen. Streams expose the metadata for the
624703that begin with an underscore, `_request_id` is public.HTTP response that opened the stream. Higher-level streaming helpers expose the
704same metadata as their underlying stream; models assembled from stream events
705do not.
706
707`last_response` and `_request_id` are only populated on top-level typed models
708and pages returned by the client. They are `nil` on constructed or nested models
709and are not included in `to_h`, JSON, or YAML output. Endpoints returning raw
710primitives, binary data, or `nil` do not expose this metadata. Unlike other
711properties that begin with an underscore, `_request_id` is public.
625 712
626For failed HTTP requests, catch `OpenAI::Errors::APIStatusError` and use713For failed HTTP requests, catch `OpenAI::Errors::APIStatusError` and use
627`request_id`:714`request_id`:
660)747)
661```748```
662 749
750To observe retries as they happen, supply an `on_retry` callback when creating
751the client. The callback runs immediately before the retry delay and receives
752an immutable `OpenAI::RetryEvent`:
753
754```ruby
755openai = OpenAI::Client.new(
756 log_level: :off,
757 on_retry: lambda do |event|
758 Rails.logger.warn(
759 "OpenAI retry #{event.attempt}/#{event.max_attempts} " \
760 "status=#{event.status.inspect} request_id=#{event.request_id.inspect}"
761 )
762 end
763)
764```
765
766For response-triggered retries, `event.response` contains the same immutable
767status, headers, and request ID shape as `last_response`. For connection errors,
768`event.error` is populated instead. Exceptions raised by the callback are
769isolated and do not replace the API result or error.
770
663### Timeouts771### Timeouts
664 772
665By default, requests will time out after 600 seconds. You can use the timeout option to configure or disable this:773By default, requests will time out after 600 seconds. You can use the timeout option to configure or disable this:
806openai.chat.completions.create(**params)914openai.chat.completions.create(**params)
807```915```
808 916
917### Structured output models
918
919The SDK includes a Tapioca DSL compiler for application-defined subclasses of
920`OpenAI::BaseModel`. When Tapioca loads your application, running
921`bundle exec tapioca dsl` generates typed readers for fields declared with
922`required`, including nested models, arrays, enums, unions, and fields declared
923with `nil?: true`.
924
925Response `parsed` fields can contain different application-defined models, so
926their generated SDK type remains broad. Cast a parsed value to the structured
927output model supplied with the request before accessing its generated readers:
928
929```ruby
930event = T.cast(content.parsed, CalendarEvent)
931puts(event.name)
932```
933
934The compiler is only loaded by Tapioca; using the SDK normally still does not
935require `sorbet-runtime`.
936
809### Enums937### Enums
810 938
811Since 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:939Since 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: