ruby_llm-agents 3.0.0 → 3.2.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 +1 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +16 -14
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +20 -20
- data/app/controllers/ruby_llm/agents/executions_controller.rb +5 -7
- data/app/helpers/ruby_llm/agents/application_helper.rb +57 -58
- data/app/models/ruby_llm/agents/execution/analytics.rb +27 -27
- data/app/models/ruby_llm/agents/execution/scopes.rb +4 -6
- data/app/models/ruby_llm/agents/execution.rb +26 -26
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +16 -10
- data/app/models/ruby_llm/agents/tenant/resettable.rb +12 -12
- data/app/models/ruby_llm/agents/tenant/trackable.rb +7 -7
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -6
- data/app/views/layouts/ruby_llm/agents/application.html.erb +142 -11
- data/app/views/ruby_llm/agents/agents/show.html.erb +10 -10
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +10 -10
- data/app/views/ruby_llm/agents/executions/show.html.erb +13 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -7
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +9 -9
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/install_generator.rb +3 -3
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +13 -13
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/templates/add_assistant_prompt_migration.rb.tt +9 -0
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +2 -1
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +22 -3
- data/lib/ruby_llm/agents/audio/speaker.rb +40 -31
- data/lib/ruby_llm/agents/audio/speech_client.rb +328 -0
- data/lib/ruby_llm/agents/audio/speech_pricing.rb +273 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +33 -33
- data/lib/ruby_llm/agents/base_agent.rb +16 -15
- data/lib/ruby_llm/agents/core/base/callbacks.rb +3 -3
- data/lib/ruby_llm/agents/core/configuration.rb +86 -73
- data/lib/ruby_llm/agents/core/errors.rb +27 -2
- data/lib/ruby_llm/agents/core/instrumentation.rb +101 -65
- data/lib/ruby_llm/agents/core/llm_tenant.rb +7 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +3 -3
- data/lib/ruby_llm/agents/dsl/reliability.rb +9 -9
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +1 -1
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +4 -4
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +1 -1
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +3 -3
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +8 -8
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -1
- data/lib/ruby_llm/agents/image/generator/pricing.rb +9 -10
- data/lib/ruby_llm/agents/image/generator.rb +6 -6
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +6 -6
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +9 -9
- data/lib/ruby_llm/agents/image/pipeline.rb +1 -1
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -1
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +1 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +3 -5
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +4 -4
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +4 -4
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +9 -9
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +3 -3
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +17 -17
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +1 -0
- data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/reliability.rb +6 -6
- data/lib/ruby_llm/agents/pipeline/builder.rb +11 -11
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +3 -3
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +4 -4
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +62 -21
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +2 -3
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +82 -4
- data/lib/ruby_llm/agents/results/background_removal_result.rb +6 -6
- data/lib/ruby_llm/agents/results/embedding_result.rb +15 -15
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +7 -7
- data/lib/ruby_llm/agents/results/image_edit_result.rb +4 -4
- data/lib/ruby_llm/agents/results/image_generation_result.rb +5 -5
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +4 -4
- data/lib/ruby_llm/agents/results/image_transform_result.rb +4 -4
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +5 -5
- data/lib/ruby_llm/agents/results/image_variation_result.rb +4 -4
- data/lib/ruby_llm/agents/results/transcription_result.rb +1 -1
- data/lib/ruby_llm/agents/text/embedder.rb +13 -13
- metadata +4 -1
|
@@ -63,48 +63,46 @@ module RubyLLM
|
|
|
63
63
|
# Use catch to intercept successful early returns from the block
|
|
64
64
|
# The block uses `throw :execution_success, result` instead of `return`
|
|
65
65
|
result = catch(:execution_success) do
|
|
66
|
+
yield(attempt_tracker)
|
|
67
|
+
# If we reach here normally (no throw), the block completed without success
|
|
68
|
+
# This happens when AllModelsExhaustedError is raised
|
|
69
|
+
nil
|
|
70
|
+
rescue Timeout::Error, Reliability::TotalTimeoutError => e
|
|
71
|
+
raised_exception = e
|
|
66
72
|
begin
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
raise
|
|
101
|
-
ensure
|
|
102
|
-
# Only run emergency fallback if we haven't completed AND we're not in success path
|
|
103
|
-
# The success path completion happens AFTER the catch block
|
|
104
|
-
unless @status_update_completed || !$!
|
|
105
|
-
actual_error = completion_error || raised_exception || $!
|
|
106
|
-
mark_execution_failed!(execution, error: actual_error)
|
|
107
|
-
end
|
|
73
|
+
complete_execution_with_attempts(
|
|
74
|
+
execution,
|
|
75
|
+
attempt_tracker: attempt_tracker,
|
|
76
|
+
completed_at: Time.current,
|
|
77
|
+
status: "timeout",
|
|
78
|
+
error: e
|
|
79
|
+
)
|
|
80
|
+
@status_update_completed = true
|
|
81
|
+
rescue => completion_err
|
|
82
|
+
completion_error = completion_err
|
|
83
|
+
end
|
|
84
|
+
raise
|
|
85
|
+
rescue => e
|
|
86
|
+
raised_exception = e
|
|
87
|
+
begin
|
|
88
|
+
complete_execution_with_attempts(
|
|
89
|
+
execution,
|
|
90
|
+
attempt_tracker: attempt_tracker,
|
|
91
|
+
completed_at: Time.current,
|
|
92
|
+
status: "error",
|
|
93
|
+
error: e
|
|
94
|
+
)
|
|
95
|
+
@status_update_completed = true
|
|
96
|
+
rescue => completion_err
|
|
97
|
+
completion_error = completion_err
|
|
98
|
+
end
|
|
99
|
+
raise
|
|
100
|
+
ensure
|
|
101
|
+
# Only run emergency fallback if we haven't completed AND we're not in success path
|
|
102
|
+
# The success path completion happens AFTER the catch block
|
|
103
|
+
unless @status_update_completed || !$!
|
|
104
|
+
actual_error = completion_error || raised_exception || $!
|
|
105
|
+
mark_execution_failed!(execution, error: actual_error)
|
|
108
106
|
end
|
|
109
107
|
end
|
|
110
108
|
|
|
@@ -119,7 +117,7 @@ module RubyLLM
|
|
|
119
117
|
status: "success"
|
|
120
118
|
)
|
|
121
119
|
@status_update_completed = true
|
|
122
|
-
rescue
|
|
120
|
+
rescue => e
|
|
123
121
|
Rails.logger.error("[RubyLLM::Agents] Failed to complete successful execution: #{e.class}: #{e.message}")
|
|
124
122
|
mark_execution_failed!(execution, error: e)
|
|
125
123
|
end
|
|
@@ -165,7 +163,7 @@ module RubyLLM
|
|
|
165
163
|
response: @last_response
|
|
166
164
|
)
|
|
167
165
|
@status_update_completed = true
|
|
168
|
-
rescue
|
|
166
|
+
rescue => e
|
|
169
167
|
completion_error = e
|
|
170
168
|
# Don't re-raise - let ensure block handle via mark_execution_failed!
|
|
171
169
|
end
|
|
@@ -181,11 +179,11 @@ module RubyLLM
|
|
|
181
179
|
error: e
|
|
182
180
|
)
|
|
183
181
|
@status_update_completed = true
|
|
184
|
-
rescue
|
|
182
|
+
rescue => completion_err
|
|
185
183
|
completion_error = completion_err
|
|
186
184
|
end
|
|
187
185
|
raise
|
|
188
|
-
rescue
|
|
186
|
+
rescue => e
|
|
189
187
|
raised_exception = e
|
|
190
188
|
begin
|
|
191
189
|
complete_execution(
|
|
@@ -195,7 +193,7 @@ module RubyLLM
|
|
|
195
193
|
error: e
|
|
196
194
|
)
|
|
197
195
|
@status_update_completed = true
|
|
198
|
-
rescue
|
|
196
|
+
rescue => completion_err
|
|
199
197
|
completion_error = completion_err
|
|
200
198
|
end
|
|
201
199
|
raise
|
|
@@ -258,7 +256,7 @@ module RubyLLM
|
|
|
258
256
|
# Add fallback chain tracking (count only on execution, chain stored in detail)
|
|
259
257
|
if fallback_chain.any?
|
|
260
258
|
execution_data[:attempts_count] = 0
|
|
261
|
-
@_pending_detail_data = {
|
|
259
|
+
@_pending_detail_data = {fallback_chain: fallback_chain, attempts: []}
|
|
262
260
|
end
|
|
263
261
|
|
|
264
262
|
# Add tenant_id if multi-tenancy is enabled
|
|
@@ -275,6 +273,9 @@ module RubyLLM
|
|
|
275
273
|
system_prompt: config.persist_prompts ? stored_system_prompt : nil,
|
|
276
274
|
user_prompt: config.persist_prompts ? stored_user_prompt : nil
|
|
277
275
|
}
|
|
276
|
+
if config.persist_prompts && assistant_prompt_column_exists?
|
|
277
|
+
detail_data[:assistant_prompt] = stored_assistant_prompt
|
|
278
|
+
end
|
|
278
279
|
detail_data.merge!(@_pending_detail_data) if @_pending_detail_data
|
|
279
280
|
@_pending_detail_data = nil
|
|
280
281
|
|
|
@@ -282,7 +283,7 @@ module RubyLLM
|
|
|
282
283
|
execution.create_detail!(detail_data) if has_data
|
|
283
284
|
|
|
284
285
|
execution
|
|
285
|
-
rescue
|
|
286
|
+
rescue => e
|
|
286
287
|
# Log error but don't fail the agent execution itself
|
|
287
288
|
Rails.logger.error("[RubyLLM::Agents] Failed to create execution record: #{e.message}")
|
|
288
289
|
nil
|
|
@@ -353,7 +354,7 @@ module RubyLLM
|
|
|
353
354
|
begin
|
|
354
355
|
execution.calculate_costs!
|
|
355
356
|
execution.save!
|
|
356
|
-
rescue
|
|
357
|
+
rescue => cost_error
|
|
357
358
|
Rails.logger.warn("[RubyLLM::Agents] Cost calculation failed: #{cost_error.message}")
|
|
358
359
|
end
|
|
359
360
|
end
|
|
@@ -361,12 +362,12 @@ module RubyLLM
|
|
|
361
362
|
# Record token usage for budget tracking
|
|
362
363
|
record_token_usage(execution)
|
|
363
364
|
rescue ActiveRecord::RecordInvalid => e
|
|
364
|
-
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(
|
|
365
|
+
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(", ")}")
|
|
365
366
|
if Rails.env.development? || Rails.env.test?
|
|
366
367
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
367
368
|
end
|
|
368
369
|
raise
|
|
369
|
-
rescue
|
|
370
|
+
rescue => e
|
|
370
371
|
Rails.logger.error("[RubyLLM::Agents] Failed to update execution record #{execution&.id}: #{e.class}: #{e.message}")
|
|
371
372
|
if Rails.env.development? || Rails.env.test?
|
|
372
373
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
@@ -462,7 +463,7 @@ module RubyLLM
|
|
|
462
463
|
begin
|
|
463
464
|
execution.aggregate_attempt_costs!
|
|
464
465
|
execution.save!
|
|
465
|
-
rescue
|
|
466
|
+
rescue => cost_error
|
|
466
467
|
Rails.logger.warn("[RubyLLM::Agents] Cost calculation failed: #{cost_error.message}")
|
|
467
468
|
end
|
|
468
469
|
end
|
|
@@ -470,12 +471,12 @@ module RubyLLM
|
|
|
470
471
|
# Record token usage for budget tracking
|
|
471
472
|
record_token_usage(execution)
|
|
472
473
|
rescue ActiveRecord::RecordInvalid => e
|
|
473
|
-
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(
|
|
474
|
+
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(", ")}")
|
|
474
475
|
if Rails.env.development? || Rails.env.test?
|
|
475
476
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
476
477
|
end
|
|
477
478
|
raise
|
|
478
|
-
rescue
|
|
479
|
+
rescue => e
|
|
479
480
|
Rails.logger.error("[RubyLLM::Agents] Failed to update execution record #{execution&.id}: #{e.class}: #{e.message}")
|
|
480
481
|
if Rails.env.development? || Rails.env.test?
|
|
481
482
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
@@ -528,7 +529,9 @@ module RubyLLM
|
|
|
528
529
|
user_prompt: safe_user_prompt,
|
|
529
530
|
messages_summary: config.persist_messages_summary ? messages_summary : {},
|
|
530
531
|
error_message: error&.message
|
|
531
|
-
}
|
|
532
|
+
}
|
|
533
|
+
detail_data[:assistant_prompt] = safe_assistant_prompt if assistant_prompt_column_exists?
|
|
534
|
+
detail_data.merge!(detail_fields || {})
|
|
532
535
|
|
|
533
536
|
execution_data[:_detail_data] = detail_data
|
|
534
537
|
|
|
@@ -619,7 +622,7 @@ module RubyLLM
|
|
|
619
622
|
# @return [String, nil] The system prompt or nil if unavailable
|
|
620
623
|
def safe_system_prompt
|
|
621
624
|
respond_to?(:system_prompt) ? system_prompt.to_s : nil
|
|
622
|
-
rescue
|
|
625
|
+
rescue => e
|
|
623
626
|
Rails.logger.warn("[RubyLLM::Agents] Could not capture system_prompt: #{e.message}")
|
|
624
627
|
nil
|
|
625
628
|
end
|
|
@@ -629,11 +632,28 @@ module RubyLLM
|
|
|
629
632
|
# @return [String, nil] The user prompt or nil if unavailable
|
|
630
633
|
def safe_user_prompt
|
|
631
634
|
respond_to?(:user_prompt) ? user_prompt.to_s : nil
|
|
632
|
-
rescue
|
|
635
|
+
rescue => e
|
|
633
636
|
Rails.logger.warn("[RubyLLM::Agents] Could not capture user_prompt: #{e.message}")
|
|
634
637
|
nil
|
|
635
638
|
end
|
|
636
639
|
|
|
640
|
+
# Safely captures assistant prompt, handling errors gracefully
|
|
641
|
+
#
|
|
642
|
+
# @return [String, nil] The assistant prompt or nil if unavailable
|
|
643
|
+
def safe_assistant_prompt
|
|
644
|
+
respond_to?(:assistant_prompt) ? assistant_prompt&.to_s : nil
|
|
645
|
+
rescue => e
|
|
646
|
+
Rails.logger.warn("[RubyLLM::Agents] Could not capture assistant_prompt: #{e.message}")
|
|
647
|
+
nil
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# Returns the assistant prompt for storage
|
|
651
|
+
#
|
|
652
|
+
# @return [String, nil] The assistant prompt
|
|
653
|
+
def stored_assistant_prompt
|
|
654
|
+
safe_assistant_prompt
|
|
655
|
+
end
|
|
656
|
+
|
|
637
657
|
# Safely extracts a value from response object
|
|
638
658
|
#
|
|
639
659
|
# @param response [Object] The response object
|
|
@@ -643,7 +663,7 @@ module RubyLLM
|
|
|
643
663
|
def safe_response_value(response, method, default = nil)
|
|
644
664
|
return default unless response.respond_to?(method)
|
|
645
665
|
response.public_send(method)
|
|
646
|
-
rescue
|
|
666
|
+
rescue
|
|
647
667
|
default
|
|
648
668
|
end
|
|
649
669
|
|
|
@@ -680,7 +700,7 @@ module RubyLLM
|
|
|
680
700
|
# @return [String, nil] Normalized finish reason
|
|
681
701
|
def safe_extract_finish_reason(response)
|
|
682
702
|
reason = safe_response_value(response, :finish_reason) ||
|
|
683
|
-
|
|
703
|
+
safe_response_value(response, :stop_reason)
|
|
684
704
|
return nil unless reason
|
|
685
705
|
|
|
686
706
|
# Normalize to standard values
|
|
@@ -828,7 +848,7 @@ module RubyLLM
|
|
|
828
848
|
if tool_call.respond_to?(:to_h)
|
|
829
849
|
tool_call.to_h
|
|
830
850
|
else
|
|
831
|
-
{
|
|
851
|
+
{id: id, name: tool_call[:name], arguments: tool_call[:arguments]}
|
|
832
852
|
end
|
|
833
853
|
end
|
|
834
854
|
end
|
|
@@ -897,7 +917,7 @@ module RubyLLM
|
|
|
897
917
|
}
|
|
898
918
|
execution.create_detail!(detail_data) if detail_data.values.any?(&:present?)
|
|
899
919
|
end
|
|
900
|
-
rescue
|
|
920
|
+
rescue => e
|
|
901
921
|
Rails.logger.error("[RubyLLM::Agents] Failed to record cache hit execution: #{e.message}")
|
|
902
922
|
end
|
|
903
923
|
|
|
@@ -918,11 +938,27 @@ module RubyLLM
|
|
|
918
938
|
tenant_id: tenant_id,
|
|
919
939
|
tenant_config: tenant_config
|
|
920
940
|
)
|
|
921
|
-
rescue
|
|
941
|
+
rescue => e
|
|
922
942
|
Rails.logger.warn("[RubyLLM::Agents] Failed to record token usage: #{e.message}")
|
|
923
943
|
end
|
|
924
944
|
end
|
|
925
945
|
|
|
946
|
+
# Checks if the assistant_prompt column exists on execution_details
|
|
947
|
+
#
|
|
948
|
+
# Memoized to avoid repeated schema queries. Returns false for older installs
|
|
949
|
+
# that haven't run the migration yet.
|
|
950
|
+
#
|
|
951
|
+
# @return [Boolean] true if the column exists
|
|
952
|
+
def assistant_prompt_column_exists?
|
|
953
|
+
return @_assistant_prompt_column_exists if defined?(@_assistant_prompt_column_exists)
|
|
954
|
+
|
|
955
|
+
@_assistant_prompt_column_exists = begin
|
|
956
|
+
RubyLLM::Agents::ExecutionDetail.column_names.include?("assistant_prompt")
|
|
957
|
+
rescue
|
|
958
|
+
false
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
|
|
926
962
|
# Emergency fallback to mark execution as failed
|
|
927
963
|
#
|
|
928
964
|
# Uses update_all to bypass ActiveRecord callbacks and validations,
|
|
@@ -963,16 +999,16 @@ module RubyLLM
|
|
|
963
999
|
|
|
964
1000
|
# Store error_message in detail table (best-effort)
|
|
965
1001
|
begin
|
|
966
|
-
detail_attrs = {
|
|
1002
|
+
detail_attrs = {error_message: error_message.to_s.truncate(65535)}
|
|
967
1003
|
if execution.detail
|
|
968
1004
|
execution.detail.update_columns(detail_attrs)
|
|
969
1005
|
else
|
|
970
1006
|
RubyLLM::Agents::ExecutionDetail.create!(detail_attrs.merge(execution_id: execution.id))
|
|
971
1007
|
end
|
|
972
|
-
rescue
|
|
1008
|
+
rescue
|
|
973
1009
|
# Non-critical — error_class on execution is sufficient for filtering
|
|
974
1010
|
end
|
|
975
|
-
rescue
|
|
1011
|
+
rescue => e
|
|
976
1012
|
Rails.logger.error("[RubyLLM::Agents] CRITICAL: Failed emergency status update for execution #{execution&.id}: #{e.message}")
|
|
977
1013
|
end
|
|
978
1014
|
end
|
|
@@ -70,9 +70,9 @@ module RubyLLM
|
|
|
70
70
|
included do
|
|
71
71
|
# Link to gem's Tenant model via polymorphic association
|
|
72
72
|
has_one :llm_tenant_record,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
class_name: "RubyLLM::Agents::Tenant",
|
|
74
|
+
as: :tenant_record,
|
|
75
|
+
dependent: :destroy
|
|
76
76
|
|
|
77
77
|
# Backward compatible alias (llm_budget points to same Tenant record)
|
|
78
78
|
# @deprecated Use llm_tenant_record instead
|
|
@@ -106,10 +106,10 @@ module RubyLLM
|
|
|
106
106
|
|
|
107
107
|
# Executions tracked for this tenant via tenant_id string column
|
|
108
108
|
has_many :llm_executions,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
class_name: "RubyLLM::Agents::Execution",
|
|
110
|
+
foreign_key: :tenant_id,
|
|
111
|
+
primary_key: id,
|
|
112
|
+
dependent: :nullify
|
|
113
113
|
|
|
114
114
|
# Auto-create tenant record callback
|
|
115
115
|
after_create :create_default_llm_tenant if llm_tenant_options[:budget]
|
|
@@ -56,7 +56,7 @@ module RubyLLM
|
|
|
56
56
|
#
|
|
57
57
|
module Base
|
|
58
58
|
# Regex pattern to extract {placeholder} parameters from prompt strings
|
|
59
|
-
PLACEHOLDER_PATTERN = /\{(\w+)\}
|
|
59
|
+
PLACEHOLDER_PATTERN = /\{(\w+)\}/
|
|
60
60
|
|
|
61
61
|
# @!group Configuration DSL
|
|
62
62
|
|
|
@@ -306,7 +306,7 @@ module RubyLLM
|
|
|
306
306
|
# @return [String] The default model
|
|
307
307
|
def default_model
|
|
308
308
|
RubyLLM::Agents.configuration.default_model
|
|
309
|
-
rescue
|
|
309
|
+
rescue
|
|
310
310
|
"gpt-4o"
|
|
311
311
|
end
|
|
312
312
|
|
|
@@ -315,7 +315,7 @@ module RubyLLM
|
|
|
315
315
|
# @return [Integer] The default timeout
|
|
316
316
|
def default_timeout
|
|
317
317
|
RubyLLM::Agents.configuration.default_timeout
|
|
318
|
-
rescue
|
|
318
|
+
rescue
|
|
319
319
|
120
|
|
320
320
|
end
|
|
321
321
|
end
|
|
@@ -168,7 +168,7 @@ module RubyLLM
|
|
|
168
168
|
def fallback_provider(provider = nil, **options)
|
|
169
169
|
if provider
|
|
170
170
|
@fallback_providers ||= []
|
|
171
|
-
@fallback_providers << {
|
|
171
|
+
@fallback_providers << {provider: provider, **options}
|
|
172
172
|
end
|
|
173
173
|
@fallback_providers || inherited_fallback_providers || []
|
|
174
174
|
end
|
|
@@ -201,7 +201,7 @@ module RubyLLM
|
|
|
201
201
|
# circuit_breaker errors: 10, within: 60, cooldown: 300
|
|
202
202
|
def circuit_breaker(errors: nil, within: nil, cooldown: nil)
|
|
203
203
|
if errors || within || cooldown
|
|
204
|
-
@circuit_breaker_config ||= {
|
|
204
|
+
@circuit_breaker_config ||= {errors: 10, within: 60, cooldown: 300}
|
|
205
205
|
@circuit_breaker_config[:errors] = errors if errors
|
|
206
206
|
@circuit_breaker_config[:within] = within if within
|
|
207
207
|
@circuit_breaker_config[:cooldown] = cooldown if cooldown
|
|
@@ -297,8 +297,8 @@ module RubyLLM
|
|
|
297
297
|
# Inner builder class for block-style configuration
|
|
298
298
|
class ReliabilityBuilder
|
|
299
299
|
attr_reader :retries_config, :fallback_models_list, :total_timeout_value,
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
:circuit_breaker_config, :retryable_patterns_list, :fallback_providers_list,
|
|
301
|
+
:non_fallback_errors_list
|
|
302
302
|
|
|
303
303
|
def initialize
|
|
304
304
|
@retries_config = nil
|
|
@@ -332,7 +332,7 @@ module RubyLLM
|
|
|
332
332
|
# fallback_provider :openai, voice: "nova"
|
|
333
333
|
# fallback_provider :elevenlabs, voice: "Rachel", model: "eleven_multilingual_v2"
|
|
334
334
|
def fallback_provider(provider, **options)
|
|
335
|
-
@fallback_providers_list << {
|
|
335
|
+
@fallback_providers_list << {provider: provider, **options}
|
|
336
336
|
end
|
|
337
337
|
|
|
338
338
|
def total_timeout(seconds)
|
|
@@ -366,8 +366,8 @@ module RubyLLM
|
|
|
366
366
|
#
|
|
367
367
|
class OnFailureBuilder
|
|
368
368
|
attr_reader :retries_config, :fallback_models_list, :total_timeout_value,
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
:circuit_breaker_config, :retryable_patterns_list, :fallback_providers_list,
|
|
370
|
+
:non_fallback_errors_list
|
|
371
371
|
|
|
372
372
|
def initialize
|
|
373
373
|
@retries_config = nil
|
|
@@ -423,7 +423,7 @@ module RubyLLM
|
|
|
423
423
|
# @param options [Hash] Provider-specific options
|
|
424
424
|
#
|
|
425
425
|
def fallback_provider(provider, **options)
|
|
426
|
-
@fallback_providers_list << {
|
|
426
|
+
@fallback_providers_list << {provider: provider, **options}
|
|
427
427
|
end
|
|
428
428
|
|
|
429
429
|
# Configure timeout for all retry/fallback attempts
|
|
@@ -440,7 +440,7 @@ module RubyLLM
|
|
|
440
440
|
end
|
|
441
441
|
|
|
442
442
|
# Also support total_timeout for compatibility
|
|
443
|
-
|
|
443
|
+
alias_method :total_timeout, :timeout
|
|
444
444
|
|
|
445
445
|
# Configure circuit breaker
|
|
446
446
|
#
|
|
@@ -31,7 +31,7 @@ module RubyLLM
|
|
|
31
31
|
def analysis_type(value = nil)
|
|
32
32
|
if value
|
|
33
33
|
unless VALID_ANALYSIS_TYPES.include?(value)
|
|
34
|
-
raise ArgumentError, "Analysis type must be one of: #{VALID_ANALYSIS_TYPES.join(
|
|
34
|
+
raise ArgumentError, "Analysis type must be one of: #{VALID_ANALYSIS_TYPES.join(", ")}"
|
|
35
35
|
end
|
|
36
36
|
@analysis_type = value
|
|
37
37
|
else
|
|
@@ -46,7 +46,7 @@ module RubyLLM
|
|
|
46
46
|
record_execution(result) if execution_tracking_enabled?
|
|
47
47
|
|
|
48
48
|
result
|
|
49
|
-
rescue
|
|
49
|
+
rescue => e
|
|
50
50
|
record_failed_execution(e, started_at) if execution_tracking_enabled?
|
|
51
51
|
build_error_result(e, started_at)
|
|
52
52
|
end
|
|
@@ -138,8 +138,8 @@ module RubyLLM
|
|
|
138
138
|
caption: "Brief caption string",
|
|
139
139
|
description: "Detailed description string (if applicable)",
|
|
140
140
|
tags: ["array", "of", "tag", "strings"],
|
|
141
|
-
objects: [{
|
|
142
|
-
colors: [{
|
|
141
|
+
objects: [{name: "object name", location: "position", confidence: "high/medium/low"}],
|
|
142
|
+
colors: [{hex: "#RRGGBB", name: "color name", percentage: 25}],
|
|
143
143
|
text: "Extracted text if any"
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -154,7 +154,7 @@ module RubyLLM
|
|
|
154
154
|
|
|
155
155
|
# Use RubyLLM chat with vision
|
|
156
156
|
chat = RubyLLM.chat(model: model)
|
|
157
|
-
chat.ask(prompt, with: {
|
|
157
|
+
chat.ask(prompt, with: {image: image_content})
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
def prepare_image_content
|
|
@@ -30,7 +30,7 @@ module RubyLLM
|
|
|
30
30
|
def output_format(value = nil)
|
|
31
31
|
if value
|
|
32
32
|
unless VALID_OUTPUT_FORMATS.include?(value)
|
|
33
|
-
raise ArgumentError, "Output format must be one of: #{VALID_OUTPUT_FORMATS.join(
|
|
33
|
+
raise ArgumentError, "Output format must be one of: #{VALID_OUTPUT_FORMATS.join(", ")}"
|
|
34
34
|
end
|
|
35
35
|
@output_format = value
|
|
36
36
|
else
|
|
@@ -46,7 +46,7 @@ module RubyLLM
|
|
|
46
46
|
record_execution(result) if execution_tracking_enabled?
|
|
47
47
|
|
|
48
48
|
result
|
|
49
|
-
rescue
|
|
49
|
+
rescue => e
|
|
50
50
|
record_failed_execution(e, started_at) if execution_tracking_enabled?
|
|
51
51
|
build_error_result(e, started_at)
|
|
52
52
|
end
|
|
@@ -112,13 +112,13 @@ module RubyLLM
|
|
|
112
112
|
mode: "segmentation_mask",
|
|
113
113
|
**build_removal_options
|
|
114
114
|
)
|
|
115
|
-
rescue
|
|
115
|
+
rescue
|
|
116
116
|
# Mask generation failed, continue without it
|
|
117
117
|
mask = nil
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
{
|
|
121
|
+
{foreground: foreground, mask: mask}
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def build_removal_options
|
|
@@ -20,11 +20,11 @@ module RubyLLM
|
|
|
20
20
|
return unless tenant
|
|
21
21
|
|
|
22
22
|
@tenant_id = case tenant
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
when Hash then tenant[:id]
|
|
24
|
+
when Integer, String then tenant
|
|
25
|
+
else
|
|
26
|
+
tenant.try(:llm_tenant_id) || tenant.try(:id)
|
|
27
|
+
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# Check budget before execution
|
|
@@ -87,7 +87,7 @@ module RubyLLM
|
|
|
87
87
|
else
|
|
88
88
|
RubyLLM::Agents::Execution.create!(execution_data)
|
|
89
89
|
end
|
|
90
|
-
rescue
|
|
90
|
+
rescue => e
|
|
91
91
|
Rails.logger.error("[RubyLLM::Agents] Failed to record #{execution_type} execution: #{e.message}") if defined?(Rails)
|
|
92
92
|
end
|
|
93
93
|
|
|
@@ -101,7 +101,7 @@ module RubyLLM
|
|
|
101
101
|
else
|
|
102
102
|
RubyLLM::Agents::Execution.create!(execution_data)
|
|
103
103
|
end
|
|
104
|
-
rescue
|
|
104
|
+
rescue => e
|
|
105
105
|
Rails.logger.error("[RubyLLM::Agents] Failed to record failed #{execution_type} execution: #{e.message}") if defined?(Rails)
|
|
106
106
|
end
|
|
107
107
|
|
|
@@ -142,7 +142,7 @@ module RubyLLM
|
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
def build_metadata(result)
|
|
145
|
-
{
|
|
145
|
+
{count: result.count}
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
def budget_tracking_enabled?
|
|
@@ -158,7 +158,7 @@ module RubyLLM
|
|
|
158
158
|
else
|
|
159
159
|
fetch_from_url
|
|
160
160
|
end
|
|
161
|
-
rescue
|
|
161
|
+
rescue => e
|
|
162
162
|
warn "[RubyLLM::Agents] Failed to fetch LiteLLM pricing: #{e.message}"
|
|
163
163
|
{}
|
|
164
164
|
end
|
|
@@ -178,7 +178,7 @@ module RubyLLM
|
|
|
178
178
|
else
|
|
179
179
|
{}
|
|
180
180
|
end
|
|
181
|
-
rescue
|
|
181
|
+
rescue => e
|
|
182
182
|
warn "[RubyLLM::Agents] HTTP error fetching LiteLLM pricing: #{e.message}"
|
|
183
183
|
{}
|
|
184
184
|
end
|
|
@@ -314,9 +314,9 @@ module RubyLLM
|
|
|
314
314
|
|
|
315
315
|
def fallback_pricing_table
|
|
316
316
|
{
|
|
317
|
-
"gpt-image-1" => {
|
|
318
|
-
"dall-e-3" => {
|
|
319
|
-
"dall-e-2" => {
|
|
317
|
+
"gpt-image-1" => {standard: 0.04, hd: 0.08, large_hd: 0.12},
|
|
318
|
+
"dall-e-3" => {standard: 0.04, hd: 0.08, large_hd: 0.12},
|
|
319
|
+
"dall-e-2" => {"1024x1024" => 0.02, "512x512" => 0.018, "256x256" => 0.016},
|
|
320
320
|
"flux-pro" => 0.05,
|
|
321
321
|
"flux-dev" => 0.025,
|
|
322
322
|
"flux-schnell" => 0.003,
|
|
@@ -332,16 +332,15 @@ module RubyLLM
|
|
|
332
332
|
width, height = size.to_s.split("x").map(&:to_i)
|
|
333
333
|
return nil if width.zero? || height.zero?
|
|
334
334
|
width * height
|
|
335
|
-
rescue
|
|
335
|
+
rescue
|
|
336
336
|
nil
|
|
337
337
|
end
|
|
338
338
|
|
|
339
339
|
def normalize_model_id(model_id)
|
|
340
340
|
model_id.to_s
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
.gsub(/^-|-$/, "")
|
|
341
|
+
.downcase
|
|
342
|
+
.gsub(/[^a-z0-9.-]/, "-").squeeze("-")
|
|
343
|
+
.gsub(/^-|-$/, "")
|
|
345
344
|
end
|
|
346
345
|
|
|
347
346
|
def config
|