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.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +155 -10
- data/app/helpers/ruby_llm/agents/application_helper.rb +15 -1
- data/app/models/ruby_llm/agents/execution/replayable.rb +124 -0
- data/app/models/ruby_llm/agents/execution/scopes.rb +42 -1
- data/app/models/ruby_llm/agents/execution.rb +50 -1
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +28 -4
- data/app/views/layouts/ruby_llm/agents/application.html.erb +41 -28
- data/app/views/ruby_llm/agents/agents/show.html.erb +16 -1
- data/app/views/ruby_llm/agents/dashboard/_top_tenants.html.erb +47 -0
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +397 -100
- data/lib/generators/ruby_llm_agents/rename_agent_generator.rb +53 -0
- data/lib/generators/ruby_llm_agents/templates/rename_agent_migration.rb.tt +19 -0
- data/lib/ruby_llm/agents/agent_tool.rb +125 -0
- data/lib/ruby_llm/agents/audio/speaker.rb +5 -3
- data/lib/ruby_llm/agents/audio/speech_pricing.rb +63 -187
- data/lib/ruby_llm/agents/audio/transcriber.rb +5 -3
- data/lib/ruby_llm/agents/audio/transcription_pricing.rb +5 -7
- data/lib/ruby_llm/agents/base_agent.rb +144 -5
- data/lib/ruby_llm/agents/core/configuration.rb +178 -53
- data/lib/ruby_llm/agents/core/errors.rb +3 -77
- data/lib/ruby_llm/agents/core/instrumentation.rb +0 -17
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +0 -8
- data/lib/ruby_llm/agents/dsl/queryable.rb +124 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -1
- data/lib/ruby_llm/agents/image/generator/pricing.rb +75 -217
- data/lib/ruby_llm/agents/image/generator.rb +5 -3
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +8 -0
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +4 -2
- data/lib/ruby_llm/agents/pipeline/builder.rb +43 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +11 -1
- data/lib/ruby_llm/agents/pipeline/executor.rb +1 -25
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +26 -1
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +18 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +130 -3
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +29 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +11 -4
- data/lib/ruby_llm/agents/pipeline.rb +0 -92
- data/lib/ruby_llm/agents/results/background_removal_result.rb +11 -1
- data/lib/ruby_llm/agents/results/base.rb +23 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +14 -1
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_edit_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_generation_result.rb +12 -3
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_transform_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_variation_result.rb +11 -1
- data/lib/ruby_llm/agents/results/speech_result.rb +20 -1
- data/lib/ruby_llm/agents/results/transcription_result.rb +20 -1
- data/lib/ruby_llm/agents/text/embedder.rb +23 -18
- data/lib/ruby_llm/agents.rb +70 -5
- data/lib/tasks/ruby_llm_agents.rake +21 -0
- metadata +7 -6
- data/lib/ruby_llm/agents/infrastructure/reliability/breaker_manager.rb +0 -80
- data/lib/ruby_llm/agents/infrastructure/reliability/execution_constraints.rb +0 -69
- data/lib/ruby_llm/agents/infrastructure/reliability/executor.rb +0 -125
- data/lib/ruby_llm/agents/infrastructure/reliability/fallback_routing.rb +0 -72
- 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
|
|
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
|
-
#
|
|
405
|
+
# Look up pricing from the model registry (same approach as BaseAgent)
|
|
404
406
|
tokens = response.input_tokens || 0
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
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
|
data/lib/ruby_llm/agents.rb
CHANGED
|
@@ -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.
|
|
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
|