ruby_llm-agents 3.5.4 → 3.6.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +155 -10
  4. data/app/helpers/ruby_llm/agents/application_helper.rb +15 -1
  5. data/app/models/ruby_llm/agents/execution/replayable.rb +124 -0
  6. data/app/models/ruby_llm/agents/execution/scopes.rb +42 -1
  7. data/app/models/ruby_llm/agents/execution.rb +50 -1
  8. data/app/models/ruby_llm/agents/tenant/budgetable.rb +28 -4
  9. data/app/views/layouts/ruby_llm/agents/application.html.erb +41 -28
  10. data/app/views/ruby_llm/agents/agents/show.html.erb +16 -1
  11. data/app/views/ruby_llm/agents/dashboard/_top_tenants.html.erb +47 -0
  12. data/app/views/ruby_llm/agents/dashboard/index.html.erb +397 -100
  13. data/lib/generators/ruby_llm_agents/rename_agent_generator.rb +53 -0
  14. data/lib/generators/ruby_llm_agents/templates/rename_agent_migration.rb.tt +19 -0
  15. data/lib/ruby_llm/agents/agent_tool.rb +125 -0
  16. data/lib/ruby_llm/agents/audio/speaker.rb +5 -3
  17. data/lib/ruby_llm/agents/audio/speech_pricing.rb +63 -187
  18. data/lib/ruby_llm/agents/audio/transcriber.rb +5 -3
  19. data/lib/ruby_llm/agents/audio/transcription_pricing.rb +5 -7
  20. data/lib/ruby_llm/agents/base_agent.rb +144 -5
  21. data/lib/ruby_llm/agents/core/configuration.rb +178 -53
  22. data/lib/ruby_llm/agents/core/errors.rb +3 -77
  23. data/lib/ruby_llm/agents/core/instrumentation.rb +0 -17
  24. data/lib/ruby_llm/agents/core/version.rb +1 -1
  25. data/lib/ruby_llm/agents/dsl/base.rb +0 -8
  26. data/lib/ruby_llm/agents/dsl/queryable.rb +124 -0
  27. data/lib/ruby_llm/agents/dsl.rb +1 -0
  28. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -1
  29. data/lib/ruby_llm/agents/image/generator/pricing.rb +75 -217
  30. data/lib/ruby_llm/agents/image/generator.rb +5 -3
  31. data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +8 -0
  32. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +4 -2
  33. data/lib/ruby_llm/agents/pipeline/builder.rb +43 -0
  34. data/lib/ruby_llm/agents/pipeline/context.rb +11 -1
  35. data/lib/ruby_llm/agents/pipeline/executor.rb +1 -25
  36. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +26 -1
  37. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +18 -0
  38. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +130 -3
  39. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +29 -0
  40. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +11 -4
  41. data/lib/ruby_llm/agents/pipeline.rb +0 -92
  42. data/lib/ruby_llm/agents/results/background_removal_result.rb +11 -1
  43. data/lib/ruby_llm/agents/results/base.rb +23 -1
  44. data/lib/ruby_llm/agents/results/embedding_result.rb +14 -1
  45. data/lib/ruby_llm/agents/results/image_analysis_result.rb +11 -1
  46. data/lib/ruby_llm/agents/results/image_edit_result.rb +11 -1
  47. data/lib/ruby_llm/agents/results/image_generation_result.rb +12 -3
  48. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +11 -1
  49. data/lib/ruby_llm/agents/results/image_transform_result.rb +11 -1
  50. data/lib/ruby_llm/agents/results/image_upscale_result.rb +11 -1
  51. data/lib/ruby_llm/agents/results/image_variation_result.rb +11 -1
  52. data/lib/ruby_llm/agents/results/speech_result.rb +20 -1
  53. data/lib/ruby_llm/agents/results/transcription_result.rb +20 -1
  54. data/lib/ruby_llm/agents/text/embedder.rb +23 -18
  55. data/lib/ruby_llm/agents.rb +70 -5
  56. data/lib/tasks/ruby_llm_agents.rake +21 -0
  57. metadata +7 -6
  58. data/lib/ruby_llm/agents/infrastructure/reliability/breaker_manager.rb +0 -80
  59. data/lib/ruby_llm/agents/infrastructure/reliability/execution_constraints.rb +0 -69
  60. data/lib/ruby_llm/agents/infrastructure/reliability/executor.rb +0 -125
  61. data/lib/ruby_llm/agents/infrastructure/reliability/fallback_routing.rb +0 -72
  62. data/lib/ruby_llm/agents/infrastructure/reliability/retry_strategy.rb +0 -82
@@ -22,7 +22,7 @@ module RubyLLM
22
22
  class ImageGenerationResult
23
23
  attr_reader :images, :prompt, :model_id, :size, :quality, :style,
24
24
  :started_at, :completed_at, :tenant_id, :generator_class,
25
- :error_class, :error_message
25
+ :error_class, :error_message, :execution_id
26
26
 
27
27
  # Initialize a new result
28
28
  #
@@ -40,7 +40,7 @@ module RubyLLM
40
40
  # @param error_message [String, nil] Error message if failed
41
41
  def initialize(images:, prompt:, model_id:, size:, quality:, style:,
42
42
  started_at:, completed_at:, tenant_id:, generator_class:,
43
- error_class: nil, error_message: nil)
43
+ error_class: nil, error_message: nil, execution_id: nil)
44
44
  @images = images
45
45
  @prompt = prompt
46
46
  @model_id = model_id
@@ -53,6 +53,14 @@ module RubyLLM
53
53
  @generator_class = generator_class
54
54
  @error_class = error_class
55
55
  @error_message = error_message
56
+ @execution_id = execution_id
57
+ end
58
+
59
+ # Loads the associated Execution record from the database
60
+ #
61
+ # @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
62
+ def execution
63
+ @execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
56
64
  end
57
65
 
58
66
  # Status helpers
@@ -257,7 +265,8 @@ module RubyLLM
257
265
  tenant_id: tenant_id,
258
266
  generator_class: generator_class,
259
267
  error_class: error_class,
260
- error_message: error_message
268
+ error_message: error_message,
269
+ execution_id: execution_id
261
270
  }
262
271
  end
263
272
 
@@ -23,6 +23,7 @@ module RubyLLM
23
23
  class ImagePipelineResult
24
24
  attr_reader :step_results, :started_at, :completed_at, :tenant_id,
25
25
  :pipeline_class, :context, :error_class, :error_message
26
+ attr_accessor :execution_id
26
27
 
27
28
  # Initialize a new pipeline result
28
29
  #
@@ -44,6 +45,14 @@ module RubyLLM
44
45
  @context = context
45
46
  @error_class = error_class
46
47
  @error_message = error_message
48
+ @execution_id = nil
49
+ end
50
+
51
+ # Loads the associated Execution record from the database
52
+ #
53
+ # @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
54
+ def execution
55
+ @execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
47
56
  end
48
57
 
49
58
  # Status helpers
@@ -315,7 +324,8 @@ module RubyLLM
315
324
  tenant_id: tenant_id,
316
325
  pipeline_class: pipeline_class,
317
326
  error_class: error_class,
318
- error_message: error_message
327
+ error_message: error_message,
328
+ execution_id: execution_id
319
329
  }
320
330
  end
321
331
 
@@ -20,6 +20,7 @@ module RubyLLM
20
20
  attr_reader :images, :source_image, :prompt, :model_id, :size, :strength,
21
21
  :started_at, :completed_at, :tenant_id, :transformer_class,
22
22
  :error_class, :error_message
23
+ attr_accessor :execution_id
23
24
 
24
25
  # Initialize a new result
25
26
  #
@@ -50,6 +51,14 @@ module RubyLLM
50
51
  @transformer_class = transformer_class
51
52
  @error_class = error_class
52
53
  @error_message = error_message
54
+ @execution_id = nil
55
+ end
56
+
57
+ # Loads the associated Execution record from the database
58
+ #
59
+ # @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
60
+ def execution
61
+ @execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
53
62
  end
54
63
 
55
64
  # Status helpers
@@ -176,7 +185,8 @@ module RubyLLM
176
185
  tenant_id: tenant_id,
177
186
  transformer_class: transformer_class,
178
187
  error_class: error_class,
179
- error_message: error_message
188
+ error_message: error_message,
189
+ execution_id: execution_id
180
190
  }
181
191
  end
182
192
 
@@ -18,6 +18,7 @@ module RubyLLM
18
18
  attr_reader :image, :source_image, :model_id, :scale, :output_size, :face_enhance,
19
19
  :started_at, :completed_at, :tenant_id, :upscaler_class,
20
20
  :error_class, :error_message
21
+ attr_accessor :execution_id
21
22
 
22
23
  # Initialize a new result
23
24
  #
@@ -48,6 +49,14 @@ module RubyLLM
48
49
  @upscaler_class = upscaler_class
49
50
  @error_class = error_class
50
51
  @error_message = error_message
52
+ @execution_id = nil
53
+ end
54
+
55
+ # Loads the associated Execution record from the database
56
+ #
57
+ # @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
58
+ def execution
59
+ @execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
51
60
  end
52
61
 
53
62
  # Status helpers
@@ -171,7 +180,8 @@ module RubyLLM
171
180
  tenant_id: tenant_id,
172
181
  upscaler_class: upscaler_class,
173
182
  error_class: error_class,
174
- error_message: error_message
183
+ error_message: error_message,
184
+ execution_id: execution_id
175
185
  }
176
186
  end
177
187
 
@@ -17,6 +17,7 @@ module RubyLLM
17
17
  attr_reader :images, :source_image, :model_id, :size, :variation_strength,
18
18
  :started_at, :completed_at, :tenant_id, :variator_class,
19
19
  :error_class, :error_message
20
+ attr_accessor :execution_id
20
21
 
21
22
  # Initialize a new result
22
23
  #
@@ -45,6 +46,14 @@ module RubyLLM
45
46
  @variator_class = variator_class
46
47
  @error_class = error_class
47
48
  @error_message = error_message
49
+ @execution_id = nil
50
+ end
51
+
52
+ # Loads the associated Execution record from the database
53
+ #
54
+ # @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
55
+ def execution
56
+ @execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
48
57
  end
49
58
 
50
59
  # Status helpers
@@ -162,7 +171,8 @@ module RubyLLM
162
171
  tenant_id: tenant_id,
163
172
  variator_class: variator_class,
164
173
  error_class: error_class,
165
- error_message: error_message
174
+ error_message: error_message,
175
+ execution_id: execution_id
166
176
  }
167
177
  end
168
178
 
@@ -151,6 +151,14 @@ module RubyLLM
151
151
 
152
152
  # @!endgroup
153
153
 
154
+ # @!group Execution Record
155
+
156
+ # @!attribute [r] execution_id
157
+ # @return [Integer, nil] Database ID of the associated Execution record
158
+ attr_reader :execution_id
159
+
160
+ # @!endgroup
161
+
154
162
  # Creates a new SpeechResult instance
155
163
  #
156
164
  # @param attributes [Hash] Result attributes
@@ -218,6 +226,16 @@ module RubyLLM
218
226
  # Error
219
227
  @error_class = attributes[:error_class]
220
228
  @error_message = attributes[:error_message]
229
+
230
+ # Execution record
231
+ @execution_id = attributes[:execution_id]
232
+ end
233
+
234
+ # Loads the associated Execution record from the database
235
+ #
236
+ # @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
237
+ def execution
238
+ @execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
221
239
  end
222
240
 
223
241
  # Returns whether the speech generation succeeded
@@ -303,7 +321,8 @@ module RubyLLM
303
321
  status: status,
304
322
  tenant_id: tenant_id,
305
323
  error_class: error_class,
306
- error_message: error_message
324
+ error_message: error_message,
325
+ execution_id: execution_id
307
326
  # Note: audio binary data excluded for serialization safety
308
327
  }
309
328
  end
@@ -166,6 +166,14 @@ module RubyLLM
166
166
 
167
167
  # @!endgroup
168
168
 
169
+ # @!group Execution Record
170
+
171
+ # @!attribute [r] execution_id
172
+ # @return [Integer, nil] Database ID of the associated Execution record
173
+ attr_reader :execution_id
174
+
175
+ # @!endgroup
176
+
169
177
  # Creates a new TranscriptionResult instance
170
178
  #
171
179
  # @param attributes [Hash] Result attributes
@@ -239,6 +247,16 @@ module RubyLLM
239
247
  # Error
240
248
  @error_class = attributes[:error_class]
241
249
  @error_message = attributes[:error_message]
250
+
251
+ # Execution record
252
+ @execution_id = attributes[:execution_id]
253
+ end
254
+
255
+ # Loads the associated Execution record from the database
256
+ #
257
+ # @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
258
+ def execution
259
+ @execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
242
260
  end
243
261
 
244
262
  # Returns whether the transcription succeeded
@@ -368,7 +386,8 @@ module RubyLLM
368
386
  status: status,
369
387
  tenant_id: tenant_id,
370
388
  error_class: error_class,
371
- error_message: error_message
389
+ error_message: error_message,
390
+ execution_id: execution_id
372
391
  }
373
392
  end
374
393
 
@@ -245,7 +245,8 @@ module RubyLLM
245
245
  started_at: context.started_at || execution_started_at,
246
246
  completed_at: execution_completed_at,
247
247
  duration_ms: duration_ms,
248
- tenant_id: context.tenant_id
248
+ tenant_id: context.tenant_id,
249
+ execution_id: context.execution_id
249
250
  )
250
251
  end
251
252
 
@@ -377,7 +378,7 @@ module RubyLLM
377
378
  # @param duration_ms [Integer] Execution duration in ms
378
379
  # @param tenant_id [String, nil] Tenant identifier
379
380
  # @return [EmbeddingResult] The final result
380
- def build_result(vectors:, input_tokens:, total_cost:, count:, started_at:, completed_at:, duration_ms:, tenant_id:)
381
+ def build_result(vectors:, input_tokens:, total_cost:, count:, started_at:, completed_at:, duration_ms:, tenant_id:, execution_id: nil)
381
382
  EmbeddingResult.new(
382
383
  vectors: vectors,
383
384
  model_id: resolved_model,
@@ -388,7 +389,8 @@ module RubyLLM
388
389
  count: count,
389
390
  started_at: started_at,
390
391
  completed_at: completed_at,
391
- tenant_id: tenant_id
392
+ tenant_id: tenant_id,
393
+ execution_id: execution_id
392
394
  )
393
395
  end
394
396
 
@@ -397,25 +399,28 @@ module RubyLLM
397
399
  # @param response [Object] The ruby_llm embedding response
398
400
  # @return [Float] Cost in USD
399
401
  def calculate_cost(response)
400
- # ruby_llm may provide cost directly, otherwise estimate
402
+ # ruby_llm may provide cost directly
401
403
  return response.input_cost if response.respond_to?(:input_cost) && response.input_cost
402
404
 
403
- # Fallback: estimate based on tokens and model
405
+ # Look up pricing from the model registry (same approach as BaseAgent)
404
406
  tokens = response.input_tokens || 0
405
- model_name = response.model.to_s
406
-
407
- price_per_million = case model_name
408
- when /text-embedding-3-small/
409
- 0.02
410
- when /text-embedding-3-large/
411
- 0.13
412
- when /text-embedding-ada/
413
- 0.10
414
- else
415
- 0.02 # Default to small pricing
416
- end
407
+ model_id = response.model.to_s
408
+
409
+ input_price = find_embedding_price(model_id)
410
+ (tokens / 1_000_000.0) * input_price
411
+ end
417
412
 
418
- (tokens / 1_000_000.0) * price_per_million
413
+ # Looks up the per-million-token input price for an embedding model
414
+ #
415
+ # @param model_id [String] The model identifier
416
+ # @return [Numeric] Price per million tokens (0 if unknown)
417
+ def find_embedding_price(model_id)
418
+ return 0 unless defined?(RubyLLM::Models)
419
+
420
+ model_info = RubyLLM::Models.find(model_id)
421
+ model_info&.pricing&.text_tokens&.input || 0
422
+ rescue
423
+ 0
419
424
  end
420
425
 
421
426
  # Resolves the model to use
@@ -13,11 +13,6 @@ require_relative "agents/core/llm_tenant"
13
13
 
14
14
  # Infrastructure - Reliability
15
15
  require_relative "agents/infrastructure/reliability"
16
- require_relative "agents/infrastructure/reliability/retry_strategy"
17
- require_relative "agents/infrastructure/reliability/fallback_routing"
18
- require_relative "agents/infrastructure/reliability/breaker_manager"
19
- require_relative "agents/infrastructure/reliability/execution_constraints"
20
- require_relative "agents/infrastructure/reliability/executor"
21
16
 
22
17
  # Pipeline infrastructure (middleware-based execution)
23
18
  require_relative "agents/pipeline"
@@ -28,6 +23,9 @@ require_relative "agents/dsl"
28
23
  # BaseAgent - new middleware-based agent architecture
29
24
  require_relative "agents/base_agent"
30
25
 
26
+ # Agent-as-Tool adapter
27
+ require_relative "agents/agent_tool"
28
+
31
29
  # Infrastructure - Budget & Utilities
32
30
  require_relative "agents/infrastructure/circuit_breaker"
33
31
  require_relative "agents/infrastructure/budget_tracker"
@@ -142,6 +140,73 @@ module RubyLLM
142
140
  def reset_configuration!
143
141
  @configuration = Configuration.new
144
142
  end
143
+
144
+ # Renames an agent in the database, updating execution records and
145
+ # tenant budget configuration keys
146
+ #
147
+ # @param old_name [String] The previous agent class name
148
+ # @param to [String] The new agent class name
149
+ # @param dry_run [Boolean] If true, returns counts without modifying data
150
+ # @return [Hash] Summary of affected records
151
+ #
152
+ # @example Rename an agent
153
+ # RubyLLM::Agents.rename_agent("CustomerSupportAgent", to: "SupportBot")
154
+ # # => { executions_updated: 1432, tenants_updated: 3 }
155
+ #
156
+ # @example Dry run first
157
+ # RubyLLM::Agents.rename_agent("CustomerSupportAgent", to: "SupportBot", dry_run: true)
158
+ # # => { executions_affected: 1432, tenants_affected: 3 }
159
+ def rename_agent(old_name, to:, dry_run: false)
160
+ old_name = old_name.to_s
161
+ new_name = to.to_s
162
+
163
+ raise ArgumentError, "old_name and new name must be different" if old_name == new_name
164
+ raise ArgumentError, "old_name cannot be blank" if old_name.blank?
165
+ raise ArgumentError, "new name cannot be blank" if new_name.blank?
166
+
167
+ execution_scope = Execution.where(agent_type: old_name)
168
+ execution_count = execution_scope.count
169
+
170
+ tenant_count = 0
171
+ if defined?(Tenant) && Tenant.table_exists?
172
+ Tenant.find_each do |tenant|
173
+ changed = false
174
+ %w[per_agent_daily per_agent_monthly].each do |field|
175
+ hash = tenant.send(field)
176
+ next unless hash.is_a?(Hash) && hash.key?(old_name)
177
+ changed = true
178
+ break
179
+ end
180
+ tenant_count += 1 if changed
181
+ end
182
+ end
183
+
184
+ if dry_run
185
+ {executions_affected: execution_count, tenants_affected: tenant_count}
186
+ else
187
+ executions_updated = execution_scope.update_all(agent_type: new_name)
188
+
189
+ tenants_updated = 0
190
+ if defined?(Tenant) && Tenant.table_exists?
191
+ Tenant.find_each do |tenant|
192
+ changed = false
193
+ %w[per_agent_daily per_agent_monthly].each do |field|
194
+ hash = tenant.send(field)
195
+ next unless hash.is_a?(Hash) && hash.key?(old_name)
196
+ hash[new_name] = hash.delete(old_name)
197
+ tenant.send(:"#{field}=", hash)
198
+ changed = true
199
+ end
200
+ if changed
201
+ tenant.save!
202
+ tenants_updated += 1
203
+ end
204
+ end
205
+ end
206
+
207
+ {executions_updated: executions_updated, tenants_updated: tenants_updated}
208
+ end
209
+ end
145
210
  end
146
211
  end
147
212
  end
@@ -1,6 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :ruby_llm_agents do
4
+ desc "Rename an agent type in execution records. Usage: rake ruby_llm_agents:rename_agent FROM=OldName TO=NewName [DRY_RUN=1]"
5
+ task rename_agent: :environment do
6
+ from = ENV["FROM"]
7
+ to = ENV["TO"]
8
+ dry_run = ENV["DRY_RUN"] == "1"
9
+
10
+ abort "Usage: rake ruby_llm_agents:rename_agent FROM=OldAgentName TO=NewAgentName [DRY_RUN=1]" unless from && to
11
+
12
+ result = RubyLLM::Agents.rename_agent(from, to: to, dry_run: dry_run)
13
+
14
+ if dry_run
15
+ puts "Dry run results:"
16
+ puts " Executions affected: #{result[:executions_affected]}"
17
+ puts " Tenants affected: #{result[:tenants_affected]}"
18
+ else
19
+ puts "Rename complete:"
20
+ puts " Executions updated: #{result[:executions_updated]}"
21
+ puts " Tenants updated: #{result[:tenants_updated]}"
22
+ end
23
+ end
24
+
4
25
  namespace :tenants do
5
26
  desc "Refresh all tenant counters from executions table"
6
27
  task refresh: :environment do
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.5.4
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - adham90
@@ -88,6 +88,7 @@ files:
88
88
  - app/models/ruby_llm/agents/execution.rb
89
89
  - app/models/ruby_llm/agents/execution/analytics.rb
90
90
  - app/models/ruby_llm/agents/execution/metrics.rb
91
+ - app/models/ruby_llm/agents/execution/replayable.rb
91
92
  - app/models/ruby_llm/agents/execution/scopes.rb
92
93
  - app/models/ruby_llm/agents/execution_detail.rb
93
94
  - app/models/ruby_llm/agents/tenant.rb
@@ -111,6 +112,7 @@ files:
111
112
  - app/views/ruby_llm/agents/agents/show.html.erb
112
113
  - app/views/ruby_llm/agents/dashboard/_action_center.html.erb
113
114
  - app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb
115
+ - app/views/ruby_llm/agents/dashboard/_top_tenants.html.erb
114
116
  - app/views/ruby_llm/agents/dashboard/index.html.erb
115
117
  - app/views/ruby_llm/agents/executions/_audio_player.html.erb
116
118
  - app/views/ruby_llm/agents/executions/_execution.html.erb
@@ -146,6 +148,7 @@ files:
146
148
  - lib/generators/ruby_llm_agents/install_generator.rb
147
149
  - lib/generators/ruby_llm_agents/migrate_structure_generator.rb
148
150
  - lib/generators/ruby_llm_agents/multi_tenancy_generator.rb
151
+ - lib/generators/ruby_llm_agents/rename_agent_generator.rb
149
152
  - lib/generators/ruby_llm_agents/restructure_generator.rb
150
153
  - lib/generators/ruby_llm_agents/speaker_generator.rb
151
154
  - lib/generators/ruby_llm_agents/templates/add_assistant_prompt_migration.rb.tt
@@ -189,6 +192,7 @@ files:
189
192
  - lib/generators/ruby_llm_agents/templates/migration.rb.tt
190
193
  - lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt
191
194
  - lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt
195
+ - lib/generators/ruby_llm_agents/templates/rename_agent_migration.rb.tt
192
196
  - lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt
193
197
  - lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt
194
198
  - lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt
@@ -210,6 +214,7 @@ files:
210
214
  - lib/generators/ruby_llm_agents/upgrade_generator.rb
211
215
  - lib/ruby_llm-agents.rb
212
216
  - lib/ruby_llm/agents.rb
217
+ - lib/ruby_llm/agents/agent_tool.rb
213
218
  - lib/ruby_llm/agents/audio/elevenlabs/model_registry.rb
214
219
  - lib/ruby_llm/agents/audio/speaker.rb
215
220
  - lib/ruby_llm/agents/audio/speaker/active_storage_support.rb
@@ -230,6 +235,7 @@ files:
230
235
  - lib/ruby_llm/agents/dsl.rb
231
236
  - lib/ruby_llm/agents/dsl/base.rb
232
237
  - lib/ruby_llm/agents/dsl/caching.rb
238
+ - lib/ruby_llm/agents/dsl/queryable.rb
233
239
  - lib/ruby_llm/agents/dsl/reliability.rb
234
240
  - lib/ruby_llm/agents/image/analyzer.rb
235
241
  - lib/ruby_llm/agents/image/analyzer/dsl.rb
@@ -269,11 +275,6 @@ files:
269
275
  - lib/ruby_llm/agents/infrastructure/circuit_breaker.rb
270
276
  - lib/ruby_llm/agents/infrastructure/execution_logger_job.rb
271
277
  - lib/ruby_llm/agents/infrastructure/reliability.rb
272
- - lib/ruby_llm/agents/infrastructure/reliability/breaker_manager.rb
273
- - lib/ruby_llm/agents/infrastructure/reliability/execution_constraints.rb
274
- - lib/ruby_llm/agents/infrastructure/reliability/executor.rb
275
- - lib/ruby_llm/agents/infrastructure/reliability/fallback_routing.rb
276
- - lib/ruby_llm/agents/infrastructure/reliability/retry_strategy.rb
277
278
  - lib/ruby_llm/agents/pipeline.rb
278
279
  - lib/ruby_llm/agents/pipeline/builder.rb
279
280
  - lib/ruby_llm/agents/pipeline/context.rb
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Agents
5
- module Reliability
6
- # Manages circuit breakers for multiple models
7
- #
8
- # Provides centralized access to circuit breakers with
9
- # multi-tenant support and caching.
10
- #
11
- # @example
12
- # manager = BreakerManager.new("MyAgent", config: { errors: 5, within: 60 })
13
- # manager.open?("gpt-4o") # => false
14
- # manager.record_failure!("gpt-4o")
15
- # manager.record_success!("gpt-4o")
16
- #
17
- # @api private
18
- class BreakerManager
19
- # @param agent_type [String] The agent class name
20
- # @param config [Hash, nil] Circuit breaker configuration
21
- # @param tenant_id [String, nil] Optional tenant identifier
22
- def initialize(agent_type, config:, tenant_id: nil)
23
- @agent_type = agent_type
24
- @config = config
25
- @tenant_id = tenant_id
26
- @breakers = {}
27
- end
28
-
29
- # Gets or creates a circuit breaker for a model
30
- #
31
- # @param model_id [String] Model identifier
32
- # @return [CircuitBreaker, nil] The circuit breaker or nil if not configured
33
- def for_model(model_id)
34
- return nil unless @config
35
-
36
- @breakers[model_id] ||= CircuitBreaker.from_config(
37
- @agent_type,
38
- model_id,
39
- @config,
40
- tenant_id: @tenant_id
41
- )
42
- end
43
-
44
- # Checks if a model's circuit breaker is open
45
- #
46
- # @param model_id [String] Model identifier
47
- # @return [Boolean] true if breaker is open
48
- def open?(model_id)
49
- breaker = for_model(model_id)
50
- breaker&.open? || false
51
- end
52
-
53
- # Records a success for a model
54
- #
55
- # @param model_id [String] Model identifier
56
- # @return [void]
57
- def record_success!(model_id)
58
- for_model(model_id)&.record_success!
59
- end
60
-
61
- # Records a failure for a model
62
- #
63
- # @param model_id [String] Model identifier
64
- # @return [Boolean] true if breaker is now open
65
- def record_failure!(model_id)
66
- breaker = for_model(model_id)
67
- breaker&.record_failure!
68
- breaker&.open? || false
69
- end
70
-
71
- # Checks if circuit breaker is configured
72
- #
73
- # @return [Boolean] true if config present
74
- def enabled?
75
- @config.present?
76
- end
77
- end
78
- end
79
- end
80
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Agents
5
- module Reliability
6
- # Manages execution constraints like total timeout and budget
7
- #
8
- # Tracks elapsed time and enforces timeout limits across
9
- # all retry and fallback attempts.
10
- #
11
- # @example
12
- # constraints = ExecutionConstraints.new(total_timeout: 30)
13
- # constraints.timeout_exceeded? # => false
14
- # constraints.enforce_timeout! # raises if exceeded
15
- # constraints.elapsed # => 5.2
16
- #
17
- # @api private
18
- class ExecutionConstraints
19
- attr_reader :total_timeout, :started_at, :deadline
20
-
21
- # @param total_timeout [Integer, nil] Total timeout in seconds
22
- def initialize(total_timeout: nil)
23
- @total_timeout = total_timeout
24
- @started_at = Time.current
25
- @deadline = total_timeout ? @started_at + total_timeout : nil
26
- end
27
-
28
- # Checks if total timeout has been exceeded
29
- #
30
- # @return [Boolean] true if past deadline
31
- def timeout_exceeded?
32
- deadline && Time.current > deadline
33
- end
34
-
35
- # Returns elapsed time since start
36
- #
37
- # @return [Float] Elapsed seconds
38
- def elapsed
39
- Time.current - started_at
40
- end
41
-
42
- # Raises TotalTimeoutError if timeout exceeded
43
- #
44
- # @raise [TotalTimeoutError] If timeout exceeded
45
- # @return [void]
46
- def enforce_timeout!
47
- if timeout_exceeded?
48
- raise TotalTimeoutError.new(total_timeout, elapsed)
49
- end
50
- end
51
-
52
- # Returns remaining time until deadline
53
- #
54
- # @return [Float, nil] Remaining seconds or nil if no timeout
55
- def remaining
56
- return nil unless deadline
57
- [deadline - Time.current, 0].max
58
- end
59
-
60
- # Checks if there's a timeout configured
61
- #
62
- # @return [Boolean] true if timeout is set
63
- def has_timeout?
64
- total_timeout.present?
65
- end
66
- end
67
- end
68
- end
69
- end