Module: RubynCode::LLM::ModelRouter
- Defined in:
- lib/rubyn_code/llm/model_router.rb
Overview
Routes tasks to appropriate model tiers based on complexity. Integrates with the multi-provider adapter layer and reads per-provider model tier overrides from config.yml.
Users can configure tier models per provider in config.yml:
providers:
anthropic:
env_key: ANTHROPIC_API_KEY
models:
cheap: claude-haiku-4-5
mid: claude-sonnet-4-6
top: claude-opus-4-6
openai:
env_key: OPENAI_API_KEY
models:
cheap: gpt-5.4-nano
mid: gpt-5.4-mini
top: gpt-5.4
Constant Summary collapse
- TASK_TIERS =
rubocop:disable Metrics/ModuleLength – tier routing with provider integration
{ cheap: %i[ file_search spec_summary schema_lookup format_code git_operations memory_retrieval context_summary chatting explore ].freeze, mid: %i[ generate_specs simple_refactor code_review documentation bug_fix ].freeze, top: %i[ architecture complex_refactor security_review performance planning ].freeze }.freeze
- TIER_DEFAULTS =
Hardcoded fallbacks when no config override exists.
{ cheap: [ %w[anthropic claude-haiku-4-5], %w[openai gpt-5.4-nano] ].freeze, mid: [ %w[anthropic claude-sonnet-4-6], %w[openai gpt-5.4-mini] ].freeze, top: [ %w[anthropic claude-opus-4-6], %w[openai gpt-5.4] ].freeze }.freeze
- COST_MULTIPLIERS =
{ cheap: 0.07, mid: 0.20, top: 1.0 }.freeze
- DEFAULT_COST_MULTIPLIER =
0.20- MESSAGE_PATTERNS =
[ [/\b(architect|design|restructure|multi.?file)\b/, :architecture], [/\b(security|vulnerab|audit|owasp)\b/, :security_review], [/\b(n\+1|performance|slow|optimize|query)\b/, :performance], [/\b(spec|test|rspec)\b/, :generate_specs], [/\b(fix|bug|error|broken)\b/, :bug_fix], [/\b(refactor|extract|rename|move)\b/, :simple_refactor], [/\b(find|where|search|locate)\b/, :file_search], [/\b(doc|readme|comment|explain)\b/, :documentation] ].freeze
Class Method Summary collapse
-
.cost_multiplier(tier) ⇒ Object
Returns cost estimate multiplier for a tier relative to top tier.
-
.detect_task(message, recent_tools: []) ⇒ Object
Detect task type from a user message and recent tool calls.
-
.model_for(task_type, available_models: []) ⇒ Object
Returns just the model name for a task type (backward-compatible).
-
.resolve(task_type, client: nil) ⇒ Hash
Resolve the best [provider, model] pair for a task type.
-
.tier_for(task_type) ⇒ Object
Determine the appropriate model tier for a task.
Class Method Details
.cost_multiplier(tier) ⇒ Object
Returns cost estimate multiplier for a tier relative to top tier.
139 140 141 |
# File 'lib/rubyn_code/llm/model_router.rb', line 139 def cost_multiplier(tier) COST_MULTIPLIERS.fetch(tier, DEFAULT_COST_MULTIPLIER) end |
.detect_task(message, recent_tools: []) ⇒ Object
Detect task type from a user message and recent tool calls.
134 135 136 |
# File 'lib/rubyn_code/llm/model_router.rb', line 134 def detect_task(, recent_tools: []) () || detect_from_tools(recent_tools) || :chatting end |
.model_for(task_type, available_models: []) ⇒ Object
Returns just the model name for a task type (backward-compatible). – config + defaults search
120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/rubyn_code/llm/model_router.rb', line 120 def model_for(task_type, available_models: []) tier = tier_for(task_type) candidates = build_candidate_list(tier) if available_models.any? candidates.each do |pair| return pair[1] if available_models.any? { |m| m.start_with?(pair[1]) } end end candidates.first&.at(1) || TIER_DEFAULTS[tier].first[1] end |
.resolve(task_type, client: nil) ⇒ Hash
Resolve the best [provider, model] pair for a task type. Checks per-provider config overrides first, then falls back to TIER_DEFAULTS.
– multi-source fallback chain
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/rubyn_code/llm/model_router.rb', line 88 def resolve(task_type, client: nil) tier = tier_for(task_type) active = active_provider # 1. Config overrides — prefer the active provider's config configured = config_tier_models(tier) active_cfg = configured.find { |p, _| p == active } return pair_to_hash(active_cfg) if active_cfg # 2. Any other configured provider configured.each do |pair| return pair_to_hash(pair) if client.nil? || provider_available?(pair[0]) end # 3. Hardcoded defaults — prefer the active provider defaults = TIER_DEFAULTS[tier] active_default = defaults.find { |p, _| p == active } return pair_to_hash(active_default) if active_default # 4. Active provider not in defaults (e.g. minimax) — use their configured model for all tiers return { provider: active, model: active_model } if provider_available?(active) # 5. Any available default defaults.each do |pair| return pair_to_hash(pair) if client.nil? || provider_available?(pair[0]) end pair_to_hash(defaults.first) end |
.tier_for(task_type) ⇒ Object
Determine the appropriate model tier for a task.
73 74 75 76 77 78 |
# File 'lib/rubyn_code/llm/model_router.rb', line 73 def tier_for(task_type) TASK_TIERS.each do |tier, tasks| return tier if tasks.include?(task_type.to_sym) end :mid end |