Class: RubyPi::LLM::BaseProvider

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_pi/llm/base_provider.rb

Overview

Abstract base class that defines the contract every LLM provider must fulfill. Provides built-in retry logic with exponential backoff for transient errors and a unified #complete interface for both synchronous and streaming completions.

Subclasses MUST implement:

  • #perform_complete(messages:, tools:, stream:, &block)

  • #model_name

  • #provider_name

Examples:

Subclass implementation

class MyProvider < RubyPi::LLM::BaseProvider
  def model_name = "my-model"
  def provider_name = :my_provider

  private
  def perform_complete(messages:, tools:, stream:, &block)
    # Make HTTP request and return RubyPi::LLM::Response
  end
end

Direct Known Subclasses

Anthropic, Fallback, Gemini, OpenAI

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: nil, max_retries: nil, retry_base_delay: nil, retry_max_delay: nil) ⇒ BaseProvider

Initializes the base provider with retry configuration.

Parameters:

  • config (RubyPi::Configuration, nil) (defaults to: nil)

    optional per-agent config override. When provided, the provider uses this config instead of the global RubyPi.configuration singleton. This enables per-agent API keys, timeouts, and retry settings.

  • max_retries (Integer, nil) (defaults to: nil)

    override max retries (defaults to config)

  • retry_base_delay (Float, nil) (defaults to: nil)

    override base delay (defaults to config)

  • retry_max_delay (Float, nil) (defaults to: nil)

    override max delay (defaults to config)



51
52
53
54
55
56
# File 'lib/ruby_pi/llm/base_provider.rb', line 51

def initialize(config: nil, max_retries: nil, retry_base_delay: nil, retry_max_delay: nil)
  @config = config || RubyPi.configuration
  @max_retries = max_retries || @config.max_retries
  @retry_base_delay = retry_base_delay || @config.retry_base_delay
  @retry_max_delay = retry_max_delay || @config.retry_max_delay
end

Instance Attribute Details

#max_retriesInteger (readonly)

Returns maximum number of retry attempts.

Returns:

  • (Integer)

    maximum number of retry attempts



34
35
36
# File 'lib/ruby_pi/llm/base_provider.rb', line 34

def max_retries
  @max_retries
end

#retry_base_delayFloat (readonly)

Returns base delay in seconds for exponential backoff.

Returns:

  • (Float)

    base delay in seconds for exponential backoff



37
38
39
# File 'lib/ruby_pi/llm/base_provider.rb', line 37

def retry_base_delay
  @retry_base_delay
end

#retry_max_delayFloat (readonly)

Returns maximum delay in seconds between retries.

Returns:

  • (Float)

    maximum delay in seconds between retries



40
41
42
# File 'lib/ruby_pi/llm/base_provider.rb', line 40

def retry_max_delay
  @retry_max_delay
end

Instance Method Details

#complete(messages:, tools: [], stream: false) {|event| ... } ⇒ RubyPi::LLM::Response

Sends a completion request to the LLM provider with automatic retry logic for transient errors. When stream is true and a block is given, yields StreamEvent objects incrementally as they arrive.

Parameters:

  • messages (Array<Hash>)

    conversation messages, each with :role and :content

  • tools (Array<Hash>) (defaults to: [])

    tool/function definitions for the model

  • stream (Boolean) (defaults to: false)

    whether to enable streaming mode

Yields:

  • (event)

    yields StreamEvent objects when streaming

Yield Parameters:

Returns:

Raises:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ruby_pi/llm/base_provider.rb', line 72

def complete(messages:, tools: [], stream: false, &block)
  attempt = 0

  begin
    attempt += 1
    perform_complete(messages: messages, tools: tools, stream: stream, &block)
  rescue RubyPi::AuthenticationError
    # Authentication errors are not retryable — raise immediately
    raise
  rescue RubyPi::RateLimitError, RubyPi::ApiError, RubyPi::TimeoutError => e
    # Retry up to max_retries times AFTER the initial attempt.
    # With max_retries: 3, attempt goes 1 (initial), 2, 3, 4 — the condition
    # `attempt <= @max_retries` allows retries on attempts 1..3, so we get
    # 3 retries + 1 initial = 4 total attempts. Previously used `< @max_retries`
    # which was off-by-one (only 2 retries with max_retries: 3).
    if attempt <= @max_retries
      delay = calculate_backoff(attempt)
      log_retry(attempt, delay, e)
      sleep(delay)
      retry
    else
      raise
    end
  end
end

#model_nameString

Returns the model name used by this provider instance. Subclasses MUST override this method.

Returns:

  • (String)

    the model identifier

Raises:



103
104
105
# File 'lib/ruby_pi/llm/base_provider.rb', line 103

def model_name
  raise RubyPi::AbstractMethodError, :model_name
end

#provider_nameSymbol

Returns the provider identifier. Subclasses MUST override this method.

Returns:

  • (Symbol)

    the provider identifier (e.g., :gemini, :anthropic, :openai)

Raises:



112
113
114
# File 'lib/ruby_pi/llm/base_provider.rb', line 112

def provider_name
  raise RubyPi::AbstractMethodError, :provider_name
end