Module: Boxcars::OpenAIClient

Defined in:
lib/boxcars/openai_client.rb

Overview

Centralized factory for OpenAI-compatible clients used by multiple engines (OpenAI, Groq, Ollama, Gemini-compatible endpoints, etc.).

Defined Under Namespace

Modules: ClientMethods

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.official_client_builderObject

Returns the value of attribute official_client_builder.



32
33
34
# File 'lib/boxcars/openai_client.rb', line 32

def official_client_builder
  @official_client_builder
end

Class Method Details

.build(access_token:, uri_base: nil, organization_id: nil, log_errors: nil) ⇒ Object



43
44
45
46
47
48
49
50
51
52
# File 'lib/boxcars/openai_client.rb', line 43

def self.build(access_token:, uri_base: nil, organization_id: nil, log_errors: nil)
  raw_client = build_official_openai_client(
    access_token:,
    uri_base:,
    organization_id:,
    log_errors:
  )

  decorate_client(raw_client)
end

.call_chat(client, parameters) ⇒ Object


Request helpers used by ClientMethods




108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/boxcars/openai_client.rb', line 108

def self.call_chat(client, parameters)
  raise Boxcars::ConfigurationError, "Official OpenAI client does not expose #chat" unless client.respond_to?(:chat)

  chat_resource = client.chat
  return chat_resource if chat_resource.is_a?(Hash)

  if chat_resource.respond_to?(:completions)
    call_create(chat_resource.completions, parameters)
  elsif chat_resource.respond_to?(:create)
    call_create(chat_resource, parameters)
  else
    raise Boxcars::ConfigurationError, "Official OpenAI client chat resource does not support #create"
  end
end

.call_completions(client, parameters) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# File 'lib/boxcars/openai_client.rb', line 123

def self.call_completions(client, parameters)
  unless client.respond_to?(:completions)
    raise Boxcars::ConfigurationError, "Official OpenAI client does not expose #completions"
  end

  completions_resource = client.completions
  return completions_resource if completions_resource.is_a?(Hash)

  call_create(completions_resource, parameters)
end

.call_embeddings(client, parameters) ⇒ Object



142
143
144
145
146
147
148
149
150
151
# File 'lib/boxcars/openai_client.rb', line 142

def self.call_embeddings(client, parameters)
  unless client.respond_to?(:embeddings)
    raise Boxcars::ConfigurationError, "Official OpenAI client does not expose #embeddings"
  end

  embeddings_resource = client.embeddings
  return embeddings_resource if embeddings_resource.is_a?(Hash)

  call_create(embeddings_resource, parameters)
end

.call_responses(client, parameters) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/boxcars/openai_client.rb', line 134

def self.call_responses(client, parameters)
  unless client.respond_to?(:responses)
    raise StandardError, "OpenAI Responses API not supported by the official OpenAI client."
  end

  call_create(client.responses, parameters)
end

.decorate_client(client) ⇒ Object



170
171
172
173
174
175
176
# File 'lib/boxcars/openai_client.rb', line 170

def self.decorate_client(client)
  client.extend(ClientMethods)
  client
rescue TypeError
  raise Boxcars::ConfigurationError,
        "Official OpenAI client object must be extensible or already implement *_create methods."
end

.install_official_client_builder!(client_class: nil) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/boxcars/openai_client.rb', line 70

def self.install_official_client_builder!(client_class: nil)
  ensure_openai_dependency!
  klass = client_class || detect_official_client_class
  return false unless klass

  self.official_client_builder = lambda do |access_token:, uri_base:, organization_id:, log_errors:|
    build_with_official_client_class(
      klass:,
      access_token:,
      uri_base:,
      organization_id:,
      log_errors:
    )
  end
  true
end

.normalize_response(obj) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/boxcars/openai_client.rb', line 153

def self.normalize_response(obj)
  case obj
  when Hash
    obj.each_with_object({}) do |(k, v), memo|
      memo[k.to_s] = normalize_response(v)
    end
  when Array
    obj.map { |v| normalize_response(v) }
  else
    if obj.respond_to?(:to_h)
      normalize_response(obj.to_h)
    else
      obj
    end
  end
end

.validate_client_configuration!Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/boxcars/openai_client.rb', line 54

def self.validate_client_configuration!
  ensure_openai_dependency!
  builder = official_client_builder || Boxcars.configuration.openai_official_client_builder
  return true if builder
  return true if detect_official_client_class

  if official_client_requires_native?
    raise Boxcars::ConfigurationError,
          "Official OpenAI client path is configured in native-only mode, but no native official OpenAI client was detected."
  end

  raise Boxcars::ConfigurationError,
        "Official OpenAI client path selected but no official client builder is configured " \
        "and no compatible OpenAI::Client was detected."
end