Skip to main content
The OpenAI Ruby SDK provides helper classes to define JSON schemas for structured outputs and function calling. These helpers make it easy to create type-safe, validated responses from the API.

BaseModel

Use OpenAI::BaseModel to define structured response schemas:
require "openai"

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

Field Options

class Location < OpenAI::BaseModel
  # Basic required field
  required :address, String

  # With documentation
  required :city, String, doc: "City name"

  # Nullable field
  required :postal_code, String, nil?: true
end
The optional method is not supported for structured output APIs. Use required with nil?: true instead.

ArrayOf

Define array types with optional documentation:
class CalendarEvent < OpenAI::BaseModel
  # Array of Participant objects
  required :participants, OpenAI::ArrayOf[Participant]

  # Array with documentation
  required :optional_participants,
           OpenAI::ArrayOf[Participant, doc: "who might not show up"],
           nil?: true
end

ArrayOf Examples

class Schedule < OpenAI::BaseModel
  # Array of strings
  required :days, OpenAI::ArrayOf[String]

  # Array of integers
  required :hours, OpenAI::ArrayOf[Integer]
end

EnumOf

Define enumerated types with fixed values:
class Participant < OpenAI::BaseModel
  # Symbol enum
  required :status, OpenAI::EnumOf[:confirmed, :unconfirmed, :tentative]
end

class Product < OpenAI::BaseModel
  # Integer enum
  required :priority, OpenAI::EnumOf[1, 2, 3, 4, 5]
end

Supported Enum Types

  • Symbols: :confirmed, :pending, :cancelled
  • Integers: 1, 2, 3
  • Floats: 1.0, 2.5, 3.0
  • Booleans: true, false
  • Null: nil

UnionOf

Define fields that can be one of multiple types:
class CalendarEvent < OpenAI::BaseModel
  # Location can be either a String or a Location object
  required :location, OpenAI::UnionOf[String, Location], nil?: true
end

class Response < OpenAI::BaseModel
  # Value can be string, integer, or array of strings
  required :value, OpenAI::UnionOf[String, Integer, OpenAI::ArrayOf[String]]
end

Complete Example

Here’s a full example showing how to define and use structured outputs:
require "openai"

# Define the schema
class Location < OpenAI::BaseModel
  required :address, String
  required :city, String, doc: "City name"
  required :postal_code, String, nil?: true
end

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]
  required :optional_participants,
           OpenAI::ArrayOf[Participant, doc: "who might not show up"],
           nil?: true
  required :is_virtual, OpenAI::Boolean
  required :location,
           OpenAI::UnionOf[String, Location],
           nil?: true,
           doc: "Event location"
end

# Use with Responses API
client = OpenAI::Client.new

response = client.responses.create(
  model: "gpt-4o-2024-08-06",
  input: [
    {role: :system, content: "Extract the event information."},
    {
      role: :user,
      content: <<~CONTENT
        Alice Shah and Lena are going to a science fair on Friday at 123 Main St. in San Diego.
        They have also invited Jasper Vellani and Talia Groves - Jasper has not responded and Talia said she is thinking about it.
      CONTENT
    }
  ],
  text: CalendarEvent
)

response
  .output
  .flat_map { _1.content }
  .grep_v(OpenAI::Models::Responses::ResponseOutputRefusal)
  .each do |content|
    # parsed is an instance of CalendarEvent
    pp(content.parsed)
  end

Usage with Chat Completions

You can also use structured outputs with the Chat Completions API:
chat_completion = client.chat.completions.create(
  model: "gpt-4o-2024-08-06",
  messages: [
    {role: :system, content: "Extract the event information."},
    {
      role: :user,
      content: "Alice and Bob are meeting on Friday."
    }
  ],
  response_format: CalendarEvent
)

chat_completion
  .choices
  .reject { _1.message.refusal }
  .each do |choice|
    # parsed is an instance of CalendarEvent
    pp(choice.message.parsed)
  end

Function Calling

Use BaseModel to define function schemas:
class GetWeather < OpenAI::BaseModel
  required :location, String, doc: "City and country e.g. Bogotá, Colombia"
end

client = OpenAI::Client.new

response = client.responses.create(
  model: "gpt-4o-2024-08-06",
  input: [
    {
      role: :user,
      content: "What's the weather like in Paris today?"
    }
  ],
  tools: [GetWeather]
)

response
  .output
  .each do |output|
    # parsed is an instance of GetWeather
    pp(output.parsed)
  end

Raw JSON Schema Equivalent

The helpers automatically generate JSON schemas. This CalendarEvent class:
class CalendarEvent < OpenAI::BaseModel
  required :name, String
  required :date, String
  required :participants, OpenAI::ArrayOf[Participant]
end
Is equivalent to this raw JSON schema:
response = client.responses.create(
  model: "gpt-4o-2024-08-06",
  input: [...],
  text: {
    format: {
      type: :json_schema,
      name: "CalendarEvent",
      strict: true,
      schema: {
        type: "object",
        properties: {
          name: {type: "string"},
          date: {type: "string"},
          participants: {
            type: "array",
            items: {
              type: "object",
              properties: {
                first_name: {type: "string"},
                last_name: {type: %w[string null]},
                status: {type: "string", enum: %w[confirmed unconfirmed tentative]}
              },
              required: %w[first_name last_name status],
              additionalProperties: false
            }
          }
        },
        required: %w[name date participants],
        additionalProperties: false
      }
    }
  }
)

Best Practices

1

Use descriptive class names

Name your BaseModel classes clearly to indicate what they represent (e.g., CalendarEvent, UserProfile).
2

Add documentation to fields

Use the doc: parameter to provide context for the AI model about what each field represents.
3

Prefer nil?: true over optional

For structured outputs, always use required with nil?: true for optional fields instead of the optional method.
4

Handle refusals

Always check for and filter out refusal responses when processing structured outputs.

Type Helpers Reference

HelperPurposeExample
BaseModelDefine object schemasclass User < OpenAI::BaseModel
ArrayOfDefine array typesOpenAI::ArrayOf[String]
EnumOfDefine enumerated valuesOpenAI::EnumOf[:pending, :complete]
UnionOfDefine union typesOpenAI::UnionOf[String, Integer]
BooleanBoolean typeOpenAI::Boolean

Build docs developers (and LLMs) love