Module: Protege::ProviderMixin

Included in:
Provider
Defined in:
lib/protege/extensions/provider_mixin.rb

Overview

Marker mixin for LLM provider extensions.

A concrete provider class subclasses Protege::Provider (which includes this module) and declares its symbolic identifier via the protege_id class macro:

class OpenRouterProvider < Protege::Provider
protege_id :openrouter

def initialize(api_key:); @api_key = api_key; end
def generate(request); ...; end
end

Providers are referenced by id (the symbol passed to protege_id). On include, the class is auto-tracked in Provider.registered; the registry uses this list to map a configured provider_id to its class without scanning ObjectSpace or requiring explicit registration calls.

The generation request/response value objects it exchanges live in the Inference::Provider namespace (+Inference::Provider::Request+, ::Response, …).

Contract:

def self.id;          end  # returns the Symbol declared above (provided by the DSL)
def generate(request); end # Request -> Response, raises a Protege::*Error on failure

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ void

This method returns an undefined value.

Extend the including class with the +protege_id+/+id+ DSL.

Parameters:

  • base (Class)

    the class that included Provider.



33
34
35
# File 'lib/protege/extensions/provider_mixin.rb', line 33

def included(base)
  base.extend(ClassMethods)
end

.registeredArray<Class>

List every concrete provider class.

Reads from Provider.descendants so the registry survives Zeitwerk reloads without a manual registration step.

Returns:

  • (Array<Class>)

    all provider subclasses currently loaded.



43
44
45
# File 'lib/protege/extensions/provider_mixin.rb', line 43

def registered
  Provider.descendants
end

Instance Method Details

#generate(_request) ⇒ Hash

Generate a response for request (abstract).

The provider contract is hash in, hash out: request is the standard (OpenAI-compatible) request hash — { messages:, tools: } — and the return is the response hash — { content:, tool_calls:, finish_reason: } (see the providers doc). The provider adds its own model/sampling/credentials from config, and raises a Protege::Error subclass on transport, auth, or parse failure. Providers never touch Protege's internal value types. Concrete providers must override this; the default body exists only to give a clear failure when one forgets to.

Parameters:

  • _request (Hash)

    the request hash (unused in the default).

Returns:

  • (Hash)

    never returns; always raises.

Raises:

  • (NotImplementedError)

    always, naming the class that failed to implement #generate.



60
61
62
63
# File 'lib/protege/extensions/provider_mixin.rb', line 60

def generate(_request)
  raise NotImplementedError,
        "#{self.class} must implement #generate(request_hash) -> response hash"
end

#generate_stream(request) {|chunk| ... } ⇒ Hash

Generate a response, yielding incremental chunks as they arrive.

The default implementation does not truly stream: it calls #generate and yields the whole text as one :text chunk (only when non-blank), then returns the response hash. Providers with native streaming should override this to yield finer-grained chunks.

Parameters:

  • request (Hash)

    the request hash.

Yields:

  • (chunk)

    each streamed chunk.

Yield Parameters:

  • chunk (Hash)

    { type: :text | :thinking | :tool_call, content: String }.

Returns:

  • (Hash)

    the final, complete response hash.



75
76
77
78
79
# File 'lib/protege/extensions/provider_mixin.rb', line 75

def generate_stream(request, &)
  response = generate(request)
  yield({ type: :text, content: response[:content] }) if response[:content].present?
  response
end

#modelString?

The model id this provider targets, read from its own config.providers slice (+:model+). Public so the harness can record which model produced a turn (for tracing); resolved per call, so a future persona-level override is reflected here. Providers may treat a missing model as a hard error at generation time — that enforcement is theirs, not this soft reader's.

Returns:

  • (String, nil)

    the configured model id, or nil when unset



87
88
89
# File 'lib/protege/extensions/provider_mixin.rb', line 87

def model
  Protege.configuration.provider_options(self.class.id)[:model]
end

#settingsHash

The sampling settings this provider applies beyond the model — the generation knobs (temperature, max tokens, …) that shape the output and so matter for training/reproducibility. The default is empty; a provider overrides this to expose its own knobs. Captured alongside #model in a trace.

Returns:

  • (Hash)

    the sampling settings (empty by default)



96
97
98
# File 'lib/protege/extensions/provider_mixin.rb', line 96

def settings
  {}
end