Class: RubyLlmMesh::Router

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_llm_mesh/router.rb,
sig/ruby_llm_mesh.rbs

Constant Summary collapse

PROVIDER_MAP =
{
  openai: Providers::Openai,
  anthropic: Providers::Anthropic,
  local_node: Providers::LocalNode
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: RubyLlmMesh.configuration, circuit_breaker: self.class.circuit_breaker) ⇒ Router

Returns a new instance of Router.



24
25
26
27
# File 'lib/ruby_llm_mesh/router.rb', line 24

def initialize(config: RubyLlmMesh.configuration, circuit_breaker: self.class.circuit_breaker)
  @config = config
  @circuit_breaker = circuit_breaker
end

Class Method Details

.circuit_breakerObject



12
13
14
15
16
17
# File 'lib/ruby_llm_mesh/router.rb', line 12

def circuit_breaker
  @circuit_breaker ||= CircuitBreaker.new(
    failure_threshold: RubyLlmMesh.configuration.circuit_failure_threshold,
    reset_timeout: RubyLlmMesh.configuration.circuit_reset_timeout
  )
end

.reset_circuit_breaker!Object



19
20
21
# File 'lib/ruby_llm_mesh/router.rb', line 19

def reset_circuit_breaker!
  @circuit_breaker = nil
end

Instance Method Details

#complete(prompt:, providers: nil, fallback: nil, system: nil, model: nil, **options) ⇒ Object

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ruby_llm_mesh/router.rb', line 29

def complete(prompt:, providers: nil, fallback: nil, system: nil, model: nil, **options)
  raise ArgumentError, "prompt is required" if prompt.nil? || prompt.to_s.strip.empty?

  provider_list = Array(providers || @config.default_providers).map(&:to_sym)
  raise ArgumentError, "providers list cannot be empty" if provider_list.empty?

  use_fallback = fallback.nil? ? @config.fallback : fallback
  errors = {}
  attempted = 0

  provider_list.each_with_index do |provider_name, index|
    unless PROVIDER_MAP.key?(provider_name)
      errors[provider_name] = ProviderError.new("Unknown provider: #{provider_name}", provider: provider_name)
      next
    end

    unless @circuit_breaker.allow?(provider_name)
      errors[provider_name] = CircuitOpenError.new(
        "Circuit open for #{provider_name}",
        provider: provider_name
      )
      log(:warn, "Skipping #{provider_name} — circuit open")
      next
    end

    begin
      attempted += 1
      log(:info, "Routing to #{provider_name}")
      provider = PROVIDER_MAP[provider_name].new(@config)
      response = provider.complete(prompt: prompt, system: system, model: model, **options)
      @circuit_breaker.record_success(provider_name)
      return Response.new(
        content: response.content,
        provider: response.provider,
        model: response.model,
        usage: response.usage,
        raw: response.raw,
        latency_ms: response.latency_ms,
        fallback_used: index.positive?
      )
    rescue ProviderError => e
      @circuit_breaker.record_failure(provider_name)
      errors[provider_name] = e
      log(:error, "#{provider_name} failed: #{e.message}")
      break unless use_fallback
    end
  end

  raise AllProvidersFailedError, errors
end