OpenAI Ruby Library

repository·main·Indexed 19 days ago

https://github.com/openai/openai-ruby

A type-safe Ruby interface for the OpenAI REST API, requiring Ruby 3.3.0+. It supports chat completions, streaming, file uploads, and structured outputs via OpenAI::BaseModel. The library includes specialized authentication for cloud workloads (Kubernetes, Azure, GCP), webhook verification, and compatibility with Amazon Bedrock's OpenAI-compatible API.

Tokens
10.5K
Snippets
29
Records
38
Agent score
65%

What's inside openai-ruby

  1. Use Structured Outputs with the Responses API

    main

    The Responses API supports structured outputs by passing a class inheriting from OpenAI::BaseModel to the text parameter. The model is instructed to output JSON matching your schema, and the SDK automatically parses the content into instances of your class. Parsed objects are accessible via event.parsed on ResponseTextDoneEvent.

    Example schema definition:

    class Haiku < OpenAI::BaseModel
      field :first_line, String
      field :second_line, String
      field :third_line, String
    end
    class Haiku < OpenAI::BaseModel
      field :first_line, String
      field :second_line, String
      field :third_line, String
    end
    
    stream = client.responses.stream(
      input: "Write a haiku about Ruby",
      model: "gpt-4o",
      text: Haiku
    )
    
    stream.each do |event|
      case event
      when OpenAI::Streaming::ResponseTextDoneEvent
        haiku = event.parsed
        puts(haiku.first_line)
      end
    end
  2. Concurrency and Connection Pooling in OpenAI::Client

    main

    The OpenAI::Client is threadsafe, making it suitable for multi-threaded applications. However, it is only fork-safe if no HTTP requests are currently in flight.

    Each client instance maintains its own HTTP connection pool with a default size of 99. Because of this, it is recommended to instantiate the client once per application and reuse it, rather than creating a new client for every request.

  3. How Enums work with Sorbet

    main

    Because the library does not depend on sorbet-runtime, it does not provide T::Enum instances. Instead, it uses "tagged symbols" which are primitives at runtime. For enum parameters, you can either use the provided enum constants to preserve type information or pass the literal symbol value directly.

    # Using enum constants preserves tagged type information
    openai.chat.completions.create(
      reasoning_effort: OpenAI::ReasoningEffort::NONE,
      # ...
    )
    
    # Literal values are also permissible
    openai.chat.completions.create(
      reasoning_effort: :none,
      # ...
    )
    
    # Accessing the constant directly
    puts(OpenAI::ReasoningEffort::NONE) # returns :none
  4. Use Structured Outputs with the Chat Completions API

    main

    The Chat Completions API supports structured outputs by passing a class inheriting from OpenAI::BaseModel to the response_format parameter. The SDK handles JSON parsing and provides the resulting object via event.parsed on ChatContentDoneEvent events.

    class Haiku < OpenAI::BaseModel
      field :first_line, String
      field :second_line, String
      field :third_line, String
    end
    
    stream = client.chat.completions.stream(
      model: "gpt-4",
      messages: [{role: "user", content: "Write a haiku about Ruby"}],
      response_format: Haiku
    )
    
    stream.each do |event|
      case event
      when OpenAI::Streaming::ChatContentDoneEvent
        haiku = event.parsed
        puts(haiku.first_line)
      end
    end
  5. Handle paginated list results

    main

    List methods in the OpenAI API return paginated responses. The library provides two ways to handle this:

    1. Auto-pagination: Use auto_paging_each to automatically fetch subsequent pages as you iterate.
    2. Manual pagination: Use #next_page? to check for more data and #next_page to retrieve the next page object.

    Note that individual items in a page are accessed via the .data array.

    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
    
    # Manual control
    if page.next_page?
      new_page = page.next_page
      puts(new_page.data[0].id)
    end
  6. Understand the OpenAI Ruby SDK versioning policy

    main

    The OpenAI Ruby SDK follows Semantic Versioning (SemVer). The impact of changes depends on whether the gem version is in the 0.x phase or has reached 1.0 or higher:

    Change TypeWhile gem is 0.xAfter 1.0
    Backwards-compatible bug or security fixPatchPatch
    Additive endpoint, optional argument, response field, or public typeMinorMinor
    Higher minimum Ruby versionMinorMajor by default
    Other user-visible breaking changeMinor (with migration guidance)Major

    Breaking changes include removing/renaming public behavior, incompatible changes to required arguments, return types, exception families, serialization, retries, or authentication. Type-only changes to RBI or RBS definitions that create substantial new type errors are treated as Minor releases.

  7. Upload files for API requests

    main

    When performing file uploads (e.g., for fine-tuning or image editing), you can pass files in several formats:

    • Pathname: Recommended to avoid paging large files into memory and to send filenames.
    • File.read or StringIO: Pass raw contents directly.
    • OpenAI::FilePart: Use this to explicitly control the content_type and filename.

    Warning: Passing a raw IO descriptor disables retries because the library cannot guarantee the descriptor can be rewound.

    require "pathname"
    
    # Using Pathname
    file_object = openai.files.create(file: Pathname("input.jsonl"), purpose: "fine-tune")
    
    # Using raw contents
    file_object = openai.files.create(file: File.read("input.jsonl"), purpose: "fine-tune")
    
    # Controlling filename and content type
    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-1",
      size: '1024x1024',
      image: image
    )
  8. Define Structured Outputs using helper classes

    main

    The SDK provides helper classes to define JSON schemas for Structured Outputs and function calling. Instead of writing raw JSON schemas, you can inherit from OpenAI::BaseModel and use helpers like OpenAI::ArrayOf, OpenAI::EnumOf, and OpenAI::UnionOf to define your data structures.

    When using these models with client.responses.create, the text parameter can accept the class itself, and the resulting response will contain a .parsed attribute containing an instance of your model.

    # Define your schema using helper classes
    class Participant < OpenAI::BaseModel
      required :first_name, String
      required :last_name, String, nil?: true
      required :status, OpenAI::EnumOf[:confirmed, :unconfirmed, :tentative]
    end
    
    class CalendarEvent < OpenAI::BaseModel
      required :name, String
      required :date, String
      required :participants, OpenAI::ArrayOf[Participant]
    end
    
    client = OpenAI::Client.new
    
    response = client.responses.create(
      model: "gpt-5.2",
      input: [
        {role: :system, content: "Extract the event information."},
        {role: :user, content: "Alice and Lena are going to a science fair..."}
      ],
      text: CalendarEvent
    )
    
    # Access the parsed object
    response.output
      .flat_map { _1.content }
      .grep_v(OpenAI::Models::Responses::ResponseOutputRefusal)
      .each do |content|
        pp(content.parsed) # 'content.parsed' is an instance of CalendarEvent
      end
  9. Stream responses using the Responses API

    main

    Use client.responses.stream to receive a ResponseStream that implements Enumerable. You can iterate over the stream to handle real-time events. The stream is automatically cancelled when the block exits, but you can manually abort it by calling stream.close.

    To retrieve the full, accumulated response after the stream has been fully consumed, use stream.get_final_response.

    stream = client.responses.stream(
      input: "Tell me a story about programming",
      model: "gpt-4o"
    )
    
    stream.each do |event|
      case event
      when OpenAI::Streaming::ResponseTextDeltaEvent
        print(event.delta)
      end
    end
    
    # After consumption, get the full object
    final_response = stream.get_final_response
  10. Configure the Amazon Bedrock provider

    main

    You can use the standard OpenAI::Client with the Bedrock provider to access Amazon Bedrock's OpenAI-compatible API. The provider defaults to the endpoint https://bedrock-mantle.<region>.api.aws/v1.

    To use Bedrock, initialize the client with OpenAI::Providers.bedrock(region: "<region>"). You can override the endpoint using the base_url option or the AWS_BEDROCK_BASE_URL environment variable.

    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)
  11. Authenticate with Amazon Bedrock using AWS credentials

    main

    To use AWS-based authentication (SigV4), you must add the aws-sdk-core gem to your project. The provider signs requests with the service name bedrock-mantle.

    Authentication follows this priority:

    1. Explicit options passed to bedrock(...) (e.g., profile, access_key_id, credentials_provider).
    2. The AWS_BEARER_TOKEN_BEDROCK environment variable.
    3. The standard AWS credential chain.

    Note: Explicit bearer and AWS credential modes are mutually exclusive. If you want to skip bearer token detection and force AWS authentication, pass api_key: nil to the provider.

    # Add this to your Gemfile
    gem "aws-sdk-core", "~> 3"