Class: Kernai::Providers::Openai

Inherits:
Kernai::Provider show all
Defined in:
lib/kernai/providers/openai.rb

Overview

Kernai::Provider adapter for the OpenAI Chat Completions API.

Supports streaming, multimodal image input (when the model declares ‘:vision`), and reasoning effort via `GenerationOptions#thinking` which maps to OpenAI’s ‘reasoning_effort` parameter (honored by o1/o3/ gpt-5 reasoning models; silently ignored by others).

Zero runtime dependencies beyond the Ruby stdlib. Works against any OpenAI-compatible endpoint by passing a custom ‘api_url:`.

Constant Summary collapse

DEFAULT_API_URL =
'https://api.openai.com/v1/chat/completions'

Instance Method Summary collapse

Methods inherited from Kernai::Provider

#encode, #fallback_for

Constructor Details

#initialize(api_key: ENV.fetch('OPENAI_API_KEY', nil), api_url: DEFAULT_API_URL) ⇒ Openai

Returns a new instance of Openai.



21
22
23
24
25
# File 'lib/kernai/providers/openai.rb', line 21

def initialize(api_key: ENV.fetch('OPENAI_API_KEY', nil), api_url: DEFAULT_API_URL)
  super()
  @api_key = api_key
  @api_url = api_url
end

Instance Method Details

#call(messages:, model:, generation: nil, &block) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/kernai/providers/openai.rb', line 27

def call(messages:, model:, generation: nil, &block)
  uri = URI(@api_url)
  payload = build_payload(messages, model, stream: block_given?, generation: generation)

  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  response = http_post(uri, payload)

  unless response.is_a?(Net::HTTPSuccess)
    raise Kernai::ProviderError, "OpenAI API error #{response.code}: #{response.body}"
  end

  content, usage = if block
                     parse_stream(response.body, &block)
                   else
                     parse_response(response.body)
                   end

  Kernai::LlmResponse.new(
    content: content,
    latency_ms: elapsed_ms(started),
    prompt_tokens: usage['prompt_tokens'],
    completion_tokens: usage['completion_tokens'],
    total_tokens: usage['total_tokens']
  )
end

#encode_part(part, model:) ⇒ Object

OpenAI chat completions accept vision parts as ‘image_url` objects. We honour both URL-backed media and inline bytes (encoded as a data URI). Anything else falls back to the base class placeholder.



56
57
58
59
60
61
62
63
64
65
# File 'lib/kernai/providers/openai.rb', line 56

def encode_part(part, model:)
  return { 'type' => 'text', 'text' => part } if part.is_a?(String)
  return nil unless part.is_a?(Kernai::Media) && part.kind == :image && model.supports?(:vision)

  url = case part.source
        when :url then part.data
        when :path, :bytes then "data:#{part.mime_type};base64,#{part.to_base64}"
        end
  { 'type' => 'image_url', 'image_url' => { 'url' => url } }
end