Class: RubyLLM::SemanticRouter::Router
- Inherits:
-
Object
- Object
- RubyLLM::SemanticRouter::Router
- Defined in:
- lib/rubyllm/semantic_router/router.rb
Overview
Main router class that routes messages to specialized agents
Defined Under Namespace
Classes: InMemoryExample
Instance Attribute Summary collapse
-
#agents ⇒ Object
readonly
Returns the value of attribute agents.
-
#current_agent ⇒ Object
readonly
Returns the value of attribute current_agent.
-
#embedding_cache ⇒ Object
readonly
Returns the value of attribute embedding_cache.
-
#last_routing_decision ⇒ Object
readonly
Returns the value of attribute last_routing_decision.
Instance Method Summary collapse
-
#add_example(text, agent:) ⇒ self
Add a routing example.
-
#agent(name) ⇒ Object
Get an agent config by name.
-
#agent_names ⇒ Object
Get the names of all registered agents.
-
#ask(message) {|chunk| ... } ⇒ RubyLLM::Message
Send a message to the router and get a response.
-
#ask_batch(messages) ⇒ Array<RoutingDecision>
Route multiple messages and return their routing decisions Useful for batch analysis or pre-routing without conversation.
-
#clear_examples! ⇒ Object
Clear all routing examples.
-
#current_chat ⇒ Object
Get the current chat object.
-
#debug_routing(message) ⇒ Hash
Get detailed routing info for debugging.
-
#examples ⇒ Object
Get all routing examples.
-
#import_examples(examples) ⇒ self
Import multiple routing examples at once (batch embedding).
-
#initialize(agents:, default_agent:, fallback: nil, similarity_threshold: nil, embedding_model: nil, k_neighbors: nil, scope: nil, strategy: nil, examples: nil, find_examples: nil, max_words: nil, logger: nil, cache_ttl: nil, max_retries: nil, retry_base_delay: nil) ⇒ Router
constructor
A new instance of Router.
-
#match(message) ⇒ RoutingDecision
Preview routing without sending the message.
-
#messages ⇒ Object
Get all messages in the conversation.
-
#on(event, &block) ⇒ Object
Register event callbacks.
-
#switch_to(agent_name) ⇒ Object
Manually switch to a specific agent.
-
#with_examples(source) ⇒ Object
Use an external examples source (e.g., ActiveRecord model).
Constructor Details
#initialize(agents:, default_agent:, fallback: nil, similarity_threshold: nil, embedding_model: nil, k_neighbors: nil, scope: nil, strategy: nil, examples: nil, find_examples: nil, max_words: nil, logger: nil, cache_ttl: nil, max_retries: nil, retry_base_delay: nil) ⇒ Router
Returns a new instance of Router.
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/rubyllm/semantic_router/router.rb', line 46 def initialize( agents:, default_agent:, fallback: nil, similarity_threshold: nil, embedding_model: nil, k_neighbors: nil, scope: nil, strategy: nil, examples: nil, find_examples: nil, max_words: nil, logger: nil, cache_ttl: nil, max_retries: nil, retry_base_delay: nil ) @agents = normalize_agents(agents) @default_agent = default_agent.to_sym @current_agent = @default_agent @strategy = strategy || Strategies::Semantic.new @examples = examples || [] @scope = scope @find_examples = find_examples validate_default_agent! global_config = SemanticRouter.configuration || Configuration.new @logger = logger || global_config.logger @max_retries = max_retries || global_config.max_retries @retry_base_delay = retry_base_delay || global_config.retry_base_delay # Set up embedding cache if TTL is configured ttl = cache_ttl || global_config.cache_ttl @embedding_cache = ttl ? EmbeddingCache.new(ttl: ttl) : nil @config = build_config( embedding_model: , similarity_threshold: similarity_threshold, k_neighbors: k_neighbors, fallback: fallback, max_words: max_words ) @chat = nil @last_routing_decision = nil log(:debug, "Router initialized with agents: #{@agents.keys.join(', ')}") end |
Instance Attribute Details
#agents ⇒ Object (readonly)
Returns the value of attribute agents.
41 42 43 |
# File 'lib/rubyllm/semantic_router/router.rb', line 41 def agents @agents end |
#current_agent ⇒ Object (readonly)
Returns the value of attribute current_agent.
41 42 43 |
# File 'lib/rubyllm/semantic_router/router.rb', line 41 def current_agent @current_agent end |
#embedding_cache ⇒ Object (readonly)
Returns the value of attribute embedding_cache.
41 42 43 |
# File 'lib/rubyllm/semantic_router/router.rb', line 41 def @embedding_cache end |
#last_routing_decision ⇒ Object (readonly)
Returns the value of attribute last_routing_decision.
41 42 43 |
# File 'lib/rubyllm/semantic_router/router.rb', line 41 def last_routing_decision @last_routing_decision end |
Instance Method Details
#add_example(text, agent:) ⇒ self
Add a routing example
159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/rubyllm/semantic_router/router.rb', line 159 def add_example(text, agent:) agent_name = agent.to_sym validate_agent_exists!(agent_name) = (text) @examples << InMemoryExample.new( agent_name: agent_name, example_text: text, embedding: ) self end |
#agent(name) ⇒ Object
Get an agent config by name
250 251 252 |
# File 'lib/rubyllm/semantic_router/router.rb', line 250 def agent(name) @agents[name.to_sym] end |
#agent_names ⇒ Object
Get the names of all registered agents
245 246 247 |
# File 'lib/rubyllm/semantic_router/router.rb', line 245 def agent_names @agents.keys end |
#ask(message) {|chunk| ... } ⇒ RubyLLM::Message
Send a message to the router and get a response
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/rubyllm/semantic_router/router.rb', line 102 def ask(, &block) log(:debug, "Routing message: #{[0..100]}...") @last_routing_decision = route() log(:info, "Routed to :#{@last_routing_decision.agent} " \ "(confidence: #{@last_routing_decision.confidence.round(3)}, " \ "reason: #{@last_routing_decision.reason})") target_agent = @last_routing_decision.agent if target_agent != @current_agent log(:debug, "Switching from :#{@current_agent} to :#{target_agent}") switch_to(target_agent) end if @last_routing_decision.needs_clarification? inject_clarification_prompt end current_chat.ask(, &block) end |
#ask_batch(messages) ⇒ Array<RoutingDecision>
Route multiple messages and return their routing decisions Useful for batch analysis or pre-routing without conversation
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/rubyllm/semantic_router/router.rb', line 129 def ask_batch() log(:debug, "Batch routing #{.size} messages") # Generate embeddings for all messages at once truncated = .map { |m| truncate_to_max_words(m) } = (truncated) # Route each message using its pre-computed embedding .each_with_index.map do |, i| decision = @strategy.route( , agents: @agents, examples: scoped_examples, current_agent: @current_agent, config: @config, find_examples: @find_examples, precomputed_embedding: [i] ) log(:debug, "Batch[#{i}] -> :#{decision.agent} (confidence: #{decision.confidence.round(3)})") emit(:on_route, decision) decision end end |
#clear_examples! ⇒ Object
Clear all routing examples
260 261 262 263 |
# File 'lib/rubyllm/semantic_router/router.rb', line 260 def clear_examples! @examples = [] self end |
#current_chat ⇒ Object
Get the current chat object
235 236 237 |
# File 'lib/rubyllm/semantic_router/router.rb', line 235 def current_chat @chat ||= build_chat_for_agent(@current_agent) end |
#debug_routing(message) ⇒ Hash
Get detailed routing info for debugging
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/rubyllm/semantic_router/router.rb', line 208 def debug_routing() = () matches = if @examples.respond_to?(:nearest_neighbors) @examples.nearest_neighbors(:embedding, , distance: :cosine) .limit(@config.k_neighbors * 2) .to_a else find_nearest_in_memory(@examples.to_a, , @config.k_neighbors * 2) end { message: , threshold: @config.similarity_threshold, current_agent: @current_agent, would_route_to: match().agent, top_matches: matches.map do |m| { agent: extract_agent_name(m), example: extract_example_text(m), confidence: calculate_confidence(m) } end } end |
#examples ⇒ Object
Get all routing examples
255 256 257 |
# File 'lib/rubyllm/semantic_router/router.rb', line 255 def examples @examples end |
#import_examples(examples) ⇒ self
Import multiple routing examples at once (batch embedding)
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/rubyllm/semantic_router/router.rb', line 177 def import_examples(examples) return self if examples.empty? examples.each { |e| validate_agent_exists!(e[:agent].to_sym) } texts = examples.map { |e| e[:text] } = (texts) examples.each_with_index do |example, i| @examples << InMemoryExample.new( agent_name: example[:agent].to_sym, example_text: example[:text], embedding: [i] ) end self end |
#match(message) ⇒ RoutingDecision
Preview routing without sending the message
200 201 202 |
# File 'lib/rubyllm/semantic_router/router.rb', line 200 def match() route() end |
#messages ⇒ Object
Get all messages in the conversation
240 241 242 |
# File 'lib/rubyllm/semantic_router/router.rb', line 240 def current_chat. end |
#on(event, &block) ⇒ Object
Register event callbacks
292 293 294 295 296 |
# File 'lib/rubyllm/semantic_router/router.rb', line 292 def on(event, &block) @callbacks ||= {} @callbacks[event] = block self end |
#switch_to(agent_name) ⇒ Object
Manually switch to a specific agent
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/rubyllm/semantic_router/router.rb', line 272 def switch_to(agent_name) agent_name = agent_name.to_sym validate_agent_exists!(agent_name) return self if agent_name == @current_agent agent = @agents[agent_name] if @chat @chat.with_instructions(agent.instructions, replace: true) @chat.with_tools(*agent.tools, replace: true) if agent.tools.any? @chat.with_model(agent.model) if agent.model @chat.with_temperature(agent.temperature) if agent.temperature end @current_agent = agent_name self end |
#with_examples(source) ⇒ Object
Use an external examples source (e.g., ActiveRecord model)
266 267 268 269 |
# File 'lib/rubyllm/semantic_router/router.rb', line 266 def with_examples(source) @examples = source self end |