ruby_llm-agents 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -1
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +27 -4
  4. data/app/services/ruby_llm/agents/agent_registry.rb +3 -1
  5. data/app/views/ruby_llm/agents/agents/_config_router.html.erb +110 -0
  6. data/app/views/ruby_llm/agents/agents/index.html.erb +6 -0
  7. data/app/views/ruby_llm/agents/executions/show.html.erb +10 -0
  8. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +8 -0
  9. data/lib/ruby_llm/agents/audio/elevenlabs/model_registry.rb +187 -0
  10. data/lib/ruby_llm/agents/audio/speaker.rb +38 -0
  11. data/lib/ruby_llm/agents/audio/speech_client.rb +26 -2
  12. data/lib/ruby_llm/agents/audio/speech_pricing.rb +44 -3
  13. data/lib/ruby_llm/agents/audio/transcriber.rb +26 -15
  14. data/lib/ruby_llm/agents/audio/transcription_pricing.rb +226 -0
  15. data/lib/ruby_llm/agents/core/configuration.rb +32 -1
  16. data/lib/ruby_llm/agents/core/version.rb +1 -1
  17. data/lib/ruby_llm/agents/pricing/data_store.rb +339 -0
  18. data/lib/ruby_llm/agents/pricing/helicone_adapter.rb +88 -0
  19. data/lib/ruby_llm/agents/pricing/litellm_adapter.rb +105 -0
  20. data/lib/ruby_llm/agents/pricing/llmpricing_adapter.rb +73 -0
  21. data/lib/ruby_llm/agents/pricing/openrouter_adapter.rb +90 -0
  22. data/lib/ruby_llm/agents/pricing/portkey_adapter.rb +94 -0
  23. data/lib/ruby_llm/agents/pricing/ruby_llm_adapter.rb +94 -0
  24. data/lib/ruby_llm/agents/results/speech_result.rb +19 -16
  25. data/lib/ruby_llm/agents/routing/class_methods.rb +92 -0
  26. data/lib/ruby_llm/agents/routing/result.rb +74 -0
  27. data/lib/ruby_llm/agents/routing.rb +140 -0
  28. data/lib/ruby_llm/agents.rb +3 -0
  29. metadata +14 -1
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ module Pricing
6
+ # Normalizes Portkey AI per-model pricing into the common format.
7
+ #
8
+ # Portkey prices are in **cents per token**. This adapter converts to
9
+ # USD per token for consistency with other adapters.
10
+ #
11
+ # Requires knowing the provider for a model, resolved via PROVIDER_MAP.
12
+ #
13
+ # @example
14
+ # PortkeyAdapter.find_model("gpt-4o")
15
+ # # => { input_cost_per_token: 0.0000025, output_cost_per_token: 0.00001, source: :portkey }
16
+ #
17
+ module PortkeyAdapter
18
+ extend self
19
+
20
+ PROVIDER_MAP = [
21
+ [/^(gpt-|o1|o3|o4|whisper|dall-e|tts-|chatgpt)/, "openai"],
22
+ [/^claude/, "anthropic"],
23
+ [/^gemini/, "google"],
24
+ [/^(mistral|codestral|pixtral|ministral)/, "mistralai"],
25
+ [/^llama/, "meta"],
26
+ [/^(command|embed)/, "cohere"],
27
+ [/^deepseek/, "deepseek"],
28
+ [/^(nova|titan)/, "amazon"]
29
+ ].freeze
30
+
31
+ # Find and normalize pricing for a model
32
+ #
33
+ # @param model_id [String] The model identifier
34
+ # @return [Hash, nil] Normalized pricing hash or nil
35
+ def find_model(model_id)
36
+ provider, model_name = resolve_provider(model_id)
37
+ return nil unless provider
38
+
39
+ raw = DataStore.portkey_data(provider, model_name)
40
+ return nil unless raw.is_a?(Hash) && raw["pay_as_you_go"]
41
+
42
+ normalize(raw)
43
+ end
44
+
45
+ private
46
+
47
+ def resolve_provider(model_id)
48
+ id = model_id.to_s
49
+
50
+ # Handle prefixed model IDs like "azure/gpt-4o" or "groq/llama-3"
51
+ if id.include?("/")
52
+ parts = id.split("/", 2)
53
+ return [parts[0], parts[1]]
54
+ end
55
+
56
+ PROVIDER_MAP.each do |pattern, provider|
57
+ return [provider, id] if id.match?(pattern)
58
+ end
59
+
60
+ nil
61
+ end
62
+
63
+ def normalize(raw)
64
+ pag = raw["pay_as_you_go"]
65
+ return nil unless pag
66
+
67
+ result = {source: :portkey}
68
+
69
+ # Main text token pricing (cents → USD)
70
+ req_price = dig_price(pag, "request_token", "price")
71
+ resp_price = dig_price(pag, "response_token", "price")
72
+ result[:input_cost_per_token] = req_price / 100.0 if req_price&.positive?
73
+ result[:output_cost_per_token] = resp_price / 100.0 if resp_price&.positive?
74
+
75
+ # Additional units (audio tokens, etc.)
76
+ additional = pag["additional_units"]
77
+ if additional.is_a?(Hash)
78
+ audio_in = dig_price(additional, "request_audio_token", "price")
79
+ audio_out = dig_price(additional, "response_audio_token", "price")
80
+ result[:input_cost_per_audio_token] = audio_in / 100.0 if audio_in&.positive?
81
+ result[:output_cost_per_audio_token] = audio_out / 100.0 if audio_out&.positive?
82
+ end
83
+
84
+ (result.keys.size > 1) ? result : nil
85
+ end
86
+
87
+ def dig_price(hash, *keys)
88
+ value = hash.dig(*keys)
89
+ value.is_a?(Numeric) ? value : nil
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ module Pricing
6
+ # Extracts pricing from the ruby_llm gem's built-in model registry.
7
+ #
8
+ # This is a local, zero-HTTP-cost source that provides pricing for
9
+ # models that ruby_llm knows about. It's the fastest adapter since
10
+ # all data is already loaded in-process.
11
+ #
12
+ # Uses RubyLLM::Models.find(model_id) which returns pricing as
13
+ # USD per million tokens.
14
+ #
15
+ # @example
16
+ # RubyLLMAdapter.find_model("gpt-4o")
17
+ # # => { input_cost_per_token: 0.0000025, output_cost_per_token: 0.00001, source: :ruby_llm }
18
+ #
19
+ module RubyLLMAdapter
20
+ extend self
21
+
22
+ # Find and normalize pricing for a model from ruby_llm's registry
23
+ #
24
+ # @param model_id [String] The model identifier
25
+ # @return [Hash, nil] Normalized pricing hash or nil
26
+ def find_model(model_id)
27
+ return nil unless defined?(::RubyLLM::Models)
28
+
29
+ model_info = ::RubyLLM::Models.find(model_id)
30
+ return nil unless model_info
31
+
32
+ normalize(model_info)
33
+ rescue
34
+ nil
35
+ end
36
+
37
+ private
38
+
39
+ def normalize(model_info)
40
+ pricing = model_info.pricing
41
+ return nil unless pricing
42
+
43
+ result = {source: :ruby_llm}
44
+
45
+ # Text tokens (per million → per token)
46
+ text_tokens = pricing.respond_to?(:text_tokens) ? pricing.text_tokens : nil
47
+ if text_tokens
48
+ input_per_million = text_tokens.respond_to?(:input) ? text_tokens.input : nil
49
+ output_per_million = text_tokens.respond_to?(:output) ? text_tokens.output : nil
50
+
51
+ if input_per_million.is_a?(Numeric) && input_per_million.positive?
52
+ result[:input_cost_per_token] = input_per_million / 1_000_000.0
53
+ end
54
+
55
+ if output_per_million.is_a?(Numeric) && output_per_million.positive?
56
+ result[:output_cost_per_token] = output_per_million / 1_000_000.0
57
+ end
58
+ end
59
+
60
+ # Audio tokens (per million → per token) if available
61
+ audio_tokens = pricing.respond_to?(:audio_tokens) ? pricing.audio_tokens : nil
62
+ if audio_tokens
63
+ audio_input = audio_tokens.respond_to?(:input) ? audio_tokens.input : nil
64
+ audio_output = audio_tokens.respond_to?(:output) ? audio_tokens.output : nil
65
+
66
+ if audio_input.is_a?(Numeric) && audio_input.positive?
67
+ result[:input_cost_per_audio_token] = audio_input / 1_000_000.0
68
+ end
69
+
70
+ if audio_output.is_a?(Numeric) && audio_output.positive?
71
+ result[:output_cost_per_audio_token] = audio_output / 1_000_000.0
72
+ end
73
+ end
74
+
75
+ # Image pricing if available
76
+ images = pricing.respond_to?(:images) ? pricing.images : nil
77
+ if images
78
+ per_image = images.respond_to?(:input) ? images.input : nil
79
+ if per_image.is_a?(Numeric) && per_image.positive?
80
+ result[:input_cost_per_image] = per_image
81
+ end
82
+ end
83
+
84
+ # Mode from model type if available
85
+ if model_info.respond_to?(:type)
86
+ result[:mode] = model_info.type.to_s
87
+ end
88
+
89
+ (result.keys.size > 1) ? result : nil
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -319,23 +319,26 @@ module RubyLLM
319
319
  #
320
320
  # @return [String] MIME type
321
321
  def mime_type_for_format
322
+ fmt = format.to_s
323
+
324
+ # Handle ElevenLabs native format strings (e.g., "mp3_44100_128")
325
+ return "audio/mpeg" if fmt.start_with?("mp3")
326
+ return "audio/wav" if fmt.start_with?("wav")
327
+ return "audio/opus" if fmt.start_with?("opus")
328
+ return "audio/pcm" if fmt.start_with?("pcm")
329
+ return "audio/alaw" if fmt.start_with?("alaw")
330
+ return "audio/basic" if fmt.start_with?("ulaw")
331
+
332
+ # Handle simple symbols (backward compatible)
322
333
  case format
323
- when :mp3
324
- "audio/mpeg"
325
- when :wav
326
- "audio/wav"
327
- when :ogg
328
- "audio/ogg"
329
- when :flac
330
- "audio/flac"
331
- when :aac
332
- "audio/aac"
333
- when :opus
334
- "audio/opus"
335
- when :pcm
336
- "audio/pcm"
337
- else
338
- "audio/mpeg" # Default to mp3
334
+ when :mp3 then "audio/mpeg"
335
+ when :wav then "audio/wav"
336
+ when :ogg then "audio/ogg"
337
+ when :flac then "audio/flac"
338
+ when :aac then "audio/aac"
339
+ when :opus then "audio/opus"
340
+ when :pcm then "audio/pcm"
341
+ else "audio/mpeg"
339
342
  end
340
343
  end
341
344
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ module Routing
6
+ # Class-level DSL for defining routes and classification categories.
7
+ #
8
+ # Extended into any BaseAgent subclass that includes the Routing concern.
9
+ # Provides `route`, `default_route`, and accessor methods for route definitions.
10
+ #
11
+ # @example
12
+ # class SupportRouter < RubyLLM::Agents::BaseAgent
13
+ # include RubyLLM::Agents::Routing
14
+ #
15
+ # route :billing, "Billing, charges, refunds"
16
+ # route :technical, "Bugs, errors, crashes"
17
+ # default_route :general
18
+ # end
19
+ #
20
+ module ClassMethods
21
+ # Define a classification route.
22
+ #
23
+ # @param name [Symbol] Route identifier
24
+ # @param description [String] What messages belong to this route
25
+ # @param agent [Class, nil] Optional agent class to map to this route
26
+ #
27
+ # @example Simple route
28
+ # route :billing, "Billing, charges, refunds"
29
+ #
30
+ # @example Route with agent mapping
31
+ # route :billing, "Billing questions", agent: BillingAgent
32
+ #
33
+ def route(name, description, agent: nil)
34
+ @routes ||= {}
35
+ @routes[name.to_sym] = {
36
+ description: description,
37
+ agent: agent
38
+ }
39
+ end
40
+
41
+ # Set the default route for unmatched classifications.
42
+ #
43
+ # @param name [Symbol] Default route name
44
+ # @param agent [Class, nil] Optional default agent class
45
+ #
46
+ def default_route(name, agent: nil)
47
+ @default_route_name = name.to_sym
48
+ @routes ||= {}
49
+ @routes[name.to_sym] ||= {
50
+ description: "Default / general category",
51
+ agent: agent
52
+ }
53
+ end
54
+
55
+ # Returns all defined routes (including inherited).
56
+ #
57
+ # @return [Hash{Symbol => Hash}] Route definitions
58
+ def routes
59
+ parent = superclass.respond_to?(:routes) ? superclass.routes : {}
60
+ parent.merge(@routes || {})
61
+ end
62
+
63
+ # Returns the default route name (including inherited).
64
+ #
65
+ # @return [Symbol] The default route name
66
+ def default_route_name
67
+ @default_route_name || (superclass.respond_to?(:default_route_name) ? superclass.default_route_name : :general)
68
+ end
69
+
70
+ # Returns :router for instrumentation/tracking.
71
+ #
72
+ # @return [Symbol]
73
+ def agent_type
74
+ :router
75
+ end
76
+
77
+ # Override call to accept message: as a named param.
78
+ #
79
+ # @param message [String, nil] The message to classify
80
+ # @param kwargs [Hash] Additional options
81
+ # @return [RoutingResult] The classification result
82
+ def call(message: nil, **kwargs, &block)
83
+ if message
84
+ super(**kwargs.merge(message: message), &block)
85
+ else
86
+ super(**kwargs, &block)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ module Routing
6
+ # Wraps a standard Result with routing-specific accessors.
7
+ #
8
+ # Delegates all standard Result methods (tokens, cost, timing, etc.)
9
+ # to the underlying result, adding only the route-specific interface.
10
+ #
11
+ # @example
12
+ # result = SupportRouter.call(message: "I was charged twice")
13
+ # result.route # => :billing
14
+ # result.agent_class # => BillingAgent (if mapped)
15
+ # result.success? # => true
16
+ # result.total_cost # => 0.0001
17
+ #
18
+ class RoutingResult < Result
19
+ # @return [Symbol] The classified route name
20
+ attr_reader :route
21
+
22
+ # @return [Class, nil] The mapped agent class (if defined via `agent:`)
23
+ attr_reader :agent_class
24
+
25
+ # @return [String] The raw text response from the LLM
26
+ attr_reader :raw_response
27
+
28
+ # Creates a new RoutingResult by wrapping a base Result with route data.
29
+ #
30
+ # @param base_result [Result] The standard Result from BaseAgent execution
31
+ # @param route_data [Hash] Parsed route information
32
+ # @option route_data [Symbol] :route The classified route name
33
+ # @option route_data [Class, nil] :agent_class Mapped agent class
34
+ # @option route_data [String] :raw_response Raw LLM text
35
+ def initialize(base_result:, route_data:)
36
+ super(
37
+ content: route_data,
38
+ input_tokens: base_result.input_tokens,
39
+ output_tokens: base_result.output_tokens,
40
+ input_cost: base_result.input_cost,
41
+ output_cost: base_result.output_cost,
42
+ total_cost: base_result.total_cost,
43
+ model_id: base_result.model_id,
44
+ chosen_model_id: base_result.chosen_model_id,
45
+ temperature: base_result.temperature,
46
+ started_at: base_result.started_at,
47
+ completed_at: base_result.completed_at,
48
+ duration_ms: base_result.duration_ms,
49
+ finish_reason: base_result.finish_reason,
50
+ streaming: base_result.streaming,
51
+ error_class: base_result.error_class,
52
+ error_message: base_result.error_message,
53
+ attempts_count: base_result.attempts_count
54
+ )
55
+
56
+ @route = route_data[:route]
57
+ @agent_class = route_data[:agent_class]
58
+ @raw_response = route_data[:raw_response]
59
+ end
60
+
61
+ # Converts the result to a hash including routing fields.
62
+ #
63
+ # @return [Hash] All result data plus route, agent_class, raw_response
64
+ def to_h
65
+ super.merge(
66
+ route: route,
67
+ agent_class: agent_class&.name,
68
+ raw_response: raw_response
69
+ )
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "routing/class_methods"
4
+ require_relative "routing/result"
5
+
6
+ module RubyLLM
7
+ module Agents
8
+ # Adds classification & routing capabilities to any BaseAgent.
9
+ #
10
+ # Include this module in a BaseAgent subclass to get:
11
+ # - `route` DSL for defining classification categories
12
+ # - `default_route` for fallback classification
13
+ # - Auto-generated system/user prompts from route definitions
14
+ # - Structured output parsing to return a RoutingResult
15
+ #
16
+ # All existing BaseAgent features (caching, reliability, retries,
17
+ # fallback models, instrumentation, multi-tenancy) work unchanged.
18
+ #
19
+ # @example Class-based router
20
+ # class SupportRouter < RubyLLM::Agents::BaseAgent
21
+ # include RubyLLM::Agents::Routing
22
+ #
23
+ # model "gpt-4o-mini"
24
+ # temperature 0.0
25
+ #
26
+ # route :billing, "Billing, charges, refunds"
27
+ # route :technical, "Bugs, errors, crashes"
28
+ # default_route :general
29
+ # end
30
+ #
31
+ # result = SupportRouter.call(message: "I was charged twice")
32
+ # result.route # => :billing
33
+ #
34
+ # @example Inline classification
35
+ # route = RubyLLM::Agents::Routing.classify(
36
+ # message: "I was charged twice",
37
+ # routes: { billing: "Billing issues", technical: "Tech issues" },
38
+ # default: :general
39
+ # )
40
+ # # => :billing
41
+ #
42
+ module Routing
43
+ def self.included(base)
44
+ unless base < BaseAgent
45
+ raise ArgumentError, "#{base} must inherit from RubyLLM::Agents::BaseAgent to include Routing"
46
+ end
47
+
48
+ base.extend(ClassMethods)
49
+ base.param(:message, required: false)
50
+ end
51
+
52
+ # Classify a message without defining a router class.
53
+ #
54
+ # Creates an anonymous BaseAgent subclass with Routing included,
55
+ # calls it, and returns just the route symbol.
56
+ #
57
+ # @param message [String] The message to classify
58
+ # @param routes [Hash{Symbol => String}] Route names to descriptions
59
+ # @param default [Symbol] Default route (:general)
60
+ # @param model [String] LLM model to use ("gpt-4o-mini")
61
+ # @param options [Hash] Extra options passed to .call
62
+ # @return [Symbol] The classified route name
63
+ def self.classify(message:, routes:, default: :general, model: "gpt-4o-mini", **options)
64
+ router_model = model
65
+ router = Class.new(BaseAgent) do
66
+ include Routing
67
+
68
+ self.model router_model
69
+ temperature 0.0
70
+
71
+ routes.each do |name, desc|
72
+ route name, desc
73
+ end
74
+ default_route default
75
+ end
76
+
77
+ result = router.call(message: message, **options)
78
+ result.route
79
+ end
80
+
81
+ # Helper to get the auto-generated system prompt for routing.
82
+ # Use this in custom system_prompt overrides to include route definitions.
83
+ #
84
+ # @return [String] The auto-generated routing system prompt
85
+ def routing_system_prompt
86
+ default = self.class.default_route_name
87
+
88
+ <<~PROMPT.strip
89
+ You are a message classifier. Classify the user's message into exactly one of the following categories:
90
+
91
+ #{routing_categories_text}
92
+
93
+ If none of the categories clearly match, classify as: #{default}
94
+
95
+ Respond with ONLY the category name, nothing else.
96
+ PROMPT
97
+ end
98
+
99
+ # Helper to get formatted route categories for use in custom prompts.
100
+ #
101
+ # @return [String] Formatted list of route categories
102
+ def routing_categories_text
103
+ self.class.routes.map do |name, config|
104
+ "- #{name}: #{config[:description]}"
105
+ end.join("\n")
106
+ end
107
+
108
+ # Auto-generated system_prompt (used if subclass doesn't override).
109
+ def system_prompt
110
+ super || routing_system_prompt
111
+ end
112
+
113
+ # Auto-generated user_prompt from the :message param.
114
+ def user_prompt
115
+ @ask_message || options[:message] || super
116
+ end
117
+
118
+ # Override process_response to parse the route from LLM output.
119
+ def process_response(response)
120
+ raw = response.content.to_s.strip.downcase.gsub(/[^a-z0-9_]/, "")
121
+ route_name = raw.to_sym
122
+
123
+ valid_routes = self.class.routes.keys
124
+ route_name = self.class.default_route_name unless valid_routes.include?(route_name)
125
+
126
+ {
127
+ route: route_name,
128
+ agent_class: self.class.routes.dig(route_name, :agent),
129
+ raw_response: response.content.to_s.strip
130
+ }
131
+ end
132
+
133
+ # Override build_result to return a RoutingResult.
134
+ def build_result(content, response, context)
135
+ base = super
136
+ RoutingResult.new(base_result: base, route_data: content)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -57,6 +57,9 @@ require_relative "agents/results/image_pipeline_result"
57
57
  require_relative "agents/image/concerns/image_operation_dsl"
58
58
  require_relative "agents/image/concerns/image_operation_execution"
59
59
 
60
+ # Routing concern (classification & routing)
61
+ require_relative "agents/routing"
62
+
60
63
  # Text agents
61
64
  require_relative "agents/text/embedder"
62
65
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - adham90
@@ -102,6 +102,7 @@ files:
102
102
  - app/views/ruby_llm/agents/agents/_config_embedder.html.erb
103
103
  - app/views/ruby_llm/agents/agents/_config_image_generator.html.erb
104
104
  - app/views/ruby_llm/agents/agents/_config_moderator.html.erb
105
+ - app/views/ruby_llm/agents/agents/_config_router.html.erb
105
106
  - app/views/ruby_llm/agents/agents/_config_speaker.html.erb
106
107
  - app/views/ruby_llm/agents/agents/_config_transcriber.html.erb
107
108
  - app/views/ruby_llm/agents/agents/_empty_state.html.erb
@@ -209,11 +210,13 @@ files:
209
210
  - lib/generators/ruby_llm_agents/upgrade_generator.rb
210
211
  - lib/ruby_llm-agents.rb
211
212
  - lib/ruby_llm/agents.rb
213
+ - lib/ruby_llm/agents/audio/elevenlabs/model_registry.rb
212
214
  - lib/ruby_llm/agents/audio/speaker.rb
213
215
  - lib/ruby_llm/agents/audio/speaker/active_storage_support.rb
214
216
  - lib/ruby_llm/agents/audio/speech_client.rb
215
217
  - lib/ruby_llm/agents/audio/speech_pricing.rb
216
218
  - lib/ruby_llm/agents/audio/transcriber.rb
219
+ - lib/ruby_llm/agents/audio/transcription_pricing.rb
217
220
  - lib/ruby_llm/agents/base_agent.rb
218
221
  - lib/ruby_llm/agents/core/base.rb
219
222
  - lib/ruby_llm/agents/core/base/callbacks.rb
@@ -281,6 +284,13 @@ files:
281
284
  - lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb
282
285
  - lib/ruby_llm/agents/pipeline/middleware/reliability.rb
283
286
  - lib/ruby_llm/agents/pipeline/middleware/tenant.rb
287
+ - lib/ruby_llm/agents/pricing/data_store.rb
288
+ - lib/ruby_llm/agents/pricing/helicone_adapter.rb
289
+ - lib/ruby_llm/agents/pricing/litellm_adapter.rb
290
+ - lib/ruby_llm/agents/pricing/llmpricing_adapter.rb
291
+ - lib/ruby_llm/agents/pricing/openrouter_adapter.rb
292
+ - lib/ruby_llm/agents/pricing/portkey_adapter.rb
293
+ - lib/ruby_llm/agents/pricing/ruby_llm_adapter.rb
284
294
  - lib/ruby_llm/agents/rails/engine.rb
285
295
  - lib/ruby_llm/agents/results/background_removal_result.rb
286
296
  - lib/ruby_llm/agents/results/base.rb
@@ -294,6 +304,9 @@ files:
294
304
  - lib/ruby_llm/agents/results/image_variation_result.rb
295
305
  - lib/ruby_llm/agents/results/speech_result.rb
296
306
  - lib/ruby_llm/agents/results/transcription_result.rb
307
+ - lib/ruby_llm/agents/routing.rb
308
+ - lib/ruby_llm/agents/routing/class_methods.rb
309
+ - lib/ruby_llm/agents/routing/result.rb
297
310
  - lib/ruby_llm/agents/text/embedder.rb
298
311
  - lib/tasks/ruby_llm_agents.rake
299
312
  homepage: https://github.com/adham90/ruby_llm-agents