Class: Kernai::Provider

Inherits:
Object
  • Object
show all
Defined in:
lib/kernai/provider.rb

Overview

Abstract base for every LLM adapter. Concrete providers implement ‘#call` to talk to their vendor, and optionally override `#encode_part` to describe how a Media part should be serialised into the vendor’s multimodal format.

The default ‘#encode_part` only knows how to pass through strings. Every other part (including Media) is handed to `#fallback_for`, which returns a plain-text placeholder. This means a text-only provider never crashes on multimodal input: it just sees `“[image: image/png]”` in the conversation — lossy but safe, and the instruction builder already prevented the agent from proposing skills it couldn’t satisfy.

Instance Method Summary collapse

Instance Method Details

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

Talk to the vendor.

Parameters:

  • messages (Array<Hash>)
    content: Array<String|Media>
  • model (Kernai::Model)

    the selected model with its capabilities

  • generation (Kernai::GenerationOptions, nil) (defaults to: nil)

    provider-agnostic knobs (temperature, max_tokens, top_p, thinking, vendor extras). Providers inspect the fields they support and ignore the rest. Nil is treated as an empty GenerationOptions.

  • block (Proc)

    optional streaming callback (yields text chunks)

Returns:

Raises:

  • (NotImplementedError)


26
27
28
# File 'lib/kernai/provider.rb', line 26

def call(messages:, model:, generation: nil, &block)
  raise NotImplementedError, "#{self.class}#call must be implemented"
end

#encode(parts, model:) ⇒ Object

Encode a list of parts into the vendor’s native shape. Walks each part through ‘#encode_part`; anything returning nil is degraded to a text placeholder via `#fallback_for`, and that placeholder is re-encoded so the subclass can wrap it in whatever text envelope the vendor expects (a bare String, `“text”, text: …`, etc). Callers never see a mixed-shape array.



36
37
38
39
40
41
42
43
44
# File 'lib/kernai/provider.rb', line 36

def encode(parts, model:)
  Array(parts).map do |p|
    encoded = encode_part(p, model: model)
    next encoded unless encoded.nil?

    placeholder = fallback_for(p)
    encode_part(placeholder, model: model) || placeholder
  end
end

#encode_part(part, model:) ⇒ Object

Override hook — return the vendor-native shape for the given part, or nil to let the base class emit a textual fallback. The default implementation passes strings through and declines everything else.



49
50
51
52
53
# File 'lib/kernai/provider.rb', line 49

def encode_part(part, model:) # rubocop:disable Lint/UnusedMethodArgument
  return part if part.is_a?(String)

  nil
end

#fallback_for(part) ⇒ Object

Lossy placeholder used whenever a part cannot be encoded for the current model. Providers almost never need to override this — the shape is intentionally provider-agnostic so it stays readable in the conversation history regardless of the vendor on the other side.



59
60
61
62
63
# File 'lib/kernai/provider.rb', line 59

def fallback_for(part)
  return part.to_s unless part.is_a?(Media)

  "[#{part.kind} unavailable: #{part.mime_type}]"
end