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
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
module Reliability
|
|
6
|
-
# Coordinates reliability features during agent execution
|
|
7
|
-
#
|
|
8
|
-
# Orchestrates retry strategy, fallback routing, circuit breakers,
|
|
9
|
-
# and execution constraints into a cohesive execution flow.
|
|
10
|
-
#
|
|
11
|
-
# @example
|
|
12
|
-
# executor = Executor.new(
|
|
13
|
-
# config: { retries: { max: 3 }, fallback_models: ["gpt-4o-mini"] },
|
|
14
|
-
# primary_model: "gpt-4o",
|
|
15
|
-
# agent_type: "MyAgent"
|
|
16
|
-
# )
|
|
17
|
-
# executor.execute { |model| call_llm(model) }
|
|
18
|
-
#
|
|
19
|
-
# @api private
|
|
20
|
-
class Executor
|
|
21
|
-
attr_reader :retry_strategy, :fallback_routing, :breaker_manager, :constraints
|
|
22
|
-
|
|
23
|
-
# @param config [Hash] Reliability configuration
|
|
24
|
-
# @param primary_model [String] Primary model identifier
|
|
25
|
-
# @param agent_type [String] Agent class name
|
|
26
|
-
# @param tenant_id [String, nil] Optional tenant identifier
|
|
27
|
-
def initialize(config:, primary_model:, agent_type:, tenant_id: nil)
|
|
28
|
-
retries_config = config[:retries] || {}
|
|
29
|
-
|
|
30
|
-
@retry_strategy = RetryStrategy.new(
|
|
31
|
-
max: retries_config[:max] || 0,
|
|
32
|
-
backoff: retries_config[:backoff] || :exponential,
|
|
33
|
-
base: retries_config[:base] || 0.4,
|
|
34
|
-
max_delay: retries_config[:max_delay] || 3.0,
|
|
35
|
-
on: retries_config[:on] || [],
|
|
36
|
-
patterns: config[:retryable_patterns]
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
@fallback_routing = FallbackRouting.new(
|
|
40
|
-
primary_model,
|
|
41
|
-
fallback_models: config[:fallback_models] || []
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
@breaker_manager = BreakerManager.new(
|
|
45
|
-
agent_type,
|
|
46
|
-
config: config[:circuit_breaker],
|
|
47
|
-
tenant_id: tenant_id
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
@constraints = ExecutionConstraints.new(
|
|
51
|
-
total_timeout: config[:total_timeout]
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
@last_error = nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Returns all models that will be tried
|
|
58
|
-
#
|
|
59
|
-
# @return [Array<String>] Model identifiers
|
|
60
|
-
def models_to_try
|
|
61
|
-
fallback_routing.models
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Executes with full reliability support
|
|
65
|
-
#
|
|
66
|
-
# Iterates through models with retries, respecting circuit breakers
|
|
67
|
-
# and timeout constraints.
|
|
68
|
-
#
|
|
69
|
-
# @yield [model] Block to execute with the current model
|
|
70
|
-
# @yieldparam model [String] The model to use for this attempt
|
|
71
|
-
# @return [Object] Result of successful execution
|
|
72
|
-
# @raise [AllModelsExhaustedError] If all models fail
|
|
73
|
-
# @raise [TotalTimeoutError] If total timeout exceeded
|
|
74
|
-
def execute
|
|
75
|
-
until fallback_routing.exhausted?
|
|
76
|
-
model = fallback_routing.current_model
|
|
77
|
-
|
|
78
|
-
# Check circuit breaker
|
|
79
|
-
if breaker_manager.open?(model)
|
|
80
|
-
fallback_routing.advance!
|
|
81
|
-
next
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Try with retries
|
|
85
|
-
result = execute_with_retries(model) { |m| yield(m) }
|
|
86
|
-
return result if result
|
|
87
|
-
|
|
88
|
-
fallback_routing.advance!
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
raise AllModelsExhaustedError.new(
|
|
92
|
-
fallback_routing.models,
|
|
93
|
-
@last_error || StandardError.new("All models failed")
|
|
94
|
-
)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
private
|
|
98
|
-
|
|
99
|
-
def execute_with_retries(model)
|
|
100
|
-
attempt_index = 0
|
|
101
|
-
|
|
102
|
-
loop do
|
|
103
|
-
constraints.enforce_timeout!
|
|
104
|
-
|
|
105
|
-
begin
|
|
106
|
-
result = yield(model)
|
|
107
|
-
breaker_manager.record_success!(model)
|
|
108
|
-
return result
|
|
109
|
-
rescue => e
|
|
110
|
-
@last_error = e
|
|
111
|
-
breaker_manager.record_failure!(model)
|
|
112
|
-
|
|
113
|
-
if retry_strategy.retryable?(e) && retry_strategy.should_retry?(attempt_index)
|
|
114
|
-
attempt_index += 1
|
|
115
|
-
sleep(retry_strategy.delay_for(attempt_index))
|
|
116
|
-
else
|
|
117
|
-
return nil # Move to next model
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
module Reliability
|
|
6
|
-
# Routes execution through fallback models when primary fails
|
|
7
|
-
#
|
|
8
|
-
# Manages the model fallback chain and tracks which models have been tried.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# routing = FallbackRouting.new("gpt-4o", fallback_models: ["gpt-4o-mini"])
|
|
12
|
-
# routing.current_model # => "gpt-4o"
|
|
13
|
-
# routing.advance! # => "gpt-4o-mini"
|
|
14
|
-
# routing.exhausted? # => false
|
|
15
|
-
#
|
|
16
|
-
# @api private
|
|
17
|
-
class FallbackRouting
|
|
18
|
-
attr_reader :models
|
|
19
|
-
|
|
20
|
-
# @param primary_model [String] The primary model identifier
|
|
21
|
-
# @param fallback_models [Array<String>] Fallback model identifiers
|
|
22
|
-
def initialize(primary_model, fallback_models: [])
|
|
23
|
-
@models = [primary_model, *fallback_models].uniq
|
|
24
|
-
@current_index = 0
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Returns the current model to try
|
|
28
|
-
#
|
|
29
|
-
# @return [String, nil] Model identifier or nil if exhausted
|
|
30
|
-
def current_model
|
|
31
|
-
models[@current_index]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Advances to the next fallback model
|
|
35
|
-
#
|
|
36
|
-
# @return [String, nil] Next model or nil if exhausted
|
|
37
|
-
def advance!
|
|
38
|
-
@current_index += 1
|
|
39
|
-
current_model
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Checks if more models are available after current
|
|
43
|
-
#
|
|
44
|
-
# @return [Boolean] true if more models to try
|
|
45
|
-
def has_more?
|
|
46
|
-
@current_index < models.length - 1
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Checks if all models have been exhausted
|
|
50
|
-
#
|
|
51
|
-
# @return [Boolean] true if no more models
|
|
52
|
-
def exhausted?
|
|
53
|
-
@current_index >= models.length
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Resets to the first model
|
|
57
|
-
#
|
|
58
|
-
# @return [void]
|
|
59
|
-
def reset!
|
|
60
|
-
@current_index = 0
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Returns models that have been tried so far
|
|
64
|
-
#
|
|
65
|
-
# @return [Array<String>] Models already attempted
|
|
66
|
-
def tried_models
|
|
67
|
-
models[0..@current_index]
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
module Reliability
|
|
6
|
-
# Handles retry logic with configurable backoff strategies
|
|
7
|
-
#
|
|
8
|
-
# Provides exponential and constant backoff with jitter,
|
|
9
|
-
# retry counting, and delay calculation.
|
|
10
|
-
#
|
|
11
|
-
# @example
|
|
12
|
-
# strategy = RetryStrategy.new(max: 3, backoff: :exponential, base: 0.4)
|
|
13
|
-
# strategy.should_retry?(attempt_index) # => true/false
|
|
14
|
-
# strategy.delay_for(attempt_index) # => 0.6 (with jitter)
|
|
15
|
-
#
|
|
16
|
-
# @api private
|
|
17
|
-
class RetryStrategy
|
|
18
|
-
attr_reader :max, :backoff, :base, :max_delay, :custom_errors, :custom_patterns
|
|
19
|
-
|
|
20
|
-
# @param max [Integer] Maximum retry attempts
|
|
21
|
-
# @param backoff [Symbol] :constant or :exponential
|
|
22
|
-
# @param base [Float] Base delay in seconds
|
|
23
|
-
# @param max_delay [Float] Maximum delay cap
|
|
24
|
-
# @param on [Array<Class>] Additional error classes to retry on
|
|
25
|
-
# @param patterns [Array<String>, nil] Additional patterns to match in error messages
|
|
26
|
-
def initialize(max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [], patterns: nil)
|
|
27
|
-
@max = max
|
|
28
|
-
@backoff = backoff
|
|
29
|
-
@base = base
|
|
30
|
-
@max_delay = max_delay
|
|
31
|
-
@custom_errors = Array(on)
|
|
32
|
-
@custom_patterns = patterns
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Determines if retry should occur
|
|
36
|
-
#
|
|
37
|
-
# @param attempt_index [Integer] Current attempt number (0-indexed)
|
|
38
|
-
# @return [Boolean] true if should retry
|
|
39
|
-
def should_retry?(attempt_index)
|
|
40
|
-
attempt_index < max
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Calculates delay before next retry
|
|
44
|
-
#
|
|
45
|
-
# @param attempt_index [Integer] Current attempt number
|
|
46
|
-
# @return [Float] Delay in seconds (includes jitter)
|
|
47
|
-
def delay_for(attempt_index)
|
|
48
|
-
base_delay = case backoff
|
|
49
|
-
when :constant
|
|
50
|
-
base
|
|
51
|
-
when :exponential
|
|
52
|
-
[base * (2**attempt_index), max_delay].min
|
|
53
|
-
else
|
|
54
|
-
base
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Add jitter (0-50% of base delay)
|
|
58
|
-
base_delay + (rand * base_delay * 0.5)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Checks if an error is retryable
|
|
62
|
-
#
|
|
63
|
-
# @param error [Exception] The error to check
|
|
64
|
-
# @return [Boolean] true if retryable
|
|
65
|
-
def retryable?(error)
|
|
66
|
-
RubyLLM::Agents::Reliability.retryable_error?(
|
|
67
|
-
error,
|
|
68
|
-
custom_errors: custom_errors,
|
|
69
|
-
custom_patterns: custom_patterns
|
|
70
|
-
)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Returns all retryable error classes
|
|
74
|
-
#
|
|
75
|
-
# @return [Array<Class>] Error classes to retry on
|
|
76
|
-
def retryable_errors
|
|
77
|
-
RubyLLM::Agents::Reliability.default_retryable_errors + custom_errors
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|