Module: RubyLLM::Agents::Routing

Defined in:
lib/ruby_llm/agents/routing.rb,
lib/ruby_llm/agents/routing/result.rb,
lib/ruby_llm/agents/routing/class_methods.rb

Overview

Adds classification & routing capabilities to any BaseAgent.

Include this module in a BaseAgent subclass to get:

  • ‘route` DSL for defining classification categories

  • ‘default_route` for fallback classification

  • Auto-generated system/user prompts from route definitions

  • Structured output parsing to return a RoutingResult

All existing BaseAgent features (caching, reliability, retries, fallback models, instrumentation, multi-tenancy) work unchanged.

Examples:

Class-based router

class SupportRouter < RubyLLM::Agents::BaseAgent
  include RubyLLM::Agents::Routing

  model "gpt-4o-mini"
  temperature 0.0

  route :billing,  "Billing, charges, refunds"
  route :technical, "Bugs, errors, crashes"
  default_route :general
end

result = SupportRouter.call(message: "I was charged twice")
result.route  # => :billing

Inline classification

route = RubyLLM::Agents::Routing.classify(
  message: "I was charged twice",
  routes: { billing: "Billing issues", technical: "Tech issues" },
  default: :general
)
# => :billing

Defined Under Namespace

Modules: ClassMethods Classes: RoutingResult

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.classify(message:, routes:, default: :general, model: "gpt-4o-mini", **options) ⇒ Symbol

Classify a message without defining a router class.

Creates an anonymous BaseAgent subclass with Routing included, calls it, and returns just the route symbol.

Parameters:

  • message (String)

    The message to classify

  • routes (Hash{Symbol => String})

    Route names to descriptions

  • default (Symbol) (defaults to: :general)

    Default route (:general)

  • model (String) (defaults to: "gpt-4o-mini")

    LLM model to use (“gpt-4o-mini”)

  • options (Hash)

    Extra options passed to .call

Returns:

  • (Symbol)

    The classified route name



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/ruby_llm/agents/routing.rb', line 63

def self.classify(message:, routes:, default: :general, model: "gpt-4o-mini", **options)
  router_model = model
  router = Class.new(BaseAgent) do
    include Routing

    self.model router_model
    temperature 0.0

    routes.each do |name, desc|
      route name, desc
    end
    default_route default
  end

  result = router.call(message: message, **options)
  result.route
end

.included(base) ⇒ Object



43
44
45
46
47
48
49
50
# File 'lib/ruby_llm/agents/routing.rb', line 43

def self.included(base)
  unless base < BaseAgent
    raise ArgumentError, "#{base} must inherit from RubyLLM::Agents::BaseAgent to include Routing"
  end

  base.extend(ClassMethods)
  base.param(:message, required: false)
end

Instance Method Details

#auto_delegate?Boolean

Whether auto-delegation to the mapped agent is enabled for this call. Defaults to true. Pass ‘auto_delegate: false` to receive a classification-only RoutingResult with `delegated? == false` and `agent_class` set so the caller can invoke it manually.

Returns:

  • (Boolean)


166
167
168
# File 'lib/ruby_llm/agents/routing.rb', line 166

def auto_delegate?
  @options.fetch(:auto_delegate, true)
end

#build_result(content, response, context) ⇒ Object

Override build_result to return a RoutingResult. Auto-delegates to the mapped agent when the route has an ‘agent:` mapping, unless the caller opts out with `auto_delegate: false`.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ruby_llm/agents/routing.rb', line 145

def build_result(content, response, context)
  base = super

  agent_class = content[:agent_class]
  if agent_class && auto_delegate?
    content[:delegated_result] = if @delegation_stream_block
      agent_class.call(**delegation_params, &@delegation_stream_block)
    else
      agent_class.call(**delegation_params)
    end
  end

  RoutingResult.new(base_result: base, route_data: content)
end

#call(&block) ⇒ Object

Override call to capture the caller’s stream block so it can be forwarded to the delegated agent. Without this, chunks from the delegated agent are swallowed because build_result has no access to the original block.



122
123
124
125
# File 'lib/ruby_llm/agents/routing.rb', line 122

def call(&block)
  @delegation_stream_block = block
  super
end

#delegation_paramsHash

Builds params to forward to the delegated agent. Forwards original message and custom params, excludes routing internals.

Returns:

  • (Hash)

    Params for the delegated agent



174
175
176
177
178
179
# File 'lib/ruby_llm/agents/routing.rb', line 174

def delegation_params
  forward = @options.except(:dry_run, :skip_cache, :debug, :stream_events, :auto_delegate)
  forward[:_parent_execution_id] = @parent_execution_id if @parent_execution_id
  forward[:_root_execution_id] = @root_execution_id if @root_execution_id
  forward
end

#process_response(response) ⇒ Object

Override process_response to parse the route from LLM output.



128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ruby_llm/agents/routing.rb', line 128

def process_response(response)
  raw = response.content.to_s.strip.downcase.gsub(/[^a-z0-9_]/, "")
  route_name = raw.to_sym

  valid_routes = self.class.routes.keys
  route_name = self.class.default_route_name unless valid_routes.include?(route_name)

  {
    route: route_name,
    agent_class: self.class.routes.dig(route_name, :agent),
    raw_response: response.content.to_s.strip
  }
end

#routing_categories_textString

Helper to get formatted route categories for use in custom prompts.

Returns:

  • (String)

    Formatted list of route categories



102
103
104
105
106
# File 'lib/ruby_llm/agents/routing.rb', line 102

def routing_categories_text
  self.class.routes.map do |name, config|
    "- #{name}: #{config[:description]}"
  end.join("\n")
end

#routing_system_promptString

Helper to get the auto-generated system prompt for routing. Use this in custom system_prompt overrides to include route definitions.

Returns:

  • (String)

    The auto-generated routing system prompt



85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ruby_llm/agents/routing.rb', line 85

def routing_system_prompt
  default = self.class.default_route_name

  <<~PROMPT.strip
    You are a message classifier. Classify the user's message into exactly one of the following categories:

    #{routing_categories_text}

    If none of the categories clearly match, classify as: #{default}

    Respond with ONLY the category name, nothing else.
  PROMPT
end

#system_promptObject

Auto-generated system_prompt (used if subclass doesn’t override).



109
110
111
# File 'lib/ruby_llm/agents/routing.rb', line 109

def system_prompt
  super || routing_system_prompt
end

#user_promptObject

Auto-generated user_prompt from the :message param.



114
115
116
# File 'lib/ruby_llm/agents/routing.rb', line 114

def user_prompt
  @ask_message || options[:message] || super
end